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