Initial Imap2 implementation
This CL includes the following: * New Imap2.apk generation (not included in builds) * "Push IMAP" option for accounts when Imap2.apk present * Account creation/setup * 2-way sync of messages, deletions, flag updates * Push (messages, flags) * Folder list hierarchy handling * Message text (one plain or html part) * Picker UI for trash folder (placeholder) * Capabilities handling/UI command Major Imap2 new features: * Push * Multiple folder sync * Sync window (like EAS) TODO: * Picker UI for sent folder * Upload of sent messages to server * Search * Multiple viewable parts * Probably lots more, incl. unit tests Change-Id: Ia5d74073d9c307e0bdae72a7f76b27140dde7d14
This commit is contained in:
parent
ab076fa633
commit
c6089bc01f
|
@ -198,6 +198,12 @@
|
|||
<meta-data android:name="android.app.searchable" android:resource="@xml/searchable" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".provider.FolderPickerActivity"
|
||||
android:label="@string/folder_picker_title"
|
||||
>
|
||||
</activity>
|
||||
|
||||
<!-- Must be exported in order for the AccountManager to launch it -->
|
||||
<!-- Also available for continuous test systems to force account creation -->
|
||||
<activity
|
||||
|
@ -508,7 +514,20 @@
|
|||
android:resource="@xml/authenticator_alternate"
|
||||
/>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".service.Imap2AuthenticatorService"
|
||||
android:exported="true"
|
||||
android:enabled="true"
|
||||
>
|
||||
<intent-filter>
|
||||
<action
|
||||
android:name="android.accounts.AccountAuthenticator" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.accounts.AccountAuthenticator"
|
||||
android:resource="@xml/imap2_authenticator"
|
||||
/>
|
||||
</service>
|
||||
<provider
|
||||
android:name=".provider.AttachmentProvider"
|
||||
android:authorities="com.android.email.attachmentprovider"
|
||||
|
|
|
@ -14,10 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.email;
|
||||
|
||||
import com.android.email.activity.setup.AccountSettingsUtils.Provider;
|
||||
import com.android.emailcommon.Logging;
|
||||
package com.android.emailcommon;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
|
@ -25,6 +22,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
|
|||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
|
@ -193,6 +191,50 @@ public class VendorPolicyLoader {
|
|||
return result;
|
||||
}
|
||||
|
||||
public static class Provider implements Serializable {
|
||||
private static final long serialVersionUID = 8511656164616538989L;
|
||||
|
||||
public String id;
|
||||
public String label;
|
||||
public String domain;
|
||||
public String incomingUriTemplate;
|
||||
public String incomingUsernameTemplate;
|
||||
public String outgoingUriTemplate;
|
||||
public String outgoingUsernameTemplate;
|
||||
public String incomingUri;
|
||||
public String incomingUsername;
|
||||
public String outgoingUri;
|
||||
public String outgoingUsername;
|
||||
public String note;
|
||||
|
||||
/**
|
||||
* Expands templates in all of the provider fields that support them. Currently,
|
||||
* templates are used in 4 fields -- incoming and outgoing URI and user name.
|
||||
* @param email user-specified data used to replace template values
|
||||
*/
|
||||
public void expandTemplates(String email) {
|
||||
String[] emailParts = email.split("@");
|
||||
String user = emailParts[0];
|
||||
|
||||
incomingUri = expandTemplate(incomingUriTemplate, email, user);
|
||||
incomingUsername = expandTemplate(incomingUsernameTemplate, email, user);
|
||||
outgoingUri = expandTemplate(outgoingUriTemplate, email, user);
|
||||
outgoingUsername = expandTemplate(outgoingUsernameTemplate, email, user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all parameterized values in the given template. The values replaced are
|
||||
* $domain, $user and $email.
|
||||
*/
|
||||
private String expandTemplate(String template, String email, String user) {
|
||||
String returnString = template;
|
||||
returnString = returnString.replaceAll("\\$email", email);
|
||||
returnString = returnString.replaceAll("\\$user", user);
|
||||
returnString = returnString.replaceAll("\\$domain", domain);
|
||||
return returnString;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns provider setup information for a given email address
|
||||
*
|
|
@ -71,6 +71,9 @@ public abstract class EmailContent {
|
|||
|
||||
public static final Uri CONTENT_NOTIFIER_URI = Uri.parse("content://" + NOTIFIER_AUTHORITY);
|
||||
|
||||
public static final Uri PICK_TRASH_FOLDER_URI =
|
||||
Uri.parse("content://" + EmailContent.AUTHORITY + "/pickTrashFolder");
|
||||
|
||||
public static final Uri MAILBOX_NOTIFICATION_URI =
|
||||
Uri.parse("content://" + EmailContent.AUTHORITY + "/mailboxNotification");
|
||||
public static final String[] NOTIFICATION_PROJECTION =
|
||||
|
@ -543,6 +546,8 @@ public abstract class EmailContent {
|
|||
public static final String PROTOCOL_SEARCH_INFO = "protocolSearchInfo";
|
||||
// Simple thread topic
|
||||
public static final String THREAD_TOPIC = "threadTopic";
|
||||
// For sync adapter use
|
||||
public static final String SYNC_DATA = "syncData";
|
||||
}
|
||||
|
||||
public static final class Message extends EmailContent implements SyncColumns, MessageColumns {
|
||||
|
@ -551,11 +556,12 @@ public abstract class EmailContent {
|
|||
public static final String DELETED_TABLE_NAME = "Message_Deletes";
|
||||
|
||||
// To refer to a specific message, use ContentUris.withAppendedId(CONTENT_URI, id)
|
||||
@SuppressWarnings("hiding")
|
||||
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/message");
|
||||
public static final Uri CONTENT_URI_LIMIT_1 = uriWithLimit(CONTENT_URI, 1);
|
||||
public static final Uri SYNCED_CONTENT_URI =
|
||||
Uri.parse(EmailContent.CONTENT_URI + "/syncedMessage");
|
||||
Uri.parse(EmailContent.CONTENT_URI + "/syncedMessage");
|
||||
public static final Uri SYNCED_SELECTION_CONTENT_URI =
|
||||
Uri.parse(EmailContent.CONTENT_URI + "/syncedMessageSelection");
|
||||
public static final Uri DELETED_CONTENT_URI =
|
||||
Uri.parse(EmailContent.CONTENT_URI + "/deletedMessage");
|
||||
public static final Uri UPDATED_CONTENT_URI =
|
||||
|
@ -589,6 +595,7 @@ public abstract class EmailContent {
|
|||
public static final int CONTENT_SNIPPET_COLUMN = 21;
|
||||
public static final int CONTENT_PROTOCOL_SEARCH_INFO_COLUMN = 22;
|
||||
public static final int CONTENT_THREAD_TOPIC_COLUMN = 23;
|
||||
public static final int CONTENT_SYNC_DATA_COLUMN = 24;
|
||||
|
||||
public static final String[] CONTENT_PROJECTION = new String[] {
|
||||
RECORD_ID,
|
||||
|
@ -603,7 +610,7 @@ public abstract class EmailContent {
|
|||
MessageColumns.BCC_LIST, MessageColumns.REPLY_TO_LIST,
|
||||
SyncColumns.SERVER_TIMESTAMP, MessageColumns.MEETING_INFO,
|
||||
MessageColumns.SNIPPET, MessageColumns.PROTOCOL_SEARCH_INFO,
|
||||
MessageColumns.THREAD_TOPIC
|
||||
MessageColumns.THREAD_TOPIC, MessageColumns.SYNC_DATA
|
||||
};
|
||||
|
||||
public static final int LIST_ID_COLUMN = 0;
|
||||
|
@ -740,6 +747,8 @@ public abstract class EmailContent {
|
|||
|
||||
public String mThreadTopic;
|
||||
|
||||
public String mSyncData;
|
||||
|
||||
/**
|
||||
* Base64-encoded representation of the byte array provided by servers for identifying
|
||||
* messages belonging to the same conversation thread. Currently unsupported and not
|
||||
|
@ -832,28 +841,22 @@ public abstract class EmailContent {
|
|||
values.put(MessageColumns.FLAG_FAVORITE, mFlagFavorite);
|
||||
values.put(MessageColumns.FLAG_ATTACHMENT, mFlagAttachment);
|
||||
values.put(MessageColumns.FLAGS, mFlags);
|
||||
|
||||
values.put(SyncColumns.SERVER_ID, mServerId);
|
||||
values.put(SyncColumns.SERVER_TIMESTAMP, mServerTimeStamp);
|
||||
values.put(MessageColumns.DRAFT_INFO, mDraftInfo);
|
||||
values.put(MessageColumns.MESSAGE_ID, mMessageId);
|
||||
|
||||
values.put(MessageColumns.MAILBOX_KEY, mMailboxKey);
|
||||
values.put(MessageColumns.ACCOUNT_KEY, mAccountKey);
|
||||
|
||||
values.put(MessageColumns.FROM_LIST, mFrom);
|
||||
values.put(MessageColumns.TO_LIST, mTo);
|
||||
values.put(MessageColumns.CC_LIST, mCc);
|
||||
values.put(MessageColumns.BCC_LIST, mBcc);
|
||||
values.put(MessageColumns.REPLY_TO_LIST, mReplyTo);
|
||||
|
||||
values.put(MessageColumns.MEETING_INFO, mMeetingInfo);
|
||||
|
||||
values.put(MessageColumns.SNIPPET, mSnippet);
|
||||
|
||||
values.put(MessageColumns.PROTOCOL_SEARCH_INFO, mProtocolSearchInfo);
|
||||
|
||||
values.put(MessageColumns.THREAD_TOPIC, mThreadTopic);
|
||||
values.put(MessageColumns.SYNC_DATA, mSyncData);
|
||||
return values;
|
||||
}
|
||||
|
||||
|
@ -889,6 +892,7 @@ public abstract class EmailContent {
|
|||
mSnippet = cursor.getString(CONTENT_SNIPPET_COLUMN);
|
||||
mProtocolSearchInfo = cursor.getString(CONTENT_PROTOCOL_SEARCH_INFO_COLUMN);
|
||||
mThreadTopic = cursor.getString(CONTENT_THREAD_TOPIC_COLUMN);
|
||||
mSyncData = cursor.getString(CONTENT_SYNC_DATA_COLUMN);
|
||||
}
|
||||
|
||||
public boolean update() {
|
||||
|
@ -991,22 +995,25 @@ public abstract class EmailContent {
|
|||
if (mQuotedTextStartPos != 0) {
|
||||
cv.put(Body.QUOTED_TEXT_START_POS, mQuotedTextStartPos);
|
||||
}
|
||||
b = ContentProviderOperation.newInsert(Body.CONTENT_URI);
|
||||
// Put our message id in the Body
|
||||
if (!isNew) {
|
||||
cv.put(Body.MESSAGE_KEY, mId);
|
||||
}
|
||||
b.withValues(cv);
|
||||
// We'll need this if we're new
|
||||
int messageBackValue = ops.size() - 1;
|
||||
// If we're new, create a back value entry
|
||||
if (isNew) {
|
||||
ContentValues backValues = new ContentValues();
|
||||
backValues.put(Body.MESSAGE_KEY, messageBackValue);
|
||||
b.withValueBackReferences(backValues);
|
||||
// Only create a body if we've got some data
|
||||
if (!cv.keySet().isEmpty()) {
|
||||
b = ContentProviderOperation.newInsert(Body.CONTENT_URI);
|
||||
// Put our message id in the Body
|
||||
if (!isNew) {
|
||||
cv.put(Body.MESSAGE_KEY, mId);
|
||||
}
|
||||
b.withValues(cv);
|
||||
// If we're new, create a back value entry
|
||||
if (isNew) {
|
||||
ContentValues backValues = new ContentValues();
|
||||
backValues.put(Body.MESSAGE_KEY, messageBackValue);
|
||||
b.withValueBackReferences(backValues);
|
||||
}
|
||||
// And add the Body operation
|
||||
ops.add(b.build());
|
||||
}
|
||||
// And add the Body operation
|
||||
ops.add(b.build());
|
||||
|
||||
// Create the attaachments, if any
|
||||
if (mAttachments != null) {
|
||||
|
|
|
@ -0,0 +1,274 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.emailcommon.provider;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.Debug;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.emailcommon.Logging;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
|
||||
import com.android.emailcommon.provider.Mailbox;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class MailboxUtilities {
|
||||
public static final String WHERE_PARENT_KEY_UNINITIALIZED =
|
||||
"(" + MailboxColumns.PARENT_KEY + " isnull OR " + MailboxColumns.PARENT_KEY + "=" +
|
||||
Mailbox.PARENT_KEY_UNINITIALIZED + ")";
|
||||
// The flag we use in Account to indicate a mailbox change in progress
|
||||
private static final int ACCOUNT_MAILBOX_CHANGE_FLAG = Account.FLAGS_SYNC_ADAPTER;
|
||||
|
||||
/**
|
||||
* Recalculate a mailbox's flags and the parent key of any children
|
||||
* @param context the caller's context
|
||||
* @param parentCursor a cursor to a mailbox that requires fixup
|
||||
*/
|
||||
public static void setFlagsAndChildrensParentKey(Context context, Cursor parentCursor,
|
||||
String accountSelector) {
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
String[] selectionArgs = new String[1];
|
||||
ContentValues parentValues = new ContentValues();
|
||||
// Get the data we need first
|
||||
long parentId = parentCursor.getLong(Mailbox.CONTENT_ID_COLUMN);
|
||||
int parentFlags = 0;
|
||||
int parentType = parentCursor.getInt(Mailbox.CONTENT_TYPE_COLUMN);
|
||||
String parentServerId = parentCursor.getString(Mailbox.CONTENT_SERVER_ID_COLUMN);
|
||||
// All email-type boxes hold mail
|
||||
if (parentType <= Mailbox.TYPE_NOT_EMAIL) {
|
||||
parentFlags |= Mailbox.FLAG_HOLDS_MAIL + Mailbox.FLAG_SUPPORTS_SETTINGS;
|
||||
}
|
||||
// Outbox, Drafts, and Sent don't allow mail to be moved to them
|
||||
if (parentType == Mailbox.TYPE_MAIL || parentType == Mailbox.TYPE_TRASH ||
|
||||
parentType == Mailbox.TYPE_JUNK || parentType == Mailbox.TYPE_INBOX) {
|
||||
parentFlags |= Mailbox.FLAG_ACCEPTS_MOVED_MAIL;
|
||||
}
|
||||
// There's no concept of "append" in EAS so FLAG_ACCEPTS_APPENDED_MAIL is never used
|
||||
// Mark parent mailboxes as parents & add parent key to children
|
||||
// An example of a mailbox with a null serverId would be an Outbox that we create locally
|
||||
// for hotmail accounts (which don't have a server-based Outbox)
|
||||
if (parentServerId != null) {
|
||||
selectionArgs[0] = parentServerId;
|
||||
Cursor childCursor = resolver.query(Mailbox.CONTENT_URI,
|
||||
Mailbox.ID_PROJECTION, MailboxColumns.PARENT_SERVER_ID + "=? AND " +
|
||||
accountSelector, selectionArgs, null);
|
||||
if (childCursor == null) return;
|
||||
try {
|
||||
while (childCursor.moveToNext()) {
|
||||
parentFlags |= Mailbox.FLAG_HAS_CHILDREN | Mailbox.FLAG_CHILDREN_VISIBLE;
|
||||
ContentValues childValues = new ContentValues();
|
||||
childValues.put(Mailbox.PARENT_KEY, parentId);
|
||||
long childId = childCursor.getLong(Mailbox.ID_PROJECTION_COLUMN);
|
||||
resolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, childId),
|
||||
childValues, null, null);
|
||||
}
|
||||
} finally {
|
||||
childCursor.close();
|
||||
}
|
||||
} else {
|
||||
// Mark this is having no parent, so that we don't examine this mailbox again
|
||||
parentValues.put(Mailbox.PARENT_KEY, Mailbox.NO_MAILBOX);
|
||||
Log.w(Logging.LOG_TAG, "Mailbox with null serverId: " +
|
||||
parentCursor.getString(Mailbox.CONTENT_DISPLAY_NAME_COLUMN) + ", type: " +
|
||||
parentType);
|
||||
}
|
||||
// Save away updated flags and parent key (if any)
|
||||
parentValues.put(Mailbox.FLAGS, parentFlags);
|
||||
resolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, parentId),
|
||||
parentValues, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate a mailbox's flags and the parent key of any children
|
||||
* @param context the caller's context
|
||||
* @param accountSelector (see description below in fixupUninitializedParentKeys)
|
||||
* @param serverId the server id of an individual mailbox
|
||||
*/
|
||||
public static void setFlagsAndChildrensParentKey(Context context, String accountSelector,
|
||||
String serverId) {
|
||||
Cursor cursor = context.getContentResolver().query(Mailbox.CONTENT_URI,
|
||||
Mailbox.CONTENT_PROJECTION, MailboxColumns.SERVER_ID + "=? AND " + accountSelector,
|
||||
new String[] {serverId}, null);
|
||||
if (cursor == null) return;
|
||||
try {
|
||||
if (cursor.moveToFirst()) {
|
||||
setFlagsAndChildrensParentKey(context, cursor, accountSelector);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an account selector, specifying the account(s) on which to work, create the parentKey
|
||||
* and flags for each mailbox in the account(s) that is uninitialized (parentKey = 0 or null)
|
||||
*
|
||||
* @param accountSelector a sqlite WHERE clause expression to be used in determining the
|
||||
* mailboxes to be acted upon, e.g. accountKey IN (1, 2), accountKey = 12, etc.
|
||||
*/
|
||||
public static void fixupUninitializedParentKeys(Context context, String accountSelector) {
|
||||
// Sanity check first on our arguments
|
||||
if (accountSelector == null) throw new IllegalArgumentException();
|
||||
// The selection we'll use to find uninitialized parent key mailboxes
|
||||
String noParentKeySelection = WHERE_PARENT_KEY_UNINITIALIZED + " AND " + accountSelector;
|
||||
|
||||
// We'll loop through mailboxes with an uninitialized parent key
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
Cursor noParentKeyMailboxCursor =
|
||||
resolver.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
|
||||
noParentKeySelection, null, null);
|
||||
if (noParentKeyMailboxCursor == null) return;
|
||||
try {
|
||||
while (noParentKeyMailboxCursor.moveToNext()) {
|
||||
setFlagsAndChildrensParentKey(context, noParentKeyMailboxCursor, accountSelector);
|
||||
String parentServerId =
|
||||
noParentKeyMailboxCursor.getString(Mailbox.CONTENT_PARENT_SERVER_ID_COLUMN);
|
||||
// Fixup the parent so that the children's parentKey is updated
|
||||
if (parentServerId != null) {
|
||||
setFlagsAndChildrensParentKey(context, accountSelector, parentServerId);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
noParentKeyMailboxCursor.close();
|
||||
}
|
||||
|
||||
// Any mailboxes without a parent key should have parentKey set to -1 (no parent)
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Mailbox.PARENT_KEY, Mailbox.NO_MAILBOX);
|
||||
resolver.update(Mailbox.CONTENT_URI, values, noParentKeySelection, null);
|
||||
}
|
||||
|
||||
private static void setAccountSyncAdapterFlag(Context context, long accountId, boolean start) {
|
||||
Account account = Account.restoreAccountWithId(context, accountId);
|
||||
if (account == null) return;
|
||||
// Set temporary flag indicating state of update of mailbox list
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(Account.FLAGS, start ? (account.mFlags | ACCOUNT_MAILBOX_CHANGE_FLAG) :
|
||||
account.mFlags & ~ACCOUNT_MAILBOX_CHANGE_FLAG);
|
||||
context.getContentResolver().update(
|
||||
ContentUris.withAppendedId(Account.CONTENT_URI, account.mId), cv, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the specified account is starting the process of changing its mailbox list
|
||||
* @param context the caller's context
|
||||
* @param accountId the account that is starting to change its mailbox list
|
||||
*/
|
||||
public static void startMailboxChanges(Context context, long accountId) {
|
||||
setAccountSyncAdapterFlag(context, accountId, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the specified account is ending the process of changing its mailbox list
|
||||
* @param context the caller's context
|
||||
* @param accountId the account that is finished with changes to its mailbox list
|
||||
*/
|
||||
public static void endMailboxChanges(Context context, long accountId) {
|
||||
setAccountSyncAdapterFlag(context, accountId, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that we didn't leave the account's mailboxes in a (possibly) inconsistent state
|
||||
* If we did, make them consistent again
|
||||
* @param context the caller's context
|
||||
* @param accountId the account whose mailboxes are to be checked
|
||||
*/
|
||||
public static void checkMailboxConsistency(Context context, long accountId) {
|
||||
// If our temporary flag is set, we were interrupted during an update
|
||||
// First, make sure we're current (really fast w/ caching)
|
||||
Account account = Account.restoreAccountWithId(context, accountId);
|
||||
if (account == null) return;
|
||||
if ((account.mFlags & ACCOUNT_MAILBOX_CHANGE_FLAG) != 0) {
|
||||
Log.w(Logging.LOG_TAG, "Account " + account.mDisplayName +
|
||||
" has inconsistent mailbox data; fixing up...");
|
||||
// Set all account mailboxes to uninitialized parent key
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Mailbox.PARENT_KEY, Mailbox.PARENT_KEY_UNINITIALIZED);
|
||||
String accountSelector = Mailbox.ACCOUNT_KEY + "=" + account.mId;
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
resolver.update(Mailbox.CONTENT_URI, values, accountSelector, null);
|
||||
// Fix up keys and flags
|
||||
MailboxUtilities.fixupUninitializedParentKeys(context, accountSelector);
|
||||
// Clear the temporary flag
|
||||
endMailboxChanges(context, accountId);
|
||||
}
|
||||
}
|
||||
|
||||
private static final String[] HIERARCHY_PROJECTION = new String[] {
|
||||
MailboxColumns.ID, MailboxColumns.DISPLAY_NAME, MailboxColumns.PARENT_KEY,
|
||||
MailboxColumns.HIERARCHICAL_NAME
|
||||
};
|
||||
private static final int HIERARCHY_ID = 0;
|
||||
private static final int HIERARCHY_NAME = 1;
|
||||
private static final int HIERARCHY_PARENT_KEY = 2;
|
||||
private static final int HIERARCHY_HIERARCHICAL_NAME = 3;
|
||||
|
||||
private static String getHierarchicalName(Context context, long id, HashMap<Long, String> map,
|
||||
String name, long parentId) {
|
||||
String hierarchicalName;
|
||||
if (map.containsKey(id)) {
|
||||
return map.get(id);
|
||||
} else if (parentId == Mailbox.NO_MAILBOX) {
|
||||
hierarchicalName = name;
|
||||
} else {
|
||||
Mailbox parent = Mailbox.restoreMailboxWithId(context, parentId);
|
||||
if (parent == null) return name + "/" + "??";
|
||||
hierarchicalName = getHierarchicalName(context, parentId, map, parent.mDisplayName,
|
||||
parent.mParentKey) + "/" + name;
|
||||
}
|
||||
map.put(id, hierarchicalName);
|
||||
return hierarchicalName;
|
||||
}
|
||||
|
||||
public static void setupHierarchicalNames(Context context, long accountId) {
|
||||
Account account = Account.restoreAccountWithId(context, accountId);
|
||||
if (account == null) return;
|
||||
// Start by clearing all names
|
||||
ContentValues values = new ContentValues();
|
||||
String accountSelector = Mailbox.ACCOUNT_KEY + "=" + account.mId;
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
HashMap<Long, String> nameMap = new HashMap<Long, String>();
|
||||
Cursor c = resolver.query(Mailbox.CONTENT_URI, HIERARCHY_PROJECTION, accountSelector,
|
||||
null, null);
|
||||
try {
|
||||
while(c.moveToNext()) {
|
||||
long id = c.getLong(HIERARCHY_ID);
|
||||
String displayName = c.getString(HIERARCHY_NAME);
|
||||
String name = getHierarchicalName(context, id, nameMap, displayName,
|
||||
c.getLong(HIERARCHY_PARENT_KEY));
|
||||
String oldHierarchicalName = c.getString(HIERARCHY_HIERARCHICAL_NAME);
|
||||
// Don't write the name unless it has changed or we don't need one (it's top-level)
|
||||
if (name.equals(oldHierarchicalName) ||
|
||||
((name.equals(displayName)) && TextUtils.isEmpty(oldHierarchicalName))) {
|
||||
continue;
|
||||
}
|
||||
// If the name has changed, update it
|
||||
values.put(MailboxColumns.HIERARCHICAL_NAME, name);
|
||||
resolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, id), values, null,
|
||||
null);
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
/* Copyright (C) 2012 The Android Open Source Project.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.emailcommon.service;
|
||||
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.emailcommon.service.IEmailServiceCallback.Stub;
|
||||
|
||||
public class EmailServiceCallback extends Stub {
|
||||
|
||||
private final RemoteCallbackList<IEmailServiceCallback> mCallbackList;
|
||||
|
||||
public EmailServiceCallback(RemoteCallbackList<IEmailServiceCallback> callbackList) {
|
||||
mCallbackList = callbackList;
|
||||
}
|
||||
/**
|
||||
* Broadcast a callback to the everyone that's registered
|
||||
*
|
||||
* @param wrapper the ServiceCallbackWrapper used in the broadcast
|
||||
*/
|
||||
private synchronized void broadcastCallback(ServiceCallbackWrapper wrapper) {
|
||||
RemoteCallbackList<IEmailServiceCallback> callbackList = mCallbackList;
|
||||
if (callbackList != null) {
|
||||
// Call everyone on our callback list
|
||||
int count = callbackList.beginBroadcast();
|
||||
try {
|
||||
for (int i = 0; i < count; i++) {
|
||||
try {
|
||||
wrapper.call(callbackList.getBroadcastItem(i));
|
||||
} catch (RemoteException e) {
|
||||
// Safe to ignore
|
||||
} catch (RuntimeException e) {
|
||||
// We don't want an exception in one call to prevent other calls, so
|
||||
// we'll just log this and continue
|
||||
Log.e("EmailServiceCallback", "Caught RuntimeException in broadcast", e);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// No matter what, we need to finish the broadcast
|
||||
callbackList.finishBroadcast();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachmentStatus(final long messageId, final long attachmentId,
|
||||
final int status, final int progress) {
|
||||
broadcastCallback(new ServiceCallbackWrapper() {
|
||||
@Override
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException {
|
||||
cb.loadAttachmentStatus(messageId, attachmentId, status, progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMessageStatus(final long messageId, final int status, final int progress) {
|
||||
broadcastCallback(new ServiceCallbackWrapper() {
|
||||
@Override
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException {
|
||||
cb.loadMessageStatus(messageId, status, progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessageStatus(final long messageId, final String subject, final int status,
|
||||
final int progress) {
|
||||
broadcastCallback(new ServiceCallbackWrapper() {
|
||||
@Override
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException {
|
||||
cb.sendMessageStatus(messageId, subject, status, progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncMailboxListStatus(final long accountId, final int status,
|
||||
final int progress) {
|
||||
broadcastCallback(new ServiceCallbackWrapper() {
|
||||
@Override
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException {
|
||||
cb.syncMailboxListStatus(accountId, status, progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncMailboxStatus(final long mailboxId, final int status,
|
||||
final int progress) {
|
||||
broadcastCallback(new ServiceCallbackWrapper() {
|
||||
@Override
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException {
|
||||
cb.syncMailboxStatus(mailboxId, status, progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private interface ServiceCallbackWrapper {
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException;
|
||||
}
|
||||
}
|
|
@ -26,4 +26,25 @@ public class SyncWindow {
|
|||
public static final int SYNC_WINDOW_2_WEEKS = 4;
|
||||
public static final int SYNC_WINDOW_1_MONTH = 5;
|
||||
public static final int SYNC_WINDOW_ALL = 6;
|
||||
|
||||
public static int toDays(int window) {
|
||||
switch(window) {
|
||||
case SYNC_WINDOW_1_DAY:
|
||||
return 1;
|
||||
case SYNC_WINDOW_3_DAYS:
|
||||
return 3;
|
||||
case SYNC_WINDOW_1_WEEK:
|
||||
return 7;
|
||||
case SYNC_WINDOW_2_WEEKS:
|
||||
return 14;
|
||||
case SYNC_WINDOW_1_MONTH:
|
||||
return 30;
|
||||
case SYNC_WINDOW_ALL:
|
||||
return 365*10;
|
||||
case SYNC_WINDOW_UNKNOWN:
|
||||
case SYNC_WINDOW_AUTO:
|
||||
default:
|
||||
return 14;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ public class EmailSyncAlarmReceiver extends BroadcastReceiver {
|
|||
ContentResolver cr = context.getContentResolver();
|
||||
|
||||
// Get a selector for EAS accounts (we don't want to sync on changes to POP/IMAP messages)
|
||||
String selector = SyncServiceManager.getAccountSelector();
|
||||
String selector = SyncManager.getAccountSelector();
|
||||
|
||||
try {
|
||||
// Find all of the deletions
|
||||
|
@ -102,7 +102,7 @@ public class EmailSyncAlarmReceiver extends BroadcastReceiver {
|
|||
|
||||
// Request service from the mailbox
|
||||
for (Long mailboxId: mailboxesToNotify) {
|
||||
SyncServiceManager.serviceRequest(mailboxId, SyncServiceManager.SYNC_UPSYNC);
|
||||
SyncManager.serviceRequest(mailboxId, SyncManager.SYNC_UPSYNC);
|
||||
}
|
||||
} catch (ProviderUnavailableException e) {
|
||||
Log.e("EmailSyncAlarmReceiver", "EmailProvider unavailable; aborting alarm receiver");
|
||||
|
|
|
@ -30,12 +30,12 @@ import android.content.Intent;
|
|||
public class MailboxAlarmReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
long mailboxId = intent.getLongExtra("mailbox", SyncServiceManager.EXTRA_MAILBOX_ID);
|
||||
long mailboxId = intent.getLongExtra("mailbox", SyncManager.EXTRA_MAILBOX_ID);
|
||||
// EXCHANGE_SERVICE_MAILBOX_ID tells us that the service is asking to be started
|
||||
if (mailboxId == SyncServiceManager.SYNC_SERVICE_MAILBOX_ID) {
|
||||
context.startService(new Intent(context, SyncServiceManager.class));
|
||||
if (mailboxId == SyncManager.SYNC_SERVICE_MAILBOX_ID) {
|
||||
context.startService(new Intent(context, SyncManager.class));
|
||||
} else {
|
||||
SyncServiceManager.alert(context, mailboxId);
|
||||
SyncManager.alert(context, mailboxId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.emailsync;
|
||||
|
||||
import com.android.emailsync.Request;
|
||||
|
||||
/**
|
||||
* MessageMoveRequest is the EAS wrapper for requesting a "move to folder"
|
||||
*/
|
||||
public class MessageMoveRequest extends Request {
|
||||
public final long mMailboxId;
|
||||
|
||||
public MessageMoveRequest(long messageId, long mailboxId) {
|
||||
super(messageId);
|
||||
mMailboxId = mailboxId;
|
||||
}
|
||||
|
||||
// MessageMoveRequests are unique by their message id (i.e. it's meaningless to have two
|
||||
// separate message moves queued at the same time)
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof MessageMoveRequest)) return false;
|
||||
return ((MessageMoveRequest)o).mMessageId == mMessageId;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return (int)mMessageId;
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ package com.android.emailsync;
|
|||
import com.android.emailcommon.provider.EmailContent.Attachment;
|
||||
|
||||
/**
|
||||
* PartRequest is the EAS wrapper for attachment loading requests. In addition to information about
|
||||
* PartRequest is the wrapper for attachment loading requests. In addition to information about
|
||||
* the attachment to be loaded, it also contains the callback to be used for status/progress
|
||||
* updates to the UI.
|
||||
*/
|
||||
|
|
|
@ -61,7 +61,6 @@ import com.android.emailcommon.provider.ProviderUnavailableException;
|
|||
import com.android.emailcommon.service.AccountServiceProxy;
|
||||
import com.android.emailcommon.service.EmailServiceProxy;
|
||||
import com.android.emailcommon.service.EmailServiceStatus;
|
||||
import com.android.emailcommon.service.IEmailServiceCallback;
|
||||
import com.android.emailcommon.service.IEmailServiceCallback.Stub;
|
||||
import com.android.emailcommon.service.PolicyServiceProxy;
|
||||
import com.android.emailcommon.utility.EmailAsyncTask;
|
||||
|
@ -88,7 +87,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
* order to maintain proper 2-way syncing of data. (More documentation to follow)
|
||||
*
|
||||
*/
|
||||
public abstract class SyncServiceManager extends Service implements Runnable {
|
||||
public abstract class SyncManager extends Service implements Runnable {
|
||||
|
||||
private static final String TAG = "SyncServiceManager";
|
||||
|
||||
|
@ -201,7 +200,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
public ContentResolver mResolver;
|
||||
|
||||
// The singleton SyncServiceManager object, with its thread and stop flag
|
||||
protected static SyncServiceManager INSTANCE;
|
||||
protected static SyncManager INSTANCE;
|
||||
protected static Thread sServiceThread = null;
|
||||
// Cached unique device id
|
||||
protected static String sDeviceId = null;
|
||||
|
@ -338,7 +337,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
}
|
||||
|
||||
public static String getAccountSelector() {
|
||||
SyncServiceManager ssm = INSTANCE;
|
||||
SyncManager ssm = INSTANCE;
|
||||
if (ssm == null) return "";
|
||||
return ssm.getAccountsSelector();
|
||||
}
|
||||
|
@ -380,8 +379,8 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
if (onSecurityHold(account)) {
|
||||
// If we're in a security hold, and our policies are active, release
|
||||
// the hold
|
||||
if (PolicyServiceProxy.isActive(SyncServiceManager.this, null)) {
|
||||
PolicyServiceProxy.setAccountHoldFlag(SyncServiceManager.this,
|
||||
if (PolicyServiceProxy.isActive(SyncManager.this, null)) {
|
||||
PolicyServiceProxy.setAccountHoldFlag(SyncManager.this,
|
||||
account, false);
|
||||
log("isActive true; release hold for " + account.mDisplayName);
|
||||
}
|
||||
|
@ -440,7 +439,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
// The implication is that the account has been deleted; let's find out
|
||||
alwaysLog("Observer found deleted account: " + account.mDisplayName);
|
||||
// Run the reconciler (the reconciliation itself runs in the Email app)
|
||||
runAccountReconcilerSync(SyncServiceManager.this);
|
||||
runAccountReconcilerSync(SyncManager.this);
|
||||
// See if the account is still around
|
||||
Account deletedAccount =
|
||||
Account.restoreAccountWithId(context, account.mId);
|
||||
|
@ -477,7 +476,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
|
||||
// See if this account is no longer on security hold
|
||||
if (onSecurityHold(account) && !onSecurityHold(updatedAccount)) {
|
||||
releaseSyncHolds(SyncServiceManager.this,
|
||||
releaseSyncHolds(SyncManager.this,
|
||||
AbstractSyncService.EXIT_SECURITY_FAILURE, account);
|
||||
}
|
||||
|
||||
|
@ -548,7 +547,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
* Unregister all CalendarObserver's
|
||||
*/
|
||||
static public void unregisterCalendarObservers() {
|
||||
SyncServiceManager ssm = INSTANCE;
|
||||
SyncManager ssm = INSTANCE;
|
||||
if (ssm == null) return;
|
||||
ContentResolver resolver = ssm.mResolver;
|
||||
for (CalendarObserver observer: ssm.mCalendarObservers.values()) {
|
||||
|
@ -718,7 +717,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
}
|
||||
|
||||
static public Account getAccountById(long accountId) {
|
||||
SyncServiceManager ssm = INSTANCE;
|
||||
SyncManager ssm = INSTANCE;
|
||||
if (ssm != null) {
|
||||
AccountList accountList = ssm.mAccountList;
|
||||
synchronized (accountList) {
|
||||
|
@ -729,7 +728,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
}
|
||||
|
||||
static public Account getAccountByName(String accountName) {
|
||||
SyncServiceManager ssm = INSTANCE;
|
||||
SyncManager ssm = INSTANCE;
|
||||
if (ssm != null) {
|
||||
AccountList accountList = ssm.mAccountList;
|
||||
synchronized (accountList) {
|
||||
|
@ -795,7 +794,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
* @param account the account whose Mailboxes should be released from security hold
|
||||
*/
|
||||
static public void releaseSecurityHold(Account account) {
|
||||
SyncServiceManager ssm = INSTANCE;
|
||||
SyncManager ssm = INSTANCE;
|
||||
if (ssm != null) {
|
||||
ssm.releaseSyncHolds(INSTANCE, AbstractSyncService.EXIT_SECURITY_FAILURE,
|
||||
account);
|
||||
|
@ -910,7 +909,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
}
|
||||
|
||||
public static void stopAccountSyncs(long acctId) {
|
||||
SyncServiceManager ssm = INSTANCE;
|
||||
SyncManager ssm = INSTANCE;
|
||||
if (ssm != null) {
|
||||
ssm.stopAccountSyncs(acctId, true);
|
||||
}
|
||||
|
@ -957,7 +956,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
* @param acctId
|
||||
*/
|
||||
static public void stopNonAccountMailboxSyncsForAccount(long acctId) {
|
||||
SyncServiceManager ssm = INSTANCE;
|
||||
SyncManager ssm = INSTANCE;
|
||||
if (ssm != null) {
|
||||
ssm.stopAccountSyncs(acctId, false);
|
||||
kick("reload folder list");
|
||||
|
@ -1051,7 +1050,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
}
|
||||
|
||||
static public void runAwake(long id) {
|
||||
SyncServiceManager ssm = INSTANCE;
|
||||
SyncManager ssm = INSTANCE;
|
||||
if (ssm != null) {
|
||||
ssm.acquireWakeLock(id);
|
||||
ssm.clearAlarm(id);
|
||||
|
@ -1059,7 +1058,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
}
|
||||
|
||||
static public void runAsleep(long id, long millis) {
|
||||
SyncServiceManager ssm = INSTANCE;
|
||||
SyncManager ssm = INSTANCE;
|
||||
if (ssm != null) {
|
||||
ssm.setAlarm(id, millis);
|
||||
ssm.releaseWakeLock(id);
|
||||
|
@ -1067,27 +1066,27 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
}
|
||||
|
||||
static public void clearWatchdogAlarm(long id) {
|
||||
SyncServiceManager ssm = INSTANCE;
|
||||
SyncManager ssm = INSTANCE;
|
||||
if (ssm != null) {
|
||||
ssm.clearAlarm(id);
|
||||
}
|
||||
}
|
||||
|
||||
static public void setWatchdogAlarm(long id, long millis) {
|
||||
SyncServiceManager ssm = INSTANCE;
|
||||
SyncManager ssm = INSTANCE;
|
||||
if (ssm != null) {
|
||||
ssm.setAlarm(id, millis);
|
||||
}
|
||||
}
|
||||
|
||||
static public void alert(Context context, final long id) {
|
||||
final SyncServiceManager ssm = INSTANCE;
|
||||
final SyncManager ssm = INSTANCE;
|
||||
checkSyncServiceManagerServiceRunning();
|
||||
if (id < 0) {
|
||||
log("SyncServiceManager alert");
|
||||
kick("ping SyncServiceManager");
|
||||
} else if (ssm == null) {
|
||||
context.startService(new Intent(context, SyncServiceManager.class));
|
||||
context.startService(new Intent(context, SyncManager.class));
|
||||
} else {
|
||||
final AbstractSyncService service = ssm.mServiceMap.get(id);
|
||||
if (service != null) {
|
||||
|
@ -1129,7 +1128,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
}
|
||||
// Shutdown the connection manager; this should close all of our
|
||||
// sockets and generate IOExceptions all around.
|
||||
SyncServiceManager.shutdownConnectionManager();
|
||||
SyncManager.shutdownConnectionManager();
|
||||
}
|
||||
}
|
||||
}}, threadName).start();
|
||||
|
@ -1177,7 +1176,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
public void run() {
|
||||
synchronized (mAccountList) {
|
||||
for (Account account : mAccountList)
|
||||
SyncServiceManager.stopAccountSyncs(account.mId);
|
||||
SyncManager.stopAccountSyncs(account.mId);
|
||||
}
|
||||
}});
|
||||
}
|
||||
|
@ -1400,13 +1399,13 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
try {
|
||||
synchronized (sSyncLock) {
|
||||
// SyncServiceManager cannot start unless we connect to AccountService
|
||||
if (!new AccountServiceProxy(SyncServiceManager.this).test()) {
|
||||
if (!new AccountServiceProxy(SyncManager.this).test()) {
|
||||
alwaysLog("!!! Email application not found; stopping self");
|
||||
stopSelf();
|
||||
}
|
||||
if (sDeviceId == null) {
|
||||
try {
|
||||
String deviceId = getDeviceId(SyncServiceManager.this);
|
||||
String deviceId = getDeviceId(SyncManager.this);
|
||||
if (deviceId != null) {
|
||||
sDeviceId = deviceId;
|
||||
}
|
||||
|
@ -1430,7 +1429,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
}
|
||||
// Run the reconciler and clean up mismatched accounts - if we weren't
|
||||
// running when accounts were deleted, it won't have been called.
|
||||
runAccountReconcilerSync(SyncServiceManager.this);
|
||||
runAccountReconcilerSync(SyncManager.this);
|
||||
// Update other services depending on final account configuration
|
||||
maybeStartSyncServiceManagerThread();
|
||||
if (sServiceThread == null) {
|
||||
|
@ -1450,7 +1449,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
}
|
||||
|
||||
public static void reconcileAccounts(Context context) {
|
||||
SyncServiceManager ssm = INSTANCE;
|
||||
SyncManager ssm = INSTANCE;
|
||||
if (ssm != null) {
|
||||
ssm.runAccountReconcilerSync(context);
|
||||
}
|
||||
|
@ -1504,11 +1503,11 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
* com.android.email) and hasn't been restarted. See the comment for onCreate for details
|
||||
*/
|
||||
static void checkSyncServiceManagerServiceRunning() {
|
||||
SyncServiceManager ssm = INSTANCE;
|
||||
SyncManager ssm = INSTANCE;
|
||||
if (ssm == null) return;
|
||||
if (sServiceThread == null) {
|
||||
log("!!! checkSyncServiceManagerServiceRunning; starting service...");
|
||||
ssm.startService(new Intent(ssm, SyncServiceManager.class));
|
||||
ssm.startService(new Intent(ssm, SyncManager.class));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1598,7 +1597,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
// process starts running again, remote processes will be started again in due course
|
||||
Log.e(TAG, "EmailProvider unavailable; shutting down");
|
||||
// Ask for our service to be restarted; this should kick-start the Email process as well
|
||||
startService(new Intent(this, SyncServiceManager.class));
|
||||
startService(new Intent(this, SyncManager.class));
|
||||
} catch (RuntimeException e) {
|
||||
// Crash; this is a completely unexpected runtime error
|
||||
Log.e(TAG, "RuntimeException in SyncServiceManager", e);
|
||||
|
@ -1718,7 +1717,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
* @return whether or not the account can sync automatically
|
||||
*/
|
||||
/*package*/ public static boolean canAutoSync(Account account) {
|
||||
SyncServiceManager ssm = INSTANCE;
|
||||
SyncManager ssm = INSTANCE;
|
||||
if (ssm == null) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1971,7 +1970,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
}
|
||||
|
||||
static public void serviceRequest(long mailboxId, long ms, int reason) {
|
||||
SyncServiceManager ssm = INSTANCE;
|
||||
SyncManager ssm = INSTANCE;
|
||||
if (ssm == null) return;
|
||||
Mailbox m = Mailbox.restoreMailboxWithId(ssm, mailboxId);
|
||||
if (m == null || !isSyncable(m)) return;
|
||||
|
@ -1989,7 +1988,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
}
|
||||
|
||||
static public void serviceRequestImmediate(long mailboxId) {
|
||||
SyncServiceManager ssm = INSTANCE;
|
||||
SyncManager ssm = INSTANCE;
|
||||
if (ssm == null) return;
|
||||
AbstractSyncService service = ssm.mServiceMap.get(mailboxId);
|
||||
if (service != null) {
|
||||
|
@ -2004,7 +2003,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
}
|
||||
|
||||
static public void sendMessageRequest(Request req) {
|
||||
SyncServiceManager ssm = INSTANCE;
|
||||
SyncManager ssm = INSTANCE;
|
||||
Message msg = Message.restoreMessageWithId(ssm, req.mMessageId);
|
||||
if (msg == null) return;
|
||||
long mailboxId = msg.mMailboxKey;
|
||||
|
@ -2044,7 +2043,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
* @return whether or not the Mailbox is available for syncing (i.e. is a valid push target)
|
||||
*/
|
||||
static public int pingStatus(long mailboxId) {
|
||||
SyncServiceManager ssm = INSTANCE;
|
||||
SyncManager ssm = INSTANCE;
|
||||
if (ssm == null) return PING_STATUS_OK;
|
||||
// Already syncing...
|
||||
if (ssm.mServiceMap.get(mailboxId) != null) {
|
||||
|
@ -2063,7 +2062,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
}
|
||||
|
||||
static public void startManualSync(long mailboxId, int reason, Request req) {
|
||||
SyncServiceManager ssm = INSTANCE;
|
||||
SyncManager ssm = INSTANCE;
|
||||
if (ssm == null) return;
|
||||
synchronized (sSyncLock) {
|
||||
AbstractSyncService svc = ssm.mServiceMap.get(mailboxId);
|
||||
|
@ -2085,7 +2084,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
|
||||
// DO NOT CALL THIS IN A LOOP ON THE SERVICEMAP
|
||||
static public void stopManualSync(long mailboxId) {
|
||||
SyncServiceManager ssm = INSTANCE;
|
||||
SyncManager ssm = INSTANCE;
|
||||
if (ssm == null) return;
|
||||
synchronized (sSyncLock) {
|
||||
AbstractSyncService svc = ssm.mServiceMap.get(mailboxId);
|
||||
|
@ -2102,7 +2101,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
* Wake up SyncServiceManager to check for mailboxes needing service
|
||||
*/
|
||||
static public void kick(String reason) {
|
||||
SyncServiceManager ssm = INSTANCE;
|
||||
SyncManager ssm = INSTANCE;
|
||||
if (ssm != null) {
|
||||
synchronized (ssm) {
|
||||
//INSTANCE.log("Kick: " + reason);
|
||||
|
@ -2122,7 +2121,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
* @param mailboxId the id of the mailbox
|
||||
*/
|
||||
static public void removeFromSyncErrorMap(long mailboxId) {
|
||||
SyncServiceManager ssm = INSTANCE;
|
||||
SyncManager ssm = INSTANCE;
|
||||
if (ssm != null) {
|
||||
ssm.mSyncErrorMap.remove(mailboxId);
|
||||
}
|
||||
|
@ -2142,7 +2141,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
* @param svc the service that is finished
|
||||
*/
|
||||
static public void done(AbstractSyncService svc) {
|
||||
SyncServiceManager ssm = INSTANCE;
|
||||
SyncManager ssm = INSTANCE;
|
||||
if (ssm == null) return;
|
||||
synchronized(sSyncLock) {
|
||||
long mailboxId = svc.mMailboxId;
|
||||
|
@ -2181,7 +2180,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||
}
|
||||
errorMap.remove(mailboxId);
|
||||
// If we've had a successful sync, clear the shutdown count
|
||||
synchronized (SyncServiceManager.class) {
|
||||
synchronized (SyncManager.class) {
|
||||
sClientConnectionManagerShutdownCount = 0;
|
||||
}
|
||||
// Leave now; other statuses are errors
|
|
@ -0,0 +1,36 @@
|
|||
# Copyright 2008, The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
LOCAL_PATH:= $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
#
|
||||
# Imap2
|
||||
#
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||
LOCAL_SRC_FILES += $(call all-java-files-under, ../src/com/beetstra)
|
||||
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := android-common com.android.emailcommon2 com.android.emailsync
|
||||
|
||||
LOCAL_PACKAGE_NAME := Imap2
|
||||
|
||||
#LOCAL_PROGUARD_FLAG_FILES := proguard.flags
|
||||
LOCAL_SDK_VERSION := 15
|
||||
|
||||
include $(BUILD_PACKAGE)
|
||||
|
||||
# additionally, build unit tests in a separate .apk
|
||||
include $(call all-makefiles-under,$(LOCAL_PATH))
|
|
@ -0,0 +1,113 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2008 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.imap2"
|
||||
android:versionCode="000000"
|
||||
android:versionName="0.1"
|
||||
>
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
<uses-permission
|
||||
android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission
|
||||
android:name="android.permission.INTERNET"/>
|
||||
<uses-permission
|
||||
android:name="android.permission.VIBRATE"/>
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.GET_ACCOUNTS" />
|
||||
<uses-permission
|
||||
android:name="android.permission.MANAGE_ACCOUNTS" />
|
||||
<uses-permission
|
||||
android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.USE_CREDENTIALS"/>
|
||||
|
||||
<!-- Only required if a store implements push mail and needs to keep network open -->
|
||||
<uses-permission
|
||||
android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_PHONE_STATE"/>
|
||||
|
||||
<uses-permission
|
||||
android:name="com.android.email.permission.READ_ATTACHMENT"/>
|
||||
<uses-permission
|
||||
android:name="com.android.email.permission.ACCESS_PROVIDER"/>
|
||||
|
||||
<application
|
||||
android:icon="@mipmap/icon"
|
||||
android:label="@string/app_name"
|
||||
android:name="Imap2"
|
||||
android:theme="@android:style/Theme.Holo.Light"
|
||||
>
|
||||
|
||||
<receiver
|
||||
android:name="com.android.emailsync.EmailSyncAlarmReceiver"/>
|
||||
<receiver
|
||||
android:name="com.android.emailsync.MailboxAlarmReceiver"/>
|
||||
|
||||
<receiver
|
||||
android:name=".Imap2BroadcastReceiver"
|
||||
android:enabled="true">
|
||||
<intent-filter>
|
||||
<action
|
||||
android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action
|
||||
android:name="android.intent.action.DEVICE_STORAGE_LOW" />
|
||||
<action
|
||||
android:name="android.intent.action.DEVICE_STORAGE_OK" />
|
||||
<action
|
||||
android:name="android.accounts.LOGIN_ACCOUNTS_CHANGED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".BroadcastProcessorService" />
|
||||
|
||||
<!--Required stanza to register the EAS EmailSyncAdapterService with SyncManager -->
|
||||
<service
|
||||
android:name=".EmailSyncAdapterService"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action
|
||||
android:name="android.content.SyncAdapter" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.content.SyncAdapter"
|
||||
android:resource="@xml/syncadapter_email" />
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name="com.android.imap2.Imap2SyncManager"
|
||||
android:enabled="true"
|
||||
android:permission="com.android.email.permission.ACCESS_PROVIDER"
|
||||
>
|
||||
<intent-filter>
|
||||
<action
|
||||
android:name="com.android.email.IMAP2_INTENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
</application>
|
||||
</manifest>
|
Binary file not shown.
After Width: | Height: | Size: 6.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2008 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
|
||||
<!-- Name of application on Home screen -->
|
||||
<string name="app_name">Imap2</string>
|
||||
|
||||
<!-- The next set of strings are used server-side and must not be localized. -->
|
||||
<!-- Do Not Translate. This is the name of the "inbox" folder, on the server. -->
|
||||
<string name="mailbox_name_server_inbox" translatable="false">Inbox</string>
|
||||
<!-- Do Not Translate. This is the name of the "outbox" folder, on the server. -->
|
||||
<string name="mailbox_name_server_outbox" translatable="false">Outbox</string>
|
||||
<!-- Do Not Translate. This is the name of the "drafts" folder, on the server. -->
|
||||
<string name="mailbox_name_server_drafts" translatable="false">Drafts</string>
|
||||
<!-- Do Not Translate. This is the name of the "trash" folder, on the server. -->
|
||||
<string name="mailbox_name_server_trash" translatable="false">Trash</string>
|
||||
<!-- Do Not Translate. This is the name of the "sent" folder, on the server. -->
|
||||
<string name="mailbox_name_server_sent" translatable="false">Sent</string>
|
||||
<!-- Do Not Translate. This is the name of the "junk" folder, on the server. -->
|
||||
<string name="mailbox_name_server_junk" translatable="false">Junk</string>
|
||||
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright (c) 2010, The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
-->
|
||||
|
||||
<!-- The attributes in this XML file provide configuration information -->
|
||||
<!-- for the SyncAdapter. -->
|
||||
|
||||
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:contentAuthority="com.android.email.provider"
|
||||
android:accountType="com.android.imap2"
|
||||
android:supportsUploading="true"
|
||||
/>
|
|
@ -0,0 +1,197 @@
|
|||
/* Copyright (C) 2012 The Android Open Source Project.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.imap2;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.android.emailcommon.provider.EmailContent.Attachment;
|
||||
import com.android.emailcommon.provider.EmailContent.Message;
|
||||
import com.android.emailcommon.service.EmailServiceStatus;
|
||||
import com.android.emailsync.PartRequest;
|
||||
import com.android.emailcommon.utility.AttachmentUtilities;
|
||||
import com.android.imap2.Imap2SyncService.Connection;
|
||||
import com.android.mail.providers.UIProvider;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Handle IMAP2 attachment loading
|
||||
*/
|
||||
public class AttachmentLoader {
|
||||
static private final int CHUNK_SIZE = 16*1024;
|
||||
|
||||
private final Context mContext;
|
||||
private final Attachment mAttachment;
|
||||
private final long mAttachmentId;
|
||||
private final long mMessageId;
|
||||
private final Message mMessage;
|
||||
private final Imap2SyncService mService;
|
||||
|
||||
public AttachmentLoader(Imap2SyncService service, PartRequest req) {
|
||||
mService = service;
|
||||
mContext = service.mContext;
|
||||
mAttachment = req.mAttachment;
|
||||
mAttachmentId = mAttachment.mId;
|
||||
mMessageId = mAttachment.mMessageKey;
|
||||
mMessage = Message.restoreMessageWithId(mContext, mMessageId);
|
||||
}
|
||||
|
||||
private void doStatusCallback(int status) {
|
||||
try {
|
||||
Imap2SyncManager.callback().loadAttachmentStatus(mMessageId, mAttachmentId, status, 0);
|
||||
} catch (RemoteException e) {
|
||||
// No danger if the client is no longer around
|
||||
}
|
||||
}
|
||||
|
||||
private void doProgressCallback(int progress) {
|
||||
try {
|
||||
Imap2SyncManager.callback().loadAttachmentStatus(mMessageId, mAttachmentId,
|
||||
EmailServiceStatus.IN_PROGRESS, progress);
|
||||
} catch (RemoteException e) {
|
||||
// No danger if the client is no longer around
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close, ignoring errors (as during cleanup)
|
||||
* @param c a Closeable
|
||||
*/
|
||||
private void close(Closeable c) {
|
||||
try {
|
||||
c.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save away the contentUri for this Attachment and notify listeners
|
||||
* @throws IOException
|
||||
*/
|
||||
private void finishLoadAttachment(File file, OutputStream os) throws IOException {
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(file);
|
||||
AttachmentUtilities.saveAttachment(mContext, in, mAttachment);
|
||||
doStatusCallback(EmailServiceStatus.SUCCESS);
|
||||
} catch (FileNotFoundException e) {
|
||||
// Not bloody likely, as we just created it successfully
|
||||
throw new IOException("Attachment file not found?");
|
||||
} finally {
|
||||
close(in);
|
||||
}
|
||||
}
|
||||
|
||||
private void readPart (ImapInputStream in, String tag, OutputStream out) throws IOException {
|
||||
String res = in.readLine();
|
||||
int bstart = res.indexOf("body[");
|
||||
if (bstart < 0)
|
||||
bstart = res.indexOf("BODY[");
|
||||
if (bstart < 0)
|
||||
return;
|
||||
int bend = res.indexOf(']', bstart);
|
||||
if (bend < 0)
|
||||
return;
|
||||
int br = res.indexOf('{');
|
||||
if (br > 0) {
|
||||
Parser p = new Parser(res, br + 1);
|
||||
int expectedLength = p.parseInteger();
|
||||
int remainingLength = expectedLength;
|
||||
int totalRead = 0;
|
||||
byte[] buf = new byte[CHUNK_SIZE];
|
||||
int lastCallbackPct = -1;
|
||||
int lastCallbackTotalRead = 0;
|
||||
while (remainingLength > 0) {
|
||||
int rdlen = (remainingLength > CHUNK_SIZE ? CHUNK_SIZE : remainingLength);
|
||||
int bytesRead = in.read(buf, 0, rdlen);
|
||||
totalRead += bytesRead;
|
||||
out.write(buf, 0, bytesRead);
|
||||
remainingLength -= bytesRead;
|
||||
int pct = (totalRead * 100) / expectedLength;
|
||||
// Callback only if we've read at least 1% more and have read more than CHUNK_SIZE
|
||||
// We don't want to spam the Email app
|
||||
if ((pct > lastCallbackPct) && (totalRead > (lastCallbackTotalRead + CHUNK_SIZE))) {
|
||||
// Report progress back to the UI
|
||||
doProgressCallback(pct);
|
||||
lastCallbackTotalRead = totalRead;
|
||||
lastCallbackPct = pct;
|
||||
}
|
||||
}
|
||||
out.close();
|
||||
String line = in.readLine();
|
||||
if (!line.endsWith(")")) {
|
||||
mService.errorLog("Bad part?");
|
||||
throw new IOException();
|
||||
}
|
||||
line = in.readLine();
|
||||
if (!line.startsWith(tag)) {
|
||||
mService.userLog("Bad part?");
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an attachment, based on the PartRequest passed in the constructor
|
||||
* @throws IOException
|
||||
*/
|
||||
public void loadAttachment(Connection conn) throws IOException {
|
||||
if (mMessage == null) {
|
||||
doStatusCallback(EmailServiceStatus.MESSAGE_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
if (mAttachment.mUiState == UIProvider.AttachmentState.SAVED) {
|
||||
return;
|
||||
}
|
||||
// Say we've started loading the attachment
|
||||
doProgressCallback(0);
|
||||
|
||||
try {
|
||||
OutputStream os = null;
|
||||
File tmpFile = null;
|
||||
try {
|
||||
tmpFile = File.createTempFile("imap2_", "tmp", mContext.getCacheDir());
|
||||
os = new FileOutputStream(tmpFile);
|
||||
String tag = mService.writeCommand(conn.writer, "uid fetch " + mMessage.mServerId +
|
||||
" body[" + mAttachment.mLocation + ']');
|
||||
readPart(conn.reader, tag, os);
|
||||
finishLoadAttachment(tmpFile, os);
|
||||
return;
|
||||
} catch (FileNotFoundException e) {
|
||||
mService.errorLog("Can't get attachment; write file not found?");
|
||||
doStatusCallback(EmailServiceStatus.ATTACHMENT_NOT_FOUND);
|
||||
} finally {
|
||||
close(os);
|
||||
if (tmpFile != null) {
|
||||
tmpFile.delete();
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Report the error, but also report back to the service
|
||||
doStatusCallback(EmailServiceStatus.CONNECTION_ERROR);
|
||||
throw e;
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.imap2;
|
||||
|
||||
import android.accounts.AccountManager;
|
||||
import android.app.IntentService;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.emailcommon.Logging;
|
||||
import com.android.emailsync.SyncManager;
|
||||
|
||||
/**
|
||||
* The service that really handles broadcast intents on a worker thread.
|
||||
*
|
||||
* We make it a service, because:
|
||||
* <ul>
|
||||
* <li>So that it's less likely for the process to get killed.
|
||||
* <li>Even if it does, the Intent that have started it will be re-delivered by the system,
|
||||
* and we can start the process again. (Using {@link #setIntentRedelivery}).
|
||||
* </ul>
|
||||
*/
|
||||
public class BroadcastProcessorService extends IntentService {
|
||||
// Action used for BroadcastReceiver entry point
|
||||
private static final String ACTION_BROADCAST = "broadcast_receiver";
|
||||
|
||||
public BroadcastProcessorService() {
|
||||
// Class name will be the thread name.
|
||||
super(BroadcastProcessorService.class.getName());
|
||||
// Intent should be redelivered if the process gets killed before completing the job.
|
||||
setIntentRedelivery(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for {@link Imap2BroadcastReceiver}.
|
||||
*/
|
||||
public static void processBroadcastIntent(Context context, Intent broadcastIntent) {
|
||||
Intent i = new Intent(context, BroadcastProcessorService.class);
|
||||
i.setAction(ACTION_BROADCAST);
|
||||
i.putExtra(Intent.EXTRA_INTENT, broadcastIntent);
|
||||
context.startService(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
// Dispatch from entry point
|
||||
final String action = intent.getAction();
|
||||
if (ACTION_BROADCAST.equals(action)) {
|
||||
final Intent broadcastIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT);
|
||||
final String broadcastAction = broadcastIntent.getAction();
|
||||
|
||||
if (Intent.ACTION_BOOT_COMPLETED.equals(broadcastAction)) {
|
||||
onBootCompleted();
|
||||
} else if (AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION.equals(broadcastAction)) {
|
||||
Log.d(Logging.LOG_TAG, "Login accounts changed; reconciling...");
|
||||
SyncManager.reconcileAccounts(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles {@link Intent#ACTION_BOOT_COMPLETED}. Called on a worker thread.
|
||||
*/
|
||||
private void onBootCompleted() {
|
||||
startService(new Intent(this, Imap2SyncManager.class));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.imap2;
|
||||
|
||||
import com.android.emailcommon.provider.EmailContent;
|
||||
import com.android.emailcommon.provider.EmailContent.AccountColumns;
|
||||
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
|
||||
import com.android.emailcommon.provider.Mailbox;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.OperationCanceledException;
|
||||
import android.app.Service;
|
||||
import android.content.AbstractThreadedSyncAdapter;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SyncResult;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
public class EmailSyncAdapterService extends Service {
|
||||
private static final String TAG = "Imap2 EmailSyncAdapterService";
|
||||
private static SyncAdapterImpl sSyncAdapter = null;
|
||||
private static final Object sSyncAdapterLock = new Object();
|
||||
|
||||
private static final String[] ID_PROJECTION = new String[] {EmailContent.RECORD_ID};
|
||||
private static final String ACCOUNT_AND_TYPE_INBOX =
|
||||
MailboxColumns.ACCOUNT_KEY + "=? AND " + MailboxColumns.TYPE + '=' + Mailbox.TYPE_INBOX;
|
||||
|
||||
public EmailSyncAdapterService() {
|
||||
super();
|
||||
}
|
||||
|
||||
private static class SyncAdapterImpl extends AbstractThreadedSyncAdapter {
|
||||
private Context mContext;
|
||||
|
||||
public SyncAdapterImpl(Context context) {
|
||||
super(context, true /* autoInitialize */);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPerformSync(Account account, Bundle extras,
|
||||
String authority, ContentProviderClient provider, SyncResult syncResult) {
|
||||
try {
|
||||
EmailSyncAdapterService.performSync(mContext, account, extras,
|
||||
authority, provider, syncResult);
|
||||
} catch (OperationCanceledException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
synchronized (sSyncAdapterLock) {
|
||||
if (sSyncAdapter == null) {
|
||||
sSyncAdapter = new SyncAdapterImpl(getApplicationContext());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return sSyncAdapter.getSyncAdapterBinder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Partial integration with system SyncManager; we tell our EAS ExchangeService to start an
|
||||
* inbox sync when we get the signal from the system SyncManager.
|
||||
*/
|
||||
private static void performSync(Context context, Account account, Bundle extras,
|
||||
String authority, ContentProviderClient provider, SyncResult syncResult)
|
||||
throws OperationCanceledException {
|
||||
ContentResolver cr = context.getContentResolver();
|
||||
Log.i(TAG, "performSync");
|
||||
|
||||
// Find the (EmailProvider) account associated with this email address
|
||||
Cursor accountCursor =
|
||||
cr.query(com.android.emailcommon.provider.Account.CONTENT_URI,
|
||||
ID_PROJECTION, AccountColumns.EMAIL_ADDRESS + "=?", new String[] {account.name},
|
||||
null);
|
||||
try {
|
||||
if (accountCursor.moveToFirst()) {
|
||||
long accountId = accountCursor.getLong(0);
|
||||
// Now, find the inbox associated with the account
|
||||
Cursor mailboxCursor = cr.query(Mailbox.CONTENT_URI, ID_PROJECTION,
|
||||
ACCOUNT_AND_TYPE_INBOX, new String[] {Long.toString(accountId)}, null);
|
||||
try {
|
||||
if (mailboxCursor.moveToFirst()) {
|
||||
Log.i(TAG, "Mail sync requested for " + account.name);
|
||||
// Ask for a sync from our sync manager
|
||||
//***
|
||||
//SyncServiceManager.serviceRequest(mailboxCursor.getLong(0),
|
||||
// SyncServiceManager.SYNC_KICK);
|
||||
}
|
||||
} finally {
|
||||
mailboxCursor.close();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
accountCursor.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.imap2;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
public class Imap2 extends Application {
|
||||
// TODO Investigate whether this class is needed
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.imap2;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
/**
|
||||
* The broadcast receiver. The actual job is done in EmailBroadcastProcessor on a worker thread.
|
||||
*/
|
||||
public class Imap2BroadcastReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
BroadcastProcessorService.processBroadcastIntent(context, intent);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,325 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.imap2;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Debug;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.emailcommon.Api;
|
||||
import com.android.emailcommon.provider.EmailContent;
|
||||
import com.android.emailcommon.provider.EmailContent.Attachment;
|
||||
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.provider.HostAuth;
|
||||
import com.android.emailcommon.provider.Mailbox;
|
||||
import com.android.emailcommon.provider.ProviderUnavailableException;
|
||||
import com.android.emailcommon.service.AccountServiceProxy;
|
||||
import com.android.emailcommon.service.EmailServiceCallback;
|
||||
import com.android.emailcommon.service.IEmailService;
|
||||
import com.android.emailcommon.service.IEmailServiceCallback;
|
||||
import com.android.emailcommon.service.IEmailServiceCallback.Stub;
|
||||
import com.android.emailcommon.service.SearchParams;
|
||||
import com.android.emailsync.AbstractSyncService;
|
||||
import com.android.emailsync.PartRequest;
|
||||
import com.android.emailsync.SyncManager;
|
||||
import com.android.mail.providers.UIProvider.AccountCapabilities;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class Imap2SyncManager extends SyncManager {
|
||||
|
||||
// Callbacks as set up via setCallback
|
||||
private static final RemoteCallbackList<IEmailServiceCallback> mCallbackList =
|
||||
new RemoteCallbackList<IEmailServiceCallback>();
|
||||
|
||||
private static final EmailServiceCallback sCallbackProxy =
|
||||
new EmailServiceCallback(mCallbackList);
|
||||
|
||||
/**
|
||||
* Create our EmailService implementation here.
|
||||
*/
|
||||
private final IEmailService.Stub mBinder = new IEmailService.Stub() {
|
||||
|
||||
@Override
|
||||
public int getApiLevel() {
|
||||
return Api.LEVEL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle validate(HostAuth hostAuth) throws RemoteException {
|
||||
return new Imap2SyncService(Imap2SyncManager.this,
|
||||
new Mailbox()).validateAccount(hostAuth, Imap2SyncManager.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle autoDiscover(String userName, String password) throws RemoteException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startSync(long mailboxId, boolean userRequest) throws RemoteException {
|
||||
SyncManager imapService = INSTANCE;
|
||||
if (imapService == null) return;
|
||||
Imap2SyncService svc = (Imap2SyncService) imapService.mServiceMap.get(mailboxId);
|
||||
if (svc == null) {
|
||||
startManualSync(mailboxId, userRequest ? SYNC_UI_REQUEST : SYNC_SERVICE_START_SYNC,
|
||||
null);
|
||||
} else {
|
||||
svc.ping();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopSync(long mailboxId) throws RemoteException {
|
||||
stopManualSync(mailboxId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachment(long attachmentId, boolean background) throws RemoteException {
|
||||
Attachment att = Attachment.restoreAttachmentWithId(Imap2SyncManager.this, attachmentId);
|
||||
log("loadAttachment " + attachmentId + ": " + att.mFileName);
|
||||
sendMessageRequest(new PartRequest(att, null, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFolderList(long accountId) throws RemoteException {
|
||||
//***
|
||||
//reloadFolderList(ImapService.this, accountId, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hostChanged(long accountId) throws RemoteException {
|
||||
SyncManager exchangeService = INSTANCE;
|
||||
if (exchangeService == null) return;
|
||||
ConcurrentHashMap<Long, SyncError> syncErrorMap = exchangeService.mSyncErrorMap;
|
||||
// Go through the various error mailboxes
|
||||
for (long mailboxId: syncErrorMap.keySet()) {
|
||||
SyncError error = syncErrorMap.get(mailboxId);
|
||||
// If it's a login failure, look a little harder
|
||||
Mailbox m = Mailbox.restoreMailboxWithId(exchangeService, mailboxId);
|
||||
// If it's for the account whose host has changed, clear the error
|
||||
// If the mailbox is no longer around, remove the entry in the map
|
||||
if (m == null) {
|
||||
syncErrorMap.remove(mailboxId);
|
||||
} else if (error != null && m.mAccountKey == accountId) {
|
||||
error.fatal = false;
|
||||
error.holdEndTime = 0;
|
||||
}
|
||||
}
|
||||
// Stop any running syncs
|
||||
exchangeService.stopAccountSyncs(accountId, true);
|
||||
// Kick ExchangeService
|
||||
kick("host changed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogging(int flags) throws RemoteException {
|
||||
// Protocol logging
|
||||
//Eas.setUserDebug(flags);
|
||||
// Sync logging
|
||||
setUserDebug(flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMeetingResponse(long messageId, int response) throws RemoteException {
|
||||
// Not used in IMAP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMore(long messageId) throws RemoteException {
|
||||
}
|
||||
|
||||
// The following three methods are not implemented in this version
|
||||
@Override
|
||||
public boolean createFolder(long accountId, String name) throws RemoteException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteFolder(long accountId, String name) throws RemoteException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean renameFolder(long accountId, String oldName, String newName)
|
||||
throws RemoteException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCallback(IEmailServiceCallback cb) throws RemoteException {
|
||||
mCallbackList.register(cb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAccountPIMData(long accountId) throws RemoteException {
|
||||
// Not required for IMAP
|
||||
}
|
||||
|
||||
@Override
|
||||
public int searchMessages(long accountId, SearchParams params, long destMailboxId)
|
||||
throws RemoteException {
|
||||
// TODO Auto-generated method stub
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMail(long accountId) throws RemoteException {
|
||||
// Not required for IMAP
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCapabilities(long accountId) throws RemoteException {
|
||||
return AccountCapabilities.SYNCABLE_FOLDERS |
|
||||
AccountCapabilities.FOLDER_SERVER_SEARCH |
|
||||
AccountCapabilities.UNDO;
|
||||
}
|
||||
};
|
||||
|
||||
static public IEmailServiceCallback callback() {
|
||||
return sCallbackProxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountObserver getAccountObserver(Handler handler) {
|
||||
return new AccountObserver(handler) {
|
||||
@Override
|
||||
public void newAccount(long acctId) {
|
||||
// Create the Inbox for the account
|
||||
Account acct = Account.restoreAccountWithId(getContext(), acctId);
|
||||
Mailbox inbox = new Mailbox();
|
||||
inbox.mDisplayName = "Inbox"; // Localize
|
||||
inbox.mServerId = "Inbox";
|
||||
inbox.mAccountKey = acct.mId;
|
||||
inbox.mType = Mailbox.TYPE_INBOX;
|
||||
inbox.mSyncInterval = acct.mSyncInterval;
|
||||
inbox.save(getContext());
|
||||
log("Creating inbox for account: " + acct.mDisplayName);
|
||||
Imap2SyncManager.kick("New account");
|
||||
// Need to sync folder list first; sigh
|
||||
Imap2SyncService svc = new Imap2SyncService(Imap2SyncManager.this, acct);
|
||||
try {
|
||||
svc.loadFolderList();
|
||||
mResolver.update(
|
||||
ContentUris.withAppendedId(EmailContent.PICK_TRASH_FOLDER_URI, acctId),
|
||||
new ContentValues(), null, null);
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartup() {
|
||||
// No special behavior
|
||||
}
|
||||
|
||||
private static final String ACCOUNT_KEY_IN = MailboxColumns.ACCOUNT_KEY + " in (";
|
||||
private String mAccountSelector;
|
||||
@Override
|
||||
public String getAccountsSelector() {
|
||||
if (mAccountSelector == null) {
|
||||
StringBuilder sb = new StringBuilder(ACCOUNT_KEY_IN);
|
||||
boolean first = true;
|
||||
synchronized (mAccountList) {
|
||||
for (Account account : mAccountList) {
|
||||
if (!first) {
|
||||
sb.append(',');
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
sb.append(account.mId);
|
||||
}
|
||||
}
|
||||
sb.append(')');
|
||||
mAccountSelector = sb.toString();
|
||||
}
|
||||
return mAccountSelector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractSyncService getServiceForMailbox(Context context, Mailbox mailbox) {
|
||||
return new Imap2SyncService(context, mailbox);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountList collectAccounts(Context context, AccountList accounts) {
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
Cursor c = resolver.query(Account.CONTENT_URI, Account.CONTENT_PROJECTION, null, null,
|
||||
null);
|
||||
// We must throw here; callers might use the information we provide for reconciliation, etc.
|
||||
if (c == null) throw new ProviderUnavailableException();
|
||||
try {
|
||||
while (c.moveToNext()) {
|
||||
long hostAuthId = c.getLong(Account.CONTENT_HOST_AUTH_KEY_RECV_COLUMN);
|
||||
if (hostAuthId > 0) {
|
||||
HostAuth ha = HostAuth.restoreHostAuthWithId(context, hostAuthId);
|
||||
if (ha != null && ha.mProtocol.equals("imap2")) {
|
||||
Account account = new Account();
|
||||
account.restore(c);
|
||||
account.mHostAuthRecv = ha;
|
||||
accounts.add(account);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
return accounts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccountManagerType() {
|
||||
return "com.android.imap2";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceIntentAction() {
|
||||
return "com.android.email.IMAP2_INTENT";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stub getCallbackProxy() {
|
||||
return sCallbackProxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runAccountReconcilerSync(Context context) {
|
||||
alwaysLog("Reconciling accounts...");
|
||||
new AccountServiceProxy(context).reconcileAccounts("imap2", getAccountManagerType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.imap2;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.emailcommon.Device;
|
||||
import com.android.emailcommon.Logging;
|
||||
import com.android.emailcommon.VendorPolicyLoader;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ImapId {
|
||||
private static String sImapId;
|
||||
|
||||
/**
|
||||
* Return, or create and return, an string suitable for use in an IMAP ID message.
|
||||
* This is constructed similarly to the way the browser sets up its user-agent strings.
|
||||
* See RFC 2971 for more details. The output of this command will be a series of key-value
|
||||
* pairs delimited by spaces (there is no point in returning a structured result because
|
||||
* this will be sent as-is to the IMAP server). No tokens, parenthesis or "ID" are included,
|
||||
* because some connections may append additional values.
|
||||
*
|
||||
* The following IMAP ID keys may be included:
|
||||
* name Android package name of the program
|
||||
* os "android"
|
||||
* os-version "version; model; build-id"
|
||||
* vendor Vendor of the client/server
|
||||
* x-android-device-model Model (only revealed if release build)
|
||||
* x-android-net-operator Mobile network operator (if known)
|
||||
* AGUID A device+account UID
|
||||
*
|
||||
* In addition, a vendor policy .apk can append key/value pairs.
|
||||
*
|
||||
* @param userName the username of the account
|
||||
* @param host the host (server) of the account
|
||||
* @param capabilities a list of the capabilities from the server
|
||||
* @return a String for use in an IMAP ID message.
|
||||
*/
|
||||
public static String getImapId(Context context, String userName, String host,
|
||||
String capabilities) {
|
||||
// The first section is global to all IMAP connections, and generates the fixed
|
||||
// values in any IMAP ID message
|
||||
synchronized (ImapId.class) {
|
||||
if (sImapId == null) {
|
||||
TelephonyManager tm =
|
||||
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
||||
String networkOperator = tm.getNetworkOperatorName();
|
||||
if (networkOperator == null) networkOperator = "";
|
||||
|
||||
sImapId = makeCommonImapId(context.getPackageName(), Build.VERSION.RELEASE,
|
||||
Build.VERSION.CODENAME, Build.MODEL, Build.ID, Build.MANUFACTURER,
|
||||
networkOperator);
|
||||
}
|
||||
}
|
||||
|
||||
// This section is per Store, and adds in a dynamic elements like UID's.
|
||||
// We don't cache the result of this work, because the caller does anyway.
|
||||
StringBuilder id = new StringBuilder(sImapId);
|
||||
|
||||
// Optionally add any vendor-supplied id keys
|
||||
String vendorId = VendorPolicyLoader.getInstance(context).getImapIdValues(userName, host,
|
||||
capabilities);
|
||||
if (vendorId != null) {
|
||||
id.append(' ');
|
||||
id.append(vendorId);
|
||||
}
|
||||
|
||||
// Generate a UID that mixes a "stable" device UID with the email address
|
||||
try {
|
||||
String devUID = Device.getConsistentDeviceId(context);
|
||||
MessageDigest messageDigest;
|
||||
messageDigest = MessageDigest.getInstance("SHA-1");
|
||||
messageDigest.update(userName.getBytes());
|
||||
messageDigest.update(devUID.getBytes());
|
||||
byte[] uid = messageDigest.digest();
|
||||
String hexUid = Base64.encodeToString(uid, Base64.NO_WRAP);
|
||||
id.append(" \"AGUID\" \"");
|
||||
id.append(hexUid);
|
||||
id.append('\"');
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.d(Logging.LOG_TAG, "couldn't obtain SHA-1 hash for device UID");
|
||||
}
|
||||
return id.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that actually builds the static part of the IMAP ID string. This is
|
||||
* separated from getImapId for testability. There is no escaping or encoding in IMAP ID so
|
||||
* any rogue chars must be filtered here.
|
||||
*
|
||||
* @param packageName context.getPackageName()
|
||||
* @param version Build.VERSION.RELEASE
|
||||
* @param codeName Build.VERSION.CODENAME
|
||||
* @param model Build.MODEL
|
||||
* @param id Build.ID
|
||||
* @param vendor Build.MANUFACTURER
|
||||
* @param networkOperator TelephonyManager.getNetworkOperatorName()
|
||||
* @return the static (never changes) portion of the IMAP ID
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static String makeCommonImapId(String packageName, String version,
|
||||
String codeName, String model, String id, String vendor, String networkOperator) {
|
||||
|
||||
// Before building up IMAP ID string, pre-filter the input strings for "legal" chars
|
||||
// This is using a fairly arbitrary char set intended to pass through most reasonable
|
||||
// version, model, and vendor strings: a-z A-Z 0-9 - _ + = ; : . , / <space>
|
||||
// The most important thing is *not* to pass parens, quotes, or CRLF, which would break
|
||||
// the format of the IMAP ID list.
|
||||
Pattern p = Pattern.compile("[^a-zA-Z0-9-_\\+=;:\\.,/ ]");
|
||||
packageName = p.matcher(packageName).replaceAll("");
|
||||
version = p.matcher(version).replaceAll("");
|
||||
codeName = p.matcher(codeName).replaceAll("");
|
||||
model = p.matcher(model).replaceAll("");
|
||||
id = p.matcher(id).replaceAll("");
|
||||
vendor = p.matcher(vendor).replaceAll("");
|
||||
networkOperator = p.matcher(networkOperator).replaceAll("");
|
||||
|
||||
// "name" "com.android.email"
|
||||
StringBuffer sb = new StringBuffer("\"name\" \"");
|
||||
sb.append(packageName);
|
||||
sb.append("\"");
|
||||
|
||||
// "os" "android"
|
||||
sb.append(" \"os\" \"android\"");
|
||||
|
||||
// "os-version" "version; build-id"
|
||||
sb.append(" \"os-version\" \"");
|
||||
if (version.length() > 0) {
|
||||
sb.append(version);
|
||||
} else {
|
||||
// default to "1.0"
|
||||
sb.append("1.0");
|
||||
}
|
||||
// add the build ID or build #
|
||||
if (id.length() > 0) {
|
||||
sb.append("; ");
|
||||
sb.append(id);
|
||||
}
|
||||
sb.append("\"");
|
||||
|
||||
// "vendor" "the vendor"
|
||||
if (vendor.length() > 0) {
|
||||
sb.append(" \"vendor\" \"");
|
||||
sb.append(vendor);
|
||||
sb.append("\"");
|
||||
}
|
||||
|
||||
// "x-android-device-model" the device model (on release builds only)
|
||||
if ("REL".equals(codeName)) {
|
||||
if (model.length() > 0) {
|
||||
sb.append(" \"x-android-device-model\" \"");
|
||||
sb.append(model);
|
||||
sb.append("\"");
|
||||
}
|
||||
}
|
||||
|
||||
// "x-android-mobile-net-operator" "name of network operator"
|
||||
if (networkOperator.length() > 0) {
|
||||
sb.append(" \"x-android-mobile-net-operator\" \"");
|
||||
sb.append(networkOperator);
|
||||
sb.append("\"");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.imap2;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class ImapInputStream extends FilterInputStream {
|
||||
|
||||
public ImapInputStream(InputStream in) {
|
||||
super(in);
|
||||
}
|
||||
|
||||
public String readLine () throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (true) {
|
||||
int b = read();
|
||||
// Line ends with \n; ignore \r
|
||||
// I'm not sure this is the right thing with a raw \r (no \n following)
|
||||
if (b < 0)
|
||||
throw new IOException("Socket closed in readLine");
|
||||
if (b == '\n')
|
||||
return sb.toString();
|
||||
else if (b != '\r') {
|
||||
sb.append((char)b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean ready () throws IOException {
|
||||
return this.available() > 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.imap2;
|
||||
|
||||
public class Parser {
|
||||
String str;
|
||||
int pos;
|
||||
int len;
|
||||
static final String white = "\r\n \t";
|
||||
|
||||
public Parser (String _str) {
|
||||
str = _str;
|
||||
pos = 0;
|
||||
len = str.length();
|
||||
}
|
||||
|
||||
public Parser (String _str, int start) {
|
||||
str = _str;
|
||||
pos = start;
|
||||
len = str.length();
|
||||
}
|
||||
|
||||
public void skipWhite () {
|
||||
while ((pos < len) && white.indexOf(str.charAt(pos)) >= 0)
|
||||
pos++;
|
||||
}
|
||||
|
||||
public String parseAtom () {
|
||||
skipWhite();
|
||||
int start = pos;
|
||||
while ((pos < len) && white.indexOf(str.charAt(pos)) < 0)
|
||||
pos++;
|
||||
if (pos > start)
|
||||
return str.substring(start, pos);
|
||||
return null;
|
||||
}
|
||||
|
||||
public char nextChar () {
|
||||
if (pos >= len)
|
||||
return 0;
|
||||
else
|
||||
return str.charAt(pos++);
|
||||
}
|
||||
|
||||
public char peekChar () {
|
||||
if (pos >= len)
|
||||
return 0;
|
||||
else
|
||||
return str.charAt(pos);
|
||||
}
|
||||
|
||||
public String parseString () {
|
||||
return parseString(false);
|
||||
}
|
||||
|
||||
public String parseStringOrAtom () {
|
||||
return parseString(true);
|
||||
}
|
||||
|
||||
public String parseString (boolean orAtom) {
|
||||
skipWhite();
|
||||
char c = nextChar();
|
||||
if (c != '\"') {
|
||||
if (c == '{') {
|
||||
int cnt = parseInteger();
|
||||
c = nextChar();
|
||||
if (c != '}')
|
||||
return null;
|
||||
int start = pos + 2;
|
||||
int end = start + cnt;
|
||||
String s = str.substring(start, end);
|
||||
pos = end;
|
||||
return s;
|
||||
} else if (orAtom) {
|
||||
backChar();
|
||||
return parseAtom();
|
||||
} else if (c == 'n' || c == 'N') {
|
||||
parseAtom();
|
||||
return null;
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
int start = pos;
|
||||
boolean quote = false;
|
||||
while (true) {
|
||||
c = nextChar();
|
||||
if (c == 0)
|
||||
return null;
|
||||
else if (quote)
|
||||
quote = false;
|
||||
else if (c == '\\')
|
||||
quote = true;
|
||||
else if (c == '\"')
|
||||
break;
|
||||
}
|
||||
return str.substring(start, pos - 1);
|
||||
}
|
||||
|
||||
public void backChar () {
|
||||
if (pos > 0)
|
||||
pos--;
|
||||
}
|
||||
|
||||
public String parseListOrNil () {
|
||||
String list = parseList();
|
||||
if (list == null) {
|
||||
parseAtom();
|
||||
list = "";
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public String parseList () {
|
||||
skipWhite();
|
||||
if (nextChar() != '(') {
|
||||
backChar();
|
||||
return null;
|
||||
}
|
||||
int start = pos;
|
||||
int level = 0;
|
||||
boolean quote = false;
|
||||
boolean string = false;
|
||||
while (true) {
|
||||
char c = nextChar();
|
||||
if (c == 0)
|
||||
return null;
|
||||
else if (quote)
|
||||
quote = false;
|
||||
else if (c == '\\' && string)
|
||||
quote = true;
|
||||
else if (c == '\"')
|
||||
string = !string;
|
||||
else if (c == '(' && !string)
|
||||
level++;
|
||||
else if (c == ')' && !string) {
|
||||
if (level-- == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return str.substring(start, pos - 1);
|
||||
}
|
||||
|
||||
public Integer parseInteger () {
|
||||
skipWhite();
|
||||
int start = pos;
|
||||
while (pos < len) {
|
||||
char c = str.charAt(pos);
|
||||
if (c >= '0' && c <= '9')
|
||||
pos++;
|
||||
else
|
||||
break;
|
||||
}
|
||||
if (pos > start) {
|
||||
try {
|
||||
Integer i = Integer.parseInt(str.substring(start, pos));
|
||||
return i;
|
||||
} catch (NumberFormatException e) {
|
||||
return -1;
|
||||
}
|
||||
} else
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int[] gatherInts () {
|
||||
int[] list = new int[128];
|
||||
int size = 128;
|
||||
int offs = 0;
|
||||
while (true) {
|
||||
// TODO Slow; handle this inline rather than calling the method
|
||||
Integer i = parseInteger();
|
||||
if (i >= 0) {
|
||||
if (offs == size) {
|
||||
// Double the size of the array as necessary
|
||||
size <<= 1;
|
||||
int[] tmp = new int[size];
|
||||
System.arraycopy(list, 0, tmp, 0, offs);
|
||||
list = tmp;
|
||||
}
|
||||
list[offs++] = i;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
int[] res = new int[offs];
|
||||
System.arraycopy(list, 0, res, 0, offs);
|
||||
return res;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.imap2;
|
||||
|
||||
public class QuotedPrintable {
|
||||
static public String toString (String str) {
|
||||
int len = str.length();
|
||||
// Make sure we don't get an index out of bounds error with the = character
|
||||
int max = len - 2;
|
||||
StringBuilder sb = new StringBuilder(len);
|
||||
try {
|
||||
for (int i = 0; i < len; i++) {
|
||||
char c = str.charAt(i);
|
||||
if (c == '=') {
|
||||
if (i < max) {
|
||||
char n = str.charAt(++i);
|
||||
if (n == '\r') {
|
||||
n = str.charAt(++i);
|
||||
if (n == '\n')
|
||||
continue;
|
||||
else
|
||||
System.err.println("Not valid QP");
|
||||
} else {
|
||||
// Must be less than 0x80, right?
|
||||
int a;
|
||||
if (n >= '0' && n <= '9')
|
||||
a = (n - '0') << 4;
|
||||
else
|
||||
a = (10 + (n - 'A')) << 4;
|
||||
|
||||
n = str.charAt(++i);
|
||||
if (n >= '0' && n <= '9')
|
||||
c = (char) (a + (n - '0'));
|
||||
else
|
||||
c = (char) (a + 10 + (n - 'A'));
|
||||
}
|
||||
} if (i + 1 == len)
|
||||
continue;
|
||||
}
|
||||
|
||||
sb.append(c);
|
||||
}
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
}
|
||||
String ret = sb.toString();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static public String encode (String str) {
|
||||
int len = str.length();
|
||||
StringBuffer sb = new StringBuffer(len + len>>2);
|
||||
int i = 0;
|
||||
while (i < len) {
|
||||
char c = str.charAt(i++);
|
||||
if (c < 0x80) {
|
||||
sb.append(c);
|
||||
} else {
|
||||
sb.append('&');
|
||||
sb.append('#');
|
||||
sb.append((int)c);
|
||||
sb.append(';');
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
static public int decode (byte[] bytes, int len) {
|
||||
// Make sure we don't get an index out of bounds error with the = character
|
||||
int max = len - 2;
|
||||
int pos = 0;
|
||||
try {
|
||||
for (int i = 0; i < len; i++) {
|
||||
char c = (char)bytes[i];
|
||||
if (c == '=') {
|
||||
if (i < max) {
|
||||
char n = (char)bytes[++i];
|
||||
if (n == '\r') {
|
||||
n = (char)bytes[++i];
|
||||
if (n == '\n')
|
||||
continue;
|
||||
else
|
||||
System.err.println("Not valid QP");
|
||||
} else {
|
||||
// Must be less than 0x80, right?
|
||||
int a;
|
||||
if (n >= '0' && n <= '9')
|
||||
a = (n - '0') << 4;
|
||||
else
|
||||
a = (10 + (n - 'A')) << 4;
|
||||
|
||||
n = (char)bytes[++i];
|
||||
if (n >= '0' && n <= '9')
|
||||
c = (char) (a + (n - '0'));
|
||||
else
|
||||
c = (char) (a + 10 + (n - 'A'));
|
||||
}
|
||||
} if (i + 1 > len)
|
||||
continue;
|
||||
}
|
||||
|
||||
bytes[pos++] = (byte)c;
|
||||
}
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
}
|
|
@ -1300,5 +1300,13 @@ as <xliff:g id="filename">%s</xliff:g>.</string>
|
|||
|
||||
<!-- Displayed in the middle of the screen when the inbox is empty [CHAR LIMIT 100]-->
|
||||
<string name="no_conversations">No messages.</string>
|
||||
|
||||
<!-- Temporary; used by AccountManager -->
|
||||
<string name="imap2_name">Push IMAP</string>
|
||||
|
||||
<string name="folder_picker_title">Picky, picky, picky!</string>
|
||||
<string name="trash_folder_selection_title">Select trash folder</string>
|
||||
<string name="create_new_folder">Create folder</string>
|
||||
|
||||
</resources>
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/**
|
||||
* Copyright (c) 2009, The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
-->
|
||||
|
||||
<!-- The attributes in this XML file provide configuration information -->
|
||||
<!-- for the Account Manager. -->
|
||||
|
||||
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:accountType="com.android.imap2"
|
||||
android:icon="@mipmap/ic_launcher_mail"
|
||||
android:smallIcon="@drawable/stat_notify_email_generic"
|
||||
android:label="@string/imap2_name"
|
||||
android:accountPreferences="@xml/account_preferences"
|
||||
/>
|
|
@ -146,6 +146,11 @@
|
|||
|
||||
<!-- USA -->
|
||||
|
||||
<provider id="eon" label="EON" domain="nextobject.com">
|
||||
<incoming uri="imap2://imap.everyone.net" username="$email" />
|
||||
<outgoing uri="smtp://smtp.everyone.net" username="$email" />
|
||||
</provider>
|
||||
|
||||
<!-- AOL and variants -->
|
||||
<!-- Note, AOL appears to support SSL on both sides, as well -->
|
||||
<provider id="aim" label="AIM" domain="aim.com">
|
||||
|
|
|
@ -101,4 +101,21 @@
|
|||
email:syncContacts="true"
|
||||
email:syncCalendar="true"
|
||||
/>
|
||||
<emailservice
|
||||
email:protocol="imap2"
|
||||
email:name="Push IMAP"
|
||||
email:accountType="com.android.imap2"
|
||||
email:intent="com.android.email.IMAP2_INTENT"
|
||||
email:port="143"
|
||||
email:portSsl="993"
|
||||
email:syncIntervalStrings="@array/account_settings_check_frequency_entries_push"
|
||||
email:syncIntervals="@array/account_settings_check_frequency_values_push"
|
||||
email:defaultSyncInterval="push"
|
||||
|
||||
email:offerTls="true"
|
||||
email:usesSmtp="true"
|
||||
email:offerAttachmentPreload="true"
|
||||
email:offerPrefix="true"
|
||||
email:syncChanges="true"
|
||||
/>
|
||||
</emailservices>
|
||||
|
|
|
@ -26,9 +26,10 @@ import android.util.Log;
|
|||
import android.widget.EditText;
|
||||
|
||||
import com.android.email.R;
|
||||
import com.android.email.VendorPolicyLoader;
|
||||
import com.android.email.provider.AccountBackupRestore;
|
||||
import com.android.emailcommon.Logging;
|
||||
import com.android.emailcommon.VendorPolicyLoader;
|
||||
import com.android.emailcommon.VendorPolicyLoader.Provider;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.provider.HostAuth;
|
||||
import com.android.emailcommon.provider.EmailContent.AccountColumns;
|
||||
|
@ -231,50 +232,6 @@ public class AccountSettingsUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public static class Provider implements Serializable {
|
||||
private static final long serialVersionUID = 8511656164616538989L;
|
||||
|
||||
public String id;
|
||||
public String label;
|
||||
public String domain;
|
||||
public String incomingUriTemplate;
|
||||
public String incomingUsernameTemplate;
|
||||
public String outgoingUriTemplate;
|
||||
public String outgoingUsernameTemplate;
|
||||
public String incomingUri;
|
||||
public String incomingUsername;
|
||||
public String outgoingUri;
|
||||
public String outgoingUsername;
|
||||
public String note;
|
||||
|
||||
/**
|
||||
* Expands templates in all of the provider fields that support them. Currently,
|
||||
* templates are used in 4 fields -- incoming and outgoing URI and user name.
|
||||
* @param email user-specified data used to replace template values
|
||||
*/
|
||||
public void expandTemplates(String email) {
|
||||
String[] emailParts = email.split("@");
|
||||
String user = emailParts[0];
|
||||
|
||||
incomingUri = expandTemplate(incomingUriTemplate, email, user);
|
||||
incomingUsername = expandTemplate(incomingUsernameTemplate, email, user);
|
||||
outgoingUri = expandTemplate(outgoingUriTemplate, email, user);
|
||||
outgoingUsername = expandTemplate(outgoingUsernameTemplate, email, user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all parameterized values in the given template. The values replaced are
|
||||
* $domain, $user and $email.
|
||||
*/
|
||||
private String expandTemplate(String template, String email, String user) {
|
||||
String returnString = template;
|
||||
returnString = returnString.replaceAll("\\$email", email);
|
||||
returnString = returnString.replaceAll("\\$user", user);
|
||||
returnString = returnString.replaceAll("\\$domain", domain);
|
||||
return returnString;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Infer potential email server addresses from domain names
|
||||
*
|
||||
|
|
|
@ -45,10 +45,10 @@ import com.android.email.EmailAddressValidator;
|
|||
import com.android.email.R;
|
||||
import com.android.email.activity.ActivityHelper;
|
||||
import com.android.email.activity.UiUtilities;
|
||||
import com.android.email.activity.setup.AccountSettingsUtils.Provider;
|
||||
import com.android.email.service.EmailServiceUtils;
|
||||
import com.android.email.service.EmailServiceUtils.EmailServiceInfo;
|
||||
import com.android.emailcommon.Logging;
|
||||
import com.android.emailcommon.VendorPolicyLoader.Provider;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.provider.EmailContent;
|
||||
import com.android.emailcommon.provider.HostAuth;
|
||||
|
|
|
@ -44,7 +44,6 @@ import com.android.email.service.MailService;
|
|||
import com.android.email2.ui.MailActivityEmail;
|
||||
import com.android.emailcommon.Logging;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.provider.HostAuth;
|
||||
import com.android.emailcommon.provider.Policy;
|
||||
import com.android.emailcommon.service.SyncWindow;
|
||||
import com.android.emailcommon.utility.Utility;
|
||||
|
@ -244,6 +243,7 @@ public class AccountSetupOptions extends AccountSetupActivity implements OnClick
|
|||
final boolean email2 = email;
|
||||
final boolean calendar2 = calendar;
|
||||
final boolean contacts2 = contacts;
|
||||
|
||||
Utility.runAsync(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
|
|
@ -27,7 +27,6 @@ import android.util.Log;
|
|||
import com.android.email.LegacyConversions;
|
||||
import com.android.email.Preferences;
|
||||
import com.android.email.R;
|
||||
import com.android.email.VendorPolicyLoader;
|
||||
import com.android.email.mail.Store;
|
||||
import com.android.email.mail.Transport;
|
||||
import com.android.email.mail.store.imap.ImapConstants;
|
||||
|
@ -35,6 +34,7 @@ import com.android.email.mail.store.imap.ImapResponse;
|
|||
import com.android.email.mail.store.imap.ImapString;
|
||||
import com.android.email.mail.transport.MailTransport;
|
||||
import com.android.emailcommon.Logging;
|
||||
import com.android.emailcommon.VendorPolicyLoader;
|
||||
import com.android.emailcommon.internet.MimeMessage;
|
||||
import com.android.emailcommon.mail.AuthenticationFailedException;
|
||||
import com.android.emailcommon.mail.Flag;
|
||||
|
@ -182,8 +182,8 @@ public class ImapStore extends Store {
|
|||
* @param capabilities a list of the capabilities from the server
|
||||
* @return a String for use in an IMAP ID message.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static String getImapId(Context context, String userName, String host, String capabilities) {
|
||||
public static String getImapId(Context context, String userName, String host,
|
||||
String capabilities) {
|
||||
// The first section is global to all IMAP connections, and generates the fixed
|
||||
// values in any IMAP ID message
|
||||
synchronized (ImapStore.class) {
|
||||
|
|
|
@ -126,8 +126,9 @@ public final class DBHelper {
|
|||
// Version 100 is first Email2 version
|
||||
// Version 101 SHOULD NOT BE USED
|
||||
// Version 102&103: Add hierarchicalName to Mailbox
|
||||
// Version 104&105: add syncData to Message
|
||||
|
||||
public static final int DATABASE_VERSION = 103;
|
||||
public static final int DATABASE_VERSION = 105;
|
||||
|
||||
// Any changes to the database format *must* include update-in-place code.
|
||||
// Original version: 2
|
||||
|
@ -172,7 +173,8 @@ public final class DBHelper {
|
|||
+ MessageColumns.MEETING_INFO + " text, "
|
||||
+ MessageColumns.SNIPPET + " text, "
|
||||
+ MessageColumns.PROTOCOL_SEARCH_INFO + " text, "
|
||||
+ MessageColumns.THREAD_TOPIC + " text"
|
||||
+ MessageColumns.THREAD_TOPIC + " text, "
|
||||
+ MessageColumns.SYNC_DATA + " text"
|
||||
+ ");";
|
||||
|
||||
// This String and the following String MUST have the same columns, except for the type
|
||||
|
@ -968,10 +970,32 @@ public final class DBHelper {
|
|||
+ " add " + MailboxColumns.HIERARCHICAL_NAME + " text");
|
||||
} catch (SQLException e) {
|
||||
// Shouldn't be needed unless we're debugging and interrupt the process
|
||||
Log.w(TAG, "Exception upgrading EmailProviderBody.db from v6 to v8", e);
|
||||
Log.w(TAG, "Exception upgrading EmailProviderBody.db from v10x to v103", e);
|
||||
}
|
||||
oldVersion = 103;
|
||||
}
|
||||
if (oldVersion == 103) {
|
||||
try {
|
||||
db.execSQL("alter table " + Message.TABLE_NAME
|
||||
+ " add " + MessageColumns.SYNC_DATA + " text");
|
||||
} catch (SQLException e) {
|
||||
// Shouldn't be needed unless we're debugging and interrupt the process
|
||||
Log.w(TAG, "Exception upgrading EmailProviderBody.db from v103 to v104", e);
|
||||
}
|
||||
oldVersion = 104;
|
||||
}
|
||||
if (oldVersion == 104) {
|
||||
try {
|
||||
db.execSQL("alter table " + Message.UPDATED_TABLE_NAME
|
||||
+ " add " + MessageColumns.SYNC_DATA + " text");
|
||||
db.execSQL("alter table " + Message.DELETED_TABLE_NAME
|
||||
+ " add " + MessageColumns.SYNC_DATA + " text");
|
||||
} catch (SQLException e) {
|
||||
// Shouldn't be needed unless we're debugging and interrupt the process
|
||||
Log.w(TAG, "Exception upgrading EmailProviderBody.db from v104 to v105", e);
|
||||
}
|
||||
oldVersion = 105;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -77,12 +77,17 @@ import com.android.emailcommon.service.IEmailServiceCallback;
|
|||
import com.android.emailcommon.service.SearchParams;
|
||||
import com.android.emailcommon.utility.AttachmentUtilities;
|
||||
import com.android.emailcommon.utility.Utility;
|
||||
import com.android.mail.providers.Conversation;
|
||||
import com.android.mail.providers.Folder;
|
||||
import com.android.mail.providers.UIProvider;
|
||||
import com.android.mail.providers.UIProvider.AccountCapabilities;
|
||||
import com.android.mail.providers.UIProvider.AccountCursorExtraKeys;
|
||||
import com.android.mail.providers.UIProvider.ConversationPriority;
|
||||
import com.android.mail.providers.UIProvider.ConversationSendingState;
|
||||
import com.android.mail.providers.UIProvider.DraftType;
|
||||
import com.android.mail.ui.ConversationUpdater;
|
||||
import com.android.mail.ui.DestructiveAction;
|
||||
import com.android.mail.ui.FoldersSelectionDialog;
|
||||
import com.android.mail.utils.LogUtils;
|
||||
import com.android.mail.utils.MatrixCursorWithExtra;
|
||||
import com.android.mail.utils.Utils;
|
||||
|
@ -181,6 +186,7 @@ public class EmailProvider extends ContentProvider {
|
|||
private static final int ACCOUNT_RESET_NEW_COUNT_ID = ACCOUNT_BASE + 4;
|
||||
private static final int ACCOUNT_DEFAULT_ID = ACCOUNT_BASE + 5;
|
||||
private static final int ACCOUNT_CHECK = ACCOUNT_BASE + 6;
|
||||
private static final int ACCOUNT_PICK_TRASH_FOLDER = ACCOUNT_BASE + 7;
|
||||
|
||||
private static final int MAILBOX_BASE = 0x1000;
|
||||
private static final int MAILBOX = MAILBOX_BASE;
|
||||
|
@ -194,6 +200,7 @@ public class EmailProvider extends ContentProvider {
|
|||
private static final int MESSAGE = MESSAGE_BASE;
|
||||
private static final int MESSAGE_ID = MESSAGE_BASE + 1;
|
||||
private static final int SYNCED_MESSAGE_ID = MESSAGE_BASE + 2;
|
||||
private static final int SYNCED_MESSAGE_SELECTION = MESSAGE_BASE + 3;
|
||||
|
||||
private static final int ATTACHMENT_BASE = 0x3000;
|
||||
private static final int ATTACHMENT = ATTACHMENT_BASE;
|
||||
|
@ -412,6 +419,8 @@ public class EmailProvider extends ContentProvider {
|
|||
* TO A SERVER VIA A SYNC ADAPTER
|
||||
*/
|
||||
matcher.addURI(EmailContent.AUTHORITY, "syncedMessage/#", SYNCED_MESSAGE_ID);
|
||||
matcher.addURI(EmailContent.AUTHORITY, "syncedMessageSelection",
|
||||
SYNCED_MESSAGE_SELECTION);
|
||||
|
||||
/**
|
||||
* THE URIs BELOW THIS POINT ARE INTENDED TO BE USED BY SYNC ADAPTERS ONLY
|
||||
|
@ -465,6 +474,7 @@ public class EmailProvider extends ContentProvider {
|
|||
matcher.addURI(EmailContent.AUTHORITY, "uirecentfolders/#", UI_RECENT_FOLDERS);
|
||||
matcher.addURI(EmailContent.AUTHORITY, "uidefaultrecentfolders/#",
|
||||
UI_DEFAULT_RECENT_FOLDERS);
|
||||
matcher.addURI(EmailContent.AUTHORITY, "pickTrashFolder/#", ACCOUNT_PICK_TRASH_FOLDER);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -784,6 +794,21 @@ public class EmailProvider extends ContentProvider {
|
|||
return uiDeleteAccountData(uri);
|
||||
case UI_ACCOUNT:
|
||||
return uiDeleteAccount(uri);
|
||||
case SYNCED_MESSAGE_SELECTION:
|
||||
Cursor findCursor = db.query(tableName, Message.ID_COLUMN_PROJECTION, selection,
|
||||
selectionArgs, null, null, null);
|
||||
try {
|
||||
if (findCursor.moveToFirst()) {
|
||||
return delete(ContentUris.withAppendedId(
|
||||
Message.SYNCED_CONTENT_URI,
|
||||
findCursor.getLong(Message.ID_COLUMNS_ID_COLUMN)),
|
||||
null, null);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} finally {
|
||||
findCursor.close();
|
||||
}
|
||||
// These are cases in which one or more Messages might get deleted, either by
|
||||
// cascade or explicitly
|
||||
case MAILBOX_ID:
|
||||
|
@ -1627,13 +1652,10 @@ public class EmailProvider extends ContentProvider {
|
|||
String id = "0";
|
||||
|
||||
try {
|
||||
if (match == MESSAGE_ID || match == SYNCED_MESSAGE_ID) {
|
||||
if (!uri.getBooleanQueryParameter(IS_UIPROVIDER, false)) {
|
||||
notifyUIConversation(uri);
|
||||
}
|
||||
}
|
||||
outer:
|
||||
switch (match) {
|
||||
case ACCOUNT_PICK_TRASH_FOLDER:
|
||||
return pickTrashFolder(uri);
|
||||
case UI_FOLDER:
|
||||
return uiUpdateFolder(uri, values);
|
||||
case UI_RECENT_FOLDERS:
|
||||
|
@ -1706,6 +1728,21 @@ outer:
|
|||
}
|
||||
}
|
||||
break;
|
||||
case SYNCED_MESSAGE_SELECTION:
|
||||
Cursor findCursor = db.query(tableName, Message.ID_COLUMN_PROJECTION, selection,
|
||||
selectionArgs, null, null, null);
|
||||
try {
|
||||
if (findCursor.moveToFirst()) {
|
||||
return update(ContentUris.withAppendedId(
|
||||
Message.SYNCED_CONTENT_URI,
|
||||
findCursor.getLong(Message.ID_COLUMNS_ID_COLUMN)),
|
||||
values, null, null);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} finally {
|
||||
findCursor.close();
|
||||
}
|
||||
case SYNCED_MESSAGE_ID:
|
||||
case UPDATED_MESSAGE_ID:
|
||||
case MESSAGE_ID:
|
||||
|
@ -1741,7 +1778,11 @@ outer:
|
|||
cache.unlock(id, values);
|
||||
}
|
||||
}
|
||||
if (match == ATTACHMENT_ID) {
|
||||
if (match == MESSAGE_ID || match == SYNCED_MESSAGE_ID) {
|
||||
if (!uri.getBooleanQueryParameter(IS_UIPROVIDER, false)) {
|
||||
notifyUIConversation(uri);
|
||||
}
|
||||
} else if (match == ATTACHMENT_ID) {
|
||||
long attId = Integer.parseInt(id);
|
||||
if (values.containsKey(Attachment.FLAGS)) {
|
||||
int flags = values.getAsInteger(Attachment.FLAGS);
|
||||
|
@ -3698,8 +3739,12 @@ outer:
|
|||
return update(ourUri, ourValues, null, null);
|
||||
}
|
||||
|
||||
public static final String PICKER_UI_ACCOUNT = "picker_ui_account";
|
||||
public static final String PICKER_MAILBOX_TYPE = "picker_mailbox_type";
|
||||
public static final String PICKER_MESSAGE_ID = "picker_message_id";
|
||||
|
||||
private int uiDeleteMessage(Uri uri) {
|
||||
Context context = getContext();
|
||||
final Context context = getContext();
|
||||
Message msg = getMessageFromLastSegment(uri);
|
||||
if (msg == null) return 0;
|
||||
Mailbox mailbox = Mailbox.restoreMailboxWithId(context, msg.mMailboxKey);
|
||||
|
@ -3709,17 +3754,42 @@ outer:
|
|||
AttachmentUtilities.deleteAllAttachmentFiles(context, msg.mAccountKey, msg.mId);
|
||||
notifyUI(UIPROVIDER_FOLDER_NOTIFIER, mailbox.mId);
|
||||
return context.getContentResolver().delete(
|
||||
ContentUris.withAppendedId(Message.CONTENT_URI, msg.mId), null, null);
|
||||
ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, msg.mId), null, null);
|
||||
}
|
||||
Mailbox trashMailbox =
|
||||
Mailbox.restoreMailboxOfType(context, msg.mAccountKey, Mailbox.TYPE_TRASH);
|
||||
if (trashMailbox == null) return 0;
|
||||
if (trashMailbox == null) {
|
||||
return 0;
|
||||
}
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(MessageColumns.MAILBOX_KEY, trashMailbox.mId);
|
||||
notifyUI(UIPROVIDER_FOLDER_NOTIFIER, mailbox.mId);
|
||||
return uiUpdateMessage(uri, values);
|
||||
}
|
||||
|
||||
private int pickTrashFolder(Uri uri) {
|
||||
Context context = getContext();
|
||||
Long acctId = Long.parseLong(uri.getLastPathSegment());
|
||||
// For push imap, for example, we want the user to select the trash mailbox
|
||||
Cursor ac = query(uiUri("uiaccount", acctId), UIProvider.ACCOUNTS_PROJECTION,
|
||||
null, null, null);
|
||||
try {
|
||||
if (ac.moveToFirst()) {
|
||||
final com.android.mail.providers.Account uiAccount =
|
||||
new com.android.mail.providers.Account(ac);
|
||||
Intent intent = new Intent(context, FolderPickerActivity.class);
|
||||
intent.putExtra(PICKER_UI_ACCOUNT, uiAccount);
|
||||
intent.putExtra(PICKER_MAILBOX_TYPE, Mailbox.TYPE_TRASH);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
} finally {
|
||||
ac.close();
|
||||
}
|
||||
}
|
||||
|
||||
private Cursor uiUndo(String[] projection) {
|
||||
// First see if we have any operations saved
|
||||
// TODO: Make sure seq matches
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Google Inc.
|
||||
* Licensed to The Android Open Source Project.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.email.provider;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.android.emailcommon.provider.Mailbox;
|
||||
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
|
||||
import com.android.mail.providers.Folder;
|
||||
|
||||
public class FolderPickerActivity extends Activity implements FolderPickerCallback {
|
||||
private long mAccountId;
|
||||
private int mMailboxType;
|
||||
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
Intent i = getIntent();
|
||||
com.android.mail.providers.Account account =
|
||||
i.getParcelableExtra(EmailProvider.PICKER_UI_ACCOUNT);
|
||||
mAccountId = Long.parseLong(account.uri.getLastPathSegment());
|
||||
mMailboxType = i.getIntExtra(EmailProvider.PICKER_MAILBOX_TYPE, -1);
|
||||
new FolderSelectionDialog(this, account, this).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void select(Folder folder) {
|
||||
String folderId = folder.uri.getLastPathSegment();
|
||||
Long id = Long.parseLong(folderId);
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
// If we already have a mailbox of this type, change it back to generic mail type
|
||||
Mailbox ofType = Mailbox.restoreMailboxOfType(this, mAccountId, mMailboxType);
|
||||
if (ofType != null) {
|
||||
values.put(MailboxColumns.TYPE, Mailbox.TYPE_MAIL);
|
||||
getContentResolver().update(
|
||||
ContentUris.withAppendedId(Mailbox.CONTENT_URI, ofType.mId), values,
|
||||
null, null);
|
||||
}
|
||||
|
||||
// Change this mailbox to be of the desired type
|
||||
Mailbox mailbox = Mailbox.restoreMailboxWithId(this, id);
|
||||
if (mailbox != null) {
|
||||
values.put(MailboxColumns.TYPE, mMailboxType);
|
||||
getContentResolver().update(
|
||||
ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailbox.mId), values,
|
||||
null, null);
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void create() {
|
||||
// TODO: Not sure about this...
|
||||
finish();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Google Inc.
|
||||
* Licensed to The Android Open Source Project.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.email.provider;
|
||||
|
||||
import com.android.mail.providers.Folder;
|
||||
|
||||
public interface FolderPickerCallback {
|
||||
public void select(Folder folder);
|
||||
public void create();
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Google Inc.
|
||||
* Licensed to The Android Open Source Project.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.email.provider;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.DialogInterface.OnMultiChoiceClickListener;
|
||||
import android.database.Cursor;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
|
||||
import com.android.mail.R;
|
||||
import com.android.mail.providers.Account;
|
||||
import com.android.mail.providers.Folder;
|
||||
import com.android.mail.providers.UIProvider;
|
||||
import com.android.mail.ui.FolderSelectorAdapter;
|
||||
import com.android.mail.ui.FolderSelectorAdapter.FolderRow;
|
||||
import com.android.mail.ui.HierarchicalFolderSelectorAdapter;
|
||||
import com.android.mail.ui.SeparatedFolderListAdapter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class FolderSelectionDialog implements OnClickListener, OnMultiChoiceClickListener {
|
||||
private AlertDialog mDialog;
|
||||
private HashMap<Folder, Boolean> mCheckedState;
|
||||
private SeparatedFolderListAdapter mAdapter;
|
||||
final private FolderPickerCallback mCallback;
|
||||
|
||||
public FolderSelectionDialog(final Context context, Account account,
|
||||
FolderPickerCallback callback) {
|
||||
mCallback = callback;
|
||||
// Mapping of a folder's uri to its checked state
|
||||
mCheckedState = new HashMap<Folder, Boolean>();
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(R.string.trash_folder_selection_title);
|
||||
builder.setPositiveButton(R.string.ok, this);
|
||||
builder.setNegativeButton(R.string.create_new_folder, this);
|
||||
final Cursor foldersCursor = context.getContentResolver().query(
|
||||
account.fullFolderListUri != null ? account.fullFolderListUri
|
||||
: account.folderListUri, UIProvider.FOLDERS_PROJECTION, null, null, null);
|
||||
try {
|
||||
mAdapter = new SeparatedFolderListAdapter(context);
|
||||
String[] headers = context.getResources()
|
||||
.getStringArray(R.array.moveto_folder_sections);
|
||||
mAdapter.addSection(headers[2], new HierarchicalFolderSelectorAdapter(context,
|
||||
foldersCursor, new HashSet<String>(), true));
|
||||
builder.setAdapter(mAdapter, this);
|
||||
} finally {
|
||||
foldersCursor.close();
|
||||
}
|
||||
mDialog = builder.create();
|
||||
}
|
||||
|
||||
public void show() {
|
||||
mDialog.show();
|
||||
mDialog.getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Object item = mAdapter.getItem(position);
|
||||
if (item instanceof FolderRow) {
|
||||
update((FolderRow) item);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this to update the state of folders as a result of them being
|
||||
* selected / de-selected.
|
||||
*
|
||||
* @param row The item being updated.
|
||||
*/
|
||||
public void update(FolderSelectorAdapter.FolderRow row) {
|
||||
// Update the UI
|
||||
final boolean add = !row.isPresent();
|
||||
if (!add) {
|
||||
// This would remove the check on a single radio button, so just
|
||||
// return.
|
||||
return;
|
||||
}
|
||||
// Clear any other checked items.
|
||||
mAdapter.getCount();
|
||||
for (int i = 0; i < mAdapter.getCount(); i++) {
|
||||
Object item = mAdapter.getItem(i);
|
||||
if (item instanceof FolderRow) {
|
||||
((FolderRow)item).setIsPresent(false);
|
||||
}
|
||||
}
|
||||
mCheckedState.clear();
|
||||
row.setIsPresent(add);
|
||||
mAdapter.notifyDataSetChanged();
|
||||
mCheckedState.put(row.getFolder(), add);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which) {
|
||||
case DialogInterface.BUTTON_POSITIVE:
|
||||
Folder folder = null;
|
||||
for (Entry<Folder, Boolean> entry : mCheckedState.entrySet()) {
|
||||
if (entry.getValue()) {
|
||||
folder = entry.getKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
mCallback.select(folder);
|
||||
break;
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
mCallback.create();
|
||||
break;
|
||||
default:
|
||||
onClick(dialog, which, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
|
||||
final FolderRow row = (FolderRow) mAdapter.getItem(which);
|
||||
// Clear any other checked items.
|
||||
mCheckedState.clear();
|
||||
isChecked = true;
|
||||
mCheckedState.put(row.getFolder(), isChecked);
|
||||
mDialog.getListView().setItemChecked(which, false);
|
||||
}
|
||||
}
|
|
@ -26,11 +26,11 @@ import android.os.IBinder;
|
|||
|
||||
import com.android.email.NotificationController;
|
||||
import com.android.email.ResourceHelper;
|
||||
import com.android.email.VendorPolicyLoader;
|
||||
import com.android.email.provider.AccountReconciler;
|
||||
import com.android.email2.ui.MailActivityEmail;
|
||||
import com.android.emailcommon.Configuration;
|
||||
import com.android.emailcommon.Device;
|
||||
import com.android.emailcommon.VendorPolicyLoader;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.provider.HostAuth;
|
||||
import com.android.emailcommon.service.IAccountService;
|
||||
|
|
|
@ -32,9 +32,9 @@ import android.util.Log;
|
|||
import com.android.email.NotificationController;
|
||||
import com.android.email.Preferences;
|
||||
import com.android.email.SecurityPolicy;
|
||||
import com.android.email.VendorPolicyLoader;
|
||||
import com.android.email.activity.setup.AccountSettings;
|
||||
import com.android.emailcommon.Logging;
|
||||
import com.android.emailcommon.VendorPolicyLoader;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.provider.EmailContent.AccountColumns;
|
||||
import com.android.emailcommon.provider.HostAuth;
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.email.service;
|
||||
|
||||
/**
|
||||
* This service needs to be declared separately from the base service
|
||||
*/
|
||||
public class Imap2AuthenticatorService extends AuthenticatorService {
|
||||
}
|
|
@ -56,6 +56,7 @@ import com.android.emailcommon.provider.EmailContent.MailboxColumns;
|
|||
import com.android.emailcommon.provider.EmailContent.MessageColumns;
|
||||
import com.android.emailcommon.provider.EmailContent.SyncColumns;
|
||||
import com.android.emailcommon.provider.Mailbox;
|
||||
import com.android.emailcommon.service.EmailServiceCallback;
|
||||
import com.android.emailcommon.service.EmailServiceStatus;
|
||||
import com.android.emailcommon.service.IEmailServiceCallback;
|
||||
import com.android.emailcommon.service.SearchParams;
|
||||
|
@ -106,102 +107,8 @@ public class ImapService extends Service {
|
|||
private static final RemoteCallbackList<IEmailServiceCallback> mCallbackList =
|
||||
new RemoteCallbackList<IEmailServiceCallback>();
|
||||
|
||||
private interface ServiceCallbackWrapper {
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy that can be used by various sync adapters to tie into ExchangeService's callback system
|
||||
* Used this way: ExchangeService.callback().callbackMethod(args...);
|
||||
* The proxy wraps checking for existence of a ExchangeService instance
|
||||
* Failures of these callbacks can be safely ignored.
|
||||
*/
|
||||
static private final IEmailServiceCallback.Stub sCallbackProxy =
|
||||
new IEmailServiceCallback.Stub() {
|
||||
|
||||
/**
|
||||
* Broadcast a callback to the everyone that's registered
|
||||
*
|
||||
* @param wrapper the ServiceCallbackWrapper used in the broadcast
|
||||
*/
|
||||
private synchronized void broadcastCallback(ServiceCallbackWrapper wrapper) {
|
||||
RemoteCallbackList<IEmailServiceCallback> callbackList = mCallbackList;
|
||||
if (callbackList != null) {
|
||||
// Call everyone on our callback list
|
||||
int count = callbackList.beginBroadcast();
|
||||
try {
|
||||
for (int i = 0; i < count; i++) {
|
||||
try {
|
||||
wrapper.call(callbackList.getBroadcastItem(i));
|
||||
} catch (RemoteException e) {
|
||||
// Safe to ignore
|
||||
} catch (RuntimeException e) {
|
||||
// We don't want an exception in one call to prevent other calls, so
|
||||
// we'll just log this and continue
|
||||
Log.e(TAG, "Caught RuntimeException in broadcast", e);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// No matter what, we need to finish the broadcast
|
||||
callbackList.finishBroadcast();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachmentStatus(final long messageId, final long attachmentId,
|
||||
final int status, final int progress) {
|
||||
broadcastCallback(new ServiceCallbackWrapper() {
|
||||
@Override
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException {
|
||||
cb.loadAttachmentStatus(messageId, attachmentId, status, progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMessageStatus(final long messageId, final int status, final int progress) {
|
||||
broadcastCallback(new ServiceCallbackWrapper() {
|
||||
@Override
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException {
|
||||
cb.loadMessageStatus(messageId, status, progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessageStatus(final long messageId, final String subject, final int status,
|
||||
final int progress) {
|
||||
broadcastCallback(new ServiceCallbackWrapper() {
|
||||
@Override
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException {
|
||||
cb.sendMessageStatus(messageId, subject, status, progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncMailboxListStatus(final long accountId, final int status,
|
||||
final int progress) {
|
||||
broadcastCallback(new ServiceCallbackWrapper() {
|
||||
@Override
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException {
|
||||
cb.syncMailboxListStatus(accountId, status, progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncMailboxStatus(final long mailboxId, final int status,
|
||||
final int progress) {
|
||||
broadcastCallback(new ServiceCallbackWrapper() {
|
||||
@Override
|
||||
public void call(IEmailServiceCallback cb) throws RemoteException {
|
||||
cb.syncMailboxStatus(mailboxId, status, progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
private static final EmailServiceCallback sCallbackProxy =
|
||||
new EmailServiceCallback(mCallbackList);
|
||||
|
||||
/**
|
||||
* Create our EmailService implementation here.
|
||||
|
@ -243,10 +150,7 @@ public class ImapService extends Service {
|
|||
}
|
||||
|
||||
private static void sendMailboxStatus(Mailbox mailbox, int status) {
|
||||
try {
|
||||
sCallbackProxy.syncMailboxStatus(mailbox.mId, status, 0);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
sCallbackProxy.syncMailboxStatus(mailbox.mId, status, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
# Copyright 2010 Google Inc. All Rights Reserved.
|
||||
#Fri Jul 16 10:03:09 PDT 2010
|
||||
currentVersion=1.0.0
|
||||
version=1.0.0
|
||||
isNative=false
|
||||
name=utf7_support
|
||||
keywords=utf7 support
|
||||
onDevice=true
|
||||
homepage=http\://sourceforge.net/projects/jutf7/
|
Loading…
Reference in New Issue