1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.content; 18 19 import android.accounts.Account; 20 import android.accounts.AccountAndUser; 21 import android.content.ComponentName; 22 import android.content.ContentResolver; 23 import android.content.Context; 24 import android.content.ISyncStatusObserver; 25 import android.content.PeriodicSync; 26 import android.content.SyncInfo; 27 import android.content.SyncRequest; 28 import android.content.SyncStatusInfo; 29 import android.database.Cursor; 30 import android.database.sqlite.SQLiteDatabase; 31 import android.database.sqlite.SQLiteException; 32 import android.database.sqlite.SQLiteQueryBuilder; 33 import android.os.Bundle; 34 import android.os.Environment; 35 import android.os.Handler; 36 import android.os.Message; 37 import android.os.Parcel; 38 import android.os.RemoteCallbackList; 39 import android.os.RemoteException; 40 import android.os.UserHandle; 41 import android.util.AtomicFile; 42 import android.util.Log; 43 import android.util.Pair; 44 import android.util.SparseArray; 45 import android.util.ArrayMap; 46 import android.util.Xml; 47 48 import com.android.internal.annotations.VisibleForTesting; 49 import com.android.internal.util.ArrayUtils; 50 import com.android.internal.util.FastXmlSerializer; 51 52 import org.xmlpull.v1.XmlPullParser; 53 import org.xmlpull.v1.XmlPullParserException; 54 import org.xmlpull.v1.XmlSerializer; 55 56 import java.io.File; 57 import java.io.FileInputStream; 58 import java.io.FileOutputStream; 59 import java.io.IOException; 60 import java.util.ArrayList; 61 import java.util.Calendar; 62 import java.util.HashMap; 63 import java.util.Iterator; 64 import java.util.List; 65 import java.util.Random; 66 import java.util.TimeZone; 67 68 /** 69 * Singleton that tracks the sync data and overall sync 70 * history on the device. 71 * 72 * @hide 73 */ 74 public 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 clearAllBackoffsLocked(SyncQueue syncQueue) { 838 boolean changed = false; 839 synchronized (mAuthorities) { 840 // Clear backoff for all sync adapters. 841 for (AccountInfo accountInfo : mAccounts.values()) { 842 for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) { 843 if (authorityInfo.backoffTime != NOT_IN_BACKOFF_MODE 844 || authorityInfo.backoffDelay != NOT_IN_BACKOFF_MODE) { 845 if (Log.isLoggable(TAG, Log.VERBOSE)) { 846 Log.v(TAG, "clearAllBackoffsLocked:" 847 + " authority:" + authorityInfo.target 848 + " account:" + accountInfo.accountAndUser.account.name 849 + " user:" + accountInfo.accountAndUser.userId 850 + " backoffTime was: " + authorityInfo.backoffTime 851 + " backoffDelay was: " + authorityInfo.backoffDelay); 852 } 853 authorityInfo.backoffTime = NOT_IN_BACKOFF_MODE; 854 authorityInfo.backoffDelay = NOT_IN_BACKOFF_MODE; 855 changed = true; 856 } 857 } 858 } 859 // Clear backoff for all sync services. 860 for (ComponentName service : mServices.keySet()) { 861 SparseArray<AuthorityInfo> aInfos = mServices.get(service); 862 for (int i = 0; i < aInfos.size(); i++) { 863 AuthorityInfo authorityInfo = aInfos.valueAt(i); 864 if (authorityInfo.backoffTime != NOT_IN_BACKOFF_MODE 865 || authorityInfo.backoffDelay != NOT_IN_BACKOFF_MODE) { 866 authorityInfo.backoffTime = NOT_IN_BACKOFF_MODE; 867 authorityInfo.backoffDelay = NOT_IN_BACKOFF_MODE; 868 } 869 } 870 syncQueue.clearBackoffs(); 871 } 872 } 873 874 if (changed) { 875 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); 876 } 877 } 878 879 public long getDelayUntilTime(EndPoint info) { 880 synchronized (mAuthorities) { 881 AuthorityInfo authority = getAuthorityLocked(info, "getDelayUntil"); 882 if (authority == null) { 883 return 0; 884 } 885 return authority.delayUntil; 886 } 887 } 888 889 public void setDelayUntilTime(EndPoint info, long delayUntil) { 890 if (Log.isLoggable(TAG, Log.VERBOSE)) { 891 Log.v(TAG, "setDelayUntil: " + info 892 + " -> delayUntil " + delayUntil); 893 } 894 synchronized (mAuthorities) { 895 AuthorityInfo authority = getOrCreateAuthorityLocked(info, -1, true); 896 if (authority.delayUntil == delayUntil) { 897 return; 898 } 899 authority.delayUntil = delayUntil; 900 } 901 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); 902 } 903 904 public void updateOrAddPeriodicSync(EndPoint info, long period, long flextime, Bundle extras) { 905 if (Log.isLoggable(TAG, Log.VERBOSE)) { 906 Log.v(TAG, "addPeriodicSync: " + info 907 + " -> period " + period + ", flex " + flextime + ", extras " 908 + extras.toString()); 909 } 910 synchronized (mAuthorities) { 911 if (period <= 0) { 912 Log.e(TAG, "period < 0, should never happen in updateOrAddPeriodicSync"); 913 } 914 if (extras == null) { 915 Log.e(TAG, "null extras, should never happen in updateOrAddPeriodicSync:"); 916 } 917 try { 918 PeriodicSync toUpdate; 919 if (info.target_provider) { 920 toUpdate = new PeriodicSync(info.account, 921 info.provider, 922 extras, 923 period, 924 flextime); 925 } else { 926 return; 927 } 928 AuthorityInfo authority = 929 getOrCreateAuthorityLocked(info, -1, false); 930 // add this periodic sync if an equivalent periodic doesn't already exist. 931 boolean alreadyPresent = false; 932 for (int i = 0, N = authority.periodicSyncs.size(); i < N; i++) { 933 PeriodicSync syncInfo = authority.periodicSyncs.get(i); 934 if (SyncManager.syncExtrasEquals(syncInfo.extras, 935 extras, 936 true /* includeSyncSettings*/)) { 937 if (period == syncInfo.period && 938 flextime == syncInfo.flexTime) { 939 // Absolutely the same. 940 return; 941 } 942 authority.periodicSyncs.set(i, toUpdate); 943 alreadyPresent = true; 944 break; 945 } 946 } 947 // If we added an entry to the periodicSyncs array also add an entry to 948 // the periodic syncs status to correspond to it. 949 if (!alreadyPresent) { 950 authority.periodicSyncs.add(toUpdate); 951 SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident); 952 // A new periodic sync is initialised as already having been run. 953 status.setPeriodicSyncTime( 954 authority.periodicSyncs.size() - 1, 955 System.currentTimeMillis()); 956 } 957 } finally { 958 writeAccountInfoLocked(); 959 writeStatusLocked(); 960 } 961 } 962 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); 963 } 964 965 public void removePeriodicSync(EndPoint info, Bundle extras) { 966 synchronized(mAuthorities) { 967 try { 968 AuthorityInfo authority = 969 getOrCreateAuthorityLocked(info, -1, false); 970 // Remove any periodic syncs that match the target and extras. 971 SyncStatusInfo status = mSyncStatus.get(authority.ident); 972 boolean changed = false; 973 Iterator<PeriodicSync> iterator = authority.periodicSyncs.iterator(); 974 int i = 0; 975 while (iterator.hasNext()) { 976 PeriodicSync syncInfo = iterator.next(); 977 if (SyncManager.syncExtrasEquals(syncInfo.extras, 978 extras, 979 true /* includeSyncSettings */)) { 980 iterator.remove(); 981 changed = true; 982 // If we removed an entry from the periodicSyncs array also 983 // remove the corresponding entry from the status 984 if (status != null) { 985 status.removePeriodicSyncTime(i); 986 } else { 987 Log.e(TAG, "Tried removing sync status on remove periodic sync but" 988 + " did not find it."); 989 } 990 } else { 991 i++; 992 } 993 } 994 if (!changed) { 995 return; 996 } 997 } finally { 998 writeAccountInfoLocked(); 999 writeStatusLocked(); 1000 } 1001 } 1002 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); 1003 } 1004 1005 /** 1006 * @return list of periodic syncs for a target. Never null. If no such syncs exist, returns an 1007 * empty list. 1008 */ 1009 public List<PeriodicSync> getPeriodicSyncs(EndPoint info) { 1010 synchronized (mAuthorities) { 1011 AuthorityInfo authorityInfo = getAuthorityLocked(info, "getPeriodicSyncs"); 1012 ArrayList<PeriodicSync> syncs = new ArrayList<PeriodicSync>(); 1013 if (authorityInfo != null) { 1014 for (PeriodicSync item : authorityInfo.periodicSyncs) { 1015 // Copy and send out. Necessary for thread-safety although it's parceled. 1016 syncs.add(new PeriodicSync(item)); 1017 } 1018 } 1019 return syncs; 1020 } 1021 } 1022 1023 public void setMasterSyncAutomatically(boolean flag, int userId) { 1024 synchronized (mAuthorities) { 1025 Boolean auto = mMasterSyncAutomatically.get(userId); 1026 if (auto != null && auto.equals(flag)) { 1027 return; 1028 } 1029 mMasterSyncAutomatically.put(userId, flag); 1030 writeAccountInfoLocked(); 1031 } 1032 if (flag) { 1033 requestSync(null, userId, SyncOperation.REASON_MASTER_SYNC_AUTO, null, 1034 new Bundle()); 1035 } 1036 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); 1037 mContext.sendBroadcast(ContentResolver.ACTION_SYNC_CONN_STATUS_CHANGED); 1038 } 1039 1040 public boolean getMasterSyncAutomatically(int userId) { 1041 synchronized (mAuthorities) { 1042 Boolean auto = mMasterSyncAutomatically.get(userId); 1043 return auto == null ? mDefaultMasterSyncAutomatically : auto; 1044 } 1045 } 1046 1047 public AuthorityInfo getAuthority(int authorityId) { 1048 synchronized (mAuthorities) { 1049 return mAuthorities.get(authorityId); 1050 } 1051 } 1052 1053 /** 1054 * Returns true if there is currently a sync operation being actively processed for the given 1055 * target. 1056 */ 1057 public boolean isSyncActive(EndPoint info) { 1058 synchronized (mAuthorities) { 1059 for (SyncInfo syncInfo : getCurrentSyncs(info.userId)) { 1060 AuthorityInfo ainfo = getAuthority(syncInfo.authorityId); 1061 if (ainfo != null && ainfo.target.matchesSpec(info)) { 1062 return true; 1063 } 1064 } 1065 } 1066 return false; 1067 } 1068 1069 public PendingOperation insertIntoPending(SyncOperation op) { 1070 PendingOperation pop; 1071 synchronized (mAuthorities) { 1072 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1073 Log.v(TAG, "insertIntoPending: authority=" + op.target 1074 + " extras=" + op.extras); 1075 } 1076 final EndPoint info = op.target; 1077 AuthorityInfo authority = 1078 getOrCreateAuthorityLocked(info, 1079 -1 /* desired identifier */, 1080 true /* write accounts to storage */); 1081 if (authority == null) { 1082 return null; 1083 } 1084 1085 pop = new PendingOperation(authority, op.reason, op.syncSource, op.extras, 1086 op.isExpedited()); 1087 mPendingOperations.add(pop); 1088 appendPendingOperationLocked(pop); 1089 1090 SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident); 1091 status.pending = true; 1092 } 1093 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING); 1094 return pop; 1095 } 1096 1097 /** 1098 * Remove from list of pending operations. If successful, search through list for matching 1099 * authorities. If there are no more pending syncs for the same target, 1100 * update the SyncStatusInfo for that target. 1101 * @param op Pending op to delete. 1102 */ 1103 public boolean deleteFromPending(PendingOperation op) { 1104 boolean res = false; 1105 synchronized (mAuthorities) { 1106 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1107 Log.v(TAG, "deleteFromPending: account=" + op.toString()); 1108 } 1109 if (mPendingOperations.remove(op)) { 1110 if (mPendingOperations.size() == 0 1111 || mNumPendingFinished >= PENDING_FINISH_TO_WRITE) { 1112 writePendingOperationsLocked(); 1113 mNumPendingFinished = 0; 1114 } else { 1115 mNumPendingFinished++; 1116 } 1117 AuthorityInfo authority = getAuthorityLocked(op.target, "deleteFromPending"); 1118 if (authority != null) { 1119 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1120 Log.v(TAG, "removing - " + authority.toString()); 1121 } 1122 final int N = mPendingOperations.size(); 1123 boolean morePending = false; 1124 for (int i = 0; i < N; i++) { 1125 PendingOperation cur = mPendingOperations.get(i); 1126 if (cur.equals(op)) { 1127 morePending = true; 1128 break; 1129 } 1130 } 1131 1132 if (!morePending) { 1133 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "no more pending!"); 1134 SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident); 1135 status.pending = false; 1136 } 1137 } 1138 res = true; 1139 } 1140 } 1141 1142 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING); 1143 return res; 1144 } 1145 1146 /** 1147 * Return a copy of the current array of pending operations. The 1148 * PendingOperation objects are the real objects stored inside, so that 1149 * they can be used with deleteFromPending(). 1150 */ 1151 public ArrayList<PendingOperation> getPendingOperations() { 1152 synchronized (mAuthorities) { 1153 return new ArrayList<PendingOperation>(mPendingOperations); 1154 } 1155 } 1156 1157 /** 1158 * Return the number of currently pending operations. 1159 */ 1160 public int getPendingOperationCount() { 1161 synchronized (mAuthorities) { 1162 return mPendingOperations.size(); 1163 } 1164 } 1165 1166 /** 1167 * Called when the set of account has changed, given the new array of 1168 * active accounts. 1169 */ 1170 public void doDatabaseCleanup(Account[] accounts, int userId) { 1171 synchronized (mAuthorities) { 1172 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1173 Log.v(TAG, "Updating for new accounts..."); 1174 } 1175 SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>(); 1176 Iterator<AccountInfo> accIt = mAccounts.values().iterator(); 1177 while (accIt.hasNext()) { 1178 AccountInfo acc = accIt.next(); 1179 if (!ArrayUtils.contains(accounts, acc.accountAndUser.account) 1180 && acc.accountAndUser.userId == userId) { 1181 // This account no longer exists... 1182 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1183 Log.v(TAG, "Account removed: " + acc.accountAndUser); 1184 } 1185 for (AuthorityInfo auth : acc.authorities.values()) { 1186 removing.put(auth.ident, auth); 1187 } 1188 accIt.remove(); 1189 } 1190 } 1191 1192 // Clean out all data structures. 1193 int i = removing.size(); 1194 if (i > 0) { 1195 while (i > 0) { 1196 i--; 1197 int ident = removing.keyAt(i); 1198 mAuthorities.remove(ident); 1199 int j = mSyncStatus.size(); 1200 while (j > 0) { 1201 j--; 1202 if (mSyncStatus.keyAt(j) == ident) { 1203 mSyncStatus.remove(mSyncStatus.keyAt(j)); 1204 } 1205 } 1206 j = mSyncHistory.size(); 1207 while (j > 0) { 1208 j--; 1209 if (mSyncHistory.get(j).authorityId == ident) { 1210 mSyncHistory.remove(j); 1211 } 1212 } 1213 } 1214 writeAccountInfoLocked(); 1215 writeStatusLocked(); 1216 writePendingOperationsLocked(); 1217 writeStatisticsLocked(); 1218 } 1219 } 1220 } 1221 1222 /** 1223 * Called when a sync is starting. Supply a valid ActiveSyncContext with information 1224 * about the sync. 1225 */ 1226 public SyncInfo addActiveSync(SyncManager.ActiveSyncContext activeSyncContext) { 1227 final SyncInfo syncInfo; 1228 synchronized (mAuthorities) { 1229 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1230 Log.v(TAG, "setActiveSync: account=" 1231 + " auth=" + activeSyncContext.mSyncOperation.target 1232 + " src=" + activeSyncContext.mSyncOperation.syncSource 1233 + " extras=" + activeSyncContext.mSyncOperation.extras); 1234 } 1235 final EndPoint info = activeSyncContext.mSyncOperation.target; 1236 AuthorityInfo authorityInfo = getOrCreateAuthorityLocked( 1237 info, 1238 -1 /* assign a new identifier if creating a new target */, 1239 true /* write to storage if this results in a change */); 1240 syncInfo = new SyncInfo( 1241 authorityInfo.ident, 1242 authorityInfo.target.account, 1243 authorityInfo.target.provider, 1244 activeSyncContext.mStartTime); 1245 getCurrentSyncs(authorityInfo.target.userId).add(syncInfo); 1246 } 1247 reportActiveChange(); 1248 return syncInfo; 1249 } 1250 1251 /** 1252 * Called to indicate that a previously active sync is no longer active. 1253 */ 1254 public void removeActiveSync(SyncInfo syncInfo, int userId) { 1255 synchronized (mAuthorities) { 1256 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1257 Log.v(TAG, "removeActiveSync: account=" + syncInfo.account 1258 + " user=" + userId 1259 + " auth=" + syncInfo.authority); 1260 } 1261 getCurrentSyncs(userId).remove(syncInfo); 1262 } 1263 1264 reportActiveChange(); 1265 } 1266 1267 /** 1268 * To allow others to send active change reports, to poke clients. 1269 */ 1270 public void reportActiveChange() { 1271 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE); 1272 } 1273 1274 /** 1275 * Note that sync has started for the given operation. 1276 */ 1277 public long insertStartSyncEvent(SyncOperation op, long now) { 1278 long id; 1279 synchronized (mAuthorities) { 1280 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1281 Log.v(TAG, "insertStartSyncEvent: " + op); 1282 } 1283 AuthorityInfo authority = getAuthorityLocked(op.target, "insertStartSyncEvent"); 1284 if (authority == null) { 1285 return -1; 1286 } 1287 SyncHistoryItem item = new SyncHistoryItem(); 1288 item.initialization = op.isInitialization(); 1289 item.authorityId = authority.ident; 1290 item.historyId = mNextHistoryId++; 1291 if (mNextHistoryId < 0) mNextHistoryId = 0; 1292 item.eventTime = now; 1293 item.source = op.syncSource; 1294 item.reason = op.reason; 1295 item.extras = op.extras; 1296 item.event = EVENT_START; 1297 mSyncHistory.add(0, item); 1298 while (mSyncHistory.size() > MAX_HISTORY) { 1299 mSyncHistory.remove(mSyncHistory.size()-1); 1300 } 1301 id = item.historyId; 1302 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "returning historyId " + id); 1303 } 1304 1305 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS); 1306 return id; 1307 } 1308 1309 public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage, 1310 long downstreamActivity, long upstreamActivity) { 1311 synchronized (mAuthorities) { 1312 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1313 Log.v(TAG, "stopSyncEvent: historyId=" + historyId); 1314 } 1315 SyncHistoryItem item = null; 1316 int i = mSyncHistory.size(); 1317 while (i > 0) { 1318 i--; 1319 item = mSyncHistory.get(i); 1320 if (item.historyId == historyId) { 1321 break; 1322 } 1323 item = null; 1324 } 1325 1326 if (item == null) { 1327 Log.w(TAG, "stopSyncEvent: no history for id " + historyId); 1328 return; 1329 } 1330 1331 item.elapsedTime = elapsedTime; 1332 item.event = EVENT_STOP; 1333 item.mesg = resultMessage; 1334 item.downstreamActivity = downstreamActivity; 1335 item.upstreamActivity = upstreamActivity; 1336 1337 SyncStatusInfo status = getOrCreateSyncStatusLocked(item.authorityId); 1338 1339 status.numSyncs++; 1340 status.totalElapsedTime += elapsedTime; 1341 switch (item.source) { 1342 case SOURCE_LOCAL: 1343 status.numSourceLocal++; 1344 break; 1345 case SOURCE_POLL: 1346 status.numSourcePoll++; 1347 break; 1348 case SOURCE_USER: 1349 status.numSourceUser++; 1350 break; 1351 case SOURCE_SERVER: 1352 status.numSourceServer++; 1353 break; 1354 case SOURCE_PERIODIC: 1355 status.numSourcePeriodic++; 1356 break; 1357 } 1358 1359 boolean writeStatisticsNow = false; 1360 int day = getCurrentDayLocked(); 1361 if (mDayStats[0] == null) { 1362 mDayStats[0] = new DayStats(day); 1363 } else if (day != mDayStats[0].day) { 1364 System.arraycopy(mDayStats, 0, mDayStats, 1, mDayStats.length-1); 1365 mDayStats[0] = new DayStats(day); 1366 writeStatisticsNow = true; 1367 } else if (mDayStats[0] == null) { 1368 } 1369 final DayStats ds = mDayStats[0]; 1370 1371 final long lastSyncTime = (item.eventTime + elapsedTime); 1372 boolean writeStatusNow = false; 1373 if (MESG_SUCCESS.equals(resultMessage)) { 1374 // - if successful, update the successful columns 1375 if (status.lastSuccessTime == 0 || status.lastFailureTime != 0) { 1376 writeStatusNow = true; 1377 } 1378 status.lastSuccessTime = lastSyncTime; 1379 status.lastSuccessSource = item.source; 1380 status.lastFailureTime = 0; 1381 status.lastFailureSource = -1; 1382 status.lastFailureMesg = null; 1383 status.initialFailureTime = 0; 1384 ds.successCount++; 1385 ds.successTime += elapsedTime; 1386 } else if (!MESG_CANCELED.equals(resultMessage)) { 1387 if (status.lastFailureTime == 0) { 1388 writeStatusNow = true; 1389 } 1390 status.lastFailureTime = lastSyncTime; 1391 status.lastFailureSource = item.source; 1392 status.lastFailureMesg = resultMessage; 1393 if (status.initialFailureTime == 0) { 1394 status.initialFailureTime = lastSyncTime; 1395 } 1396 ds.failureCount++; 1397 ds.failureTime += elapsedTime; 1398 } 1399 1400 if (writeStatusNow) { 1401 writeStatusLocked(); 1402 } else if (!hasMessages(MSG_WRITE_STATUS)) { 1403 sendMessageDelayed(obtainMessage(MSG_WRITE_STATUS), 1404 WRITE_STATUS_DELAY); 1405 } 1406 if (writeStatisticsNow) { 1407 writeStatisticsLocked(); 1408 } else if (!hasMessages(MSG_WRITE_STATISTICS)) { 1409 sendMessageDelayed(obtainMessage(MSG_WRITE_STATISTICS), 1410 WRITE_STATISTICS_DELAY); 1411 } 1412 } 1413 1414 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS); 1415 } 1416 1417 /** 1418 * Return a list of the currently active syncs. Note that the returned 1419 * items are the real, live active sync objects, so be careful what you do 1420 * with it. 1421 */ 1422 private List<SyncInfo> getCurrentSyncs(int userId) { 1423 synchronized (mAuthorities) { 1424 return getCurrentSyncsLocked(userId); 1425 } 1426 } 1427 1428 /** 1429 * @return a copy of the current syncs data structure. Will not return 1430 * null. 1431 */ 1432 public List<SyncInfo> getCurrentSyncsCopy(int userId) { 1433 synchronized (mAuthorities) { 1434 final List<SyncInfo> syncs = getCurrentSyncsLocked(userId); 1435 final List<SyncInfo> syncsCopy = new ArrayList<SyncInfo>(); 1436 for (SyncInfo sync : syncs) { 1437 syncsCopy.add(new SyncInfo(sync)); 1438 } 1439 return syncsCopy; 1440 } 1441 } 1442 1443 private List<SyncInfo> getCurrentSyncsLocked(int userId) { 1444 ArrayList<SyncInfo> syncs = mCurrentSyncs.get(userId); 1445 if (syncs == null) { 1446 syncs = new ArrayList<SyncInfo>(); 1447 mCurrentSyncs.put(userId, syncs); 1448 } 1449 return syncs; 1450 } 1451 1452 /** 1453 * Return an array of the current sync status for all authorities. Note 1454 * that the objects inside the array are the real, live status objects, 1455 * so be careful what you do with them. 1456 */ 1457 public ArrayList<SyncStatusInfo> getSyncStatus() { 1458 synchronized (mAuthorities) { 1459 final int N = mSyncStatus.size(); 1460 ArrayList<SyncStatusInfo> ops = new ArrayList<SyncStatusInfo>(N); 1461 for (int i=0; i<N; i++) { 1462 ops.add(mSyncStatus.valueAt(i)); 1463 } 1464 return ops; 1465 } 1466 } 1467 1468 /** 1469 * Return a copy of the specified target with the corresponding sync status 1470 */ 1471 public Pair<AuthorityInfo, SyncStatusInfo> getCopyOfAuthorityWithSyncStatus(EndPoint info) { 1472 synchronized (mAuthorities) { 1473 AuthorityInfo authorityInfo = getOrCreateAuthorityLocked(info, 1474 -1 /* assign a new identifier if creating a new target */, 1475 true /* write to storage if this results in a change */); 1476 return createCopyPairOfAuthorityWithSyncStatusLocked(authorityInfo); 1477 } 1478 } 1479 1480 /** 1481 * Return a copy of all authorities with their corresponding sync status 1482 */ 1483 public ArrayList<Pair<AuthorityInfo, SyncStatusInfo>> getCopyOfAllAuthoritiesWithSyncStatus() { 1484 synchronized (mAuthorities) { 1485 ArrayList<Pair<AuthorityInfo, SyncStatusInfo>> infos = 1486 new ArrayList<Pair<AuthorityInfo, SyncStatusInfo>>(mAuthorities.size()); 1487 for (int i = 0; i < mAuthorities.size(); i++) { 1488 infos.add(createCopyPairOfAuthorityWithSyncStatusLocked(mAuthorities.valueAt(i))); 1489 } 1490 return infos; 1491 } 1492 } 1493 1494 /** 1495 * Returns the status that matches the target. 1496 * 1497 * @param info the endpoint target we are querying status info for. 1498 * @return the SyncStatusInfo for the endpoint. 1499 */ 1500 public SyncStatusInfo getStatusByAuthority(EndPoint info) { 1501 if (info.target_provider && (info.account == null || info.provider == null)) { 1502 return null; 1503 } else if (info.target_service && info.service == null) { 1504 return null; 1505 } 1506 synchronized (mAuthorities) { 1507 final int N = mSyncStatus.size(); 1508 for (int i = 0; i < N; i++) { 1509 SyncStatusInfo cur = mSyncStatus.valueAt(i); 1510 AuthorityInfo ainfo = mAuthorities.get(cur.authorityId); 1511 if (ainfo != null 1512 && ainfo.target.matchesSpec(info)) { 1513 return cur; 1514 } 1515 } 1516 return null; 1517 } 1518 } 1519 1520 /** Return true if the pending status is true of any matching authorities. */ 1521 public boolean isSyncPending(EndPoint info) { 1522 synchronized (mAuthorities) { 1523 final int N = mSyncStatus.size(); 1524 for (int i = 0; i < N; i++) { 1525 SyncStatusInfo cur = mSyncStatus.valueAt(i); 1526 AuthorityInfo ainfo = mAuthorities.get(cur.authorityId); 1527 if (ainfo == null) { 1528 continue; 1529 } 1530 if (!ainfo.target.matchesSpec(info)) { 1531 continue; 1532 } 1533 if (cur.pending) { 1534 return true; 1535 } 1536 } 1537 return false; 1538 } 1539 } 1540 1541 /** 1542 * Return an array of the current sync status for all authorities. Note 1543 * that the objects inside the array are the real, live status objects, 1544 * so be careful what you do with them. 1545 */ 1546 public ArrayList<SyncHistoryItem> getSyncHistory() { 1547 synchronized (mAuthorities) { 1548 final int N = mSyncHistory.size(); 1549 ArrayList<SyncHistoryItem> items = new ArrayList<SyncHistoryItem>(N); 1550 for (int i=0; i<N; i++) { 1551 items.add(mSyncHistory.get(i)); 1552 } 1553 return items; 1554 } 1555 } 1556 1557 /** 1558 * Return an array of the current per-day statistics. Note 1559 * that the objects inside the array are the real, live status objects, 1560 * so be careful what you do with them. 1561 */ 1562 public DayStats[] getDayStatistics() { 1563 synchronized (mAuthorities) { 1564 DayStats[] ds = new DayStats[mDayStats.length]; 1565 System.arraycopy(mDayStats, 0, ds, 0, ds.length); 1566 return ds; 1567 } 1568 } 1569 1570 private Pair<AuthorityInfo, SyncStatusInfo> createCopyPairOfAuthorityWithSyncStatusLocked( 1571 AuthorityInfo authorityInfo) { 1572 SyncStatusInfo syncStatusInfo = getOrCreateSyncStatusLocked(authorityInfo.ident); 1573 return Pair.create(new AuthorityInfo(authorityInfo), new SyncStatusInfo(syncStatusInfo)); 1574 } 1575 1576 private int getCurrentDayLocked() { 1577 mCal.setTimeInMillis(System.currentTimeMillis()); 1578 final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR); 1579 if (mYear != mCal.get(Calendar.YEAR)) { 1580 mYear = mCal.get(Calendar.YEAR); 1581 mCal.clear(); 1582 mCal.set(Calendar.YEAR, mYear); 1583 mYearInDays = (int)(mCal.getTimeInMillis()/86400000); 1584 } 1585 return dayOfYear + mYearInDays; 1586 } 1587 1588 /** 1589 * Retrieve a target's full info, returning null if one does not exist. 1590 * 1591 * @param info info of the target to look up. 1592 * @param tag If non-null, this will be used in a log message if the 1593 * requested target does not exist. 1594 */ 1595 private AuthorityInfo getAuthorityLocked(EndPoint info, String tag) { 1596 if (info.target_service) { 1597 SparseArray<AuthorityInfo> aInfo = mServices.get(info.service); 1598 AuthorityInfo authority = null; 1599 if (aInfo != null) { 1600 authority = aInfo.get(info.userId); 1601 } 1602 if (authority == null) { 1603 if (tag != null) { 1604 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1605 Log.v(TAG, tag + " No authority info found for " + info.service + " for" 1606 + " user " + info.userId); 1607 } 1608 } 1609 return null; 1610 } 1611 return authority; 1612 } else if (info.target_provider){ 1613 AccountAndUser au = new AccountAndUser(info.account, info.userId); 1614 AccountInfo accountInfo = mAccounts.get(au); 1615 if (accountInfo == null) { 1616 if (tag != null) { 1617 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1618 Log.v(TAG, tag + ": unknown account " + au); 1619 } 1620 } 1621 return null; 1622 } 1623 AuthorityInfo authority = accountInfo.authorities.get(info.provider); 1624 if (authority == null) { 1625 if (tag != null) { 1626 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1627 Log.v(TAG, tag + ": unknown provider " + info.provider); 1628 } 1629 } 1630 return null; 1631 } 1632 return authority; 1633 } else { 1634 Log.e(TAG, tag + " Authority : " + info + ", invalid target"); 1635 return null; 1636 } 1637 } 1638 1639 /** 1640 * @param info info identifying target. 1641 * @param ident unique identifier for target. -1 for none. 1642 * @param doWrite if true, update the accounts.xml file on the disk. 1643 * @return the authority that corresponds to the provided sync target, creating it if none 1644 * exists. 1645 */ 1646 private AuthorityInfo getOrCreateAuthorityLocked(EndPoint info, int ident, boolean doWrite) { 1647 AuthorityInfo authority = null; 1648 if (info.target_service) { 1649 SparseArray<AuthorityInfo> aInfo = mServices.get(info.service); 1650 if (aInfo == null) { 1651 aInfo = new SparseArray<AuthorityInfo>(); 1652 mServices.put(info.service, aInfo); 1653 } 1654 authority = aInfo.get(info.userId); 1655 if (authority == null) { 1656 authority = createAuthorityLocked(info, ident, doWrite); 1657 aInfo.put(info.userId, authority); 1658 } 1659 } else if (info.target_provider) { 1660 AccountAndUser au = new AccountAndUser(info.account, info.userId); 1661 AccountInfo account = mAccounts.get(au); 1662 if (account == null) { 1663 account = new AccountInfo(au); 1664 mAccounts.put(au, account); 1665 } 1666 authority = account.authorities.get(info.provider); 1667 if (authority == null) { 1668 authority = createAuthorityLocked(info, ident, doWrite); 1669 account.authorities.put(info.provider, authority); 1670 } 1671 } 1672 return authority; 1673 } 1674 1675 private AuthorityInfo createAuthorityLocked(EndPoint info, int ident, boolean doWrite) { 1676 AuthorityInfo authority; 1677 if (ident < 0) { 1678 ident = mNextAuthorityId; 1679 mNextAuthorityId++; 1680 doWrite = true; 1681 } 1682 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1683 Log.v(TAG, "created a new AuthorityInfo for " + info); 1684 } 1685 authority = new AuthorityInfo(info, ident); 1686 mAuthorities.put(ident, authority); 1687 if (doWrite) { 1688 writeAccountInfoLocked(); 1689 } 1690 return authority; 1691 } 1692 1693 public void removeAuthority(EndPoint info) { 1694 synchronized (mAuthorities) { 1695 if (info.target_provider) { 1696 removeAuthorityLocked(info.account, info.userId, info.provider, true /* doWrite */); 1697 } else { 1698 SparseArray<AuthorityInfo> aInfos = mServices.get(info.service); 1699 if (aInfos != null) { 1700 AuthorityInfo authorityInfo = aInfos.get(info.userId); 1701 if (authorityInfo != null) { 1702 mAuthorities.remove(authorityInfo.ident); 1703 aInfos.delete(info.userId); 1704 writeAccountInfoLocked(); 1705 } 1706 } 1707 1708 } 1709 } 1710 } 1711 1712 /** 1713 * Remove an authority associated with a provider. Needs to be a standalone function for 1714 * backward compatibility. 1715 */ 1716 private void removeAuthorityLocked(Account account, int userId, String authorityName, 1717 boolean doWrite) { 1718 AccountInfo accountInfo = mAccounts.get(new AccountAndUser(account, userId)); 1719 if (accountInfo != null) { 1720 final AuthorityInfo authorityInfo = accountInfo.authorities.remove(authorityName); 1721 if (authorityInfo != null) { 1722 mAuthorities.remove(authorityInfo.ident); 1723 if (doWrite) { 1724 writeAccountInfoLocked(); 1725 } 1726 } 1727 } 1728 } 1729 1730 /** 1731 * Updates (in a synchronized way) the periodic sync time of the specified 1732 * target id and target periodic sync 1733 */ 1734 public void setPeriodicSyncTime(int authorityId, PeriodicSync targetPeriodicSync, long when) { 1735 boolean found = false; 1736 final AuthorityInfo authorityInfo; 1737 synchronized (mAuthorities) { 1738 authorityInfo = mAuthorities.get(authorityId); 1739 for (int i = 0; i < authorityInfo.periodicSyncs.size(); i++) { 1740 PeriodicSync periodicSync = authorityInfo.periodicSyncs.get(i); 1741 if (targetPeriodicSync.equals(periodicSync)) { 1742 mSyncStatus.get(authorityId).setPeriodicSyncTime(i, when); 1743 found = true; 1744 break; 1745 } 1746 } 1747 } 1748 if (!found) { 1749 Log.w(TAG, "Ignoring setPeriodicSyncTime request for a sync that does not exist. " + 1750 "Authority: " + authorityInfo.target); 1751 } 1752 } 1753 1754 private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) { 1755 SyncStatusInfo status = mSyncStatus.get(authorityId); 1756 if (status == null) { 1757 status = new SyncStatusInfo(authorityId); 1758 mSyncStatus.put(authorityId, status); 1759 } 1760 return status; 1761 } 1762 1763 public void writeAllState() { 1764 synchronized (mAuthorities) { 1765 // Account info is always written so no need to do it here. 1766 1767 if (mNumPendingFinished > 0) { 1768 // Only write these if they are out of date. 1769 writePendingOperationsLocked(); 1770 } 1771 1772 // Just always write these... they are likely out of date. 1773 writeStatusLocked(); 1774 writeStatisticsLocked(); 1775 } 1776 } 1777 1778 /** 1779 * public for testing 1780 */ 1781 public void clearAndReadState() { 1782 synchronized (mAuthorities) { 1783 mAuthorities.clear(); 1784 mAccounts.clear(); 1785 mServices.clear(); 1786 mPendingOperations.clear(); 1787 mSyncStatus.clear(); 1788 mSyncHistory.clear(); 1789 1790 readAccountInfoLocked(); 1791 readStatusLocked(); 1792 readPendingOperationsLocked(); 1793 readStatisticsLocked(); 1794 readAndDeleteLegacyAccountInfoLocked(); 1795 writeAccountInfoLocked(); 1796 writeStatusLocked(); 1797 writePendingOperationsLocked(); 1798 writeStatisticsLocked(); 1799 } 1800 } 1801 1802 /** 1803 * Read all account information back in to the initial engine state. 1804 */ 1805 private void readAccountInfoLocked() { 1806 int highestAuthorityId = -1; 1807 FileInputStream fis = null; 1808 try { 1809 fis = mAccountInfoFile.openRead(); 1810 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 1811 Log.v(TAG_FILE, "Reading " + mAccountInfoFile.getBaseFile()); 1812 } 1813 XmlPullParser parser = Xml.newPullParser(); 1814 parser.setInput(fis, null); 1815 int eventType = parser.getEventType(); 1816 while (eventType != XmlPullParser.START_TAG && 1817 eventType != XmlPullParser.END_DOCUMENT) { 1818 eventType = parser.next(); 1819 } 1820 if (eventType == XmlPullParser.END_DOCUMENT) { 1821 Log.i(TAG, "No initial accounts"); 1822 return; 1823 } 1824 1825 String tagName = parser.getName(); 1826 if ("accounts".equals(tagName)) { 1827 String listen = parser.getAttributeValue(null, XML_ATTR_LISTEN_FOR_TICKLES); 1828 String versionString = parser.getAttributeValue(null, "version"); 1829 int version; 1830 try { 1831 version = (versionString == null) ? 0 : Integer.parseInt(versionString); 1832 } catch (NumberFormatException e) { 1833 version = 0; 1834 } 1835 String nextIdString = parser.getAttributeValue(null, XML_ATTR_NEXT_AUTHORITY_ID); 1836 try { 1837 int id = (nextIdString == null) ? 0 : Integer.parseInt(nextIdString); 1838 mNextAuthorityId = Math.max(mNextAuthorityId, id); 1839 } catch (NumberFormatException e) { 1840 // don't care 1841 } 1842 String offsetString = parser.getAttributeValue(null, XML_ATTR_SYNC_RANDOM_OFFSET); 1843 try { 1844 mSyncRandomOffset = (offsetString == null) ? 0 : Integer.parseInt(offsetString); 1845 } catch (NumberFormatException e) { 1846 mSyncRandomOffset = 0; 1847 } 1848 if (mSyncRandomOffset == 0) { 1849 Random random = new Random(System.currentTimeMillis()); 1850 mSyncRandomOffset = random.nextInt(86400); 1851 } 1852 mMasterSyncAutomatically.put(0, listen == null || Boolean.parseBoolean(listen)); 1853 eventType = parser.next(); 1854 AuthorityInfo authority = null; 1855 PeriodicSync periodicSync = null; 1856 do { 1857 if (eventType == XmlPullParser.START_TAG) { 1858 tagName = parser.getName(); 1859 if (parser.getDepth() == 2) { 1860 if ("authority".equals(tagName)) { 1861 authority = parseAuthority(parser, version); 1862 periodicSync = null; 1863 if (authority.ident > highestAuthorityId) { 1864 highestAuthorityId = authority.ident; 1865 } 1866 } else if (XML_TAG_LISTEN_FOR_TICKLES.equals(tagName)) { 1867 parseListenForTickles(parser); 1868 } 1869 } else if (parser.getDepth() == 3) { 1870 if ("periodicSync".equals(tagName) && authority != null) { 1871 periodicSync = parsePeriodicSync(parser, authority); 1872 } 1873 } else if (parser.getDepth() == 4 && periodicSync != null) { 1874 if ("extra".equals(tagName)) { 1875 parseExtra(parser, periodicSync.extras); 1876 } 1877 } 1878 } 1879 eventType = parser.next(); 1880 } while (eventType != XmlPullParser.END_DOCUMENT); 1881 } 1882 } catch (XmlPullParserException e) { 1883 Log.w(TAG, "Error reading accounts", e); 1884 return; 1885 } catch (java.io.IOException e) { 1886 if (fis == null) Log.i(TAG, "No initial accounts"); 1887 else Log.w(TAG, "Error reading accounts", e); 1888 return; 1889 } finally { 1890 mNextAuthorityId = Math.max(highestAuthorityId + 1, mNextAuthorityId); 1891 if (fis != null) { 1892 try { 1893 fis.close(); 1894 } catch (java.io.IOException e1) { 1895 } 1896 } 1897 } 1898 1899 maybeMigrateSettingsForRenamedAuthorities(); 1900 } 1901 1902 /** 1903 * Ensure the old pending.bin is deleted, as it has been changed to pending.xml. 1904 * pending.xml was used starting in KLP. 1905 * @param syncDir directory where the sync files are located. 1906 */ 1907 private void maybeDeleteLegacyPendingInfoLocked(File syncDir) { 1908 File file = new File(syncDir, "pending.bin"); 1909 if (!file.exists()) { 1910 return; 1911 } else { 1912 file.delete(); 1913 } 1914 } 1915 1916 /** 1917 * some authority names have changed. copy over their settings and delete the old ones 1918 * @return true if a change was made 1919 */ 1920 private boolean maybeMigrateSettingsForRenamedAuthorities() { 1921 boolean writeNeeded = false; 1922 1923 ArrayList<AuthorityInfo> authoritiesToRemove = new ArrayList<AuthorityInfo>(); 1924 final int N = mAuthorities.size(); 1925 for (int i = 0; i < N; i++) { 1926 AuthorityInfo authority = mAuthorities.valueAt(i); 1927 // skip this authority if it doesn't target a provider 1928 if (authority.target.target_service) { 1929 continue; 1930 } 1931 // skip this authority if it isn't one of the renamed ones 1932 final String newAuthorityName = sAuthorityRenames.get(authority.target.provider); 1933 if (newAuthorityName == null) { 1934 continue; 1935 } 1936 1937 // remember this authority so we can remove it later. we can't remove it 1938 // now without messing up this loop iteration 1939 authoritiesToRemove.add(authority); 1940 1941 // this authority isn't enabled, no need to copy it to the new authority name since 1942 // the default is "disabled" 1943 if (!authority.enabled) { 1944 continue; 1945 } 1946 1947 // if we already have a record of this new authority then don't copy over the settings 1948 EndPoint newInfo = 1949 new EndPoint(authority.target.account, 1950 newAuthorityName, 1951 authority.target.userId); 1952 if (getAuthorityLocked(newInfo, "cleanup") != null) { 1953 continue; 1954 } 1955 1956 AuthorityInfo newAuthority = 1957 getOrCreateAuthorityLocked(newInfo, -1 /* ident */, false /* doWrite */); 1958 newAuthority.enabled = true; 1959 writeNeeded = true; 1960 } 1961 1962 for (AuthorityInfo authorityInfo : authoritiesToRemove) { 1963 removeAuthorityLocked( 1964 authorityInfo.target.account, 1965 authorityInfo.target.userId, 1966 authorityInfo.target.provider, 1967 false /* doWrite */); 1968 writeNeeded = true; 1969 } 1970 1971 return writeNeeded; 1972 } 1973 1974 private void parseListenForTickles(XmlPullParser parser) { 1975 String user = parser.getAttributeValue(null, XML_ATTR_USER); 1976 int userId = 0; 1977 try { 1978 userId = Integer.parseInt(user); 1979 } catch (NumberFormatException e) { 1980 Log.e(TAG, "error parsing the user for listen-for-tickles", e); 1981 } catch (NullPointerException e) { 1982 Log.e(TAG, "the user in listen-for-tickles is null", e); 1983 } 1984 String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED); 1985 boolean listen = enabled == null || Boolean.parseBoolean(enabled); 1986 mMasterSyncAutomatically.put(userId, listen); 1987 } 1988 1989 private AuthorityInfo parseAuthority(XmlPullParser parser, int version) { 1990 AuthorityInfo authority = null; 1991 int id = -1; 1992 try { 1993 id = Integer.parseInt(parser.getAttributeValue(null, "id")); 1994 } catch (NumberFormatException e) { 1995 Log.e(TAG, "error parsing the id of the authority", e); 1996 } catch (NullPointerException e) { 1997 Log.e(TAG, "the id of the authority is null", e); 1998 } 1999 if (id >= 0) { 2000 String authorityName = parser.getAttributeValue(null, "authority"); 2001 String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED); 2002 String syncable = parser.getAttributeValue(null, "syncable"); 2003 String accountName = parser.getAttributeValue(null, "account"); 2004 String accountType = parser.getAttributeValue(null, "type"); 2005 String user = parser.getAttributeValue(null, XML_ATTR_USER); 2006 String packageName = parser.getAttributeValue(null, "package"); 2007 String className = parser.getAttributeValue(null, "class"); 2008 int userId = user == null ? 0 : Integer.parseInt(user); 2009 if (accountType == null && packageName == null) { 2010 accountType = "com.google"; 2011 syncable = "unknown"; 2012 } 2013 authority = mAuthorities.get(id); 2014 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 2015 Log.v(TAG_FILE, "Adding authority:" 2016 + " account=" + accountName 2017 + " accountType=" + accountType 2018 + " auth=" + authorityName 2019 + " package=" + packageName 2020 + " class=" + className 2021 + " user=" + userId 2022 + " enabled=" + enabled 2023 + " syncable=" + syncable); 2024 } 2025 if (authority == null) { 2026 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 2027 Log.v(TAG_FILE, "Creating authority entry"); 2028 } 2029 EndPoint info; 2030 if (accountName != null && authorityName != null) { 2031 info = new EndPoint( 2032 new Account(accountName, accountType), 2033 authorityName, userId); 2034 } else { 2035 info = new EndPoint( 2036 new ComponentName(packageName, className), 2037 userId); 2038 } 2039 authority = getOrCreateAuthorityLocked(info, id, false); 2040 // If the version is 0 then we are upgrading from a file format that did not 2041 // know about periodic syncs. In that case don't clear the list since we 2042 // want the default, which is a daily periodic sync. 2043 // Otherwise clear out this default list since we will populate it later with 2044 // the periodic sync descriptions that are read from the configuration file. 2045 if (version > 0) { 2046 authority.periodicSyncs.clear(); 2047 } 2048 } 2049 if (authority != null) { 2050 authority.enabled = enabled == null || Boolean.parseBoolean(enabled); 2051 if ("unknown".equals(syncable)) { 2052 authority.syncable = -1; 2053 } else { 2054 authority.syncable = 2055 (syncable == null || Boolean.parseBoolean(syncable)) ? 1 : 0; 2056 } 2057 } else { 2058 Log.w(TAG, "Failure adding authority: account=" 2059 + accountName + " auth=" + authorityName 2060 + " enabled=" + enabled 2061 + " syncable=" + syncable); 2062 } 2063 } 2064 return authority; 2065 } 2066 2067 /** 2068 * Parse a periodic sync from accounts.xml. Sets the bundle to be empty. 2069 */ 2070 private PeriodicSync parsePeriodicSync(XmlPullParser parser, AuthorityInfo authorityInfo) { 2071 Bundle extras = new Bundle(); // Gets filled in later. 2072 String periodValue = parser.getAttributeValue(null, "period"); 2073 String flexValue = parser.getAttributeValue(null, "flex"); 2074 final long period; 2075 long flextime; 2076 try { 2077 period = Long.parseLong(periodValue); 2078 } catch (NumberFormatException e) { 2079 Log.e(TAG, "error parsing the period of a periodic sync", e); 2080 return null; 2081 } catch (NullPointerException e) { 2082 Log.e(TAG, "the period of a periodic sync is null", e); 2083 return null; 2084 } 2085 try { 2086 flextime = Long.parseLong(flexValue); 2087 } catch (NumberFormatException e) { 2088 flextime = calculateDefaultFlexTime(period); 2089 Log.e(TAG, "Error formatting value parsed for periodic sync flex: " + flexValue 2090 + ", using default: " 2091 + flextime); 2092 } catch (NullPointerException expected) { 2093 flextime = calculateDefaultFlexTime(period); 2094 Log.d(TAG, "No flex time specified for this sync, using a default. period: " 2095 + period + " flex: " + flextime); 2096 } 2097 PeriodicSync periodicSync; 2098 if (authorityInfo.target.target_provider) { 2099 periodicSync = 2100 new PeriodicSync(authorityInfo.target.account, 2101 authorityInfo.target.provider, 2102 extras, 2103 period, flextime); 2104 } else { 2105 Log.e(TAG, "Unknown target."); 2106 return null; 2107 } 2108 authorityInfo.periodicSyncs.add(periodicSync); 2109 return periodicSync; 2110 } 2111 2112 private void parseExtra(XmlPullParser parser, Bundle extras) { 2113 String name = parser.getAttributeValue(null, "name"); 2114 String type = parser.getAttributeValue(null, "type"); 2115 String value1 = parser.getAttributeValue(null, "value1"); 2116 String value2 = parser.getAttributeValue(null, "value2"); 2117 2118 try { 2119 if ("long".equals(type)) { 2120 extras.putLong(name, Long.parseLong(value1)); 2121 } else if ("integer".equals(type)) { 2122 extras.putInt(name, Integer.parseInt(value1)); 2123 } else if ("double".equals(type)) { 2124 extras.putDouble(name, Double.parseDouble(value1)); 2125 } else if ("float".equals(type)) { 2126 extras.putFloat(name, Float.parseFloat(value1)); 2127 } else if ("boolean".equals(type)) { 2128 extras.putBoolean(name, Boolean.parseBoolean(value1)); 2129 } else if ("string".equals(type)) { 2130 extras.putString(name, value1); 2131 } else if ("account".equals(type)) { 2132 extras.putParcelable(name, new Account(value1, value2)); 2133 } 2134 } catch (NumberFormatException e) { 2135 Log.e(TAG, "error parsing bundle value", e); 2136 } catch (NullPointerException e) { 2137 Log.e(TAG, "error parsing bundle value", e); 2138 } 2139 } 2140 2141 /** 2142 * Write all account information to the account file. 2143 */ 2144 private void writeAccountInfoLocked() { 2145 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 2146 Log.v(TAG_FILE, "Writing new " + mAccountInfoFile.getBaseFile()); 2147 } 2148 FileOutputStream fos = null; 2149 2150 try { 2151 fos = mAccountInfoFile.startWrite(); 2152 XmlSerializer out = new FastXmlSerializer(); 2153 out.setOutput(fos, "utf-8"); 2154 out.startDocument(null, true); 2155 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 2156 2157 out.startTag(null, "accounts"); 2158 out.attribute(null, "version", Integer.toString(ACCOUNTS_VERSION)); 2159 out.attribute(null, XML_ATTR_NEXT_AUTHORITY_ID, Integer.toString(mNextAuthorityId)); 2160 out.attribute(null, XML_ATTR_SYNC_RANDOM_OFFSET, Integer.toString(mSyncRandomOffset)); 2161 2162 // Write the Sync Automatically flags for each user 2163 final int M = mMasterSyncAutomatically.size(); 2164 for (int m = 0; m < M; m++) { 2165 int userId = mMasterSyncAutomatically.keyAt(m); 2166 Boolean listen = mMasterSyncAutomatically.valueAt(m); 2167 out.startTag(null, XML_TAG_LISTEN_FOR_TICKLES); 2168 out.attribute(null, XML_ATTR_USER, Integer.toString(userId)); 2169 out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(listen)); 2170 out.endTag(null, XML_TAG_LISTEN_FOR_TICKLES); 2171 } 2172 2173 final int N = mAuthorities.size(); 2174 for (int i = 0; i < N; i++) { 2175 AuthorityInfo authority = mAuthorities.valueAt(i); 2176 EndPoint info = authority.target; 2177 out.startTag(null, "authority"); 2178 out.attribute(null, "id", Integer.toString(authority.ident)); 2179 out.attribute(null, XML_ATTR_USER, Integer.toString(info.userId)); 2180 out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(authority.enabled)); 2181 if (info.service == null) { 2182 out.attribute(null, "account", info.account.name); 2183 out.attribute(null, "type", info.account.type); 2184 out.attribute(null, "authority", info.provider); 2185 } else { 2186 out.attribute(null, "package", info.service.getPackageName()); 2187 out.attribute(null, "class", info.service.getClassName()); 2188 } 2189 if (authority.syncable < 0) { 2190 out.attribute(null, "syncable", "unknown"); 2191 } else { 2192 out.attribute(null, "syncable", Boolean.toString(authority.syncable != 0)); 2193 } 2194 for (PeriodicSync periodicSync : authority.periodicSyncs) { 2195 out.startTag(null, "periodicSync"); 2196 out.attribute(null, "period", Long.toString(periodicSync.period)); 2197 out.attribute(null, "flex", Long.toString(periodicSync.flexTime)); 2198 final Bundle extras = periodicSync.extras; 2199 extrasToXml(out, extras); 2200 out.endTag(null, "periodicSync"); 2201 } 2202 out.endTag(null, "authority"); 2203 } 2204 out.endTag(null, "accounts"); 2205 out.endDocument(); 2206 mAccountInfoFile.finishWrite(fos); 2207 } catch (java.io.IOException e1) { 2208 Log.w(TAG, "Error writing accounts", e1); 2209 if (fos != null) { 2210 mAccountInfoFile.failWrite(fos); 2211 } 2212 } 2213 } 2214 2215 static int getIntColumn(Cursor c, String name) { 2216 return c.getInt(c.getColumnIndex(name)); 2217 } 2218 2219 static long getLongColumn(Cursor c, String name) { 2220 return c.getLong(c.getColumnIndex(name)); 2221 } 2222 2223 /** 2224 * Load sync engine state from the old syncmanager database, and then 2225 * erase it. Note that we don't deal with pending operations, active 2226 * sync, or history. 2227 */ 2228 private void readAndDeleteLegacyAccountInfoLocked() { 2229 // Look for old database to initialize from. 2230 File file = mContext.getDatabasePath("syncmanager.db"); 2231 if (!file.exists()) { 2232 return; 2233 } 2234 String path = file.getPath(); 2235 SQLiteDatabase db = null; 2236 try { 2237 db = SQLiteDatabase.openDatabase(path, null, 2238 SQLiteDatabase.OPEN_READONLY); 2239 } catch (SQLiteException e) { 2240 } 2241 2242 if (db != null) { 2243 final boolean hasType = db.getVersion() >= 11; 2244 2245 // Copy in all of the status information, as well as accounts. 2246 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 2247 Log.v(TAG_FILE, "Reading legacy sync accounts db"); 2248 } 2249 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 2250 qb.setTables("stats, status"); 2251 HashMap<String,String> map = new HashMap<String,String>(); 2252 map.put("_id", "status._id as _id"); 2253 map.put("account", "stats.account as account"); 2254 if (hasType) { 2255 map.put("account_type", "stats.account_type as account_type"); 2256 } 2257 map.put("authority", "stats.authority as authority"); 2258 map.put("totalElapsedTime", "totalElapsedTime"); 2259 map.put("numSyncs", "numSyncs"); 2260 map.put("numSourceLocal", "numSourceLocal"); 2261 map.put("numSourcePoll", "numSourcePoll"); 2262 map.put("numSourceServer", "numSourceServer"); 2263 map.put("numSourceUser", "numSourceUser"); 2264 map.put("lastSuccessSource", "lastSuccessSource"); 2265 map.put("lastSuccessTime", "lastSuccessTime"); 2266 map.put("lastFailureSource", "lastFailureSource"); 2267 map.put("lastFailureTime", "lastFailureTime"); 2268 map.put("lastFailureMesg", "lastFailureMesg"); 2269 map.put("pending", "pending"); 2270 qb.setProjectionMap(map); 2271 qb.appendWhere("stats._id = status.stats_id"); 2272 Cursor c = qb.query(db, null, null, null, null, null, null); 2273 while (c.moveToNext()) { 2274 String accountName = c.getString(c.getColumnIndex("account")); 2275 String accountType = hasType 2276 ? c.getString(c.getColumnIndex("account_type")) : null; 2277 if (accountType == null) { 2278 accountType = "com.google"; 2279 } 2280 String authorityName = c.getString(c.getColumnIndex("authority")); 2281 AuthorityInfo authority = 2282 this.getOrCreateAuthorityLocked( 2283 new EndPoint(new Account(accountName, accountType), 2284 authorityName, 2285 0 /* legacy is single-user */) 2286 , -1, 2287 false); 2288 if (authority != null) { 2289 int i = mSyncStatus.size(); 2290 boolean found = false; 2291 SyncStatusInfo st = null; 2292 while (i > 0) { 2293 i--; 2294 st = mSyncStatus.valueAt(i); 2295 if (st.authorityId == authority.ident) { 2296 found = true; 2297 break; 2298 } 2299 } 2300 if (!found) { 2301 st = new SyncStatusInfo(authority.ident); 2302 mSyncStatus.put(authority.ident, st); 2303 } 2304 st.totalElapsedTime = getLongColumn(c, "totalElapsedTime"); 2305 st.numSyncs = getIntColumn(c, "numSyncs"); 2306 st.numSourceLocal = getIntColumn(c, "numSourceLocal"); 2307 st.numSourcePoll = getIntColumn(c, "numSourcePoll"); 2308 st.numSourceServer = getIntColumn(c, "numSourceServer"); 2309 st.numSourceUser = getIntColumn(c, "numSourceUser"); 2310 st.numSourcePeriodic = 0; 2311 st.lastSuccessSource = getIntColumn(c, "lastSuccessSource"); 2312 st.lastSuccessTime = getLongColumn(c, "lastSuccessTime"); 2313 st.lastFailureSource = getIntColumn(c, "lastFailureSource"); 2314 st.lastFailureTime = getLongColumn(c, "lastFailureTime"); 2315 st.lastFailureMesg = c.getString(c.getColumnIndex("lastFailureMesg")); 2316 st.pending = getIntColumn(c, "pending") != 0; 2317 } 2318 } 2319 2320 c.close(); 2321 2322 // Retrieve the settings. 2323 qb = new SQLiteQueryBuilder(); 2324 qb.setTables("settings"); 2325 c = qb.query(db, null, null, null, null, null, null); 2326 while (c.moveToNext()) { 2327 String name = c.getString(c.getColumnIndex("name")); 2328 String value = c.getString(c.getColumnIndex("value")); 2329 if (name == null) continue; 2330 if (name.equals("listen_for_tickles")) { 2331 setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value), 0); 2332 } else if (name.startsWith("sync_provider_")) { 2333 String provider = name.substring("sync_provider_".length(), 2334 name.length()); 2335 int i = mAuthorities.size(); 2336 while (i > 0) { 2337 i--; 2338 AuthorityInfo authority = mAuthorities.valueAt(i); 2339 if (authority.target.provider.equals(provider)) { 2340 authority.enabled = value == null || Boolean.parseBoolean(value); 2341 authority.syncable = 1; 2342 } 2343 } 2344 } 2345 } 2346 2347 c.close(); 2348 2349 db.close(); 2350 2351 (new File(path)).delete(); 2352 } 2353 } 2354 2355 public static final int STATUS_FILE_END = 0; 2356 public static final int STATUS_FILE_ITEM = 100; 2357 2358 /** 2359 * Read all sync status back in to the initial engine state. 2360 */ 2361 private void readStatusLocked() { 2362 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 2363 Log.v(TAG_FILE, "Reading " + mStatusFile.getBaseFile()); 2364 } 2365 try { 2366 byte[] data = mStatusFile.readFully(); 2367 Parcel in = Parcel.obtain(); 2368 in.unmarshall(data, 0, data.length); 2369 in.setDataPosition(0); 2370 int token; 2371 while ((token=in.readInt()) != STATUS_FILE_END) { 2372 if (token == STATUS_FILE_ITEM) { 2373 SyncStatusInfo status = new SyncStatusInfo(in); 2374 if (mAuthorities.indexOfKey(status.authorityId) >= 0) { 2375 status.pending = false; 2376 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 2377 Log.v(TAG_FILE, "Adding status for id " + status.authorityId); 2378 } 2379 mSyncStatus.put(status.authorityId, status); 2380 } 2381 } else { 2382 // Ooops. 2383 Log.w(TAG, "Unknown status token: " + token); 2384 break; 2385 } 2386 } 2387 } catch (java.io.IOException e) { 2388 Log.i(TAG, "No initial status"); 2389 } 2390 } 2391 2392 /** 2393 * Write all sync status to the sync status file. 2394 */ 2395 private void writeStatusLocked() { 2396 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 2397 Log.v(TAG_FILE, "Writing new " + mStatusFile.getBaseFile()); 2398 } 2399 2400 // The file is being written, so we don't need to have a scheduled 2401 // write until the next change. 2402 removeMessages(MSG_WRITE_STATUS); 2403 2404 FileOutputStream fos = null; 2405 try { 2406 fos = mStatusFile.startWrite(); 2407 Parcel out = Parcel.obtain(); 2408 final int N = mSyncStatus.size(); 2409 for (int i=0; i<N; i++) { 2410 SyncStatusInfo status = mSyncStatus.valueAt(i); 2411 out.writeInt(STATUS_FILE_ITEM); 2412 status.writeToParcel(out, 0); 2413 } 2414 out.writeInt(STATUS_FILE_END); 2415 fos.write(out.marshall()); 2416 out.recycle(); 2417 2418 mStatusFile.finishWrite(fos); 2419 } catch (java.io.IOException e1) { 2420 Log.w(TAG, "Error writing status", e1); 2421 if (fos != null) { 2422 mStatusFile.failWrite(fos); 2423 } 2424 } 2425 } 2426 2427 public static final int PENDING_OPERATION_VERSION = 3; 2428 2429 /** Read all pending operations back in to the initial engine state. */ 2430 private void readPendingOperationsLocked() { 2431 FileInputStream fis = null; 2432 if (!mPendingFile.getBaseFile().exists()) { 2433 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 2434 Log.v(TAG_FILE, "No pending operation file."); 2435 } 2436 return; 2437 } 2438 try { 2439 fis = mPendingFile.openRead(); 2440 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 2441 Log.v(TAG_FILE, "Reading " + mPendingFile.getBaseFile()); 2442 } 2443 XmlPullParser parser; 2444 parser = Xml.newPullParser(); 2445 parser.setInput(fis, null); 2446 2447 int eventType = parser.getEventType(); 2448 while (eventType != XmlPullParser.START_TAG && 2449 eventType != XmlPullParser.END_DOCUMENT) { 2450 eventType = parser.next(); 2451 } 2452 if (eventType == XmlPullParser.END_DOCUMENT) return; // Nothing to read. 2453 2454 do { 2455 PendingOperation pop = null; 2456 if (eventType == XmlPullParser.START_TAG) { 2457 try { 2458 String tagName = parser.getName(); 2459 if (parser.getDepth() == 1 && "op".equals(tagName)) { 2460 // Verify version. 2461 String versionString = 2462 parser.getAttributeValue(null, XML_ATTR_VERSION); 2463 if (versionString == null || 2464 Integer.parseInt(versionString) != PENDING_OPERATION_VERSION) { 2465 Log.w(TAG, "Unknown pending operation version " + versionString); 2466 throw new java.io.IOException("Unknown version."); 2467 } 2468 int authorityId = Integer.valueOf(parser.getAttributeValue( 2469 null, XML_ATTR_AUTHORITYID)); 2470 boolean expedited = Boolean.valueOf(parser.getAttributeValue( 2471 null, XML_ATTR_EXPEDITED)); 2472 int syncSource = Integer.valueOf(parser.getAttributeValue( 2473 null, XML_ATTR_SOURCE)); 2474 int reason = Integer.valueOf(parser.getAttributeValue( 2475 null, XML_ATTR_REASON)); 2476 AuthorityInfo authority = mAuthorities.get(authorityId); 2477 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 2478 Log.v(TAG_FILE, authorityId + " " + expedited + " " + syncSource + " " 2479 + reason); 2480 } 2481 if (authority != null) { 2482 pop = new PendingOperation( 2483 authority, reason, syncSource, new Bundle(), expedited); 2484 pop.flatExtras = null; // No longer used. 2485 mPendingOperations.add(pop); 2486 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 2487 Log.v(TAG_FILE, "Adding pending op: " 2488 + pop.target 2489 + " src=" + pop.syncSource 2490 + " reason=" + pop.reason 2491 + " expedited=" + pop.expedited); 2492 } 2493 } else { 2494 // Skip non-existent authority. 2495 pop = null; 2496 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 2497 Log.v(TAG_FILE, "No authority found for " + authorityId 2498 + ", skipping"); 2499 } 2500 } 2501 } else if (parser.getDepth() == 2 && 2502 pop != null && 2503 "extra".equals(tagName)) { 2504 parseExtra(parser, pop.extras); 2505 } 2506 } catch (NumberFormatException e) { 2507 Log.d(TAG, "Invalid data in xml file.", e); 2508 } 2509 } 2510 eventType = parser.next(); 2511 } while(eventType != XmlPullParser.END_DOCUMENT); 2512 } catch (java.io.IOException e) { 2513 Log.w(TAG_FILE, "Error reading pending data.", e); 2514 } catch (XmlPullParserException e) { 2515 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 2516 Log.w(TAG_FILE, "Error parsing pending ops xml.", e); 2517 } 2518 } finally { 2519 if (fis != null) { 2520 try { 2521 fis.close(); 2522 } catch (java.io.IOException e1) {} 2523 } 2524 } 2525 } 2526 2527 static private byte[] flattenBundle(Bundle bundle) { 2528 byte[] flatData = null; 2529 Parcel parcel = Parcel.obtain(); 2530 try { 2531 bundle.writeToParcel(parcel, 0); 2532 flatData = parcel.marshall(); 2533 } finally { 2534 parcel.recycle(); 2535 } 2536 return flatData; 2537 } 2538 2539 static private Bundle unflattenBundle(byte[] flatData) { 2540 Bundle bundle; 2541 Parcel parcel = Parcel.obtain(); 2542 try { 2543 parcel.unmarshall(flatData, 0, flatData.length); 2544 parcel.setDataPosition(0); 2545 bundle = parcel.readBundle(); 2546 } catch (RuntimeException e) { 2547 // A RuntimeException is thrown if we were unable to parse the parcel. 2548 // Create an empty parcel in this case. 2549 bundle = new Bundle(); 2550 } finally { 2551 parcel.recycle(); 2552 } 2553 return bundle; 2554 } 2555 2556 private static final String XML_ATTR_VERSION = "version"; 2557 private static final String XML_ATTR_AUTHORITYID = "authority_id"; 2558 private static final String XML_ATTR_SOURCE = "source"; 2559 private static final String XML_ATTR_EXPEDITED = "expedited"; 2560 private static final String XML_ATTR_REASON = "reason"; 2561 2562 /** 2563 * Write all currently pending ops to the pending ops file. 2564 */ 2565 private void writePendingOperationsLocked() { 2566 final int N = mPendingOperations.size(); 2567 FileOutputStream fos = null; 2568 try { 2569 if (N == 0) { 2570 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)){ 2571 Log.v(TAG, "Truncating " + mPendingFile.getBaseFile()); 2572 } 2573 mPendingFile.truncate(); 2574 return; 2575 } 2576 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 2577 Log.v(TAG, "Writing new " + mPendingFile.getBaseFile()); 2578 } 2579 fos = mPendingFile.startWrite(); 2580 XmlSerializer out = new FastXmlSerializer(); 2581 out.setOutput(fos, "utf-8"); 2582 2583 for (int i = 0; i < N; i++) { 2584 PendingOperation pop = mPendingOperations.get(i); 2585 writePendingOperationLocked(pop, out); 2586 } 2587 out.endDocument(); 2588 mPendingFile.finishWrite(fos); 2589 } catch (java.io.IOException e1) { 2590 Log.w(TAG, "Error writing pending operations", e1); 2591 if (fos != null) { 2592 mPendingFile.failWrite(fos); 2593 } 2594 } 2595 } 2596 2597 /** Write all currently pending ops to the pending ops file. */ 2598 private void writePendingOperationLocked(PendingOperation pop, XmlSerializer out) 2599 throws IOException { 2600 // Pending operation. 2601 out.startTag(null, "op"); 2602 2603 out.attribute(null, XML_ATTR_VERSION, Integer.toString(PENDING_OPERATION_VERSION)); 2604 out.attribute(null, XML_ATTR_AUTHORITYID, Integer.toString(pop.authorityId)); 2605 out.attribute(null, XML_ATTR_SOURCE, Integer.toString(pop.syncSource)); 2606 out.attribute(null, XML_ATTR_EXPEDITED, Boolean.toString(pop.expedited)); 2607 out.attribute(null, XML_ATTR_REASON, Integer.toString(pop.reason)); 2608 extrasToXml(out, pop.extras); 2609 2610 out.endTag(null, "op"); 2611 } 2612 2613 /** 2614 * Append the given operation to the pending ops file; if unable to, 2615 * write all pending ops. 2616 */ 2617 private void appendPendingOperationLocked(PendingOperation op) { 2618 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 2619 Log.v(TAG, "Appending to " + mPendingFile.getBaseFile()); 2620 } 2621 FileOutputStream fos = null; 2622 try { 2623 fos = mPendingFile.openAppend(); 2624 } catch (java.io.IOException e) { 2625 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 2626 Log.v(TAG, "Failed append; writing full file"); 2627 } 2628 writePendingOperationsLocked(); 2629 return; 2630 } 2631 2632 try { 2633 XmlSerializer out = new FastXmlSerializer(); 2634 out.setOutput(fos, "utf-8"); 2635 writePendingOperationLocked(op, out); 2636 out.endDocument(); 2637 mPendingFile.finishWrite(fos); 2638 } catch (java.io.IOException e1) { 2639 Log.w(TAG, "Error writing appending operation", e1); 2640 mPendingFile.failWrite(fos); 2641 } finally { 2642 try { 2643 fos.close(); 2644 } catch (IOException e) {} 2645 } 2646 } 2647 2648 private void extrasToXml(XmlSerializer out, Bundle extras) throws java.io.IOException { 2649 for (String key : extras.keySet()) { 2650 out.startTag(null, "extra"); 2651 out.attribute(null, "name", key); 2652 final Object value = extras.get(key); 2653 if (value instanceof Long) { 2654 out.attribute(null, "type", "long"); 2655 out.attribute(null, "value1", value.toString()); 2656 } else if (value instanceof Integer) { 2657 out.attribute(null, "type", "integer"); 2658 out.attribute(null, "value1", value.toString()); 2659 } else if (value instanceof Boolean) { 2660 out.attribute(null, "type", "boolean"); 2661 out.attribute(null, "value1", value.toString()); 2662 } else if (value instanceof Float) { 2663 out.attribute(null, "type", "float"); 2664 out.attribute(null, "value1", value.toString()); 2665 } else if (value instanceof Double) { 2666 out.attribute(null, "type", "double"); 2667 out.attribute(null, "value1", value.toString()); 2668 } else if (value instanceof String) { 2669 out.attribute(null, "type", "string"); 2670 out.attribute(null, "value1", value.toString()); 2671 } else if (value instanceof Account) { 2672 out.attribute(null, "type", "account"); 2673 out.attribute(null, "value1", ((Account)value).name); 2674 out.attribute(null, "value2", ((Account)value).type); 2675 } 2676 out.endTag(null, "extra"); 2677 } 2678 } 2679 2680 private void requestSync(AuthorityInfo authorityInfo, int reason, Bundle extras) { 2681 if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID 2682 && mSyncRequestListener != null) { 2683 mSyncRequestListener.onSyncRequest(authorityInfo.target, reason, extras); 2684 } else { 2685 SyncRequest.Builder req = 2686 new SyncRequest.Builder() 2687 .syncOnce() 2688 .setExtras(extras); 2689 if (authorityInfo.target.target_provider) { 2690 req.setSyncAdapter(authorityInfo.target.account, authorityInfo.target.provider); 2691 } else { 2692 if (Log.isLoggable(TAG, Log.DEBUG)) { 2693 Log.d(TAG, "Unknown target, skipping sync request."); 2694 } 2695 return; 2696 } 2697 ContentResolver.requestSync(req.build()); 2698 } 2699 } 2700 2701 private void requestSync(Account account, int userId, int reason, String authority, 2702 Bundle extras) { 2703 // If this is happening in the system process, then call the syncrequest listener 2704 // to make a request back to the SyncManager directly. 2705 // If this is probably a test instance, then call back through the ContentResolver 2706 // which will know which userId to apply based on the Binder id. 2707 if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID 2708 && mSyncRequestListener != null) { 2709 mSyncRequestListener.onSyncRequest( 2710 new EndPoint(account, authority, userId), 2711 reason, 2712 extras); 2713 } else { 2714 ContentResolver.requestSync(account, authority, extras); 2715 } 2716 } 2717 2718 public static final int STATISTICS_FILE_END = 0; 2719 public static final int STATISTICS_FILE_ITEM_OLD = 100; 2720 public static final int STATISTICS_FILE_ITEM = 101; 2721 2722 /** 2723 * Read all sync statistics back in to the initial engine state. 2724 */ 2725 private void readStatisticsLocked() { 2726 try { 2727 byte[] data = mStatisticsFile.readFully(); 2728 Parcel in = Parcel.obtain(); 2729 in.unmarshall(data, 0, data.length); 2730 in.setDataPosition(0); 2731 int token; 2732 int index = 0; 2733 while ((token=in.readInt()) != STATISTICS_FILE_END) { 2734 if (token == STATISTICS_FILE_ITEM 2735 || token == STATISTICS_FILE_ITEM_OLD) { 2736 int day = in.readInt(); 2737 if (token == STATISTICS_FILE_ITEM_OLD) { 2738 day = day - 2009 + 14245; // Magic! 2739 } 2740 DayStats ds = new DayStats(day); 2741 ds.successCount = in.readInt(); 2742 ds.successTime = in.readLong(); 2743 ds.failureCount = in.readInt(); 2744 ds.failureTime = in.readLong(); 2745 if (index < mDayStats.length) { 2746 mDayStats[index] = ds; 2747 index++; 2748 } 2749 } else { 2750 // Ooops. 2751 Log.w(TAG, "Unknown stats token: " + token); 2752 break; 2753 } 2754 } 2755 } catch (java.io.IOException e) { 2756 Log.i(TAG, "No initial statistics"); 2757 } 2758 } 2759 2760 /** 2761 * Write all sync statistics to the sync status file. 2762 */ 2763 private void writeStatisticsLocked() { 2764 if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { 2765 Log.v(TAG, "Writing new " + mStatisticsFile.getBaseFile()); 2766 } 2767 2768 // The file is being written, so we don't need to have a scheduled 2769 // write until the next change. 2770 removeMessages(MSG_WRITE_STATISTICS); 2771 2772 FileOutputStream fos = null; 2773 try { 2774 fos = mStatisticsFile.startWrite(); 2775 Parcel out = Parcel.obtain(); 2776 final int N = mDayStats.length; 2777 for (int i=0; i<N; i++) { 2778 DayStats ds = mDayStats[i]; 2779 if (ds == null) { 2780 break; 2781 } 2782 out.writeInt(STATISTICS_FILE_ITEM); 2783 out.writeInt(ds.day); 2784 out.writeInt(ds.successCount); 2785 out.writeLong(ds.successTime); 2786 out.writeInt(ds.failureCount); 2787 out.writeLong(ds.failureTime); 2788 } 2789 out.writeInt(STATISTICS_FILE_END); 2790 fos.write(out.marshall()); 2791 out.recycle(); 2792 2793 mStatisticsFile.finishWrite(fos); 2794 } catch (java.io.IOException e1) { 2795 Log.w(TAG, "Error writing stats", e1); 2796 if (fos != null) { 2797 mStatisticsFile.failWrite(fos); 2798 } 2799 } 2800 } 2801 2802 /** 2803 * Dump state of PendingOperations. 2804 */ 2805 public void dumpPendingOperations(StringBuilder sb) { 2806 sb.append("Pending Ops: ").append(mPendingOperations.size()).append(" operation(s)\n"); 2807 for (PendingOperation pop : mPendingOperations) { 2808 sb.append("(info: " + pop.target.toString()) 2809 .append(", extras: " + pop.extras) 2810 .append(")\n"); 2811 } 2812 } 2813 } 2814