1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.content; 18 19 import com.android.internal.os.AtomicFile; 20 import com.android.internal.util.ArrayUtils; 21 import com.android.internal.util.FastXmlSerializer; 22 23 import org.xmlpull.v1.XmlPullParser; 24 import org.xmlpull.v1.XmlPullParserException; 25 import org.xmlpull.v1.XmlSerializer; 26 27 import android.accounts.Account; 28 import android.database.Cursor; 29 import android.database.sqlite.SQLiteDatabase; 30 import android.database.sqlite.SQLiteException; 31 import android.database.sqlite.SQLiteQueryBuilder; 32 import android.os.Bundle; 33 import android.os.Environment; 34 import android.os.Handler; 35 import android.os.Message; 36 import android.os.Parcel; 37 import android.os.RemoteCallbackList; 38 import android.os.RemoteException; 39 import android.util.Log; 40 import android.util.SparseArray; 41 import android.util.Xml; 42 import android.util.Pair; 43 44 import java.io.File; 45 import java.io.FileInputStream; 46 import java.io.FileOutputStream; 47 import java.util.ArrayList; 48 import java.util.Calendar; 49 import java.util.HashMap; 50 import java.util.Iterator; 51 import java.util.TimeZone; 52 import java.util.List; 53 54 /** 55 * Singleton that tracks the sync data and overall sync 56 * history on the device. 57 * 58 * @hide 59 */ 60 public class SyncStorageEngine extends Handler { 61 private static final String TAG = "SyncManager"; 62 private static final boolean DEBUG_FILE = false; 63 64 private static final long DEFAULT_POLL_FREQUENCY_SECONDS = 60 * 60 * 24; // One day 65 66 // @VisibleForTesting 67 static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4; 68 69 /** Enum value for a sync start event. */ 70 public static final int EVENT_START = 0; 71 72 /** Enum value for a sync stop event. */ 73 public static final int EVENT_STOP = 1; 74 75 // TODO: i18n -- grab these out of resources. 76 /** String names for the sync event types. */ 77 public static final String[] EVENTS = { "START", "STOP" }; 78 79 /** Enum value for a server-initiated sync. */ 80 public static final int SOURCE_SERVER = 0; 81 82 /** Enum value for a local-initiated sync. */ 83 public static final int SOURCE_LOCAL = 1; 84 /** 85 * Enum value for a poll-based sync (e.g., upon connection to 86 * network) 87 */ 88 public static final int SOURCE_POLL = 2; 89 90 /** Enum value for a user-initiated sync. */ 91 public static final int SOURCE_USER = 3; 92 93 /** Enum value for a periodic sync. */ 94 public static final int SOURCE_PERIODIC = 4; 95 96 public static final long NOT_IN_BACKOFF_MODE = -1; 97 98 public static final Intent SYNC_CONNECTION_SETTING_CHANGED_INTENT = 99 new Intent("com.android.sync.SYNC_CONN_STATUS_CHANGED"); 100 101 // TODO: i18n -- grab these out of resources. 102 /** String names for the sync source types. */ 103 public static final String[] SOURCES = { "SERVER", 104 "LOCAL", 105 "POLL", 106 "USER", 107 "PERIODIC" }; 108 109 // The MESG column will contain one of these or one of the Error types. 110 public static final String MESG_SUCCESS = "success"; 111 public static final String MESG_CANCELED = "canceled"; 112 113 public static final int MAX_HISTORY = 100; 114 115 private static final int MSG_WRITE_STATUS = 1; 116 private static final long WRITE_STATUS_DELAY = 1000*60*10; // 10 minutes 117 118 private static final int MSG_WRITE_STATISTICS = 2; 119 private static final long WRITE_STATISTICS_DELAY = 1000*60*30; // 1/2 hour 120 121 private static final boolean SYNC_ENABLED_DEFAULT = false; 122 123 // the version of the accounts xml file format 124 private static final int ACCOUNTS_VERSION = 2; 125 126 private static HashMap<String, String> sAuthorityRenames; 127 128 static { 129 sAuthorityRenames = new HashMap<String, String>(); 130 sAuthorityRenames.put("contacts", "com.android.contacts"); 131 sAuthorityRenames.put("calendar", "com.android.calendar"); 132 } 133 134 public static class PendingOperation { 135 final Account account; 136 final int syncSource; 137 final String authority; 138 final Bundle extras; // note: read-only. 139 final boolean expedited; 140 141 int authorityId; 142 byte[] flatExtras; 143 144 PendingOperation(Account account, int source, 145 String authority, Bundle extras, boolean expedited) { 146 this.account = account; 147 this.syncSource = source; 148 this.authority = authority; 149 this.extras = extras != null ? new Bundle(extras) : extras; 150 this.expedited = expedited; 151 this.authorityId = -1; 152 } 153 154 PendingOperation(PendingOperation other) { 155 this.account = other.account; 156 this.syncSource = other.syncSource; 157 this.authority = other.authority; 158 this.extras = other.extras; 159 this.authorityId = other.authorityId; 160 this.expedited = other.expedited; 161 } 162 } 163 164 static class AccountInfo { 165 final Account account; 166 final HashMap<String, AuthorityInfo> authorities = 167 new HashMap<String, AuthorityInfo>(); 168 169 AccountInfo(Account account) { 170 this.account = account; 171 } 172 } 173 174 public static class AuthorityInfo { 175 final Account account; 176 final String authority; 177 final int ident; 178 boolean enabled; 179 int syncable; 180 long backoffTime; 181 long backoffDelay; 182 long delayUntil; 183 final ArrayList<Pair<Bundle, Long>> periodicSyncs; 184 185 AuthorityInfo(Account account, String authority, int ident) { 186 this.account = account; 187 this.authority = authority; 188 this.ident = ident; 189 enabled = SYNC_ENABLED_DEFAULT; 190 syncable = -1; // default to "unknown" 191 backoffTime = -1; // if < 0 then we aren't in backoff mode 192 backoffDelay = -1; // if < 0 then we aren't in backoff mode 193 periodicSyncs = new ArrayList<Pair<Bundle, Long>>(); 194 periodicSyncs.add(Pair.create(new Bundle(), DEFAULT_POLL_FREQUENCY_SECONDS)); 195 } 196 } 197 198 public static class SyncHistoryItem { 199 int authorityId; 200 int historyId; 201 long eventTime; 202 long elapsedTime; 203 int source; 204 int event; 205 long upstreamActivity; 206 long downstreamActivity; 207 String mesg; 208 } 209 210 public static class DayStats { 211 public final int day; 212 public int successCount; 213 public long successTime; 214 public int failureCount; 215 public long failureTime; 216 217 public DayStats(int day) { 218 this.day = day; 219 } 220 } 221 222 // Primary list of all syncable authorities. Also our global lock. 223 private final SparseArray<AuthorityInfo> mAuthorities = 224 new SparseArray<AuthorityInfo>(); 225 226 private final HashMap<Account, AccountInfo> mAccounts = 227 new HashMap<Account, AccountInfo>(); 228 229 private final ArrayList<PendingOperation> mPendingOperations = 230 new ArrayList<PendingOperation>(); 231 232 private final ArrayList<SyncInfo> mCurrentSyncs = new ArrayList<SyncInfo>(); 233 234 private final SparseArray<SyncStatusInfo> mSyncStatus = 235 new SparseArray<SyncStatusInfo>(); 236 237 private final ArrayList<SyncHistoryItem> mSyncHistory = 238 new ArrayList<SyncHistoryItem>(); 239 240 private final RemoteCallbackList<ISyncStatusObserver> mChangeListeners 241 = new RemoteCallbackList<ISyncStatusObserver>(); 242 243 private int mNextAuthorityId = 0; 244 245 // We keep 4 weeks of stats. 246 private final DayStats[] mDayStats = new DayStats[7*4]; 247 private final Calendar mCal; 248 private int mYear; 249 private int mYearInDays; 250 251 private final Context mContext; 252 253 private static volatile SyncStorageEngine sSyncStorageEngine = null; 254 255 /** 256 * This file contains the core engine state: all accounts and the 257 * settings for them. It must never be lost, and should be changed 258 * infrequently, so it is stored as an XML file. 259 */ 260 private final AtomicFile mAccountInfoFile; 261 262 /** 263 * This file contains the current sync status. We would like to retain 264 * it across boots, but its loss is not the end of the world, so we store 265 * this information as binary data. 266 */ 267 private final AtomicFile mStatusFile; 268 269 /** 270 * This file contains sync statistics. This is purely debugging information 271 * so is written infrequently and can be thrown away at any time. 272 */ 273 private final AtomicFile mStatisticsFile; 274 275 /** 276 * This file contains the pending sync operations. It is a binary file, 277 * which must be updated every time an operation is added or removed, 278 * so we have special handling of it. 279 */ 280 private final AtomicFile mPendingFile; 281 private static final int PENDING_FINISH_TO_WRITE = 4; 282 private int mNumPendingFinished = 0; 283 284 private int mNextHistoryId = 0; 285 private boolean mMasterSyncAutomatically = true; 286 287 private SyncStorageEngine(Context context, File dataDir) { 288 mContext = context; 289 sSyncStorageEngine = this; 290 291 mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0")); 292 293 File systemDir = new File(dataDir, "system"); 294 File syncDir = new File(systemDir, "sync"); 295 syncDir.mkdirs(); 296 mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml")); 297 mStatusFile = new AtomicFile(new File(syncDir, "status.bin")); 298 mPendingFile = new AtomicFile(new File(syncDir, "pending.bin")); 299 mStatisticsFile = new AtomicFile(new File(syncDir, "stats.bin")); 300 301 readAccountInfoLocked(); 302 readStatusLocked(); 303 readPendingOperationsLocked(); 304 readStatisticsLocked(); 305 readAndDeleteLegacyAccountInfoLocked(); 306 writeAccountInfoLocked(); 307 writeStatusLocked(); 308 writePendingOperationsLocked(); 309 writeStatisticsLocked(); 310 } 311 312 public static SyncStorageEngine newTestInstance(Context context) { 313 return new SyncStorageEngine(context, context.getFilesDir()); 314 } 315 316 public static void init(Context context) { 317 if (sSyncStorageEngine != null) { 318 return; 319 } 320 // This call will return the correct directory whether Encrypted File Systems is 321 // enabled or not. 322 File dataDir = Environment.getSecureDataDirectory(); 323 sSyncStorageEngine = new SyncStorageEngine(context, dataDir); 324 } 325 326 public static SyncStorageEngine getSingleton() { 327 if (sSyncStorageEngine == null) { 328 throw new IllegalStateException("not initialized"); 329 } 330 return sSyncStorageEngine; 331 } 332 333 @Override public void handleMessage(Message msg) { 334 if (msg.what == MSG_WRITE_STATUS) { 335 synchronized (mAuthorities) { 336 writeStatusLocked(); 337 } 338 } else if (msg.what == MSG_WRITE_STATISTICS) { 339 synchronized (mAuthorities) { 340 writeStatisticsLocked(); 341 } 342 } 343 } 344 345 public void addStatusChangeListener(int mask, ISyncStatusObserver callback) { 346 synchronized (mAuthorities) { 347 mChangeListeners.register(callback, mask); 348 } 349 } 350 351 public void removeStatusChangeListener(ISyncStatusObserver callback) { 352 synchronized (mAuthorities) { 353 mChangeListeners.unregister(callback); 354 } 355 } 356 357 private void reportChange(int which) { 358 ArrayList<ISyncStatusObserver> reports = null; 359 synchronized (mAuthorities) { 360 int i = mChangeListeners.beginBroadcast(); 361 while (i > 0) { 362 i--; 363 Integer mask = (Integer)mChangeListeners.getBroadcastCookie(i); 364 if ((which & mask.intValue()) == 0) { 365 continue; 366 } 367 if (reports == null) { 368 reports = new ArrayList<ISyncStatusObserver>(i); 369 } 370 reports.add(mChangeListeners.getBroadcastItem(i)); 371 } 372 mChangeListeners.finishBroadcast(); 373 } 374 375 if (Log.isLoggable(TAG, Log.VERBOSE)) { 376 Log.v(TAG, "reportChange " + which + " to: " + reports); 377 } 378 379 if (reports != null) { 380 int i = reports.size(); 381 while (i > 0) { 382 i--; 383 try { 384 reports.get(i).onStatusChanged(which); 385 } catch (RemoteException e) { 386 // The remote callback list will take care of this for us. 387 } 388 } 389 } 390 } 391 392 public boolean getSyncAutomatically(Account account, String providerName) { 393 synchronized (mAuthorities) { 394 if (account != null) { 395 AuthorityInfo authority = getAuthorityLocked(account, providerName, 396 "getSyncAutomatically"); 397 return authority != null && authority.enabled; 398 } 399 400 int i = mAuthorities.size(); 401 while (i > 0) { 402 i--; 403 AuthorityInfo authority = mAuthorities.valueAt(i); 404 if (authority.authority.equals(providerName) 405 && authority.enabled) { 406 return true; 407 } 408 } 409 return false; 410 } 411 } 412 413 public void setSyncAutomatically(Account account, String providerName, boolean sync) { 414 Log.d(TAG, "setSyncAutomatically: " + /*account +*/ ", provider " + providerName 415 + " -> " + sync); 416 synchronized (mAuthorities) { 417 AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false); 418 if (authority.enabled == sync) { 419 Log.d(TAG, "setSyncAutomatically: already set to " + sync + ", doing nothing"); 420 return; 421 } 422 authority.enabled = sync; 423 writeAccountInfoLocked(); 424 } 425 426 if (sync) { 427 ContentResolver.requestSync(account, providerName, new Bundle()); 428 } 429 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); 430 } 431 432 public int getIsSyncable(Account account, String providerName) { 433 synchronized (mAuthorities) { 434 if (account != null) { 435 AuthorityInfo authority = getAuthorityLocked(account, providerName, 436 "getIsSyncable"); 437 if (authority == null) { 438 return -1; 439 } 440 return authority.syncable; 441 } 442 443 int i = mAuthorities.size(); 444 while (i > 0) { 445 i--; 446 AuthorityInfo authority = mAuthorities.valueAt(i); 447 if (authority.authority.equals(providerName)) { 448 return authority.syncable; 449 } 450 } 451 return -1; 452 } 453 } 454 455 public void setIsSyncable(Account account, String providerName, int syncable) { 456 if (syncable > 1) { 457 syncable = 1; 458 } else if (syncable < -1) { 459 syncable = -1; 460 } 461 Log.d(TAG, "setIsSyncable: " + account + ", provider " + providerName + " -> " + syncable); 462 synchronized (mAuthorities) { 463 AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false); 464 if (authority.syncable == syncable) { 465 Log.d(TAG, "setIsSyncable: already set to " + syncable + ", doing nothing"); 466 return; 467 } 468 authority.syncable = syncable; 469 writeAccountInfoLocked(); 470 } 471 472 if (syncable > 0) { 473 ContentResolver.requestSync(account, providerName, new Bundle()); 474 } 475 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); 476 } 477 478 public Pair<Long, Long> getBackoff(Account account, String providerName) { 479 synchronized (mAuthorities) { 480 AuthorityInfo authority = getAuthorityLocked(account, providerName, "getBackoff"); 481 if (authority == null || authority.backoffTime < 0) { 482 return null; 483 } 484 return Pair.create(authority.backoffTime, authority.backoffDelay); 485 } 486 } 487 488 public void setBackoff(Account account, String providerName, 489 long nextSyncTime, long nextDelay) { 490 if (Log.isLoggable(TAG, Log.VERBOSE)) { 491 Log.v(TAG, "setBackoff: " + account + ", provider " + providerName 492 + " -> nextSyncTime " + nextSyncTime + ", nextDelay " + nextDelay); 493 } 494 boolean changed = false; 495 synchronized (mAuthorities) { 496 if (account == null || providerName == null) { 497 for (AccountInfo accountInfo : mAccounts.values()) { 498 if (account != null && !account.equals(accountInfo.account)) continue; 499 for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) { 500 if (providerName != null && !providerName.equals(authorityInfo.authority)) { 501 continue; 502 } 503 if (authorityInfo.backoffTime != nextSyncTime 504 || authorityInfo.backoffDelay != nextDelay) { 505 authorityInfo.backoffTime = nextSyncTime; 506 authorityInfo.backoffDelay = nextDelay; 507 changed = true; 508 } 509 } 510 } 511 } else { 512 AuthorityInfo authority = 513 getOrCreateAuthorityLocked(account, providerName, -1 /* ident */, true); 514 if (authority.backoffTime == nextSyncTime && authority.backoffDelay == nextDelay) { 515 return; 516 } 517 authority.backoffTime = nextSyncTime; 518 authority.backoffDelay = nextDelay; 519 changed = true; 520 } 521 } 522 523 if (changed) { 524 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); 525 } 526 } 527 528 public void clearAllBackoffs(SyncQueue syncQueue) { 529 boolean changed = false; 530 synchronized (mAuthorities) { 531 for (AccountInfo accountInfo : mAccounts.values()) { 532 for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) { 533 if (authorityInfo.backoffTime != NOT_IN_BACKOFF_MODE 534 || authorityInfo.backoffDelay != NOT_IN_BACKOFF_MODE) { 535 if (Log.isLoggable(TAG, Log.VERBOSE)) { 536 Log.v(TAG, "clearAllBackoffs:" 537 + " authority:" + authorityInfo.authority 538 + " account:" + accountInfo.account.name 539 + " backoffTime was: " + authorityInfo.backoffTime 540 + " backoffDelay was: " + authorityInfo.backoffDelay); 541 } 542 authorityInfo.backoffTime = NOT_IN_BACKOFF_MODE; 543 authorityInfo.backoffDelay = NOT_IN_BACKOFF_MODE; 544 syncQueue.onBackoffChanged(accountInfo.account, authorityInfo.authority, 0); 545 changed = true; 546 } 547 } 548 } 549 } 550 551 if (changed) { 552 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); 553 } 554 } 555 556 public void setDelayUntilTime(Account account, String providerName, long delayUntil) { 557 if (Log.isLoggable(TAG, Log.VERBOSE)) { 558 Log.v(TAG, "setDelayUntil: " + account + ", provider " + providerName 559 + " -> delayUntil " + delayUntil); 560 } 561 synchronized (mAuthorities) { 562 AuthorityInfo authority = getOrCreateAuthorityLocked( 563 account, providerName, -1 /* ident */, true); 564 if (authority.delayUntil == delayUntil) { 565 return; 566 } 567 authority.delayUntil = delayUntil; 568 } 569 570 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); 571 } 572 573 public long getDelayUntilTime(Account account, String providerName) { 574 synchronized (mAuthorities) { 575 AuthorityInfo authority = getAuthorityLocked(account, providerName, "getDelayUntil"); 576 if (authority == null) { 577 return 0; 578 } 579 return authority.delayUntil; 580 } 581 } 582 583 private void updateOrRemovePeriodicSync(Account account, String providerName, Bundle extras, 584 long period, boolean add) { 585 if (period <= 0) { 586 period = 0; 587 } 588 if (extras == null) { 589 extras = new Bundle(); 590 } 591 if (Log.isLoggable(TAG, Log.VERBOSE)) { 592 Log.v(TAG, "addOrRemovePeriodicSync: " + account + ", provider " + providerName 593 + " -> period " + period + ", extras " + extras); 594 } 595 synchronized (mAuthorities) { 596 try { 597 AuthorityInfo authority = 598 getOrCreateAuthorityLocked(account, providerName, -1, false); 599 if (add) { 600 // add this periodic sync if one with the same extras doesn't already 601 // exist in the periodicSyncs array 602 boolean alreadyPresent = false; 603 for (int i = 0, N = authority.periodicSyncs.size(); i < N; i++) { 604 Pair<Bundle, Long> syncInfo = authority.periodicSyncs.get(i); 605 final Bundle existingExtras = syncInfo.first; 606 if (equals(existingExtras, extras)) { 607 if (syncInfo.second == period) { 608 return; 609 } 610 authority.periodicSyncs.set(i, Pair.create(extras, period)); 611 alreadyPresent = true; 612 break; 613 } 614 } 615 // if we added an entry to the periodicSyncs array also add an entry to 616 // the periodic syncs status to correspond to it 617 if (!alreadyPresent) { 618 authority.periodicSyncs.add(Pair.create(extras, period)); 619 SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident); 620 status.setPeriodicSyncTime(authority.periodicSyncs.size() - 1, 0); 621 } 622 } else { 623 // remove any periodic syncs that match the authority and extras 624 SyncStatusInfo status = mSyncStatus.get(authority.ident); 625 boolean changed = false; 626 Iterator<Pair<Bundle, Long>> iterator = authority.periodicSyncs.iterator(); 627 int i = 0; 628 while (iterator.hasNext()) { 629 Pair<Bundle, Long> syncInfo = iterator.next(); 630 if (equals(syncInfo.first, extras)) { 631 iterator.remove(); 632 changed = true; 633 // if we removed an entry from the periodicSyncs array also 634 // remove the corresponding entry from the status 635 if (status != null) { 636 status.removePeriodicSyncTime(i); 637 } 638 } else { 639 i++; 640 } 641 } 642 if (!changed) { 643 return; 644 } 645 } 646 } finally { 647 writeAccountInfoLocked(); 648 writeStatusLocked(); 649 } 650 } 651 652 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); 653 } 654 655 public void addPeriodicSync(Account account, String providerName, Bundle extras, 656 long pollFrequency) { 657 updateOrRemovePeriodicSync(account, providerName, extras, pollFrequency, true /* add */); 658 } 659 660 public void removePeriodicSync(Account account, String providerName, Bundle extras) { 661 updateOrRemovePeriodicSync(account, providerName, extras, 0 /* period, ignored */, 662 false /* remove */); 663 } 664 665 public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) { 666 ArrayList<PeriodicSync> syncs = new ArrayList<PeriodicSync>(); 667 synchronized (mAuthorities) { 668 AuthorityInfo authority = getAuthorityLocked(account, providerName, "getPeriodicSyncs"); 669 if (authority != null) { 670 for (Pair<Bundle, Long> item : authority.periodicSyncs) { 671 syncs.add(new PeriodicSync(account, providerName, item.first, item.second)); 672 } 673 } 674 } 675 return syncs; 676 } 677 678 public void setMasterSyncAutomatically(boolean flag) { 679 synchronized (mAuthorities) { 680 if (mMasterSyncAutomatically == flag) { 681 return; 682 } 683 mMasterSyncAutomatically = flag; 684 writeAccountInfoLocked(); 685 } 686 if (flag) { 687 ContentResolver.requestSync(null, null, new Bundle()); 688 } 689 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); 690 mContext.sendBroadcast(SYNC_CONNECTION_SETTING_CHANGED_INTENT); 691 } 692 693 public boolean getMasterSyncAutomatically() { 694 synchronized (mAuthorities) { 695 return mMasterSyncAutomatically; 696 } 697 } 698 699 public AuthorityInfo getOrCreateAuthority(Account account, String authority) { 700 synchronized (mAuthorities) { 701 return getOrCreateAuthorityLocked(account, authority, 702 -1 /* assign a new identifier if creating a new authority */, 703 true /* write to storage if this results in a change */); 704 } 705 } 706 707 public void removeAuthority(Account account, String authority) { 708 synchronized (mAuthorities) { 709 removeAuthorityLocked(account, authority, true /* doWrite */); 710 } 711 } 712 713 public AuthorityInfo getAuthority(int authorityId) { 714 synchronized (mAuthorities) { 715 return mAuthorities.get(authorityId); 716 } 717 } 718 719 /** 720 * Returns true if there is currently a sync operation for the given 721 * account or authority actively being processed. 722 */ 723 public boolean isSyncActive(Account account, String authority) { 724 synchronized (mAuthorities) { 725 for (SyncInfo syncInfo : mCurrentSyncs) { 726 AuthorityInfo ainfo = getAuthority(syncInfo.authorityId); 727 if (ainfo != null && ainfo.account.equals(account) 728 && ainfo.authority.equals(authority)) { 729 return true; 730 } 731 } 732 } 733 734 return false; 735 } 736 737 public PendingOperation insertIntoPending(PendingOperation op) { 738 synchronized (mAuthorities) { 739 if (Log.isLoggable(TAG, Log.VERBOSE)) { 740 Log.v(TAG, "insertIntoPending: account=" + op.account 741 + " auth=" + op.authority 742 + " src=" + op.syncSource 743 + " extras=" + op.extras); 744 } 745 746 AuthorityInfo authority = getOrCreateAuthorityLocked(op.account, 747 op.authority, 748 -1 /* desired identifier */, 749 true /* write accounts to storage */); 750 if (authority == null) { 751 return null; 752 } 753 754 op = new PendingOperation(op); 755 op.authorityId = authority.ident; 756 mPendingOperations.add(op); 757 appendPendingOperationLocked(op); 758 759 SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident); 760 status.pending = true; 761 } 762 763 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING); 764 return op; 765 } 766 767 public boolean deleteFromPending(PendingOperation op) { 768 boolean res = false; 769 synchronized (mAuthorities) { 770 if (Log.isLoggable(TAG, Log.VERBOSE)) { 771 Log.v(TAG, "deleteFromPending: account=" + op.account 772 + " auth=" + op.authority 773 + " src=" + op.syncSource 774 + " extras=" + op.extras); 775 } 776 if (mPendingOperations.remove(op)) { 777 if (mPendingOperations.size() == 0 778 || mNumPendingFinished >= PENDING_FINISH_TO_WRITE) { 779 writePendingOperationsLocked(); 780 mNumPendingFinished = 0; 781 } else { 782 mNumPendingFinished++; 783 } 784 785 AuthorityInfo authority = getAuthorityLocked(op.account, op.authority, 786 "deleteFromPending"); 787 if (authority != null) { 788 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "removing - " + authority); 789 final int N = mPendingOperations.size(); 790 boolean morePending = false; 791 for (int i=0; i<N; i++) { 792 PendingOperation cur = mPendingOperations.get(i); 793 if (cur.account.equals(op.account) 794 && cur.authority.equals(op.authority)) { 795 morePending = true; 796 break; 797 } 798 } 799 800 if (!morePending) { 801 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "no more pending!"); 802 SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident); 803 status.pending = false; 804 } 805 } 806 807 res = true; 808 } 809 } 810 811 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING); 812 return res; 813 } 814 815 public int clearPending() { 816 int num; 817 synchronized (mAuthorities) { 818 if (Log.isLoggable(TAG, Log.VERBOSE)) { 819 Log.v(TAG, "clearPending"); 820 } 821 num = mPendingOperations.size(); 822 mPendingOperations.clear(); 823 final int N = mSyncStatus.size(); 824 for (int i=0; i<N; i++) { 825 mSyncStatus.valueAt(i).pending = false; 826 } 827 writePendingOperationsLocked(); 828 } 829 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING); 830 return num; 831 } 832 833 /** 834 * Return a copy of the current array of pending operations. The 835 * PendingOperation objects are the real objects stored inside, so that 836 * they can be used with deleteFromPending(). 837 */ 838 public ArrayList<PendingOperation> getPendingOperations() { 839 synchronized (mAuthorities) { 840 return new ArrayList<PendingOperation>(mPendingOperations); 841 } 842 } 843 844 /** 845 * Return the number of currently pending operations. 846 */ 847 public int getPendingOperationCount() { 848 synchronized (mAuthorities) { 849 return mPendingOperations.size(); 850 } 851 } 852 853 /** 854 * Called when the set of account has changed, given the new array of 855 * active accounts. 856 */ 857 public void doDatabaseCleanup(Account[] accounts) { 858 synchronized (mAuthorities) { 859 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.w(TAG, "Updating for new accounts..."); 860 SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>(); 861 Iterator<AccountInfo> accIt = mAccounts.values().iterator(); 862 while (accIt.hasNext()) { 863 AccountInfo acc = accIt.next(); 864 if (!ArrayUtils.contains(accounts, acc.account)) { 865 // This account no longer exists... 866 if (Log.isLoggable(TAG, Log.VERBOSE)) { 867 Log.w(TAG, "Account removed: " + acc.account); 868 } 869 for (AuthorityInfo auth : acc.authorities.values()) { 870 removing.put(auth.ident, auth); 871 } 872 accIt.remove(); 873 } 874 } 875 876 // Clean out all data structures. 877 int i = removing.size(); 878 if (i > 0) { 879 while (i > 0) { 880 i--; 881 int ident = removing.keyAt(i); 882 mAuthorities.remove(ident); 883 int j = mSyncStatus.size(); 884 while (j > 0) { 885 j--; 886 if (mSyncStatus.keyAt(j) == ident) { 887 mSyncStatus.remove(mSyncStatus.keyAt(j)); 888 } 889 } 890 j = mSyncHistory.size(); 891 while (j > 0) { 892 j--; 893 if (mSyncHistory.get(j).authorityId == ident) { 894 mSyncHistory.remove(j); 895 } 896 } 897 } 898 writeAccountInfoLocked(); 899 writeStatusLocked(); 900 writePendingOperationsLocked(); 901 writeStatisticsLocked(); 902 } 903 } 904 } 905 906 /** 907 * Called when a sync is starting. Supply a valid ActiveSyncContext with information 908 * about the sync. 909 */ 910 public SyncInfo addActiveSync(SyncManager.ActiveSyncContext activeSyncContext) { 911 final SyncInfo syncInfo; 912 synchronized (mAuthorities) { 913 if (Log.isLoggable(TAG, Log.VERBOSE)) { 914 Log.v(TAG, "setActiveSync: account=" 915 + activeSyncContext.mSyncOperation.account 916 + " auth=" + activeSyncContext.mSyncOperation.authority 917 + " src=" + activeSyncContext.mSyncOperation.syncSource 918 + " extras=" + activeSyncContext.mSyncOperation.extras); 919 } 920 AuthorityInfo authority = getOrCreateAuthorityLocked( 921 activeSyncContext.mSyncOperation.account, 922 activeSyncContext.mSyncOperation.authority, 923 -1 /* assign a new identifier if creating a new authority */, 924 true /* write to storage if this results in a change */); 925 syncInfo = new SyncInfo(authority.ident, 926 authority.account, authority.authority, 927 activeSyncContext.mStartTime); 928 mCurrentSyncs.add(syncInfo); 929 } 930 931 reportActiveChange(); 932 return syncInfo; 933 } 934 935 /** 936 * Called to indicate that a previously active sync is no longer active. 937 */ 938 public void removeActiveSync(SyncInfo syncInfo) { 939 synchronized (mAuthorities) { 940 if (Log.isLoggable(TAG, Log.VERBOSE)) { 941 Log.v(TAG, "removeActiveSync: account=" 942 + syncInfo.account + " auth=" + syncInfo.authority); 943 } 944 mCurrentSyncs.remove(syncInfo); 945 } 946 947 reportActiveChange(); 948 } 949 950 /** 951 * To allow others to send active change reports, to poke clients. 952 */ 953 public void reportActiveChange() { 954 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE); 955 } 956 957 /** 958 * Note that sync has started for the given account and authority. 959 */ 960 public long insertStartSyncEvent(Account accountName, String authorityName, 961 long now, int source) { 962 long id; 963 synchronized (mAuthorities) { 964 if (Log.isLoggable(TAG, Log.VERBOSE)) { 965 Log.v(TAG, "insertStartSyncEvent: account=" + accountName 966 + " auth=" + authorityName + " source=" + source); 967 } 968 AuthorityInfo authority = getAuthorityLocked(accountName, authorityName, 969 "insertStartSyncEvent"); 970 if (authority == null) { 971 return -1; 972 } 973 SyncHistoryItem item = new SyncHistoryItem(); 974 item.authorityId = authority.ident; 975 item.historyId = mNextHistoryId++; 976 if (mNextHistoryId < 0) mNextHistoryId = 0; 977 item.eventTime = now; 978 item.source = source; 979 item.event = EVENT_START; 980 mSyncHistory.add(0, item); 981 while (mSyncHistory.size() > MAX_HISTORY) { 982 mSyncHistory.remove(mSyncHistory.size()-1); 983 } 984 id = item.historyId; 985 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "returning historyId " + id); 986 } 987 988 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS); 989 return id; 990 } 991 992 public static boolean equals(Bundle b1, Bundle b2) { 993 if (b1.size() != b2.size()) { 994 return false; 995 } 996 if (b1.isEmpty()) { 997 return true; 998 } 999 for (String key : b1.keySet()) { 1000 if (!b2.containsKey(key)) { 1001 return false; 1002 } 1003 if (!b1.get(key).equals(b2.get(key))) { 1004 return false; 1005 } 1006 } 1007 return true; 1008 } 1009 1010 public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage, 1011 long downstreamActivity, long upstreamActivity) { 1012 synchronized (mAuthorities) { 1013 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1014 Log.v(TAG, "stopSyncEvent: historyId=" + historyId); 1015 } 1016 SyncHistoryItem item = null; 1017 int i = mSyncHistory.size(); 1018 while (i > 0) { 1019 i--; 1020 item = mSyncHistory.get(i); 1021 if (item.historyId == historyId) { 1022 break; 1023 } 1024 item = null; 1025 } 1026 1027 if (item == null) { 1028 Log.w(TAG, "stopSyncEvent: no history for id " + historyId); 1029 return; 1030 } 1031 1032 item.elapsedTime = elapsedTime; 1033 item.event = EVENT_STOP; 1034 item.mesg = resultMessage; 1035 item.downstreamActivity = downstreamActivity; 1036 item.upstreamActivity = upstreamActivity; 1037 1038 SyncStatusInfo status = getOrCreateSyncStatusLocked(item.authorityId); 1039 1040 status.numSyncs++; 1041 status.totalElapsedTime += elapsedTime; 1042 switch (item.source) { 1043 case SOURCE_LOCAL: 1044 status.numSourceLocal++; 1045 break; 1046 case SOURCE_POLL: 1047 status.numSourcePoll++; 1048 break; 1049 case SOURCE_USER: 1050 status.numSourceUser++; 1051 break; 1052 case SOURCE_SERVER: 1053 status.numSourceServer++; 1054 break; 1055 case SOURCE_PERIODIC: 1056 status.numSourcePeriodic++; 1057 break; 1058 } 1059 1060 boolean writeStatisticsNow = false; 1061 int day = getCurrentDayLocked(); 1062 if (mDayStats[0] == null) { 1063 mDayStats[0] = new DayStats(day); 1064 } else if (day != mDayStats[0].day) { 1065 System.arraycopy(mDayStats, 0, mDayStats, 1, mDayStats.length-1); 1066 mDayStats[0] = new DayStats(day); 1067 writeStatisticsNow = true; 1068 } else if (mDayStats[0] == null) { 1069 } 1070 final DayStats ds = mDayStats[0]; 1071 1072 final long lastSyncTime = (item.eventTime + elapsedTime); 1073 boolean writeStatusNow = false; 1074 if (MESG_SUCCESS.equals(resultMessage)) { 1075 // - if successful, update the successful columns 1076 if (status.lastSuccessTime == 0 || status.lastFailureTime != 0) { 1077 writeStatusNow = true; 1078 } 1079 status.lastSuccessTime = lastSyncTime; 1080 status.lastSuccessSource = item.source; 1081 status.lastFailureTime = 0; 1082 status.lastFailureSource = -1; 1083 status.lastFailureMesg = null; 1084 status.initialFailureTime = 0; 1085 ds.successCount++; 1086 ds.successTime += elapsedTime; 1087 } else if (!MESG_CANCELED.equals(resultMessage)) { 1088 if (status.lastFailureTime == 0) { 1089 writeStatusNow = true; 1090 } 1091 status.lastFailureTime = lastSyncTime; 1092 status.lastFailureSource = item.source; 1093 status.lastFailureMesg = resultMessage; 1094 if (status.initialFailureTime == 0) { 1095 status.initialFailureTime = lastSyncTime; 1096 } 1097 ds.failureCount++; 1098 ds.failureTime += elapsedTime; 1099 } 1100 1101 if (writeStatusNow) { 1102 writeStatusLocked(); 1103 } else if (!hasMessages(MSG_WRITE_STATUS)) { 1104 sendMessageDelayed(obtainMessage(MSG_WRITE_STATUS), 1105 WRITE_STATUS_DELAY); 1106 } 1107 if (writeStatisticsNow) { 1108 writeStatisticsLocked(); 1109 } else if (!hasMessages(MSG_WRITE_STATISTICS)) { 1110 sendMessageDelayed(obtainMessage(MSG_WRITE_STATISTICS), 1111 WRITE_STATISTICS_DELAY); 1112 } 1113 } 1114 1115 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS); 1116 } 1117 1118 /** 1119 * Return a list of the currently active syncs. Note that the returned items are the 1120 * real, live active sync objects, so be careful what you do with it. 1121 */ 1122 public List<SyncInfo> getCurrentSyncs() { 1123 synchronized (mAuthorities) { 1124 return new ArrayList<SyncInfo>(mCurrentSyncs); 1125 } 1126 } 1127 1128 /** 1129 * Return an array of the current sync status for all authorities. Note 1130 * that the objects inside the array are the real, live status objects, 1131 * so be careful what you do with them. 1132 */ 1133 public ArrayList<SyncStatusInfo> getSyncStatus() { 1134 synchronized (mAuthorities) { 1135 final int N = mSyncStatus.size(); 1136 ArrayList<SyncStatusInfo> ops = new ArrayList<SyncStatusInfo>(N); 1137 for (int i=0; i<N; i++) { 1138 ops.add(mSyncStatus.valueAt(i)); 1139 } 1140 return ops; 1141 } 1142 } 1143 1144 /** 1145 * Return an array of the current authorities. Note 1146 * that the objects inside the array are the real, live objects, 1147 * so be careful what you do with them. 1148 */ 1149 public ArrayList<AuthorityInfo> getAuthorities() { 1150 synchronized (mAuthorities) { 1151 final int N = mAuthorities.size(); 1152 ArrayList<AuthorityInfo> infos = new ArrayList<AuthorityInfo>(N); 1153 for (int i=0; i<N; i++) { 1154 infos.add(mAuthorities.valueAt(i)); 1155 } 1156 return infos; 1157 } 1158 } 1159 1160 /** 1161 * Returns the status that matches the authority and account. 1162 * 1163 * @param account the account we want to check 1164 * @param authority the authority whose row should be selected 1165 * @return the SyncStatusInfo for the authority 1166 */ 1167 public SyncStatusInfo getStatusByAccountAndAuthority(Account account, String authority) { 1168 if (account == null || authority == null) { 1169 throw new IllegalArgumentException(); 1170 } 1171 synchronized (mAuthorities) { 1172 final int N = mSyncStatus.size(); 1173 for (int i=0; i<N; i++) { 1174 SyncStatusInfo cur = mSyncStatus.valueAt(i); 1175 AuthorityInfo ainfo = mAuthorities.get(cur.authorityId); 1176 1177 if (ainfo != null && ainfo.authority.equals(authority) && 1178 account.equals(ainfo.account)) { 1179 return cur; 1180 } 1181 } 1182 return null; 1183 } 1184 } 1185 1186 /** 1187 * Return true if the pending status is true of any matching authorities. 1188 */ 1189 public boolean isSyncPending(Account account, String authority) { 1190 synchronized (mAuthorities) { 1191 final int N = mSyncStatus.size(); 1192 for (int i=0; i<N; i++) { 1193 SyncStatusInfo cur = mSyncStatus.valueAt(i); 1194 AuthorityInfo ainfo = mAuthorities.get(cur.authorityId); 1195 if (ainfo == null) { 1196 continue; 1197 } 1198 if (account != null && !ainfo.account.equals(account)) { 1199 continue; 1200 } 1201 if (ainfo.authority.equals(authority) && cur.pending) { 1202 return true; 1203 } 1204 } 1205 return false; 1206 } 1207 } 1208 1209 /** 1210 * Return an array of the current sync status for all authorities. Note 1211 * that the objects inside the array are the real, live status objects, 1212 * so be careful what you do with them. 1213 */ 1214 public ArrayList<SyncHistoryItem> getSyncHistory() { 1215 synchronized (mAuthorities) { 1216 final int N = mSyncHistory.size(); 1217 ArrayList<SyncHistoryItem> items = new ArrayList<SyncHistoryItem>(N); 1218 for (int i=0; i<N; i++) { 1219 items.add(mSyncHistory.get(i)); 1220 } 1221 return items; 1222 } 1223 } 1224 1225 /** 1226 * Return an array of the current per-day statistics. Note 1227 * that the objects inside the array are the real, live status objects, 1228 * so be careful what you do with them. 1229 */ 1230 public DayStats[] getDayStatistics() { 1231 synchronized (mAuthorities) { 1232 DayStats[] ds = new DayStats[mDayStats.length]; 1233 System.arraycopy(mDayStats, 0, ds, 0, ds.length); 1234 return ds; 1235 } 1236 } 1237 1238 /** 1239 * If sync is failing for any of the provider/accounts then determine the time at which it 1240 * started failing and return the earliest time over all the provider/accounts. If none are 1241 * failing then return 0. 1242 */ 1243 public long getInitialSyncFailureTime() { 1244 synchronized (mAuthorities) { 1245 if (!mMasterSyncAutomatically) { 1246 return 0; 1247 } 1248 1249 long oldest = 0; 1250 int i = mSyncStatus.size(); 1251 while (i > 0) { 1252 i--; 1253 SyncStatusInfo stats = mSyncStatus.valueAt(i); 1254 AuthorityInfo authority = mAuthorities.get(stats.authorityId); 1255 if (authority != null && authority.enabled) { 1256 if (oldest == 0 || stats.initialFailureTime < oldest) { 1257 oldest = stats.initialFailureTime; 1258 } 1259 } 1260 } 1261 1262 return oldest; 1263 } 1264 } 1265 1266 private int getCurrentDayLocked() { 1267 mCal.setTimeInMillis(System.currentTimeMillis()); 1268 final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR); 1269 if (mYear != mCal.get(Calendar.YEAR)) { 1270 mYear = mCal.get(Calendar.YEAR); 1271 mCal.clear(); 1272 mCal.set(Calendar.YEAR, mYear); 1273 mYearInDays = (int)(mCal.getTimeInMillis()/86400000); 1274 } 1275 return dayOfYear + mYearInDays; 1276 } 1277 1278 /** 1279 * Retrieve an authority, returning null if one does not exist. 1280 * 1281 * @param accountName The name of the account for the authority. 1282 * @param authorityName The name of the authority itself. 1283 * @param tag If non-null, this will be used in a log message if the 1284 * requested authority does not exist. 1285 */ 1286 private AuthorityInfo getAuthorityLocked(Account accountName, String authorityName, 1287 String tag) { 1288 AccountInfo account = mAccounts.get(accountName); 1289 if (account == null) { 1290 if (tag != null) { 1291 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1292 Log.v(TAG, tag + ": unknown account " + accountName); 1293 } 1294 } 1295 return null; 1296 } 1297 AuthorityInfo authority = account.authorities.get(authorityName); 1298 if (authority == null) { 1299 if (tag != null) { 1300 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1301 Log.v(TAG, tag + ": unknown authority " + authorityName); 1302 } 1303 } 1304 return null; 1305 } 1306 1307 return authority; 1308 } 1309 1310 private AuthorityInfo getOrCreateAuthorityLocked(Account accountName, 1311 String authorityName, int ident, boolean doWrite) { 1312 AccountInfo account = mAccounts.get(accountName); 1313 if (account == null) { 1314 account = new AccountInfo(accountName); 1315 mAccounts.put(accountName, account); 1316 } 1317 AuthorityInfo authority = account.authorities.get(authorityName); 1318 if (authority == null) { 1319 if (ident < 0) { 1320 ident = mNextAuthorityId; 1321 mNextAuthorityId++; 1322 doWrite = true; 1323 } 1324 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1325 Log.v(TAG, "created a new AuthorityInfo for " + accountName 1326 + ", provider " + authorityName); 1327 } 1328 authority = new AuthorityInfo(accountName, authorityName, ident); 1329 account.authorities.put(authorityName, authority); 1330 mAuthorities.put(ident, authority); 1331 if (doWrite) { 1332 writeAccountInfoLocked(); 1333 } 1334 } 1335 1336 return authority; 1337 } 1338 1339 private void removeAuthorityLocked(Account account, String authorityName, boolean doWrite) { 1340 AccountInfo accountInfo = mAccounts.get(account); 1341 if (accountInfo != null) { 1342 final AuthorityInfo authorityInfo = accountInfo.authorities.remove(authorityName); 1343 if (authorityInfo != null) { 1344 mAuthorities.remove(authorityInfo.ident); 1345 if (doWrite) { 1346 writeAccountInfoLocked(); 1347 } 1348 } 1349 } 1350 } 1351 1352 public SyncStatusInfo getOrCreateSyncStatus(AuthorityInfo authority) { 1353 synchronized (mAuthorities) { 1354 return getOrCreateSyncStatusLocked(authority.ident); 1355 } 1356 } 1357 1358 private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) { 1359 SyncStatusInfo status = mSyncStatus.get(authorityId); 1360 if (status == null) { 1361 status = new SyncStatusInfo(authorityId); 1362 mSyncStatus.put(authorityId, status); 1363 } 1364 return status; 1365 } 1366 1367 public void writeAllState() { 1368 synchronized (mAuthorities) { 1369 // Account info is always written so no need to do it here. 1370 1371 if (mNumPendingFinished > 0) { 1372 // Only write these if they are out of date. 1373 writePendingOperationsLocked(); 1374 } 1375 1376 // Just always write these... they are likely out of date. 1377 writeStatusLocked(); 1378 writeStatisticsLocked(); 1379 } 1380 } 1381 1382 /** 1383 * public for testing 1384 */ 1385 public void clearAndReadState() { 1386 synchronized (mAuthorities) { 1387 mAuthorities.clear(); 1388 mAccounts.clear(); 1389 mPendingOperations.clear(); 1390 mSyncStatus.clear(); 1391 mSyncHistory.clear(); 1392 1393 readAccountInfoLocked(); 1394 readStatusLocked(); 1395 readPendingOperationsLocked(); 1396 readStatisticsLocked(); 1397 readAndDeleteLegacyAccountInfoLocked(); 1398 writeAccountInfoLocked(); 1399 writeStatusLocked(); 1400 writePendingOperationsLocked(); 1401 writeStatisticsLocked(); 1402 } 1403 } 1404 1405 /** 1406 * Read all account information back in to the initial engine state. 1407 */ 1408 private void readAccountInfoLocked() { 1409 int highestAuthorityId = -1; 1410 FileInputStream fis = null; 1411 try { 1412 fis = mAccountInfoFile.openRead(); 1413 if (DEBUG_FILE) Log.v(TAG, "Reading " + mAccountInfoFile.getBaseFile()); 1414 XmlPullParser parser = Xml.newPullParser(); 1415 parser.setInput(fis, null); 1416 int eventType = parser.getEventType(); 1417 while (eventType != XmlPullParser.START_TAG) { 1418 eventType = parser.next(); 1419 } 1420 String tagName = parser.getName(); 1421 if ("accounts".equals(tagName)) { 1422 String listen = parser.getAttributeValue( 1423 null, "listen-for-tickles"); 1424 String versionString = parser.getAttributeValue(null, "version"); 1425 int version; 1426 try { 1427 version = (versionString == null) ? 0 : Integer.parseInt(versionString); 1428 } catch (NumberFormatException e) { 1429 version = 0; 1430 } 1431 String nextIdString = parser.getAttributeValue(null, "nextAuthorityId"); 1432 try { 1433 int id = (nextIdString == null) ? 0 : Integer.parseInt(nextIdString); 1434 mNextAuthorityId = Math.max(mNextAuthorityId, id); 1435 } catch (NumberFormatException e) { 1436 // don't care 1437 } 1438 mMasterSyncAutomatically = listen == null || Boolean.parseBoolean(listen); 1439 eventType = parser.next(); 1440 AuthorityInfo authority = null; 1441 Pair<Bundle, Long> periodicSync = null; 1442 do { 1443 if (eventType == XmlPullParser.START_TAG) { 1444 tagName = parser.getName(); 1445 if (parser.getDepth() == 2) { 1446 if ("authority".equals(tagName)) { 1447 authority = parseAuthority(parser, version); 1448 periodicSync = null; 1449 if (authority.ident > highestAuthorityId) { 1450 highestAuthorityId = authority.ident; 1451 } 1452 } 1453 } else if (parser.getDepth() == 3) { 1454 if ("periodicSync".equals(tagName) && authority != null) { 1455 periodicSync = parsePeriodicSync(parser, authority); 1456 } 1457 } else if (parser.getDepth() == 4 && periodicSync != null) { 1458 if ("extra".equals(tagName)) { 1459 parseExtra(parser, periodicSync); 1460 } 1461 } 1462 } 1463 eventType = parser.next(); 1464 } while (eventType != XmlPullParser.END_DOCUMENT); 1465 } 1466 } catch (XmlPullParserException e) { 1467 Log.w(TAG, "Error reading accounts", e); 1468 return; 1469 } catch (java.io.IOException e) { 1470 if (fis == null) Log.i(TAG, "No initial accounts"); 1471 else Log.w(TAG, "Error reading accounts", e); 1472 return; 1473 } finally { 1474 mNextAuthorityId = Math.max(highestAuthorityId + 1, mNextAuthorityId); 1475 if (fis != null) { 1476 try { 1477 fis.close(); 1478 } catch (java.io.IOException e1) { 1479 } 1480 } 1481 } 1482 1483 maybeMigrateSettingsForRenamedAuthorities(); 1484 } 1485 1486 /** 1487 * some authority names have changed. copy over their settings and delete the old ones 1488 * @return true if a change was made 1489 */ 1490 private boolean maybeMigrateSettingsForRenamedAuthorities() { 1491 boolean writeNeeded = false; 1492 1493 ArrayList<AuthorityInfo> authoritiesToRemove = new ArrayList<AuthorityInfo>(); 1494 final int N = mAuthorities.size(); 1495 for (int i=0; i<N; i++) { 1496 AuthorityInfo authority = mAuthorities.valueAt(i); 1497 // skip this authority if it isn't one of the renamed ones 1498 final String newAuthorityName = sAuthorityRenames.get(authority.authority); 1499 if (newAuthorityName == null) { 1500 continue; 1501 } 1502 1503 // remember this authority so we can remove it later. we can't remove it 1504 // now without messing up this loop iteration 1505 authoritiesToRemove.add(authority); 1506 1507 // this authority isn't enabled, no need to copy it to the new authority name since 1508 // the default is "disabled" 1509 if (!authority.enabled) { 1510 continue; 1511 } 1512 1513 // if we already have a record of this new authority then don't copy over the settings 1514 if (getAuthorityLocked(authority.account, newAuthorityName, "cleanup") != null) { 1515 continue; 1516 } 1517 1518 AuthorityInfo newAuthority = getOrCreateAuthorityLocked(authority.account, 1519 newAuthorityName, -1 /* ident */, false /* doWrite */); 1520 newAuthority.enabled = true; 1521 writeNeeded = true; 1522 } 1523 1524 for (AuthorityInfo authorityInfo : authoritiesToRemove) { 1525 removeAuthorityLocked(authorityInfo.account, authorityInfo.authority, 1526 false /* doWrite */); 1527 writeNeeded = true; 1528 } 1529 1530 return writeNeeded; 1531 } 1532 1533 private AuthorityInfo parseAuthority(XmlPullParser parser, int version) { 1534 AuthorityInfo authority = null; 1535 int id = -1; 1536 try { 1537 id = Integer.parseInt(parser.getAttributeValue( 1538 null, "id")); 1539 } catch (NumberFormatException e) { 1540 Log.e(TAG, "error parsing the id of the authority", e); 1541 } catch (NullPointerException e) { 1542 Log.e(TAG, "the id of the authority is null", e); 1543 } 1544 if (id >= 0) { 1545 String authorityName = parser.getAttributeValue(null, "authority"); 1546 String enabled = parser.getAttributeValue(null, "enabled"); 1547 String syncable = parser.getAttributeValue(null, "syncable"); 1548 String accountName = parser.getAttributeValue(null, "account"); 1549 String accountType = parser.getAttributeValue(null, "type"); 1550 if (accountType == null) { 1551 accountType = "com.google"; 1552 syncable = "unknown"; 1553 } 1554 authority = mAuthorities.get(id); 1555 if (DEBUG_FILE) Log.v(TAG, "Adding authority: account=" 1556 + accountName + " auth=" + authorityName 1557 + " enabled=" + enabled 1558 + " syncable=" + syncable); 1559 if (authority == null) { 1560 if (DEBUG_FILE) Log.v(TAG, "Creating entry"); 1561 authority = getOrCreateAuthorityLocked( 1562 new Account(accountName, accountType), authorityName, id, false); 1563 // If the version is 0 then we are upgrading from a file format that did not 1564 // know about periodic syncs. In that case don't clear the list since we 1565 // want the default, which is a daily periodioc sync. 1566 // Otherwise clear out this default list since we will populate it later with 1567 // the periodic sync descriptions that are read from the configuration file. 1568 if (version > 0) { 1569 authority.periodicSyncs.clear(); 1570 } 1571 } 1572 if (authority != null) { 1573 authority.enabled = enabled == null || Boolean.parseBoolean(enabled); 1574 if ("unknown".equals(syncable)) { 1575 authority.syncable = -1; 1576 } else { 1577 authority.syncable = 1578 (syncable == null || Boolean.parseBoolean(syncable)) ? 1 : 0; 1579 } 1580 } else { 1581 Log.w(TAG, "Failure adding authority: account=" 1582 + accountName + " auth=" + authorityName 1583 + " enabled=" + enabled 1584 + " syncable=" + syncable); 1585 } 1586 } 1587 1588 return authority; 1589 } 1590 1591 private Pair<Bundle, Long> parsePeriodicSync(XmlPullParser parser, AuthorityInfo authority) { 1592 Bundle extras = new Bundle(); 1593 String periodValue = parser.getAttributeValue(null, "period"); 1594 final long period; 1595 try { 1596 period = Long.parseLong(periodValue); 1597 } catch (NumberFormatException e) { 1598 Log.e(TAG, "error parsing the period of a periodic sync", e); 1599 return null; 1600 } catch (NullPointerException e) { 1601 Log.e(TAG, "the period of a periodic sync is null", e); 1602 return null; 1603 } 1604 final Pair<Bundle, Long> periodicSync = Pair.create(extras, period); 1605 authority.periodicSyncs.add(periodicSync); 1606 1607 return periodicSync; 1608 } 1609 1610 private void parseExtra(XmlPullParser parser, Pair<Bundle, Long> periodicSync) { 1611 final Bundle extras = periodicSync.first; 1612 String name = parser.getAttributeValue(null, "name"); 1613 String type = parser.getAttributeValue(null, "type"); 1614 String value1 = parser.getAttributeValue(null, "value1"); 1615 String value2 = parser.getAttributeValue(null, "value2"); 1616 1617 try { 1618 if ("long".equals(type)) { 1619 extras.putLong(name, Long.parseLong(value1)); 1620 } else if ("integer".equals(type)) { 1621 extras.putInt(name, Integer.parseInt(value1)); 1622 } else if ("double".equals(type)) { 1623 extras.putDouble(name, Double.parseDouble(value1)); 1624 } else if ("float".equals(type)) { 1625 extras.putFloat(name, Float.parseFloat(value1)); 1626 } else if ("boolean".equals(type)) { 1627 extras.putBoolean(name, Boolean.parseBoolean(value1)); 1628 } else if ("string".equals(type)) { 1629 extras.putString(name, value1); 1630 } else if ("account".equals(type)) { 1631 extras.putParcelable(name, new Account(value1, value2)); 1632 } 1633 } catch (NumberFormatException e) { 1634 Log.e(TAG, "error parsing bundle value", e); 1635 } catch (NullPointerException e) { 1636 Log.e(TAG, "error parsing bundle value", e); 1637 } 1638 } 1639 1640 /** 1641 * Write all account information to the account file. 1642 */ 1643 private void writeAccountInfoLocked() { 1644 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mAccountInfoFile.getBaseFile()); 1645 FileOutputStream fos = null; 1646 1647 try { 1648 fos = mAccountInfoFile.startWrite(); 1649 XmlSerializer out = new FastXmlSerializer(); 1650 out.setOutput(fos, "utf-8"); 1651 out.startDocument(null, true); 1652 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 1653 1654 out.startTag(null, "accounts"); 1655 out.attribute(null, "version", Integer.toString(ACCOUNTS_VERSION)); 1656 out.attribute(null, "nextAuthorityId", Integer.toString(mNextAuthorityId)); 1657 if (!mMasterSyncAutomatically) { 1658 out.attribute(null, "listen-for-tickles", "false"); 1659 } 1660 1661 final int N = mAuthorities.size(); 1662 for (int i=0; i<N; i++) { 1663 AuthorityInfo authority = mAuthorities.valueAt(i); 1664 out.startTag(null, "authority"); 1665 out.attribute(null, "id", Integer.toString(authority.ident)); 1666 out.attribute(null, "account", authority.account.name); 1667 out.attribute(null, "type", authority.account.type); 1668 out.attribute(null, "authority", authority.authority); 1669 out.attribute(null, "enabled", Boolean.toString(authority.enabled)); 1670 if (authority.syncable < 0) { 1671 out.attribute(null, "syncable", "unknown"); 1672 } else { 1673 out.attribute(null, "syncable", Boolean.toString(authority.syncable != 0)); 1674 } 1675 for (Pair<Bundle, Long> periodicSync : authority.periodicSyncs) { 1676 out.startTag(null, "periodicSync"); 1677 out.attribute(null, "period", Long.toString(periodicSync.second)); 1678 final Bundle extras = periodicSync.first; 1679 for (String key : extras.keySet()) { 1680 out.startTag(null, "extra"); 1681 out.attribute(null, "name", key); 1682 final Object value = extras.get(key); 1683 if (value instanceof Long) { 1684 out.attribute(null, "type", "long"); 1685 out.attribute(null, "value1", value.toString()); 1686 } else if (value instanceof Integer) { 1687 out.attribute(null, "type", "integer"); 1688 out.attribute(null, "value1", value.toString()); 1689 } else if (value instanceof Boolean) { 1690 out.attribute(null, "type", "boolean"); 1691 out.attribute(null, "value1", value.toString()); 1692 } else if (value instanceof Float) { 1693 out.attribute(null, "type", "float"); 1694 out.attribute(null, "value1", value.toString()); 1695 } else if (value instanceof Double) { 1696 out.attribute(null, "type", "double"); 1697 out.attribute(null, "value1", value.toString()); 1698 } else if (value instanceof String) { 1699 out.attribute(null, "type", "string"); 1700 out.attribute(null, "value1", value.toString()); 1701 } else if (value instanceof Account) { 1702 out.attribute(null, "type", "account"); 1703 out.attribute(null, "value1", ((Account)value).name); 1704 out.attribute(null, "value2", ((Account)value).type); 1705 } 1706 out.endTag(null, "extra"); 1707 } 1708 out.endTag(null, "periodicSync"); 1709 } 1710 out.endTag(null, "authority"); 1711 } 1712 1713 out.endTag(null, "accounts"); 1714 1715 out.endDocument(); 1716 1717 mAccountInfoFile.finishWrite(fos); 1718 } catch (java.io.IOException e1) { 1719 Log.w(TAG, "Error writing accounts", e1); 1720 if (fos != null) { 1721 mAccountInfoFile.failWrite(fos); 1722 } 1723 } 1724 } 1725 1726 static int getIntColumn(Cursor c, String name) { 1727 return c.getInt(c.getColumnIndex(name)); 1728 } 1729 1730 static long getLongColumn(Cursor c, String name) { 1731 return c.getLong(c.getColumnIndex(name)); 1732 } 1733 1734 /** 1735 * Load sync engine state from the old syncmanager database, and then 1736 * erase it. Note that we don't deal with pending operations, active 1737 * sync, or history. 1738 */ 1739 private void readAndDeleteLegacyAccountInfoLocked() { 1740 // Look for old database to initialize from. 1741 File file = mContext.getDatabasePath("syncmanager.db"); 1742 if (!file.exists()) { 1743 return; 1744 } 1745 String path = file.getPath(); 1746 SQLiteDatabase db = null; 1747 try { 1748 db = SQLiteDatabase.openDatabase(path, null, 1749 SQLiteDatabase.OPEN_READONLY); 1750 } catch (SQLiteException e) { 1751 } 1752 1753 if (db != null) { 1754 final boolean hasType = db.getVersion() >= 11; 1755 1756 // Copy in all of the status information, as well as accounts. 1757 if (DEBUG_FILE) Log.v(TAG, "Reading legacy sync accounts db"); 1758 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 1759 qb.setTables("stats, status"); 1760 HashMap<String,String> map = new HashMap<String,String>(); 1761 map.put("_id", "status._id as _id"); 1762 map.put("account", "stats.account as account"); 1763 if (hasType) { 1764 map.put("account_type", "stats.account_type as account_type"); 1765 } 1766 map.put("authority", "stats.authority as authority"); 1767 map.put("totalElapsedTime", "totalElapsedTime"); 1768 map.put("numSyncs", "numSyncs"); 1769 map.put("numSourceLocal", "numSourceLocal"); 1770 map.put("numSourcePoll", "numSourcePoll"); 1771 map.put("numSourceServer", "numSourceServer"); 1772 map.put("numSourceUser", "numSourceUser"); 1773 map.put("lastSuccessSource", "lastSuccessSource"); 1774 map.put("lastSuccessTime", "lastSuccessTime"); 1775 map.put("lastFailureSource", "lastFailureSource"); 1776 map.put("lastFailureTime", "lastFailureTime"); 1777 map.put("lastFailureMesg", "lastFailureMesg"); 1778 map.put("pending", "pending"); 1779 qb.setProjectionMap(map); 1780 qb.appendWhere("stats._id = status.stats_id"); 1781 Cursor c = qb.query(db, null, null, null, null, null, null); 1782 while (c.moveToNext()) { 1783 String accountName = c.getString(c.getColumnIndex("account")); 1784 String accountType = hasType 1785 ? c.getString(c.getColumnIndex("account_type")) : null; 1786 if (accountType == null) { 1787 accountType = "com.google"; 1788 } 1789 String authorityName = c.getString(c.getColumnIndex("authority")); 1790 AuthorityInfo authority = this.getOrCreateAuthorityLocked( 1791 new Account(accountName, accountType), 1792 authorityName, -1, false); 1793 if (authority != null) { 1794 int i = mSyncStatus.size(); 1795 boolean found = false; 1796 SyncStatusInfo st = null; 1797 while (i > 0) { 1798 i--; 1799 st = mSyncStatus.valueAt(i); 1800 if (st.authorityId == authority.ident) { 1801 found = true; 1802 break; 1803 } 1804 } 1805 if (!found) { 1806 st = new SyncStatusInfo(authority.ident); 1807 mSyncStatus.put(authority.ident, st); 1808 } 1809 st.totalElapsedTime = getLongColumn(c, "totalElapsedTime"); 1810 st.numSyncs = getIntColumn(c, "numSyncs"); 1811 st.numSourceLocal = getIntColumn(c, "numSourceLocal"); 1812 st.numSourcePoll = getIntColumn(c, "numSourcePoll"); 1813 st.numSourceServer = getIntColumn(c, "numSourceServer"); 1814 st.numSourceUser = getIntColumn(c, "numSourceUser"); 1815 st.numSourcePeriodic = 0; 1816 st.lastSuccessSource = getIntColumn(c, "lastSuccessSource"); 1817 st.lastSuccessTime = getLongColumn(c, "lastSuccessTime"); 1818 st.lastFailureSource = getIntColumn(c, "lastFailureSource"); 1819 st.lastFailureTime = getLongColumn(c, "lastFailureTime"); 1820 st.lastFailureMesg = c.getString(c.getColumnIndex("lastFailureMesg")); 1821 st.pending = getIntColumn(c, "pending") != 0; 1822 } 1823 } 1824 1825 c.close(); 1826 1827 // Retrieve the settings. 1828 qb = new SQLiteQueryBuilder(); 1829 qb.setTables("settings"); 1830 c = qb.query(db, null, null, null, null, null, null); 1831 while (c.moveToNext()) { 1832 String name = c.getString(c.getColumnIndex("name")); 1833 String value = c.getString(c.getColumnIndex("value")); 1834 if (name == null) continue; 1835 if (name.equals("listen_for_tickles")) { 1836 setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value)); 1837 } else if (name.startsWith("sync_provider_")) { 1838 String provider = name.substring("sync_provider_".length(), 1839 name.length()); 1840 int i = mAuthorities.size(); 1841 while (i > 0) { 1842 i--; 1843 AuthorityInfo authority = mAuthorities.valueAt(i); 1844 if (authority.authority.equals(provider)) { 1845 authority.enabled = value == null || Boolean.parseBoolean(value); 1846 authority.syncable = 1; 1847 } 1848 } 1849 } 1850 } 1851 1852 c.close(); 1853 1854 db.close(); 1855 1856 (new File(path)).delete(); 1857 } 1858 } 1859 1860 public static final int STATUS_FILE_END = 0; 1861 public static final int STATUS_FILE_ITEM = 100; 1862 1863 /** 1864 * Read all sync status back in to the initial engine state. 1865 */ 1866 private void readStatusLocked() { 1867 if (DEBUG_FILE) Log.v(TAG, "Reading " + mStatusFile.getBaseFile()); 1868 try { 1869 byte[] data = mStatusFile.readFully(); 1870 Parcel in = Parcel.obtain(); 1871 in.unmarshall(data, 0, data.length); 1872 in.setDataPosition(0); 1873 int token; 1874 while ((token=in.readInt()) != STATUS_FILE_END) { 1875 if (token == STATUS_FILE_ITEM) { 1876 SyncStatusInfo status = new SyncStatusInfo(in); 1877 if (mAuthorities.indexOfKey(status.authorityId) >= 0) { 1878 status.pending = false; 1879 if (DEBUG_FILE) Log.v(TAG, "Adding status for id " 1880 + status.authorityId); 1881 mSyncStatus.put(status.authorityId, status); 1882 } 1883 } else { 1884 // Ooops. 1885 Log.w(TAG, "Unknown status token: " + token); 1886 break; 1887 } 1888 } 1889 } catch (java.io.IOException e) { 1890 Log.i(TAG, "No initial status"); 1891 } 1892 } 1893 1894 /** 1895 * Write all sync status to the sync status file. 1896 */ 1897 private void writeStatusLocked() { 1898 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatusFile.getBaseFile()); 1899 1900 // The file is being written, so we don't need to have a scheduled 1901 // write until the next change. 1902 removeMessages(MSG_WRITE_STATUS); 1903 1904 FileOutputStream fos = null; 1905 try { 1906 fos = mStatusFile.startWrite(); 1907 Parcel out = Parcel.obtain(); 1908 final int N = mSyncStatus.size(); 1909 for (int i=0; i<N; i++) { 1910 SyncStatusInfo status = mSyncStatus.valueAt(i); 1911 out.writeInt(STATUS_FILE_ITEM); 1912 status.writeToParcel(out, 0); 1913 } 1914 out.writeInt(STATUS_FILE_END); 1915 fos.write(out.marshall()); 1916 out.recycle(); 1917 1918 mStatusFile.finishWrite(fos); 1919 } catch (java.io.IOException e1) { 1920 Log.w(TAG, "Error writing status", e1); 1921 if (fos != null) { 1922 mStatusFile.failWrite(fos); 1923 } 1924 } 1925 } 1926 1927 public static final int PENDING_OPERATION_VERSION = 2; 1928 1929 /** 1930 * Read all pending operations back in to the initial engine state. 1931 */ 1932 private void readPendingOperationsLocked() { 1933 if (DEBUG_FILE) Log.v(TAG, "Reading " + mPendingFile.getBaseFile()); 1934 try { 1935 byte[] data = mPendingFile.readFully(); 1936 Parcel in = Parcel.obtain(); 1937 in.unmarshall(data, 0, data.length); 1938 in.setDataPosition(0); 1939 final int SIZE = in.dataSize(); 1940 while (in.dataPosition() < SIZE) { 1941 int version = in.readInt(); 1942 if (version != PENDING_OPERATION_VERSION && version != 1) { 1943 Log.w(TAG, "Unknown pending operation version " 1944 + version + "; dropping all ops"); 1945 break; 1946 } 1947 int authorityId = in.readInt(); 1948 int syncSource = in.readInt(); 1949 byte[] flatExtras = in.createByteArray(); 1950 boolean expedited; 1951 if (version == PENDING_OPERATION_VERSION) { 1952 expedited = in.readInt() != 0; 1953 } else { 1954 expedited = false; 1955 } 1956 AuthorityInfo authority = mAuthorities.get(authorityId); 1957 if (authority != null) { 1958 Bundle extras; 1959 if (flatExtras != null) { 1960 extras = unflattenBundle(flatExtras); 1961 } else { 1962 // if we are unable to parse the extras for whatever reason convert this 1963 // to a regular sync by creating an empty extras 1964 extras = new Bundle(); 1965 } 1966 PendingOperation op = new PendingOperation( 1967 authority.account, syncSource, 1968 authority.authority, extras, expedited); 1969 op.authorityId = authorityId; 1970 op.flatExtras = flatExtras; 1971 if (DEBUG_FILE) Log.v(TAG, "Adding pending op: account=" + op.account 1972 + " auth=" + op.authority 1973 + " src=" + op.syncSource 1974 + " expedited=" + op.expedited 1975 + " extras=" + op.extras); 1976 mPendingOperations.add(op); 1977 } 1978 } 1979 } catch (java.io.IOException e) { 1980 Log.i(TAG, "No initial pending operations"); 1981 } 1982 } 1983 1984 private void writePendingOperationLocked(PendingOperation op, Parcel out) { 1985 out.writeInt(PENDING_OPERATION_VERSION); 1986 out.writeInt(op.authorityId); 1987 out.writeInt(op.syncSource); 1988 if (op.flatExtras == null && op.extras != null) { 1989 op.flatExtras = flattenBundle(op.extras); 1990 } 1991 out.writeByteArray(op.flatExtras); 1992 out.writeInt(op.expedited ? 1 : 0); 1993 } 1994 1995 /** 1996 * Write all currently pending ops to the pending ops file. 1997 */ 1998 private void writePendingOperationsLocked() { 1999 final int N = mPendingOperations.size(); 2000 FileOutputStream fos = null; 2001 try { 2002 if (N == 0) { 2003 if (DEBUG_FILE) Log.v(TAG, "Truncating " + mPendingFile.getBaseFile()); 2004 mPendingFile.truncate(); 2005 return; 2006 } 2007 2008 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mPendingFile.getBaseFile()); 2009 fos = mPendingFile.startWrite(); 2010 2011 Parcel out = Parcel.obtain(); 2012 for (int i=0; i<N; i++) { 2013 PendingOperation op = mPendingOperations.get(i); 2014 writePendingOperationLocked(op, out); 2015 } 2016 fos.write(out.marshall()); 2017 out.recycle(); 2018 2019 mPendingFile.finishWrite(fos); 2020 } catch (java.io.IOException e1) { 2021 Log.w(TAG, "Error writing pending operations", e1); 2022 if (fos != null) { 2023 mPendingFile.failWrite(fos); 2024 } 2025 } 2026 } 2027 2028 /** 2029 * Append the given operation to the pending ops file; if unable to, 2030 * write all pending ops. 2031 */ 2032 private void appendPendingOperationLocked(PendingOperation op) { 2033 if (DEBUG_FILE) Log.v(TAG, "Appending to " + mPendingFile.getBaseFile()); 2034 FileOutputStream fos = null; 2035 try { 2036 fos = mPendingFile.openAppend(); 2037 } catch (java.io.IOException e) { 2038 if (DEBUG_FILE) Log.v(TAG, "Failed append; writing full file"); 2039 writePendingOperationsLocked(); 2040 return; 2041 } 2042 2043 try { 2044 Parcel out = Parcel.obtain(); 2045 writePendingOperationLocked(op, out); 2046 fos.write(out.marshall()); 2047 out.recycle(); 2048 } catch (java.io.IOException e1) { 2049 Log.w(TAG, "Error writing pending operations", e1); 2050 } finally { 2051 try { 2052 fos.close(); 2053 } catch (java.io.IOException e2) { 2054 } 2055 } 2056 } 2057 2058 static private byte[] flattenBundle(Bundle bundle) { 2059 byte[] flatData = null; 2060 Parcel parcel = Parcel.obtain(); 2061 try { 2062 bundle.writeToParcel(parcel, 0); 2063 flatData = parcel.marshall(); 2064 } finally { 2065 parcel.recycle(); 2066 } 2067 return flatData; 2068 } 2069 2070 static private Bundle unflattenBundle(byte[] flatData) { 2071 Bundle bundle; 2072 Parcel parcel = Parcel.obtain(); 2073 try { 2074 parcel.unmarshall(flatData, 0, flatData.length); 2075 parcel.setDataPosition(0); 2076 bundle = parcel.readBundle(); 2077 } catch (RuntimeException e) { 2078 // A RuntimeException is thrown if we were unable to parse the parcel. 2079 // Create an empty parcel in this case. 2080 bundle = new Bundle(); 2081 } finally { 2082 parcel.recycle(); 2083 } 2084 return bundle; 2085 } 2086 2087 public static final int STATISTICS_FILE_END = 0; 2088 public static final int STATISTICS_FILE_ITEM_OLD = 100; 2089 public static final int STATISTICS_FILE_ITEM = 101; 2090 2091 /** 2092 * Read all sync statistics back in to the initial engine state. 2093 */ 2094 private void readStatisticsLocked() { 2095 try { 2096 byte[] data = mStatisticsFile.readFully(); 2097 Parcel in = Parcel.obtain(); 2098 in.unmarshall(data, 0, data.length); 2099 in.setDataPosition(0); 2100 int token; 2101 int index = 0; 2102 while ((token=in.readInt()) != STATISTICS_FILE_END) { 2103 if (token == STATISTICS_FILE_ITEM 2104 || token == STATISTICS_FILE_ITEM_OLD) { 2105 int day = in.readInt(); 2106 if (token == STATISTICS_FILE_ITEM_OLD) { 2107 day = day - 2009 + 14245; // Magic! 2108 } 2109 DayStats ds = new DayStats(day); 2110 ds.successCount = in.readInt(); 2111 ds.successTime = in.readLong(); 2112 ds.failureCount = in.readInt(); 2113 ds.failureTime = in.readLong(); 2114 if (index < mDayStats.length) { 2115 mDayStats[index] = ds; 2116 index++; 2117 } 2118 } else { 2119 // Ooops. 2120 Log.w(TAG, "Unknown stats token: " + token); 2121 break; 2122 } 2123 } 2124 } catch (java.io.IOException e) { 2125 Log.i(TAG, "No initial statistics"); 2126 } 2127 } 2128 2129 /** 2130 * Write all sync statistics to the sync status file. 2131 */ 2132 private void writeStatisticsLocked() { 2133 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatisticsFile.getBaseFile()); 2134 2135 // The file is being written, so we don't need to have a scheduled 2136 // write until the next change. 2137 removeMessages(MSG_WRITE_STATISTICS); 2138 2139 FileOutputStream fos = null; 2140 try { 2141 fos = mStatisticsFile.startWrite(); 2142 Parcel out = Parcel.obtain(); 2143 final int N = mDayStats.length; 2144 for (int i=0; i<N; i++) { 2145 DayStats ds = mDayStats[i]; 2146 if (ds == null) { 2147 break; 2148 } 2149 out.writeInt(STATISTICS_FILE_ITEM); 2150 out.writeInt(ds.day); 2151 out.writeInt(ds.successCount); 2152 out.writeLong(ds.successTime); 2153 out.writeInt(ds.failureCount); 2154 out.writeLong(ds.failureTime); 2155 } 2156 out.writeInt(STATISTICS_FILE_END); 2157 fos.write(out.marshall()); 2158 out.recycle(); 2159 2160 mStatisticsFile.finishWrite(fos); 2161 } catch (java.io.IOException e1) { 2162 Log.w(TAG, "Error writing stats", e1); 2163 if (fos != null) { 2164 mStatisticsFile.failWrite(fos); 2165 } 2166 } 2167 } 2168 } 2169