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