SyncStorageEngine.java revision 3531fdb1d9a0ca536bd7a7a27d35b3e62c318ad9
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.database.Cursor; 29import android.database.sqlite.SQLiteDatabase; 30import android.database.sqlite.SQLiteException; 31import android.database.sqlite.SQLiteQueryBuilder; 32import android.os.Bundle; 33import android.os.Environment; 34import android.os.Handler; 35import android.os.Message; 36import android.os.Parcel; 37import android.os.RemoteCallbackList; 38import android.os.RemoteException; 39import android.util.Log; 40import android.util.SparseArray; 41import android.util.Xml; 42 43import java.io.File; 44import java.io.FileInputStream; 45import java.io.FileOutputStream; 46import java.util.ArrayList; 47import java.util.Calendar; 48import java.util.HashMap; 49import java.util.Iterator; 50import java.util.TimeZone; 51 52import com.google.android.collect.Sets; 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 // TODO: i18n -- grab these out of resources. 93 /** String names for the sync source types. */ 94 public static final String[] SOURCES = { "SERVER", 95 "LOCAL", 96 "POLL", 97 "USER" }; 98 99 // Error types 100 public static final int ERROR_SYNC_ALREADY_IN_PROGRESS = 1; 101 public static final int ERROR_AUTHENTICATION = 2; 102 public static final int ERROR_IO = 3; 103 public static final int ERROR_PARSE = 4; 104 public static final int ERROR_CONFLICT = 5; 105 public static final int ERROR_TOO_MANY_DELETIONS = 6; 106 public static final int ERROR_TOO_MANY_RETRIES = 7; 107 public static final int ERROR_INTERNAL = 8; 108 109 // The MESG column will contain one of these or one of the Error types. 110 public static final String MESG_SUCCESS = "success"; 111 public static final String MESG_CANCELED = "canceled"; 112 113 public static final int CHANGE_SETTINGS = 1<<0; 114 public static final int CHANGE_PENDING = 1<<1; 115 public static final int CHANGE_ACTIVE = 1<<2; 116 public static final int CHANGE_STATUS = 1<<3; 117 public static final int CHANGE_ALL = 0x7fffffff; 118 119 public static final int MAX_HISTORY = 15; 120 121 private static final int MSG_WRITE_STATUS = 1; 122 private static final long WRITE_STATUS_DELAY = 1000*60*10; // 10 minutes 123 124 private static final int MSG_WRITE_STATISTICS = 2; 125 private static final long WRITE_STATISTICS_DELAY = 1000*60*30; // 1/2 hour 126 127 public static class PendingOperation { 128 final Account account; 129 final int syncSource; 130 final String authority; 131 final Bundle extras; // note: read-only. 132 133 int authorityId; 134 byte[] flatExtras; 135 136 PendingOperation(Account account, int source, 137 String authority, Bundle extras) { 138 this.account = account; 139 this.syncSource = source; 140 this.authority = authority; 141 this.extras = extras != null ? new Bundle(extras) : extras; 142 this.authorityId = -1; 143 } 144 145 PendingOperation(PendingOperation other) { 146 this.account = other.account; 147 this.syncSource = other.syncSource; 148 this.authority = other.authority; 149 this.extras = other.extras; 150 this.authorityId = other.authorityId; 151 } 152 } 153 154 static class AccountInfo { 155 final Account account; 156 final HashMap<String, AuthorityInfo> authorities = 157 new HashMap<String, AuthorityInfo>(); 158 159 AccountInfo(Account account) { 160 this.account = account; 161 } 162 } 163 164 public static class AuthorityInfo { 165 final Account account; 166 final String authority; 167 final int ident; 168 boolean enabled; 169 170 AuthorityInfo(Account account, String authority, int ident) { 171 this.account = account; 172 this.authority = authority; 173 this.ident = ident; 174 enabled = true; 175 } 176 } 177 178 public static class SyncHistoryItem { 179 int authorityId; 180 int historyId; 181 long eventTime; 182 long elapsedTime; 183 int source; 184 int event; 185 long upstreamActivity; 186 long downstreamActivity; 187 String mesg; 188 } 189 190 public static class DayStats { 191 public final int day; 192 public int successCount; 193 public long successTime; 194 public int failureCount; 195 public long failureTime; 196 197 public DayStats(int day) { 198 this.day = day; 199 } 200 } 201 202 // Primary list of all syncable authorities. Also our global lock. 203 private final SparseArray<AuthorityInfo> mAuthorities = 204 new SparseArray<AuthorityInfo>(); 205 206 private final HashMap<Account, AccountInfo> mAccounts = 207 new HashMap<Account, AccountInfo>(); 208 209 private final ArrayList<PendingOperation> mPendingOperations = 210 new ArrayList<PendingOperation>(); 211 212 private ActiveSyncInfo mActiveSync; 213 214 private final SparseArray<SyncStatusInfo> mSyncStatus = 215 new SparseArray<SyncStatusInfo>(); 216 217 private final ArrayList<SyncHistoryItem> mSyncHistory = 218 new ArrayList<SyncHistoryItem>(); 219 220 private final RemoteCallbackList<ISyncStatusObserver> mChangeListeners 221 = new RemoteCallbackList<ISyncStatusObserver>(); 222 223 // We keep 4 weeks of stats. 224 private final DayStats[] mDayStats = new DayStats[7*4]; 225 private final Calendar mCal; 226 private int mYear; 227 private int mYearInDays; 228 229 private final Context mContext; 230 private static volatile SyncStorageEngine sSyncStorageEngine = null; 231 232 /** 233 * This file contains the core engine state: all accounts and the 234 * settings for them. It must never be lost, and should be changed 235 * infrequently, so it is stored as an XML file. 236 */ 237 private final AtomicFile mAccountInfoFile; 238 239 /** 240 * This file contains the current sync status. We would like to retain 241 * it across boots, but its loss is not the end of the world, so we store 242 * this information as binary data. 243 */ 244 private final AtomicFile mStatusFile; 245 246 /** 247 * This file contains sync statistics. This is purely debugging information 248 * so is written infrequently and can be thrown away at any time. 249 */ 250 private final AtomicFile mStatisticsFile; 251 252 /** 253 * This file contains the pending sync operations. It is a binary file, 254 * which must be updated every time an operation is added or removed, 255 * so we have special handling of it. 256 */ 257 private final AtomicFile mPendingFile; 258 private static final int PENDING_FINISH_TO_WRITE = 4; 259 private int mNumPendingFinished = 0; 260 261 private int mNextHistoryId = 0; 262 private boolean mListenForTickles = true; 263 264 private SyncStorageEngine(Context context) { 265 mContext = context; 266 sSyncStorageEngine = this; 267 268 mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0")); 269 270 File dataDir = Environment.getDataDirectory(); 271 File systemDir = new File(dataDir, "system"); 272 File syncDir = new File(systemDir, "sync"); 273 mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml")); 274 mStatusFile = new AtomicFile(new File(syncDir, "status.bin")); 275 mPendingFile = new AtomicFile(new File(syncDir, "pending.bin")); 276 mStatisticsFile = new AtomicFile(new File(syncDir, "stats.bin")); 277 278 readAccountInfoLocked(); 279 readStatusLocked(); 280 readPendingOperationsLocked(); 281 readStatisticsLocked(); 282 readLegacyAccountInfoLocked(); 283 } 284 285 public static SyncStorageEngine newTestInstance(Context context) { 286 return new SyncStorageEngine(context); 287 } 288 289 public static void init(Context context) { 290 if (sSyncStorageEngine != null) { 291 throw new IllegalStateException("already initialized"); 292 } 293 sSyncStorageEngine = new SyncStorageEngine(context); 294 } 295 296 public static SyncStorageEngine getSingleton() { 297 if (sSyncStorageEngine == null) { 298 throw new IllegalStateException("not initialized"); 299 } 300 return sSyncStorageEngine; 301 } 302 303 @Override public void handleMessage(Message msg) { 304 if (msg.what == MSG_WRITE_STATUS) { 305 synchronized (mAccounts) { 306 writeStatusLocked(); 307 } 308 } else if (msg.what == MSG_WRITE_STATISTICS) { 309 synchronized (mAccounts) { 310 writeStatisticsLocked(); 311 } 312 } 313 } 314 315 public void addStatusChangeListener(int mask, ISyncStatusObserver callback) { 316 synchronized (mAuthorities) { 317 mChangeListeners.register(callback, mask); 318 } 319 } 320 321 public void removeStatusChangeListener(ISyncStatusObserver callback) { 322 synchronized (mAuthorities) { 323 mChangeListeners.unregister(callback); 324 } 325 } 326 327 private void reportChange(int which) { 328 ArrayList<ISyncStatusObserver> reports = null; 329 synchronized (mAuthorities) { 330 int i = mChangeListeners.beginBroadcast(); 331 while (i > 0) { 332 i--; 333 Integer mask = (Integer)mChangeListeners.getBroadcastCookie(i); 334 if ((which & mask.intValue()) == 0) { 335 continue; 336 } 337 if (reports == null) { 338 reports = new ArrayList<ISyncStatusObserver>(i); 339 } 340 reports.add(mChangeListeners.getBroadcastItem(i)); 341 } 342 } 343 344 if (DEBUG) Log.v(TAG, "reportChange " + which + " to: " + reports); 345 346 if (reports != null) { 347 int i = reports.size(); 348 while (i > 0) { 349 i--; 350 try { 351 reports.get(i).onStatusChanged(which); 352 } catch (RemoteException e) { 353 // The remote callback list will take care of this for us. 354 } 355 } 356 } 357 } 358 359 public boolean getSyncProviderAutomatically(Account account, String providerName) { 360 synchronized (mAuthorities) { 361 if (account != null) { 362 AuthorityInfo authority = getAuthorityLocked(account, providerName, 363 "getSyncProviderAutomatically"); 364 return authority != null ? authority.enabled : false; 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 setSyncProviderAutomatically(Account account, String providerName, 381 boolean sync) { 382 synchronized (mAuthorities) { 383 if (account != null) { 384 AuthorityInfo authority = getAuthorityLocked(account, providerName, 385 "setSyncProviderAutomatically"); 386 if (authority != null) { 387 authority.enabled = sync; 388 } 389 } else { 390 int i = mAuthorities.size(); 391 while (i > 0) { 392 i--; 393 AuthorityInfo authority = mAuthorities.get(i); 394 if (authority.account.equals(account) 395 && authority.authority.equals(providerName)) { 396 authority.enabled = sync; 397 } 398 } 399 } 400 writeAccountInfoLocked(); 401 } 402 403 reportChange(CHANGE_SETTINGS); 404 } 405 406 public void setListenForNetworkTickles(boolean flag) { 407 synchronized (mAuthorities) { 408 mListenForTickles = flag; 409 writeAccountInfoLocked(); 410 } 411 reportChange(CHANGE_SETTINGS); 412 } 413 414 public boolean getListenForNetworkTickles() { 415 synchronized (mAuthorities) { 416 return mListenForTickles; 417 } 418 } 419 420 public AuthorityInfo getAuthority(Account account, String authority) { 421 synchronized (mAuthorities) { 422 return getAuthorityLocked(account, authority, null); 423 } 424 } 425 426 public AuthorityInfo getAuthority(int authorityId) { 427 synchronized (mAuthorities) { 428 return mAuthorities.get(authorityId); 429 } 430 } 431 432 /** 433 * Returns true if there is currently a sync operation for the given 434 * account or authority in the pending list, or actively being processed. 435 */ 436 public boolean isSyncActive(Account account, String authority) { 437 synchronized (mAuthorities) { 438 int i = mPendingOperations.size(); 439 while (i > 0) { 440 i--; 441 // TODO(fredq): this probably shouldn't be considering 442 // pending operations. 443 PendingOperation op = mPendingOperations.get(i); 444 if (op.account.equals(account) && op.authority.equals(authority)) { 445 return true; 446 } 447 } 448 449 if (mActiveSync != null) { 450 AuthorityInfo ainfo = getAuthority(mActiveSync.authorityId); 451 if (ainfo != null && ainfo.account.equals(account) 452 && ainfo.authority.equals(authority)) { 453 return true; 454 } 455 } 456 } 457 458 return false; 459 } 460 461 public PendingOperation insertIntoPending(PendingOperation op) { 462 synchronized (mAuthorities) { 463 if (DEBUG) Log.v(TAG, "insertIntoPending: account=" + op.account 464 + " auth=" + op.authority 465 + " src=" + op.syncSource 466 + " extras=" + op.extras); 467 468 AuthorityInfo authority = getOrCreateAuthorityLocked(op.account, 469 op.authority, 470 -1 /* desired identifier */, 471 true /* write accounts to storage */); 472 if (authority == null) { 473 return null; 474 } 475 476 op = new PendingOperation(op); 477 op.authorityId = authority.ident; 478 mPendingOperations.add(op); 479 appendPendingOperationLocked(op); 480 481 SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident); 482 status.pending = true; 483 } 484 485 reportChange(CHANGE_PENDING); 486 return op; 487 } 488 489 public boolean deleteFromPending(PendingOperation op) { 490 boolean res = false; 491 synchronized (mAuthorities) { 492 if (DEBUG) Log.v(TAG, "deleteFromPending: account=" + op.account 493 + " auth=" + op.authority 494 + " src=" + op.syncSource 495 + " extras=" + op.extras); 496 if (mPendingOperations.remove(op)) { 497 if (mPendingOperations.size() == 0 498 || mNumPendingFinished >= PENDING_FINISH_TO_WRITE) { 499 writePendingOperationsLocked(); 500 mNumPendingFinished = 0; 501 } else { 502 mNumPendingFinished++; 503 } 504 505 AuthorityInfo authority = getAuthorityLocked(op.account, op.authority, 506 "deleteFromPending"); 507 if (authority != null) { 508 if (DEBUG) Log.v(TAG, "removing - " + authority); 509 final int N = mPendingOperations.size(); 510 boolean morePending = false; 511 for (int i=0; i<N; i++) { 512 PendingOperation cur = mPendingOperations.get(i); 513 if (cur.account.equals(op.account) 514 && cur.authority.equals(op.authority)) { 515 morePending = true; 516 break; 517 } 518 } 519 520 if (!morePending) { 521 if (DEBUG) Log.v(TAG, "no more pending!"); 522 SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident); 523 status.pending = false; 524 } 525 } 526 527 res = true; 528 } 529 } 530 531 reportChange(CHANGE_PENDING); 532 return res; 533 } 534 535 public int clearPending() { 536 int num; 537 synchronized (mAuthorities) { 538 if (DEBUG) Log.v(TAG, "clearPending"); 539 num = mPendingOperations.size(); 540 mPendingOperations.clear(); 541 final int N = mSyncStatus.size(); 542 for (int i=0; i<N; i++) { 543 mSyncStatus.get(i).pending = false; 544 } 545 writePendingOperationsLocked(); 546 } 547 reportChange(CHANGE_PENDING); 548 return num; 549 } 550 551 /** 552 * Return a copy of the current array of pending operations. The 553 * PendingOperation objects are the real objects stored inside, so that 554 * they can be used with deleteFromPending(). 555 */ 556 public ArrayList<PendingOperation> getPendingOperations() { 557 synchronized (mAuthorities) { 558 return new ArrayList<PendingOperation>(mPendingOperations); 559 } 560 } 561 562 /** 563 * Return the number of currently pending operations. 564 */ 565 public int getPendingOperationCount() { 566 synchronized (mAuthorities) { 567 return mPendingOperations.size(); 568 } 569 } 570 571 /** 572 * Called when the set of account has changed, given the new array of 573 * active accounts. 574 */ 575 public void doDatabaseCleanup(Account[] accounts) { 576 synchronized (mAuthorities) { 577 if (DEBUG) Log.w(TAG, "Updating for new accounts..."); 578 SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>(); 579 Iterator<AccountInfo> accIt = mAccounts.values().iterator(); 580 while (accIt.hasNext()) { 581 AccountInfo acc = accIt.next(); 582 if (!ArrayUtils.contains(accounts, acc.account)) { 583 // This account no longer exists... 584 if (DEBUG) Log.w(TAG, "Account removed: " + acc.account); 585 for (AuthorityInfo auth : acc.authorities.values()) { 586 removing.put(auth.ident, auth); 587 } 588 accIt.remove(); 589 } 590 } 591 592 // Clean out all data structures. 593 int i = removing.size(); 594 if (i > 0) { 595 while (i > 0) { 596 i--; 597 int ident = removing.keyAt(i); 598 mAuthorities.remove(ident); 599 int j = mSyncStatus.size(); 600 while (j > 0) { 601 j--; 602 if (mSyncStatus.keyAt(j) == ident) { 603 mSyncStatus.remove(mSyncStatus.keyAt(j)); 604 } 605 } 606 j = mSyncHistory.size(); 607 while (j > 0) { 608 j--; 609 if (mSyncHistory.get(j).authorityId == ident) { 610 mSyncHistory.remove(j); 611 } 612 } 613 } 614 writeAccountInfoLocked(); 615 writeStatusLocked(); 616 writePendingOperationsLocked(); 617 writeStatisticsLocked(); 618 } 619 } 620 } 621 622 /** 623 * Called when the currently active sync is changing (there can only be 624 * one at a time). Either supply a valid ActiveSyncContext with information 625 * about the sync, or null to stop the currently active sync. 626 */ 627 public void setActiveSync(SyncManager.ActiveSyncContext activeSyncContext) { 628 synchronized (mAuthorities) { 629 if (activeSyncContext != null) { 630 if (DEBUG) Log.v(TAG, "setActiveSync: account=" 631 + activeSyncContext.mSyncOperation.account 632 + " auth=" + activeSyncContext.mSyncOperation.authority 633 + " src=" + activeSyncContext.mSyncOperation.syncSource 634 + " extras=" + activeSyncContext.mSyncOperation.extras); 635 if (mActiveSync != null) { 636 Log.w(TAG, "setActiveSync called with existing active sync!"); 637 } 638 AuthorityInfo authority = getAuthorityLocked( 639 activeSyncContext.mSyncOperation.account, 640 activeSyncContext.mSyncOperation.authority, 641 "setActiveSync"); 642 if (authority == null) { 643 return; 644 } 645 mActiveSync = new ActiveSyncInfo(authority.ident, 646 authority.account, authority.authority, 647 activeSyncContext.mStartTime); 648 } else { 649 if (DEBUG) Log.v(TAG, "setActiveSync: null"); 650 mActiveSync = null; 651 } 652 } 653 654 reportChange(CHANGE_ACTIVE); 655 } 656 657 /** 658 * To allow others to send active change reports, to poke clients. 659 */ 660 public void reportActiveChange() { 661 reportChange(CHANGE_ACTIVE); 662 } 663 664 /** 665 * Note that sync has started for the given account and authority. 666 */ 667 public long insertStartSyncEvent(Account accountName, String authorityName, 668 long now, int source) { 669 long id; 670 synchronized (mAuthorities) { 671 if (DEBUG) Log.v(TAG, "insertStartSyncEvent: account=" + accountName 672 + " auth=" + authorityName + " source=" + source); 673 AuthorityInfo authority = getAuthorityLocked(accountName, authorityName, 674 "insertStartSyncEvent"); 675 if (authority == null) { 676 return -1; 677 } 678 SyncHistoryItem item = new SyncHistoryItem(); 679 item.authorityId = authority.ident; 680 item.historyId = mNextHistoryId++; 681 if (mNextHistoryId < 0) mNextHistoryId = 0; 682 item.eventTime = now; 683 item.source = source; 684 item.event = EVENT_START; 685 mSyncHistory.add(0, item); 686 while (mSyncHistory.size() > MAX_HISTORY) { 687 mSyncHistory.remove(mSyncHistory.size()-1); 688 } 689 id = item.historyId; 690 if (DEBUG) Log.v(TAG, "returning historyId " + id); 691 } 692 693 reportChange(CHANGE_STATUS); 694 return id; 695 } 696 697 public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage, 698 long downstreamActivity, long upstreamActivity) { 699 synchronized (mAuthorities) { 700 if (DEBUG) Log.v(TAG, "stopSyncEvent: historyId=" + historyId); 701 SyncHistoryItem item = null; 702 int i = mSyncHistory.size(); 703 while (i > 0) { 704 i--; 705 item = mSyncHistory.get(i); 706 if (item.historyId == historyId) { 707 break; 708 } 709 item = null; 710 } 711 712 if (item == null) { 713 Log.w(TAG, "stopSyncEvent: no history for id " + historyId); 714 return; 715 } 716 717 item.elapsedTime = elapsedTime; 718 item.event = EVENT_STOP; 719 item.mesg = resultMessage; 720 item.downstreamActivity = downstreamActivity; 721 item.upstreamActivity = upstreamActivity; 722 723 SyncStatusInfo status = getOrCreateSyncStatusLocked(item.authorityId); 724 725 status.numSyncs++; 726 status.totalElapsedTime += elapsedTime; 727 switch (item.source) { 728 case SOURCE_LOCAL: 729 status.numSourceLocal++; 730 break; 731 case SOURCE_POLL: 732 status.numSourcePoll++; 733 break; 734 case SOURCE_USER: 735 status.numSourceUser++; 736 break; 737 case SOURCE_SERVER: 738 status.numSourceServer++; 739 break; 740 } 741 742 boolean writeStatisticsNow = false; 743 int day = getCurrentDayLocked(); 744 if (mDayStats[0] == null) { 745 mDayStats[0] = new DayStats(day); 746 } else if (day != mDayStats[0].day) { 747 System.arraycopy(mDayStats, 0, mDayStats, 1, mDayStats.length-1); 748 mDayStats[0] = new DayStats(day); 749 writeStatisticsNow = true; 750 } else if (mDayStats[0] == null) { 751 } 752 final DayStats ds = mDayStats[0]; 753 754 final long lastSyncTime = (item.eventTime + elapsedTime); 755 boolean writeStatusNow = false; 756 if (MESG_SUCCESS.equals(resultMessage)) { 757 // - if successful, update the successful columns 758 if (status.lastSuccessTime == 0 || status.lastFailureTime != 0) { 759 writeStatusNow = true; 760 } 761 status.lastSuccessTime = lastSyncTime; 762 status.lastSuccessSource = item.source; 763 status.lastFailureTime = 0; 764 status.lastFailureSource = -1; 765 status.lastFailureMesg = null; 766 status.initialFailureTime = 0; 767 ds.successCount++; 768 ds.successTime += elapsedTime; 769 } else if (!MESG_CANCELED.equals(resultMessage)) { 770 if (status.lastFailureTime == 0) { 771 writeStatusNow = true; 772 } 773 status.lastFailureTime = lastSyncTime; 774 status.lastFailureSource = item.source; 775 status.lastFailureMesg = resultMessage; 776 if (status.initialFailureTime == 0) { 777 status.initialFailureTime = lastSyncTime; 778 } 779 ds.failureCount++; 780 ds.failureTime += elapsedTime; 781 } 782 783 if (writeStatusNow) { 784 writeStatusLocked(); 785 } else if (!hasMessages(MSG_WRITE_STATUS)) { 786 sendMessageDelayed(obtainMessage(MSG_WRITE_STATUS), 787 WRITE_STATUS_DELAY); 788 } 789 if (writeStatisticsNow) { 790 writeStatisticsLocked(); 791 } else if (!hasMessages(MSG_WRITE_STATISTICS)) { 792 sendMessageDelayed(obtainMessage(MSG_WRITE_STATISTICS), 793 WRITE_STATISTICS_DELAY); 794 } 795 } 796 797 reportChange(CHANGE_STATUS); 798 } 799 800 /** 801 * Return the currently active sync information, or null if there is no 802 * active sync. Note that the returned object is the real, live active 803 * sync object, so be careful what you do with it. 804 */ 805 public ActiveSyncInfo getActiveSync() { 806 synchronized (mAuthorities) { 807 return mActiveSync; 808 } 809 } 810 811 /** 812 * Return an array of the current sync status for all authorities. Note 813 * that the objects inside the array are the real, live status objects, 814 * so be careful what you do with them. 815 */ 816 public ArrayList<SyncStatusInfo> getSyncStatus() { 817 synchronized (mAuthorities) { 818 final int N = mSyncStatus.size(); 819 ArrayList<SyncStatusInfo> ops = new ArrayList<SyncStatusInfo>(N); 820 for (int i=0; i<N; i++) { 821 ops.add(mSyncStatus.valueAt(i)); 822 } 823 return ops; 824 } 825 } 826 827 /** 828 * Returns the status that matches the authority. If there are multiples accounts for 829 * the authority, the one with the latest "lastSuccessTime" status is returned. 830 * @param authority the authority whose row should be selected 831 * @return the SyncStatusInfo for the authority, or null if none exists 832 */ 833 public SyncStatusInfo getStatusByAuthority(String authority) { 834 synchronized (mAuthorities) { 835 SyncStatusInfo best = null; 836 final int N = mSyncStatus.size(); 837 for (int i=0; i<N; i++) { 838 SyncStatusInfo cur = mSyncStatus.get(i); 839 AuthorityInfo ainfo = mAuthorities.get(cur.authorityId); 840 if (ainfo != null && ainfo.authority.equals(authority)) { 841 if (best == null) { 842 best = cur; 843 } else if (best.lastSuccessTime > cur.lastSuccessTime) { 844 best = cur; 845 } 846 } 847 } 848 return best; 849 } 850 } 851 852 /** 853 * Return true if the pending status is true of any matching authorities. 854 */ 855 public boolean isAuthorityPending(Account account, String authority) { 856 synchronized (mAuthorities) { 857 final int N = mSyncStatus.size(); 858 for (int i=0; i<N; i++) { 859 SyncStatusInfo cur = mSyncStatus.get(i); 860 AuthorityInfo ainfo = mAuthorities.get(cur.authorityId); 861 if (ainfo == null) { 862 continue; 863 } 864 if (account != null && !ainfo.account.equals(account)) { 865 continue; 866 } 867 if (ainfo.authority.equals(authority) && cur.pending) { 868 return true; 869 } 870 } 871 return false; 872 } 873 } 874 875 /** 876 * Return an array of the current sync status for all authorities. Note 877 * that the objects inside the array are the real, live status objects, 878 * so be careful what you do with them. 879 */ 880 public ArrayList<SyncHistoryItem> getSyncHistory() { 881 synchronized (mAuthorities) { 882 final int N = mSyncHistory.size(); 883 ArrayList<SyncHistoryItem> items = new ArrayList<SyncHistoryItem>(N); 884 for (int i=0; i<N; i++) { 885 items.add(mSyncHistory.get(i)); 886 } 887 return items; 888 } 889 } 890 891 /** 892 * Return an array of the current per-day statistics. Note 893 * that the objects inside the array are the real, live status objects, 894 * so be careful what you do with them. 895 */ 896 public DayStats[] getDayStatistics() { 897 synchronized (mAuthorities) { 898 DayStats[] ds = new DayStats[mDayStats.length]; 899 System.arraycopy(mDayStats, 0, ds, 0, ds.length); 900 return ds; 901 } 902 } 903 904 /** 905 * If sync is failing for any of the provider/accounts then determine the time at which it 906 * started failing and return the earliest time over all the provider/accounts. If none are 907 * failing then return 0. 908 */ 909 public long getInitialSyncFailureTime() { 910 synchronized (mAuthorities) { 911 if (!mListenForTickles) { 912 return 0; 913 } 914 915 long oldest = 0; 916 int i = mSyncStatus.size(); 917 while (i > 0) { 918 i--; 919 SyncStatusInfo stats = mSyncStatus.valueAt(i); 920 AuthorityInfo authority = mAuthorities.get(stats.authorityId); 921 if (authority != null && authority.enabled) { 922 if (oldest == 0 || stats.initialFailureTime < oldest) { 923 oldest = stats.initialFailureTime; 924 } 925 } 926 } 927 928 return oldest; 929 } 930 } 931 932 private int getCurrentDayLocked() { 933 mCal.setTimeInMillis(System.currentTimeMillis()); 934 final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR); 935 if (mYear != mCal.get(Calendar.YEAR)) { 936 mYear = mCal.get(Calendar.YEAR); 937 mCal.clear(); 938 mCal.set(Calendar.YEAR, mYear); 939 mYearInDays = (int)(mCal.getTimeInMillis()/86400000); 940 } 941 return dayOfYear + mYearInDays; 942 } 943 944 /** 945 * Retrieve an authority, returning null if one does not exist. 946 * 947 * @param accountName The name of the account for the authority. 948 * @param authorityName The name of the authority itself. 949 * @param tag If non-null, this will be used in a log message if the 950 * requested authority does not exist. 951 */ 952 private AuthorityInfo getAuthorityLocked(Account accountName, String authorityName, 953 String tag) { 954 AccountInfo account = mAccounts.get(accountName); 955 if (account == null) { 956 if (tag != null) { 957 Log.w(TAG, tag + ": unknown account " + accountName); 958 } 959 return null; 960 } 961 AuthorityInfo authority = account.authorities.get(authorityName); 962 if (authority == null) { 963 if (tag != null) { 964 Log.w(TAG, tag + ": unknown authority " + authorityName); 965 } 966 return null; 967 } 968 969 return authority; 970 } 971 972 private AuthorityInfo getOrCreateAuthorityLocked(Account accountName, 973 String authorityName, int ident, boolean doWrite) { 974 AccountInfo account = mAccounts.get(accountName); 975 if (account == null) { 976 account = new AccountInfo(accountName); 977 mAccounts.put(accountName, account); 978 } 979 AuthorityInfo authority = account.authorities.get(authorityName); 980 if (authority == null) { 981 if (ident < 0) { 982 // Look for a new identifier for this authority. 983 final int N = mAuthorities.size(); 984 ident = 0; 985 for (int i=0; i<N; i++) { 986 if (mAuthorities.valueAt(i).ident > ident) { 987 break; 988 } 989 ident++; 990 } 991 } 992 authority = new AuthorityInfo(accountName, authorityName, ident); 993 account.authorities.put(authorityName, authority); 994 mAuthorities.put(ident, authority); 995 if (doWrite) { 996 writeAccountInfoLocked(); 997 } 998 } 999 1000 return authority; 1001 } 1002 1003 private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) { 1004 SyncStatusInfo status = mSyncStatus.get(authorityId); 1005 if (status == null) { 1006 status = new SyncStatusInfo(authorityId); 1007 mSyncStatus.put(authorityId, status); 1008 } 1009 return status; 1010 } 1011 1012 public void writeAllState() { 1013 synchronized (mAuthorities) { 1014 // Account info is always written so no need to do it here. 1015 1016 if (mNumPendingFinished > 0) { 1017 // Only write these if they are out of date. 1018 writePendingOperationsLocked(); 1019 } 1020 1021 // Just always write these... they are likely out of date. 1022 writeStatusLocked(); 1023 writeStatisticsLocked(); 1024 } 1025 } 1026 1027 /** 1028 * Read all account information back in to the initial engine state. 1029 */ 1030 private void readAccountInfoLocked() { 1031 FileInputStream fis = null; 1032 try { 1033 fis = mAccountInfoFile.openRead(); 1034 if (DEBUG_FILE) Log.v(TAG, "Reading " + mAccountInfoFile.getBaseFile()); 1035 XmlPullParser parser = Xml.newPullParser(); 1036 parser.setInput(fis, null); 1037 int eventType = parser.getEventType(); 1038 while (eventType != XmlPullParser.START_TAG) { 1039 eventType = parser.next(); 1040 } 1041 String tagName = parser.getName(); 1042 if ("accounts".equals(tagName)) { 1043 String listen = parser.getAttributeValue( 1044 null, "listen-for-tickles"); 1045 mListenForTickles = listen == null 1046 || Boolean.parseBoolean(listen); 1047 eventType = parser.next(); 1048 do { 1049 if (eventType == XmlPullParser.START_TAG 1050 && parser.getDepth() == 2) { 1051 tagName = parser.getName(); 1052 if ("authority".equals(tagName)) { 1053 int id = -1; 1054 try { 1055 id = Integer.parseInt(parser.getAttributeValue( 1056 null, "id")); 1057 } catch (NumberFormatException e) { 1058 } catch (NullPointerException e) { 1059 } 1060 if (id >= 0) { 1061 String accountName = parser.getAttributeValue( 1062 null, "account"); 1063 String accountType = parser.getAttributeValue( 1064 null, "type"); 1065 if (accountType == null) { 1066 accountType = "com.google.GAIA"; 1067 } 1068 String authorityName = parser.getAttributeValue( 1069 null, "authority"); 1070 String enabled = parser.getAttributeValue( 1071 null, "enabled"); 1072 AuthorityInfo authority = mAuthorities.get(id); 1073 if (DEBUG_FILE) Log.v(TAG, "Adding authority: account=" 1074 + accountName + " auth=" + authorityName 1075 + " enabled=" + enabled); 1076 if (authority == null) { 1077 if (DEBUG_FILE) Log.v(TAG, "Creating entry"); 1078 authority = getOrCreateAuthorityLocked( 1079 new Account(accountName, accountType), 1080 authorityName, id, false); 1081 } 1082 if (authority != null) { 1083 authority.enabled = enabled == null 1084 || Boolean.parseBoolean(enabled); 1085 } else { 1086 Log.w(TAG, "Failure adding authority: account=" 1087 + accountName + " auth=" + authorityName 1088 + " enabled=" + enabled); 1089 } 1090 } 1091 } 1092 } 1093 eventType = parser.next(); 1094 } while (eventType != XmlPullParser.END_DOCUMENT); 1095 } 1096 } catch (XmlPullParserException e) { 1097 Log.w(TAG, "Error reading accounts", e); 1098 } catch (java.io.IOException e) { 1099 if (fis == null) Log.i(TAG, "No initial accounts"); 1100 else Log.w(TAG, "Error reading accounts", e); 1101 } finally { 1102 if (fis != null) { 1103 try { 1104 fis.close(); 1105 } catch (java.io.IOException e1) { 1106 } 1107 } 1108 } 1109 } 1110 1111 /** 1112 * Write all account information to the account file. 1113 */ 1114 private void writeAccountInfoLocked() { 1115 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mAccountInfoFile.getBaseFile()); 1116 FileOutputStream fos = null; 1117 1118 try { 1119 fos = mAccountInfoFile.startWrite(); 1120 XmlSerializer out = new FastXmlSerializer(); 1121 out.setOutput(fos, "utf-8"); 1122 out.startDocument(null, true); 1123 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 1124 1125 out.startTag(null, "accounts"); 1126 if (!mListenForTickles) { 1127 out.attribute(null, "listen-for-tickles", "false"); 1128 } 1129 1130 final int N = mAuthorities.size(); 1131 for (int i=0; i<N; i++) { 1132 AuthorityInfo authority = mAuthorities.get(i); 1133 out.startTag(null, "authority"); 1134 out.attribute(null, "id", Integer.toString(authority.ident)); 1135 out.attribute(null, "account", authority.account.mName); 1136 out.attribute(null, "type", authority.account.mType); 1137 out.attribute(null, "authority", authority.authority); 1138 if (!authority.enabled) { 1139 out.attribute(null, "enabled", "false"); 1140 } 1141 out.endTag(null, "authority"); 1142 } 1143 1144 out.endTag(null, "accounts"); 1145 1146 out.endDocument(); 1147 1148 mAccountInfoFile.finishWrite(fos); 1149 } catch (java.io.IOException e1) { 1150 Log.w(TAG, "Error writing accounts", e1); 1151 if (fos != null) { 1152 mAccountInfoFile.failWrite(fos); 1153 } 1154 } 1155 } 1156 1157 static int getIntColumn(Cursor c, String name) { 1158 return c.getInt(c.getColumnIndex(name)); 1159 } 1160 1161 static long getLongColumn(Cursor c, String name) { 1162 return c.getLong(c.getColumnIndex(name)); 1163 } 1164 1165 /** 1166 * Load sync engine state from the old syncmanager database, and then 1167 * erase it. Note that we don't deal with pending operations, active 1168 * sync, or history. 1169 */ 1170 private void readLegacyAccountInfoLocked() { 1171 // Look for old database to initialize from. 1172 File file = mContext.getDatabasePath("syncmanager.db"); 1173 if (!file.exists()) { 1174 return; 1175 } 1176 String path = file.getPath(); 1177 SQLiteDatabase db = null; 1178 try { 1179 db = SQLiteDatabase.openDatabase(path, null, 1180 SQLiteDatabase.OPEN_READONLY); 1181 } catch (SQLiteException e) { 1182 } 1183 1184 if (db != null) { 1185 final boolean hasType = db.getVersion() >= 11; 1186 1187 // Copy in all of the status information, as well as accounts. 1188 if (DEBUG_FILE) Log.v(TAG, "Reading legacy sync accounts db"); 1189 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 1190 qb.setTables("stats, status"); 1191 HashMap<String,String> map = new HashMap<String,String>(); 1192 map.put("_id", "status._id as _id"); 1193 map.put("account", "stats.account as account"); 1194 if (hasType) { 1195 map.put("account_type", "stats.account_type as account_type"); 1196 } 1197 map.put("authority", "stats.authority as authority"); 1198 map.put("totalElapsedTime", "totalElapsedTime"); 1199 map.put("numSyncs", "numSyncs"); 1200 map.put("numSourceLocal", "numSourceLocal"); 1201 map.put("numSourcePoll", "numSourcePoll"); 1202 map.put("numSourceServer", "numSourceServer"); 1203 map.put("numSourceUser", "numSourceUser"); 1204 map.put("lastSuccessSource", "lastSuccessSource"); 1205 map.put("lastSuccessTime", "lastSuccessTime"); 1206 map.put("lastFailureSource", "lastFailureSource"); 1207 map.put("lastFailureTime", "lastFailureTime"); 1208 map.put("lastFailureMesg", "lastFailureMesg"); 1209 map.put("pending", "pending"); 1210 qb.setProjectionMap(map); 1211 qb.appendWhere("stats._id = status.stats_id"); 1212 Cursor c = qb.query(db, null, null, null, null, null, null); 1213 while (c.moveToNext()) { 1214 String accountName = c.getString(c.getColumnIndex("account")); 1215 String accountType = hasType 1216 ? c.getString(c.getColumnIndex("account_type")) : null; 1217 if (accountType == null) { 1218 accountType = "com.google.GAIA"; 1219 } 1220 String authorityName = c.getString(c.getColumnIndex("authority")); 1221 AuthorityInfo authority = this.getOrCreateAuthorityLocked( 1222 new Account(accountName, accountType), 1223 authorityName, -1, false); 1224 if (authority != null) { 1225 int i = mSyncStatus.size(); 1226 boolean found = false; 1227 SyncStatusInfo st = null; 1228 while (i > 0) { 1229 i--; 1230 st = mSyncStatus.get(i); 1231 if (st.authorityId == authority.ident) { 1232 found = true; 1233 break; 1234 } 1235 } 1236 if (!found) { 1237 st = new SyncStatusInfo(authority.ident); 1238 mSyncStatus.put(authority.ident, st); 1239 } 1240 st.totalElapsedTime = getLongColumn(c, "totalElapsedTime"); 1241 st.numSyncs = getIntColumn(c, "numSyncs"); 1242 st.numSourceLocal = getIntColumn(c, "numSourceLocal"); 1243 st.numSourcePoll = getIntColumn(c, "numSourcePoll"); 1244 st.numSourceServer = getIntColumn(c, "numSourceServer"); 1245 st.numSourceUser = getIntColumn(c, "numSourceUser"); 1246 st.lastSuccessSource = getIntColumn(c, "lastSuccessSource"); 1247 st.lastSuccessTime = getLongColumn(c, "lastSuccessTime"); 1248 st.lastFailureSource = getIntColumn(c, "lastFailureSource"); 1249 st.lastFailureTime = getLongColumn(c, "lastFailureTime"); 1250 st.lastFailureMesg = c.getString(c.getColumnIndex("lastFailureMesg")); 1251 st.pending = getIntColumn(c, "pending") != 0; 1252 } 1253 } 1254 1255 c.close(); 1256 1257 // Retrieve the settings. 1258 qb = new SQLiteQueryBuilder(); 1259 qb.setTables("settings"); 1260 c = qb.query(db, null, null, null, null, null, null); 1261 while (c.moveToNext()) { 1262 String name = c.getString(c.getColumnIndex("name")); 1263 String value = c.getString(c.getColumnIndex("value")); 1264 if (name == null) continue; 1265 if (name.equals("listen_for_tickles")) { 1266 setListenForNetworkTickles(value == null 1267 || Boolean.parseBoolean(value)); 1268 } else if (name.startsWith("sync_provider_")) { 1269 String provider = name.substring("sync_provider_".length(), 1270 name.length()); 1271 setSyncProviderAutomatically(null, provider, 1272 value == null || Boolean.parseBoolean(value)); 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