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