1 /* 2 * Copyright (C) 2008-2009 Marc Blank 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.exchange.adapter; 19 20 import android.content.ContentProviderOperation; 21 import android.content.ContentUris; 22 import android.content.ContentValues; 23 import android.content.OperationApplicationException; 24 import android.database.Cursor; 25 import android.os.RemoteException; 26 import android.text.TextUtils; 27 28 import com.android.emailcommon.provider.Account; 29 import com.android.emailcommon.provider.EmailContent; 30 import com.android.emailcommon.provider.EmailContent.AccountColumns; 31 import com.android.emailcommon.provider.EmailContent.MailboxColumns; 32 import com.android.emailcommon.provider.Mailbox; 33 import com.android.emailcommon.service.SyncWindow; 34 import com.android.emailcommon.utility.AttachmentUtilities; 35 import com.android.emailcommon.utility.EmailAsyncTask; 36 import com.android.emailcommon.utility.Utility; 37 import com.android.exchange.CommandStatusException; 38 import com.android.exchange.CommandStatusException.CommandStatus; 39 import com.android.exchange.Eas; 40 import com.android.exchange.ExchangeService; 41 import com.android.exchange.provider.MailboxUtilities; 42 import com.google.common.annotations.VisibleForTesting; 43 44 import java.io.IOException; 45 import java.io.InputStream; 46 import java.util.ArrayList; 47 import java.util.Arrays; 48 import java.util.HashMap; 49 import java.util.List; 50 51 /** 52 * Parse the result of a FolderSync command 53 * 54 * Handles the addition, deletion, and changes to folders in the user's Exchange account. 55 **/ 56 57 public class FolderSyncParser extends AbstractSyncParser { 58 59 public static final String TAG = "FolderSyncParser"; 60 61 // These are defined by the EAS protocol 62 public static final int USER_GENERIC_TYPE = 1; 63 public static final int INBOX_TYPE = 2; 64 public static final int DRAFTS_TYPE = 3; 65 public static final int DELETED_TYPE = 4; 66 public static final int SENT_TYPE = 5; 67 public static final int OUTBOX_TYPE = 6; 68 public static final int TASKS_TYPE = 7; 69 public static final int CALENDAR_TYPE = 8; 70 public static final int CONTACTS_TYPE = 9; 71 public static final int NOTES_TYPE = 10; 72 public static final int JOURNAL_TYPE = 11; 73 public static final int USER_MAILBOX_TYPE = 12; 74 75 // Chunk size for our mailbox commits 76 public final static int MAILBOX_COMMIT_SIZE = 20; 77 78 // EAS types that we are willing to consider valid folders for EAS sync 79 public static final List<Integer> VALID_EAS_FOLDER_TYPES = Arrays.asList(INBOX_TYPE, 80 DRAFTS_TYPE, DELETED_TYPE, SENT_TYPE, OUTBOX_TYPE, USER_MAILBOX_TYPE, CALENDAR_TYPE, 81 CONTACTS_TYPE, USER_GENERIC_TYPE); 82 83 public static final String ALL_BUT_ACCOUNT_MAILBOX = MailboxColumns.ACCOUNT_KEY + "=? and " + 84 MailboxColumns.TYPE + "!=" + Mailbox.TYPE_EAS_ACCOUNT_MAILBOX; 85 86 private static final String WHERE_SERVER_ID_AND_ACCOUNT = MailboxColumns.SERVER_ID + "=? and " + 87 MailboxColumns.ACCOUNT_KEY + "=?"; 88 89 private static final String WHERE_DISPLAY_NAME_AND_ACCOUNT = MailboxColumns.DISPLAY_NAME + 90 "=? and " + MailboxColumns.ACCOUNT_KEY + "=?"; 91 92 private static final String WHERE_PARENT_SERVER_ID_AND_ACCOUNT = 93 MailboxColumns.PARENT_SERVER_ID +"=? and " + MailboxColumns.ACCOUNT_KEY + "=?"; 94 95 private static final String[] MAILBOX_ID_COLUMNS_PROJECTION = 96 new String[] {MailboxColumns.ID, MailboxColumns.SERVER_ID, MailboxColumns.PARENT_SERVER_ID}; 97 private static final int MAILBOX_ID_COLUMNS_ID = 0; 98 private static final int MAILBOX_ID_COLUMNS_SERVER_ID = 1; 99 private static final int MAILBOX_ID_COLUMNS_PARENT_SERVER_ID = 2; 100 101 @VisibleForTesting 102 long mAccountId; 103 @VisibleForTesting 104 String mAccountIdAsString; 105 private String[] mBindArguments = new String[2]; 106 private ArrayList<ContentProviderOperation> mOperations = 107 new ArrayList<ContentProviderOperation>(); 108 private boolean mInitialSync; 109 private ArrayList<String> mParentFixupsNeeded = new ArrayList<String>(); 110 private boolean mFixupUninitializedNeeded = false; 111 // If true, we only care about status (this is true when validating an account) and ignore 112 // other data 113 private final boolean mStatusOnly; 114 115 private static final ContentValues UNINITIALIZED_PARENT_KEY = new ContentValues(); 116 117 { 118 UNINITIALIZED_PARENT_KEY.put(MailboxColumns.PARENT_KEY, Mailbox.PARENT_KEY_UNINITIALIZED); 119 } 120 121 public FolderSyncParser(InputStream in, AbstractSyncAdapter adapter) throws IOException { 122 this(in, adapter, false); 123 } 124 125 public FolderSyncParser(InputStream in, AbstractSyncAdapter adapter, boolean statusOnly) 126 throws IOException { 127 super(in, adapter); 128 mAccountId = mAccount.mId; 129 mAccountIdAsString = Long.toString(mAccountId); 130 mStatusOnly = statusOnly; 131 } 132 133 @Override 134 public boolean parse() throws IOException, CommandStatusException { 135 int status; 136 boolean res = false; 137 boolean resetFolders = false; 138 // Since we're now (potentially) committing mailboxes in chunks, ensure that we start with 139 // only the account mailbox 140 String key = mAccount.mSyncKey; 141 mInitialSync = (key == null) || "0".equals(key); 142 if (mInitialSync) { 143 mContentResolver.delete(Mailbox.CONTENT_URI, ALL_BUT_ACCOUNT_MAILBOX, 144 new String[] {Long.toString(mAccountId)}); 145 } 146 if (nextTag(START_DOCUMENT) != Tags.FOLDER_FOLDER_SYNC) 147 throw new EasParserException(); 148 while (nextTag(START_DOCUMENT) != END_DOCUMENT) { 149 if (tag == Tags.FOLDER_STATUS) { 150 status = getValueInt(); 151 if (status != Eas.FOLDER_STATUS_OK) { 152 mService.errorLog("FolderSync failed: " + CommandStatus.toString(status)); 153 // If the account hasn't been saved, this is a validation attempt, so we don't 154 // try reloading the folder list... 155 if (CommandStatus.isDeniedAccess(status) || 156 CommandStatus.isNeedsProvisioning(status) || 157 (mAccount.mId == Account.NOT_SAVED)) { 158 throw new CommandStatusException(status); 159 // Note that we need to catch both old-style (Eas.FOLDER_STATUS_INVALID_KEY) 160 // and EAS 14 style command status 161 } else if (status == Eas.FOLDER_STATUS_INVALID_KEY || 162 CommandStatus.isBadSyncKey(status)) { 163 mService.errorLog("Bad sync key; RESET and delete all folders"); 164 // Reset the sync key and save 165 mAccount.mSyncKey = "0"; 166 ContentValues cv = new ContentValues(); 167 cv.put(AccountColumns.SYNC_KEY, mAccount.mSyncKey); 168 mContentResolver.update(ContentUris.withAppendedId(Account.CONTENT_URI, 169 mAccount.mId), cv, null, null); 170 // Delete PIM data 171 ExchangeService.deleteAccountPIMData(mAccountId); 172 // Save away any mailbox sync information that is NOT default 173 saveMailboxSyncOptions(); 174 // And only then, delete mailboxes 175 mContentResolver.delete(Mailbox.CONTENT_URI, ALL_BUT_ACCOUNT_MAILBOX, 176 new String[] {Long.toString(mAccountId)}); 177 // Stop existing syncs and reconstruct _main 178 ExchangeService.stopNonAccountMailboxSyncsForAccount(mAccountId); 179 res = true; 180 resetFolders = true; 181 } else { 182 // Other errors are at the server, so let's throw an error that will 183 // cause this sync to be retried at a later time 184 mService.errorLog("Throwing IOException; will retry later"); 185 throw new EasParserException("Folder status error"); 186 } 187 } 188 } else if (tag == Tags.FOLDER_SYNC_KEY) { 189 mAccount.mSyncKey = getValue(); 190 userLog("New Account SyncKey: ", mAccount.mSyncKey); 191 } else if (tag == Tags.FOLDER_CHANGES) { 192 if (mStatusOnly) return res; 193 changesParser(mOperations, mInitialSync); 194 } else 195 skipTag(); 196 } 197 if (mStatusOnly) return res; 198 synchronized (mService.getSynchronizer()) { 199 if (!mService.isStopped() || resetFolders) { 200 commit(); 201 userLog("Leaving FolderSyncParser with Account syncKey=", mAccount.mSyncKey); 202 } 203 } 204 return res; 205 } 206 207 private Cursor getServerIdCursor(String serverId) { 208 mBindArguments[0] = serverId; 209 mBindArguments[1] = mAccountIdAsString; 210 return mContentResolver.query(Mailbox.CONTENT_URI, MAILBOX_ID_COLUMNS_PROJECTION, 211 WHERE_SERVER_ID_AND_ACCOUNT, mBindArguments, null); 212 } 213 214 public void deleteParser(ArrayList<ContentProviderOperation> ops) throws IOException { 215 while (nextTag(Tags.FOLDER_DELETE) != END) { 216 switch (tag) { 217 case Tags.FOLDER_SERVER_ID: 218 String serverId = getValue(); 219 // Find the mailbox in this account with the given serverId 220 Cursor c = getServerIdCursor(serverId); 221 try { 222 if (c.moveToFirst()) { 223 userLog("Deleting ", serverId); 224 ops.add(ContentProviderOperation.newDelete( 225 ContentUris.withAppendedId(Mailbox.CONTENT_URI, 226 c.getLong(MAILBOX_ID_COLUMNS_ID))).build()); 227 AttachmentUtilities.deleteAllMailboxAttachmentFiles(mContext, 228 mAccountId, mMailbox.mId); 229 if (!mInitialSync) { 230 String parentId = c.getString(MAILBOX_ID_COLUMNS_PARENT_SERVER_ID); 231 if (!TextUtils.isEmpty(parentId)) { 232 mParentFixupsNeeded.add(parentId); 233 } 234 } 235 } 236 } finally { 237 c.close(); 238 } 239 break; 240 default: 241 skipTag(); 242 } 243 } 244 } 245 246 private static class SyncOptions { 247 private final int mInterval; 248 private final int mLookback; 249 250 private SyncOptions(int interval, int lookback) { 251 mInterval = interval; 252 mLookback = lookback; 253 } 254 } 255 256 private static final String MAILBOX_STATE_SELECTION = 257 MailboxColumns.ACCOUNT_KEY + "=? AND (" + MailboxColumns.SYNC_INTERVAL + "!=" + 258 Account.CHECK_INTERVAL_NEVER + " OR " + Mailbox.SYNC_LOOKBACK + "!=" + 259 SyncWindow.SYNC_WINDOW_UNKNOWN + ")"; 260 261 private static final String[] MAILBOX_STATE_PROJECTION = new String[] { 262 MailboxColumns.SERVER_ID, MailboxColumns.SYNC_INTERVAL, MailboxColumns.SYNC_LOOKBACK}; 263 private static final int MAILBOX_STATE_SERVER_ID = 0; 264 private static final int MAILBOX_STATE_INTERVAL = 1; 265 private static final int MAILBOX_STATE_LOOKBACK = 2; 266 @VisibleForTesting 267 final HashMap<String, SyncOptions> mSyncOptionsMap = new HashMap<String, SyncOptions>(); 268 269 /** 270 * For every mailbox in this account that has a non-default interval or lookback, save those 271 * values. 272 */ 273 @VisibleForTesting 274 void saveMailboxSyncOptions() { 275 // Shouldn't be necessary, but... 276 mSyncOptionsMap.clear(); 277 Cursor c = mContentResolver.query(Mailbox.CONTENT_URI, MAILBOX_STATE_PROJECTION, 278 MAILBOX_STATE_SELECTION, new String[] {mAccountIdAsString}, null); 279 if (c != null) { 280 try { 281 while (c.moveToNext()) { 282 mSyncOptionsMap.put(c.getString(MAILBOX_STATE_SERVER_ID), 283 new SyncOptions(c.getInt(MAILBOX_STATE_INTERVAL), 284 c.getInt(MAILBOX_STATE_LOOKBACK))); 285 } 286 } finally { 287 c.close(); 288 } 289 } 290 } 291 292 /** 293 * For every set of saved mailbox sync options, try to find and restore those values 294 */ 295 @VisibleForTesting 296 void restoreMailboxSyncOptions() { 297 try { 298 ContentValues cv = new ContentValues(); 299 mBindArguments[1] = mAccountIdAsString; 300 for (String serverId: mSyncOptionsMap.keySet()) { 301 SyncOptions options = mSyncOptionsMap.get(serverId); 302 cv.put(MailboxColumns.SYNC_INTERVAL, options.mInterval); 303 cv.put(MailboxColumns.SYNC_LOOKBACK, options.mLookback); 304 mBindArguments[0] = serverId; 305 // If we match account and server id, set the sync options 306 mContentResolver.update(Mailbox.CONTENT_URI, cv, WHERE_SERVER_ID_AND_ACCOUNT, 307 mBindArguments); 308 } 309 } finally { 310 mSyncOptionsMap.clear(); 311 } 312 } 313 314 public Mailbox addParser() throws IOException { 315 String name = null; 316 String serverId = null; 317 String parentId = null; 318 int type = 0; 319 320 while (nextTag(Tags.FOLDER_ADD) != END) { 321 switch (tag) { 322 case Tags.FOLDER_DISPLAY_NAME: { 323 name = getValue(); 324 break; 325 } 326 case Tags.FOLDER_TYPE: { 327 type = getValueInt(); 328 break; 329 } 330 case Tags.FOLDER_PARENT_ID: { 331 parentId = getValue(); 332 break; 333 } 334 case Tags.FOLDER_SERVER_ID: { 335 serverId = getValue(); 336 break; 337 } 338 default: 339 skipTag(); 340 } 341 } 342 343 if (VALID_EAS_FOLDER_TYPES.contains(type)) { 344 Mailbox mailbox = new Mailbox(); 345 mailbox.mDisplayName = name; 346 mailbox.mServerId = serverId; 347 mailbox.mAccountKey = mAccountId; 348 mailbox.mType = Mailbox.TYPE_MAIL; 349 // Note that all mailboxes default to checking "never" (i.e. manual sync only) 350 // We set specific intervals for inbox, contacts, and (eventually) calendar 351 mailbox.mSyncInterval = Mailbox.CHECK_INTERVAL_NEVER; 352 switch (type) { 353 case INBOX_TYPE: 354 mailbox.mType = Mailbox.TYPE_INBOX; 355 mailbox.mSyncInterval = mAccount.mSyncInterval; 356 break; 357 case CONTACTS_TYPE: 358 mailbox.mType = Mailbox.TYPE_CONTACTS; 359 mailbox.mSyncInterval = mAccount.mSyncInterval; 360 break; 361 case OUTBOX_TYPE: 362 // TYPE_OUTBOX mailboxes are known by ExchangeService to sync whenever they 363 // aren't empty. The value of mSyncFrequency is ignored for this kind of 364 // mailbox. 365 mailbox.mType = Mailbox.TYPE_OUTBOX; 366 break; 367 case SENT_TYPE: 368 mailbox.mType = Mailbox.TYPE_SENT; 369 break; 370 case DRAFTS_TYPE: 371 mailbox.mType = Mailbox.TYPE_DRAFTS; 372 break; 373 case DELETED_TYPE: 374 mailbox.mType = Mailbox.TYPE_TRASH; 375 break; 376 case CALENDAR_TYPE: 377 mailbox.mType = Mailbox.TYPE_CALENDAR; 378 mailbox.mSyncInterval = mAccount.mSyncInterval; 379 break; 380 case USER_GENERIC_TYPE: 381 mailbox.mType = Mailbox.TYPE_UNKNOWN; 382 break; 383 } 384 385 // Make boxes like Contacts and Calendar invisible in the folder list 386 mailbox.mFlagVisible = (mailbox.mType < Mailbox.TYPE_NOT_EMAIL); 387 388 if (!parentId.equals("0")) { 389 mailbox.mParentServerId = parentId; 390 if (!mInitialSync) { 391 mParentFixupsNeeded.add(parentId); 392 } 393 } 394 // At the least, we'll need to set flags 395 mFixupUninitializedNeeded = true; 396 397 return mailbox; 398 } 399 return null; 400 } 401 402 /** 403 * Determine whether a given mailbox holds mail, rather than other data. We do this by first 404 * checking the type of the mailbox (if it's a known good type, great; if it's a known bad 405 * type, return false). If it's unknown, we check the parent, first by trying to find it in 406 * the current set of newly synced items, and then by looking it up in EmailProvider. If 407 * we can find the parent, we use the same rules to determine if it holds mail; if it does, 408 * then its children do as well, so that's a go. 409 * 410 * @param mailbox the mailbox we're checking 411 * @param mailboxMap a HashMap relating server id's of mailboxes in the current sync set to 412 * the corresponding mailbox structures 413 * @return whether or not the mailbox contains email (rather than PIM or unknown data) 414 */ 415 /*package*/ boolean isValidMailFolder(Mailbox mailbox, HashMap<String, Mailbox> mailboxMap) { 416 int folderType = mailbox.mType; 417 // Automatically accept our email types 418 if (folderType < Mailbox.TYPE_NOT_EMAIL) return true; 419 // Automatically reject everything else but "unknown" 420 if (folderType != Mailbox.TYPE_UNKNOWN) return false; 421 // If this is TYPE_UNKNOWN, check the parent 422 Mailbox parent = mailboxMap.get(mailbox.mParentServerId); 423 // If the parent is in the map, then check it out; if not, it could be an existing saved 424 // Mailbox, so we'll have to query the database 425 if (parent == null) { 426 mBindArguments[0] = Long.toString(mAccount.mId); 427 mBindArguments[1] = mailbox.mParentServerId; 428 long parentId = Utility.getFirstRowInt(mContext, Mailbox.CONTENT_URI, 429 EmailContent.ID_PROJECTION, 430 MailboxColumns.ACCOUNT_KEY + "=? AND " + MailboxColumns.SERVER_ID + "=?", 431 mBindArguments, null, EmailContent.ID_PROJECTION_COLUMN, -1); 432 if (parentId != -1) { 433 // Get the parent from the database 434 parent = Mailbox.restoreMailboxWithId(mContext, parentId); 435 if (parent == null) return false; 436 } else { 437 return false; 438 } 439 } 440 return isValidMailFolder(parent, mailboxMap); 441 } 442 443 public void updateParser(ArrayList<ContentProviderOperation> ops) throws IOException { 444 String serverId = null; 445 String displayName = null; 446 String parentId = null; 447 while (nextTag(Tags.FOLDER_UPDATE) != END) { 448 switch (tag) { 449 case Tags.FOLDER_SERVER_ID: 450 serverId = getValue(); 451 break; 452 case Tags.FOLDER_DISPLAY_NAME: 453 displayName = getValue(); 454 break; 455 case Tags.FOLDER_PARENT_ID: 456 parentId = getValue(); 457 break; 458 default: 459 skipTag(); 460 break; 461 } 462 } 463 // We'll make a change if one of parentId or displayName are specified 464 // serverId is required, but let's be careful just the same 465 if (serverId != null && (displayName != null || parentId != null)) { 466 Cursor c = getServerIdCursor(serverId); 467 try { 468 // If we find the mailbox (using serverId), make the change 469 if (c.moveToFirst()) { 470 userLog("Updating ", serverId); 471 // Fix up old and new parents, as needed 472 if (!TextUtils.isEmpty(parentId)) { 473 mParentFixupsNeeded.add(parentId); 474 } 475 String oldParentId = c.getString(MAILBOX_ID_COLUMNS_PARENT_SERVER_ID); 476 if (!TextUtils.isEmpty(oldParentId)) { 477 mParentFixupsNeeded.add(oldParentId); 478 } 479 // Set display name if we've got one 480 ContentValues cv = new ContentValues(); 481 if (displayName != null) { 482 cv.put(Mailbox.DISPLAY_NAME, displayName); 483 } 484 // Save away the server id and uninitialize the parent key 485 cv.put(Mailbox.PARENT_SERVER_ID, parentId); 486 // Clear the parent key; it will be fixed up after the commit 487 cv.put(Mailbox.PARENT_KEY, Mailbox.PARENT_KEY_UNINITIALIZED); 488 ops.add(ContentProviderOperation.newUpdate( 489 ContentUris.withAppendedId(Mailbox.CONTENT_URI, 490 c.getLong(MAILBOX_ID_COLUMNS_ID))).withValues(cv).build()); 491 // Say we need to fixup uninitialized mailboxes 492 mFixupUninitializedNeeded = true; 493 } 494 } finally { 495 c.close(); 496 } 497 } 498 } 499 500 private boolean commitMailboxes(ArrayList<Mailbox> validMailboxes, 501 ArrayList<Mailbox> userMailboxes, HashMap<String, Mailbox> mailboxMap, 502 ArrayList<ContentProviderOperation> ops) { 503 504 // Go through the generic user mailboxes; we'll call them valid if any parent is valid 505 for (Mailbox m: userMailboxes) { 506 if (isValidMailFolder(m, mailboxMap)) { 507 m.mType = Mailbox.TYPE_MAIL; 508 validMailboxes.add(m); 509 } else { 510 userLog("Rejecting unknown type mailbox: " + m.mDisplayName); 511 } 512 } 513 514 // Add operations for all valid mailboxes 515 for (Mailbox m: validMailboxes) { 516 userLog("Adding mailbox: ", m.mDisplayName); 517 ops.add(ContentProviderOperation 518 .newInsert(Mailbox.CONTENT_URI).withValues(m.toContentValues()).build()); 519 } 520 521 // Commit the mailboxes 522 userLog("Applying ", mOperations.size(), " mailbox operations."); 523 // Execute the batch; throw IOExceptions if this fails, hoping the issue isn't repeatable 524 // If it IS repeatable, there's no good result, since the folder list will be invalid 525 try { 526 mContentResolver.applyBatch(EmailContent.AUTHORITY, mOperations); 527 return true; 528 } catch (RemoteException e) { 529 userLog("RemoteException in commitMailboxes"); 530 return false; 531 } catch (OperationApplicationException e) { 532 userLog("OperationApplicationException in commitMailboxes"); 533 return false; 534 } 535 } 536 537 public void changesParser(final ArrayList<ContentProviderOperation> ops, 538 final boolean initialSync) throws IOException { 539 // Array of added mailboxes 540 final ArrayList<Mailbox> addMailboxes = new ArrayList<Mailbox>(); 541 542 // Indicate start of (potential) mailbox changes 543 MailboxUtilities.startMailboxChanges(mContext, mAccount.mId); 544 545 while (nextTag(Tags.FOLDER_CHANGES) != END) { 546 if (tag == Tags.FOLDER_ADD) { 547 Mailbox mailbox = addParser(); 548 if (mailbox != null) { 549 addMailboxes.add(mailbox); 550 } 551 } else if (tag == Tags.FOLDER_DELETE) { 552 deleteParser(ops); 553 } else if (tag == Tags.FOLDER_UPDATE) { 554 updateParser(ops); 555 } else if (tag == Tags.FOLDER_COUNT) { 556 getValueInt(); 557 } else 558 skipTag(); 559 } 560 561 EmailAsyncTask.runAsyncParallel(new Runnable() { 562 @Override 563 public void run() { 564 // Synchronize on the parser to prevent this being run multiple times concurrently 565 // (an extremely unlikely event, but nonetheless possible) 566 synchronized (FolderSyncParser.this) { 567 // Mailboxes that we known contain email 568 ArrayList<Mailbox> validMailboxes = new ArrayList<Mailbox>(); 569 // Mailboxes that we're unsure about 570 ArrayList<Mailbox> userMailboxes = new ArrayList<Mailbox>(); 571 // Maps folder serverId to mailbox type 572 HashMap<String, Mailbox> mailboxMap = new HashMap<String, Mailbox>(); 573 int mailboxCommitCount = 0; 574 for (Mailbox mailbox : addMailboxes) { 575 // Save away the type of this folder 576 mailboxMap.put(mailbox.mServerId, mailbox); 577 // And add the mailbox to the proper list 578 if (type == USER_MAILBOX_TYPE) { 579 userMailboxes.add(mailbox); 580 } else { 581 validMailboxes.add(mailbox); 582 } 583 // On initial sync, we commit what we have every 20 mailboxes 584 if (initialSync && (++mailboxCommitCount == MAILBOX_COMMIT_SIZE)) { 585 if (!commitMailboxes(validMailboxes, userMailboxes, mailboxMap, ops)) { 586 mService.stop(); 587 return; 588 } 589 // Clear our arrays to prepare for more 590 userMailboxes.clear(); 591 validMailboxes.clear(); 592 ops.clear(); 593 mailboxCommitCount = 0; 594 } 595 } 596 // Commit the sync key and mailboxes 597 ContentValues cv = new ContentValues(); 598 cv.put(AccountColumns.SYNC_KEY, mAccount.mSyncKey); 599 ops.add(ContentProviderOperation 600 .newUpdate( 601 ContentUris.withAppendedId(Account.CONTENT_URI, mAccount.mId)) 602 .withValues(cv).build()); 603 if (!commitMailboxes(validMailboxes, userMailboxes, mailboxMap, ops)) { 604 mService.stop(); 605 } 606 String accountSelector = Mailbox.ACCOUNT_KEY + "=" + mAccount.mId; 607 // For new boxes, setup the parent key and flags 608 if (mFixupUninitializedNeeded) { 609 MailboxUtilities.fixupUninitializedParentKeys(mContext, accountSelector); 610 } 611 // For modified parents, reset the flags (and children's parent key) 612 for (String parentServerId: mParentFixupsNeeded) { 613 Cursor c = mContentResolver.query(Mailbox.CONTENT_URI, 614 Mailbox.CONTENT_PROJECTION, Mailbox.PARENT_SERVER_ID + "=?", 615 new String[] {parentServerId}, null); 616 try { 617 if (c.moveToFirst()) { 618 MailboxUtilities.setFlagsAndChildrensParentKey(mContext, c, 619 accountSelector); 620 } 621 } finally { 622 c.close(); 623 } 624 } 625 626 // Signal completion of mailbox changes 627 MailboxUtilities.endMailboxChanges(mContext, mAccount.mId); 628 } 629 }}); 630 } 631 632 /** 633 * Not needed for FolderSync parsing; everything is done within changesParser 634 */ 635 @Override 636 public void commandsParser() throws IOException { 637 } 638 639 /** 640 * Clean up after sync 641 */ 642 @Override 643 public void commit() throws IOException { 644 // Look for sync issues and its children and delete them 645 // I'm not aware of any other way to deal with this properly 646 mBindArguments[0] = "Sync Issues"; 647 mBindArguments[1] = mAccountIdAsString; 648 Cursor c = mContentResolver.query(Mailbox.CONTENT_URI, 649 MAILBOX_ID_COLUMNS_PROJECTION, WHERE_DISPLAY_NAME_AND_ACCOUNT, 650 mBindArguments, null); 651 String parentServerId = null; 652 long id = 0; 653 try { 654 if (c.moveToFirst()) { 655 id = c.getLong(MAILBOX_ID_COLUMNS_ID); 656 parentServerId = c.getString(MAILBOX_ID_COLUMNS_SERVER_ID); 657 } 658 } finally { 659 c.close(); 660 } 661 if (parentServerId != null) { 662 mContentResolver.delete(ContentUris.withAppendedId(Mailbox.CONTENT_URI, id), 663 null, null); 664 mBindArguments[0] = parentServerId; 665 mContentResolver.delete(Mailbox.CONTENT_URI, WHERE_PARENT_SERVER_ID_AND_ACCOUNT, 666 mBindArguments); 667 } 668 669 // If we have saved options, restore them now 670 if (mInitialSync) { 671 restoreMailboxSyncOptions(); 672 } 673 } 674 675 @Override 676 public void responsesParser() throws IOException { 677 } 678 679 } 680