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