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 18package com.android.exchange.adapter; 19 20import android.content.ContentProviderOperation; 21import android.content.ContentUris; 22import android.content.ContentValues; 23import android.content.OperationApplicationException; 24import android.database.Cursor; 25import android.os.RemoteException; 26import android.text.TextUtils; 27 28import com.android.emailcommon.provider.Account; 29import com.android.emailcommon.provider.EmailContent; 30import com.android.emailcommon.provider.EmailContent.AccountColumns; 31import com.android.emailcommon.provider.EmailContent.MailboxColumns; 32import com.android.emailcommon.provider.Mailbox; 33import com.android.emailcommon.service.SyncWindow; 34import com.android.emailcommon.utility.AttachmentUtilities; 35import com.android.emailcommon.utility.Utility; 36import com.android.exchange.CommandStatusException; 37import com.android.exchange.CommandStatusException.CommandStatus; 38import com.android.exchange.Eas; 39import com.android.exchange.ExchangeService; 40import com.android.exchange.provider.MailboxUtilities; 41import com.google.common.annotations.VisibleForTesting; 42 43import java.io.IOException; 44import java.io.InputStream; 45import java.util.ArrayList; 46import java.util.Arrays; 47import java.util.HashMap; 48import 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 56public 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 mAccount.mSyncKey = getValue(); 192 userLog("New Account SyncKey: ", mAccount.mSyncKey); 193 } else if (tag == Tags.FOLDER_CHANGES) { 194 if (mStatusOnly) return res; 195 changesParser(mOperations, mInitialSync); 196 } else 197 skipTag(); 198 } 199 if (mStatusOnly) return res; 200 synchronized (mService.getSynchronizer()) { 201 if (!mService.isStopped() || resetFolders) { 202 commit(); 203 userLog("Leaving FolderSyncParser with Account syncKey=", mAccount.mSyncKey); 204 } 205 } 206 return res; 207 } 208 209 private Cursor getServerIdCursor(String serverId) { 210 mBindArguments[0] = serverId; 211 mBindArguments[1] = mAccountIdAsString; 212 return mContentResolver.query(Mailbox.CONTENT_URI, MAILBOX_ID_COLUMNS_PROJECTION, 213 WHERE_SERVER_ID_AND_ACCOUNT, mBindArguments, null); 214 } 215 216 public void deleteParser(ArrayList<ContentProviderOperation> ops) throws IOException { 217 while (nextTag(Tags.FOLDER_DELETE) != END) { 218 switch (tag) { 219 case Tags.FOLDER_SERVER_ID: 220 String serverId = getValue(); 221 // Find the mailbox in this account with the given serverId 222 Cursor c = getServerIdCursor(serverId); 223 try { 224 if (c.moveToFirst()) { 225 userLog("Deleting ", serverId); 226 ops.add(ContentProviderOperation.newDelete( 227 ContentUris.withAppendedId(Mailbox.CONTENT_URI, 228 c.getLong(MAILBOX_ID_COLUMNS_ID))).build()); 229 AttachmentUtilities.deleteAllMailboxAttachmentFiles(mContext, 230 mAccountId, mMailbox.mId); 231 if (!mInitialSync) { 232 String parentId = c.getString(MAILBOX_ID_COLUMNS_PARENT_SERVER_ID); 233 if (!TextUtils.isEmpty(parentId)) { 234 mParentFixupsNeeded.add(parentId); 235 } 236 } 237 } 238 } finally { 239 c.close(); 240 } 241 break; 242 default: 243 skipTag(); 244 } 245 } 246 } 247 248 private static class SyncOptions { 249 private final int mInterval; 250 private final int mLookback; 251 252 private SyncOptions(int interval, int lookback) { 253 mInterval = interval; 254 mLookback = lookback; 255 } 256 } 257 258 private static final String MAILBOX_STATE_SELECTION = 259 MailboxColumns.ACCOUNT_KEY + "=? AND (" + MailboxColumns.SYNC_INTERVAL + "!=" + 260 Account.CHECK_INTERVAL_NEVER + " OR " + Mailbox.SYNC_LOOKBACK + "!=" + 261 SyncWindow.SYNC_WINDOW_UNKNOWN + ")"; 262 263 private static final String[] MAILBOX_STATE_PROJECTION = new String[] { 264 MailboxColumns.SERVER_ID, MailboxColumns.SYNC_INTERVAL, MailboxColumns.SYNC_LOOKBACK}; 265 private static final int MAILBOX_STATE_SERVER_ID = 0; 266 private static final int MAILBOX_STATE_INTERVAL = 1; 267 private static final int MAILBOX_STATE_LOOKBACK = 2; 268 @VisibleForTesting 269 final HashMap<String, SyncOptions> mSyncOptionsMap = new HashMap<String, SyncOptions>(); 270 271 /** 272 * For every mailbox in this account that has a non-default interval or lookback, save those 273 * values. 274 */ 275 @VisibleForTesting 276 void saveMailboxSyncOptions() { 277 // Shouldn't be necessary, but... 278 mSyncOptionsMap.clear(); 279 Cursor c = mContentResolver.query(Mailbox.CONTENT_URI, MAILBOX_STATE_PROJECTION, 280 MAILBOX_STATE_SELECTION, new String[] {mAccountIdAsString}, null); 281 if (c != null) { 282 try { 283 while (c.moveToNext()) { 284 mSyncOptionsMap.put(c.getString(MAILBOX_STATE_SERVER_ID), 285 new SyncOptions(c.getInt(MAILBOX_STATE_INTERVAL), 286 c.getInt(MAILBOX_STATE_LOOKBACK))); 287 } 288 } finally { 289 c.close(); 290 } 291 } 292 } 293 294 /** 295 * For every set of saved mailbox sync options, try to find and restore those values 296 */ 297 @VisibleForTesting 298 void restoreMailboxSyncOptions() { 299 try { 300 ContentValues cv = new ContentValues(); 301 mBindArguments[1] = mAccountIdAsString; 302 for (String serverId: mSyncOptionsMap.keySet()) { 303 SyncOptions options = mSyncOptionsMap.get(serverId); 304 cv.put(MailboxColumns.SYNC_INTERVAL, options.mInterval); 305 cv.put(MailboxColumns.SYNC_LOOKBACK, options.mLookback); 306 mBindArguments[0] = serverId; 307 // If we match account and server id, set the sync options 308 mContentResolver.update(Mailbox.CONTENT_URI, cv, WHERE_SERVER_ID_AND_ACCOUNT, 309 mBindArguments); 310 } 311 } finally { 312 mSyncOptionsMap.clear(); 313 } 314 } 315 316 public Mailbox addParser() throws IOException { 317 String name = null; 318 String serverId = null; 319 String parentId = null; 320 int type = 0; 321 322 while (nextTag(Tags.FOLDER_ADD) != END) { 323 switch (tag) { 324 case Tags.FOLDER_DISPLAY_NAME: { 325 name = getValue(); 326 break; 327 } 328 case Tags.FOLDER_TYPE: { 329 type = getValueInt(); 330 break; 331 } 332 case Tags.FOLDER_PARENT_ID: { 333 parentId = getValue(); 334 break; 335 } 336 case Tags.FOLDER_SERVER_ID: { 337 serverId = getValue(); 338 break; 339 } 340 default: 341 skipTag(); 342 } 343 } 344 345 if (VALID_EAS_FOLDER_TYPES.contains(type)) { 346 Mailbox mailbox = new Mailbox(); 347 mailbox.mDisplayName = name; 348 mailbox.mServerId = serverId; 349 mailbox.mAccountKey = mAccountId; 350 mailbox.mType = Mailbox.TYPE_MAIL; 351 // Note that all mailboxes default to checking "never" (i.e. manual sync only) 352 // We set specific intervals for inbox, contacts, and (eventually) calendar 353 mailbox.mSyncInterval = Mailbox.CHECK_INTERVAL_NEVER; 354 switch (type) { 355 case INBOX_TYPE: 356 mailbox.mType = Mailbox.TYPE_INBOX; 357 mailbox.mSyncInterval = mAccount.mSyncInterval; 358 break; 359 case CONTACTS_TYPE: 360 mailbox.mType = Mailbox.TYPE_CONTACTS; 361 mailbox.mSyncInterval = mAccount.mSyncInterval; 362 break; 363 case OUTBOX_TYPE: 364 // TYPE_OUTBOX mailboxes are known by ExchangeService to sync whenever they 365 // aren't empty. The value of mSyncFrequency is ignored for this kind of 366 // mailbox. 367 mailbox.mType = Mailbox.TYPE_OUTBOX; 368 break; 369 case SENT_TYPE: 370 mailbox.mType = Mailbox.TYPE_SENT; 371 break; 372 case DRAFTS_TYPE: 373 mailbox.mType = Mailbox.TYPE_DRAFTS; 374 break; 375 case DELETED_TYPE: 376 mailbox.mType = Mailbox.TYPE_TRASH; 377 break; 378 case CALENDAR_TYPE: 379 mailbox.mType = Mailbox.TYPE_CALENDAR; 380 mailbox.mSyncInterval = mAccount.mSyncInterval; 381 break; 382 case USER_GENERIC_TYPE: 383 mailbox.mType = Mailbox.TYPE_UNKNOWN; 384 break; 385 } 386 387 // Make boxes like Contacts and Calendar invisible in the folder list 388 mailbox.mFlagVisible = (mailbox.mType < Mailbox.TYPE_NOT_EMAIL); 389 390 if (!parentId.equals("0")) { 391 mailbox.mParentServerId = parentId; 392 if (!mInitialSync) { 393 mParentFixupsNeeded.add(parentId); 394 } 395 } 396 // At the least, we'll need to set flags 397 mFixupUninitializedNeeded = true; 398 399 return mailbox; 400 } 401 return null; 402 } 403 404 /** 405 * Determine whether a given mailbox holds mail, rather than other data. We do this by first 406 * checking the type of the mailbox (if it's a known good type, great; if it's a known bad 407 * type, return false). If it's unknown, we check the parent, first by trying to find it in 408 * the current set of newly synced items, and then by looking it up in EmailProvider. If 409 * we can find the parent, we use the same rules to determine if it holds mail; if it does, 410 * then its children do as well, so that's a go. 411 * 412 * @param mailbox the mailbox we're checking 413 * @param mailboxMap a HashMap relating server id's of mailboxes in the current sync set to 414 * the corresponding mailbox structures 415 * @return whether or not the mailbox contains email (rather than PIM or unknown data) 416 */ 417 /*package*/ boolean isValidMailFolder(Mailbox mailbox, HashMap<String, Mailbox> mailboxMap) { 418 int folderType = mailbox.mType; 419 // Automatically accept our email types 420 if (folderType < Mailbox.TYPE_NOT_EMAIL) return true; 421 // Automatically reject everything else but "unknown" 422 if (folderType != Mailbox.TYPE_UNKNOWN) return false; 423 // If this is TYPE_UNKNOWN, check the parent 424 Mailbox parent = mailboxMap.get(mailbox.mParentServerId); 425 // If the parent is in the map, then check it out; if not, it could be an existing saved 426 // Mailbox, so we'll have to query the database 427 if (parent == null) { 428 mBindArguments[0] = Long.toString(mAccount.mId); 429 long parentId = -1; 430 if (mailbox.mParentServerId != null) { 431 mBindArguments[1] = mailbox.mParentServerId; 432 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 } 437 if (parentId != -1) { 438 // Get the parent from the database 439 parent = Mailbox.restoreMailboxWithId(mContext, parentId); 440 if (parent == null) return false; 441 } else { 442 return false; 443 } 444 } 445 return isValidMailFolder(parent, mailboxMap); 446 } 447 448 public void updateParser(ArrayList<ContentProviderOperation> ops) throws IOException { 449 String serverId = null; 450 String displayName = null; 451 String parentId = null; 452 while (nextTag(Tags.FOLDER_UPDATE) != END) { 453 switch (tag) { 454 case Tags.FOLDER_SERVER_ID: 455 serverId = getValue(); 456 break; 457 case Tags.FOLDER_DISPLAY_NAME: 458 displayName = getValue(); 459 break; 460 case Tags.FOLDER_PARENT_ID: 461 parentId = getValue(); 462 break; 463 default: 464 skipTag(); 465 break; 466 } 467 } 468 // We'll make a change if one of parentId or displayName are specified 469 // serverId is required, but let's be careful just the same 470 if (serverId != null && (displayName != null || parentId != null)) { 471 Cursor c = getServerIdCursor(serverId); 472 try { 473 // If we find the mailbox (using serverId), make the change 474 if (c.moveToFirst()) { 475 userLog("Updating ", serverId); 476 // Fix up old and new parents, as needed 477 if (!TextUtils.isEmpty(parentId)) { 478 mParentFixupsNeeded.add(parentId); 479 } 480 String oldParentId = c.getString(MAILBOX_ID_COLUMNS_PARENT_SERVER_ID); 481 if (!TextUtils.isEmpty(oldParentId)) { 482 mParentFixupsNeeded.add(oldParentId); 483 } 484 // Set display name if we've got one 485 ContentValues cv = new ContentValues(); 486 if (displayName != null) { 487 cv.put(Mailbox.DISPLAY_NAME, displayName); 488 } 489 // Save away the server id and uninitialize the parent key 490 cv.put(Mailbox.PARENT_SERVER_ID, parentId); 491 // Clear the parent key; it will be fixed up after the commit 492 cv.put(Mailbox.PARENT_KEY, Mailbox.PARENT_KEY_UNINITIALIZED); 493 ops.add(ContentProviderOperation.newUpdate( 494 ContentUris.withAppendedId(Mailbox.CONTENT_URI, 495 c.getLong(MAILBOX_ID_COLUMNS_ID))).withValues(cv).build()); 496 // Say we need to fixup uninitialized mailboxes 497 mFixupUninitializedNeeded = true; 498 } 499 } finally { 500 c.close(); 501 } 502 } 503 } 504 505 private boolean commitMailboxes(ArrayList<Mailbox> validMailboxes, 506 ArrayList<Mailbox> userMailboxes, HashMap<String, Mailbox> mailboxMap, 507 ArrayList<ContentProviderOperation> ops) { 508 509 // Go through the generic user mailboxes; we'll call them valid if any parent is valid 510 for (Mailbox m: userMailboxes) { 511 if (isValidMailFolder(m, mailboxMap)) { 512 m.mType = Mailbox.TYPE_MAIL; 513 validMailboxes.add(m); 514 } else { 515 userLog("Rejecting unknown type mailbox: " + m.mDisplayName); 516 } 517 } 518 519 // Add operations for all valid mailboxes 520 for (Mailbox m: validMailboxes) { 521 userLog("Adding mailbox: ", m.mDisplayName); 522 ops.add(ContentProviderOperation 523 .newInsert(Mailbox.CONTENT_URI).withValues(m.toContentValues()).build()); 524 } 525 526 // Commit the mailboxes 527 userLog("Applying ", mOperations.size(), " mailbox operations."); 528 // Execute the batch; throw IOExceptions if this fails, hoping the issue isn't repeatable 529 // If it IS repeatable, there's no good result, since the folder list will be invalid 530 try { 531 mContentResolver.applyBatch(EmailContent.AUTHORITY, mOperations); 532 return true; 533 } catch (RemoteException e) { 534 userLog("RemoteException in commitMailboxes"); 535 return false; 536 } catch (OperationApplicationException e) { 537 userLog("OperationApplicationException in commitMailboxes"); 538 return false; 539 } 540 } 541 542 public void changesParser(final ArrayList<ContentProviderOperation> ops, 543 final boolean initialSync) throws IOException { 544 545 // Array of added mailboxes 546 final ArrayList<Mailbox> addMailboxes = new ArrayList<Mailbox>(); 547 548 // Indicate start of (potential) mailbox changes 549 MailboxUtilities.startMailboxChanges(mContext, mAccount.mId); 550 551 while (nextTag(Tags.FOLDER_CHANGES) != END) { 552 if (tag == Tags.FOLDER_ADD) { 553 Mailbox mailbox = addParser(); 554 if (mailbox != null) { 555 addMailboxes.add(mailbox); 556 } 557 } else if (tag == Tags.FOLDER_DELETE) { 558 deleteParser(ops); 559 } else if (tag == Tags.FOLDER_UPDATE) { 560 updateParser(ops); 561 } else if (tag == Tags.FOLDER_COUNT) { 562 getValueInt(); 563 } else 564 skipTag(); 565 } 566 567 // Synchronize on the parser to prevent this being run concurrently 568 // (an extremely unlikely event, but nonetheless possible) 569 synchronized (FolderSyncParser.this) { 570 // Mailboxes that we known contain email 571 ArrayList<Mailbox> validMailboxes = new ArrayList<Mailbox>(); 572 // Mailboxes that we're unsure about 573 ArrayList<Mailbox> userMailboxes = new ArrayList<Mailbox>(); 574 575 // Maps folder serverId to mailbox (used to validate user mailboxes) 576 HashMap<String, Mailbox> mailboxMap = new HashMap<String, Mailbox>(); 577 for (Mailbox mailbox : addMailboxes) { 578 mailboxMap.put(mailbox.mServerId, mailbox); 579 } 580 581 int mailboxCommitCount = 0; 582 for (Mailbox mailbox : addMailboxes) { 583 // And add the mailbox to the proper list 584 if (mailbox.mType == Mailbox.TYPE_UNKNOWN) { 585 userMailboxes.add(mailbox); 586 } else { 587 validMailboxes.add(mailbox); 588 } 589 // On initial sync, we commit what we have every 20 mailboxes 590 if (initialSync && (++mailboxCommitCount == MAILBOX_COMMIT_SIZE)) { 591 if (!commitMailboxes(validMailboxes, userMailboxes, mailboxMap, 592 ops)) { 593 mService.stop(); 594 return; 595 } 596 // Clear our arrays to prepare for more 597 userMailboxes.clear(); 598 validMailboxes.clear(); 599 ops.clear(); 600 mailboxCommitCount = 0; 601 } 602 } 603 // Commit the sync key and mailboxes 604 ContentValues cv = new ContentValues(); 605 cv.put(AccountColumns.SYNC_KEY, mAccount.mSyncKey); 606 ops.add(ContentProviderOperation 607 .newUpdate( 608 ContentUris.withAppendedId(Account.CONTENT_URI, 609 mAccount.mId)) 610 .withValues(cv).build()); 611 if (!commitMailboxes(validMailboxes, userMailboxes, mailboxMap, ops)) { 612 mService.stop(); 613 return; 614 } 615 String accountSelector = Mailbox.ACCOUNT_KEY + "=" + mAccount.mId; 616 // For new boxes, setup the parent key and flags 617 if (mFixupUninitializedNeeded) { 618 MailboxUtilities.fixupUninitializedParentKeys(mContext, 619 accountSelector); 620 } 621 // For modified parents, reset the flags (and children's parent key) 622 for (String parentServerId: mParentFixupsNeeded) { 623 Cursor c = mContentResolver.query(Mailbox.CONTENT_URI, 624 Mailbox.CONTENT_PROJECTION, Mailbox.PARENT_SERVER_ID + "=?", 625 new String[] {parentServerId}, null); 626 try { 627 if (c.moveToFirst()) { 628 MailboxUtilities.setFlagsAndChildrensParentKey(mContext, c, 629 accountSelector); 630 } 631 } finally { 632 c.close(); 633 } 634 } 635 636 // Signal completion of mailbox changes 637 MailboxUtilities.endMailboxChanges(mContext, mAccount.mId); 638 } 639 } 640 641 /** 642 * Not needed for FolderSync parsing; everything is done within changesParser 643 */ 644 @Override 645 public void commandsParser() throws IOException { 646 } 647 648 /** 649 * Clean up after sync 650 */ 651 @Override 652 public void commit() throws IOException { 653 // Look for sync issues and its children and delete them 654 // I'm not aware of any other way to deal with this properly 655 mBindArguments[0] = "Sync Issues"; 656 mBindArguments[1] = mAccountIdAsString; 657 Cursor c = mContentResolver.query(Mailbox.CONTENT_URI, 658 MAILBOX_ID_COLUMNS_PROJECTION, WHERE_DISPLAY_NAME_AND_ACCOUNT, 659 mBindArguments, null); 660 String parentServerId = null; 661 long id = 0; 662 try { 663 if (c.moveToFirst()) { 664 id = c.getLong(MAILBOX_ID_COLUMNS_ID); 665 parentServerId = c.getString(MAILBOX_ID_COLUMNS_SERVER_ID); 666 } 667 } finally { 668 c.close(); 669 } 670 if (parentServerId != null) { 671 mContentResolver.delete(ContentUris.withAppendedId(Mailbox.CONTENT_URI, id), 672 null, null); 673 mBindArguments[0] = parentServerId; 674 mContentResolver.delete(Mailbox.CONTENT_URI, WHERE_PARENT_SERVER_ID_AND_ACCOUNT, 675 mBindArguments); 676 } 677 678 // If we have saved options, restore them now 679 if (mInitialSync) { 680 restoreMailboxSyncOptions(); 681 } 682 } 683 684 @Override 685 public void responsesParser() throws IOException { 686 } 687 688} 689