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