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