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