SyncManager.java revision bd4c3ea7ae57f43dbaca6456f9a2853d2e7d827d
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server.content; 18 19import android.accounts.Account; 20import android.accounts.AccountAndUser; 21import android.accounts.AccountManager; 22import android.app.ActivityManager; 23import android.app.ActivityManagerNative; 24import android.app.AppGlobals; 25import android.app.Notification; 26import android.app.NotificationManager; 27import android.app.PendingIntent; 28import android.app.job.JobInfo; 29import android.app.job.JobScheduler; 30import android.content.BroadcastReceiver; 31import android.content.ComponentName; 32import android.content.ContentResolver; 33import android.content.Context; 34import android.content.ISyncAdapter; 35import android.content.ISyncContext; 36import android.content.Intent; 37import android.content.IntentFilter; 38import android.content.PeriodicSync; 39import android.content.ServiceConnection; 40import android.content.SyncActivityTooManyDeletes; 41import android.content.SyncAdapterType; 42import android.content.SyncAdaptersCache; 43import android.content.SyncInfo; 44import android.content.SyncResult; 45import android.content.SyncStatusInfo; 46import android.content.pm.ApplicationInfo; 47import android.content.pm.PackageInfo; 48import android.content.pm.PackageManager; 49import android.content.pm.PackageManager.NameNotFoundException; 50import android.content.pm.ProviderInfo; 51import android.content.pm.RegisteredServicesCache; 52import android.content.pm.RegisteredServicesCacheListener; 53import android.content.pm.ResolveInfo; 54import android.content.pm.UserInfo; 55import android.database.ContentObserver; 56import android.net.ConnectivityManager; 57import android.net.NetworkInfo; 58import android.net.TrafficStats; 59import android.os.BatteryStats; 60import android.os.Bundle; 61import android.os.Handler; 62import android.os.IBinder; 63import android.os.Looper; 64import android.os.Message; 65import android.os.Messenger; 66import android.os.PowerManager; 67import android.os.RemoteException; 68import android.os.ServiceManager; 69import android.os.SystemClock; 70import android.os.SystemProperties; 71import android.os.UserHandle; 72import android.os.UserManager; 73import android.os.WorkSource; 74import android.provider.Settings; 75import android.text.format.DateUtils; 76import android.text.format.Time; 77import android.util.EventLog; 78import android.util.Log; 79import android.util.Pair; 80import android.util.Slog; 81 82import com.android.server.LocalServices; 83import com.android.server.job.JobSchedulerInternal; 84import com.google.android.collect.Lists; 85import com.google.android.collect.Maps; 86 87import com.android.internal.R; 88import com.android.internal.app.IBatteryStats; 89import com.android.internal.os.BackgroundThread; 90import com.android.internal.util.IndentingPrintWriter; 91import com.android.server.accounts.AccountManagerService; 92import com.android.server.backup.AccountSyncSettingsBackupHelper; 93import com.android.server.content.SyncStorageEngine.AuthorityInfo; 94import com.android.server.content.SyncStorageEngine.EndPoint; 95import com.android.server.content.SyncStorageEngine.OnSyncRequestListener; 96 97import java.io.FileDescriptor; 98import java.io.PrintWriter; 99import java.util.ArrayList; 100import java.util.Arrays; 101import java.util.Collection; 102import java.util.Collections; 103import java.util.Comparator; 104import java.util.HashMap; 105import java.util.HashSet; 106import java.util.List; 107import java.util.Map; 108import java.util.Objects; 109import java.util.Random; 110import java.util.Set; 111 112/** 113 * Implementation details: 114 * All scheduled syncs will be passed on to JobScheduler as jobs 115 * (See {@link #scheduleSyncOperationH(SyncOperation, long)}. This function schedules a job 116 * with JobScheduler with appropriate delay and constraints (according to backoffs and extras). 117 * The scheduleSyncOperationH function also assigns a unique jobId to each 118 * SyncOperation. 119 * 120 * Periodic Syncs: 121 * Each periodic sync is scheduled as a periodic job. If a periodic sync fails, we create a new 122 * one off SyncOperation and set its {@link SyncOperation#sourcePeriodicId} field to the jobId of the 123 * periodic sync. We don't allow the periodic job to run while any job initiated by it is pending. 124 * 125 * Backoffs: 126 * Each {@link EndPoint} has a backoff associated with it. When a SyncOperation fails, we increase 127 * the backoff on the authority. Then we reschedule all syncs associated with that authority to 128 * run at a later time. Similarly, when a sync succeeds, backoff is cleared and all associated syncs 129 * are rescheduled. A rescheduled sync will get a new jobId. 130 * 131 * @hide 132 */ 133public class SyncManager { 134 static final String TAG = "SyncManager"; 135 136 /** Delay a sync due to local changes this long. In milliseconds */ 137 private static final long LOCAL_SYNC_DELAY; 138 139 static { 140 LOCAL_SYNC_DELAY = 141 SystemProperties.getLong("sync.local_sync_delay", 30 * 1000 /* 30 seconds */); 142 } 143 144 /** 145 * When retrying a sync for the first time use this delay. After that 146 * the retry time will double until it reached MAX_SYNC_RETRY_TIME. 147 * In milliseconds. 148 */ 149 private static final long INITIAL_SYNC_RETRY_TIME_IN_MS = 30 * 1000; // 30 seconds 150 151 /** 152 * Default the max sync retry time to this value. 153 */ 154 private static final long DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS = 60 * 60; // one hour 155 156 /** 157 * How long to wait before retrying a sync that failed due to one already being in progress. 158 */ 159 private static final int DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS = 10; 160 161 /** 162 * How often to periodically poll network traffic for an adapter performing a sync to determine 163 * whether progress is being made. 164 */ 165 private static final long SYNC_MONITOR_WINDOW_LENGTH_MILLIS = 60 * 1000; // 60 seconds 166 167 /** 168 * How many bytes must be transferred (Tx + Rx) over the period of time defined by 169 * {@link #SYNC_MONITOR_WINDOW_LENGTH_MILLIS} for the sync to be considered to be making 170 * progress. 171 */ 172 private static final int SYNC_MONITOR_PROGRESS_THRESHOLD_BYTES = 10; // 10 bytes 173 174 /** 175 * If a previously scheduled sync becomes ready and we are low on storage, it gets 176 * pushed back for this amount of time. 177 */ 178 private static final long SYNC_DELAY_ON_LOW_STORAGE = 60*60*1000; // 1 hour 179 180 /** 181 * If a sync becomes ready and it conflicts with an already running sync, it gets 182 * pushed back for this amount of time. 183 */ 184 private static final long SYNC_DELAY_ON_CONFLICT = 10*1000; // 10 seconds 185 186 /** 187 * Generate job ids in the range [MIN_SYNC_JOB_ID, MAX_SYNC_JOB_ID) to avoid conflicts with 188 * other jobs scheduled by the system process. 189 */ 190 private static final int MIN_SYNC_JOB_ID = 100000; 191 private static final int MAX_SYNC_JOB_ID = 110000; 192 193 private static final String SYNC_WAKE_LOCK_PREFIX = "*sync*/"; 194 private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm"; 195 private static final String SYNC_LOOP_WAKE_LOCK = "SyncLoopWakeLock"; 196 197 private Context mContext; 198 199 private static final AccountAndUser[] INITIAL_ACCOUNTS_ARRAY = new AccountAndUser[0]; 200 201 // TODO: add better locking around mRunningAccounts 202 private volatile AccountAndUser[] mRunningAccounts = INITIAL_ACCOUNTS_ARRAY; 203 204 volatile private PowerManager.WakeLock mHandleAlarmWakeLock; 205 volatile private PowerManager.WakeLock mSyncManagerWakeLock; 206 volatile private boolean mDataConnectionIsConnected = false; 207 volatile private boolean mStorageIsLow = false; 208 volatile private boolean mDeviceIsIdle = false; 209 volatile private boolean mReportedSyncActive = false; 210 211 private final NotificationManager mNotificationMgr; 212 private final IBatteryStats mBatteryStats; 213 private JobScheduler mJobScheduler; 214 private JobSchedulerInternal mJobSchedulerInternal; 215 private SyncJobService mSyncJobService; 216 217 private SyncStorageEngine mSyncStorageEngine; 218 219 protected final ArrayList<ActiveSyncContext> mActiveSyncContexts = Lists.newArrayList(); 220 221 // Synchronized on "this". Instead of using this directly one should instead call 222 // its accessor, getConnManager(). 223 private ConnectivityManager mConnManagerDoNotUseDirectly; 224 225 /** Track whether the device has already been provisioned. */ 226 private volatile boolean mProvisioned; 227 228 protected SyncAdaptersCache mSyncAdapters; 229 230 private final Random mRand; 231 232 private boolean isJobIdInUseLockedH(int jobId, List<JobInfo> pendingJobs) { 233 for (JobInfo job: pendingJobs) { 234 if (job.getId() == jobId) { 235 return true; 236 } 237 } 238 for (ActiveSyncContext asc: mActiveSyncContexts) { 239 if (asc.mSyncOperation.jobId == jobId) { 240 return true; 241 } 242 } 243 return false; 244 } 245 246 private int getUnusedJobIdH() { 247 int newJobId; 248 do { 249 newJobId = MIN_SYNC_JOB_ID + mRand.nextInt(MAX_SYNC_JOB_ID - MIN_SYNC_JOB_ID); 250 } while (isJobIdInUseLockedH(newJobId, 251 mJobSchedulerInternal.getSystemScheduledPendingJobs())); 252 return newJobId; 253 } 254 255 private List<SyncOperation> getAllPendingSyncs() { 256 verifyJobScheduler(); 257 List<JobInfo> pendingJobs = mJobSchedulerInternal.getSystemScheduledPendingJobs(); 258 List<SyncOperation> pendingSyncs = new ArrayList<SyncOperation>(pendingJobs.size()); 259 for (JobInfo job: pendingJobs) { 260 SyncOperation op = SyncOperation.maybeCreateFromJobExtras(job.getExtras()); 261 if (op != null) { 262 pendingSyncs.add(op); 263 } 264 } 265 return pendingSyncs; 266 } 267 268 private final BroadcastReceiver mStorageIntentReceiver = 269 new BroadcastReceiver() { 270 @Override 271 public void onReceive(Context context, Intent intent) { 272 String action = intent.getAction(); 273 if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) { 274 if (Log.isLoggable(TAG, Log.VERBOSE)) { 275 Slog.v(TAG, "Internal storage is low."); 276 } 277 mStorageIsLow = true; 278 cancelActiveSync( 279 SyncStorageEngine.EndPoint.USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL, 280 null /* any sync */); 281 } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) { 282 if (Log.isLoggable(TAG, Log.VERBOSE)) { 283 Slog.v(TAG, "Internal storage is ok."); 284 } 285 mStorageIsLow = false; 286 rescheduleSyncs(EndPoint.USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL); 287 } 288 } 289 }; 290 291 private final BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() { 292 @Override 293 public void onReceive(Context context, Intent intent) { 294 mBootCompleted = true; 295 // Called because it gets all pending jobs and stores them in mScheduledSyncs cache. 296 verifyJobScheduler(); 297 mSyncHandler.onBootCompleted(); 298 } 299 }; 300 301 private final BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() { 302 @Override 303 public void onReceive(Context context, Intent intent) { 304 updateRunningAccounts(EndPoint.USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL 305 /* sync all targets */); 306 } 307 }; 308 309 private final PowerManager mPowerManager; 310 311 private final UserManager mUserManager; 312 313 private List<UserInfo> getAllUsers() { 314 return mUserManager.getUsers(); 315 } 316 317 private boolean containsAccountAndUser(AccountAndUser[] accounts, Account account, int userId) { 318 boolean found = false; 319 for (int i = 0; i < accounts.length; i++) { 320 if (accounts[i].userId == userId 321 && accounts[i].account.equals(account)) { 322 found = true; 323 break; 324 } 325 } 326 return found; 327 } 328 329 /** target indicates endpoints that should be synced after account info is updated. */ 330 private void updateRunningAccounts(EndPoint target) { 331 if (Log.isLoggable(TAG, Log.VERBOSE)) Slog.v(TAG, "sending MESSAGE_ACCOUNTS_UPDATED"); 332 // Update accounts in handler thread. 333 Message m = mSyncHandler.obtainMessage(SyncHandler.MESSAGE_ACCOUNTS_UPDATED); 334 m.obj = target; 335 m.sendToTarget(); 336 } 337 338 private void doDatabaseCleanup() { 339 for (UserInfo user : mUserManager.getUsers(true)) { 340 // Skip any partially created/removed users 341 if (user.partial) continue; 342 Account[] accountsForUser = AccountManagerService.getSingleton().getAccounts( 343 user.id, mContext.getOpPackageName()); 344 345 mSyncStorageEngine.doDatabaseCleanup(accountsForUser, user.id); 346 } 347 } 348 349 private BroadcastReceiver mConnectivityIntentReceiver = 350 new BroadcastReceiver() { 351 @Override 352 public void onReceive(Context context, Intent intent) { 353 final boolean wasConnected = mDataConnectionIsConnected; 354 355 // Don't use the intent to figure out if network is connected, just check 356 // ConnectivityManager directly. 357 mDataConnectionIsConnected = readDataConnectionState(); 358 if (mDataConnectionIsConnected) { 359 if (!wasConnected) { 360 if (Log.isLoggable(TAG, Log.VERBOSE)) { 361 Slog.v(TAG, "Reconnection detected: clearing all backoffs"); 362 } 363 } 364 clearAllBackoffs(); 365 } 366 } 367 }; 368 369 private void clearAllBackoffs() { 370 mSyncStorageEngine.clearAllBackoffsLocked(); 371 rescheduleSyncs(EndPoint.USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL); 372 } 373 374 private boolean readDataConnectionState() { 375 NetworkInfo networkInfo = getConnectivityManager().getActiveNetworkInfo(); 376 return (networkInfo != null) && networkInfo.isConnected(); 377 } 378 379 private BroadcastReceiver mShutdownIntentReceiver = 380 new BroadcastReceiver() { 381 @Override 382 public void onReceive(Context context, Intent intent) { 383 Log.w(TAG, "Writing sync state before shutdown..."); 384 getSyncStorageEngine().writeAllState(); 385 } 386 }; 387 388 private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() { 389 @Override 390 public void onReceive(Context context, Intent intent) { 391 String action = intent.getAction(); 392 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); 393 if (userId == UserHandle.USER_NULL) return; 394 395 if (Intent.ACTION_USER_REMOVED.equals(action)) { 396 onUserRemoved(userId); 397 } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) { 398 onUserUnlocked(userId); 399 } else if (Intent.ACTION_USER_STOPPED.equals(action)) { 400 onUserStopped(userId); 401 } 402 } 403 }; 404 405 private final SyncHandler mSyncHandler; 406 407 private volatile boolean mBootCompleted = false; 408 private volatile boolean mJobServiceReady = false; 409 410 private ConnectivityManager getConnectivityManager() { 411 synchronized (this) { 412 if (mConnManagerDoNotUseDirectly == null) { 413 mConnManagerDoNotUseDirectly = (ConnectivityManager)mContext.getSystemService( 414 Context.CONNECTIVITY_SERVICE); 415 } 416 return mConnManagerDoNotUseDirectly; 417 } 418 } 419 420 /** 421 * Cancel all unnecessary jobs. This function will be run once after every boot. 422 */ 423 private void cleanupJobs() { 424 // O(n^2) in number of jobs, so we run this on the background thread. 425 mSyncHandler.postAtFrontOfQueue(new Runnable() { 426 @Override 427 public void run() { 428 List<SyncOperation> ops = getAllPendingSyncs(); 429 Set<String> cleanedKeys = new HashSet<String>(); 430 for (SyncOperation opx: ops) { 431 if (cleanedKeys.contains(opx.key)) { 432 continue; 433 } 434 cleanedKeys.add(opx.key); 435 for (SyncOperation opy: ops) { 436 if (opx == opy) { 437 continue; 438 } 439 if (opx.key.equals(opy.key)) { 440 mJobScheduler.cancel(opy.jobId); 441 } 442 } 443 } 444 } 445 }); 446 } 447 448 private synchronized void verifyJobScheduler() { 449 if (mJobScheduler != null) { 450 return; 451 } 452 if (Log.isLoggable(TAG, Log.VERBOSE)) { 453 Log.d(TAG, "initializing JobScheduler object."); 454 } 455 mJobScheduler = (JobScheduler) mContext.getSystemService( 456 Context.JOB_SCHEDULER_SERVICE); 457 mJobSchedulerInternal = LocalServices.getService(JobSchedulerInternal.class); 458 // Get all persisted syncs from JobScheduler 459 List<JobInfo> pendingJobs = mJobScheduler.getAllPendingJobs(); 460 for (JobInfo job : pendingJobs) { 461 SyncOperation op = SyncOperation.maybeCreateFromJobExtras(job.getExtras()); 462 if (op != null) { 463 if (!op.isPeriodic) { 464 // Set the pending status of this EndPoint to true. Pending icon is 465 // shown on the settings activity. 466 mSyncStorageEngine.markPending(op.target, true); 467 } 468 } 469 } 470 cleanupJobs(); 471 } 472 473 private JobScheduler getJobScheduler() { 474 verifyJobScheduler(); 475 return mJobScheduler; 476 } 477 478 /** 479 * Should only be created after {@link ContentService#systemReady()} so that 480 * {@link PackageManager} is ready to query. 481 */ 482 public SyncManager(Context context, boolean factoryTest) { 483 // Initialize the SyncStorageEngine first, before registering observers 484 // and creating threads and so on; it may fail if the disk is full. 485 mContext = context; 486 487 SyncStorageEngine.init(context); 488 mSyncStorageEngine = SyncStorageEngine.getSingleton(); 489 mSyncStorageEngine.setOnSyncRequestListener(new OnSyncRequestListener() { 490 @Override 491 public void onSyncRequest(SyncStorageEngine.EndPoint info, int reason, Bundle extras) { 492 scheduleSync(info.account, info.userId, reason, info.provider, extras, 493 0 /* no flexMillis */, 494 0 /* run immediately */, 495 false); 496 } 497 }); 498 499 mSyncStorageEngine.setPeriodicSyncAddedListener( 500 new SyncStorageEngine.PeriodicSyncAddedListener() { 501 @Override 502 public void onPeriodicSyncAdded(EndPoint target, Bundle extras, long pollFrequency, 503 long flex) { 504 updateOrAddPeriodicSync(target, pollFrequency, flex, extras); 505 } 506 }); 507 508 mSyncStorageEngine.setOnAuthorityRemovedListener(new SyncStorageEngine.OnAuthorityRemovedListener() { 509 @Override 510 public void onAuthorityRemoved(EndPoint removedAuthority) { 511 removeSyncsForAuthority(removedAuthority); 512 } 513 }); 514 515 mSyncAdapters = new SyncAdaptersCache(mContext); 516 517 mSyncHandler = new SyncHandler(BackgroundThread.get().getLooper()); 518 519 mSyncAdapters.setListener(new RegisteredServicesCacheListener<SyncAdapterType>() { 520 @Override 521 public void onServiceChanged(SyncAdapterType type, int userId, boolean removed) { 522 if (!removed) { 523 scheduleSync(null, UserHandle.USER_ALL, 524 SyncOperation.REASON_SERVICE_CHANGED, 525 type.authority, null, 0 /* no delay */, 0 /* no delay */, 526 false /* onlyThoseWithUnkownSyncableState */); 527 } 528 } 529 }, mSyncHandler); 530 531 mRand = new Random(System.currentTimeMillis()); 532 533 IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); 534 context.registerReceiver(mConnectivityIntentReceiver, intentFilter); 535 536 if (!factoryTest) { 537 intentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED); 538 intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 539 context.registerReceiver(mBootCompletedReceiver, intentFilter); 540 } 541 542 intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW); 543 intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); 544 context.registerReceiver(mStorageIntentReceiver, intentFilter); 545 546 intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN); 547 intentFilter.setPriority(100); 548 context.registerReceiver(mShutdownIntentReceiver, intentFilter); 549 550 intentFilter = new IntentFilter(); 551 intentFilter.addAction(Intent.ACTION_USER_REMOVED); 552 intentFilter.addAction(Intent.ACTION_USER_UNLOCKED); 553 intentFilter.addAction(Intent.ACTION_USER_STOPPED); 554 mContext.registerReceiverAsUser( 555 mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null); 556 557 if (!factoryTest) { 558 mNotificationMgr = (NotificationManager) 559 context.getSystemService(Context.NOTIFICATION_SERVICE); 560 } else { 561 mNotificationMgr = null; 562 } 563 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 564 mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 565 mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService( 566 BatteryStats.SERVICE_NAME)); 567 568 // This WakeLock is used to ensure that we stay awake between the time that we receive 569 // a sync alarm notification and when we finish processing it. We need to do this 570 // because we don't do the work in the alarm handler, rather we do it in a message 571 // handler. 572 mHandleAlarmWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 573 HANDLE_SYNC_ALARM_WAKE_LOCK); 574 mHandleAlarmWakeLock.setReferenceCounted(false); 575 576 // This WakeLock is used to ensure that we stay awake while running the sync loop 577 // message handler. Normally we will hold a sync adapter wake lock while it is being 578 // synced but during the execution of the sync loop it might finish a sync for 579 // one sync adapter before starting the sync for the other sync adapter and we 580 // don't want the device to go to sleep during that window. 581 mSyncManagerWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 582 SYNC_LOOP_WAKE_LOCK); 583 mSyncManagerWakeLock.setReferenceCounted(false); 584 585 mProvisioned = isDeviceProvisioned(); 586 if (!mProvisioned) { 587 final ContentResolver resolver = context.getContentResolver(); 588 ContentObserver provisionedObserver = 589 new ContentObserver(null /* current thread */) { 590 public void onChange(boolean selfChange) { 591 mProvisioned |= isDeviceProvisioned(); 592 if (mProvisioned) { 593 mSyncHandler.onDeviceProvisioned(); 594 resolver.unregisterContentObserver(this); 595 } 596 } 597 }; 598 599 synchronized (mSyncHandler) { 600 resolver.registerContentObserver( 601 Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), 602 false /* notifyForDescendents */, 603 provisionedObserver); 604 605 // The device *may* have been provisioned while we were registering above observer. 606 // Check again to make sure. 607 mProvisioned |= isDeviceProvisioned(); 608 if (mProvisioned) { 609 resolver.unregisterContentObserver(provisionedObserver); 610 } 611 } 612 } 613 614 if (!factoryTest) { 615 // Register for account list updates for all users 616 mContext.registerReceiverAsUser(mAccountsUpdatedReceiver, 617 UserHandle.ALL, 618 new IntentFilter(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION), 619 null, null); 620 } 621 622 // Set up the communication channel between the scheduled job and the sync manager. 623 // This is posted to the *main* looper intentionally, to defer calling startService() 624 // until after the lengthy primary boot sequence completes on that thread, to avoid 625 // spurious ANR triggering. 626 final Intent startServiceIntent = new Intent(mContext, SyncJobService.class); 627 startServiceIntent.putExtra(SyncJobService.EXTRA_MESSENGER, new Messenger(mSyncHandler)); 628 new Handler(mContext.getMainLooper()).post(new Runnable() { 629 @Override 630 public void run() { 631 mContext.startService(startServiceIntent); 632 } 633 }); 634 } 635 636 private boolean isDeviceProvisioned() { 637 final ContentResolver resolver = mContext.getContentResolver(); 638 return (Settings.Global.getInt(resolver, Settings.Global.DEVICE_PROVISIONED, 0) != 0); 639 } 640 /** 641 * Return a random value v that satisfies minValue <= v < maxValue. The difference between 642 * maxValue and minValue must be less than Integer.MAX_VALUE. 643 */ 644 private long jitterize(long minValue, long maxValue) { 645 Random random = new Random(SystemClock.elapsedRealtime()); 646 long spread = maxValue - minValue; 647 if (spread > Integer.MAX_VALUE) { 648 throw new IllegalArgumentException("the difference between the maxValue and the " 649 + "minValue must be less than " + Integer.MAX_VALUE); 650 } 651 return minValue + random.nextInt((int)spread); 652 } 653 654 public SyncStorageEngine getSyncStorageEngine() { 655 return mSyncStorageEngine; 656 } 657 658 public int getIsSyncable(Account account, int userId, String providerName) { 659 int isSyncable = mSyncStorageEngine.getIsSyncable(account, userId, providerName); 660 UserInfo userInfo = UserManager.get(mContext).getUserInfo(userId); 661 662 // If it's not a restricted user, return isSyncable. 663 if (userInfo == null || !userInfo.isRestricted()) return isSyncable; 664 665 // Else check if the sync adapter has opted-in or not. 666 RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo = 667 mSyncAdapters.getServiceInfo( 668 SyncAdapterType.newKey(providerName, account.type), userId); 669 if (syncAdapterInfo == null) return isSyncable; 670 671 PackageInfo pInfo = null; 672 try { 673 pInfo = AppGlobals.getPackageManager().getPackageInfo( 674 syncAdapterInfo.componentName.getPackageName(), 0, userId); 675 if (pInfo == null) return isSyncable; 676 } catch (RemoteException re) { 677 // Shouldn't happen. 678 return isSyncable; 679 } 680 if (pInfo.restrictedAccountType != null 681 && pInfo.restrictedAccountType.equals(account.type)) { 682 return isSyncable; 683 } else { 684 return 0; 685 } 686 } 687 688 private void setAuthorityPendingState(EndPoint info) { 689 List<SyncOperation> ops = getAllPendingSyncs(); 690 for (SyncOperation op: ops) { 691 if (!op.isPeriodic && op.target.matchesSpec(info)) { 692 getSyncStorageEngine().markPending(info, true); 693 return; 694 } 695 } 696 getSyncStorageEngine().markPending(info, false); 697 } 698 699 /** 700 * Initiate a sync. This can start a sync for all providers 701 * (pass null to url, set onlyTicklable to false), only those 702 * providers that are marked as ticklable (pass null to url, 703 * set onlyTicklable to true), or a specific provider (set url 704 * to the content url of the provider). 705 * 706 * <p>If the ContentResolver.SYNC_EXTRAS_UPLOAD boolean in extras is 707 * true then initiate a sync that just checks for local changes to send 708 * to the server, otherwise initiate a sync that first gets any 709 * changes from the server before sending local changes back to 710 * the server. 711 * 712 * <p>If a specific provider is being synced (the url is non-null) 713 * then the extras can contain SyncAdapter-specific information 714 * to control what gets synced (e.g. which specific feed to sync). 715 * 716 * <p>You'll start getting callbacks after this. 717 * 718 * @param requestedAccount the account to sync, may be null to signify all accounts 719 * @param userId the id of the user whose accounts are to be synced. If userId is USER_ALL, 720 * then all users' accounts are considered. 721 * @param reason for sync request. If this is a positive integer, it is the Linux uid 722 * assigned to the process that requested the sync. If it's negative, the sync was requested by 723 * the SyncManager itself and could be one of the following: 724 * {@link SyncOperation#REASON_BACKGROUND_DATA_SETTINGS_CHANGED} 725 * {@link SyncOperation#REASON_ACCOUNTS_UPDATED} 726 * {@link SyncOperation#REASON_SERVICE_CHANGED} 727 * {@link SyncOperation#REASON_PERIODIC} 728 * {@link SyncOperation#REASON_IS_SYNCABLE} 729 * {@link SyncOperation#REASON_SYNC_AUTO} 730 * {@link SyncOperation#REASON_MASTER_SYNC_AUTO} 731 * {@link SyncOperation#REASON_USER_START} 732 * @param requestedAuthority the authority to sync, may be null to indicate all authorities 733 * @param extras a Map of SyncAdapter-specific information to control 734 * syncs of a specific provider. Can be null. Is ignored 735 * if the url is null. 736 * @param beforeRuntimeMillis milliseconds before runtimeMillis that this sync can run. 737 * @param runtimeMillis maximum milliseconds in the future to wait before performing sync. 738 * @param onlyThoseWithUnkownSyncableState Only sync authorities that have unknown state. 739 */ 740 public void scheduleSync(Account requestedAccount, int userId, int reason, 741 String requestedAuthority, Bundle extras, long beforeRuntimeMillis, 742 long runtimeMillis, boolean onlyThoseWithUnkownSyncableState) { 743 final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); 744 if (extras == null) { 745 extras = new Bundle(); 746 } 747 if (isLoggable) { 748 Log.d(TAG, "one-time sync for: " + requestedAccount + " " + extras.toString() + " " 749 + requestedAuthority); 750 } 751 752 AccountAndUser[] accounts; 753 if (requestedAccount != null && userId != UserHandle.USER_ALL) { 754 accounts = new AccountAndUser[] { new AccountAndUser(requestedAccount, userId) }; 755 } else { 756 accounts = mRunningAccounts; 757 if (accounts.length == 0) { 758 if (isLoggable) { 759 Slog.v(TAG, "scheduleSync: no accounts configured, dropping"); 760 } 761 return; 762 } 763 } 764 765 final boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false); 766 final boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false); 767 if (manualSync) { 768 extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true); 769 extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true); 770 } 771 final boolean ignoreSettings = 772 extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false); 773 774 int source; 775 if (uploadOnly) { 776 source = SyncStorageEngine.SOURCE_LOCAL; 777 } else if (manualSync) { 778 source = SyncStorageEngine.SOURCE_USER; 779 } else if (requestedAuthority == null) { 780 source = SyncStorageEngine.SOURCE_POLL; 781 } else { 782 // This isn't strictly server, since arbitrary callers can (and do) request 783 // a non-forced two-way sync on a specific url. 784 source = SyncStorageEngine.SOURCE_SERVER; 785 } 786 787 for (AccountAndUser account : accounts) { 788 // If userId is specified, do not sync accounts of other users 789 if (userId >= UserHandle.USER_SYSTEM && account.userId >= UserHandle.USER_SYSTEM 790 && userId != account.userId) { 791 continue; 792 } 793 // Compile a list of authorities that have sync adapters. 794 // For each authority sync each account that matches a sync adapter. 795 final HashSet<String> syncableAuthorities = new HashSet<String>(); 796 for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter : 797 mSyncAdapters.getAllServices(account.userId)) { 798 syncableAuthorities.add(syncAdapter.type.authority); 799 } 800 801 // If the url was specified then replace the list of authorities 802 // with just this authority or clear it if this authority isn't 803 // syncable. 804 if (requestedAuthority != null) { 805 final boolean hasSyncAdapter = syncableAuthorities.contains(requestedAuthority); 806 syncableAuthorities.clear(); 807 if (hasSyncAdapter) syncableAuthorities.add(requestedAuthority); 808 } 809 810 for (String authority : syncableAuthorities) { 811 int isSyncable = getIsSyncable(account.account, account.userId, 812 authority); 813 if (isSyncable == AuthorityInfo.NOT_SYNCABLE) { 814 continue; 815 } 816 final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo; 817 syncAdapterInfo = mSyncAdapters.getServiceInfo( 818 SyncAdapterType.newKey(authority, account.account.type), account.userId); 819 if (syncAdapterInfo == null) { 820 continue; 821 } 822 final int owningUid = syncAdapterInfo.uid; 823 final String owningPackage = syncAdapterInfo.componentName.getPackageName(); 824 try { 825 if (ActivityManagerNative.getDefault().getAppStartMode(owningUid, 826 owningPackage) == ActivityManager.APP_START_MODE_DISABLED) { 827 Slog.w(TAG, "Not scheduling job " + syncAdapterInfo.uid + ":" 828 + syncAdapterInfo.componentName 829 + " -- package not allowed to start"); 830 continue; 831 } 832 } catch (RemoteException e) { 833 } 834 final boolean allowParallelSyncs = syncAdapterInfo.type.allowParallelSyncs(); 835 final boolean isAlwaysSyncable = syncAdapterInfo.type.isAlwaysSyncable(); 836 if (isSyncable < 0 && isAlwaysSyncable) { 837 mSyncStorageEngine.setIsSyncable( 838 account.account, account.userId, authority, AuthorityInfo.SYNCABLE); 839 isSyncable = AuthorityInfo.SYNCABLE; 840 } 841 if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) { 842 continue; 843 } 844 if (!syncAdapterInfo.type.supportsUploading() && uploadOnly) { 845 continue; 846 } 847 848 boolean syncAllowed = 849 (isSyncable < 0) // Always allow if the isSyncable state is unknown. 850 || ignoreSettings 851 || (mSyncStorageEngine.getMasterSyncAutomatically(account.userId) 852 && mSyncStorageEngine.getSyncAutomatically(account.account, 853 account.userId, authority)); 854 if (!syncAllowed) { 855 if (isLoggable) { 856 Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority 857 + " is not allowed, dropping request"); 858 } 859 continue; 860 } 861 SyncStorageEngine.EndPoint info = 862 new SyncStorageEngine.EndPoint( 863 account.account, authority, account.userId); 864 long delayUntil = 865 mSyncStorageEngine.getDelayUntilTime(info); 866 if (isSyncable < 0) { 867 // Initialisation sync. 868 Bundle newExtras = new Bundle(); 869 newExtras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true); 870 if (isLoggable) { 871 Slog.v(TAG, "schedule initialisation Sync:" 872 + ", delay until " + delayUntil 873 + ", run by " + 0 874 + ", flexMillis " + 0 875 + ", source " + source 876 + ", account " + account 877 + ", authority " + authority 878 + ", extras " + newExtras); 879 } 880 postScheduleSyncMessage( 881 new SyncOperation(account.account, account.userId, 882 owningUid, owningPackage, reason, source, 883 authority, newExtras, allowParallelSyncs) 884 ); 885 } 886 if (!onlyThoseWithUnkownSyncableState) { 887 if (isLoggable) { 888 Slog.v(TAG, "scheduleSync:" 889 + " delay until " + delayUntil 890 + " run by " + runtimeMillis 891 + " flexMillis " + beforeRuntimeMillis 892 + ", source " + source 893 + ", account " + account 894 + ", authority " + authority 895 + ", extras " + extras); 896 } 897 postScheduleSyncMessage( 898 new SyncOperation(account.account, account.userId, 899 owningUid, owningPackage, reason, source, 900 authority, extras, allowParallelSyncs) 901 ); 902 } 903 } 904 } 905 } 906 907 private void removeSyncsForAuthority(EndPoint info) { 908 verifyJobScheduler(); 909 List<SyncOperation> ops = getAllPendingSyncs(); 910 for (SyncOperation op: ops) { 911 if (op.target.matchesSpec(info)) { 912 getJobScheduler().cancel(op.jobId); 913 } 914 } 915 } 916 917 /** 918 * Remove a specific periodic sync identified by its target and extras. 919 */ 920 public void removePeriodicSync(EndPoint target, Bundle extras) { 921 Message m = mSyncHandler.obtainMessage(mSyncHandler.MESSAGE_REMOVE_PERIODIC_SYNC, target); 922 m.setData(extras); 923 m.sendToTarget(); 924 } 925 926 /** 927 * Add a periodic sync. If a sync with same target and extras exists, its period and 928 * flexMillis will be updated. 929 */ 930 public void updateOrAddPeriodicSync(EndPoint target, long pollFrequency, long flex, 931 Bundle extras) { 932 UpdatePeriodicSyncMessagePayload payload = new UpdatePeriodicSyncMessagePayload(target, 933 pollFrequency, flex, extras); 934 mSyncHandler.obtainMessage(SyncHandler.MESSAGE_UPDATE_PERIODIC_SYNC, payload) 935 .sendToTarget(); 936 } 937 938 /** 939 * Get a list of periodic syncs corresponding to the given target. 940 */ 941 public List<PeriodicSync> getPeriodicSyncs(EndPoint target) { 942 List<SyncOperation> ops = getAllPendingSyncs(); 943 List<PeriodicSync> periodicSyncs = new ArrayList<PeriodicSync>(); 944 945 for (SyncOperation op: ops) { 946 if (op.isPeriodic && op.target.matchesSpec(target)) { 947 periodicSyncs.add(new PeriodicSync(op.target.account, op.target.provider, 948 op.extras, op.periodMillis / 1000, op.flexMillis / 1000)); 949 } 950 } 951 952 return periodicSyncs; 953 } 954 955 /** 956 * Schedule sync based on local changes to a provider. Occurs within interval 957 * [LOCAL_SYNC_DELAY, 2*LOCAL_SYNC_DELAY]. 958 */ 959 public void scheduleLocalSync(Account account, int userId, int reason, String authority) { 960 final Bundle extras = new Bundle(); 961 extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true); 962 scheduleSync(account, userId, reason, authority, extras, 963 LOCAL_SYNC_DELAY /* earliest run time */, 964 2 * LOCAL_SYNC_DELAY /* latest sync time. */, 965 false /* onlyThoseWithUnkownSyncableState */); 966 } 967 968 public SyncAdapterType[] getSyncAdapterTypes(int userId) { 969 final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> serviceInfos; 970 serviceInfos = mSyncAdapters.getAllServices(userId); 971 SyncAdapterType[] types = new SyncAdapterType[serviceInfos.size()]; 972 int i = 0; 973 for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> serviceInfo : serviceInfos) { 974 types[i] = serviceInfo.type; 975 ++i; 976 } 977 return types; 978 } 979 980 public String[] getSyncAdapterPackagesForAuthorityAsUser(String authority, int userId) { 981 return mSyncAdapters.getSyncAdapterPackagesForAuthority(authority, userId); 982 } 983 984 private void sendSyncFinishedOrCanceledMessage(ActiveSyncContext syncContext, 985 SyncResult syncResult) { 986 if (Log.isLoggable(TAG, Log.VERBOSE)) Slog.v(TAG, "sending MESSAGE_SYNC_FINISHED"); 987 Message msg = mSyncHandler.obtainMessage(); 988 msg.what = SyncHandler.MESSAGE_SYNC_FINISHED; 989 msg.obj = new SyncFinishedOrCancelledMessagePayload(syncContext, syncResult); 990 mSyncHandler.sendMessage(msg); 991 } 992 993 private void sendCancelSyncsMessage(final SyncStorageEngine.EndPoint info, Bundle extras) { 994 if (Log.isLoggable(TAG, Log.VERBOSE)) Slog.v(TAG, "sending MESSAGE_CANCEL"); 995 Message msg = mSyncHandler.obtainMessage(); 996 msg.what = SyncHandler.MESSAGE_CANCEL; 997 msg.setData(extras); 998 msg.obj = info; 999 mSyncHandler.sendMessage(msg); 1000 } 1001 1002 /** 1003 * Post a delayed message that will monitor the given sync context by periodically checking how 1004 * much network has been used by the uid. 1005 */ 1006 private void postMonitorSyncProgressMessage(ActiveSyncContext activeSyncContext) { 1007 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1008 Slog.v(TAG, "posting MESSAGE_SYNC_MONITOR in " + 1009 (SYNC_MONITOR_WINDOW_LENGTH_MILLIS/1000) + "s"); 1010 } 1011 1012 activeSyncContext.mBytesTransferredAtLastPoll = 1013 getTotalBytesTransferredByUid(activeSyncContext.mSyncAdapterUid); 1014 activeSyncContext.mLastPolledTimeElapsed = SystemClock.elapsedRealtime(); 1015 Message monitorMessage = 1016 mSyncHandler.obtainMessage( 1017 SyncHandler.MESSAGE_MONITOR_SYNC, 1018 activeSyncContext); 1019 mSyncHandler.sendMessageDelayed(monitorMessage, SYNC_MONITOR_WINDOW_LENGTH_MILLIS); 1020 } 1021 1022 private void postScheduleSyncMessage(SyncOperation syncOperation) { 1023 mSyncHandler.obtainMessage(mSyncHandler.MESSAGE_SCHEDULE_SYNC, syncOperation) 1024 .sendToTarget(); 1025 } 1026 1027 /** 1028 * Monitor sync progress by calculating how many bytes it is managing to send to and fro. 1029 */ 1030 private long getTotalBytesTransferredByUid(int uid) { 1031 return (TrafficStats.getUidRxBytes(uid) + TrafficStats.getUidTxBytes(uid)); 1032 } 1033 1034 /** 1035 * Convenience class for passing parameters for a finished or cancelled sync to the handler 1036 * to be processed. 1037 */ 1038 private class SyncFinishedOrCancelledMessagePayload { 1039 public final ActiveSyncContext activeSyncContext; 1040 public final SyncResult syncResult; 1041 1042 SyncFinishedOrCancelledMessagePayload(ActiveSyncContext syncContext, 1043 SyncResult syncResult) { 1044 this.activeSyncContext = syncContext; 1045 this.syncResult = syncResult; 1046 } 1047 } 1048 1049 private class UpdatePeriodicSyncMessagePayload { 1050 public final EndPoint target; 1051 public final long pollFrequency; 1052 public final long flex; 1053 public final Bundle extras; 1054 1055 UpdatePeriodicSyncMessagePayload(EndPoint target, long pollFrequency, long flex, 1056 Bundle extras) { 1057 this.target = target; 1058 this.pollFrequency = pollFrequency; 1059 this.flex = flex; 1060 this.extras = extras; 1061 } 1062 } 1063 1064 private void clearBackoffSetting(EndPoint target) { 1065 Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(target); 1066 if (backoff != null && backoff.first == SyncStorageEngine.NOT_IN_BACKOFF_MODE && 1067 backoff.second == SyncStorageEngine.NOT_IN_BACKOFF_MODE) { 1068 return; 1069 } 1070 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1071 Slog.v(TAG, "Clearing backoffs for " + target); 1072 } 1073 mSyncStorageEngine.setBackoff(target, 1074 SyncStorageEngine.NOT_IN_BACKOFF_MODE, 1075 SyncStorageEngine.NOT_IN_BACKOFF_MODE); 1076 1077 rescheduleSyncs(target); 1078 } 1079 1080 private void increaseBackoffSetting(EndPoint target) { 1081 final long now = SystemClock.elapsedRealtime(); 1082 1083 final Pair<Long, Long> previousSettings = 1084 mSyncStorageEngine.getBackoff(target); 1085 long newDelayInMs = -1; 1086 if (previousSettings != null) { 1087 // Don't increase backoff before current backoff is expired. This will happen for op's 1088 // with ignoreBackoff set. 1089 if (now < previousSettings.first) { 1090 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1091 Slog.v(TAG, "Still in backoff, do not increase it. " 1092 + "Remaining: " + ((previousSettings.first - now) / 1000) + " seconds."); 1093 } 1094 return; 1095 } 1096 // Subsequent delays are the double of the previous delay. 1097 newDelayInMs = previousSettings.second * 2; 1098 } 1099 if (newDelayInMs <= 0) { 1100 // The initial delay is the jitterized INITIAL_SYNC_RETRY_TIME_IN_MS. 1101 newDelayInMs = jitterize(INITIAL_SYNC_RETRY_TIME_IN_MS, 1102 (long)(INITIAL_SYNC_RETRY_TIME_IN_MS * 1.1)); 1103 } 1104 1105 // Cap the delay. 1106 long maxSyncRetryTimeInSeconds = Settings.Global.getLong(mContext.getContentResolver(), 1107 Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS, 1108 DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS); 1109 if (newDelayInMs > maxSyncRetryTimeInSeconds * 1000) { 1110 newDelayInMs = maxSyncRetryTimeInSeconds * 1000; 1111 } 1112 1113 final long backoff = now + newDelayInMs; 1114 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1115 Slog.v(TAG, "Backoff until: " + backoff + ", delayTime: " + newDelayInMs); 1116 } 1117 mSyncStorageEngine.setBackoff(target, backoff, newDelayInMs); 1118 rescheduleSyncs(target); 1119 } 1120 1121 /** 1122 * Reschedule all scheduled syncs for this EndPoint. The syncs will be scheduled according 1123 * to current backoff and delayUntil values of this EndPoint. 1124 */ 1125 private void rescheduleSyncs(EndPoint target) { 1126 List<SyncOperation> ops = getAllPendingSyncs(); 1127 int count = 0; 1128 for (SyncOperation op: ops) { 1129 if (!op.isPeriodic && op.target.matchesSpec(target)) { 1130 count++; 1131 getJobScheduler().cancel(op.jobId); 1132 postScheduleSyncMessage(op); 1133 } 1134 } 1135 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1136 Slog.v(TAG, "Rescheduled " + count + " syncs for " + target); 1137 } 1138 } 1139 1140 private void setDelayUntilTime(EndPoint target, long delayUntilSeconds) { 1141 final long delayUntil = delayUntilSeconds * 1000; 1142 final long absoluteNow = System.currentTimeMillis(); 1143 long newDelayUntilTime; 1144 if (delayUntil > absoluteNow) { 1145 newDelayUntilTime = SystemClock.elapsedRealtime() + (delayUntil - absoluteNow); 1146 } else { 1147 newDelayUntilTime = 0; 1148 } 1149 mSyncStorageEngine.setDelayUntilTime(target, newDelayUntilTime); 1150 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1151 Slog.v(TAG, "Delay Until time set to " + newDelayUntilTime + " for " + target); 1152 } 1153 rescheduleSyncs(target); 1154 } 1155 1156 private boolean isAdapterDelayed(EndPoint target) { 1157 long now = SystemClock.elapsedRealtime(); 1158 Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(target); 1159 if (backoff != null && backoff.first != SyncStorageEngine.NOT_IN_BACKOFF_MODE 1160 && backoff.first > now) { 1161 return true; 1162 } 1163 if (mSyncStorageEngine.getDelayUntilTime(target) > now) { 1164 return true; 1165 } 1166 return false; 1167 } 1168 1169 /** 1170 * Cancel the active sync if it matches the target. 1171 * @param info object containing info about which syncs to cancel. The target can 1172 * have null account/provider info to specify all accounts/providers. 1173 * @param extras if non-null, specifies the exact sync to remove. 1174 */ 1175 public void cancelActiveSync(SyncStorageEngine.EndPoint info, Bundle extras) { 1176 sendCancelSyncsMessage(info, extras); 1177 } 1178 1179 /** 1180 * Schedule a sync operation with JobScheduler. 1181 */ 1182 private void scheduleSyncOperationH(SyncOperation syncOperation) { 1183 scheduleSyncOperationH(syncOperation, 0L); 1184 } 1185 1186 private void scheduleSyncOperationH(SyncOperation syncOperation, long minDelay) { 1187 final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); 1188 if (syncOperation == null) { 1189 Slog.e(TAG, "Can't schedule null sync operation."); 1190 return; 1191 } 1192 if (!syncOperation.ignoreBackoff()) { 1193 Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(syncOperation.target); 1194 if (backoff == null) { 1195 Slog.e(TAG, "Couldn't find backoff values for " + syncOperation.target); 1196 backoff = new Pair<Long, Long>(SyncStorageEngine.NOT_IN_BACKOFF_MODE, 1197 SyncStorageEngine.NOT_IN_BACKOFF_MODE); 1198 } 1199 long now = SystemClock.elapsedRealtime(); 1200 long backoffDelay = backoff.first == SyncStorageEngine.NOT_IN_BACKOFF_MODE ? 0 1201 : backoff.first - now; 1202 long delayUntil = mSyncStorageEngine.getDelayUntilTime(syncOperation.target); 1203 long delayUntilDelay = delayUntil > now ? delayUntil - now : 0; 1204 if (isLoggable) { 1205 Slog.v(TAG, "backoff delay:" + backoffDelay 1206 + " delayUntil delay:" + delayUntilDelay); 1207 } 1208 minDelay = Math.max(minDelay, Math.max(backoffDelay, delayUntilDelay)); 1209 } 1210 1211 if (minDelay < 0) { 1212 minDelay = 0; 1213 } 1214 1215 // Check if duplicate syncs are pending. If found, keep one with least expected run time. 1216 if (!syncOperation.isPeriodic) { 1217 // Check currently running syncs 1218 for (ActiveSyncContext asc: mActiveSyncContexts) { 1219 if (asc.mSyncOperation.key.equals(syncOperation.key)) { 1220 if (isLoggable) { 1221 Log.v(TAG, "Duplicate sync is already running. Not scheduling " 1222 + syncOperation); 1223 } 1224 return; 1225 } 1226 } 1227 1228 int duplicatesCount = 0; 1229 long now = SystemClock.elapsedRealtime(); 1230 syncOperation.expectedRuntime = now + minDelay; 1231 List<SyncOperation> pending = getAllPendingSyncs(); 1232 SyncOperation opWithLeastExpectedRuntime = syncOperation; 1233 for (SyncOperation op : pending) { 1234 if (op.isPeriodic) { 1235 continue; 1236 } 1237 if (op.key.equals(syncOperation.key)) { 1238 if (opWithLeastExpectedRuntime.expectedRuntime > op.expectedRuntime) { 1239 opWithLeastExpectedRuntime = op; 1240 } 1241 duplicatesCount++; 1242 } 1243 } 1244 if (duplicatesCount > 1) { 1245 Slog.e(TAG, "FATAL ERROR! File a bug if you see this."); 1246 } 1247 for (SyncOperation op : pending) { 1248 if (op.isPeriodic) { 1249 continue; 1250 } 1251 if (op.key.equals(syncOperation.key)) { 1252 if (op != opWithLeastExpectedRuntime) { 1253 if (isLoggable) { 1254 Slog.v(TAG, "Cancelling duplicate sync " + op); 1255 } 1256 getJobScheduler().cancel(op.jobId); 1257 } 1258 } 1259 } 1260 if (opWithLeastExpectedRuntime != syncOperation) { 1261 // Don't schedule because a duplicate sync with earlier expected runtime exists. 1262 if (isLoggable) { 1263 Slog.v(TAG, "Not scheduling because a duplicate exists."); 1264 } 1265 return; 1266 } 1267 } 1268 1269 // Syncs that are re-scheduled shouldn't get a new job id. 1270 if (syncOperation.jobId == SyncOperation.NO_JOB_ID) { 1271 syncOperation.jobId = getUnusedJobIdH(); 1272 } 1273 1274 if (isLoggable) { 1275 Slog.v(TAG, "scheduling sync operation " + syncOperation.toString()); 1276 } 1277 1278 int priority = syncOperation.findPriority(); 1279 1280 final int networkType = syncOperation.isNotAllowedOnMetered() ? 1281 JobInfo.NETWORK_TYPE_UNMETERED : JobInfo.NETWORK_TYPE_ANY; 1282 1283 JobInfo.Builder b = new JobInfo.Builder(syncOperation.jobId, 1284 new ComponentName(mContext, SyncJobService.class)) 1285 .setExtras(syncOperation.toJobInfoExtras()) 1286 .setRequiredNetworkType(networkType) 1287 .setPersisted(true) 1288 .setPriority(priority); 1289 1290 if (syncOperation.isPeriodic) { 1291 b.setPeriodic(syncOperation.periodMillis, syncOperation.flexMillis); 1292 } else { 1293 if (minDelay > 0) { 1294 b.setMinimumLatency(minDelay); 1295 } 1296 getSyncStorageEngine().markPending(syncOperation.target, true); 1297 } 1298 1299 if (syncOperation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_REQUIRE_CHARGING)) { 1300 b.setRequiresCharging(true); 1301 } 1302 1303 getJobScheduler().scheduleAsPackage(b.build(), syncOperation.owningPackage, 1304 syncOperation.target.userId, syncOperation.wakeLockName()); 1305 } 1306 1307 /** 1308 * Remove scheduled sync operations. 1309 * @param info limit the removals to operations that match this target. The target can 1310 * have null account/provider info to specify all accounts/providers. 1311 */ 1312 public void clearScheduledSyncOperations(SyncStorageEngine.EndPoint info) { 1313 List<SyncOperation> ops = getAllPendingSyncs(); 1314 for (SyncOperation op: ops) { 1315 if (!op.isPeriodic && op.target.matchesSpec(info)) { 1316 getJobScheduler().cancel(op.jobId); 1317 getSyncStorageEngine().markPending(op.target, false); 1318 } 1319 } 1320 mSyncStorageEngine.setBackoff(info, 1321 SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE); 1322 } 1323 1324 /** 1325 * Remove a specified sync, if it exists. 1326 * @param info Authority for which the sync is to be removed. 1327 * @param extras extras bundle to uniquely identify sync. 1328 */ 1329 public void cancelScheduledSyncOperation(SyncStorageEngine.EndPoint info, Bundle extras) { 1330 List<SyncOperation> ops = getAllPendingSyncs(); 1331 for (SyncOperation op: ops) { 1332 if (!op.isPeriodic && op.target.matchesSpec(info) 1333 && syncExtrasEquals(extras, op.extras, false)) { 1334 getJobScheduler().cancel(op.jobId); 1335 } 1336 } 1337 setAuthorityPendingState(info); 1338 // Reset the back-off if there are no more syncs pending. 1339 if (!mSyncStorageEngine.isSyncPending(info)) { 1340 mSyncStorageEngine.setBackoff(info, 1341 SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE); 1342 } 1343 } 1344 1345 private void maybeRescheduleSync(SyncResult syncResult, SyncOperation operation) { 1346 final boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG); 1347 if (isLoggable) { 1348 Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", " + operation); 1349 } 1350 1351 // The SYNC_EXTRAS_IGNORE_BACKOFF only applies to the first attempt to sync a given 1352 // request. Retries of the request will always honor the backoff, so clear the 1353 // flag in case we retry this request. 1354 if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) { 1355 operation.extras.remove(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF); 1356 } 1357 1358 if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false) 1359 && !syncResult.syncAlreadyInProgress) { 1360 // syncAlreadyInProgress flag is set by AbstractThreadedSyncAdapter. The sync adapter 1361 // has no way of knowing that a sync error occured. So we DO retry if the error is 1362 // syncAlreadyInProgress. 1363 if (isLoggable) { 1364 Log.d(TAG, "not retrying sync operation because SYNC_EXTRAS_DO_NOT_RETRY was specified " 1365 + operation); 1366 } 1367 } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false) 1368 && !syncResult.syncAlreadyInProgress) { 1369 // If this was an upward sync then schedule a two-way sync immediately. 1370 operation.extras.remove(ContentResolver.SYNC_EXTRAS_UPLOAD); 1371 if (isLoggable) { 1372 Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync " 1373 + "encountered an error: " + operation); 1374 } 1375 scheduleSyncOperationH(operation); 1376 } else if (syncResult.tooManyRetries) { 1377 // If this sync aborted because the internal sync loop retried too many times then 1378 // don't reschedule. Otherwise we risk getting into a retry loop. 1379 if (isLoggable) { 1380 Log.d(TAG, "not retrying sync operation because it retried too many times: " 1381 + operation); 1382 } 1383 } else if (syncResult.madeSomeProgress()) { 1384 // If the operation succeeded to some extent then retry immediately. 1385 if (isLoggable) { 1386 Log.d(TAG, "retrying sync operation because even though it had an error " 1387 + "it achieved some success"); 1388 } 1389 scheduleSyncOperationH(operation); 1390 } else if (syncResult.syncAlreadyInProgress) { 1391 if (isLoggable) { 1392 Log.d(TAG, "retrying sync operation that failed because there was already a " 1393 + "sync in progress: " + operation); 1394 } 1395 scheduleSyncOperationH(operation, DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000); 1396 } else if (syncResult.hasSoftError()) { 1397 // If this was a two-way sync then retry soft errors with an exponential backoff. 1398 if (isLoggable) { 1399 Log.d(TAG, "retrying sync operation because it encountered a soft error: " 1400 + operation); 1401 } 1402 scheduleSyncOperationH(operation); 1403 } else { 1404 // Otherwise do not reschedule. 1405 Log.d(TAG, "not retrying sync operation because the error is a hard error: " 1406 + operation); 1407 } 1408 } 1409 1410 private void onUserUnlocked(int userId) { 1411 // Make sure that accounts we're about to use are valid. 1412 AccountManagerService.getSingleton().validateAccounts(userId); 1413 1414 mSyncAdapters.invalidateCache(userId); 1415 1416 EndPoint target = new EndPoint(null, null, userId); 1417 updateRunningAccounts(target); 1418 1419 // Schedule sync for any accounts under started user. 1420 final Account[] accounts = AccountManagerService.getSingleton().getAccounts(userId, 1421 mContext.getOpPackageName()); 1422 for (Account account : accounts) { 1423 scheduleSync(account, userId, SyncOperation.REASON_USER_START, null, null, 1424 0 /* no delay */, 0 /* No flexMillis */, 1425 true /* onlyThoseWithUnknownSyncableState */); 1426 } 1427 } 1428 1429 private void onUserStopped(int userId) { 1430 updateRunningAccounts(null /* Don't sync any target */); 1431 1432 cancelActiveSync( 1433 new SyncStorageEngine.EndPoint( 1434 null /* any account */, 1435 null /* any authority */, 1436 userId), 1437 null /* any sync. */ 1438 ); 1439 } 1440 1441 private void onUserRemoved(int userId) { 1442 updateRunningAccounts(null /* Don't sync any target */); 1443 1444 // Clean up the storage engine database 1445 mSyncStorageEngine.doDatabaseCleanup(new Account[0], userId); 1446 List<SyncOperation> ops = getAllPendingSyncs(); 1447 for (SyncOperation op: ops) { 1448 if (op.target.userId == userId) { 1449 getJobScheduler().cancel(op.jobId); 1450 } 1451 } 1452 } 1453 1454 /** 1455 * @hide 1456 */ 1457 class ActiveSyncContext extends ISyncContext.Stub 1458 implements ServiceConnection, IBinder.DeathRecipient { 1459 final SyncOperation mSyncOperation; 1460 final long mHistoryRowId; 1461 ISyncAdapter mSyncAdapter; 1462 final long mStartTime; 1463 long mTimeoutStartTime; 1464 boolean mBound; 1465 final PowerManager.WakeLock mSyncWakeLock; 1466 final int mSyncAdapterUid; 1467 SyncInfo mSyncInfo; 1468 boolean mIsLinkedToDeath = false; 1469 String mEventName; 1470 1471 /** Total bytes transferred, counted at {@link #mLastPolledTimeElapsed} */ 1472 long mBytesTransferredAtLastPoll; 1473 /** 1474 * Last point in {@link SystemClock#elapsedRealtime()} at which we checked the # of bytes 1475 * transferred to/fro by this adapter. 1476 */ 1477 long mLastPolledTimeElapsed; 1478 1479 /** 1480 * Create an ActiveSyncContext for an impending sync and grab the wakelock for that 1481 * sync adapter. Since this grabs the wakelock you need to be sure to call 1482 * close() when you are done with this ActiveSyncContext, whether the sync succeeded 1483 * or not. 1484 * @param syncOperation the SyncOperation we are about to sync 1485 * @param historyRowId the row in which to record the history info for this sync 1486 * @param syncAdapterUid the UID of the application that contains the sync adapter 1487 * for this sync. This is used to attribute the wakelock hold to that application. 1488 */ 1489 public ActiveSyncContext(SyncOperation syncOperation, long historyRowId, 1490 int syncAdapterUid) { 1491 super(); 1492 mSyncAdapterUid = syncAdapterUid; 1493 mSyncOperation = syncOperation; 1494 mHistoryRowId = historyRowId; 1495 mSyncAdapter = null; 1496 mStartTime = SystemClock.elapsedRealtime(); 1497 mTimeoutStartTime = mStartTime; 1498 mSyncWakeLock = mSyncHandler.getSyncWakeLock(mSyncOperation); 1499 mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterUid)); 1500 mSyncWakeLock.acquire(); 1501 } 1502 1503 public void sendHeartbeat() { 1504 // Heartbeats are no longer used. 1505 } 1506 1507 public void onFinished(SyncResult result) { 1508 if (Log.isLoggable(TAG, Log.VERBOSE)) Slog.v(TAG, "onFinished: " + this); 1509 // Include "this" in the message so that the handler can ignore it if this 1510 // ActiveSyncContext is no longer the mActiveSyncContext at message handling 1511 // time. 1512 sendSyncFinishedOrCanceledMessage(this, result); 1513 } 1514 1515 public void toString(StringBuilder sb) { 1516 sb.append("startTime ").append(mStartTime) 1517 .append(", mTimeoutStartTime ").append(mTimeoutStartTime) 1518 .append(", mHistoryRowId ").append(mHistoryRowId) 1519 .append(", syncOperation ").append(mSyncOperation); 1520 } 1521 1522 public void onServiceConnected(ComponentName name, IBinder service) { 1523 Message msg = mSyncHandler.obtainMessage(); 1524 msg.what = SyncHandler.MESSAGE_SERVICE_CONNECTED; 1525 msg.obj = new ServiceConnectionData(this, service); 1526 mSyncHandler.sendMessage(msg); 1527 } 1528 1529 public void onServiceDisconnected(ComponentName name) { 1530 Message msg = mSyncHandler.obtainMessage(); 1531 msg.what = SyncHandler.MESSAGE_SERVICE_DISCONNECTED; 1532 msg.obj = new ServiceConnectionData(this, null); 1533 mSyncHandler.sendMessage(msg); 1534 } 1535 1536 boolean bindToSyncAdapter(ComponentName serviceComponent, int userId) { 1537 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1538 Log.d(TAG, "bindToSyncAdapter: " + serviceComponent + ", connection " + this); 1539 } 1540 Intent intent = new Intent(); 1541 intent.setAction("android.content.SyncAdapter"); 1542 intent.setComponent(serviceComponent); 1543 intent.putExtra(Intent.EXTRA_CLIENT_LABEL, 1544 com.android.internal.R.string.sync_binding_label); 1545 intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser( 1546 mContext, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0, 1547 null, new UserHandle(userId))); 1548 mBound = true; 1549 final boolean bindResult = mContext.bindServiceAsUser(intent, this, 1550 Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND 1551 | Context.BIND_ALLOW_OOM_MANAGEMENT, 1552 new UserHandle(mSyncOperation.target.userId)); 1553 if (!bindResult) { 1554 mBound = false; 1555 } else { 1556 try { 1557 mEventName = mSyncOperation.wakeLockName(); 1558 mBatteryStats.noteSyncStart(mEventName, mSyncAdapterUid); 1559 } catch (RemoteException e) { 1560 } 1561 } 1562 return bindResult; 1563 } 1564 1565 /** 1566 * Performs the required cleanup, which is the releasing of the wakelock and 1567 * unbinding from the sync adapter (if actually bound). 1568 */ 1569 protected void close() { 1570 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1571 Log.d(TAG, "unBindFromSyncAdapter: connection " + this); 1572 } 1573 if (mBound) { 1574 mBound = false; 1575 mContext.unbindService(this); 1576 try { 1577 mBatteryStats.noteSyncFinish(mEventName, mSyncAdapterUid); 1578 } catch (RemoteException e) { 1579 } 1580 } 1581 mSyncWakeLock.release(); 1582 mSyncWakeLock.setWorkSource(null); 1583 } 1584 1585 public String toString() { 1586 StringBuilder sb = new StringBuilder(); 1587 toString(sb); 1588 return sb.toString(); 1589 } 1590 1591 @Override 1592 public void binderDied() { 1593 sendSyncFinishedOrCanceledMessage(this, null); 1594 } 1595 } 1596 1597 protected void dump(FileDescriptor fd, PrintWriter pw) { 1598 final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); 1599 dumpPendingSyncs(pw); 1600 dumpPeriodicSyncs(pw); 1601 dumpSyncState(ipw); 1602 dumpSyncHistory(ipw); 1603 dumpSyncAdapters(ipw); 1604 } 1605 1606 static String formatTime(long time) { 1607 Time tobj = new Time(); 1608 tobj.set(time); 1609 return tobj.format("%Y-%m-%d %H:%M:%S"); 1610 } 1611 1612 protected void dumpPendingSyncs(PrintWriter pw) { 1613 pw.println("Pending Syncs:"); 1614 List<SyncOperation> pendingSyncs = getAllPendingSyncs(); 1615 int count = 0; 1616 for (SyncOperation op: pendingSyncs) { 1617 if (!op.isPeriodic) { 1618 pw.println(op.dump(null, false)); 1619 count++; 1620 } 1621 } 1622 pw.println("Total: " + count); 1623 pw.println(); 1624 } 1625 1626 protected void dumpPeriodicSyncs(PrintWriter pw) { 1627 pw.println("Periodic Syncs:"); 1628 List<SyncOperation> pendingSyncs = getAllPendingSyncs(); 1629 int count = 0; 1630 for (SyncOperation op: pendingSyncs) { 1631 if (op.isPeriodic) { 1632 pw.println(op.dump(null, false)); 1633 count++; 1634 } 1635 } 1636 pw.println("Total: " + count); 1637 pw.println(); 1638 } 1639 1640 protected void dumpSyncState(PrintWriter pw) { 1641 pw.print("data connected: "); pw.println(mDataConnectionIsConnected); 1642 pw.print("auto sync: "); 1643 List<UserInfo> users = getAllUsers(); 1644 if (users != null) { 1645 for (UserInfo user : users) { 1646 pw.print("u" + user.id + "=" 1647 + mSyncStorageEngine.getMasterSyncAutomatically(user.id) + " "); 1648 } 1649 pw.println(); 1650 } 1651 pw.print("memory low: "); pw.println(mStorageIsLow); 1652 pw.print("device idle: "); pw.println(mDeviceIsIdle); 1653 pw.print("reported active: "); pw.println(mReportedSyncActive); 1654 1655 final AccountAndUser[] accounts = AccountManagerService.getSingleton().getAllAccounts(); 1656 1657 pw.print("accounts: "); 1658 if (accounts != INITIAL_ACCOUNTS_ARRAY) { 1659 pw.println(accounts.length); 1660 } else { 1661 pw.println("not known yet"); 1662 } 1663 final long now = SystemClock.elapsedRealtime(); 1664 pw.print("now: "); pw.print(now); 1665 pw.println(" (" + formatTime(System.currentTimeMillis()) + ")"); 1666 pw.println(" (HH:MM:SS)"); 1667 pw.print("uptime: "); pw.print(DateUtils.formatElapsedTime(now / 1000)); 1668 pw.println(" (HH:MM:SS)"); 1669 pw.print("time spent syncing: "); 1670 pw.print(DateUtils.formatElapsedTime( 1671 mSyncHandler.mSyncTimeTracker.timeSpentSyncing() / 1000)); 1672 pw.print(" (HH:MM:SS), sync "); 1673 pw.print(mSyncHandler.mSyncTimeTracker.mLastWasSyncing ? "" : "not "); 1674 pw.println("in progress"); 1675 1676 pw.println(); 1677 pw.println("Active Syncs: " + mActiveSyncContexts.size()); 1678 final PackageManager pm = mContext.getPackageManager(); 1679 for (SyncManager.ActiveSyncContext activeSyncContext : mActiveSyncContexts) { 1680 final long durationInSeconds = (now - activeSyncContext.mStartTime) / 1000; 1681 pw.print(" "); 1682 pw.print(DateUtils.formatElapsedTime(durationInSeconds)); 1683 pw.print(" - "); 1684 pw.print(activeSyncContext.mSyncOperation.dump(pm, false)); 1685 pw.println(); 1686 } 1687 1688 // Join the installed sync adapter with the accounts list and emit for everything. 1689 pw.println(); 1690 pw.println("Sync Status"); 1691 for (AccountAndUser account : accounts) { 1692 pw.printf("Account %s u%d %s\n", 1693 account.account.name, account.userId, account.account.type); 1694 1695 pw.println("======================================================================="); 1696 final PrintTable table = new PrintTable(12); 1697 table.set(0, 0, 1698 "Authority", // 0 1699 "Syncable", // 1 1700 "Enabled", // 2 1701 "Delay", // 3 1702 "Loc", // 4 1703 "Poll", // 5 1704 "Per", // 6 1705 "Serv", // 7 1706 "User", // 8 1707 "Tot", // 9 1708 "Time", // 10 1709 "Last Sync" // 11 1710 ); 1711 1712 final List<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> sorted = 1713 Lists.newArrayList(); 1714 sorted.addAll(mSyncAdapters.getAllServices(account.userId)); 1715 Collections.sort(sorted, 1716 new Comparator<RegisteredServicesCache.ServiceInfo<SyncAdapterType>>() { 1717 @Override 1718 public int compare(RegisteredServicesCache.ServiceInfo<SyncAdapterType> lhs, 1719 RegisteredServicesCache.ServiceInfo<SyncAdapterType> rhs) { 1720 return lhs.type.authority.compareTo(rhs.type.authority); 1721 } 1722 }); 1723 for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterType : sorted) { 1724 if (!syncAdapterType.type.accountType.equals(account.account.type)) { 1725 continue; 1726 } 1727 int row = table.getNumRows(); 1728 Pair<AuthorityInfo, SyncStatusInfo> syncAuthoritySyncStatus = 1729 mSyncStorageEngine.getCopyOfAuthorityWithSyncStatus( 1730 new SyncStorageEngine.EndPoint( 1731 account.account, 1732 syncAdapterType.type.authority, 1733 account.userId)); 1734 SyncStorageEngine.AuthorityInfo settings = syncAuthoritySyncStatus.first; 1735 SyncStatusInfo status = syncAuthoritySyncStatus.second; 1736 String authority = settings.target.provider; 1737 if (authority.length() > 50) { 1738 authority = authority.substring(authority.length() - 50); 1739 } 1740 table.set(row, 0, authority, settings.syncable, settings.enabled); 1741 table.set(row, 4, 1742 status.numSourceLocal, 1743 status.numSourcePoll, 1744 status.numSourcePeriodic, 1745 status.numSourceServer, 1746 status.numSourceUser, 1747 status.numSyncs, 1748 DateUtils.formatElapsedTime(status.totalElapsedTime / 1000)); 1749 1750 int row1 = row; 1751 if (settings.delayUntil > now) { 1752 table.set(row1++, 12, "D: " + (settings.delayUntil - now) / 1000); 1753 if (settings.backoffTime > now) { 1754 table.set(row1++, 12, "B: " + (settings.backoffTime - now) / 1000); 1755 table.set(row1++, 12, settings.backoffDelay / 1000); 1756 } 1757 } 1758 1759 if (status.lastSuccessTime != 0) { 1760 table.set(row1++, 11, SyncStorageEngine.SOURCES[status.lastSuccessSource] 1761 + " " + "SUCCESS"); 1762 table.set(row1++, 11, formatTime(status.lastSuccessTime)); 1763 } 1764 if (status.lastFailureTime != 0) { 1765 table.set(row1++, 11, SyncStorageEngine.SOURCES[status.lastFailureSource] 1766 + " " + "FAILURE"); 1767 table.set(row1++, 11, formatTime(status.lastFailureTime)); 1768 //noinspection UnusedAssignment 1769 table.set(row1++, 11, status.lastFailureMesg); 1770 } 1771 } 1772 table.writeTo(pw); 1773 } 1774 } 1775 1776 private void dumpTimeSec(PrintWriter pw, long time) { 1777 pw.print(time/1000); pw.print('.'); pw.print((time/100)%10); 1778 pw.print('s'); 1779 } 1780 1781 private void dumpDayStatistic(PrintWriter pw, SyncStorageEngine.DayStats ds) { 1782 pw.print("Success ("); pw.print(ds.successCount); 1783 if (ds.successCount > 0) { 1784 pw.print(" for "); dumpTimeSec(pw, ds.successTime); 1785 pw.print(" avg="); dumpTimeSec(pw, ds.successTime/ds.successCount); 1786 } 1787 pw.print(") Failure ("); pw.print(ds.failureCount); 1788 if (ds.failureCount > 0) { 1789 pw.print(" for "); dumpTimeSec(pw, ds.failureTime); 1790 pw.print(" avg="); dumpTimeSec(pw, ds.failureTime/ds.failureCount); 1791 } 1792 pw.println(")"); 1793 } 1794 1795 protected void dumpSyncHistory(PrintWriter pw) { 1796 dumpRecentHistory(pw); 1797 dumpDayStatistics(pw); 1798 } 1799 1800 private void dumpRecentHistory(PrintWriter pw) { 1801 final ArrayList<SyncStorageEngine.SyncHistoryItem> items 1802 = mSyncStorageEngine.getSyncHistory(); 1803 if (items != null && items.size() > 0) { 1804 final Map<String, AuthoritySyncStats> authorityMap = Maps.newHashMap(); 1805 long totalElapsedTime = 0; 1806 long totalTimes = 0; 1807 final int N = items.size(); 1808 1809 int maxAuthority = 0; 1810 int maxAccount = 0; 1811 for (SyncStorageEngine.SyncHistoryItem item : items) { 1812 SyncStorageEngine.AuthorityInfo authorityInfo 1813 = mSyncStorageEngine.getAuthority(item.authorityId); 1814 final String authorityName; 1815 final String accountKey; 1816 if (authorityInfo != null) { 1817 authorityName = authorityInfo.target.provider; 1818 accountKey = authorityInfo.target.account.name + "/" 1819 + authorityInfo.target.account.type 1820 + " u" + authorityInfo.target.userId; 1821 } else { 1822 authorityName = "Unknown"; 1823 accountKey = "Unknown"; 1824 } 1825 1826 int length = authorityName.length(); 1827 if (length > maxAuthority) { 1828 maxAuthority = length; 1829 } 1830 length = accountKey.length(); 1831 if (length > maxAccount) { 1832 maxAccount = length; 1833 } 1834 1835 final long elapsedTime = item.elapsedTime; 1836 totalElapsedTime += elapsedTime; 1837 totalTimes++; 1838 AuthoritySyncStats authoritySyncStats = authorityMap.get(authorityName); 1839 if (authoritySyncStats == null) { 1840 authoritySyncStats = new AuthoritySyncStats(authorityName); 1841 authorityMap.put(authorityName, authoritySyncStats); 1842 } 1843 authoritySyncStats.elapsedTime += elapsedTime; 1844 authoritySyncStats.times++; 1845 final Map<String, AccountSyncStats> accountMap = authoritySyncStats.accountMap; 1846 AccountSyncStats accountSyncStats = accountMap.get(accountKey); 1847 if (accountSyncStats == null) { 1848 accountSyncStats = new AccountSyncStats(accountKey); 1849 accountMap.put(accountKey, accountSyncStats); 1850 } 1851 accountSyncStats.elapsedTime += elapsedTime; 1852 accountSyncStats.times++; 1853 1854 } 1855 1856 if (totalElapsedTime > 0) { 1857 pw.println(); 1858 pw.printf("Detailed Statistics (Recent history): " 1859 + "%d (# of times) %ds (sync time)\n", 1860 totalTimes, totalElapsedTime / 1000); 1861 1862 final List<AuthoritySyncStats> sortedAuthorities = 1863 new ArrayList<AuthoritySyncStats>(authorityMap.values()); 1864 Collections.sort(sortedAuthorities, new Comparator<AuthoritySyncStats>() { 1865 @Override 1866 public int compare(AuthoritySyncStats lhs, AuthoritySyncStats rhs) { 1867 // reverse order 1868 int compare = Integer.compare(rhs.times, lhs.times); 1869 if (compare == 0) { 1870 compare = Long.compare(rhs.elapsedTime, lhs.elapsedTime); 1871 } 1872 return compare; 1873 } 1874 }); 1875 1876 final int maxLength = Math.max(maxAuthority, maxAccount + 3); 1877 final int padLength = 2 + 2 + maxLength + 2 + 10 + 11; 1878 final char chars[] = new char[padLength]; 1879 Arrays.fill(chars, '-'); 1880 final String separator = new String(chars); 1881 1882 final String authorityFormat = 1883 String.format(" %%-%ds: %%-9s %%-11s\n", maxLength + 2); 1884 final String accountFormat = 1885 String.format(" %%-%ds: %%-9s %%-11s\n", maxLength); 1886 1887 pw.println(separator); 1888 for (AuthoritySyncStats authoritySyncStats : sortedAuthorities) { 1889 String name = authoritySyncStats.name; 1890 long elapsedTime; 1891 int times; 1892 String timeStr; 1893 String timesStr; 1894 1895 elapsedTime = authoritySyncStats.elapsedTime; 1896 times = authoritySyncStats.times; 1897 timeStr = String.format("%ds/%d%%", 1898 elapsedTime / 1000, 1899 elapsedTime * 100 / totalElapsedTime); 1900 timesStr = String.format("%d/%d%%", 1901 times, 1902 times * 100 / totalTimes); 1903 pw.printf(authorityFormat, name, timesStr, timeStr); 1904 1905 final List<AccountSyncStats> sortedAccounts = 1906 new ArrayList<AccountSyncStats>( 1907 authoritySyncStats.accountMap.values()); 1908 Collections.sort(sortedAccounts, new Comparator<AccountSyncStats>() { 1909 @Override 1910 public int compare(AccountSyncStats lhs, AccountSyncStats rhs) { 1911 // reverse order 1912 int compare = Integer.compare(rhs.times, lhs.times); 1913 if (compare == 0) { 1914 compare = Long.compare(rhs.elapsedTime, lhs.elapsedTime); 1915 } 1916 return compare; 1917 } 1918 }); 1919 for (AccountSyncStats stats: sortedAccounts) { 1920 elapsedTime = stats.elapsedTime; 1921 times = stats.times; 1922 timeStr = String.format("%ds/%d%%", 1923 elapsedTime / 1000, 1924 elapsedTime * 100 / totalElapsedTime); 1925 timesStr = String.format("%d/%d%%", 1926 times, 1927 times * 100 / totalTimes); 1928 pw.printf(accountFormat, stats.name, timesStr, timeStr); 1929 } 1930 pw.println(separator); 1931 } 1932 } 1933 1934 pw.println(); 1935 pw.println("Recent Sync History"); 1936 final String format = " %-" + maxAccount + "s %-" + maxAuthority + "s %s\n"; 1937 final Map<String, Long> lastTimeMap = Maps.newHashMap(); 1938 final PackageManager pm = mContext.getPackageManager(); 1939 for (int i = 0; i < N; i++) { 1940 SyncStorageEngine.SyncHistoryItem item = items.get(i); 1941 SyncStorageEngine.AuthorityInfo authorityInfo 1942 = mSyncStorageEngine.getAuthority(item.authorityId); 1943 final String authorityName; 1944 final String accountKey; 1945 if (authorityInfo != null) { 1946 authorityName = authorityInfo.target.provider; 1947 accountKey = authorityInfo.target.account.name + "/" 1948 + authorityInfo.target.account.type 1949 + " u" + authorityInfo.target.userId; 1950 } else { 1951 authorityName = "Unknown"; 1952 accountKey = "Unknown"; 1953 } 1954 final long elapsedTime = item.elapsedTime; 1955 final Time time = new Time(); 1956 final long eventTime = item.eventTime; 1957 time.set(eventTime); 1958 1959 final String key = authorityName + "/" + accountKey; 1960 final Long lastEventTime = lastTimeMap.get(key); 1961 final String diffString; 1962 if (lastEventTime == null) { 1963 diffString = ""; 1964 } else { 1965 final long diff = (lastEventTime - eventTime) / 1000; 1966 if (diff < 60) { 1967 diffString = String.valueOf(diff); 1968 } else if (diff < 3600) { 1969 diffString = String.format("%02d:%02d", diff / 60, diff % 60); 1970 } else { 1971 final long sec = diff % 3600; 1972 diffString = String.format("%02d:%02d:%02d", 1973 diff / 3600, sec / 60, sec % 60); 1974 } 1975 } 1976 lastTimeMap.put(key, eventTime); 1977 1978 pw.printf(" #%-3d: %s %8s %5.1fs %8s", 1979 i + 1, 1980 formatTime(eventTime), 1981 SyncStorageEngine.SOURCES[item.source], 1982 ((float) elapsedTime) / 1000, 1983 diffString); 1984 pw.printf(format, accountKey, authorityName, 1985 SyncOperation.reasonToString(pm, item.reason)); 1986 1987 if (item.event != SyncStorageEngine.EVENT_STOP 1988 || item.upstreamActivity != 0 1989 || item.downstreamActivity != 0) { 1990 pw.printf(" event=%d upstreamActivity=%d downstreamActivity=%d\n", 1991 item.event, 1992 item.upstreamActivity, 1993 item.downstreamActivity); 1994 } 1995 if (item.mesg != null 1996 && !SyncStorageEngine.MESG_SUCCESS.equals(item.mesg)) { 1997 pw.printf(" mesg=%s\n", item.mesg); 1998 } 1999 } 2000 pw.println(); 2001 pw.println("Recent Sync History Extras"); 2002 for (int i = 0; i < N; i++) { 2003 final SyncStorageEngine.SyncHistoryItem item = items.get(i); 2004 final Bundle extras = item.extras; 2005 if (extras == null || extras.size() == 0) { 2006 continue; 2007 } 2008 final SyncStorageEngine.AuthorityInfo authorityInfo 2009 = mSyncStorageEngine.getAuthority(item.authorityId); 2010 final String authorityName; 2011 final String accountKey; 2012 if (authorityInfo != null) { 2013 authorityName = authorityInfo.target.provider; 2014 accountKey = authorityInfo.target.account.name + "/" 2015 + authorityInfo.target.account.type 2016 + " u" + authorityInfo.target.userId; 2017 } else { 2018 authorityName = "Unknown"; 2019 accountKey = "Unknown"; 2020 } 2021 final Time time = new Time(); 2022 final long eventTime = item.eventTime; 2023 time.set(eventTime); 2024 2025 pw.printf(" #%-3d: %s %8s ", 2026 i + 1, 2027 formatTime(eventTime), 2028 SyncStorageEngine.SOURCES[item.source]); 2029 2030 pw.printf(format, accountKey, authorityName, extras); 2031 } 2032 } 2033 } 2034 2035 private void dumpDayStatistics(PrintWriter pw) { 2036 SyncStorageEngine.DayStats dses[] = mSyncStorageEngine.getDayStatistics(); 2037 if (dses != null && dses[0] != null) { 2038 pw.println(); 2039 pw.println("Sync Statistics"); 2040 pw.print(" Today: "); dumpDayStatistic(pw, dses[0]); 2041 int today = dses[0].day; 2042 int i; 2043 SyncStorageEngine.DayStats ds; 2044 2045 // Print each day in the current week. 2046 for (i=1; i<=6 && i < dses.length; i++) { 2047 ds = dses[i]; 2048 if (ds == null) break; 2049 int delta = today-ds.day; 2050 if (delta > 6) break; 2051 2052 pw.print(" Day-"); pw.print(delta); pw.print(": "); 2053 dumpDayStatistic(pw, ds); 2054 } 2055 2056 // Aggregate all following days into weeks and print totals. 2057 int weekDay = today; 2058 while (i < dses.length) { 2059 SyncStorageEngine.DayStats aggr = null; 2060 weekDay -= 7; 2061 while (i < dses.length) { 2062 ds = dses[i]; 2063 if (ds == null) { 2064 i = dses.length; 2065 break; 2066 } 2067 int delta = weekDay-ds.day; 2068 if (delta > 6) break; 2069 i++; 2070 2071 if (aggr == null) { 2072 aggr = new SyncStorageEngine.DayStats(weekDay); 2073 } 2074 aggr.successCount += ds.successCount; 2075 aggr.successTime += ds.successTime; 2076 aggr.failureCount += ds.failureCount; 2077 aggr.failureTime += ds.failureTime; 2078 } 2079 if (aggr != null) { 2080 pw.print(" Week-"); pw.print((today-weekDay)/7); pw.print(": "); 2081 dumpDayStatistic(pw, aggr); 2082 } 2083 } 2084 } 2085 } 2086 2087 private void dumpSyncAdapters(IndentingPrintWriter pw) { 2088 pw.println(); 2089 final List<UserInfo> users = getAllUsers(); 2090 if (users != null) { 2091 for (UserInfo user : users) { 2092 pw.println("Sync adapters for " + user + ":"); 2093 pw.increaseIndent(); 2094 for (RegisteredServicesCache.ServiceInfo<?> info : 2095 mSyncAdapters.getAllServices(user.id)) { 2096 pw.println(info); 2097 } 2098 pw.decreaseIndent(); 2099 pw.println(); 2100 } 2101 } 2102 } 2103 2104 private static class AuthoritySyncStats { 2105 String name; 2106 long elapsedTime; 2107 int times; 2108 Map<String, AccountSyncStats> accountMap = Maps.newHashMap(); 2109 2110 private AuthoritySyncStats(String name) { 2111 this.name = name; 2112 } 2113 } 2114 2115 private static class AccountSyncStats { 2116 String name; 2117 long elapsedTime; 2118 int times; 2119 2120 private AccountSyncStats(String name) { 2121 this.name = name; 2122 } 2123 } 2124 2125 /** 2126 * A helper object to keep track of the time we have spent syncing since the last boot 2127 */ 2128 private class SyncTimeTracker { 2129 /** True if a sync was in progress on the most recent call to update() */ 2130 boolean mLastWasSyncing = false; 2131 /** Used to track when lastWasSyncing was last set */ 2132 long mWhenSyncStarted = 0; 2133 /** The cumulative time we have spent syncing */ 2134 private long mTimeSpentSyncing; 2135 2136 /** Call to let the tracker know that the sync state may have changed */ 2137 public synchronized void update() { 2138 final boolean isSyncInProgress = !mActiveSyncContexts.isEmpty(); 2139 if (isSyncInProgress == mLastWasSyncing) return; 2140 final long now = SystemClock.elapsedRealtime(); 2141 if (isSyncInProgress) { 2142 mWhenSyncStarted = now; 2143 } else { 2144 mTimeSpentSyncing += now - mWhenSyncStarted; 2145 } 2146 mLastWasSyncing = isSyncInProgress; 2147 } 2148 2149 /** Get how long we have been syncing, in ms */ 2150 public synchronized long timeSpentSyncing() { 2151 if (!mLastWasSyncing) return mTimeSpentSyncing; 2152 2153 final long now = SystemClock.elapsedRealtime(); 2154 return mTimeSpentSyncing + (now - mWhenSyncStarted); 2155 } 2156 } 2157 2158 class ServiceConnectionData { 2159 public final ActiveSyncContext activeSyncContext; 2160 public final IBinder adapter; 2161 2162 ServiceConnectionData(ActiveSyncContext activeSyncContext, IBinder adapter) { 2163 this.activeSyncContext = activeSyncContext; 2164 this.adapter = adapter; 2165 } 2166 } 2167 2168 /** 2169 * Handles SyncOperation Messages that are posted to the associated 2170 * HandlerThread. 2171 */ 2172 class SyncHandler extends Handler { 2173 // Messages that can be sent on mHandler. 2174 private static final int MESSAGE_SYNC_FINISHED = 1; 2175 private static final int MESSAGE_RELEASE_MESSAGES_FROM_QUEUE = 2; 2176 private static final int MESSAGE_SERVICE_CONNECTED = 4; 2177 private static final int MESSAGE_SERVICE_DISCONNECTED = 5; 2178 private static final int MESSAGE_CANCEL = 6; 2179 static final int MESSAGE_JOBSERVICE_OBJECT = 7; 2180 static final int MESSAGE_START_SYNC = 10; 2181 static final int MESSAGE_STOP_SYNC = 11; 2182 static final int MESSAGE_SCHEDULE_SYNC = 12; 2183 static final int MESSAGE_UPDATE_PERIODIC_SYNC = 13; 2184 static final int MESSAGE_REMOVE_PERIODIC_SYNC = 14; 2185 2186 /** 2187 * Posted periodically to monitor network process for long-running syncs. 2188 * obj: {@link com.android.server.content.SyncManager.ActiveSyncContext} 2189 */ 2190 private static final int MESSAGE_MONITOR_SYNC = 8; 2191 private static final int MESSAGE_ACCOUNTS_UPDATED = 9; 2192 2193 public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker(); 2194 private final HashMap<String, PowerManager.WakeLock> mWakeLocks = Maps.newHashMap(); 2195 2196 private List<Message> mUnreadyQueue = new ArrayList<Message>(); 2197 2198 void onBootCompleted() { 2199 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2200 Slog.v(TAG, "Boot completed."); 2201 } 2202 checkIfDeviceReady(); 2203 } 2204 2205 void onDeviceProvisioned() { 2206 if (Log.isLoggable(TAG, Log.DEBUG)) { 2207 Log.d(TAG, "mProvisioned=" + mProvisioned); 2208 } 2209 checkIfDeviceReady(); 2210 } 2211 2212 void checkIfDeviceReady() { 2213 if (mProvisioned && mBootCompleted && mJobServiceReady) { 2214 synchronized(this) { 2215 mSyncStorageEngine.restoreAllPeriodicSyncs(); 2216 // Dispatch any stashed messages. 2217 obtainMessage(MESSAGE_RELEASE_MESSAGES_FROM_QUEUE).sendToTarget(); 2218 } 2219 } 2220 } 2221 2222 /** 2223 * Stash any messages that come to the handler before boot is complete or before the device 2224 * is properly provisioned (i.e. out of set-up wizard). 2225 * {@link #onBootCompleted()} and {@link SyncHandler#onDeviceProvisioned} both 2226 * need to come in before we start syncing. 2227 * @param msg Message to dispatch at a later point. 2228 * @return true if a message was enqueued, false otherwise. This is to avoid losing the 2229 * message if we manage to acquire the lock but by the time we do boot has completed. 2230 */ 2231 private boolean tryEnqueueMessageUntilReadyToRun(Message msg) { 2232 synchronized (this) { 2233 if (!mBootCompleted || !mProvisioned || !mJobServiceReady) { 2234 // Need to copy the message bc looper will recycle it. 2235 Message m = Message.obtain(msg); 2236 mUnreadyQueue.add(m); 2237 return true; 2238 } else { 2239 return false; 2240 } 2241 } 2242 } 2243 2244 public SyncHandler(Looper looper) { 2245 super(looper); 2246 } 2247 2248 public void handleMessage(Message msg) { 2249 try { 2250 mSyncManagerWakeLock.acquire(); 2251 // We only want to enqueue sync related messages until device is ready. 2252 // Other messages are handled without enqueuing. 2253 if (msg.what == MESSAGE_JOBSERVICE_OBJECT) { 2254 Slog.i(TAG, "Got SyncJobService instance."); 2255 mSyncJobService = (SyncJobService) msg.obj; 2256 mJobServiceReady = true; 2257 checkIfDeviceReady(); 2258 } else if (msg.what == SyncHandler.MESSAGE_ACCOUNTS_UPDATED) { 2259 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2260 Slog.v(TAG, "handleSyncHandlerMessage: MESSAGE_ACCOUNTS_UPDATED"); 2261 } 2262 EndPoint targets = (EndPoint) msg.obj; 2263 updateRunningAccountsH(targets); 2264 } else if (msg.what == MESSAGE_RELEASE_MESSAGES_FROM_QUEUE) { 2265 if (mUnreadyQueue != null) { 2266 for (Message m : mUnreadyQueue) { 2267 handleSyncMessage(m); 2268 } 2269 mUnreadyQueue = null; 2270 } 2271 } else if (tryEnqueueMessageUntilReadyToRun(msg)) { 2272 // No work to be done. 2273 } else { 2274 handleSyncMessage(msg); 2275 } 2276 } finally { 2277 mSyncManagerWakeLock.release(); 2278 } 2279 } 2280 2281 private void handleSyncMessage(Message msg) { 2282 final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); 2283 2284 try { 2285 mDataConnectionIsConnected = readDataConnectionState(); 2286 switch (msg.what) { 2287 case MESSAGE_SCHEDULE_SYNC: 2288 SyncOperation op = (SyncOperation) msg.obj; 2289 scheduleSyncOperationH(op); 2290 break; 2291 2292 case MESSAGE_START_SYNC: 2293 op = (SyncOperation) msg.obj; 2294 startSyncH(op); 2295 break; 2296 2297 case MESSAGE_STOP_SYNC: 2298 op = (SyncOperation) msg.obj; 2299 if (isLoggable) { 2300 Slog.v(TAG, "Stop sync received."); 2301 } 2302 ActiveSyncContext asc = findActiveSyncContextH(op.jobId); 2303 if (asc != null) { 2304 runSyncFinishedOrCanceledH(null /* no result */, asc); 2305 boolean reschedule = msg.arg1 != 0; 2306 boolean applyBackoff = msg.arg2 != 0; 2307 if (isLoggable) { 2308 Slog.v(TAG, "Stopping sync. Reschedule: " + reschedule 2309 + "Backoff: " + applyBackoff); 2310 } 2311 if (applyBackoff) { 2312 increaseBackoffSetting(op.target); 2313 } 2314 if (reschedule) { 2315 deferStoppedSyncH(op, 0); 2316 } 2317 } 2318 break; 2319 2320 case MESSAGE_UPDATE_PERIODIC_SYNC: 2321 UpdatePeriodicSyncMessagePayload data = 2322 (UpdatePeriodicSyncMessagePayload) msg.obj; 2323 updateOrAddPeriodicSyncH(data.target, data.pollFrequency, 2324 data.flex, data.extras); 2325 break; 2326 case MESSAGE_REMOVE_PERIODIC_SYNC: 2327 removePeriodicSyncH((EndPoint)msg.obj, msg.getData()); 2328 break; 2329 2330 case SyncHandler.MESSAGE_CANCEL: 2331 SyncStorageEngine.EndPoint endpoint = (SyncStorageEngine.EndPoint) msg.obj; 2332 Bundle extras = msg.peekData(); 2333 if (Log.isLoggable(TAG, Log.DEBUG)) { 2334 Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_CANCEL: " 2335 + endpoint + " bundle: " + extras); 2336 } 2337 cancelActiveSyncH(endpoint, extras); 2338 break; 2339 2340 case SyncHandler.MESSAGE_SYNC_FINISHED: 2341 SyncFinishedOrCancelledMessagePayload payload = 2342 (SyncFinishedOrCancelledMessagePayload) msg.obj; 2343 if (!isSyncStillActiveH(payload.activeSyncContext)) { 2344 Log.d(TAG, "handleSyncHandlerMessage: dropping since the " 2345 + "sync is no longer active: " 2346 + payload.activeSyncContext); 2347 break; 2348 } 2349 if (isLoggable) { 2350 Slog.v(TAG, "syncFinished" + payload.activeSyncContext.mSyncOperation); 2351 } 2352 mSyncJobService.callJobFinished( 2353 payload.activeSyncContext.mSyncOperation.jobId, false); 2354 runSyncFinishedOrCanceledH(payload.syncResult, 2355 payload.activeSyncContext); 2356 break; 2357 2358 case SyncHandler.MESSAGE_SERVICE_CONNECTED: { 2359 ServiceConnectionData msgData = (ServiceConnectionData) msg.obj; 2360 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2361 Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CONNECTED: " 2362 + msgData.activeSyncContext); 2363 } 2364 // Check that this isn't an old message. 2365 if (isSyncStillActiveH(msgData.activeSyncContext)) { 2366 runBoundToAdapterH( 2367 msgData.activeSyncContext, 2368 msgData.adapter); 2369 } 2370 break; 2371 } 2372 2373 case SyncHandler.MESSAGE_SERVICE_DISCONNECTED: { 2374 final ActiveSyncContext currentSyncContext = 2375 ((ServiceConnectionData) msg.obj).activeSyncContext; 2376 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2377 Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_DISCONNECTED: " 2378 + currentSyncContext); 2379 } 2380 // Check that this isn't an old message. 2381 if (isSyncStillActiveH(currentSyncContext)) { 2382 // cancel the sync if we have a syncadapter, which means one is 2383 // outstanding 2384 try { 2385 if (currentSyncContext.mSyncAdapter != null) { 2386 currentSyncContext.mSyncAdapter.cancelSync(currentSyncContext); 2387 } 2388 } catch (RemoteException e) { 2389 // We don't need to retry this in this case. 2390 } 2391 2392 // Pretend that the sync failed with an IOException, 2393 // which is a soft error. 2394 SyncResult syncResult = new SyncResult(); 2395 syncResult.stats.numIoExceptions++; 2396 mSyncJobService.callJobFinished( 2397 currentSyncContext.mSyncOperation.jobId, false); 2398 runSyncFinishedOrCanceledH(syncResult, currentSyncContext); 2399 } 2400 break; 2401 } 2402 2403 case SyncHandler.MESSAGE_MONITOR_SYNC: 2404 ActiveSyncContext monitoredSyncContext = (ActiveSyncContext) msg.obj; 2405 if (Log.isLoggable(TAG, Log.DEBUG)) { 2406 Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_MONITOR_SYNC: " + 2407 monitoredSyncContext.mSyncOperation.target); 2408 } 2409 2410 if (isSyncNotUsingNetworkH(monitoredSyncContext)) { 2411 Log.w(TAG, String.format( 2412 "Detected sync making no progress for %s. cancelling.", 2413 monitoredSyncContext)); 2414 mSyncJobService.callJobFinished( 2415 monitoredSyncContext.mSyncOperation.jobId, false); 2416 runSyncFinishedOrCanceledH( 2417 null /* cancel => no result */, monitoredSyncContext); 2418 } else { 2419 // Repost message to check again. 2420 postMonitorSyncProgressMessage(monitoredSyncContext); 2421 } 2422 break; 2423 2424 } 2425 } finally { 2426 mSyncTimeTracker.update(); 2427 } 2428 } 2429 2430 private PowerManager.WakeLock getSyncWakeLock(SyncOperation operation) { 2431 final String wakeLockKey = operation.wakeLockName(); 2432 PowerManager.WakeLock wakeLock = mWakeLocks.get(wakeLockKey); 2433 if (wakeLock == null) { 2434 final String name = SYNC_WAKE_LOCK_PREFIX + wakeLockKey; 2435 wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name); 2436 wakeLock.setReferenceCounted(false); 2437 mWakeLocks.put(wakeLockKey, wakeLock); 2438 } 2439 return wakeLock; 2440 } 2441 2442 /** 2443 * Defer the specified SyncOperation by rescheduling it on the JobScheduler with some 2444 * delay. This is equivalent to a failure. If this is a periodic sync, a delayed one-off 2445 * sync will be scheduled. 2446 */ 2447 private void deferSyncH(SyncOperation op, long delay) { 2448 mSyncJobService.callJobFinished(op.jobId, false); 2449 if (op.isPeriodic) { 2450 scheduleSyncOperationH(op.createOneTimeSyncOperation(), delay); 2451 } else { 2452 // mSyncJobService.callJobFinished is async, so cancel the job to ensure we don't 2453 // find the this job in the pending jobs list while looking for duplicates 2454 // before scheduling it at a later time. 2455 getJobScheduler().cancel(op.jobId); 2456 scheduleSyncOperationH(op, delay); 2457 } 2458 } 2459 2460 /* Same as deferSyncH, but assumes that job is no longer running on JobScheduler. */ 2461 private void deferStoppedSyncH(SyncOperation op, long delay) { 2462 if (op.isPeriodic) { 2463 scheduleSyncOperationH(op.createOneTimeSyncOperation(), delay); 2464 } else { 2465 scheduleSyncOperationH(op, delay); 2466 } 2467 } 2468 2469 /** 2470 * Cancel an active sync and reschedule it on the JobScheduler with some delay. 2471 */ 2472 private void deferActiveSyncH(ActiveSyncContext asc) { 2473 SyncOperation op = asc.mSyncOperation; 2474 runSyncFinishedOrCanceledH(null, asc); 2475 deferSyncH(op, SYNC_DELAY_ON_CONFLICT); 2476 } 2477 2478 private void startSyncH(SyncOperation op) { 2479 final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); 2480 if (isLoggable) Slog.v(TAG, op.toString()); 2481 2482 if (mStorageIsLow) { 2483 deferSyncH(op, SYNC_DELAY_ON_LOW_STORAGE); 2484 return; 2485 } 2486 2487 if (op.isPeriodic) { 2488 // Don't allow this periodic to run if a previous instance failed and is currently 2489 // scheduled according to some backoff criteria. 2490 List<SyncOperation> ops = getAllPendingSyncs(); 2491 for (SyncOperation syncOperation: ops) { 2492 if (syncOperation.sourcePeriodicId == op.jobId) { 2493 mSyncJobService.callJobFinished(op.jobId, false); 2494 return; 2495 } 2496 } 2497 // Don't allow this periodic to run if a previous instance failed and is currently 2498 // executing according to some backoff criteria. 2499 for (ActiveSyncContext asc: mActiveSyncContexts) { 2500 if (asc.mSyncOperation.sourcePeriodicId == op.jobId) { 2501 mSyncJobService.callJobFinished(op.jobId, false); 2502 return; 2503 } 2504 } 2505 // Check for adapter delays. 2506 if (isAdapterDelayed(op.target)) { 2507 deferSyncH(op, 0 /* No minimum delay */); 2508 return; 2509 } 2510 } 2511 2512 // Check for conflicting syncs. 2513 for (ActiveSyncContext asc: mActiveSyncContexts) { 2514 if (asc.mSyncOperation.isConflict(op)) { 2515 // If the provided SyncOperation conflicts with a running one, the lower 2516 // priority sync is pre-empted. 2517 if (asc.mSyncOperation.findPriority() >= op.findPriority()) { 2518 if (isLoggable) { 2519 Slog.v(TAG, "Rescheduling sync due to conflict " + op.toString()); 2520 } 2521 deferSyncH(op, SYNC_DELAY_ON_CONFLICT); 2522 return; 2523 } else { 2524 if (isLoggable) { 2525 Slog.v(TAG, "Pushing back running sync due to a higher priority sync"); 2526 } 2527 deferActiveSyncH(asc); 2528 break; 2529 } 2530 } 2531 } 2532 2533 if (isOperationValid(op)) { 2534 if (!dispatchSyncOperation(op)) { 2535 mSyncJobService.callJobFinished(op.jobId, false); 2536 } 2537 } else { 2538 mSyncJobService.callJobFinished(op.jobId, false); 2539 } 2540 setAuthorityPendingState(op.target); 2541 } 2542 2543 private ActiveSyncContext findActiveSyncContextH(int jobId) { 2544 for (ActiveSyncContext asc: mActiveSyncContexts) { 2545 SyncOperation op = asc.mSyncOperation; 2546 if (op != null && op.jobId == jobId) { 2547 return asc; 2548 } 2549 } 2550 return null; 2551 } 2552 2553 private void updateRunningAccountsH(EndPoint syncTargets) { 2554 AccountAndUser[] oldAccounts = mRunningAccounts; 2555 mRunningAccounts = AccountManagerService.getSingleton().getRunningAccounts(); 2556 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2557 Slog.v(TAG, "Accounts list: "); 2558 for (AccountAndUser acc : mRunningAccounts) { 2559 Slog.v(TAG, acc.toString()); 2560 } 2561 } 2562 if (mBootCompleted) { 2563 doDatabaseCleanup(); 2564 } 2565 2566 AccountAndUser[] accounts = mRunningAccounts; 2567 for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) { 2568 if (!containsAccountAndUser(accounts, 2569 currentSyncContext.mSyncOperation.target.account, 2570 currentSyncContext.mSyncOperation.target.userId)) { 2571 Log.d(TAG, "canceling sync since the account is no longer running"); 2572 sendSyncFinishedOrCanceledMessage(currentSyncContext, 2573 null /* no result since this is a cancel */); 2574 } 2575 } 2576 2577 // On account add, check if there are any settings to be restored. 2578 for (AccountAndUser aau : mRunningAccounts) { 2579 if (!containsAccountAndUser(oldAccounts, aau.account, aau.userId)) { 2580 if (Log.isLoggable(TAG, Log.DEBUG)) { 2581 Log.d(TAG, "Account " + aau.account + " added, checking sync restore data"); 2582 } 2583 AccountSyncSettingsBackupHelper.accountAdded(mContext); 2584 break; 2585 } 2586 } 2587 2588 // Cancel all jobs from non-existent accounts. 2589 AccountAndUser[] allAccounts = AccountManagerService.getSingleton().getAllAccounts(); 2590 List<SyncOperation> ops = getAllPendingSyncs(); 2591 for (SyncOperation op: ops) { 2592 if (!containsAccountAndUser(allAccounts, op.target.account, op.target.userId)) { 2593 getJobScheduler().cancel(op.jobId); 2594 } 2595 } 2596 2597 if (syncTargets != null) { 2598 scheduleSync(syncTargets.account, syncTargets.userId, 2599 SyncOperation.REASON_ACCOUNTS_UPDATED, syncTargets.provider, null, 0, 0, 2600 true); 2601 } 2602 } 2603 2604 /** 2605 * The given SyncOperation will be removed and a new one scheduled in its place if 2606 * an updated period or flex is specified. 2607 * @param syncOperation SyncOperation whose period and flex is to be updated. 2608 * @param pollFrequencyMillis new period in milliseconds. 2609 * @param flexMillis new flex time in milliseconds. 2610 */ 2611 private void maybeUpdateSyncPeriodH(SyncOperation syncOperation, long pollFrequencyMillis, 2612 long flexMillis) { 2613 if (!(pollFrequencyMillis == syncOperation.periodMillis 2614 && flexMillis == syncOperation.flexMillis)) { 2615 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2616 Slog.v(TAG, "updating period " + syncOperation + " to " + pollFrequencyMillis 2617 + " and flex to " + flexMillis); 2618 } 2619 SyncOperation newOp = new SyncOperation(syncOperation, pollFrequencyMillis, 2620 flexMillis); 2621 newOp.jobId = syncOperation.jobId; 2622 scheduleSyncOperationH(newOp); 2623 } 2624 } 2625 2626 private void updateOrAddPeriodicSyncH(EndPoint target, long pollFrequency, long flex, 2627 Bundle extras) { 2628 final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); 2629 verifyJobScheduler(); // Will fill in mScheduledSyncs cache if it is not already filled. 2630 final long pollFrequencyMillis = pollFrequency * 1000L; 2631 final long flexMillis = flex * 1000L; 2632 if (isLoggable) { 2633 Slog.v(TAG, "Addition to periodic syncs requested: " + target 2634 + " period: " + pollFrequency 2635 + " flexMillis: " + flex 2636 + " extras: " + extras.toString()); 2637 } 2638 List<SyncOperation> ops = getAllPendingSyncs(); 2639 for (SyncOperation op: ops) { 2640 if (op.isPeriodic && op.target.matchesSpec(target) 2641 && syncExtrasEquals(op.extras, extras, true /* includeSyncSettings */)) { 2642 maybeUpdateSyncPeriodH(op, pollFrequencyMillis, flexMillis); 2643 return; 2644 } 2645 } 2646 2647 if (isLoggable) { 2648 Slog.v(TAG, "Adding new periodic sync: " + target 2649 + " period: " + pollFrequency 2650 + " flexMillis: " + flex 2651 + " extras: " + extras.toString()); 2652 } 2653 2654 final RegisteredServicesCache.ServiceInfo<SyncAdapterType> 2655 syncAdapterInfo = mSyncAdapters.getServiceInfo( 2656 SyncAdapterType.newKey( 2657 target.provider, target.account.type), 2658 target.userId); 2659 if (syncAdapterInfo == null) { 2660 return; 2661 } 2662 2663 SyncOperation op = new SyncOperation(target, syncAdapterInfo.uid, 2664 syncAdapterInfo.componentName.getPackageName(), SyncOperation.REASON_PERIODIC, 2665 SyncStorageEngine.SOURCE_PERIODIC, extras, 2666 syncAdapterInfo.type.allowParallelSyncs(), true, SyncOperation.NO_JOB_ID, 2667 pollFrequencyMillis, flexMillis); 2668 scheduleSyncOperationH(op); 2669 mSyncStorageEngine.reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); 2670 } 2671 2672 /** 2673 * Remove this periodic sync operation and all one-off operations initiated by it. 2674 */ 2675 private void removePeriodicSyncInternalH(SyncOperation syncOperation) { 2676 // Remove this periodic sync and all one-off syncs initiated by it. 2677 List<SyncOperation> ops = getAllPendingSyncs(); 2678 for (SyncOperation op: ops) { 2679 if (op.sourcePeriodicId == syncOperation.jobId || op.jobId == syncOperation.jobId) { 2680 ActiveSyncContext asc = findActiveSyncContextH(syncOperation.jobId); 2681 if (asc != null) { 2682 mSyncJobService.callJobFinished(syncOperation.jobId, false); 2683 runSyncFinishedOrCanceledH(null, asc); 2684 } 2685 getJobScheduler().cancel(op.jobId); 2686 } 2687 } 2688 } 2689 2690 private void removePeriodicSyncH(EndPoint target, Bundle extras) { 2691 verifyJobScheduler(); 2692 List<SyncOperation> ops = getAllPendingSyncs(); 2693 for (SyncOperation op: ops) { 2694 if (op.isPeriodic && op.target.matchesSpec(target) 2695 && syncExtrasEquals(op.extras, extras, true /* includeSyncSettings */)) { 2696 removePeriodicSyncInternalH(op); 2697 } 2698 } 2699 } 2700 2701 private boolean isSyncNotUsingNetworkH(ActiveSyncContext activeSyncContext) { 2702 final long bytesTransferredCurrent = 2703 getTotalBytesTransferredByUid(activeSyncContext.mSyncAdapterUid); 2704 final long deltaBytesTransferred = 2705 bytesTransferredCurrent - activeSyncContext.mBytesTransferredAtLastPoll; 2706 2707 if (Log.isLoggable(TAG, Log.DEBUG)) { 2708 // Bytes transferred 2709 long remainder = deltaBytesTransferred; 2710 long mb = remainder / (1024 * 1024); 2711 remainder %= 1024 * 1024; 2712 long kb = remainder / 1024; 2713 remainder %= 1024; 2714 long b = remainder; 2715 Log.d(TAG, String.format( 2716 "Time since last update: %ds. Delta transferred: %dMBs,%dKBs,%dBs", 2717 (SystemClock.elapsedRealtime() 2718 - activeSyncContext.mLastPolledTimeElapsed)/1000, 2719 mb, kb, b) 2720 ); 2721 } 2722 return (deltaBytesTransferred <= SYNC_MONITOR_PROGRESS_THRESHOLD_BYTES); 2723 } 2724 2725 /** 2726 * Determine if a sync is no longer valid and should be dropped. 2727 */ 2728 private boolean isOperationValid(SyncOperation op) { 2729 final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); 2730 int state; 2731 final EndPoint target = op.target; 2732 boolean syncEnabled = mSyncStorageEngine.getMasterSyncAutomatically(target.userId); 2733 // Drop the sync if the account of this operation no longer exists. 2734 AccountAndUser[] accounts = mRunningAccounts; 2735 if (!containsAccountAndUser(accounts, target.account, target.userId)) { 2736 if (isLoggable) { 2737 Slog.v(TAG, " Dropping sync operation: account doesn't exist."); 2738 } 2739 return false; 2740 } 2741 // Drop this sync request if it isn't syncable. 2742 state = getIsSyncable(target.account, target.userId, target.provider); 2743 if (state == 0) { 2744 if (isLoggable) { 2745 Slog.v(TAG, " Dropping sync operation: isSyncable == 0."); 2746 } 2747 return false; 2748 } 2749 syncEnabled = syncEnabled && mSyncStorageEngine.getSyncAutomatically( 2750 target.account, target.userId, target.provider); 2751 2752 // We ignore system settings that specify the sync is invalid if: 2753 // 1) It's manual - we try it anyway. When/if it fails it will be rescheduled. 2754 // or 2755 // 2) it's an initialisation sync - we just need to connect to it. 2756 final boolean ignoreSystemConfiguration = op.isIgnoreSettings() || (state < 0); 2757 2758 // Sync not enabled. 2759 if (!syncEnabled && !ignoreSystemConfiguration) { 2760 if (isLoggable) { 2761 Slog.v(TAG, " Dropping sync operation: disallowed by settings/network."); 2762 } 2763 return false; 2764 } 2765 return true; 2766 } 2767 2768 private boolean dispatchSyncOperation(SyncOperation op) { 2769 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2770 Slog.v(TAG, "dispatchSyncOperation: we are going to sync " + op); 2771 Slog.v(TAG, "num active syncs: " + mActiveSyncContexts.size()); 2772 for (ActiveSyncContext syncContext : mActiveSyncContexts) { 2773 Slog.v(TAG, syncContext.toString()); 2774 } 2775 } 2776 // Connect to the sync adapter. 2777 int targetUid; 2778 ComponentName targetComponent; 2779 final SyncStorageEngine.EndPoint info = op.target; 2780 SyncAdapterType syncAdapterType = 2781 SyncAdapterType.newKey(info.provider, info.account.type); 2782 final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo; 2783 syncAdapterInfo = mSyncAdapters.getServiceInfo(syncAdapterType, info.userId); 2784 if (syncAdapterInfo == null) { 2785 Log.d(TAG, "can't find a sync adapter for " + syncAdapterType 2786 + ", removing settings for it"); 2787 mSyncStorageEngine.removeAuthority(info); 2788 return false; 2789 } 2790 targetUid = syncAdapterInfo.uid; 2791 targetComponent = syncAdapterInfo.componentName; 2792 ActiveSyncContext activeSyncContext = 2793 new ActiveSyncContext(op, insertStartSyncEvent(op), targetUid); 2794 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2795 Slog.v(TAG, "dispatchSyncOperation: starting " + activeSyncContext); 2796 } 2797 2798 activeSyncContext.mSyncInfo = mSyncStorageEngine.addActiveSync(activeSyncContext); 2799 mActiveSyncContexts.add(activeSyncContext); 2800 2801 // Post message to begin monitoring this sync's progress. 2802 postMonitorSyncProgressMessage(activeSyncContext); 2803 2804 if (!activeSyncContext.bindToSyncAdapter(targetComponent, info.userId)) { 2805 Slog.e(TAG, "Bind attempt failed - target: " + targetComponent); 2806 closeActiveSyncContext(activeSyncContext); 2807 return false; 2808 } 2809 2810 return true; 2811 } 2812 2813 private void runBoundToAdapterH(final ActiveSyncContext activeSyncContext, 2814 IBinder syncAdapter) { 2815 final SyncOperation syncOperation = activeSyncContext.mSyncOperation; 2816 try { 2817 activeSyncContext.mIsLinkedToDeath = true; 2818 syncAdapter.linkToDeath(activeSyncContext, 0); 2819 2820 activeSyncContext.mSyncAdapter = ISyncAdapter.Stub.asInterface(syncAdapter); 2821 activeSyncContext.mSyncAdapter 2822 .startSync(activeSyncContext, syncOperation.target.provider, 2823 syncOperation.target.account, syncOperation.extras); 2824 } catch (RemoteException remoteExc) { 2825 Log.d(TAG, "maybeStartNextSync: caught a RemoteException, rescheduling", remoteExc); 2826 closeActiveSyncContext(activeSyncContext); 2827 increaseBackoffSetting(syncOperation.target); 2828 scheduleSyncOperationH(syncOperation); 2829 } catch (RuntimeException exc) { 2830 closeActiveSyncContext(activeSyncContext); 2831 Slog.e(TAG, "Caught RuntimeException while starting the sync " + syncOperation, exc); 2832 } 2833 } 2834 2835 /** 2836 * Cancel the sync for the provided target that matches the given bundle. 2837 * @param info Can have null fields to indicate all the active syncs for that field. 2838 * @param extras Can be null to indicate <strong>all</strong> syncs for the given endpoint. 2839 */ 2840 private void cancelActiveSyncH(SyncStorageEngine.EndPoint info, Bundle extras) { 2841 ArrayList<ActiveSyncContext> activeSyncs = 2842 new ArrayList<ActiveSyncContext>(mActiveSyncContexts); 2843 for (ActiveSyncContext activeSyncContext : activeSyncs) { 2844 if (activeSyncContext != null) { 2845 final SyncStorageEngine.EndPoint opInfo = 2846 activeSyncContext.mSyncOperation.target; 2847 if (!opInfo.matchesSpec(info)) { 2848 continue; 2849 } 2850 if (extras != null && 2851 !syncExtrasEquals(activeSyncContext.mSyncOperation.extras, 2852 extras, 2853 false /* no config settings */)) { 2854 continue; 2855 } 2856 mSyncJobService.callJobFinished(activeSyncContext.mSyncOperation.jobId, false); 2857 runSyncFinishedOrCanceledH(null /* cancel => no result */, activeSyncContext); 2858 } 2859 } 2860 } 2861 2862 /** 2863 * Should be called when a one-off instance of a periodic sync completes successfully. 2864 */ 2865 private void reschedulePeriodicSyncH(SyncOperation syncOperation) { 2866 // Ensure that the periodic sync wasn't removed. 2867 SyncOperation periodicSync = null; 2868 List<SyncOperation> ops = getAllPendingSyncs(); 2869 for (SyncOperation op: ops) { 2870 if (op.isPeriodic && syncOperation.matchesPeriodicOperation(op)) { 2871 periodicSync = op; 2872 break; 2873 } 2874 } 2875 if (periodicSync == null) { 2876 return; 2877 } 2878 scheduleSyncOperationH(periodicSync); 2879 } 2880 2881 private void runSyncFinishedOrCanceledH(SyncResult syncResult, 2882 ActiveSyncContext activeSyncContext) { 2883 final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); 2884 2885 final SyncOperation syncOperation = activeSyncContext.mSyncOperation; 2886 final SyncStorageEngine.EndPoint info = syncOperation.target; 2887 2888 if (activeSyncContext.mIsLinkedToDeath) { 2889 activeSyncContext.mSyncAdapter.asBinder().unlinkToDeath(activeSyncContext, 0); 2890 activeSyncContext.mIsLinkedToDeath = false; 2891 } 2892 closeActiveSyncContext(activeSyncContext); 2893 final long elapsedTime = SystemClock.elapsedRealtime() - activeSyncContext.mStartTime; 2894 String historyMessage; 2895 int downstreamActivity; 2896 int upstreamActivity; 2897 2898 if (!syncOperation.isPeriodic) { 2899 // mSyncJobService.jobFinidhed is async, we need to ensure that this job is 2900 // removed from JobScheduler's pending jobs list before moving forward and 2901 // potentially rescheduling all pending jobs to respect new backoff values. 2902 getJobScheduler().cancel(syncOperation.jobId); 2903 } 2904 2905 if (syncResult != null) { 2906 if (isLoggable) { 2907 Slog.v(TAG, "runSyncFinishedOrCanceled [finished]: " 2908 + syncOperation + ", result " + syncResult); 2909 } 2910 2911 if (!syncResult.hasError()) { 2912 historyMessage = SyncStorageEngine.MESG_SUCCESS; 2913 // TODO: set these correctly when the SyncResult is extended to include it 2914 downstreamActivity = 0; 2915 upstreamActivity = 0; 2916 clearBackoffSetting(syncOperation.target); 2917 2918 // If the operation completes successfully and it was scheduled due to 2919 // a periodic operation failing, we reschedule the periodic operation to 2920 // start from now. 2921 if (syncOperation.isDerivedFromFailedPeriodicSync()) { 2922 reschedulePeriodicSyncH(syncOperation); 2923 } 2924 } else { 2925 Log.d(TAG, "failed sync operation " + syncOperation + ", " + syncResult); 2926 // the operation failed so increase the backoff time 2927 increaseBackoffSetting(syncOperation.target); 2928 if (!syncOperation.isPeriodic) { 2929 // reschedule the sync if so indicated by the syncResult 2930 maybeRescheduleSync(syncResult, syncOperation); 2931 } else { 2932 // create a normal sync instance that will respect adapter backoffs 2933 postScheduleSyncMessage(syncOperation.createOneTimeSyncOperation()); 2934 } 2935 historyMessage = ContentResolver.syncErrorToString( 2936 syncResultToErrorNumber(syncResult)); 2937 // TODO: set these correctly when the SyncResult is extended to include it 2938 downstreamActivity = 0; 2939 upstreamActivity = 0; 2940 } 2941 setDelayUntilTime(syncOperation.target, syncResult.delayUntil); 2942 } else { 2943 if (isLoggable) { 2944 Slog.v(TAG, "runSyncFinishedOrCanceled [canceled]: " + syncOperation); 2945 } 2946 if (activeSyncContext.mSyncAdapter != null) { 2947 try { 2948 activeSyncContext.mSyncAdapter.cancelSync(activeSyncContext); 2949 } catch (RemoteException e) { 2950 // we don't need to retry this in this case 2951 } 2952 } 2953 historyMessage = SyncStorageEngine.MESG_CANCELED; 2954 downstreamActivity = 0; 2955 upstreamActivity = 0; 2956 } 2957 2958 stopSyncEvent(activeSyncContext.mHistoryRowId, syncOperation, historyMessage, 2959 upstreamActivity, downstreamActivity, elapsedTime); 2960 // Check for full-resync and schedule it after closing off the last sync. 2961 if (syncResult != null && syncResult.tooManyDeletions) { 2962 installHandleTooManyDeletesNotification(info.account, 2963 info.provider, syncResult.stats.numDeletes, 2964 info.userId); 2965 } else { 2966 mNotificationMgr.cancelAsUser(null, 2967 info.account.hashCode() ^ info.provider.hashCode(), 2968 new UserHandle(info.userId)); 2969 } 2970 if (syncResult != null && syncResult.fullSyncRequested) { 2971 scheduleSyncOperationH( 2972 new SyncOperation(info.account, info.userId, 2973 syncOperation.owningUid, syncOperation.owningPackage, 2974 syncOperation.reason, 2975 syncOperation.syncSource, info.provider, new Bundle(), 2976 syncOperation.allowParallelSyncs)); 2977 } 2978 } 2979 2980 private void closeActiveSyncContext(ActiveSyncContext activeSyncContext) { 2981 activeSyncContext.close(); 2982 mActiveSyncContexts.remove(activeSyncContext); 2983 mSyncStorageEngine.removeActiveSync(activeSyncContext.mSyncInfo, 2984 activeSyncContext.mSyncOperation.target.userId); 2985 2986 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2987 Slog.v(TAG, "removing all MESSAGE_MONITOR_SYNC & MESSAGE_SYNC_EXPIRED for " 2988 + activeSyncContext.toString()); 2989 } 2990 mSyncHandler.removeMessages(SyncHandler.MESSAGE_MONITOR_SYNC, activeSyncContext); 2991 } 2992 2993 /** 2994 * Convert the error-containing SyncResult into the Sync.History error number. Since 2995 * the SyncResult may indicate multiple errors at once, this method just returns the 2996 * most "serious" error. 2997 * @param syncResult the SyncResult from which to read 2998 * @return the most "serious" error set in the SyncResult 2999 * @throws IllegalStateException if the SyncResult does not indicate any errors. 3000 * If SyncResult.error() is true then it is safe to call this. 3001 */ 3002 private int syncResultToErrorNumber(SyncResult syncResult) { 3003 if (syncResult.syncAlreadyInProgress) 3004 return ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS; 3005 if (syncResult.stats.numAuthExceptions > 0) 3006 return ContentResolver.SYNC_ERROR_AUTHENTICATION; 3007 if (syncResult.stats.numIoExceptions > 0) 3008 return ContentResolver.SYNC_ERROR_IO; 3009 if (syncResult.stats.numParseExceptions > 0) 3010 return ContentResolver.SYNC_ERROR_PARSE; 3011 if (syncResult.stats.numConflictDetectedExceptions > 0) 3012 return ContentResolver.SYNC_ERROR_CONFLICT; 3013 if (syncResult.tooManyDeletions) 3014 return ContentResolver.SYNC_ERROR_TOO_MANY_DELETIONS; 3015 if (syncResult.tooManyRetries) 3016 return ContentResolver.SYNC_ERROR_TOO_MANY_RETRIES; 3017 if (syncResult.databaseError) 3018 return ContentResolver.SYNC_ERROR_INTERNAL; 3019 throw new IllegalStateException("we are not in an error state, " + syncResult); 3020 } 3021 3022 private void installHandleTooManyDeletesNotification(Account account, String authority, 3023 long numDeletes, int userId) { 3024 if (mNotificationMgr == null) return; 3025 3026 final ProviderInfo providerInfo = mContext.getPackageManager().resolveContentProvider( 3027 authority, 0 /* flags */); 3028 if (providerInfo == null) { 3029 return; 3030 } 3031 CharSequence authorityName = providerInfo.loadLabel(mContext.getPackageManager()); 3032 3033 Intent clickIntent = new Intent(mContext, SyncActivityTooManyDeletes.class); 3034 clickIntent.putExtra("account", account); 3035 clickIntent.putExtra("authority", authority); 3036 clickIntent.putExtra("provider", authorityName.toString()); 3037 clickIntent.putExtra("numDeletes", numDeletes); 3038 3039 if (!isActivityAvailable(clickIntent)) { 3040 Log.w(TAG, "No activity found to handle too many deletes."); 3041 return; 3042 } 3043 3044 UserHandle user = new UserHandle(userId); 3045 final PendingIntent pendingIntent = PendingIntent 3046 .getActivityAsUser(mContext, 0, clickIntent, 3047 PendingIntent.FLAG_CANCEL_CURRENT, null, user); 3048 3049 CharSequence tooManyDeletesDescFormat = mContext.getResources().getText( 3050 R.string.contentServiceTooManyDeletesNotificationDesc); 3051 3052 Context contextForUser = getContextForUser(user); 3053 Notification notification = new Notification.Builder(contextForUser) 3054 .setSmallIcon(R.drawable.stat_notify_sync_error) 3055 .setTicker(mContext.getString(R.string.contentServiceSync)) 3056 .setWhen(System.currentTimeMillis()) 3057 .setColor(contextForUser.getColor( 3058 com.android.internal.R.color.system_notification_accent_color)) 3059 .setContentTitle(contextForUser.getString( 3060 R.string.contentServiceSyncNotificationTitle)) 3061 .setContentText( 3062 String.format(tooManyDeletesDescFormat.toString(), authorityName)) 3063 .setContentIntent(pendingIntent) 3064 .build(); 3065 notification.flags |= Notification.FLAG_ONGOING_EVENT; 3066 mNotificationMgr.notifyAsUser(null, account.hashCode() ^ authority.hashCode(), 3067 notification, user); 3068 } 3069 3070 /** 3071 * Checks whether an activity exists on the system image for the given intent. 3072 * 3073 * @param intent The intent for an activity. 3074 * @return Whether or not an activity exists. 3075 */ 3076 private boolean isActivityAvailable(Intent intent) { 3077 PackageManager pm = mContext.getPackageManager(); 3078 List<ResolveInfo> list = pm.queryIntentActivities(intent, 0); 3079 int listSize = list.size(); 3080 for (int i = 0; i < listSize; i++) { 3081 ResolveInfo resolveInfo = list.get(i); 3082 if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) 3083 != 0) { 3084 return true; 3085 } 3086 } 3087 3088 return false; 3089 } 3090 3091 public long insertStartSyncEvent(SyncOperation syncOperation) { 3092 final long now = System.currentTimeMillis(); 3093 EventLog.writeEvent(2720, 3094 syncOperation.toEventLog(SyncStorageEngine.EVENT_START)); 3095 return mSyncStorageEngine.insertStartSyncEvent(syncOperation, now); 3096 } 3097 3098 public void stopSyncEvent(long rowId, SyncOperation syncOperation, String resultMessage, 3099 int upstreamActivity, int downstreamActivity, long elapsedTime) { 3100 EventLog.writeEvent(2720, 3101 syncOperation.toEventLog(SyncStorageEngine.EVENT_STOP)); 3102 mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime, 3103 resultMessage, downstreamActivity, upstreamActivity); 3104 } 3105 } 3106 3107 private boolean isSyncStillActiveH(ActiveSyncContext activeSyncContext) { 3108 for (ActiveSyncContext sync : mActiveSyncContexts) { 3109 if (sync == activeSyncContext) { 3110 return true; 3111 } 3112 } 3113 return false; 3114 } 3115 3116 /** 3117 * Sync extra comparison function. 3118 * @param b1 bundle to compare 3119 * @param b2 other bundle to compare 3120 * @param includeSyncSettings if false, ignore system settings in bundle. 3121 */ 3122 public static boolean syncExtrasEquals(Bundle b1, Bundle b2, boolean includeSyncSettings) { 3123 if (b1 == b2) { 3124 return true; 3125 } 3126 // Exit early if we can. 3127 if (includeSyncSettings && b1.size() != b2.size()) { 3128 return false; 3129 } 3130 Bundle bigger = b1.size() > b2.size() ? b1 : b2; 3131 Bundle smaller = b1.size() > b2.size() ? b2 : b1; 3132 for (String key : bigger.keySet()) { 3133 if (!includeSyncSettings && isSyncSetting(key)) { 3134 continue; 3135 } 3136 if (!smaller.containsKey(key)) { 3137 return false; 3138 } 3139 if (!Objects.equals(bigger.get(key), smaller.get(key))) { 3140 return false; 3141 } 3142 } 3143 return true; 3144 } 3145 3146 /** 3147 * @return true if the provided key is used by the SyncManager in scheduling the sync. 3148 */ 3149 private static boolean isSyncSetting(String key) { 3150 if (key.equals(ContentResolver.SYNC_EXTRAS_EXPEDITED)) { 3151 return true; 3152 } 3153 if (key.equals(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS)) { 3154 return true; 3155 } 3156 if (key.equals(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF)) { 3157 return true; 3158 } 3159 if (key.equals(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY)) { 3160 return true; 3161 } 3162 if (key.equals(ContentResolver.SYNC_EXTRAS_MANUAL)) { 3163 return true; 3164 } 3165 if (key.equals(ContentResolver.SYNC_EXTRAS_UPLOAD)) { 3166 return true; 3167 } 3168 if (key.equals(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS)) { 3169 return true; 3170 } 3171 if (key.equals(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS)) { 3172 return true; 3173 } 3174 if (key.equals(ContentResolver.SYNC_EXTRAS_EXPECTED_UPLOAD)) { 3175 return true; 3176 } 3177 if (key.equals(ContentResolver.SYNC_EXTRAS_EXPECTED_DOWNLOAD)) { 3178 return true; 3179 } 3180 if (key.equals(ContentResolver.SYNC_EXTRAS_PRIORITY)) { 3181 return true; 3182 } 3183 if (key.equals(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED)) { 3184 return true; 3185 } 3186 if (key.equals(ContentResolver.SYNC_EXTRAS_INITIALIZE)) { 3187 return true; 3188 } 3189 return false; 3190 } 3191 3192 static class PrintTable { 3193 private ArrayList<Object[]> mTable = Lists.newArrayList(); 3194 private final int mCols; 3195 3196 PrintTable(int cols) { 3197 mCols = cols; 3198 } 3199 3200 void set(int row, int col, Object... values) { 3201 if (col + values.length > mCols) { 3202 throw new IndexOutOfBoundsException("Table only has " + mCols + 3203 " columns. can't set " + values.length + " at column " + col); 3204 } 3205 for (int i = mTable.size(); i <= row; i++) { 3206 final Object[] list = new Object[mCols]; 3207 mTable.add(list); 3208 for (int j = 0; j < mCols; j++) { 3209 list[j] = ""; 3210 } 3211 } 3212 System.arraycopy(values, 0, mTable.get(row), col, values.length); 3213 } 3214 3215 void writeTo(PrintWriter out) { 3216 final String[] formats = new String[mCols]; 3217 int totalLength = 0; 3218 for (int col = 0; col < mCols; ++col) { 3219 int maxLength = 0; 3220 for (Object[] row : mTable) { 3221 final int length = row[col].toString().length(); 3222 if (length > maxLength) { 3223 maxLength = length; 3224 } 3225 } 3226 totalLength += maxLength; 3227 formats[col] = String.format("%%-%ds", maxLength); 3228 } 3229 formats[mCols - 1] = "%s"; 3230 printRow(out, formats, mTable.get(0)); 3231 totalLength += (mCols - 1) * 2; 3232 for (int i = 0; i < totalLength; ++i) { 3233 out.print("-"); 3234 } 3235 out.println(); 3236 for (int i = 1, mTableSize = mTable.size(); i < mTableSize; i++) { 3237 Object[] row = mTable.get(i); 3238 printRow(out, formats, row); 3239 } 3240 } 3241 3242 private void printRow(PrintWriter out, String[] formats, Object[] row) { 3243 for (int j = 0, rowLength = row.length; j < rowLength; j++) { 3244 out.printf(String.format(formats[j], row[j].toString())); 3245 out.print(" "); 3246 } 3247 out.println(); 3248 } 3249 3250 public int getNumRows() { 3251 return mTable.size(); 3252 } 3253 } 3254 3255 private Context getContextForUser(UserHandle user) { 3256 try { 3257 return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user); 3258 } catch (NameNotFoundException e) { 3259 // Default to mContext, not finding the package system is running as is unlikely. 3260 return mContext; 3261 } 3262 } 3263}