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