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