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 17 package com.android.server.content; 18 19 import android.accounts.Account; 20 import android.accounts.AccountAndUser; 21 import android.accounts.AccountManager; 22 import android.app.ActivityManager; 23 import android.app.AlarmManager; 24 import android.app.AppGlobals; 25 import android.app.Notification; 26 import android.app.NotificationManager; 27 import android.app.PendingIntent; 28 import android.content.BroadcastReceiver; 29 import android.content.ComponentName; 30 import android.content.ContentResolver; 31 import android.content.Context; 32 import android.content.ISyncAdapter; 33 import android.content.ISyncContext; 34 import android.content.ISyncStatusObserver; 35 import android.content.Intent; 36 import android.content.IntentFilter; 37 import android.content.PeriodicSync; 38 import android.content.ServiceConnection; 39 import android.content.SyncActivityTooManyDeletes; 40 import android.content.SyncAdapterType; 41 import android.content.SyncAdaptersCache; 42 import android.content.SyncInfo; 43 import android.content.SyncResult; 44 import android.content.SyncStatusInfo; 45 import android.content.pm.ApplicationInfo; 46 import android.content.pm.PackageInfo; 47 import android.content.pm.PackageManager; 48 import android.content.pm.ProviderInfo; 49 import android.content.pm.RegisteredServicesCache; 50 import android.content.pm.RegisteredServicesCacheListener; 51 import android.content.pm.ResolveInfo; 52 import android.content.pm.UserInfo; 53 import android.net.ConnectivityManager; 54 import android.net.NetworkInfo; 55 import android.os.Bundle; 56 import android.os.Handler; 57 import android.os.IBinder; 58 import android.os.Looper; 59 import android.os.Message; 60 import android.os.PowerManager; 61 import android.os.RemoteException; 62 import android.os.SystemClock; 63 import android.os.SystemProperties; 64 import android.os.UserHandle; 65 import android.os.UserManager; 66 import android.os.WorkSource; 67 import android.provider.Settings; 68 import android.text.format.DateUtils; 69 import android.text.format.Time; 70 import android.text.TextUtils; 71 import android.util.EventLog; 72 import android.util.Log; 73 import android.util.Pair; 74 75 import com.android.internal.R; 76 import com.android.internal.annotations.GuardedBy; 77 import com.android.internal.os.BackgroundThread; 78 import com.android.internal.util.IndentingPrintWriter; 79 import com.android.server.accounts.AccountManagerService; 80 import com.android.server.content.SyncStorageEngine.AuthorityInfo; 81 import com.android.server.content.SyncStorageEngine.OnSyncRequestListener; 82 import com.google.android.collect.Lists; 83 import com.google.android.collect.Maps; 84 import com.google.android.collect.Sets; 85 86 import java.io.FileDescriptor; 87 import java.io.PrintWriter; 88 import java.text.SimpleDateFormat; 89 import java.util.ArrayList; 90 import java.util.Arrays; 91 import java.util.Arrays; 92 import java.util.Collection; 93 import java.util.Collections; 94 import java.util.Comparator; 95 import java.util.HashMap; 96 import java.util.HashSet; 97 import java.util.Iterator; 98 import java.util.List; 99 import java.util.Map; 100 import java.util.Random; 101 import java.util.Set; 102 import java.util.concurrent.CountDownLatch; 103 104 /** 105 * @hide 106 */ 107 public class SyncManager { 108 private static final String TAG = "SyncManager"; 109 110 /** Delay a sync due to local changes this long. In milliseconds */ 111 private static final long LOCAL_SYNC_DELAY; 112 113 /** 114 * If a sync takes longer than this and the sync queue is not empty then we will 115 * cancel it and add it back to the end of the sync queue. In milliseconds. 116 */ 117 private static final long MAX_TIME_PER_SYNC; 118 119 static { 120 final boolean isLargeRAM = !ActivityManager.isLowRamDeviceStatic(); 121 int defaultMaxInitSyncs = isLargeRAM ? 5 : 2; 122 int defaultMaxRegularSyncs = isLargeRAM ? 2 : 1; 123 MAX_SIMULTANEOUS_INITIALIZATION_SYNCS = 124 SystemProperties.getInt("sync.max_init_syncs", defaultMaxInitSyncs); 125 MAX_SIMULTANEOUS_REGULAR_SYNCS = 126 SystemProperties.getInt("sync.max_regular_syncs", defaultMaxRegularSyncs); 127 LOCAL_SYNC_DELAY = 128 SystemProperties.getLong("sync.local_sync_delay", 30 * 1000 /* 30 seconds */); 129 MAX_TIME_PER_SYNC = 130 SystemProperties.getLong("sync.max_time_per_sync", 5 * 60 * 1000 /* 5 minutes */); 131 SYNC_NOTIFICATION_DELAY = 132 SystemProperties.getLong("sync.notification_delay", 30 * 1000 /* 30 seconds */); 133 } 134 135 private static final long SYNC_NOTIFICATION_DELAY; 136 137 /** 138 * When retrying a sync for the first time use this delay. After that 139 * the retry time will double until it reached MAX_SYNC_RETRY_TIME. 140 * In milliseconds. 141 */ 142 private static final long INITIAL_SYNC_RETRY_TIME_IN_MS = 30 * 1000; // 30 seconds 143 144 /** 145 * Default the max sync retry time to this value. 146 */ 147 private static final long DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS = 60 * 60; // one hour 148 149 /** 150 * How long to wait before retrying a sync that failed due to one already being in progress. 151 */ 152 private static final int DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS = 10; 153 154 private static final int INITIALIZATION_UNBIND_DELAY_MS = 5000; 155 156 private static final String SYNC_WAKE_LOCK_PREFIX = "*sync*"; 157 private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm"; 158 private static final String SYNC_LOOP_WAKE_LOCK = "SyncLoopWakeLock"; 159 160 private static final int MAX_SIMULTANEOUS_REGULAR_SYNCS; 161 private static final int MAX_SIMULTANEOUS_INITIALIZATION_SYNCS; 162 163 private Context mContext; 164 165 private static final AccountAndUser[] INITIAL_ACCOUNTS_ARRAY = new AccountAndUser[0]; 166 167 // TODO: add better locking around mRunningAccounts 168 private volatile AccountAndUser[] mRunningAccounts = INITIAL_ACCOUNTS_ARRAY; 169 170 volatile private PowerManager.WakeLock mHandleAlarmWakeLock; 171 volatile private PowerManager.WakeLock mSyncManagerWakeLock; 172 volatile private boolean mDataConnectionIsConnected = false; 173 volatile private boolean mStorageIsLow = false; 174 175 private final NotificationManager mNotificationMgr; 176 private AlarmManager mAlarmService = null; 177 178 private SyncStorageEngine mSyncStorageEngine; 179 180 @GuardedBy("mSyncQueue") 181 private final SyncQueue mSyncQueue; 182 183 protected final ArrayList<ActiveSyncContext> mActiveSyncContexts = Lists.newArrayList(); 184 185 // set if the sync active indicator should be reported 186 private boolean mNeedSyncActiveNotification = false; 187 188 private final PendingIntent mSyncAlarmIntent; 189 // Synchronized on "this". Instead of using this directly one should instead call 190 // its accessor, getConnManager(). 191 private ConnectivityManager mConnManagerDoNotUseDirectly; 192 193 protected SyncAdaptersCache mSyncAdapters; 194 195 private BroadcastReceiver mStorageIntentReceiver = 196 new BroadcastReceiver() { 197 @Override 198 public void onReceive(Context context, Intent intent) { 199 String action = intent.getAction(); 200 if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) { 201 if (Log.isLoggable(TAG, Log.VERBOSE)) { 202 Log.v(TAG, "Internal storage is low."); 203 } 204 mStorageIsLow = true; 205 cancelActiveSync(null /* any account */, UserHandle.USER_ALL, 206 null /* any authority */); 207 } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) { 208 if (Log.isLoggable(TAG, Log.VERBOSE)) { 209 Log.v(TAG, "Internal storage is ok."); 210 } 211 mStorageIsLow = false; 212 sendCheckAlarmsMessage(); 213 } 214 } 215 }; 216 217 private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() { 218 @Override 219 public void onReceive(Context context, Intent intent) { 220 mSyncHandler.onBootCompleted(); 221 } 222 }; 223 224 private BroadcastReceiver mBackgroundDataSettingChanged = new BroadcastReceiver() { 225 @Override 226 public void onReceive(Context context, Intent intent) { 227 if (getConnectivityManager().getBackgroundDataSetting()) { 228 scheduleSync(null /* account */, UserHandle.USER_ALL, 229 SyncOperation.REASON_BACKGROUND_DATA_SETTINGS_CHANGED, 230 null /* authority */, 231 new Bundle(), 0 /* delay */, 0 /* delay */, 232 false /* onlyThoseWithUnknownSyncableState */); 233 } 234 } 235 }; 236 237 private BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() { 238 @Override 239 public void onReceive(Context context, Intent intent) { 240 updateRunningAccounts(); 241 242 // Kick off sync for everyone, since this was a radical account change 243 scheduleSync(null, UserHandle.USER_ALL, SyncOperation.REASON_ACCOUNTS_UPDATED, null, 244 null, 0 /* no delay */, 0/* no delay */, false); 245 } 246 }; 247 248 private final PowerManager mPowerManager; 249 250 // Use this as a random offset to seed all periodic syncs. 251 private int mSyncRandomOffsetMillis; 252 253 private final UserManager mUserManager; 254 255 private static final long SYNC_ALARM_TIMEOUT_MIN = 30 * 1000; // 30 seconds 256 private static final long SYNC_ALARM_TIMEOUT_MAX = 2 * 60 * 60 * 1000; // two hours 257 258 private List<UserInfo> getAllUsers() { 259 return mUserManager.getUsers(); 260 } 261 262 private boolean containsAccountAndUser(AccountAndUser[] accounts, Account account, int userId) { 263 boolean found = false; 264 for (int i = 0; i < accounts.length; i++) { 265 if (accounts[i].userId == userId 266 && accounts[i].account.equals(account)) { 267 found = true; 268 break; 269 } 270 } 271 return found; 272 } 273 274 public void updateRunningAccounts() { 275 mRunningAccounts = AccountManagerService.getSingleton().getRunningAccounts(); 276 277 if (mBootCompleted) { 278 doDatabaseCleanup(); 279 } 280 281 for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) { 282 if (!containsAccountAndUser(mRunningAccounts, 283 currentSyncContext.mSyncOperation.account, 284 currentSyncContext.mSyncOperation.userId)) { 285 Log.d(TAG, "canceling sync since the account is no longer running"); 286 sendSyncFinishedOrCanceledMessage(currentSyncContext, 287 null /* no result since this is a cancel */); 288 } 289 } 290 291 // we must do this since we don't bother scheduling alarms when 292 // the accounts are not set yet 293 sendCheckAlarmsMessage(); 294 } 295 296 private void doDatabaseCleanup() { 297 for (UserInfo user : mUserManager.getUsers(true)) { 298 // Skip any partially created/removed users 299 if (user.partial) continue; 300 Account[] accountsForUser = AccountManagerService.getSingleton().getAccounts(user.id); 301 mSyncStorageEngine.doDatabaseCleanup(accountsForUser, user.id); 302 } 303 } 304 305 private BroadcastReceiver mConnectivityIntentReceiver = 306 new BroadcastReceiver() { 307 @Override 308 public void onReceive(Context context, Intent intent) { 309 final boolean wasConnected = mDataConnectionIsConnected; 310 311 // don't use the intent to figure out if network is connected, just check 312 // ConnectivityManager directly. 313 mDataConnectionIsConnected = readDataConnectionState(); 314 if (mDataConnectionIsConnected) { 315 if (!wasConnected) { 316 if (Log.isLoggable(TAG, Log.VERBOSE)) { 317 Log.v(TAG, "Reconnection detected: clearing all backoffs"); 318 } 319 synchronized(mSyncQueue) { 320 mSyncStorageEngine.clearAllBackoffsLocked(mSyncQueue); 321 } 322 } 323 sendCheckAlarmsMessage(); 324 } 325 } 326 }; 327 328 private boolean readDataConnectionState() { 329 NetworkInfo networkInfo = getConnectivityManager().getActiveNetworkInfo(); 330 return (networkInfo != null) && networkInfo.isConnected(); 331 } 332 333 private BroadcastReceiver mShutdownIntentReceiver = 334 new BroadcastReceiver() { 335 @Override 336 public void onReceive(Context context, Intent intent) { 337 Log.w(TAG, "Writing sync state before shutdown..."); 338 getSyncStorageEngine().writeAllState(); 339 } 340 }; 341 342 private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() { 343 @Override 344 public void onReceive(Context context, Intent intent) { 345 String action = intent.getAction(); 346 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); 347 if (userId == UserHandle.USER_NULL) return; 348 349 if (Intent.ACTION_USER_REMOVED.equals(action)) { 350 onUserRemoved(userId); 351 } else if (Intent.ACTION_USER_STARTING.equals(action)) { 352 onUserStarting(userId); 353 } else if (Intent.ACTION_USER_STOPPING.equals(action)) { 354 onUserStopping(userId); 355 } 356 } 357 }; 358 359 private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM"; 360 private final SyncHandler mSyncHandler; 361 362 private volatile boolean mBootCompleted = false; 363 364 private ConnectivityManager getConnectivityManager() { 365 synchronized (this) { 366 if (mConnManagerDoNotUseDirectly == null) { 367 mConnManagerDoNotUseDirectly = (ConnectivityManager)mContext.getSystemService( 368 Context.CONNECTIVITY_SERVICE); 369 } 370 return mConnManagerDoNotUseDirectly; 371 } 372 } 373 374 /** 375 * Should only be created after {@link ContentService#systemReady()} so that 376 * {@link PackageManager} is ready to query. 377 */ 378 public SyncManager(Context context, boolean factoryTest) { 379 // Initialize the SyncStorageEngine first, before registering observers 380 // and creating threads and so on; it may fail if the disk is full. 381 mContext = context; 382 383 SyncStorageEngine.init(context); 384 mSyncStorageEngine = SyncStorageEngine.getSingleton(); 385 mSyncStorageEngine.setOnSyncRequestListener(new OnSyncRequestListener() { 386 @Override 387 public void onSyncRequest(Account account, int userId, int reason, String authority, 388 Bundle extras) { 389 scheduleSync(account, userId, reason, authority, extras, 390 0 /* no delay */, 391 0 /* no delay */, 392 false); 393 } 394 }); 395 396 mSyncAdapters = new SyncAdaptersCache(mContext); 397 mSyncQueue = new SyncQueue(mContext.getPackageManager(), mSyncStorageEngine, mSyncAdapters); 398 399 mSyncHandler = new SyncHandler(BackgroundThread.get().getLooper()); 400 401 mSyncAdapters.setListener(new RegisteredServicesCacheListener<SyncAdapterType>() { 402 @Override 403 public void onServiceChanged(SyncAdapterType type, int userId, boolean removed) { 404 if (!removed) { 405 scheduleSync(null, UserHandle.USER_ALL, 406 SyncOperation.REASON_SERVICE_CHANGED, 407 type.authority, null, 0 /* no delay */, 0 /* no delay */, 408 false /* onlyThoseWithUnkownSyncableState */); 409 } 410 } 411 }, mSyncHandler); 412 413 mSyncAlarmIntent = PendingIntent.getBroadcast( 414 mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0); 415 416 IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); 417 context.registerReceiver(mConnectivityIntentReceiver, intentFilter); 418 419 if (!factoryTest) { 420 intentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED); 421 context.registerReceiver(mBootCompletedReceiver, intentFilter); 422 } 423 424 intentFilter = new IntentFilter(ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED); 425 context.registerReceiver(mBackgroundDataSettingChanged, intentFilter); 426 427 intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW); 428 intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); 429 context.registerReceiver(mStorageIntentReceiver, intentFilter); 430 431 intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN); 432 intentFilter.setPriority(100); 433 context.registerReceiver(mShutdownIntentReceiver, intentFilter); 434 435 intentFilter = new IntentFilter(); 436 intentFilter.addAction(Intent.ACTION_USER_REMOVED); 437 intentFilter.addAction(Intent.ACTION_USER_STARTING); 438 intentFilter.addAction(Intent.ACTION_USER_STOPPING); 439 mContext.registerReceiverAsUser( 440 mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null); 441 442 if (!factoryTest) { 443 mNotificationMgr = (NotificationManager) 444 context.getSystemService(Context.NOTIFICATION_SERVICE); 445 context.registerReceiver(new SyncAlarmIntentReceiver(), 446 new IntentFilter(ACTION_SYNC_ALARM)); 447 } else { 448 mNotificationMgr = null; 449 } 450 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 451 mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 452 453 // This WakeLock is used to ensure that we stay awake between the time that we receive 454 // a sync alarm notification and when we finish processing it. We need to do this 455 // because we don't do the work in the alarm handler, rather we do it in a message 456 // handler. 457 mHandleAlarmWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 458 HANDLE_SYNC_ALARM_WAKE_LOCK); 459 mHandleAlarmWakeLock.setReferenceCounted(false); 460 461 // This WakeLock is used to ensure that we stay awake while running the sync loop 462 // message handler. Normally we will hold a sync adapter wake lock while it is being 463 // synced but during the execution of the sync loop it might finish a sync for 464 // one sync adapter before starting the sync for the other sync adapter and we 465 // don't want the device to go to sleep during that window. 466 mSyncManagerWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 467 SYNC_LOOP_WAKE_LOCK); 468 mSyncManagerWakeLock.setReferenceCounted(false); 469 470 mSyncStorageEngine.addStatusChangeListener( 471 ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, new ISyncStatusObserver.Stub() { 472 @Override 473 public void onStatusChanged(int which) { 474 // force the sync loop to run if the settings change 475 sendCheckAlarmsMessage(); 476 } 477 }); 478 479 if (!factoryTest) { 480 // Register for account list updates for all users 481 mContext.registerReceiverAsUser(mAccountsUpdatedReceiver, 482 UserHandle.ALL, 483 new IntentFilter(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION), 484 null, null); 485 } 486 487 // Pick a random second in a day to seed all periodic syncs 488 mSyncRandomOffsetMillis = mSyncStorageEngine.getSyncRandomOffset() * 1000; 489 } 490 491 /** 492 * Return a random value v that satisfies minValue <= v < maxValue. The difference between 493 * maxValue and minValue must be less than Integer.MAX_VALUE. 494 */ 495 private long jitterize(long minValue, long maxValue) { 496 Random random = new Random(SystemClock.elapsedRealtime()); 497 long spread = maxValue - minValue; 498 if (spread > Integer.MAX_VALUE) { 499 throw new IllegalArgumentException("the difference between the maxValue and the " 500 + "minValue must be less than " + Integer.MAX_VALUE); 501 } 502 return minValue + random.nextInt((int)spread); 503 } 504 505 public SyncStorageEngine getSyncStorageEngine() { 506 return mSyncStorageEngine; 507 } 508 509 public int getIsSyncable(Account account, int userId, String providerName) { 510 int isSyncable = mSyncStorageEngine.getIsSyncable(account, userId, providerName); 511 UserInfo userInfo = UserManager.get(mContext).getUserInfo(userId); 512 513 // If it's not a restricted user, return isSyncable 514 if (userInfo == null || !userInfo.isRestricted()) return isSyncable; 515 516 // Else check if the sync adapter has opted-in or not 517 RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo = 518 mSyncAdapters.getServiceInfo( 519 SyncAdapterType.newKey(providerName, account.type), userId); 520 if (syncAdapterInfo == null) return isSyncable; 521 522 PackageInfo pInfo = null; 523 try { 524 pInfo = AppGlobals.getPackageManager().getPackageInfo( 525 syncAdapterInfo.componentName.getPackageName(), 0, userId); 526 if (pInfo == null) return isSyncable; 527 } catch (RemoteException re) { 528 // Shouldn't happen 529 return isSyncable; 530 } 531 if (pInfo.restrictedAccountType != null 532 && pInfo.restrictedAccountType.equals(account.type)) { 533 return isSyncable; 534 } else { 535 return 0; 536 } 537 } 538 539 private void ensureAlarmService() { 540 if (mAlarmService == null) { 541 mAlarmService = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); 542 } 543 } 544 545 /** 546 * Initiate a sync. This can start a sync for all providers 547 * (pass null to url, set onlyTicklable to false), only those 548 * providers that are marked as ticklable (pass null to url, 549 * set onlyTicklable to true), or a specific provider (set url 550 * to the content url of the provider). 551 * 552 * <p>If the ContentResolver.SYNC_EXTRAS_UPLOAD boolean in extras is 553 * true then initiate a sync that just checks for local changes to send 554 * to the server, otherwise initiate a sync that first gets any 555 * changes from the server before sending local changes back to 556 * the server. 557 * 558 * <p>If a specific provider is being synced (the url is non-null) 559 * then the extras can contain SyncAdapter-specific information 560 * to control what gets synced (e.g. which specific feed to sync). 561 * 562 * <p>You'll start getting callbacks after this. 563 * 564 * @param requestedAccount the account to sync, may be null to signify all accounts 565 * @param userId the id of the user whose accounts are to be synced. If userId is USER_ALL, 566 * then all users' accounts are considered. 567 * @param reason for sync request. If this is a positive integer, it is the Linux uid 568 * assigned to the process that requested the sync. If it's negative, the sync was requested by 569 * the SyncManager itself and could be one of the following: 570 * {@link SyncOperation#REASON_BACKGROUND_DATA_SETTINGS_CHANGED} 571 * {@link SyncOperation#REASON_ACCOUNTS_UPDATED} 572 * {@link SyncOperation#REASON_SERVICE_CHANGED} 573 * {@link SyncOperation#REASON_PERIODIC} 574 * {@link SyncOperation#REASON_IS_SYNCABLE} 575 * {@link SyncOperation#REASON_SYNC_AUTO} 576 * {@link SyncOperation#REASON_MASTER_SYNC_AUTO} 577 * {@link SyncOperation#REASON_USER_START} 578 * @param requestedAuthority the authority to sync, may be null to indicate all authorities 579 * @param extras a Map of SyncAdapter-specific information to control 580 * syncs of a specific provider. Can be null. Is ignored 581 * if the url is null. 582 * @param beforeRuntimeMillis milliseconds before runtimeMillis that this sync can run. 583 * @param runtimeMillis maximum milliseconds in the future to wait before performing sync. 584 * @param onlyThoseWithUnkownSyncableState Only sync authorities that have unknown state. 585 */ 586 public void scheduleSync(Account requestedAccount, int userId, int reason, 587 String requestedAuthority, Bundle extras, long beforeRuntimeMillis, 588 long runtimeMillis, boolean onlyThoseWithUnkownSyncableState) { 589 boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); 590 591 final boolean backgroundDataUsageAllowed = !mBootCompleted || 592 getConnectivityManager().getBackgroundDataSetting(); 593 594 if (extras == null) { 595 extras = new Bundle(); 596 } 597 if (isLoggable) { 598 Log.d(TAG, "one-time sync for: " + requestedAccount + " " + extras.toString() + " " 599 + requestedAuthority); 600 } 601 Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false); 602 if (expedited) { 603 runtimeMillis = -1; // this means schedule at the front of the queue 604 } 605 606 AccountAndUser[] accounts; 607 if (requestedAccount != null && userId != UserHandle.USER_ALL) { 608 accounts = new AccountAndUser[] { new AccountAndUser(requestedAccount, userId) }; 609 } else { 610 // if the accounts aren't configured yet then we can't support an account-less 611 // sync request 612 accounts = mRunningAccounts; 613 if (accounts.length == 0) { 614 if (isLoggable) { 615 Log.v(TAG, "scheduleSync: no accounts configured, dropping"); 616 } 617 return; 618 } 619 } 620 621 final boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false); 622 final boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false); 623 if (manualSync) { 624 extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true); 625 extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true); 626 } 627 final boolean ignoreSettings = 628 extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false); 629 630 int source; 631 if (uploadOnly) { 632 source = SyncStorageEngine.SOURCE_LOCAL; 633 } else if (manualSync) { 634 source = SyncStorageEngine.SOURCE_USER; 635 } else if (requestedAuthority == null) { 636 source = SyncStorageEngine.SOURCE_POLL; 637 } else { 638 // this isn't strictly server, since arbitrary callers can (and do) request 639 // a non-forced two-way sync on a specific url 640 source = SyncStorageEngine.SOURCE_SERVER; 641 } 642 643 for (AccountAndUser account : accounts) { 644 // Compile a list of authorities that have sync adapters. 645 // For each authority sync each account that matches a sync adapter. 646 final HashSet<String> syncableAuthorities = new HashSet<String>(); 647 for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter : 648 mSyncAdapters.getAllServices(account.userId)) { 649 syncableAuthorities.add(syncAdapter.type.authority); 650 } 651 652 // if the url was specified then replace the list of authorities 653 // with just this authority or clear it if this authority isn't 654 // syncable 655 if (requestedAuthority != null) { 656 final boolean hasSyncAdapter = syncableAuthorities.contains(requestedAuthority); 657 syncableAuthorities.clear(); 658 if (hasSyncAdapter) syncableAuthorities.add(requestedAuthority); 659 } 660 661 for (String authority : syncableAuthorities) { 662 int isSyncable = getIsSyncable(account.account, account.userId, 663 authority); 664 if (isSyncable == 0) { 665 continue; 666 } 667 final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo; 668 syncAdapterInfo = mSyncAdapters.getServiceInfo( 669 SyncAdapterType.newKey(authority, account.account.type), account.userId); 670 if (syncAdapterInfo == null) { 671 continue; 672 } 673 final boolean allowParallelSyncs = syncAdapterInfo.type.allowParallelSyncs(); 674 final boolean isAlwaysSyncable = syncAdapterInfo.type.isAlwaysSyncable(); 675 if (isSyncable < 0 && isAlwaysSyncable) { 676 mSyncStorageEngine.setIsSyncable(account.account, account.userId, authority, 1); 677 isSyncable = 1; 678 } 679 if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) { 680 continue; 681 } 682 if (!syncAdapterInfo.type.supportsUploading() && uploadOnly) { 683 continue; 684 } 685 686 // always allow if the isSyncable state is unknown 687 boolean syncAllowed = 688 (isSyncable < 0) 689 || ignoreSettings 690 || (backgroundDataUsageAllowed 691 && mSyncStorageEngine.getMasterSyncAutomatically(account.userId) 692 && mSyncStorageEngine.getSyncAutomatically(account.account, 693 account.userId, authority)); 694 if (!syncAllowed) { 695 if (isLoggable) { 696 Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority 697 + " is not allowed, dropping request"); 698 } 699 continue; 700 } 701 702 Pair<Long, Long> backoff = mSyncStorageEngine 703 .getBackoff(account.account, account.userId, authority); 704 long delayUntil = mSyncStorageEngine.getDelayUntilTime(account.account, 705 account.userId, authority); 706 final long backoffTime = backoff != null ? backoff.first : 0; 707 if (isSyncable < 0) { 708 // Initialisation sync. 709 Bundle newExtras = new Bundle(); 710 newExtras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true); 711 if (isLoggable) { 712 Log.v(TAG, "schedule initialisation Sync:" 713 + ", delay until " + delayUntil 714 + ", run by " + 0 715 + ", source " + source 716 + ", account " + account 717 + ", authority " + authority 718 + ", extras " + newExtras); 719 } 720 scheduleSyncOperation( 721 new SyncOperation(account.account, account.userId, reason, source, 722 authority, newExtras, 0 /* immediate */, 0 /* No flex time*/, 723 backoffTime, delayUntil, allowParallelSyncs)); 724 } 725 if (!onlyThoseWithUnkownSyncableState) { 726 if (isLoggable) { 727 Log.v(TAG, "scheduleSync:" 728 + " delay until " + delayUntil 729 + " run by " + runtimeMillis 730 + " flex " + beforeRuntimeMillis 731 + ", source " + source 732 + ", account " + account 733 + ", authority " + authority 734 + ", extras " + extras); 735 } 736 scheduleSyncOperation( 737 new SyncOperation(account.account, account.userId, reason, source, 738 authority, extras, runtimeMillis, beforeRuntimeMillis, 739 backoffTime, delayUntil, allowParallelSyncs)); 740 } 741 } 742 } 743 } 744 745 /** 746 * Schedule sync based on local changes to a provider. Occurs within interval 747 * [LOCAL_SYNC_DELAY, 2*LOCAL_SYNC_DELAY]. 748 */ 749 public void scheduleLocalSync(Account account, int userId, int reason, String authority) { 750 final Bundle extras = new Bundle(); 751 extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true); 752 scheduleSync(account, userId, reason, authority, extras, 753 LOCAL_SYNC_DELAY /* earliest run time */, 754 2 * LOCAL_SYNC_DELAY /* latest sync time. */, 755 false /* onlyThoseWithUnkownSyncableState */); 756 } 757 758 public SyncAdapterType[] getSyncAdapterTypes(int userId) { 759 final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> serviceInfos; 760 serviceInfos = mSyncAdapters.getAllServices(userId); 761 SyncAdapterType[] types = new SyncAdapterType[serviceInfos.size()]; 762 int i = 0; 763 for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> serviceInfo : serviceInfos) { 764 types[i] = serviceInfo.type; 765 ++i; 766 } 767 return types; 768 } 769 770 private void sendSyncAlarmMessage() { 771 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_ALARM"); 772 mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_SYNC_ALARM); 773 } 774 775 private void sendCheckAlarmsMessage() { 776 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CHECK_ALARMS"); 777 mSyncHandler.removeMessages(SyncHandler.MESSAGE_CHECK_ALARMS); 778 mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_CHECK_ALARMS); 779 } 780 781 private void sendSyncFinishedOrCanceledMessage(ActiveSyncContext syncContext, 782 SyncResult syncResult) { 783 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_FINISHED"); 784 Message msg = mSyncHandler.obtainMessage(); 785 msg.what = SyncHandler.MESSAGE_SYNC_FINISHED; 786 msg.obj = new SyncHandlerMessagePayload(syncContext, syncResult); 787 mSyncHandler.sendMessage(msg); 788 } 789 790 private void sendCancelSyncsMessage(final Account account, final int userId, 791 final String authority) { 792 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CANCEL"); 793 Message msg = mSyncHandler.obtainMessage(); 794 msg.what = SyncHandler.MESSAGE_CANCEL; 795 msg.obj = Pair.create(account, authority); 796 msg.arg1 = userId; 797 mSyncHandler.sendMessage(msg); 798 } 799 800 class SyncHandlerMessagePayload { 801 public final ActiveSyncContext activeSyncContext; 802 public final SyncResult syncResult; 803 804 SyncHandlerMessagePayload(ActiveSyncContext syncContext, SyncResult syncResult) { 805 this.activeSyncContext = syncContext; 806 this.syncResult = syncResult; 807 } 808 } 809 810 class SyncAlarmIntentReceiver extends BroadcastReceiver { 811 @Override 812 public void onReceive(Context context, Intent intent) { 813 mHandleAlarmWakeLock.acquire(); 814 sendSyncAlarmMessage(); 815 } 816 } 817 818 private void clearBackoffSetting(SyncOperation op) { 819 mSyncStorageEngine.setBackoff(op.account, op.userId, op.authority, 820 SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE); 821 synchronized (mSyncQueue) { 822 mSyncQueue.onBackoffChanged(op.account, op.userId, op.authority, 0); 823 } 824 } 825 826 private void increaseBackoffSetting(SyncOperation op) { 827 // TODO: Use this function to align it to an already scheduled sync 828 // operation in the specified window 829 final long now = SystemClock.elapsedRealtime(); 830 831 final Pair<Long, Long> previousSettings = 832 mSyncStorageEngine.getBackoff(op.account, op.userId, op.authority); 833 long newDelayInMs = -1; 834 if (previousSettings != null) { 835 // don't increase backoff before current backoff is expired. This will happen for op's 836 // with ignoreBackoff set. 837 if (now < previousSettings.first) { 838 if (Log.isLoggable(TAG, Log.VERBOSE)) { 839 Log.v(TAG, "Still in backoff, do not increase it. " 840 + "Remaining: " + ((previousSettings.first - now) / 1000) + " seconds."); 841 } 842 return; 843 } 844 // Subsequent delays are the double of the previous delay 845 newDelayInMs = previousSettings.second * 2; 846 } 847 if (newDelayInMs <= 0) { 848 // The initial delay is the jitterized INITIAL_SYNC_RETRY_TIME_IN_MS 849 newDelayInMs = jitterize(INITIAL_SYNC_RETRY_TIME_IN_MS, 850 (long)(INITIAL_SYNC_RETRY_TIME_IN_MS * 1.1)); 851 } 852 853 // Cap the delay 854 long maxSyncRetryTimeInSeconds = Settings.Global.getLong(mContext.getContentResolver(), 855 Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS, 856 DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS); 857 if (newDelayInMs > maxSyncRetryTimeInSeconds * 1000) { 858 newDelayInMs = maxSyncRetryTimeInSeconds * 1000; 859 } 860 861 final long backoff = now + newDelayInMs; 862 863 mSyncStorageEngine.setBackoff(op.account, op.userId, op.authority, 864 backoff, newDelayInMs); 865 866 op.backoff = backoff; 867 op.updateEffectiveRunTime(); 868 869 synchronized (mSyncQueue) { 870 mSyncQueue.onBackoffChanged(op.account, op.userId, op.authority, backoff); 871 } 872 } 873 874 private void setDelayUntilTime(SyncOperation op, long delayUntilSeconds) { 875 final long delayUntil = delayUntilSeconds * 1000; 876 final long absoluteNow = System.currentTimeMillis(); 877 long newDelayUntilTime; 878 if (delayUntil > absoluteNow) { 879 newDelayUntilTime = SystemClock.elapsedRealtime() + (delayUntil - absoluteNow); 880 } else { 881 newDelayUntilTime = 0; 882 } 883 mSyncStorageEngine 884 .setDelayUntilTime(op.account, op.userId, op.authority, newDelayUntilTime); 885 synchronized (mSyncQueue) { 886 mSyncQueue.onDelayUntilTimeChanged(op.account, op.authority, newDelayUntilTime); 887 } 888 } 889 890 /** 891 * Cancel the active sync if it matches the authority and account. 892 * @param account limit the cancelations to syncs with this account, if non-null 893 * @param authority limit the cancelations to syncs with this authority, if non-null 894 */ 895 public void cancelActiveSync(Account account, int userId, String authority) { 896 sendCancelSyncsMessage(account, userId, authority); 897 } 898 899 /** 900 * Create and schedule a SyncOperation. 901 * 902 * @param syncOperation the SyncOperation to schedule 903 */ 904 public void scheduleSyncOperation(SyncOperation syncOperation) { 905 boolean queueChanged; 906 synchronized (mSyncQueue) { 907 queueChanged = mSyncQueue.add(syncOperation); 908 } 909 910 if (queueChanged) { 911 if (Log.isLoggable(TAG, Log.VERBOSE)) { 912 Log.v(TAG, "scheduleSyncOperation: enqueued " + syncOperation); 913 } 914 sendCheckAlarmsMessage(); 915 } else { 916 if (Log.isLoggable(TAG, Log.VERBOSE)) { 917 Log.v(TAG, "scheduleSyncOperation: dropping duplicate sync operation " 918 + syncOperation); 919 } 920 } 921 } 922 923 /** 924 * Remove scheduled sync operations. 925 * @param account limit the removals to operations with this account, if non-null 926 * @param authority limit the removals to operations with this authority, if non-null 927 */ 928 public void clearScheduledSyncOperations(Account account, int userId, String authority) { 929 synchronized (mSyncQueue) { 930 mSyncQueue.remove(account, userId, authority); 931 } 932 mSyncStorageEngine.setBackoff(account, userId, authority, 933 SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE); 934 } 935 936 void maybeRescheduleSync(SyncResult syncResult, SyncOperation operation) { 937 boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG); 938 if (isLoggable) { 939 Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", " + operation); 940 } 941 942 operation = new SyncOperation(operation); 943 944 // The SYNC_EXTRAS_IGNORE_BACKOFF only applies to the first attempt to sync a given 945 // request. Retries of the request will always honor the backoff, so clear the 946 // flag in case we retry this request. 947 if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) { 948 operation.extras.remove(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF); 949 } 950 951 // If this sync aborted because the internal sync loop retried too many times then 952 // don't reschedule. Otherwise we risk getting into a retry loop. 953 // If the operation succeeded to some extent then retry immediately. 954 // If this was a two-way sync then retry soft errors with an exponential backoff. 955 // If this was an upward sync then schedule a two-way sync immediately. 956 // Otherwise do not reschedule. 957 if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)) { 958 Log.d(TAG, "not retrying sync operation because SYNC_EXTRAS_DO_NOT_RETRY was specified " 959 + operation); 960 } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false) 961 && !syncResult.syncAlreadyInProgress) { 962 operation.extras.remove(ContentResolver.SYNC_EXTRAS_UPLOAD); 963 Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync " 964 + "encountered an error: " + operation); 965 scheduleSyncOperation(operation); 966 } else if (syncResult.tooManyRetries) { 967 Log.d(TAG, "not retrying sync operation because it retried too many times: " 968 + operation); 969 } else if (syncResult.madeSomeProgress()) { 970 if (isLoggable) { 971 Log.d(TAG, "retrying sync operation because even though it had an error " 972 + "it achieved some success"); 973 } 974 scheduleSyncOperation(operation); 975 } else if (syncResult.syncAlreadyInProgress) { 976 if (isLoggable) { 977 Log.d(TAG, "retrying sync operation that failed because there was already a " 978 + "sync in progress: " + operation); 979 } 980 scheduleSyncOperation( 981 new SyncOperation( 982 operation.account, operation.userId, 983 operation.reason, 984 operation.syncSource, 985 operation.authority, operation.extras, 986 DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000, operation.flexTime, 987 operation.backoff, operation.delayUntil, operation.allowParallelSyncs)); 988 } else if (syncResult.hasSoftError()) { 989 if (isLoggable) { 990 Log.d(TAG, "retrying sync operation because it encountered a soft error: " 991 + operation); 992 } 993 scheduleSyncOperation(operation); 994 } else { 995 Log.d(TAG, "not retrying sync operation because the error is a hard error: " 996 + operation); 997 } 998 } 999 1000 private void onUserStarting(int userId) { 1001 // Make sure that accounts we're about to use are valid 1002 AccountManagerService.getSingleton().validateAccounts(userId); 1003 1004 mSyncAdapters.invalidateCache(userId); 1005 1006 updateRunningAccounts(); 1007 1008 synchronized (mSyncQueue) { 1009 mSyncQueue.addPendingOperations(userId); 1010 } 1011 1012 // Schedule sync for any accounts under started user 1013 final Account[] accounts = AccountManagerService.getSingleton().getAccounts(userId); 1014 for (Account account : accounts) { 1015 scheduleSync(account, userId, SyncOperation.REASON_USER_START, null, null, 1016 0 /* no delay */, 0 /* No flex */, 1017 true /* onlyThoseWithUnknownSyncableState */); 1018 } 1019 1020 sendCheckAlarmsMessage(); 1021 } 1022 1023 private void onUserStopping(int userId) { 1024 updateRunningAccounts(); 1025 1026 cancelActiveSync( 1027 null /* any account */, 1028 userId, 1029 null /* any authority */); 1030 } 1031 1032 private void onUserRemoved(int userId) { 1033 updateRunningAccounts(); 1034 1035 // Clean up the storage engine database 1036 mSyncStorageEngine.doDatabaseCleanup(new Account[0], userId); 1037 synchronized (mSyncQueue) { 1038 mSyncQueue.removeUser(userId); 1039 } 1040 } 1041 1042 /** 1043 * @hide 1044 */ 1045 class ActiveSyncContext extends ISyncContext.Stub 1046 implements ServiceConnection, IBinder.DeathRecipient { 1047 final SyncOperation mSyncOperation; 1048 final long mHistoryRowId; 1049 ISyncAdapter mSyncAdapter; 1050 final long mStartTime; 1051 long mTimeoutStartTime; 1052 boolean mBound; 1053 final PowerManager.WakeLock mSyncWakeLock; 1054 final int mSyncAdapterUid; 1055 SyncInfo mSyncInfo; 1056 boolean mIsLinkedToDeath = false; 1057 1058 /** 1059 * Create an ActiveSyncContext for an impending sync and grab the wakelock for that 1060 * sync adapter. Since this grabs the wakelock you need to be sure to call 1061 * close() when you are done with this ActiveSyncContext, whether the sync succeeded 1062 * or not. 1063 * @param syncOperation the SyncOperation we are about to sync 1064 * @param historyRowId the row in which to record the history info for this sync 1065 * @param syncAdapterUid the UID of the application that contains the sync adapter 1066 * for this sync. This is used to attribute the wakelock hold to that application. 1067 */ 1068 public ActiveSyncContext(SyncOperation syncOperation, long historyRowId, 1069 int syncAdapterUid) { 1070 super(); 1071 mSyncAdapterUid = syncAdapterUid; 1072 mSyncOperation = syncOperation; 1073 mHistoryRowId = historyRowId; 1074 mSyncAdapter = null; 1075 mStartTime = SystemClock.elapsedRealtime(); 1076 mTimeoutStartTime = mStartTime; 1077 mSyncWakeLock = mSyncHandler.getSyncWakeLock( 1078 mSyncOperation.account, mSyncOperation.authority); 1079 mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterUid)); 1080 mSyncWakeLock.acquire(); 1081 } 1082 1083 public void sendHeartbeat() { 1084 // heartbeats are no longer used 1085 } 1086 1087 public void onFinished(SyncResult result) { 1088 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "onFinished: " + this); 1089 // include "this" in the message so that the handler can ignore it if this 1090 // ActiveSyncContext is no longer the mActiveSyncContext at message handling 1091 // time 1092 sendSyncFinishedOrCanceledMessage(this, result); 1093 } 1094 1095 public void toString(StringBuilder sb) { 1096 sb.append("startTime ").append(mStartTime) 1097 .append(", mTimeoutStartTime ").append(mTimeoutStartTime) 1098 .append(", mHistoryRowId ").append(mHistoryRowId) 1099 .append(", syncOperation ").append(mSyncOperation); 1100 } 1101 1102 public void onServiceConnected(ComponentName name, IBinder service) { 1103 Message msg = mSyncHandler.obtainMessage(); 1104 msg.what = SyncHandler.MESSAGE_SERVICE_CONNECTED; 1105 msg.obj = new ServiceConnectionData(this, ISyncAdapter.Stub.asInterface(service)); 1106 mSyncHandler.sendMessage(msg); 1107 } 1108 1109 public void onServiceDisconnected(ComponentName name) { 1110 Message msg = mSyncHandler.obtainMessage(); 1111 msg.what = SyncHandler.MESSAGE_SERVICE_DISCONNECTED; 1112 msg.obj = new ServiceConnectionData(this, null); 1113 mSyncHandler.sendMessage(msg); 1114 } 1115 1116 boolean bindToSyncAdapter(RegisteredServicesCache.ServiceInfo info, int userId) { 1117 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1118 Log.d(TAG, "bindToSyncAdapter: " + info.componentName + ", connection " + this); 1119 } 1120 Intent intent = new Intent(); 1121 intent.setAction("android.content.SyncAdapter"); 1122 intent.setComponent(info.componentName); 1123 intent.putExtra(Intent.EXTRA_CLIENT_LABEL, 1124 com.android.internal.R.string.sync_binding_label); 1125 intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser( 1126 mContext, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0, 1127 null, new UserHandle(userId))); 1128 mBound = true; 1129 final boolean bindResult = mContext.bindServiceAsUser(intent, this, 1130 Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND 1131 | Context.BIND_ALLOW_OOM_MANAGEMENT, 1132 new UserHandle(mSyncOperation.userId)); 1133 if (!bindResult) { 1134 mBound = false; 1135 } 1136 return bindResult; 1137 } 1138 1139 /** 1140 * Performs the required cleanup, which is the releasing of the wakelock and 1141 * unbinding from the sync adapter (if actually bound). 1142 */ 1143 protected void close() { 1144 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1145 Log.d(TAG, "unBindFromSyncAdapter: connection " + this); 1146 } 1147 if (mBound) { 1148 mBound = false; 1149 mContext.unbindService(this); 1150 } 1151 mSyncWakeLock.release(); 1152 mSyncWakeLock.setWorkSource(null); 1153 } 1154 1155 @Override 1156 public String toString() { 1157 StringBuilder sb = new StringBuilder(); 1158 toString(sb); 1159 return sb.toString(); 1160 } 1161 1162 @Override 1163 public void binderDied() { 1164 sendSyncFinishedOrCanceledMessage(this, null); 1165 } 1166 } 1167 1168 protected void dump(FileDescriptor fd, PrintWriter pw) { 1169 final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); 1170 dumpSyncState(ipw); 1171 dumpSyncHistory(ipw); 1172 dumpSyncAdapters(ipw); 1173 } 1174 1175 static String formatTime(long time) { 1176 Time tobj = new Time(); 1177 tobj.set(time); 1178 return tobj.format("%Y-%m-%d %H:%M:%S"); 1179 } 1180 1181 protected void dumpSyncState(PrintWriter pw) { 1182 pw.print("data connected: "); pw.println(mDataConnectionIsConnected); 1183 pw.print("auto sync: "); 1184 List<UserInfo> users = getAllUsers(); 1185 if (users != null) { 1186 for (UserInfo user : users) { 1187 pw.print("u" + user.id + "=" 1188 + mSyncStorageEngine.getMasterSyncAutomatically(user.id) + " "); 1189 } 1190 pw.println(); 1191 } 1192 pw.print("memory low: "); pw.println(mStorageIsLow); 1193 1194 final AccountAndUser[] accounts = AccountManagerService.getSingleton().getAllAccounts(); 1195 1196 pw.print("accounts: "); 1197 if (accounts != INITIAL_ACCOUNTS_ARRAY) { 1198 pw.println(accounts.length); 1199 } else { 1200 pw.println("not known yet"); 1201 } 1202 final long now = SystemClock.elapsedRealtime(); 1203 pw.print("now: "); pw.print(now); 1204 pw.println(" (" + formatTime(System.currentTimeMillis()) + ")"); 1205 pw.print("offset: "); pw.print(DateUtils.formatElapsedTime(mSyncRandomOffsetMillis/1000)); 1206 pw.println(" (HH:MM:SS)"); 1207 pw.print("uptime: "); pw.print(DateUtils.formatElapsedTime(now/1000)); 1208 pw.println(" (HH:MM:SS)"); 1209 pw.print("time spent syncing: "); 1210 pw.print(DateUtils.formatElapsedTime( 1211 mSyncHandler.mSyncTimeTracker.timeSpentSyncing() / 1000)); 1212 pw.print(" (HH:MM:SS), sync "); 1213 pw.print(mSyncHandler.mSyncTimeTracker.mLastWasSyncing ? "" : "not "); 1214 pw.println("in progress"); 1215 if (mSyncHandler.mAlarmScheduleTime != null) { 1216 pw.print("next alarm time: "); pw.print(mSyncHandler.mAlarmScheduleTime); 1217 pw.print(" ("); 1218 pw.print(DateUtils.formatElapsedTime((mSyncHandler.mAlarmScheduleTime-now)/1000)); 1219 pw.println(" (HH:MM:SS) from now)"); 1220 } else { 1221 pw.println("no alarm is scheduled (there had better not be any pending syncs)"); 1222 } 1223 1224 pw.print("notification info: "); 1225 final StringBuilder sb = new StringBuilder(); 1226 mSyncHandler.mSyncNotificationInfo.toString(sb); 1227 pw.println(sb.toString()); 1228 1229 pw.println(); 1230 pw.println("Active Syncs: " + mActiveSyncContexts.size()); 1231 final PackageManager pm = mContext.getPackageManager(); 1232 for (SyncManager.ActiveSyncContext activeSyncContext : mActiveSyncContexts) { 1233 final long durationInSeconds = (now - activeSyncContext.mStartTime) / 1000; 1234 pw.print(" "); 1235 pw.print(DateUtils.formatElapsedTime(durationInSeconds)); 1236 pw.print(" - "); 1237 pw.print(activeSyncContext.mSyncOperation.dump(pm, false)); 1238 pw.println(); 1239 } 1240 1241 synchronized (mSyncQueue) { 1242 sb.setLength(0); 1243 mSyncQueue.dump(sb); 1244 // Dump Pending Operations. 1245 getSyncStorageEngine().dumpPendingOperations(sb); 1246 } 1247 1248 pw.println(); 1249 pw.print(sb.toString()); 1250 1251 // join the installed sync adapter with the accounts list and emit for everything 1252 pw.println(); 1253 pw.println("Sync Status"); 1254 for (AccountAndUser account : accounts) { 1255 pw.printf("Account %s u%d %s\n", 1256 account.account.name, account.userId, account.account.type); 1257 1258 pw.println("======================================================================="); 1259 final PrintTable table = new PrintTable(13); 1260 table.set(0, 0, 1261 "Authority", // 0 1262 "Syncable", // 1 1263 "Enabled", // 2 1264 "Delay", // 3 1265 "Loc", // 4 1266 "Poll", // 5 1267 "Per", // 6 1268 "Serv", // 7 1269 "User", // 8 1270 "Tot", // 9 1271 "Time", // 10 1272 "Last Sync", // 11 1273 "Periodic" // 12 1274 ); 1275 1276 final List<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> sorted = 1277 Lists.newArrayList(); 1278 sorted.addAll(mSyncAdapters.getAllServices(account.userId)); 1279 Collections.sort(sorted, 1280 new Comparator<RegisteredServicesCache.ServiceInfo<SyncAdapterType>>() { 1281 @Override 1282 public int compare(RegisteredServicesCache.ServiceInfo<SyncAdapterType> lhs, 1283 RegisteredServicesCache.ServiceInfo<SyncAdapterType> rhs) { 1284 return lhs.type.authority.compareTo(rhs.type.authority); 1285 } 1286 }); 1287 for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterType : sorted) { 1288 if (!syncAdapterType.type.accountType.equals(account.account.type)) { 1289 continue; 1290 } 1291 int row = table.getNumRows(); 1292 Pair<AuthorityInfo, SyncStatusInfo> syncAuthoritySyncStatus = 1293 mSyncStorageEngine.getCopyOfAuthorityWithSyncStatus( 1294 account.account, account.userId, syncAdapterType.type.authority); 1295 SyncStorageEngine.AuthorityInfo settings = syncAuthoritySyncStatus.first; 1296 SyncStatusInfo status = syncAuthoritySyncStatus.second; 1297 1298 String authority = settings.authority; 1299 if (authority.length() > 50) { 1300 authority = authority.substring(authority.length() - 50); 1301 } 1302 table.set(row, 0, authority, settings.syncable, settings.enabled); 1303 table.set(row, 4, 1304 status.numSourceLocal, 1305 status.numSourcePoll, 1306 status.numSourcePeriodic, 1307 status.numSourceServer, 1308 status.numSourceUser, 1309 status.numSyncs, 1310 DateUtils.formatElapsedTime(status.totalElapsedTime / 1000)); 1311 1312 1313 for (int i = 0; i < settings.periodicSyncs.size(); i++) { 1314 final PeriodicSync sync = settings.periodicSyncs.get(i); 1315 final String period = 1316 String.format("[p:%d s, f: %d s]", sync.period, sync.flexTime); 1317 final String extras = 1318 sync.extras.size() > 0 ? 1319 sync.extras.toString() : "Bundle[]"; 1320 final String next = "Next sync: " + formatTime(status.getPeriodicSyncTime(i) 1321 + sync.period * 1000); 1322 table.set(row + i * 2, 12, period + " " + extras); 1323 table.set(row + i * 2 + 1, 12, next); 1324 } 1325 1326 int row1 = row; 1327 if (settings.delayUntil > now) { 1328 table.set(row1++, 12, "D: " + (settings.delayUntil - now) / 1000); 1329 if (settings.backoffTime > now) { 1330 table.set(row1++, 12, "B: " + (settings.backoffTime - now) / 1000); 1331 table.set(row1++, 12, settings.backoffDelay / 1000); 1332 } 1333 } 1334 1335 if (status.lastSuccessTime != 0) { 1336 table.set(row1++, 11, SyncStorageEngine.SOURCES[status.lastSuccessSource] 1337 + " " + "SUCCESS"); 1338 table.set(row1++, 11, formatTime(status.lastSuccessTime)); 1339 } 1340 if (status.lastFailureTime != 0) { 1341 table.set(row1++, 11, SyncStorageEngine.SOURCES[status.lastFailureSource] 1342 + " " + "FAILURE"); 1343 table.set(row1++, 11, formatTime(status.lastFailureTime)); 1344 //noinspection UnusedAssignment 1345 table.set(row1++, 11, status.lastFailureMesg); 1346 } 1347 } 1348 table.writeTo(pw); 1349 } 1350 } 1351 1352 private String getLastFailureMessage(int code) { 1353 switch (code) { 1354 case ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS: 1355 return "sync already in progress"; 1356 1357 case ContentResolver.SYNC_ERROR_AUTHENTICATION: 1358 return "authentication error"; 1359 1360 case ContentResolver.SYNC_ERROR_IO: 1361 return "I/O error"; 1362 1363 case ContentResolver.SYNC_ERROR_PARSE: 1364 return "parse error"; 1365 1366 case ContentResolver.SYNC_ERROR_CONFLICT: 1367 return "conflict error"; 1368 1369 case ContentResolver.SYNC_ERROR_TOO_MANY_DELETIONS: 1370 return "too many deletions error"; 1371 1372 case ContentResolver.SYNC_ERROR_TOO_MANY_RETRIES: 1373 return "too many retries error"; 1374 1375 case ContentResolver.SYNC_ERROR_INTERNAL: 1376 return "internal error"; 1377 1378 default: 1379 return "unknown"; 1380 } 1381 } 1382 1383 private void dumpTimeSec(PrintWriter pw, long time) { 1384 pw.print(time/1000); pw.print('.'); pw.print((time/100)%10); 1385 pw.print('s'); 1386 } 1387 1388 private void dumpDayStatistic(PrintWriter pw, SyncStorageEngine.DayStats ds) { 1389 pw.print("Success ("); pw.print(ds.successCount); 1390 if (ds.successCount > 0) { 1391 pw.print(" for "); dumpTimeSec(pw, ds.successTime); 1392 pw.print(" avg="); dumpTimeSec(pw, ds.successTime/ds.successCount); 1393 } 1394 pw.print(") Failure ("); pw.print(ds.failureCount); 1395 if (ds.failureCount > 0) { 1396 pw.print(" for "); dumpTimeSec(pw, ds.failureTime); 1397 pw.print(" avg="); dumpTimeSec(pw, ds.failureTime/ds.failureCount); 1398 } 1399 pw.println(")"); 1400 } 1401 1402 protected void dumpSyncHistory(PrintWriter pw) { 1403 dumpRecentHistory(pw); 1404 dumpDayStatistics(pw); 1405 } 1406 1407 private void dumpRecentHistory(PrintWriter pw) { 1408 final ArrayList<SyncStorageEngine.SyncHistoryItem> items 1409 = mSyncStorageEngine.getSyncHistory(); 1410 if (items != null && items.size() > 0) { 1411 final Map<String, AuthoritySyncStats> authorityMap = Maps.newHashMap(); 1412 long totalElapsedTime = 0; 1413 long totalTimes = 0; 1414 final int N = items.size(); 1415 1416 int maxAuthority = 0; 1417 int maxAccount = 0; 1418 for (SyncStorageEngine.SyncHistoryItem item : items) { 1419 SyncStorageEngine.AuthorityInfo authority 1420 = mSyncStorageEngine.getAuthority(item.authorityId); 1421 final String authorityName; 1422 final String accountKey; 1423 if (authority != null) { 1424 authorityName = authority.authority; 1425 accountKey = authority.account.name + "/" + authority.account.type 1426 + " u" + authority.userId; 1427 } else { 1428 authorityName = "Unknown"; 1429 accountKey = "Unknown"; 1430 } 1431 1432 int length = authorityName.length(); 1433 if (length > maxAuthority) { 1434 maxAuthority = length; 1435 } 1436 length = accountKey.length(); 1437 if (length > maxAccount) { 1438 maxAccount = length; 1439 } 1440 1441 final long elapsedTime = item.elapsedTime; 1442 totalElapsedTime += elapsedTime; 1443 totalTimes++; 1444 AuthoritySyncStats authoritySyncStats = authorityMap.get(authorityName); 1445 if (authoritySyncStats == null) { 1446 authoritySyncStats = new AuthoritySyncStats(authorityName); 1447 authorityMap.put(authorityName, authoritySyncStats); 1448 } 1449 authoritySyncStats.elapsedTime += elapsedTime; 1450 authoritySyncStats.times++; 1451 final Map<String, AccountSyncStats> accountMap = authoritySyncStats.accountMap; 1452 AccountSyncStats accountSyncStats = accountMap.get(accountKey); 1453 if (accountSyncStats == null) { 1454 accountSyncStats = new AccountSyncStats(accountKey); 1455 accountMap.put(accountKey, accountSyncStats); 1456 } 1457 accountSyncStats.elapsedTime += elapsedTime; 1458 accountSyncStats.times++; 1459 1460 } 1461 1462 if (totalElapsedTime > 0) { 1463 pw.println(); 1464 pw.printf("Detailed Statistics (Recent history): " 1465 + "%d (# of times) %ds (sync time)\n", 1466 totalTimes, totalElapsedTime / 1000); 1467 1468 final List<AuthoritySyncStats> sortedAuthorities = 1469 new ArrayList<AuthoritySyncStats>(authorityMap.values()); 1470 Collections.sort(sortedAuthorities, new Comparator<AuthoritySyncStats>() { 1471 @Override 1472 public int compare(AuthoritySyncStats lhs, AuthoritySyncStats rhs) { 1473 // reverse order 1474 int compare = Integer.compare(rhs.times, lhs.times); 1475 if (compare == 0) { 1476 compare = Long.compare(rhs.elapsedTime, lhs.elapsedTime); 1477 } 1478 return compare; 1479 } 1480 }); 1481 1482 final int maxLength = Math.max(maxAuthority, maxAccount + 3); 1483 final int padLength = 2 + 2 + maxLength + 2 + 10 + 11; 1484 final char chars[] = new char[padLength]; 1485 Arrays.fill(chars, '-'); 1486 final String separator = new String(chars); 1487 1488 final String authorityFormat = 1489 String.format(" %%-%ds: %%-9s %%-11s\n", maxLength + 2); 1490 final String accountFormat = 1491 String.format(" %%-%ds: %%-9s %%-11s\n", maxLength); 1492 1493 pw.println(separator); 1494 for (AuthoritySyncStats authoritySyncStats : sortedAuthorities) { 1495 String name = authoritySyncStats.name; 1496 long elapsedTime; 1497 int times; 1498 String timeStr; 1499 String timesStr; 1500 1501 elapsedTime = authoritySyncStats.elapsedTime; 1502 times = authoritySyncStats.times; 1503 timeStr = String.format("%ds/%d%%", 1504 elapsedTime / 1000, 1505 elapsedTime * 100 / totalElapsedTime); 1506 timesStr = String.format("%d/%d%%", 1507 times, 1508 times * 100 / totalTimes); 1509 pw.printf(authorityFormat, name, timesStr, timeStr); 1510 1511 final List<AccountSyncStats> sortedAccounts = 1512 new ArrayList<AccountSyncStats>( 1513 authoritySyncStats.accountMap.values()); 1514 Collections.sort(sortedAccounts, new Comparator<AccountSyncStats>() { 1515 @Override 1516 public int compare(AccountSyncStats lhs, AccountSyncStats rhs) { 1517 // reverse order 1518 int compare = Integer.compare(rhs.times, lhs.times); 1519 if (compare == 0) { 1520 compare = Long.compare(rhs.elapsedTime, lhs.elapsedTime); 1521 } 1522 return compare; 1523 } 1524 }); 1525 for (AccountSyncStats stats: sortedAccounts) { 1526 elapsedTime = stats.elapsedTime; 1527 times = stats.times; 1528 timeStr = String.format("%ds/%d%%", 1529 elapsedTime / 1000, 1530 elapsedTime * 100 / totalElapsedTime); 1531 timesStr = String.format("%d/%d%%", 1532 times, 1533 times * 100 / totalTimes); 1534 pw.printf(accountFormat, stats.name, timesStr, timeStr); 1535 } 1536 pw.println(separator); 1537 } 1538 } 1539 1540 pw.println(); 1541 pw.println("Recent Sync History"); 1542 final String format = " %-" + maxAccount + "s %-" + maxAuthority + "s %s\n"; 1543 final Map<String, Long> lastTimeMap = Maps.newHashMap(); 1544 final PackageManager pm = mContext.getPackageManager(); 1545 for (int i = 0; i < N; i++) { 1546 SyncStorageEngine.SyncHistoryItem item = items.get(i); 1547 SyncStorageEngine.AuthorityInfo authority 1548 = mSyncStorageEngine.getAuthority(item.authorityId); 1549 final String authorityName; 1550 final String accountKey; 1551 if (authority != null) { 1552 authorityName = authority.authority; 1553 accountKey = authority.account.name + "/" + authority.account.type 1554 + " u" + authority.userId; 1555 } else { 1556 authorityName = "Unknown"; 1557 accountKey = "Unknown"; 1558 } 1559 final long elapsedTime = item.elapsedTime; 1560 final Time time = new Time(); 1561 final long eventTime = item.eventTime; 1562 time.set(eventTime); 1563 1564 final String key = authorityName + "/" + accountKey; 1565 final Long lastEventTime = lastTimeMap.get(key); 1566 final String diffString; 1567 if (lastEventTime == null) { 1568 diffString = ""; 1569 } else { 1570 final long diff = (lastEventTime - eventTime) / 1000; 1571 if (diff < 60) { 1572 diffString = String.valueOf(diff); 1573 } else if (diff < 3600) { 1574 diffString = String.format("%02d:%02d", diff / 60, diff % 60); 1575 } else { 1576 final long sec = diff % 3600; 1577 diffString = String.format("%02d:%02d:%02d", 1578 diff / 3600, sec / 60, sec % 60); 1579 } 1580 } 1581 lastTimeMap.put(key, eventTime); 1582 1583 pw.printf(" #%-3d: %s %8s %5.1fs %8s", 1584 i + 1, 1585 formatTime(eventTime), 1586 SyncStorageEngine.SOURCES[item.source], 1587 ((float) elapsedTime) / 1000, 1588 diffString); 1589 pw.printf(format, accountKey, authorityName, 1590 SyncOperation.reasonToString(pm, item.reason)); 1591 1592 if (item.event != SyncStorageEngine.EVENT_STOP 1593 || item.upstreamActivity != 0 1594 || item.downstreamActivity != 0) { 1595 pw.printf(" event=%d upstreamActivity=%d downstreamActivity=%d\n", 1596 item.event, 1597 item.upstreamActivity, 1598 item.downstreamActivity); 1599 } 1600 if (item.mesg != null 1601 && !SyncStorageEngine.MESG_SUCCESS.equals(item.mesg)) { 1602 pw.printf(" mesg=%s\n", item.mesg); 1603 } 1604 } 1605 pw.println(); 1606 pw.println("Recent Sync History Extras"); 1607 for (int i = 0; i < N; i++) { 1608 final SyncStorageEngine.SyncHistoryItem item = items.get(i); 1609 final Bundle extras = item.extras; 1610 if (extras == null || extras.size() == 0) { 1611 continue; 1612 } 1613 final SyncStorageEngine.AuthorityInfo authority 1614 = mSyncStorageEngine.getAuthority(item.authorityId); 1615 final String authorityName; 1616 final String accountKey; 1617 if (authority != null) { 1618 authorityName = authority.authority; 1619 accountKey = authority.account.name + "/" + authority.account.type 1620 + " u" + authority.userId; 1621 } else { 1622 authorityName = "Unknown"; 1623 accountKey = "Unknown"; 1624 } 1625 final Time time = new Time(); 1626 final long eventTime = item.eventTime; 1627 time.set(eventTime); 1628 1629 pw.printf(" #%-3d: %s %8s ", 1630 i + 1, 1631 formatTime(eventTime), 1632 SyncStorageEngine.SOURCES[item.source]); 1633 1634 pw.printf(format, accountKey, authorityName, extras); 1635 } 1636 } 1637 } 1638 1639 private void dumpDayStatistics(PrintWriter pw) { 1640 SyncStorageEngine.DayStats dses[] = mSyncStorageEngine.getDayStatistics(); 1641 if (dses != null && dses[0] != null) { 1642 pw.println(); 1643 pw.println("Sync Statistics"); 1644 pw.print(" Today: "); dumpDayStatistic(pw, dses[0]); 1645 int today = dses[0].day; 1646 int i; 1647 SyncStorageEngine.DayStats ds; 1648 1649 // Print each day in the current week. 1650 for (i=1; i<=6 && i < dses.length; i++) { 1651 ds = dses[i]; 1652 if (ds == null) break; 1653 int delta = today-ds.day; 1654 if (delta > 6) break; 1655 1656 pw.print(" Day-"); pw.print(delta); pw.print(": "); 1657 dumpDayStatistic(pw, ds); 1658 } 1659 1660 // Aggregate all following days into weeks and print totals. 1661 int weekDay = today; 1662 while (i < dses.length) { 1663 SyncStorageEngine.DayStats aggr = null; 1664 weekDay -= 7; 1665 while (i < dses.length) { 1666 ds = dses[i]; 1667 if (ds == null) { 1668 i = dses.length; 1669 break; 1670 } 1671 int delta = weekDay-ds.day; 1672 if (delta > 6) break; 1673 i++; 1674 1675 if (aggr == null) { 1676 aggr = new SyncStorageEngine.DayStats(weekDay); 1677 } 1678 aggr.successCount += ds.successCount; 1679 aggr.successTime += ds.successTime; 1680 aggr.failureCount += ds.failureCount; 1681 aggr.failureTime += ds.failureTime; 1682 } 1683 if (aggr != null) { 1684 pw.print(" Week-"); pw.print((today-weekDay)/7); pw.print(": "); 1685 dumpDayStatistic(pw, aggr); 1686 } 1687 } 1688 } 1689 } 1690 1691 private void dumpSyncAdapters(IndentingPrintWriter pw) { 1692 pw.println(); 1693 final List<UserInfo> users = getAllUsers(); 1694 if (users != null) { 1695 for (UserInfo user : users) { 1696 pw.println("Sync adapters for " + user + ":"); 1697 pw.increaseIndent(); 1698 for (RegisteredServicesCache.ServiceInfo<?> info : 1699 mSyncAdapters.getAllServices(user.id)) { 1700 pw.println(info); 1701 } 1702 pw.decreaseIndent(); 1703 pw.println(); 1704 } 1705 } 1706 } 1707 1708 private static class AuthoritySyncStats { 1709 String name; 1710 long elapsedTime; 1711 int times; 1712 Map<String, AccountSyncStats> accountMap = Maps.newHashMap(); 1713 1714 private AuthoritySyncStats(String name) { 1715 this.name = name; 1716 } 1717 } 1718 1719 private static class AccountSyncStats { 1720 String name; 1721 long elapsedTime; 1722 int times; 1723 1724 private AccountSyncStats(String name) { 1725 this.name = name; 1726 } 1727 } 1728 1729 /** 1730 * A helper object to keep track of the time we have spent syncing since the last boot 1731 */ 1732 private class SyncTimeTracker { 1733 /** True if a sync was in progress on the most recent call to update() */ 1734 boolean mLastWasSyncing = false; 1735 /** Used to track when lastWasSyncing was last set */ 1736 long mWhenSyncStarted = 0; 1737 /** The cumulative time we have spent syncing */ 1738 private long mTimeSpentSyncing; 1739 1740 /** Call to let the tracker know that the sync state may have changed */ 1741 public synchronized void update() { 1742 final boolean isSyncInProgress = !mActiveSyncContexts.isEmpty(); 1743 if (isSyncInProgress == mLastWasSyncing) return; 1744 final long now = SystemClock.elapsedRealtime(); 1745 if (isSyncInProgress) { 1746 mWhenSyncStarted = now; 1747 } else { 1748 mTimeSpentSyncing += now - mWhenSyncStarted; 1749 } 1750 mLastWasSyncing = isSyncInProgress; 1751 } 1752 1753 /** Get how long we have been syncing, in ms */ 1754 public synchronized long timeSpentSyncing() { 1755 if (!mLastWasSyncing) return mTimeSpentSyncing; 1756 1757 final long now = SystemClock.elapsedRealtime(); 1758 return mTimeSpentSyncing + (now - mWhenSyncStarted); 1759 } 1760 } 1761 1762 class ServiceConnectionData { 1763 public final ActiveSyncContext activeSyncContext; 1764 public final ISyncAdapter syncAdapter; 1765 ServiceConnectionData(ActiveSyncContext activeSyncContext, ISyncAdapter syncAdapter) { 1766 this.activeSyncContext = activeSyncContext; 1767 this.syncAdapter = syncAdapter; 1768 } 1769 } 1770 1771 /** 1772 * Handles SyncOperation Messages that are posted to the associated 1773 * HandlerThread. 1774 */ 1775 class SyncHandler extends Handler { 1776 // Messages that can be sent on mHandler 1777 private static final int MESSAGE_SYNC_FINISHED = 1; 1778 private static final int MESSAGE_SYNC_ALARM = 2; 1779 private static final int MESSAGE_CHECK_ALARMS = 3; 1780 private static final int MESSAGE_SERVICE_CONNECTED = 4; 1781 private static final int MESSAGE_SERVICE_DISCONNECTED = 5; 1782 private static final int MESSAGE_CANCEL = 6; 1783 1784 public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo(); 1785 private Long mAlarmScheduleTime = null; 1786 public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker(); 1787 private final HashMap<Pair<Account, String>, PowerManager.WakeLock> mWakeLocks = 1788 Maps.newHashMap(); 1789 private List<Message> mBootQueue = new ArrayList<Message>(); 1790 1791 public void onBootCompleted() { 1792 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1793 Log.v(TAG, "Boot completed, clearing boot queue."); 1794 } 1795 doDatabaseCleanup(); 1796 synchronized(this) { 1797 // Dispatch any stashed messages. 1798 for (Message message : mBootQueue) { 1799 sendMessage(message); 1800 } 1801 mBootQueue = null; 1802 mBootCompleted = true; 1803 } 1804 } 1805 1806 private PowerManager.WakeLock getSyncWakeLock(Account account, String authority) { 1807 final Pair<Account, String> wakeLockKey = Pair.create(account, authority); 1808 PowerManager.WakeLock wakeLock = mWakeLocks.get(wakeLockKey); 1809 if (wakeLock == null) { 1810 final String name = SYNC_WAKE_LOCK_PREFIX + "/" + authority + "/" + account.type 1811 + "/" + account.name; 1812 wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name); 1813 wakeLock.setReferenceCounted(false); 1814 mWakeLocks.put(wakeLockKey, wakeLock); 1815 } 1816 return wakeLock; 1817 } 1818 1819 /** 1820 * Stash any messages that come to the handler before boot is complete. 1821 * {@link #onBootCompleted()} will disable this and dispatch all the messages collected. 1822 * @param msg Message to dispatch at a later point. 1823 * @return true if a message was enqueued, false otherwise. This is to avoid losing the 1824 * message if we manage to acquire the lock but by the time we do boot has completed. 1825 */ 1826 private boolean tryEnqueueMessageUntilReadyToRun(Message msg) { 1827 synchronized (this) { 1828 if (!mBootCompleted) { 1829 // Need to copy the message bc looper will recycle it. 1830 mBootQueue.add(Message.obtain(msg)); 1831 return true; 1832 } 1833 return false; 1834 } 1835 } 1836 1837 /** 1838 * Used to keep track of whether a sync notification is active and who it is for. 1839 */ 1840 class SyncNotificationInfo { 1841 // true iff the notification manager has been asked to send the notification 1842 public boolean isActive = false; 1843 1844 // Set when we transition from not running a sync to running a sync, and cleared on 1845 // the opposite transition. 1846 public Long startTime = null; 1847 1848 public void toString(StringBuilder sb) { 1849 sb.append("isActive ").append(isActive).append(", startTime ").append(startTime); 1850 } 1851 1852 @Override 1853 public String toString() { 1854 StringBuilder sb = new StringBuilder(); 1855 toString(sb); 1856 return sb.toString(); 1857 } 1858 } 1859 1860 public SyncHandler(Looper looper) { 1861 super(looper); 1862 } 1863 1864 @Override 1865 public void handleMessage(Message msg) { 1866 if (tryEnqueueMessageUntilReadyToRun(msg)) { 1867 return; 1868 } 1869 1870 long earliestFuturePollTime = Long.MAX_VALUE; 1871 long nextPendingSyncTime = Long.MAX_VALUE; 1872 // Setting the value here instead of a method because we want the dumpsys logs 1873 // to have the most recent value used. 1874 try { 1875 mDataConnectionIsConnected = readDataConnectionState(); 1876 mSyncManagerWakeLock.acquire(); 1877 // Always do this first so that we be sure that any periodic syncs that 1878 // are ready to run have been converted into pending syncs. This allows the 1879 // logic that considers the next steps to take based on the set of pending syncs 1880 // to also take into account the periodic syncs. 1881 earliestFuturePollTime = scheduleReadyPeriodicSyncs(); 1882 switch (msg.what) { 1883 case SyncHandler.MESSAGE_CANCEL: { 1884 Pair<Account, String> payload = (Pair<Account, String>) msg.obj; 1885 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1886 Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CANCEL: " 1887 + payload.first + ", " + payload.second); 1888 } 1889 cancelActiveSyncLocked(payload.first, msg.arg1, payload.second); 1890 nextPendingSyncTime = maybeStartNextSyncLocked(); 1891 break; 1892 } 1893 1894 case SyncHandler.MESSAGE_SYNC_FINISHED: 1895 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1896 Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_FINISHED"); 1897 } 1898 SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload)msg.obj; 1899 if (!isSyncStillActive(payload.activeSyncContext)) { 1900 Log.d(TAG, "handleSyncHandlerMessage: dropping since the " 1901 + "sync is no longer active: " 1902 + payload.activeSyncContext); 1903 break; 1904 } 1905 runSyncFinishedOrCanceledLocked(payload.syncResult, payload.activeSyncContext); 1906 1907 // since a sync just finished check if it is time to start a new sync 1908 nextPendingSyncTime = maybeStartNextSyncLocked(); 1909 break; 1910 1911 case SyncHandler.MESSAGE_SERVICE_CONNECTED: { 1912 ServiceConnectionData msgData = (ServiceConnectionData)msg.obj; 1913 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1914 Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CONNECTED: " 1915 + msgData.activeSyncContext); 1916 } 1917 // check that this isn't an old message 1918 if (isSyncStillActive(msgData.activeSyncContext)) { 1919 runBoundToSyncAdapter(msgData.activeSyncContext, msgData.syncAdapter); 1920 } 1921 break; 1922 } 1923 1924 case SyncHandler.MESSAGE_SERVICE_DISCONNECTED: { 1925 final ActiveSyncContext currentSyncContext = 1926 ((ServiceConnectionData)msg.obj).activeSyncContext; 1927 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1928 Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_DISCONNECTED: " 1929 + currentSyncContext); 1930 } 1931 // check that this isn't an old message 1932 if (isSyncStillActive(currentSyncContext)) { 1933 // cancel the sync if we have a syncadapter, which means one is 1934 // outstanding 1935 if (currentSyncContext.mSyncAdapter != null) { 1936 try { 1937 currentSyncContext.mSyncAdapter.cancelSync(currentSyncContext); 1938 } catch (RemoteException e) { 1939 // we don't need to retry this in this case 1940 } 1941 } 1942 1943 // pretend that the sync failed with an IOException, 1944 // which is a soft error 1945 SyncResult syncResult = new SyncResult(); 1946 syncResult.stats.numIoExceptions++; 1947 runSyncFinishedOrCanceledLocked(syncResult, currentSyncContext); 1948 1949 // since a sync just finished check if it is time to start a new sync 1950 nextPendingSyncTime = maybeStartNextSyncLocked(); 1951 } 1952 1953 break; 1954 } 1955 1956 case SyncHandler.MESSAGE_SYNC_ALARM: { 1957 boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); 1958 if (isLoggable) { 1959 Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_ALARM"); 1960 } 1961 mAlarmScheduleTime = null; 1962 try { 1963 nextPendingSyncTime = maybeStartNextSyncLocked(); 1964 } finally { 1965 mHandleAlarmWakeLock.release(); 1966 } 1967 break; 1968 } 1969 1970 case SyncHandler.MESSAGE_CHECK_ALARMS: 1971 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1972 Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_CHECK_ALARMS"); 1973 } 1974 nextPendingSyncTime = maybeStartNextSyncLocked(); 1975 break; 1976 } 1977 } finally { 1978 manageSyncNotificationLocked(); 1979 manageSyncAlarmLocked(earliestFuturePollTime, nextPendingSyncTime); 1980 mSyncTimeTracker.update(); 1981 mSyncManagerWakeLock.release(); 1982 } 1983 } 1984 1985 /** 1986 * Turn any periodic sync operations that are ready to run into pending sync operations. 1987 * @return the desired start time of the earliest future periodic sync operation, 1988 * in milliseconds since boot 1989 */ 1990 private long scheduleReadyPeriodicSyncs() { 1991 final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); 1992 if (isLoggable) { 1993 Log.v(TAG, "scheduleReadyPeriodicSyncs"); 1994 } 1995 final boolean backgroundDataUsageAllowed = 1996 getConnectivityManager().getBackgroundDataSetting(); 1997 long earliestFuturePollTime = Long.MAX_VALUE; 1998 if (!backgroundDataUsageAllowed) { 1999 return earliestFuturePollTime; 2000 } 2001 2002 AccountAndUser[] accounts = mRunningAccounts; 2003 2004 final long nowAbsolute = System.currentTimeMillis(); 2005 final long shiftedNowAbsolute = (0 < nowAbsolute - mSyncRandomOffsetMillis) 2006 ? (nowAbsolute - mSyncRandomOffsetMillis) : 0; 2007 2008 ArrayList<Pair<AuthorityInfo, SyncStatusInfo>> infos = mSyncStorageEngine 2009 .getCopyOfAllAuthoritiesWithSyncStatus(); 2010 for (Pair<AuthorityInfo, SyncStatusInfo> info : infos) { 2011 final AuthorityInfo authorityInfo = info.first; 2012 final SyncStatusInfo status = info.second; 2013 if (TextUtils.isEmpty(authorityInfo.authority)) { 2014 Log.e(TAG, "Got an empty provider string. Skipping: " + authorityInfo); 2015 continue; 2016 } 2017 // skip the sync if the account of this operation no longer exists 2018 if (!containsAccountAndUser( 2019 accounts, authorityInfo.account, authorityInfo.userId)) { 2020 continue; 2021 } 2022 2023 if (!mSyncStorageEngine.getMasterSyncAutomatically(authorityInfo.userId) 2024 || !mSyncStorageEngine.getSyncAutomatically( 2025 authorityInfo.account, authorityInfo.userId, 2026 authorityInfo.authority)) { 2027 continue; 2028 } 2029 2030 if (getIsSyncable( 2031 authorityInfo.account, authorityInfo.userId, authorityInfo.authority) 2032 == 0) { 2033 continue; 2034 } 2035 2036 for (int i = 0, N = authorityInfo.periodicSyncs.size(); i < N; i++) { 2037 final PeriodicSync sync = authorityInfo.periodicSyncs.get(i); 2038 final Bundle extras = sync.extras; 2039 final long periodInMillis = sync.period * 1000; 2040 final long flexInMillis = sync.flexTime * 1000; 2041 // Skip if the period is invalid. 2042 if (periodInMillis <= 0) { 2043 continue; 2044 } 2045 // Find when this periodic sync was last scheduled to run. 2046 final long lastPollTimeAbsolute = status.getPeriodicSyncTime(i); 2047 long remainingMillis 2048 = periodInMillis - (shiftedNowAbsolute % periodInMillis); 2049 long timeSinceLastRunMillis 2050 = (nowAbsolute - lastPollTimeAbsolute); 2051 // Schedule this periodic sync to run early if it's close enough to its next 2052 // runtime, and far enough from its last run time. 2053 // If we are early, there will still be time remaining in this period. 2054 boolean runEarly = remainingMillis <= flexInMillis 2055 && timeSinceLastRunMillis > periodInMillis - flexInMillis; 2056 if (isLoggable) { 2057 Log.v(TAG, "sync: " + i + " for " + authorityInfo.authority + "." 2058 + " period: " + (periodInMillis) 2059 + " flex: " + (flexInMillis) 2060 + " remaining: " + (remainingMillis) 2061 + " time_since_last: " + timeSinceLastRunMillis 2062 + " last poll absol: " + lastPollTimeAbsolute 2063 + " shifted now: " + shiftedNowAbsolute 2064 + " run_early: " + runEarly); 2065 } 2066 /* 2067 * Sync scheduling strategy: Set the next periodic sync 2068 * based on a random offset (in seconds). Also sync right 2069 * now if any of the following cases hold and mark it as 2070 * having been scheduled 2071 * Case 1: This sync is ready to run now. 2072 * Case 2: If the lastPollTimeAbsolute is in the 2073 * future, sync now and reinitialize. This can happen for 2074 * example if the user changed the time, synced and changed 2075 * back. 2076 * Case 3: If we failed to sync at the last scheduled 2077 * time. 2078 * Case 4: This sync is close enough to the time that we can schedule it. 2079 */ 2080 if (runEarly // Case 4 2081 || remainingMillis == periodInMillis // Case 1 2082 || lastPollTimeAbsolute > nowAbsolute // Case 2 2083 || timeSinceLastRunMillis >= periodInMillis) { // Case 3 2084 // Sync now 2085 2086 final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff( 2087 authorityInfo.account, authorityInfo.userId, 2088 authorityInfo.authority); 2089 final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo; 2090 syncAdapterInfo = mSyncAdapters.getServiceInfo( 2091 SyncAdapterType.newKey( 2092 authorityInfo.authority, authorityInfo.account.type), 2093 authorityInfo.userId); 2094 if (syncAdapterInfo == null) { 2095 continue; 2096 } 2097 mSyncStorageEngine.setPeriodicSyncTime(authorityInfo.ident, 2098 authorityInfo.periodicSyncs.get(i), nowAbsolute); 2099 scheduleSyncOperation( 2100 new SyncOperation(authorityInfo.account, authorityInfo.userId, 2101 SyncOperation.REASON_PERIODIC, 2102 SyncStorageEngine.SOURCE_PERIODIC, 2103 authorityInfo.authority, extras, 2104 0 /* runtime */, 0 /* flex */, 2105 backoff != null ? backoff.first : 0, 2106 mSyncStorageEngine.getDelayUntilTime( 2107 authorityInfo.account, authorityInfo.userId, 2108 authorityInfo.authority), 2109 syncAdapterInfo.type.allowParallelSyncs())); 2110 2111 } 2112 // Compute when this periodic sync should next run. 2113 long nextPollTimeAbsolute; 2114 if (runEarly) { 2115 // Add the time remaining so we don't get out of phase. 2116 nextPollTimeAbsolute = nowAbsolute + periodInMillis + remainingMillis; 2117 } else { 2118 nextPollTimeAbsolute = nowAbsolute + remainingMillis; 2119 } 2120 if (nextPollTimeAbsolute < earliestFuturePollTime) { 2121 earliestFuturePollTime = nextPollTimeAbsolute; 2122 } 2123 } 2124 } 2125 2126 if (earliestFuturePollTime == Long.MAX_VALUE) { 2127 return Long.MAX_VALUE; 2128 } 2129 2130 // convert absolute time to elapsed time 2131 return SystemClock.elapsedRealtime() + 2132 ((earliestFuturePollTime < nowAbsolute) ? 2133 0 : (earliestFuturePollTime - nowAbsolute)); 2134 } 2135 2136 private long maybeStartNextSyncLocked() { 2137 final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); 2138 if (isLoggable) Log.v(TAG, "maybeStartNextSync"); 2139 2140 // If we aren't ready to run (e.g. the data connection is down), get out. 2141 if (!mDataConnectionIsConnected) { 2142 if (isLoggable) { 2143 Log.v(TAG, "maybeStartNextSync: no data connection, skipping"); 2144 } 2145 return Long.MAX_VALUE; 2146 } 2147 2148 if (mStorageIsLow) { 2149 if (isLoggable) { 2150 Log.v(TAG, "maybeStartNextSync: memory low, skipping"); 2151 } 2152 return Long.MAX_VALUE; 2153 } 2154 2155 // If the accounts aren't known yet then we aren't ready to run. We will be kicked 2156 // when the account lookup request does complete. 2157 AccountAndUser[] accounts = mRunningAccounts; 2158 if (accounts == INITIAL_ACCOUNTS_ARRAY) { 2159 if (isLoggable) { 2160 Log.v(TAG, "maybeStartNextSync: accounts not known, skipping"); 2161 } 2162 return Long.MAX_VALUE; 2163 } 2164 2165 // Otherwise consume SyncOperations from the head of the SyncQueue until one is 2166 // found that is runnable (not disabled, etc). If that one is ready to run then 2167 // start it, otherwise just get out. 2168 final boolean backgroundDataUsageAllowed = 2169 getConnectivityManager().getBackgroundDataSetting(); 2170 2171 final long now = SystemClock.elapsedRealtime(); 2172 2173 // will be set to the next time that a sync should be considered for running 2174 long nextReadyToRunTime = Long.MAX_VALUE; 2175 2176 // order the sync queue, dropping syncs that are not allowed 2177 ArrayList<SyncOperation> operations = new ArrayList<SyncOperation>(); 2178 synchronized (mSyncQueue) { 2179 if (isLoggable) { 2180 Log.v(TAG, "build the operation array, syncQueue size is " 2181 + mSyncQueue.getOperations().size()); 2182 } 2183 final Iterator<SyncOperation> operationIterator = 2184 mSyncQueue.getOperations().iterator(); 2185 2186 final ActivityManager activityManager 2187 = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); 2188 final Set<Integer> removedUsers = Sets.newHashSet(); 2189 while (operationIterator.hasNext()) { 2190 final SyncOperation op = operationIterator.next(); 2191 2192 // Drop the sync if the account of this operation no longer exists. 2193 if (!containsAccountAndUser(accounts, op.account, op.userId)) { 2194 operationIterator.remove(); 2195 mSyncStorageEngine.deleteFromPending(op.pendingOperation); 2196 if (isLoggable) { 2197 Log.v(TAG, " Dropping sync operation: account doesn't exist."); 2198 } 2199 continue; 2200 } 2201 2202 // Drop this sync request if it isn't syncable. 2203 int syncableState = getIsSyncable( 2204 op.account, op.userId, op.authority); 2205 if (syncableState == 0) { 2206 operationIterator.remove(); 2207 mSyncStorageEngine.deleteFromPending(op.pendingOperation); 2208 if (isLoggable) { 2209 Log.v(TAG, " Dropping sync operation: isSyncable == 0."); 2210 } 2211 continue; 2212 } 2213 2214 // If the user is not running, drop the request. 2215 if (!activityManager.isUserRunning(op.userId)) { 2216 final UserInfo userInfo = mUserManager.getUserInfo(op.userId); 2217 if (userInfo == null) { 2218 removedUsers.add(op.userId); 2219 } 2220 if (isLoggable) { 2221 Log.v(TAG, " Dropping sync operation: user not running."); 2222 } 2223 continue; 2224 } 2225 2226 // If the next run time is in the future, even given the flexible scheduling, 2227 // return the time. 2228 if (op.effectiveRunTime - op.flexTime > now) { 2229 if (nextReadyToRunTime > op.effectiveRunTime) { 2230 nextReadyToRunTime = op.effectiveRunTime; 2231 } 2232 if (isLoggable) { 2233 Log.v(TAG, " Dropping sync operation: Sync too far in future."); 2234 } 2235 continue; 2236 } 2237 2238 // If the op isn't allowed on metered networks and we're on one, drop it. 2239 if (getConnectivityManager().isActiveNetworkMetered() 2240 && op.isMeteredDisallowed()) { 2241 operationIterator.remove(); 2242 mSyncStorageEngine.deleteFromPending(op.pendingOperation); 2243 continue; 2244 } 2245 2246 // TODO: change this behaviour for non-registered syncs. 2247 final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo; 2248 syncAdapterInfo = mSyncAdapters.getServiceInfo( 2249 SyncAdapterType.newKey(op.authority, op.account.type), op.userId); 2250 2251 // only proceed if network is connected for requesting UID 2252 final boolean uidNetworkConnected; 2253 if (syncAdapterInfo != null) { 2254 final NetworkInfo networkInfo = getConnectivityManager() 2255 .getActiveNetworkInfoForUid(syncAdapterInfo.uid); 2256 uidNetworkConnected = networkInfo != null && networkInfo.isConnected(); 2257 } else { 2258 uidNetworkConnected = false; 2259 } 2260 2261 // skip the sync if it isn't manual, and auto sync or 2262 // background data usage is disabled or network is 2263 // disconnected for the target UID. 2264 if (!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false) 2265 && (syncableState > 0) 2266 && (!mSyncStorageEngine.getMasterSyncAutomatically(op.userId) 2267 || !backgroundDataUsageAllowed 2268 || !uidNetworkConnected 2269 || !mSyncStorageEngine.getSyncAutomatically( 2270 op.account, op.userId, op.authority))) { 2271 operationIterator.remove(); 2272 mSyncStorageEngine.deleteFromPending(op.pendingOperation); 2273 continue; 2274 } 2275 2276 operations.add(op); 2277 } 2278 for (Integer user : removedUsers) { 2279 // if it's still removed 2280 if (mUserManager.getUserInfo(user) == null) { 2281 onUserRemoved(user); 2282 } 2283 } 2284 } 2285 2286 // find the next operation to dispatch, if one is ready 2287 // iterate from the top, keep issuing (while potentially canceling existing syncs) 2288 // until the quotas are filled. 2289 // once the quotas are filled iterate once more to find when the next one would be 2290 // (also considering pre-emption reasons). 2291 if (isLoggable) Log.v(TAG, "sort the candidate operations, size " + operations.size()); 2292 Collections.sort(operations); 2293 if (isLoggable) Log.v(TAG, "dispatch all ready sync operations"); 2294 for (int i = 0, N = operations.size(); i < N; i++) { 2295 final SyncOperation candidate = operations.get(i); 2296 final boolean candidateIsInitialization = candidate.isInitialization(); 2297 2298 int numInit = 0; 2299 int numRegular = 0; 2300 ActiveSyncContext conflict = null; 2301 ActiveSyncContext longRunning = null; 2302 ActiveSyncContext toReschedule = null; 2303 ActiveSyncContext oldestNonExpeditedRegular = null; 2304 2305 for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) { 2306 final SyncOperation activeOp = activeSyncContext.mSyncOperation; 2307 if (activeOp.isInitialization()) { 2308 numInit++; 2309 } else { 2310 numRegular++; 2311 if (!activeOp.isExpedited()) { 2312 if (oldestNonExpeditedRegular == null 2313 || (oldestNonExpeditedRegular.mStartTime 2314 > activeSyncContext.mStartTime)) { 2315 oldestNonExpeditedRegular = activeSyncContext; 2316 } 2317 } 2318 } 2319 if (activeOp.account.type.equals(candidate.account.type) 2320 && activeOp.authority.equals(candidate.authority) 2321 && activeOp.userId == candidate.userId 2322 && (!activeOp.allowParallelSyncs 2323 || activeOp.account.name.equals(candidate.account.name))) { 2324 conflict = activeSyncContext; 2325 // don't break out since we want to do a full count of the varieties 2326 } else { 2327 if (candidateIsInitialization == activeOp.isInitialization() 2328 && activeSyncContext.mStartTime + MAX_TIME_PER_SYNC < now) { 2329 longRunning = activeSyncContext; 2330 // don't break out since we want to do a full count of the varieties 2331 } 2332 } 2333 } 2334 2335 if (isLoggable) { 2336 Log.v(TAG, "candidate " + (i + 1) + " of " + N + ": " + candidate); 2337 Log.v(TAG, " numActiveInit=" + numInit + ", numActiveRegular=" + numRegular); 2338 Log.v(TAG, " longRunning: " + longRunning); 2339 Log.v(TAG, " conflict: " + conflict); 2340 Log.v(TAG, " oldestNonExpeditedRegular: " + oldestNonExpeditedRegular); 2341 } 2342 2343 final boolean roomAvailable = candidateIsInitialization 2344 ? numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS 2345 : numRegular < MAX_SIMULTANEOUS_REGULAR_SYNCS; 2346 2347 if (conflict != null) { 2348 if (candidateIsInitialization && !conflict.mSyncOperation.isInitialization() 2349 && numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS) { 2350 toReschedule = conflict; 2351 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2352 Log.v(TAG, "canceling and rescheduling sync since an initialization " 2353 + "takes higher priority, " + conflict); 2354 } 2355 } else if (candidate.isExpedited() && !conflict.mSyncOperation.isExpedited() 2356 && (candidateIsInitialization 2357 == conflict.mSyncOperation.isInitialization())) { 2358 toReschedule = conflict; 2359 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2360 Log.v(TAG, "canceling and rescheduling sync since an expedited " 2361 + "takes higher priority, " + conflict); 2362 } 2363 } else { 2364 continue; 2365 } 2366 } else if (roomAvailable) { 2367 // dispatch candidate 2368 } else if (candidate.isExpedited() && oldestNonExpeditedRegular != null 2369 && !candidateIsInitialization) { 2370 // We found an active, non-expedited regular sync. We also know that the 2371 // candidate doesn't conflict with this active sync since conflict 2372 // is null. Reschedule the active sync and start the candidate. 2373 toReschedule = oldestNonExpeditedRegular; 2374 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2375 Log.v(TAG, "canceling and rescheduling sync since an expedited is ready to run, " 2376 + oldestNonExpeditedRegular); 2377 } 2378 } else if (longRunning != null 2379 && (candidateIsInitialization 2380 == longRunning.mSyncOperation.isInitialization())) { 2381 // We found an active, long-running sync. Reschedule the active 2382 // sync and start the candidate. 2383 toReschedule = longRunning; 2384 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2385 Log.v(TAG, "canceling and rescheduling sync since it ran roo long, " 2386 + longRunning); 2387 } 2388 } else { 2389 // we were unable to find or make space to run this candidate, go on to 2390 // the next one 2391 continue; 2392 } 2393 2394 if (toReschedule != null) { 2395 runSyncFinishedOrCanceledLocked(null, toReschedule); 2396 scheduleSyncOperation(toReschedule.mSyncOperation); 2397 } 2398 synchronized (mSyncQueue) { 2399 mSyncQueue.remove(candidate); 2400 } 2401 dispatchSyncOperation(candidate); 2402 } 2403 2404 return nextReadyToRunTime; 2405 } 2406 2407 private boolean dispatchSyncOperation(SyncOperation op) { 2408 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2409 Log.v(TAG, "dispatchSyncOperation: we are going to sync " + op); 2410 Log.v(TAG, "num active syncs: " + mActiveSyncContexts.size()); 2411 for (ActiveSyncContext syncContext : mActiveSyncContexts) { 2412 Log.v(TAG, syncContext.toString()); 2413 } 2414 } 2415 2416 // connect to the sync adapter 2417 SyncAdapterType syncAdapterType = SyncAdapterType.newKey(op.authority, op.account.type); 2418 final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo; 2419 syncAdapterInfo = mSyncAdapters.getServiceInfo(syncAdapterType, op.userId); 2420 if (syncAdapterInfo == null) { 2421 Log.d(TAG, "can't find a sync adapter for " + syncAdapterType 2422 + ", removing settings for it"); 2423 mSyncStorageEngine.removeAuthority(op.account, op.userId, op.authority); 2424 return false; 2425 } 2426 2427 ActiveSyncContext activeSyncContext = 2428 new ActiveSyncContext(op, insertStartSyncEvent(op), syncAdapterInfo.uid); 2429 activeSyncContext.mSyncInfo = mSyncStorageEngine.addActiveSync(activeSyncContext); 2430 mActiveSyncContexts.add(activeSyncContext); 2431 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2432 Log.v(TAG, "dispatchSyncOperation: starting " + activeSyncContext); 2433 } 2434 if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo, op.userId)) { 2435 Log.e(TAG, "Bind attempt failed to " + syncAdapterInfo); 2436 closeActiveSyncContext(activeSyncContext); 2437 return false; 2438 } 2439 2440 return true; 2441 } 2442 2443 private void runBoundToSyncAdapter(final ActiveSyncContext activeSyncContext, 2444 ISyncAdapter syncAdapter) { 2445 activeSyncContext.mSyncAdapter = syncAdapter; 2446 final SyncOperation syncOperation = activeSyncContext.mSyncOperation; 2447 try { 2448 activeSyncContext.mIsLinkedToDeath = true; 2449 syncAdapter.asBinder().linkToDeath(activeSyncContext, 0); 2450 2451 syncAdapter.startSync(activeSyncContext, syncOperation.authority, 2452 syncOperation.account, syncOperation.extras); 2453 } catch (RemoteException remoteExc) { 2454 Log.d(TAG, "maybeStartNextSync: caught a RemoteException, rescheduling", remoteExc); 2455 closeActiveSyncContext(activeSyncContext); 2456 increaseBackoffSetting(syncOperation); 2457 scheduleSyncOperation(new SyncOperation(syncOperation)); 2458 } catch (RuntimeException exc) { 2459 closeActiveSyncContext(activeSyncContext); 2460 Log.e(TAG, "Caught RuntimeException while starting the sync " + syncOperation, exc); 2461 } 2462 } 2463 2464 private void cancelActiveSyncLocked(Account account, int userId, String authority) { 2465 ArrayList<ActiveSyncContext> activeSyncs = 2466 new ArrayList<ActiveSyncContext>(mActiveSyncContexts); 2467 for (ActiveSyncContext activeSyncContext : activeSyncs) { 2468 if (activeSyncContext != null) { 2469 // if an account was specified then only cancel the sync if it matches 2470 if (account != null) { 2471 if (!account.equals(activeSyncContext.mSyncOperation.account)) { 2472 continue; 2473 } 2474 } 2475 // if an authority was specified then only cancel the sync if it matches 2476 if (authority != null) { 2477 if (!authority.equals(activeSyncContext.mSyncOperation.authority)) { 2478 continue; 2479 } 2480 } 2481 // check if the userid matches 2482 if (userId != UserHandle.USER_ALL 2483 && userId != activeSyncContext.mSyncOperation.userId) { 2484 continue; 2485 } 2486 runSyncFinishedOrCanceledLocked(null /* no result since this is a cancel */, 2487 activeSyncContext); 2488 } 2489 } 2490 } 2491 2492 private void runSyncFinishedOrCanceledLocked(SyncResult syncResult, 2493 ActiveSyncContext activeSyncContext) { 2494 boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); 2495 2496 if (activeSyncContext.mIsLinkedToDeath) { 2497 activeSyncContext.mSyncAdapter.asBinder().unlinkToDeath(activeSyncContext, 0); 2498 activeSyncContext.mIsLinkedToDeath = false; 2499 } 2500 closeActiveSyncContext(activeSyncContext); 2501 2502 final SyncOperation syncOperation = activeSyncContext.mSyncOperation; 2503 2504 final long elapsedTime = SystemClock.elapsedRealtime() - activeSyncContext.mStartTime; 2505 2506 String historyMessage; 2507 int downstreamActivity; 2508 int upstreamActivity; 2509 if (syncResult != null) { 2510 if (isLoggable) { 2511 Log.v(TAG, "runSyncFinishedOrCanceled [finished]: " 2512 + syncOperation + ", result " + syncResult); 2513 } 2514 2515 if (!syncResult.hasError()) { 2516 historyMessage = SyncStorageEngine.MESG_SUCCESS; 2517 // TODO: set these correctly when the SyncResult is extended to include it 2518 downstreamActivity = 0; 2519 upstreamActivity = 0; 2520 clearBackoffSetting(syncOperation); 2521 } else { 2522 Log.d(TAG, "failed sync operation " + syncOperation + ", " + syncResult); 2523 // the operation failed so increase the backoff time 2524 if (!syncResult.syncAlreadyInProgress) { 2525 increaseBackoffSetting(syncOperation); 2526 } 2527 // reschedule the sync if so indicated by the syncResult 2528 maybeRescheduleSync(syncResult, syncOperation); 2529 historyMessage = ContentResolver.syncErrorToString( 2530 syncResultToErrorNumber(syncResult)); 2531 // TODO: set these correctly when the SyncResult is extended to include it 2532 downstreamActivity = 0; 2533 upstreamActivity = 0; 2534 } 2535 2536 setDelayUntilTime(syncOperation, syncResult.delayUntil); 2537 } else { 2538 if (isLoggable) { 2539 Log.v(TAG, "runSyncFinishedOrCanceled [canceled]: " + syncOperation); 2540 } 2541 if (activeSyncContext.mSyncAdapter != null) { 2542 try { 2543 activeSyncContext.mSyncAdapter.cancelSync(activeSyncContext); 2544 } catch (RemoteException e) { 2545 // we don't need to retry this in this case 2546 } 2547 } 2548 historyMessage = SyncStorageEngine.MESG_CANCELED; 2549 downstreamActivity = 0; 2550 upstreamActivity = 0; 2551 } 2552 2553 stopSyncEvent(activeSyncContext.mHistoryRowId, syncOperation, historyMessage, 2554 upstreamActivity, downstreamActivity, elapsedTime); 2555 2556 if (syncResult != null && syncResult.tooManyDeletions) { 2557 installHandleTooManyDeletesNotification(syncOperation.account, 2558 syncOperation.authority, syncResult.stats.numDeletes, 2559 syncOperation.userId); 2560 } else { 2561 mNotificationMgr.cancelAsUser(null, 2562 syncOperation.account.hashCode() ^ syncOperation.authority.hashCode(), 2563 new UserHandle(syncOperation.userId)); 2564 } 2565 2566 if (syncResult != null && syncResult.fullSyncRequested) { 2567 scheduleSyncOperation( 2568 new SyncOperation(syncOperation.account, syncOperation.userId, 2569 syncOperation.reason, 2570 syncOperation.syncSource, syncOperation.authority, new Bundle(), 2571 0 /* delay */, 0 /* flex */, 2572 syncOperation.backoff, syncOperation.delayUntil, 2573 syncOperation.allowParallelSyncs)); 2574 } 2575 // no need to schedule an alarm, as that will be done by our caller. 2576 } 2577 2578 private void closeActiveSyncContext(ActiveSyncContext activeSyncContext) { 2579 activeSyncContext.close(); 2580 mActiveSyncContexts.remove(activeSyncContext); 2581 mSyncStorageEngine.removeActiveSync(activeSyncContext.mSyncInfo, 2582 activeSyncContext.mSyncOperation.userId); 2583 } 2584 2585 /** 2586 * Convert the error-containing SyncResult into the Sync.History error number. Since 2587 * the SyncResult may indicate multiple errors at once, this method just returns the 2588 * most "serious" error. 2589 * @param syncResult the SyncResult from which to read 2590 * @return the most "serious" error set in the SyncResult 2591 * @throws IllegalStateException if the SyncResult does not indicate any errors. 2592 * If SyncResult.error() is true then it is safe to call this. 2593 */ 2594 private int syncResultToErrorNumber(SyncResult syncResult) { 2595 if (syncResult.syncAlreadyInProgress) 2596 return ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS; 2597 if (syncResult.stats.numAuthExceptions > 0) 2598 return ContentResolver.SYNC_ERROR_AUTHENTICATION; 2599 if (syncResult.stats.numIoExceptions > 0) 2600 return ContentResolver.SYNC_ERROR_IO; 2601 if (syncResult.stats.numParseExceptions > 0) 2602 return ContentResolver.SYNC_ERROR_PARSE; 2603 if (syncResult.stats.numConflictDetectedExceptions > 0) 2604 return ContentResolver.SYNC_ERROR_CONFLICT; 2605 if (syncResult.tooManyDeletions) 2606 return ContentResolver.SYNC_ERROR_TOO_MANY_DELETIONS; 2607 if (syncResult.tooManyRetries) 2608 return ContentResolver.SYNC_ERROR_TOO_MANY_RETRIES; 2609 if (syncResult.databaseError) 2610 return ContentResolver.SYNC_ERROR_INTERNAL; 2611 throw new IllegalStateException("we are not in an error state, " + syncResult); 2612 } 2613 2614 private void manageSyncNotificationLocked() { 2615 boolean shouldCancel; 2616 boolean shouldInstall; 2617 2618 if (mActiveSyncContexts.isEmpty()) { 2619 mSyncNotificationInfo.startTime = null; 2620 2621 // we aren't syncing. if the notification is active then remember that we need 2622 // to cancel it and then clear out the info 2623 shouldCancel = mSyncNotificationInfo.isActive; 2624 shouldInstall = false; 2625 } else { 2626 // we are syncing 2627 final long now = SystemClock.elapsedRealtime(); 2628 if (mSyncNotificationInfo.startTime == null) { 2629 mSyncNotificationInfo.startTime = now; 2630 } 2631 2632 // there are three cases: 2633 // - the notification is up: do nothing 2634 // - the notification is not up but it isn't time yet: don't install 2635 // - the notification is not up and it is time: need to install 2636 2637 if (mSyncNotificationInfo.isActive) { 2638 shouldInstall = shouldCancel = false; 2639 } else { 2640 // it isn't currently up, so there is nothing to cancel 2641 shouldCancel = false; 2642 2643 final boolean timeToShowNotification = 2644 now > mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY; 2645 if (timeToShowNotification) { 2646 shouldInstall = true; 2647 } else { 2648 // show the notification immediately if this is a manual sync 2649 shouldInstall = false; 2650 for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) { 2651 final boolean manualSync = activeSyncContext.mSyncOperation.extras 2652 .getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false); 2653 if (manualSync) { 2654 shouldInstall = true; 2655 break; 2656 } 2657 } 2658 } 2659 } 2660 } 2661 2662 if (shouldCancel && !shouldInstall) { 2663 mNeedSyncActiveNotification = false; 2664 sendSyncStateIntent(); 2665 mSyncNotificationInfo.isActive = false; 2666 } 2667 2668 if (shouldInstall) { 2669 mNeedSyncActiveNotification = true; 2670 sendSyncStateIntent(); 2671 mSyncNotificationInfo.isActive = true; 2672 } 2673 } 2674 2675 private void manageSyncAlarmLocked(long nextPeriodicEventElapsedTime, 2676 long nextPendingEventElapsedTime) { 2677 // in each of these cases the sync loop will be kicked, which will cause this 2678 // method to be called again 2679 if (!mDataConnectionIsConnected) return; 2680 if (mStorageIsLow) return; 2681 2682 // When the status bar notification should be raised 2683 final long notificationTime = 2684 (!mSyncHandler.mSyncNotificationInfo.isActive 2685 && mSyncHandler.mSyncNotificationInfo.startTime != null) 2686 ? mSyncHandler.mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY 2687 : Long.MAX_VALUE; 2688 2689 // When we should consider canceling an active sync 2690 long earliestTimeoutTime = Long.MAX_VALUE; 2691 for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) { 2692 final long currentSyncTimeoutTime = 2693 currentSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC; 2694 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2695 Log.v(TAG, "manageSyncAlarm: active sync, mTimeoutStartTime + MAX is " 2696 + currentSyncTimeoutTime); 2697 } 2698 if (earliestTimeoutTime > currentSyncTimeoutTime) { 2699 earliestTimeoutTime = currentSyncTimeoutTime; 2700 } 2701 } 2702 2703 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2704 Log.v(TAG, "manageSyncAlarm: notificationTime is " + notificationTime); 2705 } 2706 2707 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2708 Log.v(TAG, "manageSyncAlarm: earliestTimeoutTime is " + earliestTimeoutTime); 2709 } 2710 2711 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2712 Log.v(TAG, "manageSyncAlarm: nextPeriodicEventElapsedTime is " 2713 + nextPeriodicEventElapsedTime); 2714 } 2715 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2716 Log.v(TAG, "manageSyncAlarm: nextPendingEventElapsedTime is " 2717 + nextPendingEventElapsedTime); 2718 } 2719 2720 long alarmTime = Math.min(notificationTime, earliestTimeoutTime); 2721 alarmTime = Math.min(alarmTime, nextPeriodicEventElapsedTime); 2722 alarmTime = Math.min(alarmTime, nextPendingEventElapsedTime); 2723 2724 // Bound the alarm time. 2725 final long now = SystemClock.elapsedRealtime(); 2726 if (alarmTime < now + SYNC_ALARM_TIMEOUT_MIN) { 2727 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2728 Log.v(TAG, "manageSyncAlarm: the alarmTime is too small, " 2729 + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN)); 2730 } 2731 alarmTime = now + SYNC_ALARM_TIMEOUT_MIN; 2732 } else if (alarmTime > now + SYNC_ALARM_TIMEOUT_MAX) { 2733 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2734 Log.v(TAG, "manageSyncAlarm: the alarmTime is too large, " 2735 + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN)); 2736 } 2737 alarmTime = now + SYNC_ALARM_TIMEOUT_MAX; 2738 } 2739 2740 // determine if we need to set or cancel the alarm 2741 boolean shouldSet = false; 2742 boolean shouldCancel = false; 2743 final boolean alarmIsActive = (mAlarmScheduleTime != null) && (now < mAlarmScheduleTime); 2744 final boolean needAlarm = alarmTime != Long.MAX_VALUE; 2745 if (needAlarm) { 2746 // Need the alarm if 2747 // - it's currently not set 2748 // - if the alarm is set in the past. 2749 if (!alarmIsActive || alarmTime < mAlarmScheduleTime) { 2750 shouldSet = true; 2751 } 2752 } else { 2753 shouldCancel = alarmIsActive; 2754 } 2755 2756 // Set or cancel the alarm as directed. 2757 ensureAlarmService(); 2758 if (shouldSet) { 2759 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2760 Log.v(TAG, "requesting that the alarm manager wake us up at elapsed time " 2761 + alarmTime + ", now is " + now + ", " + ((alarmTime - now) / 1000) 2762 + " secs from now"); 2763 } 2764 mAlarmScheduleTime = alarmTime; 2765 mAlarmService.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, 2766 mSyncAlarmIntent); 2767 } else if (shouldCancel) { 2768 mAlarmScheduleTime = null; 2769 mAlarmService.cancel(mSyncAlarmIntent); 2770 } 2771 } 2772 2773 private void sendSyncStateIntent() { 2774 Intent syncStateIntent = new Intent(Intent.ACTION_SYNC_STATE_CHANGED); 2775 syncStateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 2776 syncStateIntent.putExtra("active", mNeedSyncActiveNotification); 2777 syncStateIntent.putExtra("failing", false); 2778 mContext.sendBroadcastAsUser(syncStateIntent, UserHandle.OWNER); 2779 } 2780 2781 private void installHandleTooManyDeletesNotification(Account account, String authority, 2782 long numDeletes, int userId) { 2783 if (mNotificationMgr == null) return; 2784 2785 final ProviderInfo providerInfo = mContext.getPackageManager().resolveContentProvider( 2786 authority, 0 /* flags */); 2787 if (providerInfo == null) { 2788 return; 2789 } 2790 CharSequence authorityName = providerInfo.loadLabel(mContext.getPackageManager()); 2791 2792 Intent clickIntent = new Intent(mContext, SyncActivityTooManyDeletes.class); 2793 clickIntent.putExtra("account", account); 2794 clickIntent.putExtra("authority", authority); 2795 clickIntent.putExtra("provider", authorityName.toString()); 2796 clickIntent.putExtra("numDeletes", numDeletes); 2797 2798 if (!isActivityAvailable(clickIntent)) { 2799 Log.w(TAG, "No activity found to handle too many deletes."); 2800 return; 2801 } 2802 2803 final PendingIntent pendingIntent = PendingIntent 2804 .getActivityAsUser(mContext, 0, clickIntent, 2805 PendingIntent.FLAG_CANCEL_CURRENT, null, new UserHandle(userId)); 2806 2807 CharSequence tooManyDeletesDescFormat = mContext.getResources().getText( 2808 R.string.contentServiceTooManyDeletesNotificationDesc); 2809 2810 Notification notification = 2811 new Notification(R.drawable.stat_notify_sync_error, 2812 mContext.getString(R.string.contentServiceSync), 2813 System.currentTimeMillis()); 2814 notification.setLatestEventInfo(mContext, 2815 mContext.getString(R.string.contentServiceSyncNotificationTitle), 2816 String.format(tooManyDeletesDescFormat.toString(), authorityName), 2817 pendingIntent); 2818 notification.flags |= Notification.FLAG_ONGOING_EVENT; 2819 mNotificationMgr.notifyAsUser(null, account.hashCode() ^ authority.hashCode(), 2820 notification, new UserHandle(userId)); 2821 } 2822 2823 /** 2824 * Checks whether an activity exists on the system image for the given intent. 2825 * 2826 * @param intent The intent for an activity. 2827 * @return Whether or not an activity exists. 2828 */ 2829 private boolean isActivityAvailable(Intent intent) { 2830 PackageManager pm = mContext.getPackageManager(); 2831 List<ResolveInfo> list = pm.queryIntentActivities(intent, 0); 2832 int listSize = list.size(); 2833 for (int i = 0; i < listSize; i++) { 2834 ResolveInfo resolveInfo = list.get(i); 2835 if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) 2836 != 0) { 2837 return true; 2838 } 2839 } 2840 2841 return false; 2842 } 2843 2844 public long insertStartSyncEvent(SyncOperation syncOperation) { 2845 final int source = syncOperation.syncSource; 2846 final long now = System.currentTimeMillis(); 2847 2848 EventLog.writeEvent(2720, syncOperation.authority, 2849 SyncStorageEngine.EVENT_START, source, 2850 syncOperation.account.name.hashCode()); 2851 2852 return mSyncStorageEngine.insertStartSyncEvent( 2853 syncOperation.account, syncOperation.userId, syncOperation.reason, 2854 syncOperation.authority, 2855 now, source, syncOperation.isInitialization(), syncOperation.extras 2856 ); 2857 } 2858 2859 public void stopSyncEvent(long rowId, SyncOperation syncOperation, String resultMessage, 2860 int upstreamActivity, int downstreamActivity, long elapsedTime) { 2861 EventLog.writeEvent(2720, syncOperation.authority, 2862 SyncStorageEngine.EVENT_STOP, syncOperation.syncSource, 2863 syncOperation.account.name.hashCode()); 2864 2865 mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime, 2866 resultMessage, downstreamActivity, upstreamActivity); 2867 } 2868 } 2869 2870 private boolean isSyncStillActive(ActiveSyncContext activeSyncContext) { 2871 for (ActiveSyncContext sync : mActiveSyncContexts) { 2872 if (sync == activeSyncContext) { 2873 return true; 2874 } 2875 } 2876 return false; 2877 } 2878 2879 static class PrintTable { 2880 private ArrayList<Object[]> mTable = Lists.newArrayList(); 2881 private final int mCols; 2882 2883 PrintTable(int cols) { 2884 mCols = cols; 2885 } 2886 2887 void set(int row, int col, Object... values) { 2888 if (col + values.length > mCols) { 2889 throw new IndexOutOfBoundsException("Table only has " + mCols + 2890 " columns. can't set " + values.length + " at column " + col); 2891 } 2892 for (int i = mTable.size(); i <= row; i++) { 2893 final Object[] list = new Object[mCols]; 2894 mTable.add(list); 2895 for (int j = 0; j < mCols; j++) { 2896 list[j] = ""; 2897 } 2898 } 2899 System.arraycopy(values, 0, mTable.get(row), col, values.length); 2900 } 2901 2902 void writeTo(PrintWriter out) { 2903 final String[] formats = new String[mCols]; 2904 int totalLength = 0; 2905 for (int col = 0; col < mCols; ++col) { 2906 int maxLength = 0; 2907 for (Object[] row : mTable) { 2908 final int length = row[col].toString().length(); 2909 if (length > maxLength) { 2910 maxLength = length; 2911 } 2912 } 2913 totalLength += maxLength; 2914 formats[col] = String.format("%%-%ds", maxLength); 2915 } 2916 printRow(out, formats, mTable.get(0)); 2917 totalLength += (mCols - 1) * 2; 2918 for (int i = 0; i < totalLength; ++i) { 2919 out.print("-"); 2920 } 2921 out.println(); 2922 for (int i = 1, mTableSize = mTable.size(); i < mTableSize; i++) { 2923 Object[] row = mTable.get(i); 2924 printRow(out, formats, row); 2925 } 2926 } 2927 2928 private void printRow(PrintWriter out, String[] formats, Object[] row) { 2929 for (int j = 0, rowLength = row.length; j < rowLength; j++) { 2930 out.printf(String.format(formats[j], row[j].toString())); 2931 out.print(" "); 2932 } 2933 out.println(); 2934 } 2935 2936 public int getNumRows() { 2937 return mTable.size(); 2938 } 2939 } 2940 } 2941