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