Fix folder resync.
Bug: 9226893
Change-Id: I5dc96f13e0c8fc5d5cff9da40d09cd9eb8f50a26
diff --git a/src/com/android/exchange/adapter/FolderSyncParser.java b/src/com/android/exchange/adapter/FolderSyncParser.java
index 302e558..8bd2051 100644
--- a/src/com/android/exchange/adapter/FolderSyncParser.java
+++ b/src/com/android/exchange/adapter/FolderSyncParser.java
@@ -27,13 +27,11 @@
import android.os.RemoteException;
import android.text.TextUtils;
-import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.Account;
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 com.android.emailcommon.provider.MailboxUtilities;
import com.android.emailcommon.service.SyncWindow;
import com.android.emailcommon.utility.AttachmentUtilities;
import com.android.exchange.CommandStatusException;
@@ -74,34 +72,63 @@
public static final int JOURNAL_TYPE = 11;
public static final int USER_MAILBOX_TYPE = 12;
- // Chunk size for our mailbox commits
- private final static int MAILBOX_COMMIT_SIZE = 20;
- // Max mailboxes per account
- private final static int MAX_MAILBOXES_PER_ACCOUNT = 1000000;
-
// EAS types that we are willing to consider valid folders for EAS sync
private static final List<Integer> VALID_EAS_FOLDER_TYPES = Arrays.asList(INBOX_TYPE,
DRAFTS_TYPE, DELETED_TYPE, SENT_TYPE, OUTBOX_TYPE, USER_MAILBOX_TYPE, CALENDAR_TYPE,
CONTACTS_TYPE, USER_GENERIC_TYPE);
- public static final String ALL_BUT_ACCOUNT_MAILBOX = MailboxColumns.ACCOUNT_KEY + "=? and " +
- MailboxColumns.TYPE + "!=" + Mailbox.TYPE_EAS_ACCOUNT_MAILBOX;
+ /** Content selection for all mailboxes belonging to an account. */
+ private static final String WHERE_ACCOUNT_KEY = MailboxColumns.ACCOUNT_KEY + "=?";
+ /**
+ * Content selection to find a specific mailbox by server id. Since server ids aren't unique
+ * across all accounts, this must also check account id.
+ */
private static final String WHERE_SERVER_ID_AND_ACCOUNT = MailboxColumns.SERVER_ID + "=? and " +
MailboxColumns.ACCOUNT_KEY + "=?";
+ /**
+ * Content selection to find a specific mailbox by display name and account.
+ */
private static final String WHERE_DISPLAY_NAME_AND_ACCOUNT = MailboxColumns.DISPLAY_NAME +
"=? and " + MailboxColumns.ACCOUNT_KEY + "=?";
+ /**
+ * Content selection to find children by parent's server id. Since server ids aren't unique
+ * across accounts, this must also use account id.
+ */
private static final String WHERE_PARENT_SERVER_ID_AND_ACCOUNT =
MailboxColumns.PARENT_SERVER_ID +"=? and " + MailboxColumns.ACCOUNT_KEY + "=?";
+ /** Projection used when fetching a Mailbox's ids. */
private static final String[] MAILBOX_ID_COLUMNS_PROJECTION =
new String[] {MailboxColumns.ID, MailboxColumns.SERVER_ID, MailboxColumns.PARENT_SERVER_ID};
private static final int MAILBOX_ID_COLUMNS_ID = 0;
private static final int MAILBOX_ID_COLUMNS_SERVER_ID = 1;
private static final int MAILBOX_ID_COLUMNS_PARENT_SERVER_ID = 2;
+ /** Projection used for changed parents during parent/child fixup. */
+ private static final String[] FIXUP_PARENT_PROJECTION =
+ { MailboxColumns.ID, MailboxColumns.DISPLAY_NAME, MailboxColumns.HIERARCHICAL_NAME,
+ MailboxColumns.FLAGS };
+ private static final int FIXUP_PARENT_ID_COLUMN = 0;
+ private static final int FIXUP_PARENT_DISPLAY_NAME_COLUMN = 1;
+ private static final int FIXUP_PARENT_HIERARCHICAL_NAME_COLUMN = 2;
+ private static final int FIXUP_PARENT_FLAGS_COLUMN = 3;
+
+ /** Projection used for changed children during parent/child fixup. */
+ private static final String[] FIXUP_CHILD_PROJECTION =
+ { MailboxColumns.ID, MailboxColumns.DISPLAY_NAME };
+ private static final int FIXUP_CHILD_ID_COLUMN = 0;
+ private static final int FIXUP_CHILD_DISPLAY_NAME_COLUMN = 1;
+
+ /** Flags that are set or cleared when a mailbox's child status changes. */
+ private static final int HAS_CHILDREN_FLAGS =
+ Mailbox.FLAG_HAS_CHILDREN | Mailbox.FLAG_CHILDREN_VISIBLE;
+
+ /** Mailbox.NO_MAILBOX, as a string (convenience since this is used in several places). */
+ private static final String NO_MAILBOX_STRING = Long.toString(Mailbox.NO_MAILBOX);
+
@VisibleForTesting
long mAccountId;
@VisibleForTesting
@@ -110,11 +137,17 @@
boolean mInUnitTest = false;
private String[] mBindArguments = new String[2];
+
+ /** List of pending operations to send as a batch to the content provider. */
private ArrayList<ContentProviderOperation> mOperations =
- new ArrayList<ContentProviderOperation>();
+ new ArrayList<ContentProviderOperation>();
+ /** Indicates whether this sync is an initial FolderSync. */
private boolean mInitialSync;
+ /** List of folder server ids whose children changed with this sync. */
private ArrayList<String> mParentFixupsNeeded = new ArrayList<String>();
- private boolean mFixupUninitializedNeeded = false;
+ /** Indicates whether the sync response provided a different sync key than we had. */
+ private boolean mSyncKeyChanged = false;
+
// If true, we only care about status (this is true when validating an account) and ignore
// other data
private final boolean mStatusOnly;
@@ -151,13 +184,11 @@
int status;
boolean res = false;
boolean resetFolders = false;
- // Since we're now (potentially) committing mailboxes in chunks, ensure that we start with
- // only the account mailbox
- String key = mAccount.mSyncKey;
- mInitialSync = (key == null) || "0".equals(key);
+ mInitialSync = (mAccount.mSyncKey == null) || "0".equals(mAccount.mSyncKey);
if (mInitialSync) {
- mContentResolver.delete(Mailbox.CONTENT_URI, ALL_BUT_ACCOUNT_MAILBOX,
- new String[] {Long.toString(mAccountId)});
+ // We're resyncing all folders for this account, so nuke any existing ones.
+ mContentResolver.delete(Mailbox.CONTENT_URI, WHERE_ACCOUNT_KEY,
+ new String[] {mAccountIdAsString});
}
if (nextTag(START_DOCUMENT) != Tags.FOLDER_FOLDER_SYNC)
throw new EasParserException();
@@ -174,11 +205,8 @@
UNINITIALIZED_PARENT_KEY, null, null);
}
if (dupes > 0) {
- String e = "Duplicate mailboxes found for account " + mAccountId + ": " + dupes;
- // For verbose logging, make sure this is in emaillog.txt
- userLog(e);
- // Worthy of logging, regardless
- LogUtils.w(Logging.LOG_TAG, e);
+ LogUtils.w(TAG, "Duplicate mailboxes found for account %d: %d", mAccountId,
+ dupes);
status = Eas.FOLDER_STATUS_INVALID_KEY;
}
if (status != Eas.FOLDER_STATUS_OK) {
@@ -197,9 +225,8 @@
// Save away any mailbox sync information that is NOT default
saveMailboxSyncOptions();
// And only then, delete mailboxes
- mContentResolver.delete(Mailbox.CONTENT_URI,
- MailboxColumns.ACCOUNT_KEY + "=?",
- new String[] {Long.toString(mAccountId)});
+ mContentResolver.delete(Mailbox.CONTENT_URI, WHERE_ACCOUNT_KEY,
+ new String[] {mAccountIdAsString});
// Reconstruct _main
res = true;
resetFolders = true;
@@ -218,13 +245,14 @@
}
}
} else if (tag == Tags.FOLDER_SYNC_KEY) {
- String newKey = getValue();
- if (!resetFolders) {
+ final String newKey = getValue();
+ if (newKey != null && !resetFolders) {
+ mSyncKeyChanged = !newKey.equals(mAccount.mSyncKey);
mAccount.mSyncKey = newKey;
}
} else if (tag == Tags.FOLDER_CHANGES) {
if (mStatusOnly) return res;
- changesParser(mOperations, mInitialSync);
+ changesParser();
} else
skipTag();
}
@@ -234,14 +262,24 @@
return res;
}
- private Cursor getServerIdCursor(String serverId) {
+ /**
+ * Get a cursor with folder ids for a specific folder.
+ * @param serverId The server id for the folder we are interested in.
+ * @return A cursor for the folder specified by serverId for this account.
+ */
+ private Cursor getServerIdCursor(final String serverId) {
mBindArguments[0] = serverId;
mBindArguments[1] = mAccountIdAsString;
return mContentResolver.query(Mailbox.CONTENT_URI, MAILBOX_ID_COLUMNS_PROJECTION,
WHERE_SERVER_ID_AND_ACCOUNT, mBindArguments, null);
}
- private void deleteParser(ArrayList<ContentProviderOperation> ops) throws IOException {
+ /**
+ * Add the appropriate {@link ContentProviderOperation} to {@link #mOperations} for a Delete
+ * change in the FolderSync response.
+ * @throws IOException
+ */
+ private void deleteParser() throws IOException {
while (nextTag(Tags.FOLDER_DELETE) != END) {
switch (tag) {
case Tags.FOLDER_SERVER_ID:
@@ -250,18 +288,17 @@
final Cursor c = getServerIdCursor(serverId);
try {
if (c.moveToFirst()) {
- userLog("Deleting ", serverId);
+ LogUtils.i(TAG, "Deleting %s", serverId);
final long mailboxId = c.getLong(MAILBOX_ID_COLUMNS_ID);
- ops.add(ContentProviderOperation.newDelete(
+ mOperations.add(ContentProviderOperation.newDelete(
ContentUris.withAppendedId(Mailbox.CONTENT_URI,
mailboxId)).build());
AttachmentUtilities.deleteAllMailboxAttachmentFiles(mContext,
mAccountId, mailboxId);
- if (!mInitialSync) {
- String parentId = c.getString(MAILBOX_ID_COLUMNS_PARENT_SERVER_ID);
- if (!TextUtils.isEmpty(parentId)) {
- mParentFixupsNeeded.add(parentId);
- }
+ final String parentId =
+ c.getString(MAILBOX_ID_COLUMNS_PARENT_SERVER_ID);
+ if (!TextUtils.isEmpty(parentId)) {
+ mParentFixupsNeeded.add(parentId);
}
}
} finally {
@@ -342,7 +379,96 @@
}
}
- private Mailbox addParser() throws IOException {
+ /**
+ * Add a {@link ContentProviderOperation} to {@link #mOperations} to add a mailbox.
+ * @param name The new mailbox's name.
+ * @param serverId The new mailbox's server id.
+ * @param parentServerId The server id of the new mailbox's parent ("0" if none).
+ * @param easType The mailbox's type, in terms of the EAS values (NOT {@link Mailbox}'s type
+ * values).
+ * @throws IOException
+ */
+ private void addMailboxOp(final String name, final String serverId,
+ final String parentServerId, final int easType) throws IOException {
+ final ContentValues cv = new ContentValues();
+ cv.put(MailboxColumns.DISPLAY_NAME, name);
+ cv.put(MailboxColumns.SERVER_ID, serverId);
+ final String parentId;
+ if (parentServerId.equals("0")) {
+ parentId = NO_MAILBOX_STRING;
+ cv.put(Mailbox.PARENT_KEY, Mailbox.NO_MAILBOX);
+ } else {
+ parentId = parentServerId;
+ mParentFixupsNeeded.add(parentId);
+ }
+ cv.put(MailboxColumns.PARENT_SERVER_ID, parentId);
+ cv.put(MailboxColumns.ACCOUNT_KEY, mAccountId);
+ final int mailboxType;
+ final long syncInterval;
+ switch (easType) {
+ case INBOX_TYPE:
+ mailboxType = Mailbox.TYPE_INBOX;
+ syncInterval = mAccount.mSyncInterval;
+ break;
+ case CONTACTS_TYPE:
+ mailboxType = Mailbox.TYPE_CONTACTS;
+ syncInterval = mAccount.mSyncInterval;
+ break;
+ case OUTBOX_TYPE:
+ // TYPE_OUTBOX mailboxes are known by ExchangeService to sync whenever they
+ // aren't empty. The value of mSyncFrequency is ignored for this kind of
+ // mailbox.
+ mailboxType = Mailbox.TYPE_OUTBOX;
+ syncInterval = Mailbox.CHECK_INTERVAL_NEVER;
+ break;
+ case SENT_TYPE:
+ mailboxType = Mailbox.TYPE_SENT;
+ syncInterval = Mailbox.CHECK_INTERVAL_NEVER;
+ break;
+ case DRAFTS_TYPE:
+ mailboxType = Mailbox.TYPE_DRAFTS;
+ syncInterval = Mailbox.CHECK_INTERVAL_NEVER;
+ break;
+ case DELETED_TYPE:
+ mailboxType = Mailbox.TYPE_TRASH;
+ syncInterval = Mailbox.CHECK_INTERVAL_NEVER;
+ break;
+ case CALENDAR_TYPE:
+ mailboxType = Mailbox.TYPE_CALENDAR;
+ syncInterval = mAccount.mSyncInterval;
+ break;
+ default:
+ mailboxType = Mailbox.TYPE_MAIL;
+ syncInterval = Mailbox.CHECK_INTERVAL_NEVER;
+ }
+ cv.put(MailboxColumns.TYPE, mailboxType);
+ cv.put(MailboxColumns.SYNC_INTERVAL, syncInterval);
+
+ // Set basic flags
+ int flags = 0;
+ if (mailboxType <= Mailbox.TYPE_NOT_EMAIL) {
+ flags |= Mailbox.FLAG_HOLDS_MAIL + Mailbox.FLAG_SUPPORTS_SETTINGS;
+ }
+ // Outbox, Drafts, and Sent don't allow mail to be moved to them
+ if (mailboxType == Mailbox.TYPE_MAIL || mailboxType == Mailbox.TYPE_TRASH ||
+ mailboxType == Mailbox.TYPE_JUNK || mailboxType == Mailbox.TYPE_INBOX) {
+ flags |= Mailbox.FLAG_ACCEPTS_MOVED_MAIL;
+ }
+ cv.put(MailboxColumns.FLAGS, flags);
+
+ // Make boxes like Contacts and Calendar invisible in the folder list
+ cv.put(MailboxColumns.FLAG_VISIBLE, (mailboxType < Mailbox.TYPE_NOT_EMAIL));
+
+ mOperations.add(
+ ContentProviderOperation.newInsert(Mailbox.CONTENT_URI).withValues(cv).build());
+ }
+
+ /**
+ * Add the appropriate {@link ContentProviderOperation} to {@link #mOperations} for an Add
+ * change in the FolderSync response.
+ * @throws IOException
+ */
+ private void addParser() throws IOException {
String name = null;
String serverId = null;
String parentId = null;
@@ -372,62 +498,16 @@
}
if (VALID_EAS_FOLDER_TYPES.contains(type)) {
- Mailbox mailbox = new Mailbox();
- mailbox.mDisplayName = name;
- mailbox.mServerId = serverId;
- mailbox.mAccountKey = mAccountId;
- mailbox.mType = Mailbox.TYPE_MAIL;
- // Note that all mailboxes default to checking "never" (i.e. manual sync only)
- // We set specific intervals for inbox, contacts, and (eventually) calendar
- mailbox.mSyncInterval = Mailbox.CHECK_INTERVAL_NEVER;
- switch (type) {
- case INBOX_TYPE:
- mailbox.mType = Mailbox.TYPE_INBOX;
- mailbox.mSyncInterval = mAccount.mSyncInterval;
- break;
- case CONTACTS_TYPE:
- mailbox.mType = Mailbox.TYPE_CONTACTS;
- mailbox.mSyncInterval = mAccount.mSyncInterval;
- break;
- case OUTBOX_TYPE:
- // TYPE_OUTBOX mailboxes are known by ExchangeService to sync whenever they
- // aren't empty. The value of mSyncFrequency is ignored for this kind of
- // mailbox.
- mailbox.mType = Mailbox.TYPE_OUTBOX;
- break;
- case SENT_TYPE:
- mailbox.mType = Mailbox.TYPE_SENT;
- break;
- case DRAFTS_TYPE:
- mailbox.mType = Mailbox.TYPE_DRAFTS;
- break;
- case DELETED_TYPE:
- mailbox.mType = Mailbox.TYPE_TRASH;
- break;
- case CALENDAR_TYPE:
- mailbox.mType = Mailbox.TYPE_CALENDAR;
- mailbox.mSyncInterval = mAccount.mSyncInterval;
- break;
- }
-
- // Make boxes like Contacts and Calendar invisible in the folder list
- mailbox.mFlagVisible = (mailbox.mType < Mailbox.TYPE_NOT_EMAIL);
-
- if (!parentId.equals("0")) {
- mailbox.mParentServerId = parentId;
- if (!mInitialSync) {
- mParentFixupsNeeded.add(parentId);
- }
- }
- // At the least, we'll need to set flags
- mFixupUninitializedNeeded = true;
-
- return mailbox;
+ addMailboxOp(name, serverId, parentId, type);
}
- return null;
}
- private void updateParser(ArrayList<ContentProviderOperation> ops) throws IOException {
+ /**
+ * Add the appropriate {@link ContentProviderOperation} to {@link #mOperations} for an Update
+ * change in the FolderSync response.
+ * @throws IOException
+ */
+ private void updateParser() throws IOException {
String serverId = null;
String displayName = null;
String parentId = null;
@@ -450,33 +530,31 @@
// We'll make a change if one of parentId or displayName are specified
// serverId is required, but let's be careful just the same
if (serverId != null && (displayName != null || parentId != null)) {
- Cursor c = getServerIdCursor(serverId);
+ final Cursor c = getServerIdCursor(serverId);
try {
// If we find the mailbox (using serverId), make the change
if (c.moveToFirst()) {
- userLog("Updating ", serverId);
+ LogUtils.i(TAG, "Updating %s", serverId);
+ final ContentValues cv = new ContentValues();
+ // Store the new parent key.
+ cv.put(Mailbox.PARENT_SERVER_ID, parentId);
// Fix up old and new parents, as needed
if (!TextUtils.isEmpty(parentId)) {
mParentFixupsNeeded.add(parentId);
+ } else {
+ cv.put(Mailbox.PARENT_KEY, Mailbox.NO_MAILBOX);
}
- String oldParentId = c.getString(MAILBOX_ID_COLUMNS_PARENT_SERVER_ID);
+ final String oldParentId = c.getString(MAILBOX_ID_COLUMNS_PARENT_SERVER_ID);
if (!TextUtils.isEmpty(oldParentId)) {
mParentFixupsNeeded.add(oldParentId);
}
// Set display name if we've got one
- ContentValues cv = new ContentValues();
if (displayName != null) {
cv.put(Mailbox.DISPLAY_NAME, displayName);
}
- // Save away the server id and uninitialize the parent key
- cv.put(Mailbox.PARENT_SERVER_ID, parentId);
- // Clear the parent key; it will be fixed up after the commit
- cv.put(Mailbox.PARENT_KEY, Mailbox.PARENT_KEY_UNINITIALIZED);
- ops.add(ContentProviderOperation.newUpdate(
+ mOperations.add(ContentProviderOperation.newUpdate(
ContentUris.withAppendedId(Mailbox.CONTENT_URI,
c.getLong(MAILBOX_ID_COLUMNS_ID))).withValues(cv).build());
- // Say we need to fixup uninitialized mailboxes
- mFixupUninitializedNeeded = true;
}
} finally {
c.close();
@@ -484,188 +562,175 @@
}
}
- private boolean commitMailboxes(ArrayList<ContentProviderOperation> ops) {
- // Commit the mailboxes
- userLog("Applying ", mOperations.size(), " mailbox operations.");
- // Execute the batch; throw IOExceptions if this fails, hoping the issue isn't repeatable
- // If it IS repeatable, there's no good result, since the folder list will be invalid
- try {
- mContentResolver.applyBatch(EmailContent.AUTHORITY, mOperations);
- return true;
- } catch (RemoteException e) {
- userLog("RemoteException in commitMailboxes");
- return false;
- } catch (OperationApplicationException e) {
- userLog("OperationApplicationException in commitMailboxes");
- return false;
- }
- }
-
- private void changesParser(final ArrayList<ContentProviderOperation> ops,
- final boolean initialSync) throws IOException {
-
- // Array of added mailboxes
- final ArrayList<Mailbox> addMailboxes = new ArrayList<Mailbox>();
-
- // Indicate start of (potential) mailbox changes
- MailboxUtilities.startMailboxChanges(mContext, mAccount.mId);
-
+ /**
+ * Handle the Changes element of the FolderSync response. This is the container for Add, Delete,
+ * and Update elements.
+ * @throws IOException
+ */
+ private void changesParser() throws IOException {
while (nextTag(Tags.FOLDER_CHANGES) != END) {
if (tag == Tags.FOLDER_ADD) {
- final Mailbox mailbox = addParser();
- if (mailbox != null) {
- addMailboxes.add(mailbox);
- }
+ addParser();
} else if (tag == Tags.FOLDER_DELETE) {
- deleteParser(ops);
+ deleteParser();
} else if (tag == Tags.FOLDER_UPDATE) {
- updateParser(ops);
+ updateParser();
} else if (tag == Tags.FOLDER_COUNT) {
+ // TODO: Maybe we can make use of this count somehow.
getValueInt();
} else
skipTag();
}
-
- // Map folder serverId to mailbox (used to validate user mailboxes)
- final HashMap<String, Mailbox> mailboxMap = new HashMap<String, Mailbox>();
- for (final Mailbox mailbox : addMailboxes) {
- mailboxMap.put(mailbox.mServerId, mailbox);
- }
- userLog("Total of " + addMailboxes.size() + " mailboxes parsed");
-
- // Synchronize on the parser to prevent this being run concurrently
- // (an extremely unlikely event, but nonetheless possible)
- if (mInitialSync) {
- synchronized (FolderSyncParser.this) {
- // Assign unique sequential ids and set appropriate mailbox flags; it's safe to
- // assume that there won't be more than one million mailboxes defined for an
- // account. I use millions for ease in debugging (i.e. associating an int in
- // the debugger with an account)
- long mailboxId = (mAccount.mId * MAX_MAILBOXES_PER_ACCOUNT) + 1;
- // Set basic flags
- for (Mailbox mailbox : addMailboxes) {
- int type = mailbox.mType;
- if (type <= Mailbox.TYPE_NOT_EMAIL) {
- mailbox.mFlags |= Mailbox.FLAG_HOLDS_MAIL + Mailbox.FLAG_SUPPORTS_SETTINGS;
- }
- // Outbox, Drafts, and Sent don't allow mail to be moved to them
- if (type == Mailbox.TYPE_MAIL || type == Mailbox.TYPE_TRASH ||
- type == Mailbox.TYPE_JUNK || type == Mailbox.TYPE_INBOX) {
- mailbox.mFlags |= Mailbox.FLAG_ACCEPTS_MOVED_MAIL;
- }
- mailbox.mId = mailboxId++;
- }
- // Set parent mailbox key and hierarchical name; set parent flags on parents
- for (Mailbox mailbox: addMailboxes) {
- String parentServerId = mailbox.mParentServerId;
- if (parentServerId == null || parentServerId.equals("0")) {
- mailbox.mParentKey = Mailbox.NO_MAILBOX;
- } else {
- Mailbox parentMailbox = mailboxMap.get(parentServerId);
- if (parentMailbox != null) {
- mailbox.mParentKey = parentMailbox.mId;
- parentMailbox.mFlags |=
- Mailbox.FLAG_HAS_CHILDREN + Mailbox.FLAG_CHILDREN_VISIBLE;
- String hierarchicalName = mailbox.mDisplayName;
- while (parentMailbox != null) {
- hierarchicalName = parentMailbox.mDisplayName + "/" +
- hierarchicalName;
- if (parentMailbox.mParentServerId != null &&
- !parentMailbox.mParentServerId.equals("0")) {
- parentMailbox = mailboxMap.get(parentMailbox.mParentServerId);
- } else {
- break;
- }
- }
- mailbox.mHierarchicalName = hierarchicalName;
- } else {
- userLog("Parent not found with serverId = " + parentServerId);
- }
- }
- }
- }
-
- // Save all the new mailboxes away in groups of 20
- int batchCount = 0;
- for (Mailbox mailbox: addMailboxes) {
- if (mailbox.mId == Mailbox.NO_MAILBOX) {
- userLog("Skipping mailbox: ", mailbox.mDisplayName);
- continue;
- }
- if (++batchCount == MAILBOX_COMMIT_SIZE) {
- if (!commitMailboxes(ops)) {
- //mService.stop();
- return;
- }
- ops.clear();
- batchCount = 0;
- }
- userLog("Adding mailbox: ", mailbox.mDisplayName);
- ContentValues initialValues = mailbox.toContentValues();
- // We already have an id if this is the initial sync
- if (mInitialSync) {
- initialValues.put(MailboxColumns.ID, mailbox.mId);
- }
- ops.add(ContentProviderOperation.newInsert(
- Mailbox.CONTENT_URI).withValues(initialValues).build());
- }
-
- // Save away the new sync key with the last batch
- ContentValues cv = new ContentValues();
- cv.put(AccountColumns.SYNC_KEY, mAccount.mSyncKey);
- ops.add(ContentProviderOperation
- .newUpdate(
- ContentUris.withAppendedId(Account.CONTENT_URI,
- mAccount.mId))
- .withValues(cv).build());
- if (!commitMailboxes(ops)) {
- //mService.stop();
- return;
- }
- }
-
- // If this isn't the initial sync, we need to fix up the hierarchy
- if (!mInitialSync) {
- String accountSelector = Mailbox.ACCOUNT_KEY + "=" + mAccount.mId;
- // For new boxes, setup the parent key and flags
- if (mFixupUninitializedNeeded) {
- MailboxUtilities.fixupUninitializedParentKeys(mContext,
- accountSelector);
- }
- // For modified parents, reset the flags (and children's parent key)
- for (String parentServerId: mParentFixupsNeeded) {
- Cursor c = mContentResolver.query(Mailbox.CONTENT_URI,
- Mailbox.CONTENT_PROJECTION, Mailbox.PARENT_SERVER_ID + "=?",
- new String[] {parentServerId}, null);
- try {
- if (c.moveToFirst()) {
- MailboxUtilities.setFlagsAndChildrensParentKey(mContext, c,
- accountSelector);
- }
- } finally {
- c.close();
- }
- }
-
- MailboxUtilities.setupHierarchicalNames(mContext, mAccount.mId);
- }
-
- // Signal completion of mailbox changes
- MailboxUtilities.endMailboxChanges(mContext, mAccount.mId);
}
/**
- * Not needed for FolderSync parsing; everything is done within changesParser
+ * Commit the contents of {@link #mOperations} to the content provider.
+ * @throws IOException
*/
+ private void flushOperations() throws IOException {
+ if (mOperations.isEmpty()) {
+ return;
+ }
+ // Execute the batch; throw IOExceptions if this fails, hoping the issue isn't repeatable
+ // If it IS repeatable, there's no good result, since the folder list will be invalid
+ try {
+ mContentResolver.applyBatch(EmailContent.AUTHORITY, mOperations);
+ } catch (final RemoteException e) {
+ LogUtils.e(TAG, "RemoteException in commit");
+ throw new IOException("RemoteException in commit");
+ } catch (final OperationApplicationException e) {
+ LogUtils.e(TAG, "OperationApplicationException in commit");
+ throw new IOException("OperationApplicationException in commit");
+ }
+ mOperations.clear();
+ }
+
+ /**
+ * Fix folder data for any folders whose parent or children changed during this sync.
+ * Unfortunately this cannot be done in the same pass as the actual sync: newly synced folders
+ * lack ids until they're committed to the content provider, so we can't set the parentKey
+ * for their children.
+ * During parsing, we only track the parents who have changed. We need to do a query for
+ * children anyway (to determine whether a parent still has any) so it's simpler to not bother
+ * tracking which folders have had their parents changed.
+ * TODO: Figure out if we can avoid the two-pass.
+ * @throws IOException
+ */
+ private void doParentFixups() throws IOException {
+ if (mParentFixupsNeeded.isEmpty()) {
+ return;
+ }
+
+ // These objects will be used in every loop iteration, so create them here for efficiency
+ // and just reset the values inside the loop as necessary.
+ final String[] bindArguments = new String[2];
+ bindArguments[1] = mAccountIdAsString;
+ final ContentValues cv = new ContentValues(2);
+
+ for (final String parentServerId : mParentFixupsNeeded) {
+ // Get info about this parent.
+ bindArguments[0] = parentServerId;
+ final Cursor parentCursor = mContentResolver.query(Mailbox.CONTENT_URI,
+ FIXUP_PARENT_PROJECTION, WHERE_SERVER_ID_AND_ACCOUNT, bindArguments, null);
+ if (parentCursor == null) {
+ // TODO: Error handling.
+ continue;
+ }
+ final long parentId;
+ final String parentHierarchicalName;
+ final int parentFlags;
+ try {
+ if (parentCursor.moveToFirst()) {
+ parentId = parentCursor.getLong(FIXUP_PARENT_ID_COLUMN);
+ final String hierarchicalName = parentCursor.getString(
+ FIXUP_PARENT_HIERARCHICAL_NAME_COLUMN);
+ if (hierarchicalName != null) {
+ parentHierarchicalName = hierarchicalName;
+ } else {
+ parentHierarchicalName = parentCursor.getString(
+ FIXUP_PARENT_DISPLAY_NAME_COLUMN);
+ }
+ parentFlags = parentCursor.getInt(FIXUP_PARENT_FLAGS_COLUMN);
+ } else {
+ // TODO: Error handling.
+ continue;
+ }
+ } finally {
+ parentCursor.close();
+ }
+
+ // Fix any children for this parent.
+ final Cursor childCursor = mContentResolver.query(Mailbox.CONTENT_URI,
+ FIXUP_CHILD_PROJECTION, WHERE_PARENT_SERVER_ID_AND_ACCOUNT, bindArguments,
+ null);
+ boolean hasChildren = false;
+ if (childCursor != null) {
+ try {
+ // Clear the results of the last iteration.
+ cv.clear();
+ // All children in this loop share the same parentId.
+ cv.put(MailboxColumns.PARENT_KEY, parentId);
+ while (childCursor.moveToNext()) {
+ final long childId = childCursor.getLong(FIXUP_CHILD_ID_COLUMN);
+ final String childName =
+ childCursor.getString(FIXUP_CHILD_DISPLAY_NAME_COLUMN);
+ cv.put(MailboxColumns.HIERARCHICAL_NAME,
+ parentHierarchicalName + "/" + childName);
+ mOperations.add(ContentProviderOperation.newUpdate(
+ ContentUris.withAppendedId(Mailbox.CONTENT_URI, childId)).
+ withValues(cv).build());
+ hasChildren = true;
+ }
+ } finally {
+ childCursor.close();
+ }
+ }
+
+ // Fix the parent's flags based on whether it now has children.
+ final int newFlags;
+
+ if (hasChildren) {
+ newFlags = parentFlags | HAS_CHILDREN_FLAGS;
+ } else {
+ newFlags = parentFlags & ~HAS_CHILDREN_FLAGS;
+ }
+ if (newFlags != parentFlags) {
+ cv.clear();
+ cv.put(MailboxColumns.FLAGS, newFlags);
+ mOperations.add(ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
+ Mailbox.CONTENT_URI, parentId)).withValues(cv).build());
+ }
+ }
+
+ flushOperations();
+ }
+
@Override
public void commandsParser() throws IOException {
}
- /**
- * Clean up after sync
- */
@Override
public void commit() throws IOException {
+ // Set the account sync key.
+ if (mSyncKeyChanged) {
+ final ContentValues cv = new ContentValues(1);
+ cv.put(AccountColumns.SYNC_KEY, mAccount.mSyncKey);
+ mOperations.add(
+ ContentProviderOperation.newUpdate(mAccount.getUri()).withValues(cv).build());
+ }
+
+ // If this is the initial sync, add the outbox.
+ if (mInitialSync) {
+ addMailboxOp(Mailbox.getSystemMailboxName(mContext, Mailbox.TYPE_OUTBOX), "0", "0",
+ OUTBOX_TYPE);
+ }
+
+ // Send all operations so far.
+ flushOperations();
+
+ // Now that new mailboxes are committed, let's do parent fixups.
+ doParentFixups();
+
// Look for sync issues and its children and delete them
// I'm not aware of any other way to deal with this properly
mBindArguments[0] = "Sync Issues";
diff --git a/src/com/android/exchange/service/EasAccountValidator.java b/src/com/android/exchange/service/EasAccountValidator.java
index ea12599..9be2c01 100644
--- a/src/com/android/exchange/service/EasAccountValidator.java
+++ b/src/com/android/exchange/service/EasAccountValidator.java
@@ -9,7 +9,6 @@
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent.AccountColumns;
import com.android.emailcommon.provider.HostAuth;
-import com.android.emailcommon.provider.Mailbox;
import com.android.emailcommon.provider.Policy;
import com.android.emailcommon.service.EmailServiceProxy;
import com.android.emailcommon.service.PolicyServiceProxy;
@@ -220,7 +219,6 @@
resp.getInputStream(), mAccount, isStatusOnly).parse();
}
resultCode = MessagingException.NO_ERROR;
- // TODO: Save mAccount (at least syncKey should be different).
} else if (code == HttpStatus.SC_FORBIDDEN) {
// For validation only, we take 403 as ACCESS_DENIED (the account isn't
// authorized, possibly due to device type)
@@ -611,13 +609,6 @@
if (bundle != null) {
bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, resultCode);
- } else if (resultCode == MessagingException.NO_ERROR) {
- // This is an actual sync which succeeded. Let's force the outbox to exist as well.
- if (Mailbox.findMailboxOfType(mContext, mAccount.mId, Mailbox.TYPE_OUTBOX) ==
- Mailbox.NO_MAILBOX) {
- Mailbox.newSystemMailbox(mContext, mAccount.mId, Mailbox.TYPE_OUTBOX)
- .save(mContext);
- }
}
}