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