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