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