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