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