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