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