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