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