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