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