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