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