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