SyncStorageEngine.java revision b763ab265130b5aa59e4de561ba3836ac294fe62
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 17package android.content; 18 19import com.android.internal.os.AtomicFile; 20import com.android.internal.util.ArrayUtils; 21import com.android.internal.util.FastXmlSerializer; 22 23import org.xmlpull.v1.XmlPullParser; 24import org.xmlpull.v1.XmlPullParserException; 25import org.xmlpull.v1.XmlSerializer; 26 27import android.accounts.Account; 28import android.backup.IBackupManager; 29import android.database.Cursor; 30import android.database.sqlite.SQLiteDatabase; 31import android.database.sqlite.SQLiteException; 32import android.database.sqlite.SQLiteQueryBuilder; 33import android.os.Bundle; 34import android.os.Environment; 35import android.os.Handler; 36import android.os.Message; 37import android.os.Parcel; 38import android.os.RemoteCallbackList; 39import android.os.RemoteException; 40import android.os.ServiceManager; 41import android.util.Log; 42import android.util.SparseArray; 43import android.util.Xml; 44 45import java.io.File; 46import java.io.FileInputStream; 47import java.io.FileOutputStream; 48import java.util.ArrayList; 49import java.util.Calendar; 50import java.util.HashMap; 51import java.util.Iterator; 52import java.util.TimeZone; 53 54/** 55 * Singleton that tracks the sync data and overall sync 56 * history on the device. 57 * 58 * @hide 59 */ 60public class SyncStorageEngine extends Handler { 61 private static final String TAG = "SyncManager"; 62 private static final boolean DEBUG = false; 63 private static final boolean DEBUG_FILE = false; 64 65 // @VisibleForTesting 66 static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4; 67 68 /** Enum value for a sync start event. */ 69 public static final int EVENT_START = 0; 70 71 /** Enum value for a sync stop event. */ 72 public static final int EVENT_STOP = 1; 73 74 // TODO: i18n -- grab these out of resources. 75 /** String names for the sync event types. */ 76 public static final String[] EVENTS = { "START", "STOP" }; 77 78 /** Enum value for a server-initiated sync. */ 79 public static final int SOURCE_SERVER = 0; 80 81 /** Enum value for a local-initiated sync. */ 82 public static final int SOURCE_LOCAL = 1; 83 /** 84 * Enum value for a poll-based sync (e.g., upon connection to 85 * network) 86 */ 87 public static final int SOURCE_POLL = 2; 88 89 /** Enum value for a user-initiated sync. */ 90 public static final int SOURCE_USER = 3; 91 92 private static final Intent SYNC_CONNECTION_SETTING_CHANGED_INTENT = 93 new Intent("com.android.sync.SYNC_CONN_STATUS_CHANGED"); 94 95 // TODO: i18n -- grab these out of resources. 96 /** String names for the sync source types. */ 97 public static final String[] SOURCES = { "SERVER", 98 "LOCAL", 99 "POLL", 100 "USER" }; 101 102 // The MESG column will contain one of these or one of the Error types. 103 public static final String MESG_SUCCESS = "success"; 104 public static final String MESG_CANCELED = "canceled"; 105 106 public static final int MAX_HISTORY = 15; 107 108 private static final int MSG_WRITE_STATUS = 1; 109 private static final long WRITE_STATUS_DELAY = 1000*60*10; // 10 minutes 110 111 private static final int MSG_WRITE_STATISTICS = 2; 112 private static final long WRITE_STATISTICS_DELAY = 1000*60*30; // 1/2 hour 113 114 private static final boolean SYNC_ENABLED_DEFAULT = false; 115 116 public static class PendingOperation { 117 final Account account; 118 final int syncSource; 119 final String authority; 120 final Bundle extras; // note: read-only. 121 122 int authorityId; 123 byte[] flatExtras; 124 125 PendingOperation(Account account, int source, 126 String authority, Bundle extras) { 127 this.account = account; 128 this.syncSource = source; 129 this.authority = authority; 130 this.extras = extras != null ? new Bundle(extras) : extras; 131 this.authorityId = -1; 132 } 133 134 PendingOperation(PendingOperation other) { 135 this.account = other.account; 136 this.syncSource = other.syncSource; 137 this.authority = other.authority; 138 this.extras = other.extras; 139 this.authorityId = other.authorityId; 140 } 141 } 142 143 static class AccountInfo { 144 final Account account; 145 final HashMap<String, AuthorityInfo> authorities = 146 new HashMap<String, AuthorityInfo>(); 147 148 AccountInfo(Account account) { 149 this.account = account; 150 } 151 } 152 153 public static class AuthorityInfo { 154 final Account account; 155 final String authority; 156 final int ident; 157 boolean enabled; 158 int syncable; 159 160 AuthorityInfo(Account account, String authority, int ident) { 161 this.account = account; 162 this.authority = authority; 163 this.ident = ident; 164 enabled = SYNC_ENABLED_DEFAULT; 165 syncable = -1; // default to "unknown" 166 } 167 } 168 169 public static class SyncHistoryItem { 170 int authorityId; 171 int historyId; 172 long eventTime; 173 long elapsedTime; 174 int source; 175 int event; 176 long upstreamActivity; 177 long downstreamActivity; 178 String mesg; 179 } 180 181 public static class DayStats { 182 public final int day; 183 public int successCount; 184 public long successTime; 185 public int failureCount; 186 public long failureTime; 187 188 public DayStats(int day) { 189 this.day = day; 190 } 191 } 192 193 // Primary list of all syncable authorities. Also our global lock. 194 private final SparseArray<AuthorityInfo> mAuthorities = 195 new SparseArray<AuthorityInfo>(); 196 197 private final HashMap<Account, AccountInfo> mAccounts = 198 new HashMap<Account, AccountInfo>(); 199 200 private final ArrayList<PendingOperation> mPendingOperations = 201 new ArrayList<PendingOperation>(); 202 203 private ActiveSyncInfo mActiveSync; 204 205 private final SparseArray<SyncStatusInfo> mSyncStatus = 206 new SparseArray<SyncStatusInfo>(); 207 208 private final ArrayList<SyncHistoryItem> mSyncHistory = 209 new ArrayList<SyncHistoryItem>(); 210 211 private final RemoteCallbackList<ISyncStatusObserver> mChangeListeners 212 = new RemoteCallbackList<ISyncStatusObserver>(); 213 214 // We keep 4 weeks of stats. 215 private final DayStats[] mDayStats = new DayStats[7*4]; 216 private final Calendar mCal; 217 private int mYear; 218 private int mYearInDays; 219 220 private final Context mContext; 221 private static volatile SyncStorageEngine sSyncStorageEngine = null; 222 223 /** 224 * This file contains the core engine state: all accounts and the 225 * settings for them. It must never be lost, and should be changed 226 * infrequently, so it is stored as an XML file. 227 */ 228 private final AtomicFile mAccountInfoFile; 229 230 /** 231 * This file contains the current sync status. We would like to retain 232 * it across boots, but its loss is not the end of the world, so we store 233 * this information as binary data. 234 */ 235 private final AtomicFile mStatusFile; 236 237 /** 238 * This file contains sync statistics. This is purely debugging information 239 * so is written infrequently and can be thrown away at any time. 240 */ 241 private final AtomicFile mStatisticsFile; 242 243 /** 244 * This file contains the pending sync operations. It is a binary file, 245 * which must be updated every time an operation is added or removed, 246 * so we have special handling of it. 247 */ 248 private final AtomicFile mPendingFile; 249 private static final int PENDING_FINISH_TO_WRITE = 4; 250 private int mNumPendingFinished = 0; 251 252 private int mNextHistoryId = 0; 253 private boolean mMasterSyncAutomatically = true; 254 255 private SyncStorageEngine(Context context) { 256 mContext = context; 257 sSyncStorageEngine = this; 258 259 mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0")); 260 261 File dataDir = Environment.getDataDirectory(); 262 File systemDir = new File(dataDir, "system"); 263 File syncDir = new File(systemDir, "sync"); 264 mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml")); 265 mStatusFile = new AtomicFile(new File(syncDir, "status.bin")); 266 mPendingFile = new AtomicFile(new File(syncDir, "pending.bin")); 267 mStatisticsFile = new AtomicFile(new File(syncDir, "stats.bin")); 268 269 readAccountInfoLocked(); 270 readStatusLocked(); 271 readPendingOperationsLocked(); 272 readStatisticsLocked(); 273 readLegacyAccountInfoLocked(); 274 } 275 276 public static SyncStorageEngine newTestInstance(Context context) { 277 return new SyncStorageEngine(context); 278 } 279 280 public static void init(Context context) { 281 if (sSyncStorageEngine != null) { 282 throw new IllegalStateException("already initialized"); 283 } 284 sSyncStorageEngine = new SyncStorageEngine(context); 285 } 286 287 public static SyncStorageEngine getSingleton() { 288 if (sSyncStorageEngine == null) { 289 throw new IllegalStateException("not initialized"); 290 } 291 return sSyncStorageEngine; 292 } 293 294 @Override public void handleMessage(Message msg) { 295 if (msg.what == MSG_WRITE_STATUS) { 296 synchronized (mAccounts) { 297 writeStatusLocked(); 298 } 299 } else if (msg.what == MSG_WRITE_STATISTICS) { 300 synchronized (mAccounts) { 301 writeStatisticsLocked(); 302 } 303 } 304 } 305 306 public void addStatusChangeListener(int mask, ISyncStatusObserver callback) { 307 synchronized (mAuthorities) { 308 mChangeListeners.register(callback, mask); 309 } 310 } 311 312 public void removeStatusChangeListener(ISyncStatusObserver callback) { 313 synchronized (mAuthorities) { 314 mChangeListeners.unregister(callback); 315 } 316 } 317 318 private void reportChange(int which) { 319 ArrayList<ISyncStatusObserver> reports = null; 320 synchronized (mAuthorities) { 321 int i = mChangeListeners.beginBroadcast(); 322 while (i > 0) { 323 i--; 324 Integer mask = (Integer)mChangeListeners.getBroadcastCookie(i); 325 if ((which & mask.intValue()) == 0) { 326 continue; 327 } 328 if (reports == null) { 329 reports = new ArrayList<ISyncStatusObserver>(i); 330 } 331 reports.add(mChangeListeners.getBroadcastItem(i)); 332 } 333 mChangeListeners.finishBroadcast(); 334 } 335 336 if (DEBUG) Log.v(TAG, "reportChange " + which + " to: " + reports); 337 338 if (reports != null) { 339 int i = reports.size(); 340 while (i > 0) { 341 i--; 342 try { 343 reports.get(i).onStatusChanged(which); 344 } catch (RemoteException e) { 345 // The remote callback list will take care of this for us. 346 } 347 } 348 } 349 // Inform the backup manager about a data change 350 IBackupManager ibm = IBackupManager.Stub.asInterface( 351 ServiceManager.getService(Context.BACKUP_SERVICE)); 352 if (ibm != null) { 353 try { 354 ibm.dataChanged("com.android.providers.settings"); 355 } catch (RemoteException e) { 356 // Try again later 357 } 358 } 359 } 360 361 public boolean getSyncAutomatically(Account account, String providerName) { 362 synchronized (mAuthorities) { 363 if (account != null) { 364 AuthorityInfo authority = getAuthorityLocked(account, providerName, 365 "getSyncAutomatically"); 366 return authority != null && authority.enabled; 367 } 368 369 int i = mAuthorities.size(); 370 while (i > 0) { 371 i--; 372 AuthorityInfo authority = mAuthorities.get(i); 373 if (authority.authority.equals(providerName) 374 && authority.enabled) { 375 return true; 376 } 377 } 378 return false; 379 } 380 } 381 382 public void setSyncAutomatically(Account account, String providerName, boolean sync) { 383 boolean wasEnabled; 384 synchronized (mAuthorities) { 385 AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false); 386 wasEnabled = authority.enabled; 387 authority.enabled = sync; 388 writeAccountInfoLocked(); 389 } 390 391 if (!wasEnabled && sync) { 392 mContext.getContentResolver().requestSync(account, providerName, new Bundle()); 393 } 394 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); 395 } 396 397 public int getIsSyncable(Account account, String providerName) { 398 synchronized (mAuthorities) { 399 if (account != null) { 400 AuthorityInfo authority = getAuthorityLocked(account, providerName, 401 "getIsSyncable"); 402 if (authority == null) { 403 return -1; 404 } 405 return authority.syncable; 406 } 407 408 int i = mAuthorities.size(); 409 while (i > 0) { 410 i--; 411 AuthorityInfo authority = mAuthorities.get(i); 412 if (authority.authority.equals(providerName)) { 413 return authority.syncable; 414 } 415 } 416 return -1; 417 } 418 } 419 420 public void setIsSyncable(Account account, String providerName, int syncable) { 421 int oldState; 422 if (syncable > 1) { 423 syncable = 1; 424 } else if (syncable < -1) { 425 syncable = -1; 426 } 427 Log.d(TAG, "setIsSyncable: " + account + ", provider " + providerName + " -> " + syncable); 428 synchronized (mAuthorities) { 429 AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false); 430 oldState = authority.syncable; 431 authority.syncable = syncable; 432 writeAccountInfoLocked(); 433 } 434 435 if (oldState <= 0 && syncable > 0) { 436 mContext.getContentResolver().requestSync(account, providerName, new Bundle()); 437 } 438 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); 439 } 440 441 public void setMasterSyncAutomatically(boolean flag) { 442 boolean old; 443 synchronized (mAuthorities) { 444 old = mMasterSyncAutomatically; 445 mMasterSyncAutomatically = flag; 446 writeAccountInfoLocked(); 447 } 448 if (!old && flag) { 449 mContext.getContentResolver().requestSync(null, null, new Bundle()); 450 } 451 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); 452 mContext.sendBroadcast(SYNC_CONNECTION_SETTING_CHANGED_INTENT); 453 } 454 455 public boolean getMasterSyncAutomatically() { 456 synchronized (mAuthorities) { 457 return mMasterSyncAutomatically; 458 } 459 } 460 461 public AuthorityInfo getAuthority(Account account, String authority) { 462 synchronized (mAuthorities) { 463 return getAuthorityLocked(account, authority, null); 464 } 465 } 466 467 public AuthorityInfo getAuthority(int authorityId) { 468 synchronized (mAuthorities) { 469 return mAuthorities.get(authorityId); 470 } 471 } 472 473 /** 474 * Returns true if there is currently a sync operation for the given 475 * account or authority in the pending list, or actively being processed. 476 */ 477 public boolean isSyncActive(Account account, String authority) { 478 synchronized (mAuthorities) { 479 int i = mPendingOperations.size(); 480 while (i > 0) { 481 i--; 482 // TODO(fredq): this probably shouldn't be considering 483 // pending operations. 484 PendingOperation op = mPendingOperations.get(i); 485 if (op.account.equals(account) && op.authority.equals(authority)) { 486 return true; 487 } 488 } 489 490 if (mActiveSync != null) { 491 AuthorityInfo ainfo = getAuthority(mActiveSync.authorityId); 492 if (ainfo != null && ainfo.account.equals(account) 493 && ainfo.authority.equals(authority)) { 494 return true; 495 } 496 } 497 } 498 499 return false; 500 } 501 502 public PendingOperation insertIntoPending(PendingOperation op) { 503 synchronized (mAuthorities) { 504 if (DEBUG) Log.v(TAG, "insertIntoPending: account=" + op.account 505 + " auth=" + op.authority 506 + " src=" + op.syncSource 507 + " extras=" + op.extras); 508 509 AuthorityInfo authority = getOrCreateAuthorityLocked(op.account, 510 op.authority, 511 -1 /* desired identifier */, 512 true /* write accounts to storage */); 513 if (authority == null) { 514 return null; 515 } 516 517 op = new PendingOperation(op); 518 op.authorityId = authority.ident; 519 mPendingOperations.add(op); 520 appendPendingOperationLocked(op); 521 522 SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident); 523 status.pending = true; 524 } 525 526 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING); 527 return op; 528 } 529 530 public boolean deleteFromPending(PendingOperation op) { 531 boolean res = false; 532 synchronized (mAuthorities) { 533 if (DEBUG) Log.v(TAG, "deleteFromPending: account=" + op.account 534 + " auth=" + op.authority 535 + " src=" + op.syncSource 536 + " extras=" + op.extras); 537 if (mPendingOperations.remove(op)) { 538 if (mPendingOperations.size() == 0 539 || mNumPendingFinished >= PENDING_FINISH_TO_WRITE) { 540 writePendingOperationsLocked(); 541 mNumPendingFinished = 0; 542 } else { 543 mNumPendingFinished++; 544 } 545 546 AuthorityInfo authority = getAuthorityLocked(op.account, op.authority, 547 "deleteFromPending"); 548 if (authority != null) { 549 if (DEBUG) Log.v(TAG, "removing - " + authority); 550 final int N = mPendingOperations.size(); 551 boolean morePending = false; 552 for (int i=0; i<N; i++) { 553 PendingOperation cur = mPendingOperations.get(i); 554 if (cur.account.equals(op.account) 555 && cur.authority.equals(op.authority)) { 556 morePending = true; 557 break; 558 } 559 } 560 561 if (!morePending) { 562 if (DEBUG) Log.v(TAG, "no more pending!"); 563 SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident); 564 status.pending = false; 565 } 566 } 567 568 res = true; 569 } 570 } 571 572 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING); 573 return res; 574 } 575 576 public int clearPending() { 577 int num; 578 synchronized (mAuthorities) { 579 if (DEBUG) Log.v(TAG, "clearPending"); 580 num = mPendingOperations.size(); 581 mPendingOperations.clear(); 582 final int N = mSyncStatus.size(); 583 for (int i=0; i<N; i++) { 584 mSyncStatus.get(i).pending = false; 585 } 586 writePendingOperationsLocked(); 587 } 588 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING); 589 return num; 590 } 591 592 /** 593 * Return a copy of the current array of pending operations. The 594 * PendingOperation objects are the real objects stored inside, so that 595 * they can be used with deleteFromPending(). 596 */ 597 public ArrayList<PendingOperation> getPendingOperations() { 598 synchronized (mAuthorities) { 599 return new ArrayList<PendingOperation>(mPendingOperations); 600 } 601 } 602 603 /** 604 * Return the number of currently pending operations. 605 */ 606 public int getPendingOperationCount() { 607 synchronized (mAuthorities) { 608 return mPendingOperations.size(); 609 } 610 } 611 612 /** 613 * Called when the set of account has changed, given the new array of 614 * active accounts. 615 */ 616 public void doDatabaseCleanup(Account[] accounts) { 617 synchronized (mAuthorities) { 618 if (DEBUG) Log.w(TAG, "Updating for new accounts..."); 619 SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>(); 620 Iterator<AccountInfo> accIt = mAccounts.values().iterator(); 621 while (accIt.hasNext()) { 622 AccountInfo acc = accIt.next(); 623 if (!ArrayUtils.contains(accounts, acc.account)) { 624 // This account no longer exists... 625 if (DEBUG) Log.w(TAG, "Account removed: " + acc.account); 626 for (AuthorityInfo auth : acc.authorities.values()) { 627 removing.put(auth.ident, auth); 628 } 629 accIt.remove(); 630 } 631 } 632 633 // Clean out all data structures. 634 int i = removing.size(); 635 if (i > 0) { 636 while (i > 0) { 637 i--; 638 int ident = removing.keyAt(i); 639 mAuthorities.remove(ident); 640 int j = mSyncStatus.size(); 641 while (j > 0) { 642 j--; 643 if (mSyncStatus.keyAt(j) == ident) { 644 mSyncStatus.remove(mSyncStatus.keyAt(j)); 645 } 646 } 647 j = mSyncHistory.size(); 648 while (j > 0) { 649 j--; 650 if (mSyncHistory.get(j).authorityId == ident) { 651 mSyncHistory.remove(j); 652 } 653 } 654 } 655 writeAccountInfoLocked(); 656 writeStatusLocked(); 657 writePendingOperationsLocked(); 658 writeStatisticsLocked(); 659 } 660 } 661 } 662 663 /** 664 * Called when the currently active sync is changing (there can only be 665 * one at a time). Either supply a valid ActiveSyncContext with information 666 * about the sync, or null to stop the currently active sync. 667 */ 668 public void setActiveSync(SyncManager.ActiveSyncContext activeSyncContext) { 669 synchronized (mAuthorities) { 670 if (activeSyncContext != null) { 671 if (DEBUG) Log.v(TAG, "setActiveSync: account=" 672 + activeSyncContext.mSyncOperation.account 673 + " auth=" + activeSyncContext.mSyncOperation.authority 674 + " src=" + activeSyncContext.mSyncOperation.syncSource 675 + " extras=" + activeSyncContext.mSyncOperation.extras); 676 if (mActiveSync != null) { 677 Log.w(TAG, "setActiveSync called with existing active sync!"); 678 } 679 AuthorityInfo authority = getAuthorityLocked( 680 activeSyncContext.mSyncOperation.account, 681 activeSyncContext.mSyncOperation.authority, 682 "setActiveSync"); 683 if (authority == null) { 684 return; 685 } 686 mActiveSync = new ActiveSyncInfo(authority.ident, 687 authority.account, authority.authority, 688 activeSyncContext.mStartTime); 689 } else { 690 if (DEBUG) Log.v(TAG, "setActiveSync: null"); 691 mActiveSync = null; 692 } 693 } 694 695 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE); 696 } 697 698 /** 699 * To allow others to send active change reports, to poke clients. 700 */ 701 public void reportActiveChange() { 702 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE); 703 } 704 705 /** 706 * Note that sync has started for the given account and authority. 707 */ 708 public long insertStartSyncEvent(Account accountName, String authorityName, 709 long now, int source) { 710 long id; 711 synchronized (mAuthorities) { 712 if (DEBUG) Log.v(TAG, "insertStartSyncEvent: account=" + accountName 713 + " auth=" + authorityName + " source=" + source); 714 AuthorityInfo authority = getAuthorityLocked(accountName, authorityName, 715 "insertStartSyncEvent"); 716 if (authority == null) { 717 return -1; 718 } 719 SyncHistoryItem item = new SyncHistoryItem(); 720 item.authorityId = authority.ident; 721 item.historyId = mNextHistoryId++; 722 if (mNextHistoryId < 0) mNextHistoryId = 0; 723 item.eventTime = now; 724 item.source = source; 725 item.event = EVENT_START; 726 mSyncHistory.add(0, item); 727 while (mSyncHistory.size() > MAX_HISTORY) { 728 mSyncHistory.remove(mSyncHistory.size()-1); 729 } 730 id = item.historyId; 731 if (DEBUG) Log.v(TAG, "returning historyId " + id); 732 } 733 734 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS); 735 return id; 736 } 737 738 public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage, 739 long downstreamActivity, long upstreamActivity) { 740 synchronized (mAuthorities) { 741 if (DEBUG) Log.v(TAG, "stopSyncEvent: historyId=" + historyId); 742 SyncHistoryItem item = null; 743 int i = mSyncHistory.size(); 744 while (i > 0) { 745 i--; 746 item = mSyncHistory.get(i); 747 if (item.historyId == historyId) { 748 break; 749 } 750 item = null; 751 } 752 753 if (item == null) { 754 Log.w(TAG, "stopSyncEvent: no history for id " + historyId); 755 return; 756 } 757 758 item.elapsedTime = elapsedTime; 759 item.event = EVENT_STOP; 760 item.mesg = resultMessage; 761 item.downstreamActivity = downstreamActivity; 762 item.upstreamActivity = upstreamActivity; 763 764 SyncStatusInfo status = getOrCreateSyncStatusLocked(item.authorityId); 765 766 status.numSyncs++; 767 status.totalElapsedTime += elapsedTime; 768 switch (item.source) { 769 case SOURCE_LOCAL: 770 status.numSourceLocal++; 771 break; 772 case SOURCE_POLL: 773 status.numSourcePoll++; 774 break; 775 case SOURCE_USER: 776 status.numSourceUser++; 777 break; 778 case SOURCE_SERVER: 779 status.numSourceServer++; 780 break; 781 } 782 783 boolean writeStatisticsNow = false; 784 int day = getCurrentDayLocked(); 785 if (mDayStats[0] == null) { 786 mDayStats[0] = new DayStats(day); 787 } else if (day != mDayStats[0].day) { 788 System.arraycopy(mDayStats, 0, mDayStats, 1, mDayStats.length-1); 789 mDayStats[0] = new DayStats(day); 790 writeStatisticsNow = true; 791 } else if (mDayStats[0] == null) { 792 } 793 final DayStats ds = mDayStats[0]; 794 795 final long lastSyncTime = (item.eventTime + elapsedTime); 796 boolean writeStatusNow = false; 797 if (MESG_SUCCESS.equals(resultMessage)) { 798 // - if successful, update the successful columns 799 if (status.lastSuccessTime == 0 || status.lastFailureTime != 0) { 800 writeStatusNow = true; 801 } 802 status.lastSuccessTime = lastSyncTime; 803 status.lastSuccessSource = item.source; 804 status.lastFailureTime = 0; 805 status.lastFailureSource = -1; 806 status.lastFailureMesg = null; 807 status.initialFailureTime = 0; 808 ds.successCount++; 809 ds.successTime += elapsedTime; 810 } else if (!MESG_CANCELED.equals(resultMessage)) { 811 if (status.lastFailureTime == 0) { 812 writeStatusNow = true; 813 } 814 status.lastFailureTime = lastSyncTime; 815 status.lastFailureSource = item.source; 816 status.lastFailureMesg = resultMessage; 817 if (status.initialFailureTime == 0) { 818 status.initialFailureTime = lastSyncTime; 819 } 820 ds.failureCount++; 821 ds.failureTime += elapsedTime; 822 } 823 824 if (writeStatusNow) { 825 writeStatusLocked(); 826 } else if (!hasMessages(MSG_WRITE_STATUS)) { 827 sendMessageDelayed(obtainMessage(MSG_WRITE_STATUS), 828 WRITE_STATUS_DELAY); 829 } 830 if (writeStatisticsNow) { 831 writeStatisticsLocked(); 832 } else if (!hasMessages(MSG_WRITE_STATISTICS)) { 833 sendMessageDelayed(obtainMessage(MSG_WRITE_STATISTICS), 834 WRITE_STATISTICS_DELAY); 835 } 836 } 837 838 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS); 839 } 840 841 /** 842 * Return the currently active sync information, or null if there is no 843 * active sync. Note that the returned object is the real, live active 844 * sync object, so be careful what you do with it. 845 */ 846 public ActiveSyncInfo getActiveSync() { 847 synchronized (mAuthorities) { 848 return mActiveSync; 849 } 850 } 851 852 /** 853 * Return an array of the current sync status for all authorities. Note 854 * that the objects inside the array are the real, live status objects, 855 * so be careful what you do with them. 856 */ 857 public ArrayList<SyncStatusInfo> getSyncStatus() { 858 synchronized (mAuthorities) { 859 final int N = mSyncStatus.size(); 860 ArrayList<SyncStatusInfo> ops = new ArrayList<SyncStatusInfo>(N); 861 for (int i=0; i<N; i++) { 862 ops.add(mSyncStatus.valueAt(i)); 863 } 864 return ops; 865 } 866 } 867 868 /** 869 * Returns the status that matches the authority. If there are multiples accounts for 870 * the authority, the one with the latest "lastSuccessTime" status is returned. 871 * @param authority the authority whose row should be selected 872 * @return the SyncStatusInfo for the authority, or null if none exists 873 */ 874 public SyncStatusInfo getStatusByAuthority(String authority) { 875 synchronized (mAuthorities) { 876 SyncStatusInfo best = null; 877 final int N = mSyncStatus.size(); 878 for (int i=0; i<N; i++) { 879 SyncStatusInfo cur = mSyncStatus.get(i); 880 AuthorityInfo ainfo = mAuthorities.get(cur.authorityId); 881 if (ainfo != null && ainfo.authority.equals(authority)) { 882 if (best == null) { 883 best = cur; 884 } else if (best.lastSuccessTime > cur.lastSuccessTime) { 885 best = cur; 886 } 887 } 888 } 889 return best; 890 } 891 } 892 893 /** 894 * Return true if the pending status is true of any matching authorities. 895 */ 896 public boolean isSyncPending(Account account, String authority) { 897 synchronized (mAuthorities) { 898 final int N = mSyncStatus.size(); 899 for (int i=0; i<N; i++) { 900 SyncStatusInfo cur = mSyncStatus.get(i); 901 AuthorityInfo ainfo = mAuthorities.get(cur.authorityId); 902 if (ainfo == null) { 903 continue; 904 } 905 if (account != null && !ainfo.account.equals(account)) { 906 continue; 907 } 908 if (ainfo.authority.equals(authority) && cur.pending) { 909 return true; 910 } 911 } 912 return false; 913 } 914 } 915 916 /** 917 * Return an array of the current sync status for all authorities. Note 918 * that the objects inside the array are the real, live status objects, 919 * so be careful what you do with them. 920 */ 921 public ArrayList<SyncHistoryItem> getSyncHistory() { 922 synchronized (mAuthorities) { 923 final int N = mSyncHistory.size(); 924 ArrayList<SyncHistoryItem> items = new ArrayList<SyncHistoryItem>(N); 925 for (int i=0; i<N; i++) { 926 items.add(mSyncHistory.get(i)); 927 } 928 return items; 929 } 930 } 931 932 /** 933 * Return an array of the current per-day statistics. Note 934 * that the objects inside the array are the real, live status objects, 935 * so be careful what you do with them. 936 */ 937 public DayStats[] getDayStatistics() { 938 synchronized (mAuthorities) { 939 DayStats[] ds = new DayStats[mDayStats.length]; 940 System.arraycopy(mDayStats, 0, ds, 0, ds.length); 941 return ds; 942 } 943 } 944 945 /** 946 * If sync is failing for any of the provider/accounts then determine the time at which it 947 * started failing and return the earliest time over all the provider/accounts. If none are 948 * failing then return 0. 949 */ 950 public long getInitialSyncFailureTime() { 951 synchronized (mAuthorities) { 952 if (!mMasterSyncAutomatically) { 953 return 0; 954 } 955 956 long oldest = 0; 957 int i = mSyncStatus.size(); 958 while (i > 0) { 959 i--; 960 SyncStatusInfo stats = mSyncStatus.valueAt(i); 961 AuthorityInfo authority = mAuthorities.get(stats.authorityId); 962 if (authority != null && authority.enabled) { 963 if (oldest == 0 || stats.initialFailureTime < oldest) { 964 oldest = stats.initialFailureTime; 965 } 966 } 967 } 968 969 return oldest; 970 } 971 } 972 973 private int getCurrentDayLocked() { 974 mCal.setTimeInMillis(System.currentTimeMillis()); 975 final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR); 976 if (mYear != mCal.get(Calendar.YEAR)) { 977 mYear = mCal.get(Calendar.YEAR); 978 mCal.clear(); 979 mCal.set(Calendar.YEAR, mYear); 980 mYearInDays = (int)(mCal.getTimeInMillis()/86400000); 981 } 982 return dayOfYear + mYearInDays; 983 } 984 985 /** 986 * Retrieve an authority, returning null if one does not exist. 987 * 988 * @param accountName The name of the account for the authority. 989 * @param authorityName The name of the authority itself. 990 * @param tag If non-null, this will be used in a log message if the 991 * requested authority does not exist. 992 */ 993 private AuthorityInfo getAuthorityLocked(Account accountName, String authorityName, 994 String tag) { 995 AccountInfo account = mAccounts.get(accountName); 996 if (account == null) { 997 if (tag != null) { 998 if (Log.isLoggable(TAG, Log.VERBOSE)) { 999 Log.v(TAG, tag + ": unknown account " + accountName); 1000 } 1001 } 1002 return null; 1003 } 1004 AuthorityInfo authority = account.authorities.get(authorityName); 1005 if (authority == null) { 1006 if (tag != null) { 1007 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1008 Log.v(TAG, tag + ": unknown authority " + authorityName); 1009 } 1010 } 1011 return null; 1012 } 1013 1014 return authority; 1015 } 1016 1017 private AuthorityInfo getOrCreateAuthorityLocked(Account accountName, 1018 String authorityName, int ident, boolean doWrite) { 1019 AccountInfo account = mAccounts.get(accountName); 1020 if (account == null) { 1021 account = new AccountInfo(accountName); 1022 mAccounts.put(accountName, account); 1023 } 1024 AuthorityInfo authority = account.authorities.get(authorityName); 1025 if (authority == null) { 1026 if (ident < 0) { 1027 // Look for a new identifier for this authority. 1028 final int N = mAuthorities.size(); 1029 ident = 0; 1030 for (int i=0; i<N; i++) { 1031 if (mAuthorities.valueAt(i).ident > ident) { 1032 break; 1033 } 1034 ident++; 1035 } 1036 } 1037 Log.d(TAG, "created a new AuthorityInfo for " + accountName 1038 + ", provider " + authorityName); 1039 authority = new AuthorityInfo(accountName, authorityName, ident); 1040 account.authorities.put(authorityName, authority); 1041 mAuthorities.put(ident, authority); 1042 if (doWrite) { 1043 writeAccountInfoLocked(); 1044 } 1045 } 1046 1047 return authority; 1048 } 1049 1050 private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) { 1051 SyncStatusInfo status = mSyncStatus.get(authorityId); 1052 if (status == null) { 1053 status = new SyncStatusInfo(authorityId); 1054 mSyncStatus.put(authorityId, status); 1055 } 1056 return status; 1057 } 1058 1059 public void writeAllState() { 1060 synchronized (mAuthorities) { 1061 // Account info is always written so no need to do it here. 1062 1063 if (mNumPendingFinished > 0) { 1064 // Only write these if they are out of date. 1065 writePendingOperationsLocked(); 1066 } 1067 1068 // Just always write these... they are likely out of date. 1069 writeStatusLocked(); 1070 writeStatisticsLocked(); 1071 } 1072 } 1073 1074 /** 1075 * Read all account information back in to the initial engine state. 1076 */ 1077 private void readAccountInfoLocked() { 1078 FileInputStream fis = null; 1079 try { 1080 fis = mAccountInfoFile.openRead(); 1081 if (DEBUG_FILE) Log.v(TAG, "Reading " + mAccountInfoFile.getBaseFile()); 1082 XmlPullParser parser = Xml.newPullParser(); 1083 parser.setInput(fis, null); 1084 int eventType = parser.getEventType(); 1085 while (eventType != XmlPullParser.START_TAG) { 1086 eventType = parser.next(); 1087 } 1088 String tagName = parser.getName(); 1089 if ("accounts".equals(tagName)) { 1090 String listen = parser.getAttributeValue( 1091 null, "listen-for-tickles"); 1092 mMasterSyncAutomatically = listen == null 1093 || Boolean.parseBoolean(listen); 1094 eventType = parser.next(); 1095 do { 1096 if (eventType == XmlPullParser.START_TAG 1097 && parser.getDepth() == 2) { 1098 tagName = parser.getName(); 1099 if ("authority".equals(tagName)) { 1100 int id = -1; 1101 try { 1102 id = Integer.parseInt(parser.getAttributeValue( 1103 null, "id")); 1104 } catch (NumberFormatException e) { 1105 } catch (NullPointerException e) { 1106 } 1107 if (id >= 0) { 1108 String accountName = parser.getAttributeValue( 1109 null, "account"); 1110 String accountType = parser.getAttributeValue( 1111 null, "type"); 1112 if (accountType == null) { 1113 accountType = "com.google.GAIA"; 1114 } 1115 String authorityName = parser.getAttributeValue( 1116 null, "authority"); 1117 String enabled = parser.getAttributeValue( 1118 null, "enabled"); 1119 String syncable = parser.getAttributeValue(null, "syncable"); 1120 AuthorityInfo authority = mAuthorities.get(id); 1121 if (DEBUG_FILE) Log.v(TAG, "Adding authority: account=" 1122 + accountName + " auth=" + authorityName 1123 + " enabled=" + enabled 1124 + " syncable=" + syncable); 1125 if (authority == null) { 1126 if (DEBUG_FILE) Log.v(TAG, "Creating entry"); 1127 authority = getOrCreateAuthorityLocked( 1128 new Account(accountName, accountType), 1129 authorityName, id, false); 1130 } 1131 if (authority != null) { 1132 authority.enabled = enabled == null 1133 || Boolean.parseBoolean(enabled); 1134 if ("unknown".equals(syncable)) { 1135 authority.syncable = -1; 1136 } else { 1137 authority.syncable = 1138 (syncable == null || Boolean.parseBoolean(enabled)) 1139 ? 1 1140 : 0; 1141 } 1142 } else { 1143 Log.w(TAG, "Failure adding authority: account=" 1144 + accountName + " auth=" + authorityName 1145 + " enabled=" + enabled 1146 + " syncable=" + syncable); 1147 } 1148 } 1149 } 1150 } 1151 eventType = parser.next(); 1152 } while (eventType != XmlPullParser.END_DOCUMENT); 1153 } 1154 } catch (XmlPullParserException e) { 1155 Log.w(TAG, "Error reading accounts", e); 1156 } catch (java.io.IOException e) { 1157 if (fis == null) Log.i(TAG, "No initial accounts"); 1158 else Log.w(TAG, "Error reading accounts", e); 1159 } finally { 1160 if (fis != null) { 1161 try { 1162 fis.close(); 1163 } catch (java.io.IOException e1) { 1164 } 1165 } 1166 } 1167 } 1168 1169 /** 1170 * Write all account information to the account file. 1171 */ 1172 private void writeAccountInfoLocked() { 1173 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mAccountInfoFile.getBaseFile()); 1174 FileOutputStream fos = null; 1175 1176 try { 1177 fos = mAccountInfoFile.startWrite(); 1178 XmlSerializer out = new FastXmlSerializer(); 1179 out.setOutput(fos, "utf-8"); 1180 out.startDocument(null, true); 1181 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 1182 1183 out.startTag(null, "accounts"); 1184 if (!mMasterSyncAutomatically) { 1185 out.attribute(null, "listen-for-tickles", "false"); 1186 } 1187 1188 final int N = mAuthorities.size(); 1189 for (int i=0; i<N; i++) { 1190 AuthorityInfo authority = mAuthorities.get(i); 1191 out.startTag(null, "authority"); 1192 out.attribute(null, "id", Integer.toString(authority.ident)); 1193 out.attribute(null, "account", authority.account.name); 1194 out.attribute(null, "type", authority.account.type); 1195 out.attribute(null, "authority", authority.authority); 1196 if (!authority.enabled) { 1197 out.attribute(null, "enabled", "false"); 1198 } 1199 if (authority.syncable < 0) { 1200 out.attribute(null, "syncable", "unknown"); 1201 } else if (authority.syncable == 0) { 1202 out.attribute(null, "syncable", "false"); 1203 } 1204 out.endTag(null, "authority"); 1205 } 1206 1207 out.endTag(null, "accounts"); 1208 1209 out.endDocument(); 1210 1211 mAccountInfoFile.finishWrite(fos); 1212 } catch (java.io.IOException e1) { 1213 Log.w(TAG, "Error writing accounts", e1); 1214 if (fos != null) { 1215 mAccountInfoFile.failWrite(fos); 1216 } 1217 } 1218 } 1219 1220 static int getIntColumn(Cursor c, String name) { 1221 return c.getInt(c.getColumnIndex(name)); 1222 } 1223 1224 static long getLongColumn(Cursor c, String name) { 1225 return c.getLong(c.getColumnIndex(name)); 1226 } 1227 1228 /** 1229 * Load sync engine state from the old syncmanager database, and then 1230 * erase it. Note that we don't deal with pending operations, active 1231 * sync, or history. 1232 */ 1233 private void readLegacyAccountInfoLocked() { 1234 // Look for old database to initialize from. 1235 File file = mContext.getDatabasePath("syncmanager.db"); 1236 if (!file.exists()) { 1237 return; 1238 } 1239 String path = file.getPath(); 1240 SQLiteDatabase db = null; 1241 try { 1242 db = SQLiteDatabase.openDatabase(path, null, 1243 SQLiteDatabase.OPEN_READONLY); 1244 } catch (SQLiteException e) { 1245 } 1246 1247 if (db != null) { 1248 final boolean hasType = db.getVersion() >= 11; 1249 1250 // Copy in all of the status information, as well as accounts. 1251 if (DEBUG_FILE) Log.v(TAG, "Reading legacy sync accounts db"); 1252 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 1253 qb.setTables("stats, status"); 1254 HashMap<String,String> map = new HashMap<String,String>(); 1255 map.put("_id", "status._id as _id"); 1256 map.put("account", "stats.account as account"); 1257 if (hasType) { 1258 map.put("account_type", "stats.account_type as account_type"); 1259 } 1260 map.put("authority", "stats.authority as authority"); 1261 map.put("totalElapsedTime", "totalElapsedTime"); 1262 map.put("numSyncs", "numSyncs"); 1263 map.put("numSourceLocal", "numSourceLocal"); 1264 map.put("numSourcePoll", "numSourcePoll"); 1265 map.put("numSourceServer", "numSourceServer"); 1266 map.put("numSourceUser", "numSourceUser"); 1267 map.put("lastSuccessSource", "lastSuccessSource"); 1268 map.put("lastSuccessTime", "lastSuccessTime"); 1269 map.put("lastFailureSource", "lastFailureSource"); 1270 map.put("lastFailureTime", "lastFailureTime"); 1271 map.put("lastFailureMesg", "lastFailureMesg"); 1272 map.put("pending", "pending"); 1273 qb.setProjectionMap(map); 1274 qb.appendWhere("stats._id = status.stats_id"); 1275 Cursor c = qb.query(db, null, null, null, null, null, null); 1276 while (c.moveToNext()) { 1277 String accountName = c.getString(c.getColumnIndex("account")); 1278 String accountType = hasType 1279 ? c.getString(c.getColumnIndex("account_type")) : null; 1280 if (accountType == null) { 1281 accountType = "com.google.GAIA"; 1282 } 1283 String authorityName = c.getString(c.getColumnIndex("authority")); 1284 AuthorityInfo authority = this.getOrCreateAuthorityLocked( 1285 new Account(accountName, accountType), 1286 authorityName, -1, false); 1287 if (authority != null) { 1288 int i = mSyncStatus.size(); 1289 boolean found = false; 1290 SyncStatusInfo st = null; 1291 while (i > 0) { 1292 i--; 1293 st = mSyncStatus.get(i); 1294 if (st.authorityId == authority.ident) { 1295 found = true; 1296 break; 1297 } 1298 } 1299 if (!found) { 1300 st = new SyncStatusInfo(authority.ident); 1301 mSyncStatus.put(authority.ident, st); 1302 } 1303 st.totalElapsedTime = getLongColumn(c, "totalElapsedTime"); 1304 st.numSyncs = getIntColumn(c, "numSyncs"); 1305 st.numSourceLocal = getIntColumn(c, "numSourceLocal"); 1306 st.numSourcePoll = getIntColumn(c, "numSourcePoll"); 1307 st.numSourceServer = getIntColumn(c, "numSourceServer"); 1308 st.numSourceUser = getIntColumn(c, "numSourceUser"); 1309 st.lastSuccessSource = getIntColumn(c, "lastSuccessSource"); 1310 st.lastSuccessTime = getLongColumn(c, "lastSuccessTime"); 1311 st.lastFailureSource = getIntColumn(c, "lastFailureSource"); 1312 st.lastFailureTime = getLongColumn(c, "lastFailureTime"); 1313 st.lastFailureMesg = c.getString(c.getColumnIndex("lastFailureMesg")); 1314 st.pending = getIntColumn(c, "pending") != 0; 1315 } 1316 } 1317 1318 c.close(); 1319 1320 // Retrieve the settings. 1321 qb = new SQLiteQueryBuilder(); 1322 qb.setTables("settings"); 1323 c = qb.query(db, null, null, null, null, null, null); 1324 while (c.moveToNext()) { 1325 String name = c.getString(c.getColumnIndex("name")); 1326 String value = c.getString(c.getColumnIndex("value")); 1327 if (name == null) continue; 1328 if (name.equals("listen_for_tickles")) { 1329 setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value)); 1330 } else if (name.startsWith("sync_provider_")) { 1331 String provider = name.substring("sync_provider_".length(), 1332 name.length()); 1333 int i = mAuthorities.size(); 1334 while (i > 0) { 1335 i--; 1336 AuthorityInfo authority = mAuthorities.get(i); 1337 if (authority.authority.equals(provider)) { 1338 authority.enabled = value == null || Boolean.parseBoolean(value); 1339 authority.syncable = 1; 1340 } 1341 } 1342 } 1343 } 1344 1345 c.close(); 1346 1347 db.close(); 1348 1349 writeAccountInfoLocked(); 1350 writeStatusLocked(); 1351 (new File(path)).delete(); 1352 } 1353 } 1354 1355 public static final int STATUS_FILE_END = 0; 1356 public static final int STATUS_FILE_ITEM = 100; 1357 1358 /** 1359 * Read all sync status back in to the initial engine state. 1360 */ 1361 private void readStatusLocked() { 1362 if (DEBUG_FILE) Log.v(TAG, "Reading " + mStatusFile.getBaseFile()); 1363 try { 1364 byte[] data = mStatusFile.readFully(); 1365 Parcel in = Parcel.obtain(); 1366 in.unmarshall(data, 0, data.length); 1367 in.setDataPosition(0); 1368 int token; 1369 while ((token=in.readInt()) != STATUS_FILE_END) { 1370 if (token == STATUS_FILE_ITEM) { 1371 SyncStatusInfo status = new SyncStatusInfo(in); 1372 if (mAuthorities.indexOfKey(status.authorityId) >= 0) { 1373 status.pending = false; 1374 if (DEBUG_FILE) Log.v(TAG, "Adding status for id " 1375 + status.authorityId); 1376 mSyncStatus.put(status.authorityId, status); 1377 } 1378 } else { 1379 // Ooops. 1380 Log.w(TAG, "Unknown status token: " + token); 1381 break; 1382 } 1383 } 1384 } catch (java.io.IOException e) { 1385 Log.i(TAG, "No initial status"); 1386 } 1387 } 1388 1389 /** 1390 * Write all sync status to the sync status file. 1391 */ 1392 private void writeStatusLocked() { 1393 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatusFile.getBaseFile()); 1394 1395 // The file is being written, so we don't need to have a scheduled 1396 // write until the next change. 1397 removeMessages(MSG_WRITE_STATUS); 1398 1399 FileOutputStream fos = null; 1400 try { 1401 fos = mStatusFile.startWrite(); 1402 Parcel out = Parcel.obtain(); 1403 final int N = mSyncStatus.size(); 1404 for (int i=0; i<N; i++) { 1405 SyncStatusInfo status = mSyncStatus.valueAt(i); 1406 out.writeInt(STATUS_FILE_ITEM); 1407 status.writeToParcel(out, 0); 1408 } 1409 out.writeInt(STATUS_FILE_END); 1410 fos.write(out.marshall()); 1411 out.recycle(); 1412 1413 mStatusFile.finishWrite(fos); 1414 } catch (java.io.IOException e1) { 1415 Log.w(TAG, "Error writing status", e1); 1416 if (fos != null) { 1417 mStatusFile.failWrite(fos); 1418 } 1419 } 1420 } 1421 1422 public static final int PENDING_OPERATION_VERSION = 1; 1423 1424 /** 1425 * Read all pending operations back in to the initial engine state. 1426 */ 1427 private void readPendingOperationsLocked() { 1428 if (DEBUG_FILE) Log.v(TAG, "Reading " + mPendingFile.getBaseFile()); 1429 try { 1430 byte[] data = mPendingFile.readFully(); 1431 Parcel in = Parcel.obtain(); 1432 in.unmarshall(data, 0, data.length); 1433 in.setDataPosition(0); 1434 final int SIZE = in.dataSize(); 1435 while (in.dataPosition() < SIZE) { 1436 int version = in.readInt(); 1437 if (version != PENDING_OPERATION_VERSION) { 1438 Log.w(TAG, "Unknown pending operation version " 1439 + version + "; dropping all ops"); 1440 break; 1441 } 1442 int authorityId = in.readInt(); 1443 int syncSource = in.readInt(); 1444 byte[] flatExtras = in.createByteArray(); 1445 AuthorityInfo authority = mAuthorities.get(authorityId); 1446 if (authority != null) { 1447 Bundle extras = null; 1448 if (flatExtras != null) { 1449 extras = unflattenBundle(flatExtras); 1450 } 1451 PendingOperation op = new PendingOperation( 1452 authority.account, syncSource, 1453 authority.authority, extras); 1454 op.authorityId = authorityId; 1455 op.flatExtras = flatExtras; 1456 if (DEBUG_FILE) Log.v(TAG, "Adding pending op: account=" + op.account 1457 + " auth=" + op.authority 1458 + " src=" + op.syncSource 1459 + " extras=" + op.extras); 1460 mPendingOperations.add(op); 1461 } 1462 } 1463 } catch (java.io.IOException e) { 1464 Log.i(TAG, "No initial pending operations"); 1465 } 1466 } 1467 1468 private void writePendingOperationLocked(PendingOperation op, Parcel out) { 1469 out.writeInt(PENDING_OPERATION_VERSION); 1470 out.writeInt(op.authorityId); 1471 out.writeInt(op.syncSource); 1472 if (op.flatExtras == null && op.extras != null) { 1473 op.flatExtras = flattenBundle(op.extras); 1474 } 1475 out.writeByteArray(op.flatExtras); 1476 } 1477 1478 /** 1479 * Write all currently pending ops to the pending ops file. 1480 */ 1481 private void writePendingOperationsLocked() { 1482 final int N = mPendingOperations.size(); 1483 FileOutputStream fos = null; 1484 try { 1485 if (N == 0) { 1486 if (DEBUG_FILE) Log.v(TAG, "Truncating " + mPendingFile.getBaseFile()); 1487 mPendingFile.truncate(); 1488 return; 1489 } 1490 1491 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mPendingFile.getBaseFile()); 1492 fos = mPendingFile.startWrite(); 1493 1494 Parcel out = Parcel.obtain(); 1495 for (int i=0; i<N; i++) { 1496 PendingOperation op = mPendingOperations.get(i); 1497 writePendingOperationLocked(op, out); 1498 } 1499 fos.write(out.marshall()); 1500 out.recycle(); 1501 1502 mPendingFile.finishWrite(fos); 1503 } catch (java.io.IOException e1) { 1504 Log.w(TAG, "Error writing pending operations", e1); 1505 if (fos != null) { 1506 mPendingFile.failWrite(fos); 1507 } 1508 } 1509 } 1510 1511 /** 1512 * Append the given operation to the pending ops file; if unable to, 1513 * write all pending ops. 1514 */ 1515 private void appendPendingOperationLocked(PendingOperation op) { 1516 if (DEBUG_FILE) Log.v(TAG, "Appending to " + mPendingFile.getBaseFile()); 1517 FileOutputStream fos = null; 1518 try { 1519 fos = mPendingFile.openAppend(); 1520 } catch (java.io.IOException e) { 1521 if (DEBUG_FILE) Log.v(TAG, "Failed append; writing full file"); 1522 writePendingOperationsLocked(); 1523 return; 1524 } 1525 1526 try { 1527 Parcel out = Parcel.obtain(); 1528 writePendingOperationLocked(op, out); 1529 fos.write(out.marshall()); 1530 out.recycle(); 1531 } catch (java.io.IOException e1) { 1532 Log.w(TAG, "Error writing pending operations", e1); 1533 } finally { 1534 try { 1535 fos.close(); 1536 } catch (java.io.IOException e2) { 1537 } 1538 } 1539 } 1540 1541 static private byte[] flattenBundle(Bundle bundle) { 1542 byte[] flatData = null; 1543 Parcel parcel = Parcel.obtain(); 1544 try { 1545 bundle.writeToParcel(parcel, 0); 1546 flatData = parcel.marshall(); 1547 } finally { 1548 parcel.recycle(); 1549 } 1550 return flatData; 1551 } 1552 1553 static private Bundle unflattenBundle(byte[] flatData) { 1554 Bundle bundle; 1555 Parcel parcel = Parcel.obtain(); 1556 try { 1557 parcel.unmarshall(flatData, 0, flatData.length); 1558 parcel.setDataPosition(0); 1559 bundle = parcel.readBundle(); 1560 } catch (RuntimeException e) { 1561 // A RuntimeException is thrown if we were unable to parse the parcel. 1562 // Create an empty parcel in this case. 1563 bundle = new Bundle(); 1564 } finally { 1565 parcel.recycle(); 1566 } 1567 return bundle; 1568 } 1569 1570 public static final int STATISTICS_FILE_END = 0; 1571 public static final int STATISTICS_FILE_ITEM_OLD = 100; 1572 public static final int STATISTICS_FILE_ITEM = 101; 1573 1574 /** 1575 * Read all sync statistics back in to the initial engine state. 1576 */ 1577 private void readStatisticsLocked() { 1578 try { 1579 byte[] data = mStatisticsFile.readFully(); 1580 Parcel in = Parcel.obtain(); 1581 in.unmarshall(data, 0, data.length); 1582 in.setDataPosition(0); 1583 int token; 1584 int index = 0; 1585 while ((token=in.readInt()) != STATISTICS_FILE_END) { 1586 if (token == STATISTICS_FILE_ITEM 1587 || token == STATISTICS_FILE_ITEM_OLD) { 1588 int day = in.readInt(); 1589 if (token == STATISTICS_FILE_ITEM_OLD) { 1590 day = day - 2009 + 14245; // Magic! 1591 } 1592 DayStats ds = new DayStats(day); 1593 ds.successCount = in.readInt(); 1594 ds.successTime = in.readLong(); 1595 ds.failureCount = in.readInt(); 1596 ds.failureTime = in.readLong(); 1597 if (index < mDayStats.length) { 1598 mDayStats[index] = ds; 1599 index++; 1600 } 1601 } else { 1602 // Ooops. 1603 Log.w(TAG, "Unknown stats token: " + token); 1604 break; 1605 } 1606 } 1607 } catch (java.io.IOException e) { 1608 Log.i(TAG, "No initial statistics"); 1609 } 1610 } 1611 1612 /** 1613 * Write all sync statistics to the sync status file. 1614 */ 1615 private void writeStatisticsLocked() { 1616 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatisticsFile.getBaseFile()); 1617 1618 // The file is being written, so we don't need to have a scheduled 1619 // write until the next change. 1620 removeMessages(MSG_WRITE_STATISTICS); 1621 1622 FileOutputStream fos = null; 1623 try { 1624 fos = mStatisticsFile.startWrite(); 1625 Parcel out = Parcel.obtain(); 1626 final int N = mDayStats.length; 1627 for (int i=0; i<N; i++) { 1628 DayStats ds = mDayStats[i]; 1629 if (ds == null) { 1630 break; 1631 } 1632 out.writeInt(STATISTICS_FILE_ITEM); 1633 out.writeInt(ds.day); 1634 out.writeInt(ds.successCount); 1635 out.writeLong(ds.successTime); 1636 out.writeInt(ds.failureCount); 1637 out.writeLong(ds.failureTime); 1638 } 1639 out.writeInt(STATISTICS_FILE_END); 1640 fos.write(out.marshall()); 1641 out.recycle(); 1642 1643 mStatisticsFile.finishWrite(fos); 1644 } catch (java.io.IOException e1) { 1645 Log.w(TAG, "Error writing stats", e1); 1646 if (fos != null) { 1647 mStatisticsFile.failWrite(fos); 1648 } 1649 } 1650 } 1651} 1652