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