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