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