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