• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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