SyncManager.java revision 44037e6c41f8167bf5ac51fcb684ad28b20073ce
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.content; 18 19import com.google.android.collect.Maps; 20 21import com.android.internal.R; 22import com.android.internal.util.ArrayUtils; 23 24import android.accounts.Account; 25import android.accounts.AccountManager; 26import android.accounts.OnAccountsUpdateListener; 27import android.app.AlarmManager; 28import android.app.Notification; 29import android.app.NotificationManager; 30import android.app.PendingIntent; 31import android.content.pm.ApplicationInfo; 32import android.content.pm.IPackageManager; 33import android.content.pm.PackageManager; 34import android.content.pm.ResolveInfo; 35import android.content.pm.RegisteredServicesCache; 36import android.content.pm.ProviderInfo; 37import android.content.pm.RegisteredServicesCacheListener; 38import android.net.ConnectivityManager; 39import android.net.NetworkInfo; 40import android.os.Bundle; 41import android.os.Handler; 42import android.os.HandlerThread; 43import android.os.IBinder; 44import android.os.Looper; 45import android.os.Message; 46import android.os.PowerManager; 47import android.os.Process; 48import android.os.RemoteException; 49import android.os.ServiceManager; 50import android.os.SystemClock; 51import android.os.SystemProperties; 52import android.provider.Settings; 53import android.text.format.DateUtils; 54import android.text.format.Time; 55import android.util.Config; 56import android.util.EventLog; 57import android.util.Log; 58 59import java.io.DataInputStream; 60import java.io.DataOutputStream; 61import java.io.File; 62import java.io.FileDescriptor; 63import java.io.FileInputStream; 64import java.io.FileNotFoundException; 65import java.io.FileOutputStream; 66import java.io.IOException; 67import java.io.PrintWriter; 68import java.util.ArrayList; 69import java.util.HashMap; 70import java.util.HashSet; 71import java.util.Iterator; 72import java.util.List; 73import java.util.Map; 74import java.util.PriorityQueue; 75import java.util.Random; 76import java.util.Collection; 77import java.util.concurrent.CountDownLatch; 78 79/** 80 * @hide 81 */ 82class SyncManager implements OnAccountsUpdateListener { 83 private static final String TAG = "SyncManager"; 84 85 // used during dumping of the Sync history 86 private static final long MILLIS_IN_HOUR = 1000 * 60 * 60; 87 private static final long MILLIS_IN_DAY = MILLIS_IN_HOUR * 24; 88 private static final long MILLIS_IN_WEEK = MILLIS_IN_DAY * 7; 89 private static final long MILLIS_IN_4WEEKS = MILLIS_IN_WEEK * 4; 90 91 /** Delay a sync due to local changes this long. In milliseconds */ 92 private static final long LOCAL_SYNC_DELAY; 93 94 /** 95 * If a sync takes longer than this and the sync queue is not empty then we will 96 * cancel it and add it back to the end of the sync queue. In milliseconds. 97 */ 98 private static final long MAX_TIME_PER_SYNC; 99 100 static { 101 String localSyncDelayString = SystemProperties.get("sync.local_sync_delay"); 102 long localSyncDelay = 30 * 1000; // 30 seconds 103 if (localSyncDelayString != null) { 104 try { 105 localSyncDelay = Long.parseLong(localSyncDelayString); 106 } catch (NumberFormatException nfe) { 107 // ignore, use default 108 } 109 } 110 LOCAL_SYNC_DELAY = localSyncDelay; 111 112 String maxTimePerSyncString = SystemProperties.get("sync.max_time_per_sync"); 113 long maxTimePerSync = 5 * 60 * 1000; // 5 minutes 114 if (maxTimePerSyncString != null) { 115 try { 116 maxTimePerSync = Long.parseLong(maxTimePerSyncString); 117 } catch (NumberFormatException nfe) { 118 // ignore, use default 119 } 120 } 121 MAX_TIME_PER_SYNC = maxTimePerSync; 122 } 123 124 private static final long SYNC_NOTIFICATION_DELAY = 30 * 1000; // 30 seconds 125 126 /** 127 * When retrying a sync for the first time use this delay. After that 128 * the retry time will double until it reached MAX_SYNC_RETRY_TIME. 129 * In milliseconds. 130 */ 131 private static final long INITIAL_SYNC_RETRY_TIME_IN_MS = 30 * 1000; // 30 seconds 132 133 /** 134 * Default the max sync retry time to this value. 135 */ 136 private static final long DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS = 60 * 60; // one hour 137 138 /** 139 * An error notification is sent if sync of any of the providers has been failing for this long. 140 */ 141 private static final long ERROR_NOTIFICATION_DELAY_MS = 1000 * 60 * 10; // 10 minutes 142 143 private static final String SYNC_WAKE_LOCK = "SyncManagerSyncWakeLock"; 144 private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarmWakeLock"; 145 146 private Context mContext; 147 148 private String mStatusText = ""; 149 private long mHeartbeatTime = 0; 150 151 private volatile Account[] mAccounts = null; 152 153 volatile private PowerManager.WakeLock mSyncWakeLock; 154 volatile private PowerManager.WakeLock mHandleAlarmWakeLock; 155 volatile private boolean mDataConnectionIsConnected = false; 156 volatile private boolean mStorageIsLow = false; 157 158 private final NotificationManager mNotificationMgr; 159 private AlarmManager mAlarmService = null; 160 private HandlerThread mSyncThread; 161 162 private volatile IPackageManager mPackageManager; 163 164 private final SyncStorageEngine mSyncStorageEngine; 165 private final SyncQueue mSyncQueue; 166 167 private ActiveSyncContext mActiveSyncContext = null; 168 169 // set if the sync error indicator should be reported. 170 private boolean mNeedSyncErrorNotification = false; 171 // set if the sync active indicator should be reported 172 private boolean mNeedSyncActiveNotification = false; 173 174 private volatile boolean mSyncPollInitialized; 175 private final PendingIntent mSyncAlarmIntent; 176 private final PendingIntent mSyncPollAlarmIntent; 177 // Synchronized on "this". Instead of using this directly one should instead call 178 // its accessor, getConnManager(). 179 private ConnectivityManager mConnManagerDoNotUseDirectly; 180 181 private final SyncAdaptersCache mSyncAdapters; 182 183 private BroadcastReceiver mStorageIntentReceiver = 184 new BroadcastReceiver() { 185 public void onReceive(Context context, Intent intent) { 186 String action = intent.getAction(); 187 if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) { 188 if (Log.isLoggable(TAG, Log.VERBOSE)) { 189 Log.v(TAG, "Internal storage is low."); 190 } 191 mStorageIsLow = true; 192 cancelActiveSync(null /* any account */, null /* any authority */); 193 } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) { 194 if (Log.isLoggable(TAG, Log.VERBOSE)) { 195 Log.v(TAG, "Internal storage is ok."); 196 } 197 mStorageIsLow = false; 198 sendCheckAlarmsMessage(); 199 } 200 } 201 }; 202 203 private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() { 204 public void onReceive(Context context, Intent intent) { 205 mSyncHandler.onBootCompleted(); 206 } 207 }; 208 209 private BroadcastReceiver mBackgroundDataSettingChanged = new BroadcastReceiver() { 210 public void onReceive(Context context, Intent intent) { 211 if (getConnectivityManager().getBackgroundDataSetting()) { 212 scheduleSync(null /* account */, null /* authority */, new Bundle(), 0 /* delay */, 213 false /* onlyThoseWithUnknownSyncableState */); 214 } 215 } 216 }; 217 218 public void onAccountsUpdated(Account[] accounts) { 219 // remember if this was the first time this was called after an update 220 final boolean justBootedUp = mAccounts == null; 221 mAccounts = accounts; 222 223 // if a sync is in progress yet it is no longer in the accounts list, 224 // cancel it 225 ActiveSyncContext activeSyncContext = mActiveSyncContext; 226 if (activeSyncContext != null) { 227 if (!ArrayUtils.contains(accounts, activeSyncContext.mSyncOperation.account)) { 228 Log.d(TAG, "canceling sync since the account has been removed"); 229 sendSyncFinishedOrCanceledMessage(activeSyncContext, 230 null /* no result since this is a cancel */); 231 } 232 } 233 234 // we must do this since we don't bother scheduling alarms when 235 // the accounts are not set yet 236 sendCheckAlarmsMessage(); 237 238 mSyncStorageEngine.doDatabaseCleanup(accounts); 239 240 if (accounts.length > 0) { 241 // If this is the first time this was called after a bootup then 242 // the accounts haven't really changed, instead they were just loaded 243 // from the AccountManager. Otherwise at least one of the accounts 244 // has a change. 245 // 246 // If there was a real account change then force a sync of all accounts. 247 // This is a bit of overkill, but at least it will end up retrying syncs 248 // that failed due to an authentication failure and thus will recover if the 249 // account change was a password update. 250 // 251 // If this was the bootup case then don't sync everything, instead only 252 // sync those that have an unknown syncable state, which will give them 253 // a chance to set their syncable state. 254 boolean onlyThoseWithUnkownSyncableState = justBootedUp; 255 scheduleSync(null, null, null, 0 /* no delay */, onlyThoseWithUnkownSyncableState); 256 } 257 } 258 259 private BroadcastReceiver mConnectivityIntentReceiver = 260 new BroadcastReceiver() { 261 public void onReceive(Context context, Intent intent) { 262 NetworkInfo networkInfo = 263 intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); 264 NetworkInfo.State state = (networkInfo == null ? NetworkInfo.State.UNKNOWN : 265 networkInfo.getState()); 266 if (Log.isLoggable(TAG, Log.VERBOSE)) { 267 Log.v(TAG, "received connectivity action. network info: " + networkInfo); 268 } 269 270 // only pay attention to the CONNECTED and DISCONNECTED states. 271 // if connected, we are connected. 272 // if disconnected, we may not be connected. in some cases, we may be connected on 273 // a different network. 274 // e.g., if switching from GPRS to WiFi, we may receive the CONNECTED to WiFi and 275 // DISCONNECTED for GPRS in any order. if we receive the CONNECTED first, and then 276 // a DISCONNECTED, we want to make sure we set mDataConnectionIsConnected to true 277 // since we still have a WiFi connection. 278 switch (state) { 279 case CONNECTED: 280 mDataConnectionIsConnected = true; 281 break; 282 case DISCONNECTED: 283 if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false)) { 284 mDataConnectionIsConnected = false; 285 } else { 286 mDataConnectionIsConnected = true; 287 } 288 break; 289 default: 290 // ignore the rest of the states -- leave our boolean alone. 291 } 292 if (mDataConnectionIsConnected) { 293 initializeSyncPoll(); 294 sendCheckAlarmsMessage(); 295 } 296 } 297 }; 298 299 private BroadcastReceiver mShutdownIntentReceiver = 300 new BroadcastReceiver() { 301 public void onReceive(Context context, Intent intent) { 302 Log.w(TAG, "Writing sync state before shutdown..."); 303 getSyncStorageEngine().writeAllState(); 304 } 305 }; 306 307 private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM"; 308 private static final String SYNC_POLL_ALARM = "android.content.syncmanager.SYNC_POLL_ALARM"; 309 private final SyncHandler mSyncHandler; 310 311 private static final int MAX_SYNC_POLL_DELAY_SECONDS = 36 * 60 * 60; // 36 hours 312 private static final int MIN_SYNC_POLL_DELAY_SECONDS = 24 * 60 * 60; // 24 hours 313 314 private static final String SYNCMANAGER_PREFS_FILENAME = "/data/system/syncmanager.prefs"; 315 316 private final boolean mFactoryTest; 317 318 private volatile boolean mBootCompleted = false; 319 320 private ConnectivityManager getConnectivityManager() { 321 synchronized (this) { 322 if (mConnManagerDoNotUseDirectly == null) { 323 mConnManagerDoNotUseDirectly = (ConnectivityManager)mContext.getSystemService( 324 Context.CONNECTIVITY_SERVICE); 325 } 326 return mConnManagerDoNotUseDirectly; 327 } 328 } 329 330 public SyncManager(Context context, boolean factoryTest) { 331 mFactoryTest = factoryTest; 332 333 // Initialize the SyncStorageEngine first, before registering observers 334 // and creating threads and so on; it may fail if the disk is full. 335 SyncStorageEngine.init(context); 336 mSyncStorageEngine = SyncStorageEngine.getSingleton(); 337 mSyncQueue = new SyncQueue(mSyncStorageEngine); 338 339 mContext = context; 340 341 mSyncThread = new HandlerThread("SyncHandlerThread", Process.THREAD_PRIORITY_BACKGROUND); 342 mSyncThread.start(); 343 mSyncHandler = new SyncHandler(mSyncThread.getLooper()); 344 345 mPackageManager = null; 346 347 mSyncAdapters = new SyncAdaptersCache(mContext); 348 mSyncAdapters.setListener(new RegisteredServicesCacheListener<SyncAdapterType>() { 349 public void onServiceChanged(SyncAdapterType type, boolean removed) { 350 if (!removed) { 351 scheduleSync(null, type.authority, null, 0 /* no delay */, 352 false /* onlyThoseWithUnkownSyncableState */); 353 } 354 } 355 }, mSyncHandler); 356 357 mSyncAlarmIntent = PendingIntent.getBroadcast( 358 mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0); 359 360 mSyncPollAlarmIntent = PendingIntent.getBroadcast( 361 mContext, 0 /* ignored */, new Intent(SYNC_POLL_ALARM), 0); 362 363 IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); 364 context.registerReceiver(mConnectivityIntentReceiver, intentFilter); 365 366 if (!factoryTest) { 367 intentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED); 368 context.registerReceiver(mBootCompletedReceiver, intentFilter); 369 } 370 371 intentFilter = new IntentFilter(ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED); 372 context.registerReceiver(mBackgroundDataSettingChanged, intentFilter); 373 374 intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW); 375 intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); 376 context.registerReceiver(mStorageIntentReceiver, intentFilter); 377 378 intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN); 379 intentFilter.setPriority(100); 380 context.registerReceiver(mShutdownIntentReceiver, intentFilter); 381 382 if (!factoryTest) { 383 mNotificationMgr = (NotificationManager) 384 context.getSystemService(Context.NOTIFICATION_SERVICE); 385 context.registerReceiver(new SyncAlarmIntentReceiver(), 386 new IntentFilter(ACTION_SYNC_ALARM)); 387 } else { 388 mNotificationMgr = null; 389 } 390 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 391 mSyncWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, SYNC_WAKE_LOCK); 392 mSyncWakeLock.setReferenceCounted(false); 393 394 // This WakeLock is used to ensure that we stay awake between the time that we receive 395 // a sync alarm notification and when we finish processing it. We need to do this 396 // because we don't do the work in the alarm handler, rather we do it in a message 397 // handler. 398 mHandleAlarmWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 399 HANDLE_SYNC_ALARM_WAKE_LOCK); 400 mHandleAlarmWakeLock.setReferenceCounted(false); 401 402 mSyncStorageEngine.addStatusChangeListener( 403 ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, new ISyncStatusObserver.Stub() { 404 public void onStatusChanged(int which) { 405 // force the sync loop to run if the settings change 406 sendCheckAlarmsMessage(); 407 } 408 }); 409 410 if (!factoryTest) { 411 AccountManager.get(mContext).addOnAccountsUpdatedListener(SyncManager.this, 412 mSyncHandler, false /* updateImmediately */); 413 // do this synchronously to ensure we have the accounts before this call returns 414 onAccountsUpdated(AccountManager.get(mContext).getAccounts()); 415 } 416 } 417 418 private synchronized void initializeSyncPoll() { 419 if (mSyncPollInitialized) return; 420 mSyncPollInitialized = true; 421 422 mContext.registerReceiver(new SyncPollAlarmReceiver(), new IntentFilter(SYNC_POLL_ALARM)); 423 424 // load the next poll time from shared preferences 425 long absoluteAlarmTime = readSyncPollTime(); 426 427 if (Log.isLoggable(TAG, Log.VERBOSE)) { 428 Log.v(TAG, "initializeSyncPoll: absoluteAlarmTime is " + absoluteAlarmTime); 429 } 430 431 // Convert absoluteAlarmTime to elapsed realtime. If this time was in the past then 432 // schedule the poll immediately, if it is too far in the future then cap it at 433 // MAX_SYNC_POLL_DELAY_SECONDS. 434 long absoluteNow = System.currentTimeMillis(); 435 long relativeNow = SystemClock.elapsedRealtime(); 436 long relativeAlarmTime = relativeNow; 437 if (absoluteAlarmTime > absoluteNow) { 438 long delayInMs = absoluteAlarmTime - absoluteNow; 439 final int maxDelayInMs = MAX_SYNC_POLL_DELAY_SECONDS * 1000; 440 if (delayInMs > maxDelayInMs) { 441 delayInMs = MAX_SYNC_POLL_DELAY_SECONDS * 1000; 442 } 443 relativeAlarmTime += delayInMs; 444 } 445 446 // schedule an alarm for the next poll time 447 scheduleSyncPollAlarm(relativeAlarmTime); 448 } 449 450 private void scheduleSyncPollAlarm(long relativeAlarmTime) { 451 if (Log.isLoggable(TAG, Log.VERBOSE)) { 452 Log.v(TAG, "scheduleSyncPollAlarm: relativeAlarmTime is " + relativeAlarmTime 453 + ", now is " + SystemClock.elapsedRealtime() 454 + ", delay is " + (relativeAlarmTime - SystemClock.elapsedRealtime())); 455 } 456 ensureAlarmService(); 457 mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, relativeAlarmTime, 458 mSyncPollAlarmIntent); 459 } 460 461 /** 462 * Return a random value v that satisfies minValue <= v < maxValue. The difference between 463 * maxValue and minValue must be less than Integer.MAX_VALUE. 464 */ 465 private long jitterize(long minValue, long maxValue) { 466 Random random = new Random(SystemClock.elapsedRealtime()); 467 long spread = maxValue - minValue; 468 if (spread > Integer.MAX_VALUE) { 469 throw new IllegalArgumentException("the difference between the maxValue and the " 470 + "minValue must be less than " + Integer.MAX_VALUE); 471 } 472 return minValue + random.nextInt((int)spread); 473 } 474 475 private void handleSyncPollAlarm() { 476 // determine the next poll time 477 long delayMs = jitterize(MIN_SYNC_POLL_DELAY_SECONDS, MAX_SYNC_POLL_DELAY_SECONDS) * 1000; 478 long nextRelativePollTimeMs = SystemClock.elapsedRealtime() + delayMs; 479 480 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "handleSyncPollAlarm: delay " + delayMs); 481 482 // write the absolute time to shared preferences 483 writeSyncPollTime(System.currentTimeMillis() + delayMs); 484 485 // schedule an alarm for the next poll time 486 scheduleSyncPollAlarm(nextRelativePollTimeMs); 487 488 // perform a poll 489 scheduleSync(null /* sync all syncable accounts */, null /* sync all syncable providers */, 490 new Bundle(), 0 /* no delay */, false /* onlyThoseWithUnkownSyncableState */); 491 } 492 493 private void writeSyncPollTime(long when) { 494 File f = new File(SYNCMANAGER_PREFS_FILENAME); 495 DataOutputStream str = null; 496 try { 497 str = new DataOutputStream(new FileOutputStream(f)); 498 str.writeLong(when); 499 } catch (FileNotFoundException e) { 500 Log.w(TAG, "error writing to file " + f, e); 501 } catch (IOException e) { 502 Log.w(TAG, "error writing to file " + f, e); 503 } finally { 504 if (str != null) { 505 try { 506 str.close(); 507 } catch (IOException e) { 508 Log.w(TAG, "error closing file " + f, e); 509 } 510 } 511 } 512 } 513 514 private long readSyncPollTime() { 515 File f = new File(SYNCMANAGER_PREFS_FILENAME); 516 517 DataInputStream str = null; 518 try { 519 str = new DataInputStream(new FileInputStream(f)); 520 return str.readLong(); 521 } catch (FileNotFoundException e) { 522 writeSyncPollTime(0); 523 } catch (IOException e) { 524 Log.w(TAG, "error reading file " + f, e); 525 } finally { 526 if (str != null) { 527 try { 528 str.close(); 529 } catch (IOException e) { 530 Log.w(TAG, "error closing file " + f, e); 531 } 532 } 533 } 534 return 0; 535 } 536 537 public ActiveSyncContext getActiveSyncContext() { 538 return mActiveSyncContext; 539 } 540 541 public SyncStorageEngine getSyncStorageEngine() { 542 return mSyncStorageEngine; 543 } 544 545 private void ensureAlarmService() { 546 if (mAlarmService == null) { 547 mAlarmService = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); 548 } 549 } 550 551 public Account getSyncingAccount() { 552 ActiveSyncContext activeSyncContext = mActiveSyncContext; 553 return (activeSyncContext != null) ? activeSyncContext.mSyncOperation.account : null; 554 } 555 556 /** 557 * Returns whether or not sync is enabled. Sync can be enabled by 558 * setting the system property "ro.config.sync" to the value "yes". 559 * This is normally done at boot time on builds that support sync. 560 * @return true if sync is enabled 561 */ 562 private boolean isSyncEnabled() { 563 // Require the precise value "yes" to discourage accidental activation. 564 return "yes".equals(SystemProperties.get("ro.config.sync")); 565 } 566 567 /** 568 * Initiate a sync. This can start a sync for all providers 569 * (pass null to url, set onlyTicklable to false), only those 570 * providers that are marked as ticklable (pass null to url, 571 * set onlyTicklable to true), or a specific provider (set url 572 * to the content url of the provider). 573 * 574 * <p>If the ContentResolver.SYNC_EXTRAS_UPLOAD boolean in extras is 575 * true then initiate a sync that just checks for local changes to send 576 * to the server, otherwise initiate a sync that first gets any 577 * changes from the server before sending local changes back to 578 * the server. 579 * 580 * <p>If a specific provider is being synced (the url is non-null) 581 * then the extras can contain SyncAdapter-specific information 582 * to control what gets synced (e.g. which specific feed to sync). 583 * 584 * <p>You'll start getting callbacks after this. 585 * 586 * @param requestedAccount the account to sync, may be null to signify all accounts 587 * @param requestedAuthority the authority to sync, may be null to indicate all authorities 588 * @param extras a Map of SyncAdapter-specific information to control 589* syncs of a specific provider. Can be null. Is ignored 590* if the url is null. 591 * @param delay how many milliseconds in the future to wait before performing this 592 * @param onlyThoseWithUnkownSyncableState 593 */ 594 public void scheduleSync(Account requestedAccount, String requestedAuthority, 595 Bundle extras, long delay, boolean onlyThoseWithUnkownSyncableState) { 596 boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); 597 598 if (mAccounts == null) { 599 Log.e(TAG, "scheduleSync: the accounts aren't known yet, this should never happen"); 600 return; 601 } 602 603 if (!isSyncEnabled()) { 604 if (isLoggable) { 605 Log.v(TAG, "not syncing because sync is disabled"); 606 } 607 setStatusText("Sync is disabled."); 608 return; 609 } 610 611 final boolean backgroundDataUsageAllowed = !mBootCompleted || 612 getConnectivityManager().getBackgroundDataSetting(); 613 614 if (!mDataConnectionIsConnected) setStatusText("No data connection"); 615 if (mStorageIsLow) setStatusText("Memory low"); 616 617 if (extras == null) extras = new Bundle(); 618 619 Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false); 620 if (expedited) { 621 delay = -1; // this means schedule at the front of the queue 622 } 623 624 Account[] accounts; 625 if (requestedAccount != null) { 626 accounts = new Account[]{requestedAccount}; 627 } else { 628 // if the accounts aren't configured yet then we can't support an account-less 629 // sync request 630 accounts = mAccounts; 631 if (accounts == null) { 632 // not ready yet 633 if (isLoggable) { 634 Log.v(TAG, "scheduleSync: no accounts yet, dropping"); 635 } 636 return; 637 } 638 if (accounts.length == 0) { 639 if (isLoggable) { 640 Log.v(TAG, "scheduleSync: no accounts configured, dropping"); 641 } 642 setStatusText("No accounts are configured."); 643 return; 644 } 645 } 646 647 final boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false); 648 final boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false); 649 650 int source; 651 if (uploadOnly) { 652 source = SyncStorageEngine.SOURCE_LOCAL; 653 } else if (manualSync) { 654 source = SyncStorageEngine.SOURCE_USER; 655 } else if (requestedAuthority == null) { 656 source = SyncStorageEngine.SOURCE_POLL; 657 } else { 658 // this isn't strictly server, since arbitrary callers can (and do) request 659 // a non-forced two-way sync on a specific url 660 source = SyncStorageEngine.SOURCE_SERVER; 661 } 662 663 // Compile a list of authorities that have sync adapters. 664 // For each authority sync each account that matches a sync adapter. 665 final HashSet<String> syncableAuthorities = new HashSet<String>(); 666 for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter : 667 mSyncAdapters.getAllServices()) { 668 syncableAuthorities.add(syncAdapter.type.authority); 669 } 670 671 // if the url was specified then replace the list of authorities with just this authority 672 // or clear it if this authority isn't syncable 673 if (requestedAuthority != null) { 674 final boolean hasSyncAdapter = syncableAuthorities.contains(requestedAuthority); 675 syncableAuthorities.clear(); 676 if (hasSyncAdapter) syncableAuthorities.add(requestedAuthority); 677 } 678 679 final boolean masterSyncAutomatically = mSyncStorageEngine.getMasterSyncAutomatically(); 680 681 for (String authority : syncableAuthorities) { 682 for (Account account : accounts) { 683 int isSyncable = mSyncStorageEngine.getIsSyncable(account, authority); 684 if (isSyncable == 0) { 685 continue; 686 } 687 if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) { 688 continue; 689 } 690 final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo = 691 mSyncAdapters.getServiceInfo( 692 SyncAdapterType.newKey(authority, account.type)); 693 if (syncAdapterInfo != null) { 694 if (!syncAdapterInfo.type.supportsUploading() && uploadOnly) { 695 continue; 696 } 697 698 // make this an initialization sync if the isSyncable state is unknown 699 Bundle extrasCopy = extras; 700 long delayCopy = delay; 701 if (isSyncable < 0) { 702 extrasCopy = new Bundle(extras); 703 extrasCopy.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true); 704 delayCopy = -1; // expedite this 705 } else { 706 final boolean syncAutomatically = masterSyncAutomatically 707 && mSyncStorageEngine.getSyncAutomatically(account, authority); 708 boolean syncAllowed = 709 manualSync || (backgroundDataUsageAllowed && syncAutomatically); 710 if (!syncAllowed) { 711 if (isLoggable) { 712 Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority 713 + " is not allowed, dropping request"); 714 } 715 continue; 716 } 717 } 718 if (isLoggable) { 719 Log.v(TAG, "scheduleSync:" 720 + " delay " + delayCopy 721 + ", source " + source 722 + ", account " + account 723 + ", authority " + authority 724 + ", extras " + extrasCopy); 725 } 726 scheduleSyncOperation( 727 new SyncOperation(account, source, authority, extrasCopy, delayCopy)); 728 } 729 } 730 } 731 } 732 733 private void setStatusText(String message) { 734 mStatusText = message; 735 } 736 737 public void scheduleLocalSync(Account account, String authority) { 738 final Bundle extras = new Bundle(); 739 extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true); 740 scheduleSync(account, authority, extras, LOCAL_SYNC_DELAY, 741 false /* onlyThoseWithUnkownSyncableState */); 742 } 743 744 private IPackageManager getPackageManager() { 745 // Don't bother synchronizing on this. The worst that can happen is that two threads 746 // can try to get the package manager at the same time but only one result gets 747 // used. Since there is only one package manager in the system this doesn't matter. 748 if (mPackageManager == null) { 749 IBinder b = ServiceManager.getService("package"); 750 mPackageManager = IPackageManager.Stub.asInterface(b); 751 } 752 return mPackageManager; 753 } 754 755 public SyncAdapterType[] getSyncAdapterTypes() { 756 final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> serviceInfos = 757 mSyncAdapters.getAllServices(); 758 SyncAdapterType[] types = new SyncAdapterType[serviceInfos.size()]; 759 int i = 0; 760 for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> serviceInfo : serviceInfos) { 761 types[i] = serviceInfo.type; 762 ++i; 763 } 764 return types; 765 } 766 767 public void updateHeartbeatTime() { 768 mHeartbeatTime = SystemClock.elapsedRealtime(); 769 mSyncStorageEngine.reportActiveChange(); 770 } 771 772 private void sendSyncAlarmMessage() { 773 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_ALARM"); 774 mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_SYNC_ALARM); 775 } 776 777 private void sendCheckAlarmsMessage() { 778 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CHECK_ALARMS"); 779 mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_CHECK_ALARMS); 780 } 781 782 private void sendSyncFinishedOrCanceledMessage(ActiveSyncContext syncContext, 783 SyncResult syncResult) { 784 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_FINISHED"); 785 Message msg = mSyncHandler.obtainMessage(); 786 msg.what = SyncHandler.MESSAGE_SYNC_FINISHED; 787 msg.obj = new SyncHandlerMessagePayload(syncContext, syncResult); 788 mSyncHandler.sendMessage(msg); 789 } 790 791 class SyncHandlerMessagePayload { 792 public final ActiveSyncContext activeSyncContext; 793 public final SyncResult syncResult; 794 795 SyncHandlerMessagePayload(ActiveSyncContext syncContext, SyncResult syncResult) { 796 this.activeSyncContext = syncContext; 797 this.syncResult = syncResult; 798 } 799 } 800 801 class SyncAlarmIntentReceiver extends BroadcastReceiver { 802 public void onReceive(Context context, Intent intent) { 803 mHandleAlarmWakeLock.acquire(); 804 sendSyncAlarmMessage(); 805 } 806 } 807 808 class SyncPollAlarmReceiver extends BroadcastReceiver { 809 public void onReceive(Context context, Intent intent) { 810 handleSyncPollAlarm(); 811 } 812 } 813 814 private void rescheduleImmediately(SyncOperation syncOperation) { 815 SyncOperation rescheduledSyncOperation = new SyncOperation(syncOperation); 816 rescheduledSyncOperation.setDelay(0); 817 scheduleSyncOperation(rescheduledSyncOperation); 818 } 819 820 private long rescheduleWithDelay(SyncOperation syncOperation) { 821 long newDelayInMs; 822 823 if (syncOperation.delay <= 0) { 824 // The initial delay is the jitterized INITIAL_SYNC_RETRY_TIME_IN_MS 825 newDelayInMs = jitterize(INITIAL_SYNC_RETRY_TIME_IN_MS, 826 (long)(INITIAL_SYNC_RETRY_TIME_IN_MS * 1.1)); 827 } else { 828 // Subsequent delays are the double of the previous delay 829 newDelayInMs = syncOperation.delay * 2; 830 } 831 832 // Cap the delay 833 long maxSyncRetryTimeInSeconds = Settings.Gservices.getLong(mContext.getContentResolver(), 834 Settings.Gservices.SYNC_MAX_RETRY_DELAY_IN_SECONDS, 835 DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS); 836 if (newDelayInMs > maxSyncRetryTimeInSeconds * 1000) { 837 newDelayInMs = maxSyncRetryTimeInSeconds * 1000; 838 } 839 840 SyncOperation rescheduledSyncOperation = new SyncOperation(syncOperation); 841 rescheduledSyncOperation.setDelay(newDelayInMs); 842 scheduleSyncOperation(rescheduledSyncOperation); 843 return newDelayInMs; 844 } 845 846 /** 847 * Cancel the active sync if it matches the authority and account. 848 * @param account limit the cancelations to syncs with this account, if non-null 849 * @param authority limit the cancelations to syncs with this authority, if non-null 850 */ 851 public void cancelActiveSync(Account account, String authority) { 852 ActiveSyncContext activeSyncContext = mActiveSyncContext; 853 if (activeSyncContext != null) { 854 // if an authority was specified then only cancel the sync if it matches 855 if (account != null) { 856 if (!account.equals(activeSyncContext.mSyncOperation.account)) { 857 return; 858 } 859 } 860 // if an account was specified then only cancel the sync if it matches 861 if (authority != null) { 862 if (!authority.equals(activeSyncContext.mSyncOperation.authority)) { 863 return; 864 } 865 } 866 sendSyncFinishedOrCanceledMessage(activeSyncContext, 867 null /* no result since this is a cancel */); 868 } 869 } 870 871 /** 872 * Create and schedule a SyncOperation. 873 * 874 * @param syncOperation the SyncOperation to schedule 875 */ 876 public void scheduleSyncOperation(SyncOperation syncOperation) { 877 // If this operation is expedited and there is a sync in progress then 878 // reschedule the current operation and send a cancel for it. 879 final boolean expedited = syncOperation.delay < 0; 880 final ActiveSyncContext activeSyncContext = mActiveSyncContext; 881 if (expedited && activeSyncContext != null) { 882 final boolean activeIsExpedited = activeSyncContext.mSyncOperation.delay < 0; 883 final boolean hasSameKey = 884 activeSyncContext.mSyncOperation.key.equals(syncOperation.key); 885 // This request is expedited and there is a sync in progress. 886 // Interrupt the current sync only if it is not expedited and if it has a different 887 // key than the one we are scheduling. 888 if (!activeIsExpedited && !hasSameKey) { 889 rescheduleImmediately(activeSyncContext.mSyncOperation); 890 sendSyncFinishedOrCanceledMessage(activeSyncContext, 891 null /* no result since this is a cancel */); 892 } 893 } 894 895 boolean operationEnqueued; 896 synchronized (mSyncQueue) { 897 operationEnqueued = mSyncQueue.add(syncOperation); 898 } 899 900 if (operationEnqueued) { 901 if (Log.isLoggable(TAG, Log.VERBOSE)) { 902 Log.v(TAG, "scheduleSyncOperation: enqueued " + syncOperation); 903 } 904 sendCheckAlarmsMessage(); 905 } else { 906 if (Log.isLoggable(TAG, Log.VERBOSE)) { 907 Log.v(TAG, "scheduleSyncOperation: dropping duplicate sync operation " 908 + syncOperation); 909 } 910 } 911 } 912 913 /** 914 * Remove scheduled sync operations. 915 * @param account limit the removals to operations with this account, if non-null 916 * @param authority limit the removals to operations with this authority, if non-null 917 */ 918 public void clearScheduledSyncOperations(Account account, String authority) { 919 synchronized (mSyncQueue) { 920 mSyncQueue.clear(account, authority); 921 } 922 } 923 924 void maybeRescheduleSync(SyncResult syncResult, SyncOperation previousSyncOperation) { 925 boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG); 926 if (isLoggable) { 927 Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", " 928 + previousSyncOperation); 929 } 930 931 // If this sync aborted because the internal sync loop retried too many times then 932 // don't reschedule. Otherwise we risk getting into a retry loop. 933 // If the operation succeeded to some extent then retry immediately. 934 // If this was a two-way sync then retry soft errors with an exponential backoff. 935 // If this was an upward sync then schedule a two-way sync immediately. 936 // Otherwise do not reschedule. 937 if (syncResult.tooManyRetries) { 938 Log.d(TAG, "not retrying sync operation because it retried too many times: " 939 + previousSyncOperation); 940 } else if (syncResult.madeSomeProgress()) { 941 if (isLoggable) { 942 Log.d(TAG, "retrying sync operation immediately because " 943 + "even though it had an error it achieved some success"); 944 } 945 rescheduleImmediately(previousSyncOperation); 946 } else if (previousSyncOperation.extras.getBoolean( 947 ContentResolver.SYNC_EXTRAS_UPLOAD, false)) { 948 final SyncOperation newSyncOperation = new SyncOperation(previousSyncOperation); 949 newSyncOperation.extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false); 950 newSyncOperation.setDelay(0); 951 if (Config.LOGD) { 952 Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync " 953 + "encountered an error: " + previousSyncOperation); 954 } 955 scheduleSyncOperation(newSyncOperation); 956 } else if (syncResult.hasSoftError()) { 957 long delay = rescheduleWithDelay(previousSyncOperation); 958 if (delay >= 0) { 959 if (isLoggable) { 960 Log.d(TAG, "retrying sync operation in " + delay + " ms because " 961 + "it encountered a soft error: " + previousSyncOperation); 962 } 963 } 964 } else { 965 if (Config.LOGD) { 966 Log.d(TAG, "not retrying sync operation because the error is a hard error: " 967 + previousSyncOperation); 968 } 969 } 970 } 971 972 /** 973 * Value type that represents a sync operation. 974 */ 975 static class SyncOperation implements Comparable { 976 final Account account; 977 int syncSource; 978 String authority; 979 Bundle extras; 980 final String key; 981 long earliestRunTime; 982 long delay; 983 SyncStorageEngine.PendingOperation pendingOperation; 984 985 SyncOperation(Account account, int source, String authority, Bundle extras, long delay) { 986 this.account = account; 987 this.syncSource = source; 988 this.authority = authority; 989 this.extras = new Bundle(extras); 990 this.setDelay(delay); 991 this.key = toKey(); 992 } 993 994 SyncOperation(SyncOperation other) { 995 this.account = other.account; 996 this.syncSource = other.syncSource; 997 this.authority = other.authority; 998 this.extras = new Bundle(other.extras); 999 this.delay = other.delay; 1000 this.earliestRunTime = other.earliestRunTime; 1001 this.key = toKey(); 1002 } 1003 1004 public void setDelay(long delay) { 1005 this.delay = delay; 1006 if (delay >= 0) { 1007 this.earliestRunTime = SystemClock.elapsedRealtime() + delay; 1008 } else { 1009 this.earliestRunTime = 0; 1010 } 1011 } 1012 1013 public String toString() { 1014 StringBuilder sb = new StringBuilder(); 1015 sb.append("authority: ").append(authority); 1016 sb.append(" account: ").append(account); 1017 sb.append(" extras: "); 1018 extrasToStringBuilder(extras, sb); 1019 sb.append(" syncSource: ").append(syncSource); 1020 sb.append(" when: ").append(earliestRunTime); 1021 sb.append(" delay: ").append(delay); 1022 sb.append(" key: {").append(key).append("}"); 1023 if (pendingOperation != null) sb.append(" pendingOperation: ").append(pendingOperation); 1024 return sb.toString(); 1025 } 1026 1027 private String toKey() { 1028 StringBuilder sb = new StringBuilder(); 1029 sb.append("authority: ").append(authority); 1030 sb.append(" account: ").append(account); 1031 sb.append(" extras: "); 1032 extrasToStringBuilder(extras, sb); 1033 return sb.toString(); 1034 } 1035 1036 private static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) { 1037 sb.append("["); 1038 for (String key : bundle.keySet()) { 1039 sb.append(key).append("=").append(bundle.get(key)).append(" "); 1040 } 1041 sb.append("]"); 1042 } 1043 1044 public int compareTo(Object o) { 1045 SyncOperation other = (SyncOperation)o; 1046 if (earliestRunTime == other.earliestRunTime) { 1047 return 0; 1048 } 1049 return (earliestRunTime < other.earliestRunTime) ? -1 : 1; 1050 } 1051 } 1052 1053 /** 1054 * @hide 1055 */ 1056 class ActiveSyncContext extends ISyncContext.Stub implements ServiceConnection { 1057 final SyncOperation mSyncOperation; 1058 final long mHistoryRowId; 1059 ISyncAdapter mSyncAdapter; 1060 final long mStartTime; 1061 long mTimeoutStartTime; 1062 1063 public ActiveSyncContext(SyncOperation syncOperation, 1064 long historyRowId) { 1065 super(); 1066 mSyncOperation = syncOperation; 1067 mHistoryRowId = historyRowId; 1068 mSyncAdapter = null; 1069 mStartTime = SystemClock.elapsedRealtime(); 1070 mTimeoutStartTime = mStartTime; 1071 } 1072 1073 public void sendHeartbeat() { 1074 // ignore this call if it corresponds to an old sync session 1075 if (mActiveSyncContext == this) { 1076 SyncManager.this.updateHeartbeatTime(); 1077 } 1078 } 1079 1080 public void onFinished(SyncResult result) { 1081 // include "this" in the message so that the handler can ignore it if this 1082 // ActiveSyncContext is no longer the mActiveSyncContext at message handling 1083 // time 1084 sendSyncFinishedOrCanceledMessage(this, result); 1085 } 1086 1087 public void toString(StringBuilder sb) { 1088 sb.append("startTime ").append(mStartTime) 1089 .append(", mTimeoutStartTime ").append(mTimeoutStartTime) 1090 .append(", mHistoryRowId ").append(mHistoryRowId) 1091 .append(", syncOperation ").append(mSyncOperation); 1092 } 1093 1094 public void onServiceConnected(ComponentName name, IBinder service) { 1095 Message msg = mSyncHandler.obtainMessage(); 1096 msg.what = SyncHandler.MESSAGE_SERVICE_CONNECTED; 1097 msg.obj = new ServiceConnectionData(this, ISyncAdapter.Stub.asInterface(service)); 1098 mSyncHandler.sendMessage(msg); 1099 } 1100 1101 public void onServiceDisconnected(ComponentName name) { 1102 Message msg = mSyncHandler.obtainMessage(); 1103 msg.what = SyncHandler.MESSAGE_SERVICE_DISCONNECTED; 1104 msg.obj = new ServiceConnectionData(this, null); 1105 mSyncHandler.sendMessage(msg); 1106 } 1107 1108 boolean bindToSyncAdapter(RegisteredServicesCache.ServiceInfo info) { 1109 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1110 Log.d(TAG, "bindToSyncAdapter: " + info.componentName + ", connection " + this); 1111 } 1112 Intent intent = new Intent(); 1113 intent.setAction("android.content.SyncAdapter"); 1114 intent.setComponent(info.componentName); 1115 intent.putExtra(Intent.EXTRA_CLIENT_LABEL, 1116 com.android.internal.R.string.sync_binding_label); 1117 intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( 1118 mContext, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0)); 1119 return mContext.bindService(intent, this, Context.BIND_AUTO_CREATE); 1120 } 1121 1122 void unBindFromSyncAdapter() { 1123 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1124 Log.d(TAG, "unBindFromSyncAdapter: connection " + this); 1125 } 1126 mContext.unbindService(this); 1127 } 1128 1129 @Override 1130 public String toString() { 1131 StringBuilder sb = new StringBuilder(); 1132 toString(sb); 1133 return sb.toString(); 1134 } 1135 } 1136 1137 protected void dump(FileDescriptor fd, PrintWriter pw) { 1138 StringBuilder sb = new StringBuilder(); 1139 dumpSyncState(pw, sb); 1140 if (isSyncEnabled()) { 1141 dumpSyncHistory(pw, sb); 1142 } 1143 1144 pw.println(); 1145 pw.println("SyncAdapters:"); 1146 for (RegisteredServicesCache.ServiceInfo info : mSyncAdapters.getAllServices()) { 1147 pw.println(" " + info); 1148 } 1149 } 1150 1151 static String formatTime(long time) { 1152 Time tobj = new Time(); 1153 tobj.set(time); 1154 return tobj.format("%Y-%m-%d %H:%M:%S"); 1155 } 1156 1157 protected void dumpSyncState(PrintWriter pw, StringBuilder sb) { 1158 pw.print("sync enabled: "); pw.println(isSyncEnabled()); 1159 pw.print("data connected: "); pw.println(mDataConnectionIsConnected); 1160 pw.print("memory low: "); pw.println(mStorageIsLow); 1161 1162 final Account[] accounts = mAccounts; 1163 pw.print("accounts: "); 1164 if (accounts != null) { 1165 pw.println(accounts.length); 1166 } else { 1167 pw.println("none"); 1168 } 1169 final long now = SystemClock.elapsedRealtime(); 1170 pw.print("now: "); pw.println(now); 1171 pw.print("uptime: "); pw.print(DateUtils.formatElapsedTime(now/1000)); 1172 pw.println(" (HH:MM:SS)"); 1173 pw.print("time spent syncing: "); 1174 pw.print(DateUtils.formatElapsedTime( 1175 mSyncHandler.mSyncTimeTracker.timeSpentSyncing() / 1000)); 1176 pw.print(" (HH:MM:SS), sync "); 1177 pw.print(mSyncHandler.mSyncTimeTracker.mLastWasSyncing ? "" : "not "); 1178 pw.println("in progress"); 1179 if (mSyncHandler.mAlarmScheduleTime != null) { 1180 pw.print("next alarm time: "); pw.print(mSyncHandler.mAlarmScheduleTime); 1181 pw.print(" ("); 1182 pw.print(DateUtils.formatElapsedTime((mSyncHandler.mAlarmScheduleTime-now)/1000)); 1183 pw.println(" (HH:MM:SS) from now)"); 1184 } else { 1185 pw.println("no alarm is scheduled (there had better not be any pending syncs)"); 1186 } 1187 1188 pw.print("active sync: "); pw.println(mActiveSyncContext); 1189 1190 pw.print("notification info: "); 1191 sb.setLength(0); 1192 mSyncHandler.mSyncNotificationInfo.toString(sb); 1193 pw.println(sb.toString()); 1194 1195 synchronized (mSyncQueue) { 1196 pw.print("sync queue: "); 1197 sb.setLength(0); 1198 mSyncQueue.dump(sb); 1199 pw.println(sb.toString()); 1200 } 1201 1202 ActiveSyncInfo active = mSyncStorageEngine.getActiveSync(); 1203 if (active != null) { 1204 SyncStorageEngine.AuthorityInfo authority 1205 = mSyncStorageEngine.getAuthority(active.authorityId); 1206 final long durationInSeconds = (now - active.startTime) / 1000; 1207 pw.print("Active sync: "); 1208 pw.print(authority != null ? authority.account : "<no account>"); 1209 pw.print(" "); 1210 pw.print(authority != null ? authority.authority : "<no account>"); 1211 pw.print(", duration is "); 1212 pw.println(DateUtils.formatElapsedTime(durationInSeconds)); 1213 } else { 1214 pw.println("No sync is in progress."); 1215 } 1216 1217 ArrayList<SyncStorageEngine.PendingOperation> ops 1218 = mSyncStorageEngine.getPendingOperations(); 1219 if (ops != null && ops.size() > 0) { 1220 pw.println(); 1221 pw.println("Pending Syncs"); 1222 final int N = ops.size(); 1223 for (int i=0; i<N; i++) { 1224 SyncStorageEngine.PendingOperation op = ops.get(i); 1225 pw.print(" #"); pw.print(i); pw.print(": account="); 1226 pw.print(op.account.name); pw.print(":"); 1227 pw.print(op.account.type); pw.print(" authority="); 1228 pw.println(op.authority); 1229 if (op.extras != null && op.extras.size() > 0) { 1230 sb.setLength(0); 1231 SyncOperation.extrasToStringBuilder(op.extras, sb); 1232 pw.print(" extras: "); pw.println(sb.toString()); 1233 } 1234 } 1235 } 1236 1237 HashSet<Account> processedAccounts = new HashSet<Account>(); 1238 ArrayList<SyncStatusInfo> statuses 1239 = mSyncStorageEngine.getSyncStatus(); 1240 if (statuses != null && statuses.size() > 0) { 1241 pw.println(); 1242 pw.println("Sync Status"); 1243 final int N = statuses.size(); 1244 for (int i=0; i<N; i++) { 1245 SyncStatusInfo status = statuses.get(i); 1246 SyncStorageEngine.AuthorityInfo authority 1247 = mSyncStorageEngine.getAuthority(status.authorityId); 1248 if (authority != null) { 1249 Account curAccount = authority.account; 1250 1251 if (processedAccounts.contains(curAccount)) { 1252 continue; 1253 } 1254 1255 processedAccounts.add(curAccount); 1256 1257 pw.print(" Account "); pw.print(authority.account.name); 1258 pw.print(" "); pw.print(authority.account.type); 1259 pw.println(":"); 1260 for (int j=i; j<N; j++) { 1261 status = statuses.get(j); 1262 authority = mSyncStorageEngine.getAuthority(status.authorityId); 1263 if (!curAccount.equals(authority.account)) { 1264 continue; 1265 } 1266 pw.print(" "); pw.print(authority.authority); 1267 pw.println(":"); 1268 pw.print(" count: local="); pw.print(status.numSourceLocal); 1269 pw.print(" poll="); pw.print(status.numSourcePoll); 1270 pw.print(" server="); pw.print(status.numSourceServer); 1271 pw.print(" user="); pw.print(status.numSourceUser); 1272 pw.print(" total="); pw.println(status.numSyncs); 1273 pw.print(" total duration: "); 1274 pw.println(DateUtils.formatElapsedTime( 1275 status.totalElapsedTime/1000)); 1276 if (status.lastSuccessTime != 0) { 1277 pw.print(" SUCCESS: source="); 1278 pw.print(SyncStorageEngine.SOURCES[ 1279 status.lastSuccessSource]); 1280 pw.print(" time="); 1281 pw.println(formatTime(status.lastSuccessTime)); 1282 } else { 1283 pw.print(" FAILURE: source="); 1284 pw.print(SyncStorageEngine.SOURCES[ 1285 status.lastFailureSource]); 1286 pw.print(" initialTime="); 1287 pw.print(formatTime(status.initialFailureTime)); 1288 pw.print(" lastTime="); 1289 pw.println(formatTime(status.lastFailureTime)); 1290 pw.print(" message: "); pw.println(status.lastFailureMesg); 1291 } 1292 } 1293 } 1294 } 1295 } 1296 } 1297 1298 private void dumpTimeSec(PrintWriter pw, long time) { 1299 pw.print(time/1000); pw.print('.'); pw.print((time/100)%10); 1300 pw.print('s'); 1301 } 1302 1303 private void dumpDayStatistic(PrintWriter pw, SyncStorageEngine.DayStats ds) { 1304 pw.print("Success ("); pw.print(ds.successCount); 1305 if (ds.successCount > 0) { 1306 pw.print(" for "); dumpTimeSec(pw, ds.successTime); 1307 pw.print(" avg="); dumpTimeSec(pw, ds.successTime/ds.successCount); 1308 } 1309 pw.print(") Failure ("); pw.print(ds.failureCount); 1310 if (ds.failureCount > 0) { 1311 pw.print(" for "); dumpTimeSec(pw, ds.failureTime); 1312 pw.print(" avg="); dumpTimeSec(pw, ds.failureTime/ds.failureCount); 1313 } 1314 pw.println(")"); 1315 } 1316 1317 protected void dumpSyncHistory(PrintWriter pw, StringBuilder sb) { 1318 SyncStorageEngine.DayStats dses[] = mSyncStorageEngine.getDayStatistics(); 1319 if (dses != null && dses[0] != null) { 1320 pw.println(); 1321 pw.println("Sync Statistics"); 1322 pw.print(" Today: "); dumpDayStatistic(pw, dses[0]); 1323 int today = dses[0].day; 1324 int i; 1325 SyncStorageEngine.DayStats ds; 1326 1327 // Print each day in the current week. 1328 for (i=1; i<=6 && i < dses.length; i++) { 1329 ds = dses[i]; 1330 if (ds == null) break; 1331 int delta = today-ds.day; 1332 if (delta > 6) break; 1333 1334 pw.print(" Day-"); pw.print(delta); pw.print(": "); 1335 dumpDayStatistic(pw, ds); 1336 } 1337 1338 // Aggregate all following days into weeks and print totals. 1339 int weekDay = today; 1340 while (i < dses.length) { 1341 SyncStorageEngine.DayStats aggr = null; 1342 weekDay -= 7; 1343 while (i < dses.length) { 1344 ds = dses[i]; 1345 if (ds == null) { 1346 i = dses.length; 1347 break; 1348 } 1349 int delta = weekDay-ds.day; 1350 if (delta > 6) break; 1351 i++; 1352 1353 if (aggr == null) { 1354 aggr = new SyncStorageEngine.DayStats(weekDay); 1355 } 1356 aggr.successCount += ds.successCount; 1357 aggr.successTime += ds.successTime; 1358 aggr.failureCount += ds.failureCount; 1359 aggr.failureTime += ds.failureTime; 1360 } 1361 if (aggr != null) { 1362 pw.print(" Week-"); pw.print((today-weekDay)/7); pw.print(": "); 1363 dumpDayStatistic(pw, aggr); 1364 } 1365 } 1366 } 1367 1368 ArrayList<SyncStorageEngine.SyncHistoryItem> items 1369 = mSyncStorageEngine.getSyncHistory(); 1370 if (items != null && items.size() > 0) { 1371 pw.println(); 1372 pw.println("Recent Sync History"); 1373 final int N = items.size(); 1374 for (int i=0; i<N; i++) { 1375 SyncStorageEngine.SyncHistoryItem item = items.get(i); 1376 SyncStorageEngine.AuthorityInfo authority 1377 = mSyncStorageEngine.getAuthority(item.authorityId); 1378 pw.print(" #"); pw.print(i+1); pw.print(": "); 1379 if (authority != null) { 1380 pw.print(authority.account.name); 1381 pw.print(":"); 1382 pw.print(authority.account.type); 1383 pw.print(" "); 1384 pw.print(authority.authority); 1385 } else { 1386 pw.print("<no account>"); 1387 } 1388 Time time = new Time(); 1389 time.set(item.eventTime); 1390 pw.print(" "); pw.print(SyncStorageEngine.SOURCES[item.source]); 1391 pw.print(" @ "); 1392 pw.print(formatTime(item.eventTime)); 1393 pw.print(" for "); 1394 dumpTimeSec(pw, item.elapsedTime); 1395 pw.println(); 1396 if (item.event != SyncStorageEngine.EVENT_STOP 1397 || item.upstreamActivity !=0 1398 || item.downstreamActivity != 0) { 1399 pw.print(" event="); pw.print(item.event); 1400 pw.print(" upstreamActivity="); pw.print(item.upstreamActivity); 1401 pw.print(" downstreamActivity="); pw.println(item.downstreamActivity); 1402 } 1403 if (item.mesg != null 1404 && !SyncStorageEngine.MESG_SUCCESS.equals(item.mesg)) { 1405 pw.print(" mesg="); pw.println(item.mesg); 1406 } 1407 } 1408 } 1409 } 1410 1411 /** 1412 * A helper object to keep track of the time we have spent syncing since the last boot 1413 */ 1414 private class SyncTimeTracker { 1415 /** True if a sync was in progress on the most recent call to update() */ 1416 boolean mLastWasSyncing = false; 1417 /** Used to track when lastWasSyncing was last set */ 1418 long mWhenSyncStarted = 0; 1419 /** The cumulative time we have spent syncing */ 1420 private long mTimeSpentSyncing; 1421 1422 /** Call to let the tracker know that the sync state may have changed */ 1423 public synchronized void update() { 1424 final boolean isSyncInProgress = mActiveSyncContext != null; 1425 if (isSyncInProgress == mLastWasSyncing) return; 1426 final long now = SystemClock.elapsedRealtime(); 1427 if (isSyncInProgress) { 1428 mWhenSyncStarted = now; 1429 } else { 1430 mTimeSpentSyncing += now - mWhenSyncStarted; 1431 } 1432 mLastWasSyncing = isSyncInProgress; 1433 } 1434 1435 /** Get how long we have been syncing, in ms */ 1436 public synchronized long timeSpentSyncing() { 1437 if (!mLastWasSyncing) return mTimeSpentSyncing; 1438 1439 final long now = SystemClock.elapsedRealtime(); 1440 return mTimeSpentSyncing + (now - mWhenSyncStarted); 1441 } 1442 } 1443 1444 class ServiceConnectionData { 1445 public final ActiveSyncContext activeSyncContext; 1446 public final ISyncAdapter syncAdapter; 1447 ServiceConnectionData(ActiveSyncContext activeSyncContext, ISyncAdapter syncAdapter) { 1448 this.activeSyncContext = activeSyncContext; 1449 this.syncAdapter = syncAdapter; 1450 } 1451 } 1452 1453 /** 1454 * Handles SyncOperation Messages that are posted to the associated 1455 * HandlerThread. 1456 */ 1457 class SyncHandler extends Handler { 1458 // Messages that can be sent on mHandler 1459 private static final int MESSAGE_SYNC_FINISHED = 1; 1460 private static final int MESSAGE_SYNC_ALARM = 2; 1461 private static final int MESSAGE_CHECK_ALARMS = 3; 1462 private static final int MESSAGE_SERVICE_CONNECTED = 4; 1463 private static final int MESSAGE_SERVICE_DISCONNECTED = 5; 1464 1465 public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo(); 1466 private Long mAlarmScheduleTime = null; 1467 public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker(); 1468 1469 // used to track if we have installed the error notification so that we don't reinstall 1470 // it if sync is still failing 1471 private boolean mErrorNotificationInstalled = false; 1472 private volatile CountDownLatch mReadyToRunLatch = new CountDownLatch(1); 1473 1474 public void onBootCompleted() { 1475 mBootCompleted = true; 1476 if (mReadyToRunLatch != null) { 1477 mReadyToRunLatch.countDown(); 1478 } 1479 } 1480 1481 private void waitUntilReadyToRun() { 1482 CountDownLatch latch = mReadyToRunLatch; 1483 if (latch != null) { 1484 while (true) { 1485 try { 1486 latch.await(); 1487 mReadyToRunLatch = null; 1488 return; 1489 } catch (InterruptedException e) { 1490 Thread.currentThread().interrupt(); 1491 } 1492 } 1493 } 1494 } 1495 /** 1496 * Used to keep track of whether a sync notification is active and who it is for. 1497 */ 1498 class SyncNotificationInfo { 1499 // only valid if isActive is true 1500 public Account account; 1501 1502 // only valid if isActive is true 1503 public String authority; 1504 1505 // true iff the notification manager has been asked to send the notification 1506 public boolean isActive = false; 1507 1508 // Set when we transition from not running a sync to running a sync, and cleared on 1509 // the opposite transition. 1510 public Long startTime = null; 1511 1512 public void toString(StringBuilder sb) { 1513 sb.append("account ").append(account) 1514 .append(", authority ").append(authority) 1515 .append(", isActive ").append(isActive) 1516 .append(", startTime ").append(startTime); 1517 } 1518 1519 @Override 1520 public String toString() { 1521 StringBuilder sb = new StringBuilder(); 1522 toString(sb); 1523 return sb.toString(); 1524 } 1525 } 1526 1527 public SyncHandler(Looper looper) { 1528 super(looper); 1529 } 1530 1531 public void handleMessage(Message msg) { 1532 try { 1533 waitUntilReadyToRun(); 1534 switch (msg.what) { 1535 case SyncHandler.MESSAGE_SYNC_FINISHED: 1536 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1537 Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_FINISHED"); 1538 } 1539 SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload)msg.obj; 1540 if (mActiveSyncContext != payload.activeSyncContext) { 1541 if (Config.LOGD) { 1542 Log.d(TAG, "handleSyncHandlerMessage: sync context doesn't match, " 1543 + "dropping: mActiveSyncContext " + mActiveSyncContext 1544 + " != " + payload.activeSyncContext); 1545 } 1546 return; 1547 } 1548 runSyncFinishedOrCanceled(payload.syncResult); 1549 1550 // since we are no longer syncing, check if it is time to start a new sync 1551 runStateIdle(); 1552 break; 1553 1554 case SyncHandler.MESSAGE_SERVICE_CONNECTED: { 1555 ServiceConnectionData msgData = (ServiceConnectionData)msg.obj; 1556 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1557 Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CONNECTED: " 1558 + msgData.activeSyncContext 1559 + " active is " + mActiveSyncContext); 1560 } 1561 // check that this isn't an old message 1562 if (mActiveSyncContext == msgData.activeSyncContext) { 1563 runBoundToSyncAdapter(msgData.syncAdapter); 1564 } 1565 break; 1566 } 1567 1568 case SyncHandler.MESSAGE_SERVICE_DISCONNECTED: { 1569 ServiceConnectionData msgData = (ServiceConnectionData)msg.obj; 1570 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1571 Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_DISCONNECTED: " 1572 + msgData.activeSyncContext 1573 + " active is " + mActiveSyncContext); 1574 } 1575 // check that this isn't an old message 1576 if (mActiveSyncContext == msgData.activeSyncContext) { 1577 // cancel the sync if we have a syncadapter, which means one is 1578 // outstanding 1579 if (mActiveSyncContext.mSyncAdapter != null) { 1580 try { 1581 mActiveSyncContext.mSyncAdapter.cancelSync(mActiveSyncContext); 1582 } catch (RemoteException e) { 1583 // we don't need to retry this in this case 1584 } 1585 } 1586 1587 // pretend that the sync failed with an IOException, 1588 // which is a soft error 1589 SyncResult syncResult = new SyncResult(); 1590 syncResult.stats.numIoExceptions++; 1591 runSyncFinishedOrCanceled(syncResult); 1592 1593 // since we are no longer syncing, check if it is time to start a new 1594 // sync 1595 runStateIdle(); 1596 } 1597 1598 break; 1599 } 1600 1601 case SyncHandler.MESSAGE_SYNC_ALARM: { 1602 boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); 1603 if (isLoggable) { 1604 Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_ALARM"); 1605 } 1606 mAlarmScheduleTime = null; 1607 try { 1608 if (mActiveSyncContext != null) { 1609 if (isLoggable) { 1610 Log.v(TAG, "handleSyncHandlerMessage: sync context is active"); 1611 } 1612 runStateSyncing(); 1613 } 1614 1615 // if the above call to runStateSyncing() resulted in the end of a sync, 1616 // check if it is time to start a new sync 1617 if (mActiveSyncContext == null) { 1618 if (isLoggable) { 1619 Log.v(TAG, "handleSyncHandlerMessage: " 1620 + "sync context is not active"); 1621 } 1622 runStateIdle(); 1623 } 1624 } finally { 1625 mHandleAlarmWakeLock.release(); 1626 } 1627 break; 1628 } 1629 1630 case SyncHandler.MESSAGE_CHECK_ALARMS: 1631 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1632 Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_CHECK_ALARMS"); 1633 } 1634 // we do all the work for this case in the finally block 1635 break; 1636 } 1637 } finally { 1638 final boolean isSyncInProgress = mActiveSyncContext != null; 1639 if (!isSyncInProgress) { 1640 mSyncWakeLock.release(); 1641 } 1642 manageSyncNotification(); 1643 manageErrorNotification(); 1644 manageSyncAlarm(); 1645 mSyncTimeTracker.update(); 1646 } 1647 } 1648 1649 private void runStateSyncing() { 1650 // if the sync timeout has been reached then cancel it 1651 1652 ActiveSyncContext activeSyncContext = mActiveSyncContext; 1653 1654 final long now = SystemClock.elapsedRealtime(); 1655 if (now > activeSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC) { 1656 SyncOperation nextSyncOperation; 1657 synchronized (mSyncQueue) { 1658 nextSyncOperation = mSyncQueue.head(); 1659 } 1660 if (nextSyncOperation != null && nextSyncOperation.earliestRunTime <= now) { 1661 if (Config.LOGD) { 1662 Log.d(TAG, "canceling and rescheduling sync because it ran too long: " 1663 + activeSyncContext.mSyncOperation); 1664 } 1665 rescheduleImmediately(activeSyncContext.mSyncOperation); 1666 sendSyncFinishedOrCanceledMessage(activeSyncContext, 1667 null /* no result since this is a cancel */); 1668 } else { 1669 activeSyncContext.mTimeoutStartTime = now + MAX_TIME_PER_SYNC; 1670 } 1671 } 1672 1673 // no need to schedule an alarm, as that will be done by our caller. 1674 } 1675 1676 private void runStateIdle() { 1677 boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); 1678 if (isLoggable) Log.v(TAG, "runStateIdle"); 1679 1680 // If we aren't ready to run (e.g. the data connection is down), get out. 1681 if (!mDataConnectionIsConnected) { 1682 if (isLoggable) { 1683 Log.v(TAG, "runStateIdle: no data connection, skipping"); 1684 } 1685 setStatusText("No data connection"); 1686 return; 1687 } 1688 1689 if (mStorageIsLow) { 1690 if (isLoggable) { 1691 Log.v(TAG, "runStateIdle: memory low, skipping"); 1692 } 1693 setStatusText("Memory low"); 1694 return; 1695 } 1696 1697 // If the accounts aren't known yet then we aren't ready to run. We will be kicked 1698 // when the account lookup request does complete. 1699 Account[] accounts = mAccounts; 1700 if (accounts == null) { 1701 if (isLoggable) { 1702 Log.v(TAG, "runStateIdle: accounts not known, skipping"); 1703 } 1704 setStatusText("Accounts not known yet"); 1705 return; 1706 } 1707 1708 // Otherwise consume SyncOperations from the head of the SyncQueue until one is 1709 // found that is runnable (not disabled, etc). If that one is ready to run then 1710 // start it, otherwise just get out. 1711 SyncOperation op; 1712 final boolean backgroundDataUsageAllowed = 1713 getConnectivityManager().getBackgroundDataSetting(); 1714 synchronized (mSyncQueue) { 1715 while (true) { 1716 op = mSyncQueue.head(); 1717 if (op == null) { 1718 if (isLoggable) { 1719 Log.v(TAG, "runStateIdle: no more sync operations, returning"); 1720 } 1721 return; 1722 } 1723 1724 // Sync is disabled, drop this operation. 1725 if (!isSyncEnabled()) { 1726 if (isLoggable) { 1727 Log.v(TAG, "runStateIdle: sync disabled, dropping " + op); 1728 } 1729 mSyncQueue.popHead(); 1730 continue; 1731 } 1732 1733 // skip the sync if it isn't manual and auto sync is disabled 1734 final boolean manualSync = op.extras.getBoolean( 1735 ContentResolver.SYNC_EXTRAS_MANUAL, false); 1736 final boolean syncAutomatically = 1737 mSyncStorageEngine.getSyncAutomatically(op.account, op.authority) 1738 && mSyncStorageEngine.getMasterSyncAutomatically(); 1739 boolean syncAllowed = 1740 manualSync || (backgroundDataUsageAllowed && syncAutomatically); 1741 int isSyncable = mSyncStorageEngine.getIsSyncable(op.account, op.authority); 1742 if (isSyncable == 0) { 1743 // if not syncable, don't allow 1744 syncAllowed = false; 1745 } else if (isSyncable < 0) { 1746 // if the syncable state is unknown, only allow initialization syncs 1747 syncAllowed = 1748 op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false); 1749 } 1750 if (!syncAllowed) { 1751 if (isLoggable) { 1752 Log.v(TAG, "runStateIdle: sync off, dropping " + op); 1753 } 1754 mSyncQueue.popHead(); 1755 continue; 1756 } 1757 1758 // skip the sync if the account of this operation no longer exists 1759 if (!ArrayUtils.contains(accounts, op.account)) { 1760 mSyncQueue.popHead(); 1761 if (isLoggable) { 1762 Log.v(TAG, "runStateIdle: account not present, dropping " + op); 1763 } 1764 continue; 1765 } 1766 1767 // go ahead and try to sync this syncOperation 1768 if (isLoggable) { 1769 Log.v(TAG, "runStateIdle: found sync candidate: " + op); 1770 } 1771 break; 1772 } 1773 1774 // If the first SyncOperation isn't ready to run schedule a wakeup and 1775 // get out. 1776 final long now = SystemClock.elapsedRealtime(); 1777 if (op.earliestRunTime > now) { 1778 if (Log.isLoggable(TAG, Log.DEBUG)) { 1779 Log.d(TAG, "runStateIdle: the time is " + now + " yet the next " 1780 + "sync operation is for " + op.earliestRunTime + ": " + op); 1781 } 1782 return; 1783 } 1784 1785 // We will do this sync. Remove it from the queue and run it outside of the 1786 // synchronized block. 1787 if (isLoggable) { 1788 Log.v(TAG, "runStateIdle: we are going to sync " + op); 1789 } 1790 mSyncQueue.popHead(); 1791 } 1792 1793 // connect to the sync adapter 1794 SyncAdapterType syncAdapterType = SyncAdapterType.newKey(op.authority, op.account.type); 1795 RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo = 1796 mSyncAdapters.getServiceInfo(syncAdapterType); 1797 if (syncAdapterInfo == null) { 1798 if (Config.LOGD) { 1799 Log.d(TAG, "can't find a sync adapter for " + syncAdapterType); 1800 } 1801 runStateIdle(); 1802 return; 1803 } 1804 1805 ActiveSyncContext activeSyncContext = 1806 new ActiveSyncContext(op, insertStartSyncEvent(op)); 1807 mActiveSyncContext = activeSyncContext; 1808 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1809 Log.v(TAG, "runStateIdle: setting mActiveSyncContext to " + mActiveSyncContext); 1810 } 1811 mSyncStorageEngine.setActiveSync(mActiveSyncContext); 1812 if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo)) { 1813 Log.e(TAG, "Bind attempt failed to " + syncAdapterInfo); 1814 mActiveSyncContext = null; 1815 mSyncStorageEngine.setActiveSync(mActiveSyncContext); 1816 runStateIdle(); 1817 return; 1818 } 1819 1820 mSyncWakeLock.acquire(); 1821 // no need to schedule an alarm, as that will be done by our caller. 1822 1823 // the next step will occur when we get either a timeout or a 1824 // MESSAGE_SERVICE_CONNECTED or MESSAGE_SERVICE_DISCONNECTED message 1825 } 1826 1827 private void runBoundToSyncAdapter(ISyncAdapter syncAdapter) { 1828 mActiveSyncContext.mSyncAdapter = syncAdapter; 1829 final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation; 1830 try { 1831 syncAdapter.startSync(mActiveSyncContext, syncOperation.authority, 1832 syncOperation.account, syncOperation.extras); 1833 } catch (RemoteException remoteExc) { 1834 if (Config.LOGD) { 1835 Log.d(TAG, "runStateIdle: caught a RemoteException, rescheduling", remoteExc); 1836 } 1837 mActiveSyncContext.unBindFromSyncAdapter(); 1838 mActiveSyncContext = null; 1839 mSyncStorageEngine.setActiveSync(mActiveSyncContext); 1840 rescheduleWithDelay(syncOperation); 1841 } catch (RuntimeException exc) { 1842 mActiveSyncContext.unBindFromSyncAdapter(); 1843 mActiveSyncContext = null; 1844 mSyncStorageEngine.setActiveSync(mActiveSyncContext); 1845 Log.e(TAG, "Caught a RuntimeException while starting the sync " + syncOperation, 1846 exc); 1847 } 1848 } 1849 1850 private void runSyncFinishedOrCanceled(SyncResult syncResult) { 1851 boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); 1852 if (isLoggable) Log.v(TAG, "runSyncFinishedOrCanceled"); 1853 final ActiveSyncContext activeSyncContext = mActiveSyncContext; 1854 mActiveSyncContext = null; 1855 mSyncStorageEngine.setActiveSync(mActiveSyncContext); 1856 1857 final SyncOperation syncOperation = activeSyncContext.mSyncOperation; 1858 1859 final long elapsedTime = SystemClock.elapsedRealtime() - activeSyncContext.mStartTime; 1860 1861 String historyMessage; 1862 int downstreamActivity; 1863 int upstreamActivity; 1864 if (syncResult != null) { 1865 if (isLoggable) { 1866 Log.v(TAG, "runSyncFinishedOrCanceled: is a finished: operation " 1867 + syncOperation + ", result " + syncResult); 1868 } 1869 1870 if (!syncResult.hasError()) { 1871 if (isLoggable) { 1872 Log.v(TAG, "finished sync operation " + syncOperation); 1873 } 1874 historyMessage = SyncStorageEngine.MESG_SUCCESS; 1875 // TODO: set these correctly when the SyncResult is extended to include it 1876 downstreamActivity = 0; 1877 upstreamActivity = 0; 1878 } else { 1879 maybeRescheduleSync(syncResult, syncOperation); 1880 if (Config.LOGD) { 1881 Log.d(TAG, "failed sync operation " + syncOperation); 1882 } 1883 historyMessage = Integer.toString(syncResultToErrorNumber(syncResult)); 1884 // TODO: set these correctly when the SyncResult is extended to include it 1885 downstreamActivity = 0; 1886 upstreamActivity = 0; 1887 } 1888 } else { 1889 if (isLoggable) { 1890 Log.v(TAG, "runSyncFinishedOrCanceled: is a cancel: operation " 1891 + syncOperation); 1892 } 1893 if (activeSyncContext.mSyncAdapter != null) { 1894 try { 1895 activeSyncContext.mSyncAdapter.cancelSync(activeSyncContext); 1896 } catch (RemoteException e) { 1897 // we don't need to retry this in this case 1898 } 1899 } 1900 historyMessage = SyncStorageEngine.MESG_CANCELED; 1901 downstreamActivity = 0; 1902 upstreamActivity = 0; 1903 } 1904 1905 stopSyncEvent(activeSyncContext.mHistoryRowId, syncOperation, historyMessage, 1906 upstreamActivity, downstreamActivity, elapsedTime); 1907 1908 activeSyncContext.unBindFromSyncAdapter(); 1909 1910 if (syncResult != null && syncResult.tooManyDeletions) { 1911 installHandleTooManyDeletesNotification(syncOperation.account, 1912 syncOperation.authority, syncResult.stats.numDeletes); 1913 } else { 1914 mNotificationMgr.cancel( 1915 syncOperation.account.hashCode() ^ syncOperation.authority.hashCode()); 1916 } 1917 1918 if (syncResult != null && syncResult.fullSyncRequested) { 1919 scheduleSyncOperation(new SyncOperation(syncOperation.account, 1920 syncOperation.syncSource, syncOperation.authority, new Bundle(), 0)); 1921 } 1922 // no need to schedule an alarm, as that will be done by our caller. 1923 } 1924 1925 /** 1926 * Convert the error-containing SyncResult into the Sync.History error number. Since 1927 * the SyncResult may indicate multiple errors at once, this method just returns the 1928 * most "serious" error. 1929 * @param syncResult the SyncResult from which to read 1930 * @return the most "serious" error set in the SyncResult 1931 * @throws IllegalStateException if the SyncResult does not indicate any errors. 1932 * If SyncResult.error() is true then it is safe to call this. 1933 */ 1934 private int syncResultToErrorNumber(SyncResult syncResult) { 1935 if (syncResult.syncAlreadyInProgress) 1936 return ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS; 1937 if (syncResult.stats.numAuthExceptions > 0) 1938 return ContentResolver.SYNC_ERROR_AUTHENTICATION; 1939 if (syncResult.stats.numIoExceptions > 0) 1940 return ContentResolver.SYNC_ERROR_IO; 1941 if (syncResult.stats.numParseExceptions > 0) 1942 return ContentResolver.SYNC_ERROR_PARSE; 1943 if (syncResult.stats.numConflictDetectedExceptions > 0) 1944 return ContentResolver.SYNC_ERROR_CONFLICT; 1945 if (syncResult.tooManyDeletions) 1946 return ContentResolver.SYNC_ERROR_TOO_MANY_DELETIONS; 1947 if (syncResult.tooManyRetries) 1948 return ContentResolver.SYNC_ERROR_TOO_MANY_RETRIES; 1949 if (syncResult.databaseError) 1950 return ContentResolver.SYNC_ERROR_INTERNAL; 1951 throw new IllegalStateException("we are not in an error state, " + syncResult); 1952 } 1953 1954 private void manageSyncNotification() { 1955 boolean shouldCancel; 1956 boolean shouldInstall; 1957 1958 if (mActiveSyncContext == null) { 1959 mSyncNotificationInfo.startTime = null; 1960 1961 // we aren't syncing. if the notification is active then remember that we need 1962 // to cancel it and then clear out the info 1963 shouldCancel = mSyncNotificationInfo.isActive; 1964 shouldInstall = false; 1965 } else { 1966 // we are syncing 1967 final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation; 1968 1969 final long now = SystemClock.elapsedRealtime(); 1970 if (mSyncNotificationInfo.startTime == null) { 1971 mSyncNotificationInfo.startTime = now; 1972 } 1973 1974 // cancel the notification if it is up and the authority or account is wrong 1975 shouldCancel = mSyncNotificationInfo.isActive && 1976 (!syncOperation.authority.equals(mSyncNotificationInfo.authority) 1977 || !syncOperation.account.equals(mSyncNotificationInfo.account)); 1978 1979 // there are four cases: 1980 // - the notification is up and there is no change: do nothing 1981 // - the notification is up but we should cancel since it is stale: 1982 // need to install 1983 // - the notification is not up but it isn't time yet: don't install 1984 // - the notification is not up and it is time: need to install 1985 1986 if (mSyncNotificationInfo.isActive) { 1987 shouldInstall = shouldCancel; 1988 } else { 1989 final boolean timeToShowNotification = 1990 now > mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY; 1991 // show the notification immediately if this is a manual sync 1992 final boolean manualSync = syncOperation.extras 1993 .getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false); 1994 shouldInstall = timeToShowNotification || manualSync; 1995 } 1996 } 1997 1998 if (shouldCancel && !shouldInstall) { 1999 mNeedSyncActiveNotification = false; 2000 sendSyncStateIntent(); 2001 mSyncNotificationInfo.isActive = false; 2002 } 2003 2004 if (shouldInstall) { 2005 SyncOperation syncOperation = mActiveSyncContext.mSyncOperation; 2006 mNeedSyncActiveNotification = true; 2007 sendSyncStateIntent(); 2008 mSyncNotificationInfo.isActive = true; 2009 mSyncNotificationInfo.account = syncOperation.account; 2010 mSyncNotificationInfo.authority = syncOperation.authority; 2011 } 2012 } 2013 2014 /** 2015 * Check if there were any long-lasting errors, if so install the error notification, 2016 * otherwise cancel the error notification. 2017 */ 2018 private void manageErrorNotification() { 2019 // 2020 long when = mSyncStorageEngine.getInitialSyncFailureTime(); 2021 if ((when > 0) && (when + ERROR_NOTIFICATION_DELAY_MS < System.currentTimeMillis())) { 2022 if (!mErrorNotificationInstalled) { 2023 mNeedSyncErrorNotification = true; 2024 sendSyncStateIntent(); 2025 } 2026 mErrorNotificationInstalled = true; 2027 } else { 2028 if (mErrorNotificationInstalled) { 2029 mNeedSyncErrorNotification = false; 2030 sendSyncStateIntent(); 2031 } 2032 mErrorNotificationInstalled = false; 2033 } 2034 } 2035 2036 private void manageSyncAlarm() { 2037 // in each of these cases the sync loop will be kicked, which will cause this 2038 // method to be called again 2039 if (!mDataConnectionIsConnected) return; 2040 if (mAccounts == null) return; 2041 if (mStorageIsLow) return; 2042 2043 // Compute the alarm fire time: 2044 // - not syncing: time of the next sync operation 2045 // - syncing, no notification: time from sync start to notification create time 2046 // - syncing, with notification: time till timeout of the active sync operation 2047 Long alarmTime = null; 2048 ActiveSyncContext activeSyncContext = mActiveSyncContext; 2049 if (activeSyncContext == null) { 2050 SyncOperation syncOperation; 2051 synchronized (mSyncQueue) { 2052 syncOperation = mSyncQueue.head(); 2053 } 2054 if (syncOperation != null) { 2055 alarmTime = syncOperation.earliestRunTime; 2056 } 2057 } else { 2058 final long notificationTime = 2059 mSyncHandler.mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY; 2060 final long timeoutTime = 2061 mActiveSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC; 2062 if (mSyncHandler.mSyncNotificationInfo.isActive) { 2063 alarmTime = timeoutTime; 2064 } else { 2065 alarmTime = Math.min(notificationTime, timeoutTime); 2066 } 2067 } 2068 2069 // adjust the alarmTime so that we will wake up when it is time to 2070 // install the error notification 2071 if (!mErrorNotificationInstalled) { 2072 long when = mSyncStorageEngine.getInitialSyncFailureTime(); 2073 if (when > 0) { 2074 when += ERROR_NOTIFICATION_DELAY_MS; 2075 // convert when fron absolute time to elapsed run time 2076 long delay = when - System.currentTimeMillis(); 2077 when = SystemClock.elapsedRealtime() + delay; 2078 alarmTime = alarmTime != null ? Math.min(alarmTime, when) : when; 2079 } 2080 } 2081 2082 // determine if we need to set or cancel the alarm 2083 boolean shouldSet = false; 2084 boolean shouldCancel = false; 2085 final boolean alarmIsActive = mAlarmScheduleTime != null; 2086 final boolean needAlarm = alarmTime != null; 2087 if (needAlarm) { 2088 if (!alarmIsActive || alarmTime < mAlarmScheduleTime) { 2089 shouldSet = true; 2090 } 2091 } else { 2092 shouldCancel = alarmIsActive; 2093 } 2094 2095 // set or cancel the alarm as directed 2096 ensureAlarmService(); 2097 if (shouldSet) { 2098 mAlarmScheduleTime = alarmTime; 2099 mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, 2100 mSyncAlarmIntent); 2101 } else if (shouldCancel) { 2102 mAlarmScheduleTime = null; 2103 mAlarmService.cancel(mSyncAlarmIntent); 2104 } 2105 } 2106 2107 private void sendSyncStateIntent() { 2108 Intent syncStateIntent = new Intent(Intent.ACTION_SYNC_STATE_CHANGED); 2109 syncStateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 2110 syncStateIntent.putExtra("active", mNeedSyncActiveNotification); 2111 syncStateIntent.putExtra("failing", mNeedSyncErrorNotification); 2112 mContext.sendBroadcast(syncStateIntent); 2113 } 2114 2115 private void installHandleTooManyDeletesNotification(Account account, String authority, 2116 long numDeletes) { 2117 if (mNotificationMgr == null) return; 2118 2119 final ProviderInfo providerInfo = mContext.getPackageManager().resolveContentProvider( 2120 authority, 0 /* flags */); 2121 if (providerInfo == null) { 2122 return; 2123 } 2124 CharSequence authorityName = providerInfo.loadLabel(mContext.getPackageManager()); 2125 2126 Intent clickIntent = new Intent(); 2127 clickIntent.setClassName("com.android.providers.subscribedfeeds", 2128 "com.android.settings.SyncActivityTooManyDeletes"); 2129 clickIntent.putExtra("account", account); 2130 clickIntent.putExtra("authority", authority); 2131 clickIntent.putExtra("provider", authorityName.toString()); 2132 clickIntent.putExtra("numDeletes", numDeletes); 2133 2134 if (!isActivityAvailable(clickIntent)) { 2135 Log.w(TAG, "No activity found to handle too many deletes."); 2136 return; 2137 } 2138 2139 final PendingIntent pendingIntent = PendingIntent 2140 .getActivity(mContext, 0, clickIntent, PendingIntent.FLAG_CANCEL_CURRENT); 2141 2142 CharSequence tooManyDeletesDescFormat = mContext.getResources().getText( 2143 R.string.contentServiceTooManyDeletesNotificationDesc); 2144 2145 Notification notification = 2146 new Notification(R.drawable.stat_notify_sync_error, 2147 mContext.getString(R.string.contentServiceSync), 2148 System.currentTimeMillis()); 2149 notification.setLatestEventInfo(mContext, 2150 mContext.getString(R.string.contentServiceSyncNotificationTitle), 2151 String.format(tooManyDeletesDescFormat.toString(), authorityName), 2152 pendingIntent); 2153 notification.flags |= Notification.FLAG_ONGOING_EVENT; 2154 mNotificationMgr.notify(account.hashCode() ^ authority.hashCode(), notification); 2155 } 2156 2157 /** 2158 * Checks whether an activity exists on the system image for the given intent. 2159 * 2160 * @param intent The intent for an activity. 2161 * @return Whether or not an activity exists. 2162 */ 2163 private boolean isActivityAvailable(Intent intent) { 2164 PackageManager pm = mContext.getPackageManager(); 2165 List<ResolveInfo> list = pm.queryIntentActivities(intent, 0); 2166 int listSize = list.size(); 2167 for (int i = 0; i < listSize; i++) { 2168 ResolveInfo resolveInfo = list.get(i); 2169 if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) 2170 != 0) { 2171 return true; 2172 } 2173 } 2174 2175 return false; 2176 } 2177 2178 public long insertStartSyncEvent(SyncOperation syncOperation) { 2179 final int source = syncOperation.syncSource; 2180 final long now = System.currentTimeMillis(); 2181 2182 EventLog.writeEvent(2720, syncOperation.authority, 2183 SyncStorageEngine.EVENT_START, source, 2184 syncOperation.account.name.hashCode()); 2185 2186 return mSyncStorageEngine.insertStartSyncEvent( 2187 syncOperation.account, syncOperation.authority, now, source); 2188 } 2189 2190 public void stopSyncEvent(long rowId, SyncOperation syncOperation, String resultMessage, 2191 int upstreamActivity, int downstreamActivity, long elapsedTime) { 2192 EventLog.writeEvent(2720, syncOperation.authority, 2193 SyncStorageEngine.EVENT_STOP, syncOperation.syncSource, 2194 syncOperation.account.name.hashCode()); 2195 2196 mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime, resultMessage, 2197 downstreamActivity, upstreamActivity); 2198 } 2199 } 2200 2201 static class SyncQueue { 2202 private SyncStorageEngine mSyncStorageEngine; 2203 2204 private static final boolean DEBUG_CHECK_DATA_CONSISTENCY = false; 2205 2206 // A priority queue of scheduled SyncOperations that is designed to make it quick 2207 // to find the next SyncOperation that should be considered for running. 2208 private final PriorityQueue<SyncOperation> mOpsByWhen = new PriorityQueue<SyncOperation>(); 2209 2210 // A Map of SyncOperations operationKey -> SyncOperation that is designed for 2211 // quick lookup of an enqueued SyncOperation. 2212 private final HashMap<String, SyncOperation> mOpsByKey = Maps.newHashMap(); 2213 2214 public SyncQueue(SyncStorageEngine syncStorageEngine) { 2215 mSyncStorageEngine = syncStorageEngine; 2216 ArrayList<SyncStorageEngine.PendingOperation> ops 2217 = mSyncStorageEngine.getPendingOperations(); 2218 final int N = ops.size(); 2219 for (int i=0; i<N; i++) { 2220 SyncStorageEngine.PendingOperation op = ops.get(i); 2221 SyncOperation syncOperation = new SyncOperation( 2222 op.account, op.syncSource, op.authority, op.extras, 0); 2223 syncOperation.pendingOperation = op; 2224 add(syncOperation, op); 2225 } 2226 2227 if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */); 2228 } 2229 2230 public boolean add(SyncOperation operation) { 2231 return add(new SyncOperation(operation), 2232 null /* this is not coming from the database */); 2233 } 2234 2235 private boolean add(SyncOperation operation, 2236 SyncStorageEngine.PendingOperation pop) { 2237 if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(pop == null); 2238 2239 // If this operation is expedited then set its earliestRunTime to be immediately 2240 // before the head of the list, or not if none are in the list. 2241 if (operation.delay < 0) { 2242 SyncOperation headOperation = head(); 2243 if (headOperation != null) { 2244 operation.earliestRunTime = Math.min(SystemClock.elapsedRealtime(), 2245 headOperation.earliestRunTime - 1); 2246 } else { 2247 operation.earliestRunTime = SystemClock.elapsedRealtime(); 2248 } 2249 } 2250 2251 // - if an operation with the same key exists and this one should run earlier, 2252 // delete the old one and add the new one 2253 // - if an operation with the same key exists and if this one should run 2254 // later, ignore it 2255 // - if no operation exists then add the new one 2256 final String operationKey = operation.key; 2257 SyncOperation existingOperation = mOpsByKey.get(operationKey); 2258 2259 // if this operation matches an existing operation that is being retried (delay > 0) 2260 // and this isn't a manual sync operation, ignore this operation 2261 if (existingOperation != null && existingOperation.delay > 0) { 2262 if (!operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)) { 2263 return false; 2264 } 2265 } 2266 2267 if (existingOperation != null 2268 && operation.earliestRunTime >= existingOperation.earliestRunTime) { 2269 if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(pop == null); 2270 return false; 2271 } 2272 2273 if (existingOperation != null) { 2274 removeByKey(operationKey); 2275 } 2276 2277 operation.pendingOperation = pop; 2278 if (operation.pendingOperation == null) { 2279 pop = new SyncStorageEngine.PendingOperation( 2280 operation.account, operation.syncSource, 2281 operation.authority, operation.extras); 2282 pop = mSyncStorageEngine.insertIntoPending(pop); 2283 if (pop == null) { 2284 throw new IllegalStateException("error adding pending sync operation " 2285 + operation); 2286 } 2287 operation.pendingOperation = pop; 2288 } 2289 2290 if (DEBUG_CHECK_DATA_CONSISTENCY) { 2291 debugCheckDataStructures( 2292 false /* don't compare with the DB, since we know 2293 it is inconsistent right now */ ); 2294 } 2295 mOpsByKey.put(operationKey, operation); 2296 mOpsByWhen.add(operation); 2297 if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(pop == null); 2298 return true; 2299 } 2300 2301 public void removeByKey(String operationKey) { 2302 if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */); 2303 SyncOperation operationToRemove = mOpsByKey.remove(operationKey); 2304 if (!mOpsByWhen.remove(operationToRemove)) { 2305 throw new IllegalStateException( 2306 "unable to find " + operationToRemove + " in mOpsByWhen"); 2307 } 2308 2309 if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) { 2310 final String errorMessage = "unable to find pending row for " + operationToRemove; 2311 Log.e(TAG, errorMessage, new IllegalStateException(errorMessage)); 2312 } 2313 2314 if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */); 2315 } 2316 2317 public SyncOperation head() { 2318 if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */); 2319 return mOpsByWhen.peek(); 2320 } 2321 2322 public void popHead() { 2323 if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */); 2324 SyncOperation operation = mOpsByWhen.remove(); 2325 if (mOpsByKey.remove(operation.key) == null) { 2326 throw new IllegalStateException("unable to find " + operation + " in mOpsByKey"); 2327 } 2328 2329 if (!mSyncStorageEngine.deleteFromPending(operation.pendingOperation)) { 2330 final String errorMessage = "unable to find pending row for " + operation; 2331 Log.e(TAG, errorMessage, new IllegalStateException(errorMessage)); 2332 } 2333 2334 if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */); 2335 } 2336 2337 public void clear(Account account, String authority) { 2338 Iterator<Map.Entry<String, SyncOperation>> entries = mOpsByKey.entrySet().iterator(); 2339 while (entries.hasNext()) { 2340 Map.Entry<String, SyncOperation> entry = entries.next(); 2341 SyncOperation syncOperation = entry.getValue(); 2342 if (account != null && !syncOperation.account.equals(account)) continue; 2343 if (authority != null && !syncOperation.authority.equals(authority)) continue; 2344 2345 if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */); 2346 entries.remove(); 2347 if (!mOpsByWhen.remove(syncOperation)) { 2348 throw new IllegalStateException( 2349 "unable to find " + syncOperation + " in mOpsByWhen"); 2350 } 2351 2352 if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) { 2353 final String errorMessage = "unable to find pending row for " + syncOperation; 2354 Log.e(TAG, errorMessage, new IllegalStateException(errorMessage)); 2355 } 2356 2357 if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */); 2358 } 2359 } 2360 2361 public void dump(StringBuilder sb) { 2362 sb.append("SyncQueue: ").append(mOpsByWhen.size()).append(" operation(s)\n"); 2363 for (SyncOperation operation : mOpsByWhen) { 2364 sb.append(operation).append("\n"); 2365 } 2366 } 2367 2368 private void debugCheckDataStructures(boolean checkDatabase) { 2369 if (mOpsByKey.size() != mOpsByWhen.size()) { 2370 throw new IllegalStateException("size mismatch: " 2371 + mOpsByKey .size() + " != " + mOpsByWhen.size()); 2372 } 2373 for (SyncOperation operation : mOpsByWhen) { 2374 if (!mOpsByKey.containsKey(operation.key)) { 2375 throw new IllegalStateException( 2376 "operation " + operation + " is in mOpsByWhen but not mOpsByKey"); 2377 } 2378 } 2379 for (Map.Entry<String, SyncOperation> entry : mOpsByKey.entrySet()) { 2380 final SyncOperation operation = entry.getValue(); 2381 final String key = entry.getKey(); 2382 if (!key.equals(operation.key)) { 2383 throw new IllegalStateException( 2384 "operation " + operation + " in mOpsByKey doesn't match key " + key); 2385 } 2386 if (!mOpsByWhen.contains(operation)) { 2387 throw new IllegalStateException( 2388 "operation " + operation + " is in mOpsByKey but not mOpsByWhen"); 2389 } 2390 } 2391 2392 if (checkDatabase) { 2393 final int N = mSyncStorageEngine.getPendingOperationCount(); 2394 if (mOpsByKey.size() != N) { 2395 ArrayList<SyncStorageEngine.PendingOperation> ops 2396 = mSyncStorageEngine.getPendingOperations(); 2397 StringBuilder sb = new StringBuilder(); 2398 for (int i=0; i<N; i++) { 2399 SyncStorageEngine.PendingOperation op = ops.get(i); 2400 sb.append("#"); 2401 sb.append(i); 2402 sb.append(": account="); 2403 sb.append(op.account); 2404 sb.append(" syncSource="); 2405 sb.append(op.syncSource); 2406 sb.append(" authority="); 2407 sb.append(op.authority); 2408 sb.append("\n"); 2409 } 2410 dump(sb); 2411 throw new IllegalStateException("DB size mismatch: " 2412 + mOpsByKey.size() + " != " + N + "\n" 2413 + sb.toString()); 2414 } 2415 } 2416 } 2417 } 2418} 2419