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