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