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