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