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