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