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