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