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