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 getAuthorityCount()914 public int getAuthorityCount() { 915 synchronized (mAuthorities) { 916 return mAuthorities.size(); 917 } 918 } 919 getAuthority(int authorityId)920 public AuthorityInfo getAuthority(int authorityId) { 921 synchronized (mAuthorities) { 922 return mAuthorities.get(authorityId); 923 } 924 } 925 926 /** 927 * Returns true if there is currently a sync operation being actively processed for the given 928 * target. 929 */ isSyncActive(EndPoint info)930 public boolean isSyncActive(EndPoint info) { 931 synchronized (mAuthorities) { 932 for (SyncInfo syncInfo : getCurrentSyncs(info.userId)) { 933 AuthorityInfo ainfo = getAuthority(syncInfo.authorityId); 934 if (ainfo != null && ainfo.target.matchesSpec(info)) { 935 return true; 936 } 937 } 938 } 939 return false; 940 } 941 markPending(EndPoint info, boolean pendingValue)942 public void markPending(EndPoint info, boolean pendingValue) { 943 synchronized (mAuthorities) { 944 AuthorityInfo authority = getOrCreateAuthorityLocked(info, 945 -1 /* desired identifier */, 946 true /* write accounts to storage */); 947 if (authority == null) { 948 return; 949 } 950 SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident); 951 status.pending = pendingValue; 952 } 953 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING); 954 } 955 956 /** 957 * Called when the set of account has changed, given the new array of 958 * active accounts. 959 */ doDatabaseCleanup(Account[] accounts, int userId)960 public void doDatabaseCleanup(Account[] accounts, int userId) { 961 synchronized (mAuthorities) { 962 if (Log.isLoggable(TAG, Log.VERBOSE)) { 963 Slog.v(TAG, "Updating for new accounts..."); 964 } 965 SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>(); 966 Iterator<AccountInfo> accIt = mAccounts.values().iterator(); 967 while (accIt.hasNext()) { 968 AccountInfo acc = accIt.next(); 969 if (!ArrayUtils.contains(accounts, acc.accountAndUser.account) 970 && acc.accountAndUser.userId == userId) { 971 // This account no longer exists... 972 if (Log.isLoggable(TAG, Log.VERBOSE)) { 973 Slog.v(TAG, "Account removed: " + acc.accountAndUser); 974 } 975 for (AuthorityInfo auth : acc.authorities.values()) { 976 removing.put(auth.ident, auth); 977 } 978 accIt.remove(); 979 } 980 } 981 982 // Clean out all data structures. 983 int i = removing.size(); 984 if (i > 0) { 985 while (i > 0) { 986 i--; 987 int ident = removing.keyAt(i); 988 AuthorityInfo auth = removing.valueAt(i); 989 if (mAuthorityRemovedListener != null) { 990 mAuthorityRemovedListener.onAuthorityRemoved(auth.target); 991 } 992 mAuthorities.remove(ident); 993 int j = mSyncStatus.size(); 994 while (j > 0) { 995 j--; 996 if (mSyncStatus.keyAt(j) == ident) { 997 mSyncStatus.remove(mSyncStatus.keyAt(j)); 998 } 999 } 1000 j = mSyncHistory.size(); 1001 while (j > 0) { 1002 j--; 1003 if (mSyncHistory.get(j).authorityId == ident) { 1004 mSyncHistory.remove(j); 1005 } 1006 } 1007 } 1008 writeAccountInfoLocked(); 1009 writeStatusLocked(); 1010 writeStatisticsLocked(); 1011 } 1012 } 1013 } 1014 1015 /** 1016 * Called when a sync is starting. Supply a valid ActiveSyncContext with information 1017 * about the sync. 1018 */ addActiveSync(SyncManager.ActiveSyncContext activeSyncContext)1019 public SyncInfo addActiveSync(SyncManager.ActiveSyncContext activeSyncContext) { 1020 final SyncInfo syncInfo; 1021 synchronized (mAuthorities) { 1022 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1023 Slog.v(TAG, "setActiveSync: account=" 1024 + " auth=" + activeSyncContext.mSyncOperation.target 1025 + " src=" + activeSyncContext.mSyncOperation.syncSource 1026 + " extras=" + activeSyncContext.mSyncOperation.extras); 1027 } 1028 final EndPoint info = activeSyncContext.mSyncOperation.target; 1029 AuthorityInfo authorityInfo = getOrCreateAuthorityLocked( 1030 info, 1031 -1 /* assign a new identifier if creating a new target */, 1032 true /* write to storage if this results in a change */); 1033 syncInfo = new SyncInfo( 1034 authorityInfo.ident, 1035 authorityInfo.target.account, 1036 authorityInfo.target.provider, 1037 activeSyncContext.mStartTime); 1038 getCurrentSyncs(authorityInfo.target.userId).add(syncInfo); 1039 } 1040 reportActiveChange(); 1041 return syncInfo; 1042 } 1043 1044 /** 1045 * Called to indicate that a previously active sync is no longer active. 1046 */ removeActiveSync(SyncInfo syncInfo, int userId)1047 public void removeActiveSync(SyncInfo syncInfo, int userId) { 1048 synchronized (mAuthorities) { 1049 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1050 Slog.v(TAG, "removeActiveSync: account=" + syncInfo.account 1051 + " user=" + userId 1052 + " auth=" + syncInfo.authority); 1053 } 1054 getCurrentSyncs(userId).remove(syncInfo); 1055 } 1056 1057 reportActiveChange(); 1058 } 1059 1060 /** 1061 * To allow others to send active change reports, to poke clients. 1062 */ reportActiveChange()1063 public void reportActiveChange() { 1064 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE); 1065 } 1066 1067 /** 1068 * Note that sync has started for the given operation. 1069 */ insertStartSyncEvent(SyncOperation op, long now)1070 public long insertStartSyncEvent(SyncOperation op, long now) { 1071 long id; 1072 synchronized (mAuthorities) { 1073 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1074 Slog.v(TAG, "insertStartSyncEvent: " + op); 1075 } 1076 AuthorityInfo authority = getAuthorityLocked(op.target, "insertStartSyncEvent"); 1077 if (authority == null) { 1078 return -1; 1079 } 1080 SyncHistoryItem item = new SyncHistoryItem(); 1081 item.initialization = op.isInitialization(); 1082 item.authorityId = authority.ident; 1083 item.historyId = mNextHistoryId++; 1084 if (mNextHistoryId < 0) mNextHistoryId = 0; 1085 item.eventTime = now; 1086 item.source = op.syncSource; 1087 item.reason = op.reason; 1088 item.extras = op.extras; 1089 item.event = EVENT_START; 1090 mSyncHistory.add(0, item); 1091 while (mSyncHistory.size() > MAX_HISTORY) { 1092 mSyncHistory.remove(mSyncHistory.size()-1); 1093 } 1094 id = item.historyId; 1095 if (Log.isLoggable(TAG, Log.VERBOSE)) Slog.v(TAG, "returning historyId " + id); 1096 } 1097 1098 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS); 1099 return id; 1100 } 1101 stopSyncEvent(long historyId, long elapsedTime, String resultMessage, long downstreamActivity, long upstreamActivity)1102 public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage, 1103 long downstreamActivity, long upstreamActivity) { 1104 synchronized (mAuthorities) { 1105 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1106 Slog.v(TAG, "stopSyncEvent: historyId=" + historyId); 1107 } 1108 SyncHistoryItem item = null; 1109 int i = mSyncHistory.size(); 1110 while (i > 0) { 1111 i--; 1112 item = mSyncHistory.get(i); 1113 if (item.historyId == historyId) { 1114 break; 1115 } 1116 item = null; 1117 } 1118 1119 if (item == null) { 1120 Slog.w(TAG, "stopSyncEvent: no history for id " + historyId); 1121 return; 1122 } 1123 1124 item.elapsedTime = elapsedTime; 1125 item.event = EVENT_STOP; 1126 item.mesg = resultMessage; 1127 item.downstreamActivity = downstreamActivity; 1128 item.upstreamActivity = upstreamActivity; 1129 1130 SyncStatusInfo status = getOrCreateSyncStatusLocked(item.authorityId); 1131 1132 status.numSyncs++; 1133 status.totalElapsedTime += elapsedTime; 1134 switch (item.source) { 1135 case SOURCE_LOCAL: 1136 status.numSourceLocal++; 1137 break; 1138 case SOURCE_POLL: 1139 status.numSourcePoll++; 1140 break; 1141 case SOURCE_USER: 1142 status.numSourceUser++; 1143 break; 1144 case SOURCE_SERVER: 1145 status.numSourceServer++; 1146 break; 1147 case SOURCE_PERIODIC: 1148 status.numSourcePeriodic++; 1149 break; 1150 } 1151 1152 boolean writeStatisticsNow = false; 1153 int day = getCurrentDayLocked(); 1154 if (mDayStats[0] == null) { 1155 mDayStats[0] = new DayStats(day); 1156 } else if (day != mDayStats[0].day) { 1157 System.arraycopy(mDayStats, 0, mDayStats, 1, mDayStats.length-1); 1158 mDayStats[0] = new DayStats(day); 1159 writeStatisticsNow = true; 1160 } else if (mDayStats[0] == null) { 1161 } 1162 final DayStats ds = mDayStats[0]; 1163 1164 final long lastSyncTime = (item.eventTime + elapsedTime); 1165 boolean writeStatusNow = false; 1166 if (MESG_SUCCESS.equals(resultMessage)) { 1167 // - if successful, update the successful columns 1168 if (status.lastSuccessTime == 0 || status.lastFailureTime != 0) { 1169 writeStatusNow = true; 1170 } 1171 status.lastSuccessTime = lastSyncTime; 1172 status.lastSuccessSource = item.source; 1173 status.lastFailureTime = 0; 1174 status.lastFailureSource = -1; 1175 status.lastFailureMesg = null; 1176 status.initialFailureTime = 0; 1177 ds.successCount++; 1178 ds.successTime += elapsedTime; 1179 } else if (!MESG_CANCELED.equals(resultMessage)) { 1180 if (status.lastFailureTime == 0) { 1181 writeStatusNow = true; 1182 } 1183 status.lastFailureTime = lastSyncTime; 1184 status.lastFailureSource = item.source; 1185 status.lastFailureMesg = resultMessage; 1186 if (status.initialFailureTime == 0) { 1187 status.initialFailureTime = lastSyncTime; 1188 } 1189 ds.failureCount++; 1190 ds.failureTime += elapsedTime; 1191 } 1192 final StringBuilder event = new StringBuilder(); 1193 event.append("" + resultMessage + " Source=" + SyncStorageEngine.SOURCES[item.source] 1194 + " Elapsed="); 1195 SyncManager.formatDurationHMS(event, elapsedTime); 1196 event.append(" Reason="); 1197 event.append(SyncOperation.reasonToString(null, item.reason)); 1198 event.append(" Extras="); 1199 SyncOperation.extrasToStringBuilder(item.extras, event); 1200 1201 status.addEvent(event.toString()); 1202 1203 if (writeStatusNow) { 1204 writeStatusLocked(); 1205 } else if (!hasMessages(MSG_WRITE_STATUS)) { 1206 sendMessageDelayed(obtainMessage(MSG_WRITE_STATUS), 1207 WRITE_STATUS_DELAY); 1208 } 1209 if (writeStatisticsNow) { 1210 writeStatisticsLocked(); 1211 } else if (!hasMessages(MSG_WRITE_STATISTICS)) { 1212 sendMessageDelayed(obtainMessage(MSG_WRITE_STATISTICS), 1213 WRITE_STATISTICS_DELAY); 1214 } 1215 } 1216 1217 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS); 1218 } 1219 1220 /** 1221 * Return a list of the currently active syncs. Note that the returned 1222 * items are the real, live active sync objects, so be careful what you do 1223 * with it. 1224 */ getCurrentSyncs(int userId)1225 private List<SyncInfo> getCurrentSyncs(int userId) { 1226 synchronized (mAuthorities) { 1227 return getCurrentSyncsLocked(userId); 1228 } 1229 } 1230 1231 /** 1232 * @param userId Id of user to return current sync info. 1233 * @param canAccessAccounts Determines whether to redact Account information from the result. 1234 * @return a copy of the current syncs data structure. Will not return null. 1235 */ getCurrentSyncsCopy(int userId, boolean canAccessAccounts)1236 public List<SyncInfo> getCurrentSyncsCopy(int userId, boolean canAccessAccounts) { 1237 synchronized (mAuthorities) { 1238 final List<SyncInfo> syncs = getCurrentSyncsLocked(userId); 1239 final List<SyncInfo> syncsCopy = new ArrayList<SyncInfo>(); 1240 for (SyncInfo sync : syncs) { 1241 SyncInfo copy; 1242 if (!canAccessAccounts) { 1243 copy = SyncInfo.createAccountRedacted( 1244 sync.authorityId, sync.authority, sync.startTime); 1245 } else { 1246 copy = new SyncInfo(sync); 1247 } 1248 syncsCopy.add(copy); 1249 } 1250 return syncsCopy; 1251 } 1252 } 1253 getCurrentSyncsLocked(int userId)1254 private List<SyncInfo> getCurrentSyncsLocked(int userId) { 1255 ArrayList<SyncInfo> syncs = mCurrentSyncs.get(userId); 1256 if (syncs == null) { 1257 syncs = new ArrayList<SyncInfo>(); 1258 mCurrentSyncs.put(userId, syncs); 1259 } 1260 return syncs; 1261 } 1262 1263 /** 1264 * Return a copy of the specified target with the corresponding sync status 1265 */ getCopyOfAuthorityWithSyncStatus(EndPoint info)1266 public Pair<AuthorityInfo, SyncStatusInfo> getCopyOfAuthorityWithSyncStatus(EndPoint info) { 1267 synchronized (mAuthorities) { 1268 AuthorityInfo authorityInfo = getOrCreateAuthorityLocked(info, 1269 -1 /* assign a new identifier if creating a new target */, 1270 true /* write to storage if this results in a change */); 1271 return createCopyPairOfAuthorityWithSyncStatusLocked(authorityInfo); 1272 } 1273 } 1274 1275 /** 1276 * Returns the status that matches the target. 1277 * 1278 * @param info the endpoint target we are querying status info for. 1279 * @return the SyncStatusInfo for the endpoint. 1280 */ getStatusByAuthority(EndPoint info)1281 public SyncStatusInfo getStatusByAuthority(EndPoint info) { 1282 if (info.account == null || info.provider == null) { 1283 return null; 1284 } 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 && ainfo.target.matchesSpec(info)) { 1292 return cur; 1293 } 1294 } 1295 return null; 1296 } 1297 } 1298 1299 /** Return true if the pending status is true of any matching authorities. */ isSyncPending(EndPoint info)1300 public boolean isSyncPending(EndPoint info) { 1301 synchronized (mAuthorities) { 1302 final int N = mSyncStatus.size(); 1303 for (int i = 0; i < N; i++) { 1304 SyncStatusInfo cur = mSyncStatus.valueAt(i); 1305 AuthorityInfo ainfo = mAuthorities.get(cur.authorityId); 1306 if (ainfo == null) { 1307 continue; 1308 } 1309 if (!ainfo.target.matchesSpec(info)) { 1310 continue; 1311 } 1312 if (cur.pending) { 1313 return true; 1314 } 1315 } 1316 return false; 1317 } 1318 } 1319 1320 /** 1321 * Return an array of the current sync status for all authorities. Note 1322 * that the objects inside the array are the real, live status objects, 1323 * so be careful what you do with them. 1324 */ getSyncHistory()1325 public ArrayList<SyncHistoryItem> getSyncHistory() { 1326 synchronized (mAuthorities) { 1327 final int N = mSyncHistory.size(); 1328 ArrayList<SyncHistoryItem> items = new ArrayList<SyncHistoryItem>(N); 1329 for (int i=0; i<N; i++) { 1330 items.add(mSyncHistory.get(i)); 1331 } 1332 return items; 1333 } 1334 } 1335 1336 /** 1337 * Return an array of the current per-day statistics. Note 1338 * that the objects inside the array are the real, live status objects, 1339 * so be careful what you do with them. 1340 */ getDayStatistics()1341 public DayStats[] getDayStatistics() { 1342 synchronized (mAuthorities) { 1343 DayStats[] ds = new DayStats[mDayStats.length]; 1344 System.arraycopy(mDayStats, 0, ds, 0, ds.length); 1345 return ds; 1346 } 1347 } 1348 createCopyPairOfAuthorityWithSyncStatusLocked( AuthorityInfo authorityInfo)1349 private Pair<AuthorityInfo, SyncStatusInfo> createCopyPairOfAuthorityWithSyncStatusLocked( 1350 AuthorityInfo authorityInfo) { 1351 SyncStatusInfo syncStatusInfo = getOrCreateSyncStatusLocked(authorityInfo.ident); 1352 return Pair.create(new AuthorityInfo(authorityInfo), new SyncStatusInfo(syncStatusInfo)); 1353 } 1354 getCurrentDayLocked()1355 private int getCurrentDayLocked() { 1356 mCal.setTimeInMillis(System.currentTimeMillis()); 1357 final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR); 1358 if (mYear != mCal.get(Calendar.YEAR)) { 1359 mYear = mCal.get(Calendar.YEAR); 1360 mCal.clear(); 1361 mCal.set(Calendar.YEAR, mYear); 1362 mYearInDays = (int)(mCal.getTimeInMillis()/86400000); 1363 } 1364 return dayOfYear + mYearInDays; 1365 } 1366 1367 /** 1368 * Retrieve a target's full info, returning null if one does not exist. 1369 * 1370 * @param info info of the target to look up. 1371 * @param tag If non-null, this will be used in a log message if the 1372 * requested target does not exist. 1373 */ getAuthorityLocked(EndPoint info, String tag)1374 private AuthorityInfo getAuthorityLocked(EndPoint info, String tag) { 1375 AccountAndUser au = new AccountAndUser(info.account, info.userId); 1376 AccountInfo accountInfo = mAccounts.get(au); 1377 if (accountInfo == null) { 1378 if (tag != null) { 1379 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1380 Slog.v(TAG, tag + ": unknown account " + au); 1381 } 1382 } 1383 return null; 1384 } 1385 AuthorityInfo authority = accountInfo.authorities.get(info.provider); 1386 if (authority == null) { 1387 if (tag != null) { 1388 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1389 Slog.v(TAG, tag + ": unknown provider " + info.provider); 1390 } 1391 } 1392 return null; 1393 } 1394 return authority; 1395 } 1396 1397 /** 1398 * @param info info identifying target. 1399 * @param ident unique identifier for target. -1 for none. 1400 * @param doWrite if true, update the accounts.xml file on the disk. 1401 * @return the authority that corresponds to the provided sync target, creating it if none 1402 * exists. 1403 */ getOrCreateAuthorityLocked(EndPoint info, int ident, boolean doWrite)1404 private AuthorityInfo getOrCreateAuthorityLocked(EndPoint info, int ident, boolean doWrite) { 1405 AuthorityInfo authority = null; 1406 AccountAndUser au = new AccountAndUser(info.account, info.userId); 1407 AccountInfo account = mAccounts.get(au); 1408 if (account == null) { 1409 account = new AccountInfo(au); 1410 mAccounts.put(au, account); 1411 } 1412 authority = account.authorities.get(info.provider); 1413 if (authority == null) { 1414 authority = createAuthorityLocked(info, ident, doWrite); 1415 account.authorities.put(info.provider, authority); 1416 } 1417 return authority; 1418 } 1419 createAuthorityLocked(EndPoint info, int ident, boolean doWrite)1420 private AuthorityInfo createAuthorityLocked(EndPoint info, int ident, boolean doWrite) { 1421 AuthorityInfo authority; 1422 if (ident < 0) { 1423 ident = mNextAuthorityId; 1424 mNextAuthorityId++; 1425 doWrite = true; 1426 } 1427 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1428 Slog.v(TAG, "created a new AuthorityInfo for " + info); 1429 } 1430 authority = new AuthorityInfo(info, ident); 1431 mAuthorities.put(ident, authority); 1432 if (doWrite) { 1433 writeAccountInfoLocked(); 1434 } 1435 return authority; 1436 } 1437 removeAuthority(EndPoint info)1438 public void removeAuthority(EndPoint info) { 1439 synchronized (mAuthorities) { 1440 removeAuthorityLocked(info.account, info.userId, info.provider, true /* doWrite */); 1441 } 1442 } 1443 1444 1445 /** 1446 * Remove an authority associated with a provider. Needs to be a standalone function for 1447 * backward compatibility. 1448 */ removeAuthorityLocked(Account account, int userId, String authorityName, boolean doWrite)1449 private void removeAuthorityLocked(Account account, int userId, String authorityName, 1450 boolean doWrite) { 1451 AccountInfo accountInfo = mAccounts.get(new AccountAndUser(account, userId)); 1452 if (accountInfo != null) { 1453 final AuthorityInfo authorityInfo = accountInfo.authorities.remove(authorityName); 1454 if (authorityInfo != null) { 1455 if (mAuthorityRemovedListener != null) { 1456 mAuthorityRemovedListener.onAuthorityRemoved(authorityInfo.target); 1457 } 1458 mAuthorities.remove(authorityInfo.ident); 1459 if (doWrite) { 1460 writeAccountInfoLocked(); 1461 } 1462 } 1463 } 1464 } 1465 getOrCreateSyncStatusLocked(int authorityId)1466 private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) { 1467 SyncStatusInfo status = mSyncStatus.get(authorityId); 1468 if (status == null) { 1469 status = new SyncStatusInfo(authorityId); 1470 mSyncStatus.put(authorityId, status); 1471 } 1472 return status; 1473 } 1474 writeAllState()1475 public void writeAllState() { 1476 synchronized (mAuthorities) { 1477 // Account info is always written so no need to do it here. 1478 writeStatusLocked(); 1479 writeStatisticsLocked(); 1480 } 1481 } 1482 shouldGrantSyncAdaptersAccountAccess()1483 public boolean shouldGrantSyncAdaptersAccountAccess() { 1484 return mGrantSyncAdaptersAccountAccess; 1485 } 1486 1487 /** 1488 * public for testing 1489 */ clearAndReadState()1490 public void clearAndReadState() { 1491 synchronized (mAuthorities) { 1492 mAuthorities.clear(); 1493 mAccounts.clear(); 1494 mServices.clear(); 1495 mSyncStatus.clear(); 1496 mSyncHistory.clear(); 1497 1498 readAccountInfoLocked(); 1499 readStatusLocked(); 1500 readStatisticsLocked(); 1501 readAndDeleteLegacyAccountInfoLocked(); 1502 writeAccountInfoLocked(); 1503 writeStatusLocked(); 1504 writeStatisticsLocked(); 1505 } 1506 } 1507 1508 /** 1509 * Read all account information back in to the initial engine state. 1510 */ readAccountInfoLocked()1511 private void readAccountInfoLocked() { 1512 int highestAuthorityId = -1; 1513 FileInputStream fis = null; 1514 try { 1515 fis = mAccountInfoFile.openRead(); 1516 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 1517 Slog.v(TAG_FILE, "Reading " + mAccountInfoFile.getBaseFile()); 1518 } 1519 XmlPullParser parser = Xml.newPullParser(); 1520 parser.setInput(fis, StandardCharsets.UTF_8.name()); 1521 int eventType = parser.getEventType(); 1522 while (eventType != XmlPullParser.START_TAG && 1523 eventType != XmlPullParser.END_DOCUMENT) { 1524 eventType = parser.next(); 1525 } 1526 if (eventType == XmlPullParser.END_DOCUMENT) { 1527 Slog.i(TAG, "No initial accounts"); 1528 return; 1529 } 1530 1531 String tagName = parser.getName(); 1532 if ("accounts".equals(tagName)) { 1533 String listen = parser.getAttributeValue(null, XML_ATTR_LISTEN_FOR_TICKLES); 1534 String versionString = parser.getAttributeValue(null, "version"); 1535 int version; 1536 try { 1537 version = (versionString == null) ? 0 : Integer.parseInt(versionString); 1538 } catch (NumberFormatException e) { 1539 version = 0; 1540 } 1541 1542 if (version < 3) { 1543 mGrantSyncAdaptersAccountAccess = true; 1544 } 1545 1546 String nextIdString = parser.getAttributeValue(null, XML_ATTR_NEXT_AUTHORITY_ID); 1547 try { 1548 int id = (nextIdString == null) ? 0 : Integer.parseInt(nextIdString); 1549 mNextAuthorityId = Math.max(mNextAuthorityId, id); 1550 } catch (NumberFormatException e) { 1551 // don't care 1552 } 1553 String offsetString = parser.getAttributeValue(null, XML_ATTR_SYNC_RANDOM_OFFSET); 1554 try { 1555 mSyncRandomOffset = (offsetString == null) ? 0 : Integer.parseInt(offsetString); 1556 } catch (NumberFormatException e) { 1557 mSyncRandomOffset = 0; 1558 } 1559 if (mSyncRandomOffset == 0) { 1560 Random random = new Random(System.currentTimeMillis()); 1561 mSyncRandomOffset = random.nextInt(86400); 1562 } 1563 mMasterSyncAutomatically.put(0, listen == null || Boolean.parseBoolean(listen)); 1564 eventType = parser.next(); 1565 AuthorityInfo authority = null; 1566 PeriodicSync periodicSync = null; 1567 AccountAuthorityValidator validator = new AccountAuthorityValidator(mContext); 1568 do { 1569 if (eventType == XmlPullParser.START_TAG) { 1570 tagName = parser.getName(); 1571 if (parser.getDepth() == 2) { 1572 if ("authority".equals(tagName)) { 1573 authority = parseAuthority(parser, version, validator); 1574 periodicSync = null; 1575 if (authority != null) { 1576 if (authority.ident > highestAuthorityId) { 1577 highestAuthorityId = authority.ident; 1578 } 1579 } else { 1580 EventLog.writeEvent(0x534e4554, "26513719", -1, 1581 "Malformed authority"); 1582 } 1583 } else if (XML_TAG_LISTEN_FOR_TICKLES.equals(tagName)) { 1584 parseListenForTickles(parser); 1585 } 1586 } else if (parser.getDepth() == 3) { 1587 if ("periodicSync".equals(tagName) && authority != null) { 1588 periodicSync = parsePeriodicSync(parser, authority); 1589 } 1590 } else if (parser.getDepth() == 4 && periodicSync != null) { 1591 if ("extra".equals(tagName)) { 1592 parseExtra(parser, periodicSync.extras); 1593 } 1594 } 1595 } 1596 eventType = parser.next(); 1597 } while (eventType != XmlPullParser.END_DOCUMENT); 1598 } 1599 } catch (XmlPullParserException e) { 1600 Slog.w(TAG, "Error reading accounts", e); 1601 return; 1602 } catch (java.io.IOException e) { 1603 if (fis == null) Slog.i(TAG, "No initial accounts"); 1604 else Slog.w(TAG, "Error reading accounts", e); 1605 return; 1606 } finally { 1607 mNextAuthorityId = Math.max(highestAuthorityId + 1, mNextAuthorityId); 1608 if (fis != null) { 1609 try { 1610 fis.close(); 1611 } catch (java.io.IOException e1) { 1612 } 1613 } 1614 } 1615 1616 maybeMigrateSettingsForRenamedAuthorities(); 1617 } 1618 1619 /** 1620 * Ensure the old pending.bin is deleted, as it has been changed to pending.xml. 1621 * pending.xml was used starting in KLP. 1622 * @param syncDir directory where the sync files are located. 1623 */ maybeDeleteLegacyPendingInfoLocked(File syncDir)1624 private void maybeDeleteLegacyPendingInfoLocked(File syncDir) { 1625 File file = new File(syncDir, "pending.bin"); 1626 if (!file.exists()) { 1627 return; 1628 } else { 1629 file.delete(); 1630 } 1631 } 1632 1633 /** 1634 * some authority names have changed. copy over their settings and delete the old ones 1635 * @return true if a change was made 1636 */ maybeMigrateSettingsForRenamedAuthorities()1637 private boolean maybeMigrateSettingsForRenamedAuthorities() { 1638 boolean writeNeeded = false; 1639 1640 ArrayList<AuthorityInfo> authoritiesToRemove = new ArrayList<AuthorityInfo>(); 1641 final int N = mAuthorities.size(); 1642 for (int i = 0; i < N; i++) { 1643 AuthorityInfo authority = mAuthorities.valueAt(i); 1644 // skip this authority if it isn't one of the renamed ones 1645 final String newAuthorityName = sAuthorityRenames.get(authority.target.provider); 1646 if (newAuthorityName == null) { 1647 continue; 1648 } 1649 1650 // remember this authority so we can remove it later. we can't remove it 1651 // now without messing up this loop iteration 1652 authoritiesToRemove.add(authority); 1653 1654 // this authority isn't enabled, no need to copy it to the new authority name since 1655 // the default is "disabled" 1656 if (!authority.enabled) { 1657 continue; 1658 } 1659 1660 // if we already have a record of this new authority then don't copy over the settings 1661 EndPoint newInfo = 1662 new EndPoint(authority.target.account, 1663 newAuthorityName, 1664 authority.target.userId); 1665 if (getAuthorityLocked(newInfo, "cleanup") != null) { 1666 continue; 1667 } 1668 1669 AuthorityInfo newAuthority = 1670 getOrCreateAuthorityLocked(newInfo, -1 /* ident */, false /* doWrite */); 1671 newAuthority.enabled = true; 1672 writeNeeded = true; 1673 } 1674 1675 for (AuthorityInfo authorityInfo : authoritiesToRemove) { 1676 removeAuthorityLocked( 1677 authorityInfo.target.account, 1678 authorityInfo.target.userId, 1679 authorityInfo.target.provider, 1680 false /* doWrite */); 1681 writeNeeded = true; 1682 } 1683 1684 return writeNeeded; 1685 } 1686 parseListenForTickles(XmlPullParser parser)1687 private void parseListenForTickles(XmlPullParser parser) { 1688 String user = parser.getAttributeValue(null, XML_ATTR_USER); 1689 int userId = 0; 1690 try { 1691 userId = Integer.parseInt(user); 1692 } catch (NumberFormatException e) { 1693 Slog.e(TAG, "error parsing the user for listen-for-tickles", e); 1694 } catch (NullPointerException e) { 1695 Slog.e(TAG, "the user in listen-for-tickles is null", e); 1696 } 1697 String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED); 1698 boolean listen = enabled == null || Boolean.parseBoolean(enabled); 1699 mMasterSyncAutomatically.put(userId, listen); 1700 } 1701 parseAuthority(XmlPullParser parser, int version, AccountAuthorityValidator validator)1702 private AuthorityInfo parseAuthority(XmlPullParser parser, int version, 1703 AccountAuthorityValidator validator) { 1704 AuthorityInfo authority = null; 1705 int id = -1; 1706 try { 1707 id = Integer.parseInt(parser.getAttributeValue(null, "id")); 1708 } catch (NumberFormatException e) { 1709 Slog.e(TAG, "error parsing the id of the authority", e); 1710 } catch (NullPointerException e) { 1711 Slog.e(TAG, "the id of the authority is null", e); 1712 } 1713 if (id >= 0) { 1714 String authorityName = parser.getAttributeValue(null, "authority"); 1715 String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED); 1716 String syncable = parser.getAttributeValue(null, "syncable"); 1717 String accountName = parser.getAttributeValue(null, "account"); 1718 String accountType = parser.getAttributeValue(null, "type"); 1719 String user = parser.getAttributeValue(null, XML_ATTR_USER); 1720 String packageName = parser.getAttributeValue(null, "package"); 1721 String className = parser.getAttributeValue(null, "class"); 1722 int userId = user == null ? 0 : Integer.parseInt(user); 1723 if (accountType == null && packageName == null) { 1724 accountType = "com.google"; 1725 syncable = String.valueOf(AuthorityInfo.NOT_INITIALIZED); 1726 } 1727 authority = mAuthorities.get(id); 1728 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 1729 Slog.v(TAG_FILE, "Adding authority:" 1730 + " account=" + accountName 1731 + " accountType=" + accountType 1732 + " auth=" + authorityName 1733 + " package=" + packageName 1734 + " class=" + className 1735 + " user=" + userId 1736 + " enabled=" + enabled 1737 + " syncable=" + syncable); 1738 } 1739 if (authority == null) { 1740 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 1741 Slog.v(TAG_FILE, "Creating authority entry"); 1742 } 1743 if (accountName != null && authorityName != null) { 1744 EndPoint info = new EndPoint( 1745 new Account(accountName, accountType), 1746 authorityName, userId); 1747 if (validator.isAccountValid(info.account, userId) 1748 && validator.isAuthorityValid(authorityName, userId)) { 1749 authority = getOrCreateAuthorityLocked(info, id, false); 1750 // If the version is 0 then we are upgrading from a file format that did not 1751 // know about periodic syncs. In that case don't clear the list since we 1752 // want the default, which is a daily periodic sync. 1753 // Otherwise clear out this default list since we will populate it later 1754 // with 1755 // the periodic sync descriptions that are read from the configuration file. 1756 if (version > 0) { 1757 authority.periodicSyncs.clear(); 1758 } 1759 } else { 1760 EventLog.writeEvent(0x534e4554, "35028827", -1, 1761 "account:" + info.account + " provider:" + authorityName + " user:" 1762 + userId); 1763 } 1764 } 1765 } 1766 if (authority != null) { 1767 authority.enabled = enabled == null || Boolean.parseBoolean(enabled); 1768 try { 1769 authority.syncable = (syncable == null) ? 1770 AuthorityInfo.NOT_INITIALIZED : Integer.parseInt(syncable); 1771 } catch (NumberFormatException e) { 1772 // On L we stored this as {"unknown", "true", "false"} so fall back to this 1773 // format. 1774 if ("unknown".equals(syncable)) { 1775 authority.syncable = AuthorityInfo.NOT_INITIALIZED; 1776 } else { 1777 authority.syncable = Boolean.parseBoolean(syncable) ? 1778 AuthorityInfo.SYNCABLE : AuthorityInfo.NOT_SYNCABLE; 1779 } 1780 1781 } 1782 } else { 1783 Slog.w(TAG, "Failure adding authority: account=" 1784 + accountName + " auth=" + authorityName 1785 + " enabled=" + enabled 1786 + " syncable=" + syncable); 1787 } 1788 } 1789 return authority; 1790 } 1791 1792 /** 1793 * Parse a periodic sync from accounts.xml. Sets the bundle to be empty. 1794 */ parsePeriodicSync(XmlPullParser parser, AuthorityInfo authorityInfo)1795 private PeriodicSync parsePeriodicSync(XmlPullParser parser, AuthorityInfo authorityInfo) { 1796 Bundle extras = new Bundle(); // Gets filled in later. 1797 String periodValue = parser.getAttributeValue(null, "period"); 1798 String flexValue = parser.getAttributeValue(null, "flex"); 1799 final long period; 1800 long flextime; 1801 try { 1802 period = Long.parseLong(periodValue); 1803 } catch (NumberFormatException e) { 1804 Slog.e(TAG, "error parsing the period of a periodic sync", e); 1805 return null; 1806 } catch (NullPointerException e) { 1807 Slog.e(TAG, "the period of a periodic sync is null", e); 1808 return null; 1809 } 1810 try { 1811 flextime = Long.parseLong(flexValue); 1812 } catch (NumberFormatException e) { 1813 flextime = calculateDefaultFlexTime(period); 1814 Slog.e(TAG, "Error formatting value parsed for periodic sync flex: " + flexValue 1815 + ", using default: " 1816 + flextime); 1817 } catch (NullPointerException expected) { 1818 flextime = calculateDefaultFlexTime(period); 1819 Slog.d(TAG, "No flex time specified for this sync, using a default. period: " 1820 + period + " flex: " + flextime); 1821 } 1822 PeriodicSync periodicSync; 1823 periodicSync = 1824 new PeriodicSync(authorityInfo.target.account, 1825 authorityInfo.target.provider, 1826 extras, 1827 period, flextime); 1828 authorityInfo.periodicSyncs.add(periodicSync); 1829 return periodicSync; 1830 } 1831 parseExtra(XmlPullParser parser, Bundle extras)1832 private void parseExtra(XmlPullParser parser, Bundle extras) { 1833 String name = parser.getAttributeValue(null, "name"); 1834 String type = parser.getAttributeValue(null, "type"); 1835 String value1 = parser.getAttributeValue(null, "value1"); 1836 String value2 = parser.getAttributeValue(null, "value2"); 1837 1838 try { 1839 if ("long".equals(type)) { 1840 extras.putLong(name, Long.parseLong(value1)); 1841 } else if ("integer".equals(type)) { 1842 extras.putInt(name, Integer.parseInt(value1)); 1843 } else if ("double".equals(type)) { 1844 extras.putDouble(name, Double.parseDouble(value1)); 1845 } else if ("float".equals(type)) { 1846 extras.putFloat(name, Float.parseFloat(value1)); 1847 } else if ("boolean".equals(type)) { 1848 extras.putBoolean(name, Boolean.parseBoolean(value1)); 1849 } else if ("string".equals(type)) { 1850 extras.putString(name, value1); 1851 } else if ("account".equals(type)) { 1852 extras.putParcelable(name, new Account(value1, value2)); 1853 } 1854 } catch (NumberFormatException e) { 1855 Slog.e(TAG, "error parsing bundle value", e); 1856 } catch (NullPointerException e) { 1857 Slog.e(TAG, "error parsing bundle value", e); 1858 } 1859 } 1860 1861 /** 1862 * Write all account information to the account file. 1863 */ writeAccountInfoLocked()1864 private void writeAccountInfoLocked() { 1865 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 1866 Slog.v(TAG_FILE, "Writing new " + mAccountInfoFile.getBaseFile()); 1867 } 1868 FileOutputStream fos = null; 1869 1870 try { 1871 fos = mAccountInfoFile.startWrite(); 1872 XmlSerializer out = new FastXmlSerializer(); 1873 out.setOutput(fos, StandardCharsets.UTF_8.name()); 1874 out.startDocument(null, true); 1875 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 1876 1877 out.startTag(null, "accounts"); 1878 out.attribute(null, "version", Integer.toString(ACCOUNTS_VERSION)); 1879 out.attribute(null, XML_ATTR_NEXT_AUTHORITY_ID, Integer.toString(mNextAuthorityId)); 1880 out.attribute(null, XML_ATTR_SYNC_RANDOM_OFFSET, Integer.toString(mSyncRandomOffset)); 1881 1882 // Write the Sync Automatically flags for each user 1883 final int M = mMasterSyncAutomatically.size(); 1884 for (int m = 0; m < M; m++) { 1885 int userId = mMasterSyncAutomatically.keyAt(m); 1886 Boolean listen = mMasterSyncAutomatically.valueAt(m); 1887 out.startTag(null, XML_TAG_LISTEN_FOR_TICKLES); 1888 out.attribute(null, XML_ATTR_USER, Integer.toString(userId)); 1889 out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(listen)); 1890 out.endTag(null, XML_TAG_LISTEN_FOR_TICKLES); 1891 } 1892 1893 final int N = mAuthorities.size(); 1894 for (int i = 0; i < N; i++) { 1895 AuthorityInfo authority = mAuthorities.valueAt(i); 1896 EndPoint info = authority.target; 1897 out.startTag(null, "authority"); 1898 out.attribute(null, "id", Integer.toString(authority.ident)); 1899 out.attribute(null, XML_ATTR_USER, Integer.toString(info.userId)); 1900 out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(authority.enabled)); 1901 out.attribute(null, "account", info.account.name); 1902 out.attribute(null, "type", info.account.type); 1903 out.attribute(null, "authority", info.provider); 1904 out.attribute(null, "syncable", Integer.toString(authority.syncable)); 1905 out.endTag(null, "authority"); 1906 } 1907 out.endTag(null, "accounts"); 1908 out.endDocument(); 1909 mAccountInfoFile.finishWrite(fos); 1910 } catch (java.io.IOException e1) { 1911 Slog.w(TAG, "Error writing accounts", e1); 1912 if (fos != null) { 1913 mAccountInfoFile.failWrite(fos); 1914 } 1915 } 1916 } 1917 getIntColumn(Cursor c, String name)1918 static int getIntColumn(Cursor c, String name) { 1919 return c.getInt(c.getColumnIndex(name)); 1920 } 1921 getLongColumn(Cursor c, String name)1922 static long getLongColumn(Cursor c, String name) { 1923 return c.getLong(c.getColumnIndex(name)); 1924 } 1925 1926 /** 1927 * Load sync engine state from the old syncmanager database, and then 1928 * erase it. Note that we don't deal with pending operations, active 1929 * sync, or history. 1930 */ readAndDeleteLegacyAccountInfoLocked()1931 private void readAndDeleteLegacyAccountInfoLocked() { 1932 // Look for old database to initialize from. 1933 File file = mContext.getDatabasePath("syncmanager.db"); 1934 if (!file.exists()) { 1935 return; 1936 } 1937 String path = file.getPath(); 1938 SQLiteDatabase db = null; 1939 try { 1940 db = SQLiteDatabase.openDatabase(path, null, 1941 SQLiteDatabase.OPEN_READONLY); 1942 } catch (SQLiteException e) { 1943 } 1944 1945 if (db != null) { 1946 final boolean hasType = db.getVersion() >= 11; 1947 1948 // Copy in all of the status information, as well as accounts. 1949 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 1950 Slog.v(TAG_FILE, "Reading legacy sync accounts db"); 1951 } 1952 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 1953 qb.setTables("stats, status"); 1954 HashMap<String,String> map = new HashMap<String,String>(); 1955 map.put("_id", "status._id as _id"); 1956 map.put("account", "stats.account as account"); 1957 if (hasType) { 1958 map.put("account_type", "stats.account_type as account_type"); 1959 } 1960 map.put("authority", "stats.authority as authority"); 1961 map.put("totalElapsedTime", "totalElapsedTime"); 1962 map.put("numSyncs", "numSyncs"); 1963 map.put("numSourceLocal", "numSourceLocal"); 1964 map.put("numSourcePoll", "numSourcePoll"); 1965 map.put("numSourceServer", "numSourceServer"); 1966 map.put("numSourceUser", "numSourceUser"); 1967 map.put("lastSuccessSource", "lastSuccessSource"); 1968 map.put("lastSuccessTime", "lastSuccessTime"); 1969 map.put("lastFailureSource", "lastFailureSource"); 1970 map.put("lastFailureTime", "lastFailureTime"); 1971 map.put("lastFailureMesg", "lastFailureMesg"); 1972 map.put("pending", "pending"); 1973 qb.setProjectionMap(map); 1974 qb.appendWhere("stats._id = status.stats_id"); 1975 Cursor c = qb.query(db, null, null, null, null, null, null); 1976 while (c.moveToNext()) { 1977 String accountName = c.getString(c.getColumnIndex("account")); 1978 String accountType = hasType 1979 ? c.getString(c.getColumnIndex("account_type")) : null; 1980 if (accountType == null) { 1981 accountType = "com.google"; 1982 } 1983 String authorityName = c.getString(c.getColumnIndex("authority")); 1984 AuthorityInfo authority = 1985 this.getOrCreateAuthorityLocked( 1986 new EndPoint(new Account(accountName, accountType), 1987 authorityName, 1988 0 /* legacy is single-user */) 1989 , -1, 1990 false); 1991 if (authority != null) { 1992 int i = mSyncStatus.size(); 1993 boolean found = false; 1994 SyncStatusInfo st = null; 1995 while (i > 0) { 1996 i--; 1997 st = mSyncStatus.valueAt(i); 1998 if (st.authorityId == authority.ident) { 1999 found = true; 2000 break; 2001 } 2002 } 2003 if (!found) { 2004 st = new SyncStatusInfo(authority.ident); 2005 mSyncStatus.put(authority.ident, st); 2006 } 2007 st.totalElapsedTime = getLongColumn(c, "totalElapsedTime"); 2008 st.numSyncs = getIntColumn(c, "numSyncs"); 2009 st.numSourceLocal = getIntColumn(c, "numSourceLocal"); 2010 st.numSourcePoll = getIntColumn(c, "numSourcePoll"); 2011 st.numSourceServer = getIntColumn(c, "numSourceServer"); 2012 st.numSourceUser = getIntColumn(c, "numSourceUser"); 2013 st.numSourcePeriodic = 0; 2014 st.lastSuccessSource = getIntColumn(c, "lastSuccessSource"); 2015 st.lastSuccessTime = getLongColumn(c, "lastSuccessTime"); 2016 st.lastFailureSource = getIntColumn(c, "lastFailureSource"); 2017 st.lastFailureTime = getLongColumn(c, "lastFailureTime"); 2018 st.lastFailureMesg = c.getString(c.getColumnIndex("lastFailureMesg")); 2019 st.pending = getIntColumn(c, "pending") != 0; 2020 } 2021 } 2022 2023 c.close(); 2024 2025 // Retrieve the settings. 2026 qb = new SQLiteQueryBuilder(); 2027 qb.setTables("settings"); 2028 c = qb.query(db, null, null, null, null, null, null); 2029 while (c.moveToNext()) { 2030 String name = c.getString(c.getColumnIndex("name")); 2031 String value = c.getString(c.getColumnIndex("value")); 2032 if (name == null) continue; 2033 if (name.equals("listen_for_tickles")) { 2034 setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value), 0); 2035 } else if (name.startsWith("sync_provider_")) { 2036 String provider = name.substring("sync_provider_".length(), 2037 name.length()); 2038 int i = mAuthorities.size(); 2039 while (i > 0) { 2040 i--; 2041 AuthorityInfo authority = mAuthorities.valueAt(i); 2042 if (authority.target.provider.equals(provider)) { 2043 authority.enabled = value == null || Boolean.parseBoolean(value); 2044 authority.syncable = 1; 2045 } 2046 } 2047 } 2048 } 2049 2050 c.close(); 2051 2052 db.close(); 2053 2054 (new File(path)).delete(); 2055 } 2056 } 2057 2058 public static final int STATUS_FILE_END = 0; 2059 public static final int STATUS_FILE_ITEM = 100; 2060 2061 /** 2062 * Read all sync status back in to the initial engine state. 2063 */ readStatusLocked()2064 private void readStatusLocked() { 2065 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 2066 Slog.v(TAG_FILE, "Reading " + mStatusFile.getBaseFile()); 2067 } 2068 try { 2069 byte[] data = mStatusFile.readFully(); 2070 Parcel in = Parcel.obtain(); 2071 in.unmarshall(data, 0, data.length); 2072 in.setDataPosition(0); 2073 int token; 2074 while ((token=in.readInt()) != STATUS_FILE_END) { 2075 if (token == STATUS_FILE_ITEM) { 2076 SyncStatusInfo status = new SyncStatusInfo(in); 2077 if (mAuthorities.indexOfKey(status.authorityId) >= 0) { 2078 status.pending = false; 2079 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 2080 Slog.v(TAG_FILE, "Adding status for id " + status.authorityId); 2081 } 2082 mSyncStatus.put(status.authorityId, status); 2083 } 2084 } else { 2085 // Ooops. 2086 Slog.w(TAG, "Unknown status token: " + token); 2087 break; 2088 } 2089 } 2090 } catch (java.io.IOException e) { 2091 Slog.i(TAG, "No initial status"); 2092 } 2093 } 2094 2095 /** 2096 * Write all sync status to the sync status file. 2097 */ writeStatusLocked()2098 private void writeStatusLocked() { 2099 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 2100 Slog.v(TAG_FILE, "Writing new " + mStatusFile.getBaseFile()); 2101 } 2102 2103 // The file is being written, so we don't need to have a scheduled 2104 // write until the next change. 2105 removeMessages(MSG_WRITE_STATUS); 2106 2107 FileOutputStream fos = null; 2108 try { 2109 fos = mStatusFile.startWrite(); 2110 Parcel out = Parcel.obtain(); 2111 final int N = mSyncStatus.size(); 2112 for (int i=0; i<N; i++) { 2113 SyncStatusInfo status = mSyncStatus.valueAt(i); 2114 out.writeInt(STATUS_FILE_ITEM); 2115 status.writeToParcel(out, 0); 2116 } 2117 out.writeInt(STATUS_FILE_END); 2118 fos.write(out.marshall()); 2119 out.recycle(); 2120 2121 mStatusFile.finishWrite(fos); 2122 } catch (java.io.IOException e1) { 2123 Slog.w(TAG, "Error writing status", e1); 2124 if (fos != null) { 2125 mStatusFile.failWrite(fos); 2126 } 2127 } 2128 } 2129 requestSync(AuthorityInfo authorityInfo, int reason, Bundle extras)2130 private void requestSync(AuthorityInfo authorityInfo, int reason, Bundle extras) { 2131 if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID 2132 && mSyncRequestListener != null) { 2133 mSyncRequestListener.onSyncRequest(authorityInfo.target, reason, extras); 2134 } else { 2135 SyncRequest.Builder req = 2136 new SyncRequest.Builder() 2137 .syncOnce() 2138 .setExtras(extras); 2139 req.setSyncAdapter(authorityInfo.target.account, authorityInfo.target.provider); 2140 ContentResolver.requestSync(req.build()); 2141 } 2142 } 2143 requestSync(Account account, int userId, int reason, String authority, Bundle extras)2144 private void requestSync(Account account, int userId, int reason, String authority, 2145 Bundle extras) { 2146 // If this is happening in the system process, then call the syncrequest listener 2147 // to make a request back to the SyncManager directly. 2148 // If this is probably a test instance, then call back through the ContentResolver 2149 // which will know which userId to apply based on the Binder id. 2150 if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID 2151 && mSyncRequestListener != null) { 2152 mSyncRequestListener.onSyncRequest( 2153 new EndPoint(account, authority, userId), 2154 reason, 2155 extras); 2156 } else { 2157 ContentResolver.requestSync(account, authority, extras); 2158 } 2159 } 2160 2161 public static final int STATISTICS_FILE_END = 0; 2162 public static final int STATISTICS_FILE_ITEM_OLD = 100; 2163 public static final int STATISTICS_FILE_ITEM = 101; 2164 2165 /** 2166 * Read all sync statistics back in to the initial engine state. 2167 */ readStatisticsLocked()2168 private void readStatisticsLocked() { 2169 try { 2170 byte[] data = mStatisticsFile.readFully(); 2171 Parcel in = Parcel.obtain(); 2172 in.unmarshall(data, 0, data.length); 2173 in.setDataPosition(0); 2174 int token; 2175 int index = 0; 2176 while ((token=in.readInt()) != STATISTICS_FILE_END) { 2177 if (token == STATISTICS_FILE_ITEM 2178 || token == STATISTICS_FILE_ITEM_OLD) { 2179 int day = in.readInt(); 2180 if (token == STATISTICS_FILE_ITEM_OLD) { 2181 day = day - 2009 + 14245; // Magic! 2182 } 2183 DayStats ds = new DayStats(day); 2184 ds.successCount = in.readInt(); 2185 ds.successTime = in.readLong(); 2186 ds.failureCount = in.readInt(); 2187 ds.failureTime = in.readLong(); 2188 if (index < mDayStats.length) { 2189 mDayStats[index] = ds; 2190 index++; 2191 } 2192 } else { 2193 // Ooops. 2194 Slog.w(TAG, "Unknown stats token: " + token); 2195 break; 2196 } 2197 } 2198 } catch (java.io.IOException e) { 2199 Slog.i(TAG, "No initial statistics"); 2200 } 2201 } 2202 2203 /** 2204 * Write all sync statistics to the sync status file. 2205 */ writeStatisticsLocked()2206 private void writeStatisticsLocked() { 2207 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 2208 Slog.v(TAG, "Writing new " + mStatisticsFile.getBaseFile()); 2209 } 2210 2211 // The file is being written, so we don't need to have a scheduled 2212 // write until the next change. 2213 removeMessages(MSG_WRITE_STATISTICS); 2214 2215 FileOutputStream fos = null; 2216 try { 2217 fos = mStatisticsFile.startWrite(); 2218 Parcel out = Parcel.obtain(); 2219 final int N = mDayStats.length; 2220 for (int i=0; i<N; i++) { 2221 DayStats ds = mDayStats[i]; 2222 if (ds == null) { 2223 break; 2224 } 2225 out.writeInt(STATISTICS_FILE_ITEM); 2226 out.writeInt(ds.day); 2227 out.writeInt(ds.successCount); 2228 out.writeLong(ds.successTime); 2229 out.writeInt(ds.failureCount); 2230 out.writeLong(ds.failureTime); 2231 } 2232 out.writeInt(STATISTICS_FILE_END); 2233 fos.write(out.marshall()); 2234 out.recycle(); 2235 2236 mStatisticsFile.finishWrite(fos); 2237 } catch (java.io.IOException e1) { 2238 Slog.w(TAG, "Error writing stats", e1); 2239 if (fos != null) { 2240 mStatisticsFile.failWrite(fos); 2241 } 2242 } 2243 } 2244 2245 /** 2246 * Let the BackupManager know that account sync settings have changed. This will trigger 2247 * {@link com.android.server.backup.SystemBackupAgent} to run. 2248 */ queueBackup()2249 public void queueBackup() { 2250 BackupManager.dataChanged("android"); 2251 } 2252 } 2253