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