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