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