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" />
|
<meta-data android:name="android.app.searchable" android:resource="@xml/searchable" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".provider.FolderPickerActivity"
|
||||||
|
android:label="@string/folder_picker_title"
|
||||||
|
>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<!-- Must be exported in order for the AccountManager to launch it -->
|
<!-- Must be exported in order for the AccountManager to launch it -->
|
||||||
<!-- Also available for continuous test systems to force account creation -->
|
<!-- Also available for continuous test systems to force account creation -->
|
||||||
<activity
|
<activity
|
||||||
@ -508,7 +514,20 @@
|
|||||||
android:resource="@xml/authenticator_alternate"
|
android:resource="@xml/authenticator_alternate"
|
||||||
/>
|
/>
|
||||||
</service>
|
</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
|
<provider
|
||||||
android:name=".provider.AttachmentProvider"
|
android:name=".provider.AttachmentProvider"
|
||||||
android:authorities="com.android.email.attachmentprovider"
|
android:authorities="com.android.email.attachmentprovider"
|
||||||
|
@ -14,10 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.android.email;
|
package com.android.emailcommon;
|
||||||
|
|
||||||
import com.android.email.activity.setup.AccountSettingsUtils.Provider;
|
|
||||||
import com.android.emailcommon.Logging;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
@ -25,6 +22,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -193,6 +191,50 @@ public class VendorPolicyLoader {
|
|||||||
return result;
|
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
|
* 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 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 =
|
public static final Uri MAILBOX_NOTIFICATION_URI =
|
||||||
Uri.parse("content://" + EmailContent.AUTHORITY + "/mailboxNotification");
|
Uri.parse("content://" + EmailContent.AUTHORITY + "/mailboxNotification");
|
||||||
public static final String[] NOTIFICATION_PROJECTION =
|
public static final String[] NOTIFICATION_PROJECTION =
|
||||||
@ -543,6 +546,8 @@ public abstract class EmailContent {
|
|||||||
public static final String PROTOCOL_SEARCH_INFO = "protocolSearchInfo";
|
public static final String PROTOCOL_SEARCH_INFO = "protocolSearchInfo";
|
||||||
// Simple thread topic
|
// Simple thread topic
|
||||||
public static final String THREAD_TOPIC = "threadTopic";
|
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 {
|
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";
|
public static final String DELETED_TABLE_NAME = "Message_Deletes";
|
||||||
|
|
||||||
// To refer to a specific message, use ContentUris.withAppendedId(CONTENT_URI, id)
|
// 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 = Uri.parse(EmailContent.CONTENT_URI + "/message");
|
||||||
public static final Uri CONTENT_URI_LIMIT_1 = uriWithLimit(CONTENT_URI, 1);
|
public static final Uri CONTENT_URI_LIMIT_1 = uriWithLimit(CONTENT_URI, 1);
|
||||||
public static final Uri SYNCED_CONTENT_URI =
|
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 =
|
public static final Uri DELETED_CONTENT_URI =
|
||||||
Uri.parse(EmailContent.CONTENT_URI + "/deletedMessage");
|
Uri.parse(EmailContent.CONTENT_URI + "/deletedMessage");
|
||||||
public static final Uri UPDATED_CONTENT_URI =
|
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_SNIPPET_COLUMN = 21;
|
||||||
public static final int CONTENT_PROTOCOL_SEARCH_INFO_COLUMN = 22;
|
public static final int CONTENT_PROTOCOL_SEARCH_INFO_COLUMN = 22;
|
||||||
public static final int CONTENT_THREAD_TOPIC_COLUMN = 23;
|
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[] {
|
public static final String[] CONTENT_PROJECTION = new String[] {
|
||||||
RECORD_ID,
|
RECORD_ID,
|
||||||
@ -603,7 +610,7 @@ public abstract class EmailContent {
|
|||||||
MessageColumns.BCC_LIST, MessageColumns.REPLY_TO_LIST,
|
MessageColumns.BCC_LIST, MessageColumns.REPLY_TO_LIST,
|
||||||
SyncColumns.SERVER_TIMESTAMP, MessageColumns.MEETING_INFO,
|
SyncColumns.SERVER_TIMESTAMP, MessageColumns.MEETING_INFO,
|
||||||
MessageColumns.SNIPPET, MessageColumns.PROTOCOL_SEARCH_INFO,
|
MessageColumns.SNIPPET, MessageColumns.PROTOCOL_SEARCH_INFO,
|
||||||
MessageColumns.THREAD_TOPIC
|
MessageColumns.THREAD_TOPIC, MessageColumns.SYNC_DATA
|
||||||
};
|
};
|
||||||
|
|
||||||
public static final int LIST_ID_COLUMN = 0;
|
public static final int LIST_ID_COLUMN = 0;
|
||||||
@ -740,6 +747,8 @@ public abstract class EmailContent {
|
|||||||
|
|
||||||
public String mThreadTopic;
|
public String mThreadTopic;
|
||||||
|
|
||||||
|
public String mSyncData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base64-encoded representation of the byte array provided by servers for identifying
|
* Base64-encoded representation of the byte array provided by servers for identifying
|
||||||
* messages belonging to the same conversation thread. Currently unsupported and not
|
* 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_FAVORITE, mFlagFavorite);
|
||||||
values.put(MessageColumns.FLAG_ATTACHMENT, mFlagAttachment);
|
values.put(MessageColumns.FLAG_ATTACHMENT, mFlagAttachment);
|
||||||
values.put(MessageColumns.FLAGS, mFlags);
|
values.put(MessageColumns.FLAGS, mFlags);
|
||||||
|
|
||||||
values.put(SyncColumns.SERVER_ID, mServerId);
|
values.put(SyncColumns.SERVER_ID, mServerId);
|
||||||
values.put(SyncColumns.SERVER_TIMESTAMP, mServerTimeStamp);
|
values.put(SyncColumns.SERVER_TIMESTAMP, mServerTimeStamp);
|
||||||
values.put(MessageColumns.DRAFT_INFO, mDraftInfo);
|
values.put(MessageColumns.DRAFT_INFO, mDraftInfo);
|
||||||
values.put(MessageColumns.MESSAGE_ID, mMessageId);
|
values.put(MessageColumns.MESSAGE_ID, mMessageId);
|
||||||
|
|
||||||
values.put(MessageColumns.MAILBOX_KEY, mMailboxKey);
|
values.put(MessageColumns.MAILBOX_KEY, mMailboxKey);
|
||||||
values.put(MessageColumns.ACCOUNT_KEY, mAccountKey);
|
values.put(MessageColumns.ACCOUNT_KEY, mAccountKey);
|
||||||
|
|
||||||
values.put(MessageColumns.FROM_LIST, mFrom);
|
values.put(MessageColumns.FROM_LIST, mFrom);
|
||||||
values.put(MessageColumns.TO_LIST, mTo);
|
values.put(MessageColumns.TO_LIST, mTo);
|
||||||
values.put(MessageColumns.CC_LIST, mCc);
|
values.put(MessageColumns.CC_LIST, mCc);
|
||||||
values.put(MessageColumns.BCC_LIST, mBcc);
|
values.put(MessageColumns.BCC_LIST, mBcc);
|
||||||
values.put(MessageColumns.REPLY_TO_LIST, mReplyTo);
|
values.put(MessageColumns.REPLY_TO_LIST, mReplyTo);
|
||||||
|
|
||||||
values.put(MessageColumns.MEETING_INFO, mMeetingInfo);
|
values.put(MessageColumns.MEETING_INFO, mMeetingInfo);
|
||||||
|
|
||||||
values.put(MessageColumns.SNIPPET, mSnippet);
|
values.put(MessageColumns.SNIPPET, mSnippet);
|
||||||
|
|
||||||
values.put(MessageColumns.PROTOCOL_SEARCH_INFO, mProtocolSearchInfo);
|
values.put(MessageColumns.PROTOCOL_SEARCH_INFO, mProtocolSearchInfo);
|
||||||
|
|
||||||
values.put(MessageColumns.THREAD_TOPIC, mThreadTopic);
|
values.put(MessageColumns.THREAD_TOPIC, mThreadTopic);
|
||||||
|
values.put(MessageColumns.SYNC_DATA, mSyncData);
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -889,6 +892,7 @@ public abstract class EmailContent {
|
|||||||
mSnippet = cursor.getString(CONTENT_SNIPPET_COLUMN);
|
mSnippet = cursor.getString(CONTENT_SNIPPET_COLUMN);
|
||||||
mProtocolSearchInfo = cursor.getString(CONTENT_PROTOCOL_SEARCH_INFO_COLUMN);
|
mProtocolSearchInfo = cursor.getString(CONTENT_PROTOCOL_SEARCH_INFO_COLUMN);
|
||||||
mThreadTopic = cursor.getString(CONTENT_THREAD_TOPIC_COLUMN);
|
mThreadTopic = cursor.getString(CONTENT_THREAD_TOPIC_COLUMN);
|
||||||
|
mSyncData = cursor.getString(CONTENT_SYNC_DATA_COLUMN);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean update() {
|
public boolean update() {
|
||||||
@ -991,14 +995,16 @@ public abstract class EmailContent {
|
|||||||
if (mQuotedTextStartPos != 0) {
|
if (mQuotedTextStartPos != 0) {
|
||||||
cv.put(Body.QUOTED_TEXT_START_POS, mQuotedTextStartPos);
|
cv.put(Body.QUOTED_TEXT_START_POS, mQuotedTextStartPos);
|
||||||
}
|
}
|
||||||
|
// We'll need this if we're new
|
||||||
|
int messageBackValue = ops.size() - 1;
|
||||||
|
// Only create a body if we've got some data
|
||||||
|
if (!cv.keySet().isEmpty()) {
|
||||||
b = ContentProviderOperation.newInsert(Body.CONTENT_URI);
|
b = ContentProviderOperation.newInsert(Body.CONTENT_URI);
|
||||||
// Put our message id in the Body
|
// Put our message id in the Body
|
||||||
if (!isNew) {
|
if (!isNew) {
|
||||||
cv.put(Body.MESSAGE_KEY, mId);
|
cv.put(Body.MESSAGE_KEY, mId);
|
||||||
}
|
}
|
||||||
b.withValues(cv);
|
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 we're new, create a back value entry
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
ContentValues backValues = new ContentValues();
|
ContentValues backValues = new ContentValues();
|
||||||
@ -1007,6 +1013,7 @@ public abstract class EmailContent {
|
|||||||
}
|
}
|
||||||
// And add the Body operation
|
// And add the Body operation
|
||||||
ops.add(b.build());
|
ops.add(b.build());
|
||||||
|
}
|
||||||
|
|
||||||
// Create the attaachments, if any
|
// Create the attaachments, if any
|
||||||
if (mAttachments != null) {
|
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_2_WEEKS = 4;
|
||||||
public static final int SYNC_WINDOW_1_MONTH = 5;
|
public static final int SYNC_WINDOW_1_MONTH = 5;
|
||||||
public static final int SYNC_WINDOW_ALL = 6;
|
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();
|
ContentResolver cr = context.getContentResolver();
|
||||||
|
|
||||||
// Get a selector for EAS accounts (we don't want to sync on changes to POP/IMAP messages)
|
// 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 {
|
try {
|
||||||
// Find all of the deletions
|
// Find all of the deletions
|
||||||
@ -102,7 +102,7 @@ public class EmailSyncAlarmReceiver extends BroadcastReceiver {
|
|||||||
|
|
||||||
// Request service from the mailbox
|
// Request service from the mailbox
|
||||||
for (Long mailboxId: mailboxesToNotify) {
|
for (Long mailboxId: mailboxesToNotify) {
|
||||||
SyncServiceManager.serviceRequest(mailboxId, SyncServiceManager.SYNC_UPSYNC);
|
SyncManager.serviceRequest(mailboxId, SyncManager.SYNC_UPSYNC);
|
||||||
}
|
}
|
||||||
} catch (ProviderUnavailableException e) {
|
} catch (ProviderUnavailableException e) {
|
||||||
Log.e("EmailSyncAlarmReceiver", "EmailProvider unavailable; aborting alarm receiver");
|
Log.e("EmailSyncAlarmReceiver", "EmailProvider unavailable; aborting alarm receiver");
|
||||||
|
@ -30,12 +30,12 @@ import android.content.Intent;
|
|||||||
public class MailboxAlarmReceiver extends BroadcastReceiver {
|
public class MailboxAlarmReceiver extends BroadcastReceiver {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
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
|
// EXCHANGE_SERVICE_MAILBOX_ID tells us that the service is asking to be started
|
||||||
if (mailboxId == SyncServiceManager.SYNC_SERVICE_MAILBOX_ID) {
|
if (mailboxId == SyncManager.SYNC_SERVICE_MAILBOX_ID) {
|
||||||
context.startService(new Intent(context, SyncServiceManager.class));
|
context.startService(new Intent(context, SyncManager.class));
|
||||||
} else {
|
} else {
|
||||||
SyncServiceManager.alert(context, mailboxId);
|
SyncManager.alert(context, mailboxId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
42
emailsync/src/com/android/emailsync/MessageMoveRequest.java
Normal file
42
emailsync/src/com/android/emailsync/MessageMoveRequest.java
Normal file
@ -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;
|
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
|
* the attachment to be loaded, it also contains the callback to be used for status/progress
|
||||||
* updates to the UI.
|
* 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.AccountServiceProxy;
|
||||||
import com.android.emailcommon.service.EmailServiceProxy;
|
import com.android.emailcommon.service.EmailServiceProxy;
|
||||||
import com.android.emailcommon.service.EmailServiceStatus;
|
import com.android.emailcommon.service.EmailServiceStatus;
|
||||||
import com.android.emailcommon.service.IEmailServiceCallback;
|
|
||||||
import com.android.emailcommon.service.IEmailServiceCallback.Stub;
|
import com.android.emailcommon.service.IEmailServiceCallback.Stub;
|
||||||
import com.android.emailcommon.service.PolicyServiceProxy;
|
import com.android.emailcommon.service.PolicyServiceProxy;
|
||||||
import com.android.emailcommon.utility.EmailAsyncTask;
|
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)
|
* 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";
|
private static final String TAG = "SyncServiceManager";
|
||||||
|
|
||||||
@ -201,7 +200,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||||||
public ContentResolver mResolver;
|
public ContentResolver mResolver;
|
||||||
|
|
||||||
// The singleton SyncServiceManager object, with its thread and stop flag
|
// The singleton SyncServiceManager object, with its thread and stop flag
|
||||||
protected static SyncServiceManager INSTANCE;
|
protected static SyncManager INSTANCE;
|
||||||
protected static Thread sServiceThread = null;
|
protected static Thread sServiceThread = null;
|
||||||
// Cached unique device id
|
// Cached unique device id
|
||||||
protected static String sDeviceId = null;
|
protected static String sDeviceId = null;
|
||||||
@ -338,7 +337,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String getAccountSelector() {
|
public static String getAccountSelector() {
|
||||||
SyncServiceManager ssm = INSTANCE;
|
SyncManager ssm = INSTANCE;
|
||||||
if (ssm == null) return "";
|
if (ssm == null) return "";
|
||||||
return ssm.getAccountsSelector();
|
return ssm.getAccountsSelector();
|
||||||
}
|
}
|
||||||
@ -380,8 +379,8 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||||||
if (onSecurityHold(account)) {
|
if (onSecurityHold(account)) {
|
||||||
// If we're in a security hold, and our policies are active, release
|
// If we're in a security hold, and our policies are active, release
|
||||||
// the hold
|
// the hold
|
||||||
if (PolicyServiceProxy.isActive(SyncServiceManager.this, null)) {
|
if (PolicyServiceProxy.isActive(SyncManager.this, null)) {
|
||||||
PolicyServiceProxy.setAccountHoldFlag(SyncServiceManager.this,
|
PolicyServiceProxy.setAccountHoldFlag(SyncManager.this,
|
||||||
account, false);
|
account, false);
|
||||||
log("isActive true; release hold for " + account.mDisplayName);
|
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
|
// The implication is that the account has been deleted; let's find out
|
||||||
alwaysLog("Observer found deleted account: " + account.mDisplayName);
|
alwaysLog("Observer found deleted account: " + account.mDisplayName);
|
||||||
// Run the reconciler (the reconciliation itself runs in the Email app)
|
// Run the reconciler (the reconciliation itself runs in the Email app)
|
||||||
runAccountReconcilerSync(SyncServiceManager.this);
|
runAccountReconcilerSync(SyncManager.this);
|
||||||
// See if the account is still around
|
// See if the account is still around
|
||||||
Account deletedAccount =
|
Account deletedAccount =
|
||||||
Account.restoreAccountWithId(context, account.mId);
|
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
|
// See if this account is no longer on security hold
|
||||||
if (onSecurityHold(account) && !onSecurityHold(updatedAccount)) {
|
if (onSecurityHold(account) && !onSecurityHold(updatedAccount)) {
|
||||||
releaseSyncHolds(SyncServiceManager.this,
|
releaseSyncHolds(SyncManager.this,
|
||||||
AbstractSyncService.EXIT_SECURITY_FAILURE, account);
|
AbstractSyncService.EXIT_SECURITY_FAILURE, account);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -548,7 +547,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||||||
* Unregister all CalendarObserver's
|
* Unregister all CalendarObserver's
|
||||||
*/
|
*/
|
||||||
static public void unregisterCalendarObservers() {
|
static public void unregisterCalendarObservers() {
|
||||||
SyncServiceManager ssm = INSTANCE;
|
SyncManager ssm = INSTANCE;
|
||||||
if (ssm == null) return;
|
if (ssm == null) return;
|
||||||
ContentResolver resolver = ssm.mResolver;
|
ContentResolver resolver = ssm.mResolver;
|
||||||
for (CalendarObserver observer: ssm.mCalendarObservers.values()) {
|
for (CalendarObserver observer: ssm.mCalendarObservers.values()) {
|
||||||
@ -718,7 +717,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static public Account getAccountById(long accountId) {
|
static public Account getAccountById(long accountId) {
|
||||||
SyncServiceManager ssm = INSTANCE;
|
SyncManager ssm = INSTANCE;
|
||||||
if (ssm != null) {
|
if (ssm != null) {
|
||||||
AccountList accountList = ssm.mAccountList;
|
AccountList accountList = ssm.mAccountList;
|
||||||
synchronized (accountList) {
|
synchronized (accountList) {
|
||||||
@ -729,7 +728,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static public Account getAccountByName(String accountName) {
|
static public Account getAccountByName(String accountName) {
|
||||||
SyncServiceManager ssm = INSTANCE;
|
SyncManager ssm = INSTANCE;
|
||||||
if (ssm != null) {
|
if (ssm != null) {
|
||||||
AccountList accountList = ssm.mAccountList;
|
AccountList accountList = ssm.mAccountList;
|
||||||
synchronized (accountList) {
|
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
|
* @param account the account whose Mailboxes should be released from security hold
|
||||||
*/
|
*/
|
||||||
static public void releaseSecurityHold(Account account) {
|
static public void releaseSecurityHold(Account account) {
|
||||||
SyncServiceManager ssm = INSTANCE;
|
SyncManager ssm = INSTANCE;
|
||||||
if (ssm != null) {
|
if (ssm != null) {
|
||||||
ssm.releaseSyncHolds(INSTANCE, AbstractSyncService.EXIT_SECURITY_FAILURE,
|
ssm.releaseSyncHolds(INSTANCE, AbstractSyncService.EXIT_SECURITY_FAILURE,
|
||||||
account);
|
account);
|
||||||
@ -910,7 +909,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void stopAccountSyncs(long acctId) {
|
public static void stopAccountSyncs(long acctId) {
|
||||||
SyncServiceManager ssm = INSTANCE;
|
SyncManager ssm = INSTANCE;
|
||||||
if (ssm != null) {
|
if (ssm != null) {
|
||||||
ssm.stopAccountSyncs(acctId, true);
|
ssm.stopAccountSyncs(acctId, true);
|
||||||
}
|
}
|
||||||
@ -957,7 +956,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||||||
* @param acctId
|
* @param acctId
|
||||||
*/
|
*/
|
||||||
static public void stopNonAccountMailboxSyncsForAccount(long acctId) {
|
static public void stopNonAccountMailboxSyncsForAccount(long acctId) {
|
||||||
SyncServiceManager ssm = INSTANCE;
|
SyncManager ssm = INSTANCE;
|
||||||
if (ssm != null) {
|
if (ssm != null) {
|
||||||
ssm.stopAccountSyncs(acctId, false);
|
ssm.stopAccountSyncs(acctId, false);
|
||||||
kick("reload folder list");
|
kick("reload folder list");
|
||||||
@ -1051,7 +1050,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static public void runAwake(long id) {
|
static public void runAwake(long id) {
|
||||||
SyncServiceManager ssm = INSTANCE;
|
SyncManager ssm = INSTANCE;
|
||||||
if (ssm != null) {
|
if (ssm != null) {
|
||||||
ssm.acquireWakeLock(id);
|
ssm.acquireWakeLock(id);
|
||||||
ssm.clearAlarm(id);
|
ssm.clearAlarm(id);
|
||||||
@ -1059,7 +1058,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static public void runAsleep(long id, long millis) {
|
static public void runAsleep(long id, long millis) {
|
||||||
SyncServiceManager ssm = INSTANCE;
|
SyncManager ssm = INSTANCE;
|
||||||
if (ssm != null) {
|
if (ssm != null) {
|
||||||
ssm.setAlarm(id, millis);
|
ssm.setAlarm(id, millis);
|
||||||
ssm.releaseWakeLock(id);
|
ssm.releaseWakeLock(id);
|
||||||
@ -1067,27 +1066,27 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static public void clearWatchdogAlarm(long id) {
|
static public void clearWatchdogAlarm(long id) {
|
||||||
SyncServiceManager ssm = INSTANCE;
|
SyncManager ssm = INSTANCE;
|
||||||
if (ssm != null) {
|
if (ssm != null) {
|
||||||
ssm.clearAlarm(id);
|
ssm.clearAlarm(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static public void setWatchdogAlarm(long id, long millis) {
|
static public void setWatchdogAlarm(long id, long millis) {
|
||||||
SyncServiceManager ssm = INSTANCE;
|
SyncManager ssm = INSTANCE;
|
||||||
if (ssm != null) {
|
if (ssm != null) {
|
||||||
ssm.setAlarm(id, millis);
|
ssm.setAlarm(id, millis);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static public void alert(Context context, final long id) {
|
static public void alert(Context context, final long id) {
|
||||||
final SyncServiceManager ssm = INSTANCE;
|
final SyncManager ssm = INSTANCE;
|
||||||
checkSyncServiceManagerServiceRunning();
|
checkSyncServiceManagerServiceRunning();
|
||||||
if (id < 0) {
|
if (id < 0) {
|
||||||
log("SyncServiceManager alert");
|
log("SyncServiceManager alert");
|
||||||
kick("ping SyncServiceManager");
|
kick("ping SyncServiceManager");
|
||||||
} else if (ssm == null) {
|
} else if (ssm == null) {
|
||||||
context.startService(new Intent(context, SyncServiceManager.class));
|
context.startService(new Intent(context, SyncManager.class));
|
||||||
} else {
|
} else {
|
||||||
final AbstractSyncService service = ssm.mServiceMap.get(id);
|
final AbstractSyncService service = ssm.mServiceMap.get(id);
|
||||||
if (service != null) {
|
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
|
// Shutdown the connection manager; this should close all of our
|
||||||
// sockets and generate IOExceptions all around.
|
// sockets and generate IOExceptions all around.
|
||||||
SyncServiceManager.shutdownConnectionManager();
|
SyncManager.shutdownConnectionManager();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}, threadName).start();
|
}}, threadName).start();
|
||||||
@ -1177,7 +1176,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||||||
public void run() {
|
public void run() {
|
||||||
synchronized (mAccountList) {
|
synchronized (mAccountList) {
|
||||||
for (Account account : 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 {
|
try {
|
||||||
synchronized (sSyncLock) {
|
synchronized (sSyncLock) {
|
||||||
// SyncServiceManager cannot start unless we connect to AccountService
|
// 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");
|
alwaysLog("!!! Email application not found; stopping self");
|
||||||
stopSelf();
|
stopSelf();
|
||||||
}
|
}
|
||||||
if (sDeviceId == null) {
|
if (sDeviceId == null) {
|
||||||
try {
|
try {
|
||||||
String deviceId = getDeviceId(SyncServiceManager.this);
|
String deviceId = getDeviceId(SyncManager.this);
|
||||||
if (deviceId != null) {
|
if (deviceId != null) {
|
||||||
sDeviceId = deviceId;
|
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
|
// Run the reconciler and clean up mismatched accounts - if we weren't
|
||||||
// running when accounts were deleted, it won't have been called.
|
// 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
|
// Update other services depending on final account configuration
|
||||||
maybeStartSyncServiceManagerThread();
|
maybeStartSyncServiceManagerThread();
|
||||||
if (sServiceThread == null) {
|
if (sServiceThread == null) {
|
||||||
@ -1450,7 +1449,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void reconcileAccounts(Context context) {
|
public static void reconcileAccounts(Context context) {
|
||||||
SyncServiceManager ssm = INSTANCE;
|
SyncManager ssm = INSTANCE;
|
||||||
if (ssm != null) {
|
if (ssm != null) {
|
||||||
ssm.runAccountReconcilerSync(context);
|
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
|
* com.android.email) and hasn't been restarted. See the comment for onCreate for details
|
||||||
*/
|
*/
|
||||||
static void checkSyncServiceManagerServiceRunning() {
|
static void checkSyncServiceManagerServiceRunning() {
|
||||||
SyncServiceManager ssm = INSTANCE;
|
SyncManager ssm = INSTANCE;
|
||||||
if (ssm == null) return;
|
if (ssm == null) return;
|
||||||
if (sServiceThread == null) {
|
if (sServiceThread == null) {
|
||||||
log("!!! checkSyncServiceManagerServiceRunning; starting service...");
|
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
|
// process starts running again, remote processes will be started again in due course
|
||||||
Log.e(TAG, "EmailProvider unavailable; shutting down");
|
Log.e(TAG, "EmailProvider unavailable; shutting down");
|
||||||
// Ask for our service to be restarted; this should kick-start the Email process as well
|
// 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) {
|
} catch (RuntimeException e) {
|
||||||
// Crash; this is a completely unexpected runtime error
|
// Crash; this is a completely unexpected runtime error
|
||||||
Log.e(TAG, "RuntimeException in SyncServiceManager", e);
|
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
|
* @return whether or not the account can sync automatically
|
||||||
*/
|
*/
|
||||||
/*package*/ public static boolean canAutoSync(Account account) {
|
/*package*/ public static boolean canAutoSync(Account account) {
|
||||||
SyncServiceManager ssm = INSTANCE;
|
SyncManager ssm = INSTANCE;
|
||||||
if (ssm == null) {
|
if (ssm == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1971,7 +1970,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static public void serviceRequest(long mailboxId, long ms, int reason) {
|
static public void serviceRequest(long mailboxId, long ms, int reason) {
|
||||||
SyncServiceManager ssm = INSTANCE;
|
SyncManager ssm = INSTANCE;
|
||||||
if (ssm == null) return;
|
if (ssm == null) return;
|
||||||
Mailbox m = Mailbox.restoreMailboxWithId(ssm, mailboxId);
|
Mailbox m = Mailbox.restoreMailboxWithId(ssm, mailboxId);
|
||||||
if (m == null || !isSyncable(m)) return;
|
if (m == null || !isSyncable(m)) return;
|
||||||
@ -1989,7 +1988,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static public void serviceRequestImmediate(long mailboxId) {
|
static public void serviceRequestImmediate(long mailboxId) {
|
||||||
SyncServiceManager ssm = INSTANCE;
|
SyncManager ssm = INSTANCE;
|
||||||
if (ssm == null) return;
|
if (ssm == null) return;
|
||||||
AbstractSyncService service = ssm.mServiceMap.get(mailboxId);
|
AbstractSyncService service = ssm.mServiceMap.get(mailboxId);
|
||||||
if (service != null) {
|
if (service != null) {
|
||||||
@ -2004,7 +2003,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static public void sendMessageRequest(Request req) {
|
static public void sendMessageRequest(Request req) {
|
||||||
SyncServiceManager ssm = INSTANCE;
|
SyncManager ssm = INSTANCE;
|
||||||
Message msg = Message.restoreMessageWithId(ssm, req.mMessageId);
|
Message msg = Message.restoreMessageWithId(ssm, req.mMessageId);
|
||||||
if (msg == null) return;
|
if (msg == null) return;
|
||||||
long mailboxId = msg.mMailboxKey;
|
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)
|
* @return whether or not the Mailbox is available for syncing (i.e. is a valid push target)
|
||||||
*/
|
*/
|
||||||
static public int pingStatus(long mailboxId) {
|
static public int pingStatus(long mailboxId) {
|
||||||
SyncServiceManager ssm = INSTANCE;
|
SyncManager ssm = INSTANCE;
|
||||||
if (ssm == null) return PING_STATUS_OK;
|
if (ssm == null) return PING_STATUS_OK;
|
||||||
// Already syncing...
|
// Already syncing...
|
||||||
if (ssm.mServiceMap.get(mailboxId) != null) {
|
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) {
|
static public void startManualSync(long mailboxId, int reason, Request req) {
|
||||||
SyncServiceManager ssm = INSTANCE;
|
SyncManager ssm = INSTANCE;
|
||||||
if (ssm == null) return;
|
if (ssm == null) return;
|
||||||
synchronized (sSyncLock) {
|
synchronized (sSyncLock) {
|
||||||
AbstractSyncService svc = ssm.mServiceMap.get(mailboxId);
|
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
|
// DO NOT CALL THIS IN A LOOP ON THE SERVICEMAP
|
||||||
static public void stopManualSync(long mailboxId) {
|
static public void stopManualSync(long mailboxId) {
|
||||||
SyncServiceManager ssm = INSTANCE;
|
SyncManager ssm = INSTANCE;
|
||||||
if (ssm == null) return;
|
if (ssm == null) return;
|
||||||
synchronized (sSyncLock) {
|
synchronized (sSyncLock) {
|
||||||
AbstractSyncService svc = ssm.mServiceMap.get(mailboxId);
|
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
|
* Wake up SyncServiceManager to check for mailboxes needing service
|
||||||
*/
|
*/
|
||||||
static public void kick(String reason) {
|
static public void kick(String reason) {
|
||||||
SyncServiceManager ssm = INSTANCE;
|
SyncManager ssm = INSTANCE;
|
||||||
if (ssm != null) {
|
if (ssm != null) {
|
||||||
synchronized (ssm) {
|
synchronized (ssm) {
|
||||||
//INSTANCE.log("Kick: " + reason);
|
//INSTANCE.log("Kick: " + reason);
|
||||||
@ -2122,7 +2121,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||||||
* @param mailboxId the id of the mailbox
|
* @param mailboxId the id of the mailbox
|
||||||
*/
|
*/
|
||||||
static public void removeFromSyncErrorMap(long mailboxId) {
|
static public void removeFromSyncErrorMap(long mailboxId) {
|
||||||
SyncServiceManager ssm = INSTANCE;
|
SyncManager ssm = INSTANCE;
|
||||||
if (ssm != null) {
|
if (ssm != null) {
|
||||||
ssm.mSyncErrorMap.remove(mailboxId);
|
ssm.mSyncErrorMap.remove(mailboxId);
|
||||||
}
|
}
|
||||||
@ -2142,7 +2141,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||||||
* @param svc the service that is finished
|
* @param svc the service that is finished
|
||||||
*/
|
*/
|
||||||
static public void done(AbstractSyncService svc) {
|
static public void done(AbstractSyncService svc) {
|
||||||
SyncServiceManager ssm = INSTANCE;
|
SyncManager ssm = INSTANCE;
|
||||||
if (ssm == null) return;
|
if (ssm == null) return;
|
||||||
synchronized(sSyncLock) {
|
synchronized(sSyncLock) {
|
||||||
long mailboxId = svc.mMailboxId;
|
long mailboxId = svc.mMailboxId;
|
||||||
@ -2181,7 +2180,7 @@ public abstract class SyncServiceManager extends Service implements Runnable {
|
|||||||
}
|
}
|
||||||
errorMap.remove(mailboxId);
|
errorMap.remove(mailboxId);
|
||||||
// If we've had a successful sync, clear the shutdown count
|
// If we've had a successful sync, clear the shutdown count
|
||||||
synchronized (SyncServiceManager.class) {
|
synchronized (SyncManager.class) {
|
||||||
sClientConnectionManagerShutdownCount = 0;
|
sClientConnectionManagerShutdownCount = 0;
|
||||||
}
|
}
|
||||||
// Leave now; other statuses are errors
|
// Leave now; other statuses are errors
|
36
imap2/Android.mk
Normal file
36
imap2/Android.mk
Normal file
@ -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))
|
113
imap2/AndroidManifest.xml
Normal file
113
imap2/AndroidManifest.xml
Normal file
@ -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>
|
BIN
imap2/res/mipmap-hdpi/icon.png
Normal file
BIN
imap2/res/mipmap-hdpi/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.8 KiB |
BIN
imap2/res/mipmap-mdpi/icon.png
Normal file
BIN
imap2/res/mipmap-mdpi/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
BIN
imap2/res/mipmap-xhdpi/icon.png
Normal file
BIN
imap2/res/mipmap-xhdpi/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
37
imap2/res/values/strings.xml
Normal file
37
imap2/res/values/strings.xml
Normal file
@ -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>
|
||||||
|
|
27
imap2/res/xml/syncadapter_email.xml
Normal file
27
imap2/res/xml/syncadapter_email.xml
Normal file
@ -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"
|
||||||
|
/>
|
197
imap2/src/com/android/imap2/AttachmentLoader.java
Normal file
197
imap2/src/com/android/imap2/AttachmentLoader.java
Normal file
@ -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 {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
82
imap2/src/com/android/imap2/BroadcastProcessorService.java
Normal file
82
imap2/src/com/android/imap2/BroadcastProcessorService.java
Normal file
@ -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));
|
||||||
|
}
|
||||||
|
}
|
122
imap2/src/com/android/imap2/EmailSyncAdapterService.java
Normal file
122
imap2/src/com/android/imap2/EmailSyncAdapterService.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
imap2/src/com/android/imap2/Imap2.java
Normal file
23
imap2/src/com/android/imap2/Imap2.java
Normal file
@ -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
|
||||||
|
}
|
31
imap2/src/com/android/imap2/Imap2BroadcastReceiver.java
Normal file
31
imap2/src/com/android/imap2/Imap2BroadcastReceiver.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
325
imap2/src/com/android/imap2/Imap2SyncManager.java
Normal file
325
imap2/src/com/android/imap2/Imap2SyncManager.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
2056
imap2/src/com/android/imap2/Imap2SyncService.java
Normal file
2056
imap2/src/com/android/imap2/Imap2SyncService.java
Normal file
File diff suppressed because it is too large
Load Diff
189
imap2/src/com/android/imap2/ImapId.java
Normal file
189
imap2/src/com/android/imap2/ImapId.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
48
imap2/src/com/android/imap2/ImapInputStream.java
Normal file
48
imap2/src/com/android/imap2/ImapInputStream.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
202
imap2/src/com/android/imap2/Parser.java
Normal file
202
imap2/src/com/android/imap2/Parser.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
121
imap2/src/com/android/imap2/QuotedPrintable.java
Normal file
121
imap2/src/com/android/imap2/QuotedPrintable.java
Normal file
@ -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]-->
|
<!-- Displayed in the middle of the screen when the inbox is empty [CHAR LIMIT 100]-->
|
||||||
<string name="no_conversations">No messages.</string>
|
<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>
|
</resources>
|
||||||
|
|
||||||
|
29
res/xml/imap2_authenticator.xml
Normal file
29
res/xml/imap2_authenticator.xml
Normal file
@ -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 -->
|
<!-- 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 -->
|
<!-- AOL and variants -->
|
||||||
<!-- Note, AOL appears to support SSL on both sides, as well -->
|
<!-- Note, AOL appears to support SSL on both sides, as well -->
|
||||||
<provider id="aim" label="AIM" domain="aim.com">
|
<provider id="aim" label="AIM" domain="aim.com">
|
||||||
|
@ -101,4 +101,21 @@
|
|||||||
email:syncContacts="true"
|
email:syncContacts="true"
|
||||||
email:syncCalendar="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>
|
</emailservices>
|
||||||
|
@ -26,9 +26,10 @@ import android.util.Log;
|
|||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
|
||||||
import com.android.email.R;
|
import com.android.email.R;
|
||||||
import com.android.email.VendorPolicyLoader;
|
|
||||||
import com.android.email.provider.AccountBackupRestore;
|
import com.android.email.provider.AccountBackupRestore;
|
||||||
import com.android.emailcommon.Logging;
|
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.Account;
|
||||||
import com.android.emailcommon.provider.HostAuth;
|
import com.android.emailcommon.provider.HostAuth;
|
||||||
import com.android.emailcommon.provider.EmailContent.AccountColumns;
|
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
|
* 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.R;
|
||||||
import com.android.email.activity.ActivityHelper;
|
import com.android.email.activity.ActivityHelper;
|
||||||
import com.android.email.activity.UiUtilities;
|
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;
|
||||||
import com.android.email.service.EmailServiceUtils.EmailServiceInfo;
|
import com.android.email.service.EmailServiceUtils.EmailServiceInfo;
|
||||||
import com.android.emailcommon.Logging;
|
import com.android.emailcommon.Logging;
|
||||||
|
import com.android.emailcommon.VendorPolicyLoader.Provider;
|
||||||
import com.android.emailcommon.provider.Account;
|
import com.android.emailcommon.provider.Account;
|
||||||
import com.android.emailcommon.provider.EmailContent;
|
import com.android.emailcommon.provider.EmailContent;
|
||||||
import com.android.emailcommon.provider.HostAuth;
|
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.email2.ui.MailActivityEmail;
|
||||||
import com.android.emailcommon.Logging;
|
import com.android.emailcommon.Logging;
|
||||||
import com.android.emailcommon.provider.Account;
|
import com.android.emailcommon.provider.Account;
|
||||||
import com.android.emailcommon.provider.HostAuth;
|
|
||||||
import com.android.emailcommon.provider.Policy;
|
import com.android.emailcommon.provider.Policy;
|
||||||
import com.android.emailcommon.service.SyncWindow;
|
import com.android.emailcommon.service.SyncWindow;
|
||||||
import com.android.emailcommon.utility.Utility;
|
import com.android.emailcommon.utility.Utility;
|
||||||
@ -244,6 +243,7 @@ public class AccountSetupOptions extends AccountSetupActivity implements OnClick
|
|||||||
final boolean email2 = email;
|
final boolean email2 = email;
|
||||||
final boolean calendar2 = calendar;
|
final boolean calendar2 = calendar;
|
||||||
final boolean contacts2 = contacts;
|
final boolean contacts2 = contacts;
|
||||||
|
|
||||||
Utility.runAsync(new Runnable() {
|
Utility.runAsync(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
@ -27,7 +27,6 @@ import android.util.Log;
|
|||||||
import com.android.email.LegacyConversions;
|
import com.android.email.LegacyConversions;
|
||||||
import com.android.email.Preferences;
|
import com.android.email.Preferences;
|
||||||
import com.android.email.R;
|
import com.android.email.R;
|
||||||
import com.android.email.VendorPolicyLoader;
|
|
||||||
import com.android.email.mail.Store;
|
import com.android.email.mail.Store;
|
||||||
import com.android.email.mail.Transport;
|
import com.android.email.mail.Transport;
|
||||||
import com.android.email.mail.store.imap.ImapConstants;
|
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.store.imap.ImapString;
|
||||||
import com.android.email.mail.transport.MailTransport;
|
import com.android.email.mail.transport.MailTransport;
|
||||||
import com.android.emailcommon.Logging;
|
import com.android.emailcommon.Logging;
|
||||||
|
import com.android.emailcommon.VendorPolicyLoader;
|
||||||
import com.android.emailcommon.internet.MimeMessage;
|
import com.android.emailcommon.internet.MimeMessage;
|
||||||
import com.android.emailcommon.mail.AuthenticationFailedException;
|
import com.android.emailcommon.mail.AuthenticationFailedException;
|
||||||
import com.android.emailcommon.mail.Flag;
|
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
|
* @param capabilities a list of the capabilities from the server
|
||||||
* @return a String for use in an IMAP ID message.
|
* @return a String for use in an IMAP ID message.
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
public static String getImapId(Context context, String userName, String host,
|
||||||
static String getImapId(Context context, String userName, String host, String capabilities) {
|
String capabilities) {
|
||||||
// The first section is global to all IMAP connections, and generates the fixed
|
// The first section is global to all IMAP connections, and generates the fixed
|
||||||
// values in any IMAP ID message
|
// values in any IMAP ID message
|
||||||
synchronized (ImapStore.class) {
|
synchronized (ImapStore.class) {
|
||||||
|
@ -126,8 +126,9 @@ public final class DBHelper {
|
|||||||
// Version 100 is first Email2 version
|
// Version 100 is first Email2 version
|
||||||
// Version 101 SHOULD NOT BE USED
|
// Version 101 SHOULD NOT BE USED
|
||||||
// Version 102&103: Add hierarchicalName to Mailbox
|
// 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.
|
// Any changes to the database format *must* include update-in-place code.
|
||||||
// Original version: 2
|
// Original version: 2
|
||||||
@ -172,7 +173,8 @@ public final class DBHelper {
|
|||||||
+ MessageColumns.MEETING_INFO + " text, "
|
+ MessageColumns.MEETING_INFO + " text, "
|
||||||
+ MessageColumns.SNIPPET + " text, "
|
+ MessageColumns.SNIPPET + " text, "
|
||||||
+ MessageColumns.PROTOCOL_SEARCH_INFO + " 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
|
// 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");
|
+ " add " + MailboxColumns.HIERARCHICAL_NAME + " text");
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
// Shouldn't be needed unless we're debugging and interrupt the process
|
// 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;
|
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
|
@Override
|
||||||
|
@ -77,12 +77,17 @@ import com.android.emailcommon.service.IEmailServiceCallback;
|
|||||||
import com.android.emailcommon.service.SearchParams;
|
import com.android.emailcommon.service.SearchParams;
|
||||||
import com.android.emailcommon.utility.AttachmentUtilities;
|
import com.android.emailcommon.utility.AttachmentUtilities;
|
||||||
import com.android.emailcommon.utility.Utility;
|
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;
|
||||||
import com.android.mail.providers.UIProvider.AccountCapabilities;
|
import com.android.mail.providers.UIProvider.AccountCapabilities;
|
||||||
import com.android.mail.providers.UIProvider.AccountCursorExtraKeys;
|
import com.android.mail.providers.UIProvider.AccountCursorExtraKeys;
|
||||||
import com.android.mail.providers.UIProvider.ConversationPriority;
|
import com.android.mail.providers.UIProvider.ConversationPriority;
|
||||||
import com.android.mail.providers.UIProvider.ConversationSendingState;
|
import com.android.mail.providers.UIProvider.ConversationSendingState;
|
||||||
import com.android.mail.providers.UIProvider.DraftType;
|
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.LogUtils;
|
||||||
import com.android.mail.utils.MatrixCursorWithExtra;
|
import com.android.mail.utils.MatrixCursorWithExtra;
|
||||||
import com.android.mail.utils.Utils;
|
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_RESET_NEW_COUNT_ID = ACCOUNT_BASE + 4;
|
||||||
private static final int ACCOUNT_DEFAULT_ID = ACCOUNT_BASE + 5;
|
private static final int ACCOUNT_DEFAULT_ID = ACCOUNT_BASE + 5;
|
||||||
private static final int ACCOUNT_CHECK = ACCOUNT_BASE + 6;
|
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_BASE = 0x1000;
|
||||||
private static final int MAILBOX = MAILBOX_BASE;
|
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 = MESSAGE_BASE;
|
||||||
private static final int MESSAGE_ID = MESSAGE_BASE + 1;
|
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_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_BASE = 0x3000;
|
||||||
private static final int ATTACHMENT = ATTACHMENT_BASE;
|
private static final int ATTACHMENT = ATTACHMENT_BASE;
|
||||||
@ -412,6 +419,8 @@ public class EmailProvider extends ContentProvider {
|
|||||||
* TO A SERVER VIA A SYNC ADAPTER
|
* TO A SERVER VIA A SYNC ADAPTER
|
||||||
*/
|
*/
|
||||||
matcher.addURI(EmailContent.AUTHORITY, "syncedMessage/#", SYNCED_MESSAGE_ID);
|
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
|
* 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, "uirecentfolders/#", UI_RECENT_FOLDERS);
|
||||||
matcher.addURI(EmailContent.AUTHORITY, "uidefaultrecentfolders/#",
|
matcher.addURI(EmailContent.AUTHORITY, "uidefaultrecentfolders/#",
|
||||||
UI_DEFAULT_RECENT_FOLDERS);
|
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);
|
return uiDeleteAccountData(uri);
|
||||||
case UI_ACCOUNT:
|
case UI_ACCOUNT:
|
||||||
return uiDeleteAccount(uri);
|
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
|
// These are cases in which one or more Messages might get deleted, either by
|
||||||
// cascade or explicitly
|
// cascade or explicitly
|
||||||
case MAILBOX_ID:
|
case MAILBOX_ID:
|
||||||
@ -1627,13 +1652,10 @@ public class EmailProvider extends ContentProvider {
|
|||||||
String id = "0";
|
String id = "0";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (match == MESSAGE_ID || match == SYNCED_MESSAGE_ID) {
|
|
||||||
if (!uri.getBooleanQueryParameter(IS_UIPROVIDER, false)) {
|
|
||||||
notifyUIConversation(uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
outer:
|
outer:
|
||||||
switch (match) {
|
switch (match) {
|
||||||
|
case ACCOUNT_PICK_TRASH_FOLDER:
|
||||||
|
return pickTrashFolder(uri);
|
||||||
case UI_FOLDER:
|
case UI_FOLDER:
|
||||||
return uiUpdateFolder(uri, values);
|
return uiUpdateFolder(uri, values);
|
||||||
case UI_RECENT_FOLDERS:
|
case UI_RECENT_FOLDERS:
|
||||||
@ -1706,6 +1728,21 @@ outer:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
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 SYNCED_MESSAGE_ID:
|
||||||
case UPDATED_MESSAGE_ID:
|
case UPDATED_MESSAGE_ID:
|
||||||
case MESSAGE_ID:
|
case MESSAGE_ID:
|
||||||
@ -1741,7 +1778,11 @@ outer:
|
|||||||
cache.unlock(id, values);
|
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);
|
long attId = Integer.parseInt(id);
|
||||||
if (values.containsKey(Attachment.FLAGS)) {
|
if (values.containsKey(Attachment.FLAGS)) {
|
||||||
int flags = values.getAsInteger(Attachment.FLAGS);
|
int flags = values.getAsInteger(Attachment.FLAGS);
|
||||||
@ -3698,8 +3739,12 @@ outer:
|
|||||||
return update(ourUri, ourValues, null, null);
|
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) {
|
private int uiDeleteMessage(Uri uri) {
|
||||||
Context context = getContext();
|
final Context context = getContext();
|
||||||
Message msg = getMessageFromLastSegment(uri);
|
Message msg = getMessageFromLastSegment(uri);
|
||||||
if (msg == null) return 0;
|
if (msg == null) return 0;
|
||||||
Mailbox mailbox = Mailbox.restoreMailboxWithId(context, msg.mMailboxKey);
|
Mailbox mailbox = Mailbox.restoreMailboxWithId(context, msg.mMailboxKey);
|
||||||
@ -3709,17 +3754,42 @@ outer:
|
|||||||
AttachmentUtilities.deleteAllAttachmentFiles(context, msg.mAccountKey, msg.mId);
|
AttachmentUtilities.deleteAllAttachmentFiles(context, msg.mAccountKey, msg.mId);
|
||||||
notifyUI(UIPROVIDER_FOLDER_NOTIFIER, mailbox.mId);
|
notifyUI(UIPROVIDER_FOLDER_NOTIFIER, mailbox.mId);
|
||||||
return context.getContentResolver().delete(
|
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 trashMailbox =
|
||||||
Mailbox.restoreMailboxOfType(context, msg.mAccountKey, Mailbox.TYPE_TRASH);
|
Mailbox.restoreMailboxOfType(context, msg.mAccountKey, Mailbox.TYPE_TRASH);
|
||||||
if (trashMailbox == null) return 0;
|
if (trashMailbox == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put(MessageColumns.MAILBOX_KEY, trashMailbox.mId);
|
values.put(MessageColumns.MAILBOX_KEY, trashMailbox.mId);
|
||||||
notifyUI(UIPROVIDER_FOLDER_NOTIFIER, mailbox.mId);
|
notifyUI(UIPROVIDER_FOLDER_NOTIFIER, mailbox.mId);
|
||||||
return uiUpdateMessage(uri, values);
|
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) {
|
private Cursor uiUndo(String[] projection) {
|
||||||
// First see if we have any operations saved
|
// First see if we have any operations saved
|
||||||
// TODO: Make sure seq matches
|
// TODO: Make sure seq matches
|
||||||
|
75
src/com/android/email/provider/FolderPickerActivity.java
Normal file
75
src/com/android/email/provider/FolderPickerActivity.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
25
src/com/android/email/provider/FolderPickerCallback.java
Normal file
25
src/com/android/email/provider/FolderPickerCallback.java
Normal file
@ -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();
|
||||||
|
}
|
145
src/com/android/email/provider/FolderSelectionDialog.java
Normal file
145
src/com/android/email/provider/FolderSelectionDialog.java
Normal file
@ -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.NotificationController;
|
||||||
import com.android.email.ResourceHelper;
|
import com.android.email.ResourceHelper;
|
||||||
import com.android.email.VendorPolicyLoader;
|
|
||||||
import com.android.email.provider.AccountReconciler;
|
import com.android.email.provider.AccountReconciler;
|
||||||
import com.android.email2.ui.MailActivityEmail;
|
import com.android.email2.ui.MailActivityEmail;
|
||||||
import com.android.emailcommon.Configuration;
|
import com.android.emailcommon.Configuration;
|
||||||
import com.android.emailcommon.Device;
|
import com.android.emailcommon.Device;
|
||||||
|
import com.android.emailcommon.VendorPolicyLoader;
|
||||||
import com.android.emailcommon.provider.Account;
|
import com.android.emailcommon.provider.Account;
|
||||||
import com.android.emailcommon.provider.HostAuth;
|
import com.android.emailcommon.provider.HostAuth;
|
||||||
import com.android.emailcommon.service.IAccountService;
|
import com.android.emailcommon.service.IAccountService;
|
||||||
|
@ -32,9 +32,9 @@ import android.util.Log;
|
|||||||
import com.android.email.NotificationController;
|
import com.android.email.NotificationController;
|
||||||
import com.android.email.Preferences;
|
import com.android.email.Preferences;
|
||||||
import com.android.email.SecurityPolicy;
|
import com.android.email.SecurityPolicy;
|
||||||
import com.android.email.VendorPolicyLoader;
|
|
||||||
import com.android.email.activity.setup.AccountSettings;
|
import com.android.email.activity.setup.AccountSettings;
|
||||||
import com.android.emailcommon.Logging;
|
import com.android.emailcommon.Logging;
|
||||||
|
import com.android.emailcommon.VendorPolicyLoader;
|
||||||
import com.android.emailcommon.provider.Account;
|
import com.android.emailcommon.provider.Account;
|
||||||
import com.android.emailcommon.provider.EmailContent.AccountColumns;
|
import com.android.emailcommon.provider.EmailContent.AccountColumns;
|
||||||
import com.android.emailcommon.provider.HostAuth;
|
import com.android.emailcommon.provider.HostAuth;
|
||||||
|
23
src/com/android/email/service/Imap2AuthenticatorService.java
Normal file
23
src/com/android/email/service/Imap2AuthenticatorService.java
Normal file
@ -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.MessageColumns;
|
||||||
import com.android.emailcommon.provider.EmailContent.SyncColumns;
|
import com.android.emailcommon.provider.EmailContent.SyncColumns;
|
||||||
import com.android.emailcommon.provider.Mailbox;
|
import com.android.emailcommon.provider.Mailbox;
|
||||||
|
import com.android.emailcommon.service.EmailServiceCallback;
|
||||||
import com.android.emailcommon.service.EmailServiceStatus;
|
import com.android.emailcommon.service.EmailServiceStatus;
|
||||||
import com.android.emailcommon.service.IEmailServiceCallback;
|
import com.android.emailcommon.service.IEmailServiceCallback;
|
||||||
import com.android.emailcommon.service.SearchParams;
|
import com.android.emailcommon.service.SearchParams;
|
||||||
@ -106,102 +107,8 @@ public class ImapService extends Service {
|
|||||||
private static final RemoteCallbackList<IEmailServiceCallback> mCallbackList =
|
private static final RemoteCallbackList<IEmailServiceCallback> mCallbackList =
|
||||||
new RemoteCallbackList<IEmailServiceCallback>();
|
new RemoteCallbackList<IEmailServiceCallback>();
|
||||||
|
|
||||||
private interface ServiceCallbackWrapper {
|
private static final EmailServiceCallback sCallbackProxy =
|
||||||
public void call(IEmailServiceCallback cb) throws RemoteException;
|
new EmailServiceCallback(mCallbackList);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create our EmailService implementation here.
|
* Create our EmailService implementation here.
|
||||||
@ -243,10 +150,7 @@ public class ImapService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void sendMailboxStatus(Mailbox mailbox, int status) {
|
private static void sendMailboxStatus(Mailbox mailbox, int status) {
|
||||||
try {
|
|
||||||
sCallbackProxy.syncMailboxStatus(mailbox.mId, status, 0);
|
sCallbackProxy.syncMailboxStatus(mailbox.mId, status, 0);
|
||||||
} catch (RemoteException e) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
Block a user