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