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