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