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