SyncManager.java revision 307da1a46b4c9b711bafe8fbaaa6b98e8868c18e
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 android.content;
18
19import com.android.internal.R;
20import com.android.internal.util.ArrayUtils;
21
22import android.accounts.Account;
23import android.accounts.AccountManager;
24import android.accounts.OnAccountsUpdateListener;
25import android.app.AlarmManager;
26import android.app.Notification;
27import android.app.NotificationManager;
28import android.app.PendingIntent;
29import android.content.pm.ApplicationInfo;
30import android.content.pm.PackageManager;
31import android.content.pm.ResolveInfo;
32import android.content.pm.RegisteredServicesCache;
33import android.content.pm.ProviderInfo;
34import android.net.ConnectivityManager;
35import android.net.NetworkInfo;
36import android.os.Bundle;
37import android.os.Handler;
38import android.os.HandlerThread;
39import android.os.IBinder;
40import android.os.Looper;
41import android.os.Message;
42import android.os.PowerManager;
43import android.os.Process;
44import android.os.RemoteException;
45import android.os.SystemClock;
46import android.os.SystemProperties;
47import android.provider.Settings;
48import android.text.format.DateUtils;
49import android.text.format.Time;
50import android.util.EventLog;
51import android.util.Log;
52import android.util.Pair;
53
54import java.io.DataInputStream;
55import java.io.DataOutputStream;
56import java.io.File;
57import java.io.FileDescriptor;
58import java.io.FileInputStream;
59import java.io.FileNotFoundException;
60import java.io.FileOutputStream;
61import java.io.IOException;
62import java.io.PrintWriter;
63import java.util.ArrayList;
64import java.util.HashSet;
65import java.util.List;
66import java.util.Random;
67import java.util.Collection;
68import java.util.concurrent.CountDownLatch;
69
70/**
71 * @hide
72 */
73public class SyncManager implements OnAccountsUpdateListener {
74    private static final String TAG = "SyncManager";
75
76    // used during dumping of the Sync history
77    private static final long MILLIS_IN_HOUR = 1000 * 60 * 60;
78    private static final long MILLIS_IN_DAY = MILLIS_IN_HOUR * 24;
79    private static final long MILLIS_IN_WEEK = MILLIS_IN_DAY * 7;
80    private static final long MILLIS_IN_4WEEKS = MILLIS_IN_WEEK * 4;
81
82    /** Delay a sync due to local changes this long. In milliseconds */
83    private static final long LOCAL_SYNC_DELAY;
84
85    /**
86     * If a sync takes longer than this and the sync queue is not empty then we will
87     * cancel it and add it back to the end of the sync queue. In milliseconds.
88     */
89    private static final long MAX_TIME_PER_SYNC;
90
91    static {
92        String localSyncDelayString = SystemProperties.get("sync.local_sync_delay");
93        long localSyncDelay = 30 * 1000; // 30 seconds
94        if (localSyncDelayString != null) {
95            try {
96                localSyncDelay = Long.parseLong(localSyncDelayString);
97            } catch (NumberFormatException nfe) {
98                // ignore, use default
99            }
100        }
101        LOCAL_SYNC_DELAY = localSyncDelay;
102
103        String maxTimePerSyncString = SystemProperties.get("sync.max_time_per_sync");
104        long maxTimePerSync = 5 * 60 * 1000; // 5 minutes
105        if (maxTimePerSyncString != null) {
106            try {
107                maxTimePerSync = Long.parseLong(maxTimePerSyncString);
108            } catch (NumberFormatException nfe) {
109                // ignore, use default
110            }
111        }
112        MAX_TIME_PER_SYNC = maxTimePerSync;
113    }
114
115    private static final long SYNC_NOTIFICATION_DELAY = 30 * 1000; // 30 seconds
116
117    /**
118     * When retrying a sync for the first time use this delay. After that
119     * the retry time will double until it reached MAX_SYNC_RETRY_TIME.
120     * In milliseconds.
121     */
122    private static final long INITIAL_SYNC_RETRY_TIME_IN_MS = 30 * 1000; // 30 seconds
123
124    /**
125     * Default the max sync retry time to this value.
126     */
127    private static final long DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS = 60 * 60; // one hour
128
129    /**
130     * An error notification is sent if sync of any of the providers has been failing for this long.
131     */
132    private static final long ERROR_NOTIFICATION_DELAY_MS = 1000 * 60 * 10; // 10 minutes
133
134    private static final String SYNC_WAKE_LOCK = "SyncManagerSyncWakeLock";
135    private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarmWakeLock";
136
137    private Context mContext;
138
139    private volatile Account[] mAccounts = null;
140
141    volatile private PowerManager.WakeLock mSyncWakeLock;
142    volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
143    volatile private boolean mDataConnectionIsConnected = false;
144    volatile private boolean mStorageIsLow = false;
145
146    private final NotificationManager mNotificationMgr;
147    private AlarmManager mAlarmService = null;
148
149    private final SyncStorageEngine mSyncStorageEngine;
150    public final SyncQueue mSyncQueue;
151
152    private ActiveSyncContext mActiveSyncContext = null;
153
154    // set if the sync error indicator should be reported.
155    private boolean mNeedSyncErrorNotification = false;
156    // set if the sync active indicator should be reported
157    private boolean mNeedSyncActiveNotification = false;
158
159    private volatile boolean mSyncPollInitialized;
160    private final PendingIntent mSyncAlarmIntent;
161    private final PendingIntent mSyncPollAlarmIntent;
162    // Synchronized on "this". Instead of using this directly one should instead call
163    // its accessor, getConnManager().
164    private ConnectivityManager mConnManagerDoNotUseDirectly;
165
166    private final SyncAdaptersCache mSyncAdapters;
167
168    private BroadcastReceiver mStorageIntentReceiver =
169            new BroadcastReceiver() {
170                public void onReceive(Context context, Intent intent) {
171                    String action = intent.getAction();
172                    if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
173                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
174                            Log.v(TAG, "Internal storage is low.");
175                        }
176                        mStorageIsLow = true;
177                        cancelActiveSync(null /* any account */, null /* any authority */);
178                    } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
179                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
180                            Log.v(TAG, "Internal storage is ok.");
181                        }
182                        mStorageIsLow = false;
183                        sendCheckAlarmsMessage();
184                    }
185                }
186            };
187
188    private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
189        public void onReceive(Context context, Intent intent) {
190            mSyncHandler.onBootCompleted();
191        }
192    };
193
194    private BroadcastReceiver mBackgroundDataSettingChanged = new BroadcastReceiver() {
195        public void onReceive(Context context, Intent intent) {
196            if (getConnectivityManager().getBackgroundDataSetting()) {
197                scheduleSync(null /* account */, null /* authority */, new Bundle(), 0 /* delay */,
198                        false /* onlyThoseWithUnknownSyncableState */);
199            }
200        }
201    };
202
203    public void onAccountsUpdated(Account[] accounts) {
204        // remember if this was the first time this was called after an update
205        final boolean justBootedUp = mAccounts == null;
206        mAccounts = accounts;
207
208        // if a sync is in progress yet it is no longer in the accounts list,
209        // cancel it
210        ActiveSyncContext activeSyncContext = mActiveSyncContext;
211        if (activeSyncContext != null) {
212            if (!ArrayUtils.contains(accounts, activeSyncContext.mSyncOperation.account)) {
213                Log.d(TAG, "canceling sync since the account has been removed");
214                sendSyncFinishedOrCanceledMessage(activeSyncContext,
215                        null /* no result since this is a cancel */);
216            }
217        }
218
219        // we must do this since we don't bother scheduling alarms when
220        // the accounts are not set yet
221        sendCheckAlarmsMessage();
222
223        mSyncStorageEngine.doDatabaseCleanup(accounts);
224
225        if (accounts.length > 0) {
226            // If this is the first time this was called after a bootup then
227            // the accounts haven't really changed, instead they were just loaded
228            // from the AccountManager. Otherwise at least one of the accounts
229            // has a change.
230            //
231            // If there was a real account change then force a sync of all accounts.
232            // This is a bit of overkill, but at least it will end up retrying syncs
233            // that failed due to an authentication failure and thus will recover if the
234            // account change was a password update.
235            //
236            // If this was the bootup case then don't sync everything, instead only
237            // sync those that have an unknown syncable state, which will give them
238            // a chance to set their syncable state.
239            boolean onlyThoseWithUnkownSyncableState = justBootedUp;
240            scheduleSync(null, null, null, 0 /* no delay */, onlyThoseWithUnkownSyncableState);
241        }
242    }
243
244    private BroadcastReceiver mConnectivityIntentReceiver =
245            new BroadcastReceiver() {
246        public void onReceive(Context context, Intent intent) {
247            NetworkInfo networkInfo =
248                    intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
249            NetworkInfo.State state = (networkInfo == null ? NetworkInfo.State.UNKNOWN :
250                    networkInfo.getState());
251            if (Log.isLoggable(TAG, Log.VERBOSE)) {
252                Log.v(TAG, "received connectivity action.  network info: " + networkInfo);
253            }
254
255            // only pay attention to the CONNECTED and DISCONNECTED states.
256            // if connected, we are connected.
257            // if disconnected, we may not be connected.  in some cases, we may be connected on
258            // a different network.
259            // e.g., if switching from GPRS to WiFi, we may receive the CONNECTED to WiFi and
260            // DISCONNECTED for GPRS in any order.  if we receive the CONNECTED first, and then
261            // a DISCONNECTED, we want to make sure we set mDataConnectionIsConnected to true
262            // since we still have a WiFi connection.
263            switch (state) {
264                case CONNECTED:
265                    mDataConnectionIsConnected = true;
266                    break;
267                case DISCONNECTED:
268                    if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false)) {
269                        mDataConnectionIsConnected = false;
270                    } else {
271                        mDataConnectionIsConnected = true;
272                    }
273                    break;
274                default:
275                    // ignore the rest of the states -- leave our boolean alone.
276            }
277            if (mDataConnectionIsConnected) {
278                initializeSyncPoll();
279                sendCheckAlarmsMessage();
280            }
281        }
282    };
283
284    private BroadcastReceiver mShutdownIntentReceiver =
285            new BroadcastReceiver() {
286        public void onReceive(Context context, Intent intent) {
287            Log.w(TAG, "Writing sync state before shutdown...");
288            getSyncStorageEngine().writeAllState();
289        }
290    };
291
292    private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM";
293    private static final String SYNC_POLL_ALARM = "android.content.syncmanager.SYNC_POLL_ALARM";
294    private final SyncHandler mSyncHandler;
295
296    private static final int MAX_SYNC_POLL_DELAY_SECONDS = 36 * 60 * 60; // 36 hours
297    private static final int MIN_SYNC_POLL_DELAY_SECONDS = 24 * 60 * 60; // 24 hours
298
299    private static final String SYNCMANAGER_PREFS_FILENAME = "/data/system/syncmanager.prefs";
300
301    private volatile boolean mBootCompleted = false;
302
303    private ConnectivityManager getConnectivityManager() {
304        synchronized (this) {
305            if (mConnManagerDoNotUseDirectly == null) {
306                mConnManagerDoNotUseDirectly = (ConnectivityManager)mContext.getSystemService(
307                        Context.CONNECTIVITY_SERVICE);
308            }
309            return mConnManagerDoNotUseDirectly;
310        }
311    }
312
313    public SyncManager(Context context, boolean factoryTest) {
314        // Initialize the SyncStorageEngine first, before registering observers
315        // and creating threads and so on; it may fail if the disk is full.
316        SyncStorageEngine.init(context);
317        mSyncStorageEngine = SyncStorageEngine.getSingleton();
318        mSyncQueue = new SyncQueue(mSyncStorageEngine);
319
320        mContext = context;
321
322        HandlerThread syncThread = new HandlerThread("SyncHandlerThread",
323                Process.THREAD_PRIORITY_BACKGROUND);
324        syncThread.start();
325        mSyncHandler = new SyncHandler(syncThread.getLooper());
326
327        mSyncAdapters = new SyncAdaptersCache(mContext);
328
329        mSyncAlarmIntent = PendingIntent.getBroadcast(
330                mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0);
331
332        mSyncPollAlarmIntent = PendingIntent.getBroadcast(
333                mContext, 0 /* ignored */, new Intent(SYNC_POLL_ALARM), 0);
334
335        IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
336        context.registerReceiver(mConnectivityIntentReceiver, intentFilter);
337
338        if (!factoryTest) {
339            intentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
340            context.registerReceiver(mBootCompletedReceiver, intentFilter);
341        }
342
343        intentFilter = new IntentFilter(ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
344        context.registerReceiver(mBackgroundDataSettingChanged, intentFilter);
345
346        intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
347        intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
348        context.registerReceiver(mStorageIntentReceiver, intentFilter);
349
350        intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
351        intentFilter.setPriority(100);
352        context.registerReceiver(mShutdownIntentReceiver, intentFilter);
353
354        if (!factoryTest) {
355            mNotificationMgr = (NotificationManager)
356                context.getSystemService(Context.NOTIFICATION_SERVICE);
357            context.registerReceiver(new SyncAlarmIntentReceiver(),
358                    new IntentFilter(ACTION_SYNC_ALARM));
359        } else {
360            mNotificationMgr = null;
361        }
362        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
363        mSyncWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, SYNC_WAKE_LOCK);
364        mSyncWakeLock.setReferenceCounted(false);
365
366        // This WakeLock is used to ensure that we stay awake between the time that we receive
367        // a sync alarm notification and when we finish processing it. We need to do this
368        // because we don't do the work in the alarm handler, rather we do it in a message
369        // handler.
370        mHandleAlarmWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
371                HANDLE_SYNC_ALARM_WAKE_LOCK);
372        mHandleAlarmWakeLock.setReferenceCounted(false);
373
374        mSyncStorageEngine.addStatusChangeListener(
375                ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, new ISyncStatusObserver.Stub() {
376            public void onStatusChanged(int which) {
377                // force the sync loop to run if the settings change
378                sendCheckAlarmsMessage();
379            }
380        });
381
382        if (!factoryTest) {
383            AccountManager.get(mContext).addOnAccountsUpdatedListener(SyncManager.this,
384                mSyncHandler, false /* updateImmediately */);
385            // do this synchronously to ensure we have the accounts before this call returns
386            onAccountsUpdated(AccountManager.get(mContext).getAccounts());
387        }
388    }
389
390    private synchronized void initializeSyncPoll() {
391        if (mSyncPollInitialized) return;
392        mSyncPollInitialized = true;
393
394        mContext.registerReceiver(new SyncPollAlarmReceiver(), new IntentFilter(SYNC_POLL_ALARM));
395
396        // load the next poll time from shared preferences
397        long absoluteAlarmTime = readSyncPollTime();
398
399        if (Log.isLoggable(TAG, Log.VERBOSE)) {
400            Log.v(TAG, "initializeSyncPoll: absoluteAlarmTime is " + absoluteAlarmTime);
401        }
402
403        // Convert absoluteAlarmTime to elapsed realtime. If this time was in the past then
404        // schedule the poll immediately, if it is too far in the future then cap it at
405        // MAX_SYNC_POLL_DELAY_SECONDS.
406        long absoluteNow = System.currentTimeMillis();
407        long relativeNow = SystemClock.elapsedRealtime();
408        long relativeAlarmTime = relativeNow;
409        if (absoluteAlarmTime > absoluteNow) {
410            long delayInMs = absoluteAlarmTime - absoluteNow;
411            final int maxDelayInMs = MAX_SYNC_POLL_DELAY_SECONDS * 1000;
412            if (delayInMs > maxDelayInMs) {
413                delayInMs = MAX_SYNC_POLL_DELAY_SECONDS * 1000;
414            }
415            relativeAlarmTime += delayInMs;
416        }
417
418        // schedule an alarm for the next poll time
419        scheduleSyncPollAlarm(relativeAlarmTime);
420    }
421
422    private void scheduleSyncPollAlarm(long relativeAlarmTime) {
423        if (Log.isLoggable(TAG, Log.VERBOSE)) {
424            Log.v(TAG, "scheduleSyncPollAlarm: relativeAlarmTime is " + relativeAlarmTime
425                    + ", now is " + SystemClock.elapsedRealtime()
426                    + ", delay is " + (relativeAlarmTime - SystemClock.elapsedRealtime()));
427        }
428        ensureAlarmService();
429        mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, relativeAlarmTime,
430                mSyncPollAlarmIntent);
431    }
432
433    /**
434     * Return a random value v that satisfies minValue <= v < maxValue. The difference between
435     * maxValue and minValue must be less than Integer.MAX_VALUE.
436     */
437    private long jitterize(long minValue, long maxValue) {
438        Random random = new Random(SystemClock.elapsedRealtime());
439        long spread = maxValue - minValue;
440        if (spread > Integer.MAX_VALUE) {
441            throw new IllegalArgumentException("the difference between the maxValue and the "
442                    + "minValue must be less than " + Integer.MAX_VALUE);
443        }
444        return minValue + random.nextInt((int)spread);
445    }
446
447    private void handleSyncPollAlarm() {
448        // determine the next poll time
449        long delayMs = jitterize(MIN_SYNC_POLL_DELAY_SECONDS, MAX_SYNC_POLL_DELAY_SECONDS) * 1000;
450        long nextRelativePollTimeMs = SystemClock.elapsedRealtime() + delayMs;
451
452        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "handleSyncPollAlarm: delay " + delayMs);
453
454        // write the absolute time to shared preferences
455        writeSyncPollTime(System.currentTimeMillis() + delayMs);
456
457        // schedule an alarm for the next poll time
458        scheduleSyncPollAlarm(nextRelativePollTimeMs);
459
460        // perform a poll
461        scheduleSync(null /* sync all syncable accounts */, null /* sync all syncable providers */,
462                new Bundle(), 0 /* no delay */, false /* onlyThoseWithUnkownSyncableState */);
463    }
464
465    private void writeSyncPollTime(long when) {
466        File f = new File(SYNCMANAGER_PREFS_FILENAME);
467        DataOutputStream str = null;
468        try {
469            str = new DataOutputStream(new FileOutputStream(f));
470            str.writeLong(when);
471        } catch (FileNotFoundException e) {
472            Log.w(TAG, "error writing to file " + f, e);
473        } catch (IOException e) {
474            Log.w(TAG, "error writing to file " + f, e);
475        } finally {
476            if (str != null) {
477                try {
478                    str.close();
479                } catch (IOException e) {
480                    Log.w(TAG, "error closing file " + f, e);
481                }
482            }
483        }
484    }
485
486    private long readSyncPollTime() {
487        File f = new File(SYNCMANAGER_PREFS_FILENAME);
488
489        DataInputStream str = null;
490        try {
491            str = new DataInputStream(new FileInputStream(f));
492            return str.readLong();
493        } catch (FileNotFoundException e) {
494            writeSyncPollTime(0);
495        } catch (IOException e) {
496            Log.w(TAG, "error reading file " + f, e);
497        } finally {
498            if (str != null) {
499                try {
500                    str.close();
501                } catch (IOException e) {
502                    Log.w(TAG, "error closing file " + f, e);
503                }
504            }
505        }
506        return 0;
507    }
508
509    public ActiveSyncContext getActiveSyncContext() {
510        return mActiveSyncContext;
511    }
512
513    public SyncStorageEngine getSyncStorageEngine() {
514        return mSyncStorageEngine;
515    }
516
517    private void ensureAlarmService() {
518        if (mAlarmService == null) {
519            mAlarmService = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
520        }
521    }
522
523    public Account getSyncingAccount() {
524        ActiveSyncContext activeSyncContext = mActiveSyncContext;
525        return (activeSyncContext != null) ? activeSyncContext.mSyncOperation.account : null;
526    }
527
528    private void initializeSyncAdapter(Account account, String authority) {
529        SyncAdapterType syncAdapterType = SyncAdapterType.newKey(authority, account.type);
530        RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
531                mSyncAdapters.getServiceInfo(syncAdapterType);
532        if (syncAdapterInfo == null) {
533            Log.w(TAG, "can't find a sync adapter for " + syncAdapterType);
534            return;
535        }
536
537        Intent intent = new Intent();
538        intent.setAction("android.content.SyncAdapter");
539        intent.setComponent(syncAdapterInfo.componentName);
540        mContext.bindService(intent, new InitializerServiceConnection(account, authority),
541                Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND);
542    }
543
544    private class InitializerServiceConnection implements ServiceConnection {
545        private final Account mAccount;
546        private final String mAuthority;
547
548        public InitializerServiceConnection(Account account, String authority) {
549            mAccount = account;
550            mAuthority = authority;
551        }
552
553        public void onServiceConnected(ComponentName name, IBinder service) {
554            try {
555                ISyncAdapter.Stub.asInterface(service).initialize(mAccount, mAuthority);
556            } catch (RemoteException e) {
557                // doesn't matter, we will retry again later
558            } finally {
559                mContext.unbindService(this);
560            }
561        }
562
563        public void onServiceDisconnected(ComponentName name) {
564            mContext.unbindService(this);
565        }
566    }
567
568    /**
569     * Returns whether or not sync is enabled.  Sync can be enabled by
570     * setting the system property "ro.config.sync" to the value "yes".
571     * This is normally done at boot time on builds that support sync.
572     * @return true if sync is enabled
573     */
574    private boolean isSyncEnabled() {
575        // Require the precise value "yes" to discourage accidental activation.
576        return "yes".equals(SystemProperties.get("ro.config.sync"));
577    }
578
579    /**
580     * Initiate a sync. This can start a sync for all providers
581     * (pass null to url, set onlyTicklable to false), only those
582     * providers that are marked as ticklable (pass null to url,
583     * set onlyTicklable to true), or a specific provider (set url
584     * to the content url of the provider).
585     *
586     * <p>If the ContentResolver.SYNC_EXTRAS_UPLOAD boolean in extras is
587     * true then initiate a sync that just checks for local changes to send
588     * to the server, otherwise initiate a sync that first gets any
589     * changes from the server before sending local changes back to
590     * the server.
591     *
592     * <p>If a specific provider is being synced (the url is non-null)
593     * then the extras can contain SyncAdapter-specific information
594     * to control what gets synced (e.g. which specific feed to sync).
595     *
596     * <p>You'll start getting callbacks after this.
597     *
598     * @param requestedAccount the account to sync, may be null to signify all accounts
599     * @param requestedAuthority the authority to sync, may be null to indicate all authorities
600     * @param extras a Map of SyncAdapter-specific information to control
601*          syncs of a specific provider. Can be null. Is ignored
602*          if the url is null.
603     * @param delay how many milliseconds in the future to wait before performing this
604     * @param onlyThoseWithUnkownSyncableState
605     */
606    public void scheduleSync(Account requestedAccount, String requestedAuthority,
607            Bundle extras, long delay, boolean onlyThoseWithUnkownSyncableState) {
608        boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
609
610        if (mAccounts == null) {
611            Log.e(TAG, "scheduleSync: the accounts aren't known yet, this should never happen");
612            return;
613        }
614
615        if (!isSyncEnabled()) {
616            if (isLoggable) {
617                Log.v(TAG, "not syncing because sync is disabled");
618            }
619            return;
620        }
621
622        final boolean backgroundDataUsageAllowed = !mBootCompleted ||
623                getConnectivityManager().getBackgroundDataSetting();
624
625        if (extras == null) extras = new Bundle();
626
627        Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
628        if (expedited) {
629            delay = -1; // this means schedule at the front of the queue
630        }
631
632        Account[] accounts;
633        if (requestedAccount != null) {
634            accounts = new Account[]{requestedAccount};
635        } else {
636            // if the accounts aren't configured yet then we can't support an account-less
637            // sync request
638            accounts = mAccounts;
639            if (accounts == null) {
640                // not ready yet
641                if (isLoggable) {
642                    Log.v(TAG, "scheduleSync: no accounts yet, dropping");
643                }
644                return;
645            }
646            if (accounts.length == 0) {
647                if (isLoggable) {
648                    Log.v(TAG, "scheduleSync: no accounts configured, dropping");
649                }
650                return;
651            }
652        }
653
654        final boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
655        final boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
656
657        int source;
658        if (uploadOnly) {
659            source = SyncStorageEngine.SOURCE_LOCAL;
660        } else if (manualSync) {
661            source = SyncStorageEngine.SOURCE_USER;
662        } else if (requestedAuthority == null) {
663            source = SyncStorageEngine.SOURCE_POLL;
664        } else {
665            // this isn't strictly server, since arbitrary callers can (and do) request
666            // a non-forced two-way sync on a specific url
667            source = SyncStorageEngine.SOURCE_SERVER;
668        }
669
670        // Compile a list of authorities that have sync adapters.
671        // For each authority sync each account that matches a sync adapter.
672        final HashSet<String> syncableAuthorities = new HashSet<String>();
673        for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter :
674                mSyncAdapters.getAllServices()) {
675            syncableAuthorities.add(syncAdapter.type.authority);
676        }
677
678        // if the url was specified then replace the list of authorities with just this authority
679        // or clear it if this authority isn't syncable
680        if (requestedAuthority != null) {
681            final boolean hasSyncAdapter = syncableAuthorities.contains(requestedAuthority);
682            syncableAuthorities.clear();
683            if (hasSyncAdapter) syncableAuthorities.add(requestedAuthority);
684        }
685
686        final boolean masterSyncAutomatically = mSyncStorageEngine.getMasterSyncAutomatically();
687
688        for (String authority : syncableAuthorities) {
689            for (Account account : accounts) {
690                int isSyncable = mSyncStorageEngine.getIsSyncable(account, authority);
691                if (isSyncable == 0) {
692                    continue;
693                }
694                if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) {
695                    continue;
696                }
697                final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
698                        mSyncAdapters.getServiceInfo(
699                                SyncAdapterType.newKey(authority, account.type));
700                if (syncAdapterInfo != null) {
701                    if (!syncAdapterInfo.type.supportsUploading() && uploadOnly) {
702                        continue;
703                    }
704
705                    // initialize the SyncAdapter if the isSyncable state is unknown
706                    if (isSyncable < 0) {
707                        initializeSyncAdapter(account, authority);
708                        continue;
709                    }
710
711                    final boolean syncAutomatically = masterSyncAutomatically
712                            && mSyncStorageEngine.getSyncAutomatically(account, authority);
713                    boolean syncAllowed =
714                            manualSync || (backgroundDataUsageAllowed && syncAutomatically);
715                    if (!syncAllowed) {
716                        if (isLoggable) {
717                            Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority
718                                    + " is not allowed, dropping request");
719                        }
720                        continue;
721                    }
722
723                    if (isLoggable) {
724                        Log.v(TAG, "scheduleSync:"
725                                + " delay " + delay
726                                + ", source " + source
727                                + ", account " + account
728                                + ", authority " + authority
729                                + ", extras " + extras);
730                    }
731                    scheduleSyncOperation(
732                            new SyncOperation(account, source, authority, extras, delay));
733                }
734            }
735        }
736    }
737
738    public void scheduleLocalSync(Account account, String authority) {
739        final Bundle extras = new Bundle();
740        extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
741        scheduleSync(account, authority, extras, LOCAL_SYNC_DELAY,
742                false /* onlyThoseWithUnkownSyncableState */);
743    }
744
745    public SyncAdapterType[] getSyncAdapterTypes() {
746        final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> serviceInfos =
747                mSyncAdapters.getAllServices();
748        SyncAdapterType[] types = new SyncAdapterType[serviceInfos.size()];
749        int i = 0;
750        for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> serviceInfo : serviceInfos) {
751            types[i] = serviceInfo.type;
752            ++i;
753        }
754        return types;
755    }
756
757    private void sendSyncAlarmMessage() {
758        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_ALARM");
759        mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_SYNC_ALARM);
760    }
761
762    private void sendCheckAlarmsMessage() {
763        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CHECK_ALARMS");
764        mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_CHECK_ALARMS);
765    }
766
767    private void sendSyncFinishedOrCanceledMessage(ActiveSyncContext syncContext,
768            SyncResult syncResult) {
769        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_FINISHED");
770        Message msg = mSyncHandler.obtainMessage();
771        msg.what = SyncHandler.MESSAGE_SYNC_FINISHED;
772        msg.obj = new SyncHandlerMessagePayload(syncContext, syncResult);
773        mSyncHandler.sendMessage(msg);
774    }
775
776    class SyncHandlerMessagePayload {
777        public final ActiveSyncContext activeSyncContext;
778        public final SyncResult syncResult;
779
780        SyncHandlerMessagePayload(ActiveSyncContext syncContext, SyncResult syncResult) {
781            this.activeSyncContext = syncContext;
782            this.syncResult = syncResult;
783        }
784    }
785
786    class SyncAlarmIntentReceiver extends BroadcastReceiver {
787        public void onReceive(Context context, Intent intent) {
788            mHandleAlarmWakeLock.acquire();
789            sendSyncAlarmMessage();
790        }
791    }
792
793    class SyncPollAlarmReceiver extends BroadcastReceiver {
794        public void onReceive(Context context, Intent intent) {
795            handleSyncPollAlarm();
796        }
797    }
798
799    private void clearBackoffSetting(SyncOperation op) {
800        mSyncStorageEngine.setBackoff(op.account, op.authority,
801                SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
802    }
803
804    private void increaseBackoffSetting(SyncOperation op) {
805        final long now = SystemClock.elapsedRealtime();
806
807        final Pair<Long, Long> previousSettings =
808                mSyncStorageEngine.getBackoff(op.account, op.authority);
809        long newDelayInMs;
810        if (previousSettings == null || previousSettings.second <= 0) {
811            // The initial delay is the jitterized INITIAL_SYNC_RETRY_TIME_IN_MS
812            newDelayInMs = jitterize(INITIAL_SYNC_RETRY_TIME_IN_MS,
813                    (long)(INITIAL_SYNC_RETRY_TIME_IN_MS * 1.1));
814        } else {
815            // Subsequent delays are the double of the previous delay
816            newDelayInMs = previousSettings.second * 2;
817        }
818
819        // Cap the delay
820        long maxSyncRetryTimeInSeconds = Settings.Secure.getLong(mContext.getContentResolver(),
821                Settings.Secure.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
822                DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS);
823        if (newDelayInMs > maxSyncRetryTimeInSeconds * 1000) {
824            newDelayInMs = maxSyncRetryTimeInSeconds * 1000;
825        }
826
827        mSyncStorageEngine.setBackoff(op.account, op.authority,
828                now + newDelayInMs, newDelayInMs);
829    }
830
831    private void setDelayUntilTime(SyncOperation op, long delayUntilSeconds) {
832        final long delayUntil = delayUntilSeconds * 1000;
833        final long absoluteNow = System.currentTimeMillis();
834        long newDelayUntilTime;
835        if (delayUntil > absoluteNow) {
836            newDelayUntilTime = SystemClock.elapsedRealtime() + (delayUntil - absoluteNow);
837        } else {
838            newDelayUntilTime = 0;
839        }
840        mSyncStorageEngine.setDelayUntilTime(op.account, op.authority, newDelayUntilTime);
841    }
842
843    /**
844     * Cancel the active sync if it matches the authority and account.
845     * @param account limit the cancelations to syncs with this account, if non-null
846     * @param authority limit the cancelations to syncs with this authority, if non-null
847     */
848    public void cancelActiveSync(Account account, String authority) {
849        ActiveSyncContext activeSyncContext = mActiveSyncContext;
850        if (activeSyncContext != null) {
851            // if an authority was specified then only cancel the sync if it matches
852            if (account != null) {
853                if (!account.equals(activeSyncContext.mSyncOperation.account)) {
854                    return;
855                }
856            }
857            // if an account was specified then only cancel the sync if it matches
858            if (authority != null) {
859                if (!authority.equals(activeSyncContext.mSyncOperation.authority)) {
860                    return;
861                }
862            }
863            sendSyncFinishedOrCanceledMessage(activeSyncContext,
864                    null /* no result since this is a cancel */);
865        }
866    }
867
868    /**
869     * Create and schedule a SyncOperation.
870     *
871     * @param syncOperation the SyncOperation to schedule
872     */
873    public void scheduleSyncOperation(SyncOperation syncOperation) {
874        // If this operation is expedited and there is a sync in progress then
875        // reschedule the current operation and send a cancel for it.
876        final ActiveSyncContext activeSyncContext = mActiveSyncContext;
877        if (syncOperation.expedited && activeSyncContext != null) {
878            final boolean hasSameKey =
879                    activeSyncContext.mSyncOperation.key.equals(syncOperation.key);
880            // This request is expedited and there is a sync in progress.
881            // Interrupt the current sync only if it is not expedited and if it has a different
882            // key than the one we are scheduling.
883            if (!activeSyncContext.mSyncOperation.expedited && !hasSameKey) {
884                scheduleSyncOperation(new SyncOperation(activeSyncContext.mSyncOperation));
885                sendSyncFinishedOrCanceledMessage(activeSyncContext,
886                        null /* no result since this is a cancel */);
887            }
888        }
889
890        boolean queueChanged;
891        synchronized (mSyncQueue) {
892            queueChanged = mSyncQueue.add(syncOperation);
893        }
894
895        if (queueChanged) {
896            if (Log.isLoggable(TAG, Log.VERBOSE)) {
897                Log.v(TAG, "scheduleSyncOperation: enqueued " + syncOperation);
898            }
899            sendCheckAlarmsMessage();
900        } else {
901            if (Log.isLoggable(TAG, Log.VERBOSE)) {
902                Log.v(TAG, "scheduleSyncOperation: dropping duplicate sync operation "
903                        + syncOperation);
904            }
905        }
906    }
907
908    /**
909     * Remove scheduled sync operations.
910     * @param account limit the removals to operations with this account, if non-null
911     * @param authority limit the removals to operations with this authority, if non-null
912     */
913    public void clearScheduledSyncOperations(Account account, String authority) {
914        mSyncStorageEngine.setBackoff(account, authority,
915                SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
916        synchronized (mSyncQueue) {
917            mSyncQueue.clear(account, authority);
918        }
919    }
920
921    void maybeRescheduleSync(SyncResult syncResult, SyncOperation operation) {
922        boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
923        if (isLoggable) {
924            Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", " + operation);
925        }
926
927        // If this sync aborted because the internal sync loop retried too many times then
928        //   don't reschedule. Otherwise we risk getting into a retry loop.
929        // If the operation succeeded to some extent then retry immediately.
930        // If this was a two-way sync then retry soft errors with an exponential backoff.
931        // If this was an upward sync then schedule a two-way sync immediately.
932        // Otherwise do not reschedule.
933        if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)) {
934            Log.d(TAG, "not retrying sync operation because it is a manual sync: "
935                    + operation);
936        } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)) {
937            final SyncOperation newSyncOperation = new SyncOperation(operation);
938            newSyncOperation.extras.remove(ContentResolver.SYNC_EXTRAS_UPLOAD);
939            Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
940                    + "encountered an error: " + operation);
941            scheduleSyncOperation(newSyncOperation);
942        } else if (syncResult.tooManyRetries) {
943            Log.d(TAG, "not retrying sync operation because it retried too many times: "
944                    + operation);
945        } else if (syncResult.madeSomeProgress()) {
946            if (isLoggable) {
947                Log.d(TAG, "retrying sync operation because even though it had an error "
948                        + "it achieved some success");
949            }
950            scheduleSyncOperation(new SyncOperation(operation));
951        } else if (syncResult.hasSoftError()) {
952            if (isLoggable) {
953                Log.d(TAG, "retrying sync operation because it encountered a soft error: "
954                        + operation);
955            }
956            scheduleSyncOperation(new SyncOperation(operation));
957        } else {
958            Log.d(TAG, "not retrying sync operation because the error is a hard error: "
959                    + operation);
960        }
961    }
962
963    /**
964     * @hide
965     */
966    class ActiveSyncContext extends ISyncContext.Stub implements ServiceConnection {
967        final SyncOperation mSyncOperation;
968        final long mHistoryRowId;
969        ISyncAdapter mSyncAdapter;
970        final long mStartTime;
971        long mTimeoutStartTime;
972
973        public ActiveSyncContext(SyncOperation syncOperation,
974                long historyRowId) {
975            super();
976            mSyncOperation = syncOperation;
977            mHistoryRowId = historyRowId;
978            mSyncAdapter = null;
979            mStartTime = SystemClock.elapsedRealtime();
980            mTimeoutStartTime = mStartTime;
981        }
982
983        public void sendHeartbeat() {
984            // heartbeats are no longer used
985        }
986
987        public void onFinished(SyncResult result) {
988            // include "this" in the message so that the handler can ignore it if this
989            // ActiveSyncContext is no longer the mActiveSyncContext at message handling
990            // time
991            sendSyncFinishedOrCanceledMessage(this, result);
992        }
993
994        public void toString(StringBuilder sb) {
995            sb.append("startTime ").append(mStartTime)
996                    .append(", mTimeoutStartTime ").append(mTimeoutStartTime)
997                    .append(", mHistoryRowId ").append(mHistoryRowId)
998                    .append(", syncOperation ").append(mSyncOperation);
999        }
1000
1001        public void onServiceConnected(ComponentName name, IBinder service) {
1002            Message msg = mSyncHandler.obtainMessage();
1003            msg.what = SyncHandler.MESSAGE_SERVICE_CONNECTED;
1004            msg.obj = new ServiceConnectionData(this, ISyncAdapter.Stub.asInterface(service));
1005            mSyncHandler.sendMessage(msg);
1006        }
1007
1008        public void onServiceDisconnected(ComponentName name) {
1009            Message msg = mSyncHandler.obtainMessage();
1010            msg.what = SyncHandler.MESSAGE_SERVICE_DISCONNECTED;
1011            msg.obj = new ServiceConnectionData(this, null);
1012            mSyncHandler.sendMessage(msg);
1013        }
1014
1015        boolean bindToSyncAdapter(RegisteredServicesCache.ServiceInfo info) {
1016            if (Log.isLoggable(TAG, Log.VERBOSE)) {
1017                Log.d(TAG, "bindToSyncAdapter: " + info.componentName + ", connection " + this);
1018            }
1019            Intent intent = new Intent();
1020            intent.setAction("android.content.SyncAdapter");
1021            intent.setComponent(info.componentName);
1022            intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
1023                    com.android.internal.R.string.sync_binding_label);
1024            intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
1025                    mContext, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0));
1026            return mContext.bindService(intent, this,
1027                    Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND);
1028        }
1029
1030        void unBindFromSyncAdapter() {
1031            if (Log.isLoggable(TAG, Log.VERBOSE)) {
1032                Log.d(TAG, "unBindFromSyncAdapter: connection " + this);
1033            }
1034            mContext.unbindService(this);
1035        }
1036
1037        @Override
1038        public String toString() {
1039            StringBuilder sb = new StringBuilder();
1040            toString(sb);
1041            return sb.toString();
1042        }
1043    }
1044
1045    protected void dump(FileDescriptor fd, PrintWriter pw) {
1046        StringBuilder sb = new StringBuilder();
1047        dumpSyncState(pw, sb);
1048        if (isSyncEnabled()) {
1049            dumpSyncHistory(pw, sb);
1050        }
1051
1052        pw.println();
1053        pw.println("SyncAdapters:");
1054        for (RegisteredServicesCache.ServiceInfo info : mSyncAdapters.getAllServices()) {
1055            pw.println("  " + info);
1056        }
1057    }
1058
1059    static String formatTime(long time) {
1060        Time tobj = new Time();
1061        tobj.set(time);
1062        return tobj.format("%Y-%m-%d %H:%M:%S");
1063    }
1064
1065    protected void dumpSyncState(PrintWriter pw, StringBuilder sb) {
1066        pw.print("sync enabled: "); pw.println(isSyncEnabled());
1067        pw.print("data connected: "); pw.println(mDataConnectionIsConnected);
1068        pw.print("memory low: "); pw.println(mStorageIsLow);
1069
1070        final Account[] accounts = mAccounts;
1071        pw.print("accounts: ");
1072        if (accounts != null) {
1073            pw.println(accounts.length);
1074        } else {
1075            pw.println("none");
1076        }
1077        final long now = SystemClock.elapsedRealtime();
1078        pw.print("now: "); pw.println(now);
1079        pw.print("uptime: "); pw.print(DateUtils.formatElapsedTime(now/1000));
1080                pw.println(" (HH:MM:SS)");
1081        pw.print("time spent syncing: ");
1082                pw.print(DateUtils.formatElapsedTime(
1083                        mSyncHandler.mSyncTimeTracker.timeSpentSyncing() / 1000));
1084                pw.print(" (HH:MM:SS), sync ");
1085                pw.print(mSyncHandler.mSyncTimeTracker.mLastWasSyncing ? "" : "not ");
1086                pw.println("in progress");
1087        if (mSyncHandler.mAlarmScheduleTime != null) {
1088            pw.print("next alarm time: "); pw.print(mSyncHandler.mAlarmScheduleTime);
1089                    pw.print(" (");
1090                    pw.print(DateUtils.formatElapsedTime((mSyncHandler.mAlarmScheduleTime-now)/1000));
1091                    pw.println(" (HH:MM:SS) from now)");
1092        } else {
1093            pw.println("no alarm is scheduled (there had better not be any pending syncs)");
1094        }
1095
1096        pw.print("active sync: "); pw.println(mActiveSyncContext);
1097
1098        pw.print("notification info: ");
1099        sb.setLength(0);
1100        mSyncHandler.mSyncNotificationInfo.toString(sb);
1101        pw.println(sb.toString());
1102
1103        synchronized (mSyncQueue) {
1104            pw.print("sync queue: ");
1105            sb.setLength(0);
1106            mSyncQueue.dump(sb);
1107            pw.println(sb.toString());
1108        }
1109
1110        ActiveSyncInfo active = mSyncStorageEngine.getActiveSync();
1111        if (active != null) {
1112            SyncStorageEngine.AuthorityInfo authority
1113                    = mSyncStorageEngine.getAuthority(active.authorityId);
1114            final long durationInSeconds = (now - active.startTime) / 1000;
1115            pw.print("Active sync: ");
1116                    pw.print(authority != null ? authority.account : "<no account>");
1117                    pw.print(" ");
1118                    pw.print(authority != null ? authority.authority : "<no account>");
1119                    pw.print(", duration is ");
1120                    pw.println(DateUtils.formatElapsedTime(durationInSeconds));
1121        } else {
1122            pw.println("No sync is in progress.");
1123        }
1124
1125        ArrayList<SyncStorageEngine.PendingOperation> ops
1126                = mSyncStorageEngine.getPendingOperations();
1127        if (ops != null && ops.size() > 0) {
1128            pw.println();
1129            pw.println("Pending Syncs");
1130            final int N = ops.size();
1131            for (int i=0; i<N; i++) {
1132                SyncStorageEngine.PendingOperation op = ops.get(i);
1133                pw.print("  #"); pw.print(i); pw.print(": account=");
1134                pw.print(op.account.name); pw.print(":");
1135                pw.print(op.account.type); pw.print(" authority=");
1136                pw.print(op.authority); pw.print(" expedited=");
1137                pw.println(op.expedited);
1138                if (op.extras != null && op.extras.size() > 0) {
1139                    sb.setLength(0);
1140                    SyncOperation.extrasToStringBuilder(op.extras, sb);
1141                    pw.print("    extras: "); pw.println(sb.toString());
1142                }
1143            }
1144        }
1145
1146        HashSet<Account> processedAccounts = new HashSet<Account>();
1147        ArrayList<SyncStatusInfo> statuses
1148                = mSyncStorageEngine.getSyncStatus();
1149        if (statuses != null && statuses.size() > 0) {
1150            pw.println();
1151            pw.println("Sync Status");
1152            final int N = statuses.size();
1153            for (int i=0; i<N; i++) {
1154                SyncStatusInfo status = statuses.get(i);
1155                SyncStorageEngine.AuthorityInfo authority
1156                        = mSyncStorageEngine.getAuthority(status.authorityId);
1157                if (authority != null) {
1158                    Account curAccount = authority.account;
1159
1160                    if (processedAccounts.contains(curAccount)) {
1161                        continue;
1162                    }
1163
1164                    processedAccounts.add(curAccount);
1165
1166                    pw.print("  Account "); pw.print(authority.account.name);
1167                            pw.print(" "); pw.print(authority.account.type);
1168                            pw.println(":");
1169                    for (int j=i; j<N; j++) {
1170                        status = statuses.get(j);
1171                        authority = mSyncStorageEngine.getAuthority(status.authorityId);
1172                        if (!curAccount.equals(authority.account)) {
1173                            continue;
1174                        }
1175                        pw.print("    "); pw.print(authority.authority);
1176                        pw.println(":");
1177                        final String syncable = authority.syncable > 0
1178                                ? "syncable"
1179                                : (authority.syncable == 0 ? "not syncable" : "not initialized");
1180                        final String enabled = authority.enabled ? "enabled" : "disabled";
1181                        final String delayUntil = authority.delayUntil > now
1182                                ? "delay for " + ((authority.delayUntil - now) / 1000) + " sec"
1183                                : "no delay required";
1184                        final String backoff = authority.backoffTime > now
1185                                ? "backoff for " + ((authority.backoffTime - now) / 1000)
1186                                  + " sec"
1187                                : "no backoff required";
1188                        final String backoffDelay = authority.backoffDelay > 0
1189                                ? ("the backoff increment is " + authority.backoffDelay / 1000
1190                                        + " sec")
1191                                : "no backoff increment";
1192                        pw.println(String.format(
1193                                "      settings: %s, %s, %s, %s, %s",
1194                                enabled, syncable, backoff, backoffDelay, delayUntil));
1195                        pw.print("      count: local="); pw.print(status.numSourceLocal);
1196                                pw.print(" poll="); pw.print(status.numSourcePoll);
1197                                pw.print(" server="); pw.print(status.numSourceServer);
1198                                pw.print(" user="); pw.print(status.numSourceUser);
1199                                pw.print(" total="); pw.println(status.numSyncs);
1200                        pw.print("      total duration: ");
1201                                pw.println(DateUtils.formatElapsedTime(
1202                                        status.totalElapsedTime/1000));
1203                        if (status.lastSuccessTime != 0) {
1204                            pw.print("      SUCCESS: source=");
1205                                    pw.print(SyncStorageEngine.SOURCES[
1206                                            status.lastSuccessSource]);
1207                                    pw.print(" time=");
1208                                    pw.println(formatTime(status.lastSuccessTime));
1209                        } else {
1210                            pw.print("      FAILURE: source=");
1211                                    pw.print(SyncStorageEngine.SOURCES[
1212                                            status.lastFailureSource]);
1213                                    pw.print(" initialTime=");
1214                                    pw.print(formatTime(status.initialFailureTime));
1215                                    pw.print(" lastTime=");
1216                                    pw.println(formatTime(status.lastFailureTime));
1217                            pw.print("      message: "); pw.println(status.lastFailureMesg);
1218                        }
1219                    }
1220                }
1221            }
1222        }
1223    }
1224
1225    private void dumpTimeSec(PrintWriter pw, long time) {
1226        pw.print(time/1000); pw.print('.'); pw.print((time/100)%10);
1227        pw.print('s');
1228    }
1229
1230    private void dumpDayStatistic(PrintWriter pw, SyncStorageEngine.DayStats ds) {
1231        pw.print("Success ("); pw.print(ds.successCount);
1232        if (ds.successCount > 0) {
1233            pw.print(" for "); dumpTimeSec(pw, ds.successTime);
1234            pw.print(" avg="); dumpTimeSec(pw, ds.successTime/ds.successCount);
1235        }
1236        pw.print(") Failure ("); pw.print(ds.failureCount);
1237        if (ds.failureCount > 0) {
1238            pw.print(" for "); dumpTimeSec(pw, ds.failureTime);
1239            pw.print(" avg="); dumpTimeSec(pw, ds.failureTime/ds.failureCount);
1240        }
1241        pw.println(")");
1242    }
1243
1244    protected void dumpSyncHistory(PrintWriter pw, StringBuilder sb) {
1245        SyncStorageEngine.DayStats dses[] = mSyncStorageEngine.getDayStatistics();
1246        if (dses != null && dses[0] != null) {
1247            pw.println();
1248            pw.println("Sync Statistics");
1249            pw.print("  Today:  "); dumpDayStatistic(pw, dses[0]);
1250            int today = dses[0].day;
1251            int i;
1252            SyncStorageEngine.DayStats ds;
1253
1254            // Print each day in the current week.
1255            for (i=1; i<=6 && i < dses.length; i++) {
1256                ds = dses[i];
1257                if (ds == null) break;
1258                int delta = today-ds.day;
1259                if (delta > 6) break;
1260
1261                pw.print("  Day-"); pw.print(delta); pw.print(":  ");
1262                dumpDayStatistic(pw, ds);
1263            }
1264
1265            // Aggregate all following days into weeks and print totals.
1266            int weekDay = today;
1267            while (i < dses.length) {
1268                SyncStorageEngine.DayStats aggr = null;
1269                weekDay -= 7;
1270                while (i < dses.length) {
1271                    ds = dses[i];
1272                    if (ds == null) {
1273                        i = dses.length;
1274                        break;
1275                    }
1276                    int delta = weekDay-ds.day;
1277                    if (delta > 6) break;
1278                    i++;
1279
1280                    if (aggr == null) {
1281                        aggr = new SyncStorageEngine.DayStats(weekDay);
1282                    }
1283                    aggr.successCount += ds.successCount;
1284                    aggr.successTime += ds.successTime;
1285                    aggr.failureCount += ds.failureCount;
1286                    aggr.failureTime += ds.failureTime;
1287                }
1288                if (aggr != null) {
1289                    pw.print("  Week-"); pw.print((today-weekDay)/7); pw.print(": ");
1290                    dumpDayStatistic(pw, aggr);
1291                }
1292            }
1293        }
1294
1295        ArrayList<SyncStorageEngine.SyncHistoryItem> items
1296                = mSyncStorageEngine.getSyncHistory();
1297        if (items != null && items.size() > 0) {
1298            pw.println();
1299            pw.println("Recent Sync History");
1300            final int N = items.size();
1301            for (int i=0; i<N; i++) {
1302                SyncStorageEngine.SyncHistoryItem item = items.get(i);
1303                SyncStorageEngine.AuthorityInfo authority
1304                        = mSyncStorageEngine.getAuthority(item.authorityId);
1305                pw.print("  #"); pw.print(i+1); pw.print(": ");
1306                        if (authority != null) {
1307                            pw.print(authority.account.name);
1308                            pw.print(":");
1309                            pw.print(authority.account.type);
1310                            pw.print(" ");
1311                            pw.print(authority.authority);
1312                        } else {
1313                            pw.print("<no account>");
1314                        }
1315                Time time = new Time();
1316                time.set(item.eventTime);
1317                pw.print(" "); pw.print(SyncStorageEngine.SOURCES[item.source]);
1318                        pw.print(" @ ");
1319                        pw.print(formatTime(item.eventTime));
1320                        pw.print(" for ");
1321                        dumpTimeSec(pw, item.elapsedTime);
1322                        pw.println();
1323                if (item.event != SyncStorageEngine.EVENT_STOP
1324                        || item.upstreamActivity !=0
1325                        || item.downstreamActivity != 0) {
1326                    pw.print("    event="); pw.print(item.event);
1327                            pw.print(" upstreamActivity="); pw.print(item.upstreamActivity);
1328                            pw.print(" downstreamActivity="); pw.println(item.downstreamActivity);
1329                }
1330                if (item.mesg != null
1331                        && !SyncStorageEngine.MESG_SUCCESS.equals(item.mesg)) {
1332                    pw.print("    mesg="); pw.println(item.mesg);
1333                }
1334            }
1335        }
1336    }
1337
1338    /**
1339     * A helper object to keep track of the time we have spent syncing since the last boot
1340     */
1341    private class SyncTimeTracker {
1342        /** True if a sync was in progress on the most recent call to update() */
1343        boolean mLastWasSyncing = false;
1344        /** Used to track when lastWasSyncing was last set */
1345        long mWhenSyncStarted = 0;
1346        /** The cumulative time we have spent syncing */
1347        private long mTimeSpentSyncing;
1348
1349        /** Call to let the tracker know that the sync state may have changed */
1350        public synchronized void update() {
1351            final boolean isSyncInProgress = mActiveSyncContext != null;
1352            if (isSyncInProgress == mLastWasSyncing) return;
1353            final long now = SystemClock.elapsedRealtime();
1354            if (isSyncInProgress) {
1355                mWhenSyncStarted = now;
1356            } else {
1357                mTimeSpentSyncing += now - mWhenSyncStarted;
1358            }
1359            mLastWasSyncing = isSyncInProgress;
1360        }
1361
1362        /** Get how long we have been syncing, in ms */
1363        public synchronized long timeSpentSyncing() {
1364            if (!mLastWasSyncing) return mTimeSpentSyncing;
1365
1366            final long now = SystemClock.elapsedRealtime();
1367            return mTimeSpentSyncing + (now - mWhenSyncStarted);
1368        }
1369    }
1370
1371    class ServiceConnectionData {
1372        public final ActiveSyncContext activeSyncContext;
1373        public final ISyncAdapter syncAdapter;
1374        ServiceConnectionData(ActiveSyncContext activeSyncContext, ISyncAdapter syncAdapter) {
1375            this.activeSyncContext = activeSyncContext;
1376            this.syncAdapter = syncAdapter;
1377        }
1378    }
1379
1380    /**
1381     * Handles SyncOperation Messages that are posted to the associated
1382     * HandlerThread.
1383     */
1384    class SyncHandler extends Handler {
1385        // Messages that can be sent on mHandler
1386        private static final int MESSAGE_SYNC_FINISHED = 1;
1387        private static final int MESSAGE_SYNC_ALARM = 2;
1388        private static final int MESSAGE_CHECK_ALARMS = 3;
1389        private static final int MESSAGE_SERVICE_CONNECTED = 4;
1390        private static final int MESSAGE_SERVICE_DISCONNECTED = 5;
1391
1392        public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo();
1393        private Long mAlarmScheduleTime = null;
1394        public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker();
1395
1396        // used to track if we have installed the error notification so that we don't reinstall
1397        // it if sync is still failing
1398        private boolean mErrorNotificationInstalled = false;
1399        private volatile CountDownLatch mReadyToRunLatch = new CountDownLatch(1);
1400
1401        public void onBootCompleted() {
1402            mBootCompleted = true;
1403            if (mReadyToRunLatch != null) {
1404                mReadyToRunLatch.countDown();
1405            }
1406        }
1407
1408        private void waitUntilReadyToRun() {
1409            CountDownLatch latch = mReadyToRunLatch;
1410            if (latch != null) {
1411                while (true) {
1412                    try {
1413                        latch.await();
1414                        mReadyToRunLatch = null;
1415                        return;
1416                    } catch (InterruptedException e) {
1417                        Thread.currentThread().interrupt();
1418                    }
1419                }
1420            }
1421        }
1422        /**
1423         * Used to keep track of whether a sync notification is active and who it is for.
1424         */
1425        class SyncNotificationInfo {
1426            // only valid if isActive is true
1427            public Account account;
1428
1429            // only valid if isActive is true
1430            public String authority;
1431
1432            // true iff the notification manager has been asked to send the notification
1433            public boolean isActive = false;
1434
1435            // Set when we transition from not running a sync to running a sync, and cleared on
1436            // the opposite transition.
1437            public Long startTime = null;
1438
1439            public void toString(StringBuilder sb) {
1440                sb.append("account ").append(account)
1441                        .append(", authority ").append(authority)
1442                        .append(", isActive ").append(isActive)
1443                        .append(", startTime ").append(startTime);
1444            }
1445
1446            @Override
1447            public String toString() {
1448                StringBuilder sb = new StringBuilder();
1449                toString(sb);
1450                return sb.toString();
1451            }
1452        }
1453
1454        public SyncHandler(Looper looper) {
1455            super(looper);
1456        }
1457
1458        public void handleMessage(Message msg) {
1459            try {
1460                waitUntilReadyToRun();
1461                switch (msg.what) {
1462                    case SyncHandler.MESSAGE_SYNC_FINISHED:
1463                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
1464                            Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_FINISHED");
1465                        }
1466                        SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload)msg.obj;
1467                        if (mActiveSyncContext != payload.activeSyncContext) {
1468                            Log.d(TAG, "handleSyncHandlerMessage: sync context doesn't match, "
1469                                    + "dropping: mActiveSyncContext " + mActiveSyncContext
1470                                    + " != " + payload.activeSyncContext);
1471                            return;
1472                        }
1473                        runSyncFinishedOrCanceled(payload.syncResult);
1474
1475                        // since we are no longer syncing, check if it is time to start a new sync
1476                        runStateIdle();
1477                        break;
1478
1479                    case SyncHandler.MESSAGE_SERVICE_CONNECTED: {
1480                        ServiceConnectionData msgData = (ServiceConnectionData)msg.obj;
1481                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
1482                            Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CONNECTED: "
1483                                    + msgData.activeSyncContext
1484                                    + " active is " + mActiveSyncContext);
1485                        }
1486                        // check that this isn't an old message
1487                        if (mActiveSyncContext == msgData.activeSyncContext) {
1488                            runBoundToSyncAdapter(msgData.syncAdapter);
1489                        }
1490                        break;
1491                    }
1492
1493                    case SyncHandler.MESSAGE_SERVICE_DISCONNECTED: {
1494                        ServiceConnectionData msgData = (ServiceConnectionData)msg.obj;
1495                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
1496                            Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_DISCONNECTED: "
1497                                    + msgData.activeSyncContext
1498                                    + " active is " + mActiveSyncContext);
1499                        }
1500                        // check that this isn't an old message
1501                        if (mActiveSyncContext == msgData.activeSyncContext) {
1502                            // cancel the sync if we have a syncadapter, which means one is
1503                            // outstanding
1504                            if (mActiveSyncContext.mSyncAdapter != null) {
1505                                try {
1506                                    mActiveSyncContext.mSyncAdapter.cancelSync(mActiveSyncContext);
1507                                } catch (RemoteException e) {
1508                                    // we don't need to retry this in this case
1509                                }
1510                            }
1511
1512                            // pretend that the sync failed with an IOException,
1513                            // which is a soft error
1514                            SyncResult syncResult = new SyncResult();
1515                            syncResult.stats.numIoExceptions++;
1516                            runSyncFinishedOrCanceled(syncResult);
1517
1518                            // since we are no longer syncing, check if it is time to start a new
1519                            // sync
1520                            runStateIdle();
1521                        }
1522
1523                        break;
1524                    }
1525
1526                    case SyncHandler.MESSAGE_SYNC_ALARM: {
1527                        boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
1528                        if (isLoggable) {
1529                            Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_ALARM");
1530                        }
1531                        mAlarmScheduleTime = null;
1532                        try {
1533                            if (mActiveSyncContext != null) {
1534                                if (isLoggable) {
1535                                    Log.v(TAG, "handleSyncHandlerMessage: sync context is active");
1536                                }
1537                                runStateSyncing();
1538                            }
1539
1540                            // if the above call to runStateSyncing() resulted in the end of a sync,
1541                            // check if it is time to start a new sync
1542                            if (mActiveSyncContext == null) {
1543                                if (isLoggable) {
1544                                    Log.v(TAG, "handleSyncHandlerMessage: "
1545                                            + "sync context is not active");
1546                                }
1547                                runStateIdle();
1548                            }
1549                        } finally {
1550                            mHandleAlarmWakeLock.release();
1551                        }
1552                        break;
1553                    }
1554
1555                    case SyncHandler.MESSAGE_CHECK_ALARMS:
1556                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
1557                            Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_CHECK_ALARMS");
1558                        }
1559                        // we do all the work for this case in the finally block
1560                        break;
1561                }
1562            } finally {
1563                final boolean isSyncInProgress = mActiveSyncContext != null;
1564                if (!isSyncInProgress) {
1565                    mSyncWakeLock.release();
1566                }
1567                manageSyncNotification();
1568                manageErrorNotification();
1569                manageSyncAlarm();
1570                mSyncTimeTracker.update();
1571            }
1572        }
1573
1574        private void runStateSyncing() {
1575            // if the sync timeout has been reached then cancel it
1576
1577            ActiveSyncContext activeSyncContext = mActiveSyncContext;
1578
1579            final long now = SystemClock.elapsedRealtime();
1580            if (now > activeSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC) {
1581                SyncOperation nextSyncOperation;
1582                synchronized (mSyncQueue) {
1583                    nextSyncOperation = mSyncQueue.nextReadyToRun(now);
1584                }
1585                if (nextSyncOperation != null) {
1586                    Log.d(TAG, "canceling and rescheduling sync because it ran too long: "
1587                            + activeSyncContext.mSyncOperation);
1588                    scheduleSyncOperation(new SyncOperation(activeSyncContext.mSyncOperation));
1589                    sendSyncFinishedOrCanceledMessage(activeSyncContext,
1590                            null /* no result since this is a cancel */);
1591                } else {
1592                    activeSyncContext.mTimeoutStartTime = now + MAX_TIME_PER_SYNC;
1593                }
1594            }
1595
1596            // no need to schedule an alarm, as that will be done by our caller.
1597        }
1598
1599        private void runStateIdle() {
1600            boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
1601            if (isLoggable) Log.v(TAG, "runStateIdle");
1602
1603            // If we aren't ready to run (e.g. the data connection is down), get out.
1604            if (!mDataConnectionIsConnected) {
1605                if (isLoggable) {
1606                    Log.v(TAG, "runStateIdle: no data connection, skipping");
1607                }
1608                return;
1609            }
1610
1611            if (mStorageIsLow) {
1612                if (isLoggable) {
1613                    Log.v(TAG, "runStateIdle: memory low, skipping");
1614                }
1615                return;
1616            }
1617
1618            // If the accounts aren't known yet then we aren't ready to run. We will be kicked
1619            // when the account lookup request does complete.
1620            Account[] accounts = mAccounts;
1621            if (accounts == null) {
1622                if (isLoggable) {
1623                    Log.v(TAG, "runStateIdle: accounts not known, skipping");
1624                }
1625                return;
1626            }
1627
1628            // Otherwise consume SyncOperations from the head of the SyncQueue until one is
1629            // found that is runnable (not disabled, etc). If that one is ready to run then
1630            // start it, otherwise just get out.
1631            SyncOperation op;
1632            final boolean backgroundDataUsageAllowed =
1633                    getConnectivityManager().getBackgroundDataSetting();
1634            synchronized (mSyncQueue) {
1635                final long now = SystemClock.elapsedRealtime();
1636                while (true) {
1637                    op = mSyncQueue.nextReadyToRun(now);
1638                    if (op == null) {
1639                        if (isLoggable) {
1640                            Log.v(TAG, "runStateIdle: no more sync operations, returning");
1641                        }
1642                        return;
1643                    }
1644
1645                    // we are either going to run this sync or drop it so go ahead and remove it
1646                    // from the queue now
1647                    mSyncQueue.remove(op);
1648
1649                    // Sync is disabled, drop this operation.
1650                    if (!isSyncEnabled()) {
1651                        if (isLoggable) {
1652                            Log.v(TAG, "runStateIdle: sync disabled, dropping " + op);
1653                        }
1654                        continue;
1655                    }
1656
1657                    // skip the sync if the account of this operation no longer exists
1658                    if (!ArrayUtils.contains(accounts, op.account)) {
1659                        if (isLoggable) {
1660                            Log.v(TAG, "runStateIdle: account not present, dropping " + op);
1661                        }
1662                        continue;
1663                    }
1664
1665                    // skip the sync if it isn't manual and auto sync is disabled
1666                    final boolean manualSync =
1667                            op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
1668                    final boolean syncAutomatically =
1669                            mSyncStorageEngine.getSyncAutomatically(op.account, op.authority)
1670                                    && mSyncStorageEngine.getMasterSyncAutomatically();
1671                    if (!(manualSync || (backgroundDataUsageAllowed && syncAutomatically))) {
1672                        if (isLoggable) {
1673                            Log.v(TAG, "runStateIdle: sync of this operation is not allowed, "
1674                                    + "dropping " + op);
1675                        }
1676                        continue;
1677                    }
1678
1679                    if (mSyncStorageEngine.getIsSyncable(op.account, op.authority) <= 0) {
1680                        // if not syncable or if the syncable is unknown (< 0), don't allow
1681                        if (isLoggable) {
1682                            Log.v(TAG, "runStateIdle: sync of this operation is not allowed, "
1683                                    + "dropping " + op);
1684                        }
1685                        continue;
1686                    }
1687
1688                    // go ahead and try to sync this syncOperation
1689                    break;
1690                }
1691
1692                // We will do this sync. Run it outside of the synchronized block.
1693                if (isLoggable) {
1694                    Log.v(TAG, "runStateIdle: we are going to sync " + op);
1695                }
1696            }
1697
1698            // connect to the sync adapter
1699            SyncAdapterType syncAdapterType = SyncAdapterType.newKey(op.authority, op.account.type);
1700            RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
1701                    mSyncAdapters.getServiceInfo(syncAdapterType);
1702            if (syncAdapterInfo == null) {
1703                Log.d(TAG, "can't find a sync adapter for " + syncAdapterType);
1704                runStateIdle();
1705                return;
1706            }
1707
1708            ActiveSyncContext activeSyncContext =
1709                    new ActiveSyncContext(op, insertStartSyncEvent(op));
1710            mActiveSyncContext = activeSyncContext;
1711            if (Log.isLoggable(TAG, Log.VERBOSE)) {
1712                Log.v(TAG, "runStateIdle: setting mActiveSyncContext to " + mActiveSyncContext);
1713            }
1714            mSyncStorageEngine.setActiveSync(mActiveSyncContext);
1715            if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo)) {
1716                Log.e(TAG, "Bind attempt failed to " + syncAdapterInfo);
1717                mActiveSyncContext = null;
1718                mSyncStorageEngine.setActiveSync(mActiveSyncContext);
1719                runStateIdle();
1720                return;
1721            }
1722
1723            mSyncWakeLock.acquire();
1724            // no need to schedule an alarm, as that will be done by our caller.
1725
1726            // the next step will occur when we get either a timeout or a
1727            // MESSAGE_SERVICE_CONNECTED or MESSAGE_SERVICE_DISCONNECTED message
1728        }
1729
1730        private void runBoundToSyncAdapter(ISyncAdapter syncAdapter) {
1731            mActiveSyncContext.mSyncAdapter = syncAdapter;
1732            final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
1733            try {
1734                syncAdapter.startSync(mActiveSyncContext, syncOperation.authority,
1735                        syncOperation.account, syncOperation.extras);
1736            } catch (RemoteException remoteExc) {
1737                Log.d(TAG, "runStateIdle: caught a RemoteException, rescheduling", remoteExc);
1738                mActiveSyncContext.unBindFromSyncAdapter();
1739                mActiveSyncContext = null;
1740                mSyncStorageEngine.setActiveSync(mActiveSyncContext);
1741                increaseBackoffSetting(syncOperation);
1742                scheduleSyncOperation(new SyncOperation(syncOperation));
1743            } catch (RuntimeException exc) {
1744                mActiveSyncContext.unBindFromSyncAdapter();
1745                mActiveSyncContext = null;
1746                mSyncStorageEngine.setActiveSync(mActiveSyncContext);
1747                Log.e(TAG, "Caught RuntimeException while starting the sync " + syncOperation, exc);
1748            }
1749        }
1750
1751        private void runSyncFinishedOrCanceled(SyncResult syncResult) {
1752            boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
1753            final ActiveSyncContext activeSyncContext = mActiveSyncContext;
1754            mActiveSyncContext = null;
1755            mSyncStorageEngine.setActiveSync(mActiveSyncContext);
1756
1757            final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
1758
1759            final long elapsedTime = SystemClock.elapsedRealtime() - activeSyncContext.mStartTime;
1760
1761            String historyMessage;
1762            int downstreamActivity;
1763            int upstreamActivity;
1764            if (syncResult != null) {
1765                if (isLoggable) {
1766                    Log.v(TAG, "runSyncFinishedOrCanceled [finished]: "
1767                            + syncOperation + ", result " + syncResult);
1768                }
1769
1770                if (!syncResult.hasError()) {
1771                    historyMessage = SyncStorageEngine.MESG_SUCCESS;
1772                    // TODO: set these correctly when the SyncResult is extended to include it
1773                    downstreamActivity = 0;
1774                    upstreamActivity = 0;
1775                    clearBackoffSetting(syncOperation);
1776                } else {
1777                    Log.d(TAG, "failed sync operation " + syncOperation + ", " + syncResult);
1778                    // the operation failed so increase the backoff time
1779                    if (!syncResult.syncAlreadyInProgress) {
1780                        increaseBackoffSetting(syncOperation);
1781                    }
1782                    // reschedule the sync if so indicated by the syncResult
1783                    maybeRescheduleSync(syncResult, syncOperation);
1784                    historyMessage = Integer.toString(syncResultToErrorNumber(syncResult));
1785                    // TODO: set these correctly when the SyncResult is extended to include it
1786                    downstreamActivity = 0;
1787                    upstreamActivity = 0;
1788                }
1789
1790                setDelayUntilTime(syncOperation, syncResult.delayUntil);
1791            } else {
1792                if (isLoggable) {
1793                    Log.v(TAG, "runSyncFinishedOrCanceled [canceled]: " + syncOperation);
1794                }
1795                if (activeSyncContext.mSyncAdapter != null) {
1796                    try {
1797                        activeSyncContext.mSyncAdapter.cancelSync(activeSyncContext);
1798                    } catch (RemoteException e) {
1799                        // we don't need to retry this in this case
1800                    }
1801                }
1802                historyMessage = SyncStorageEngine.MESG_CANCELED;
1803                downstreamActivity = 0;
1804                upstreamActivity = 0;
1805            }
1806
1807            stopSyncEvent(activeSyncContext.mHistoryRowId, syncOperation, historyMessage,
1808                    upstreamActivity, downstreamActivity, elapsedTime);
1809
1810            activeSyncContext.unBindFromSyncAdapter();
1811
1812            if (syncResult != null && syncResult.tooManyDeletions) {
1813                installHandleTooManyDeletesNotification(syncOperation.account,
1814                        syncOperation.authority, syncResult.stats.numDeletes);
1815            } else {
1816                mNotificationMgr.cancel(
1817                        syncOperation.account.hashCode() ^ syncOperation.authority.hashCode());
1818            }
1819
1820            if (syncResult != null && syncResult.fullSyncRequested) {
1821                scheduleSyncOperation(new SyncOperation(syncOperation.account,
1822                        syncOperation.syncSource, syncOperation.authority, new Bundle(), 0));
1823            }
1824            // no need to schedule an alarm, as that will be done by our caller.
1825        }
1826
1827        /**
1828         * Convert the error-containing SyncResult into the Sync.History error number. Since
1829         * the SyncResult may indicate multiple errors at once, this method just returns the
1830         * most "serious" error.
1831         * @param syncResult the SyncResult from which to read
1832         * @return the most "serious" error set in the SyncResult
1833         * @throws IllegalStateException if the SyncResult does not indicate any errors.
1834         *   If SyncResult.error() is true then it is safe to call this.
1835         */
1836        private int syncResultToErrorNumber(SyncResult syncResult) {
1837            if (syncResult.syncAlreadyInProgress)
1838                return ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS;
1839            if (syncResult.stats.numAuthExceptions > 0)
1840                return ContentResolver.SYNC_ERROR_AUTHENTICATION;
1841            if (syncResult.stats.numIoExceptions > 0)
1842                return ContentResolver.SYNC_ERROR_IO;
1843            if (syncResult.stats.numParseExceptions > 0)
1844                return ContentResolver.SYNC_ERROR_PARSE;
1845            if (syncResult.stats.numConflictDetectedExceptions > 0)
1846                return ContentResolver.SYNC_ERROR_CONFLICT;
1847            if (syncResult.tooManyDeletions)
1848                return ContentResolver.SYNC_ERROR_TOO_MANY_DELETIONS;
1849            if (syncResult.tooManyRetries)
1850                return ContentResolver.SYNC_ERROR_TOO_MANY_RETRIES;
1851            if (syncResult.databaseError)
1852                return ContentResolver.SYNC_ERROR_INTERNAL;
1853            throw new IllegalStateException("we are not in an error state, " + syncResult);
1854        }
1855
1856        private void manageSyncNotification() {
1857            boolean shouldCancel;
1858            boolean shouldInstall;
1859
1860            if (mActiveSyncContext == null) {
1861                mSyncNotificationInfo.startTime = null;
1862
1863                // we aren't syncing. if the notification is active then remember that we need
1864                // to cancel it and then clear out the info
1865                shouldCancel = mSyncNotificationInfo.isActive;
1866                shouldInstall = false;
1867            } else {
1868                // we are syncing
1869                final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
1870
1871                final long now = SystemClock.elapsedRealtime();
1872                if (mSyncNotificationInfo.startTime == null) {
1873                    mSyncNotificationInfo.startTime = now;
1874                }
1875
1876                // cancel the notification if it is up and the authority or account is wrong
1877                shouldCancel = mSyncNotificationInfo.isActive &&
1878                        (!syncOperation.authority.equals(mSyncNotificationInfo.authority)
1879                        || !syncOperation.account.equals(mSyncNotificationInfo.account));
1880
1881                // there are four cases:
1882                // - the notification is up and there is no change: do nothing
1883                // - the notification is up but we should cancel since it is stale:
1884                //   need to install
1885                // - the notification is not up but it isn't time yet: don't install
1886                // - the notification is not up and it is time: need to install
1887
1888                if (mSyncNotificationInfo.isActive) {
1889                    shouldInstall = shouldCancel;
1890                } else {
1891                    final boolean timeToShowNotification =
1892                            now > mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY;
1893                    // show the notification immediately if this is a manual sync
1894                    final boolean manualSync = syncOperation.extras
1895                            .getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
1896                    shouldInstall = timeToShowNotification || manualSync;
1897                }
1898            }
1899
1900            if (shouldCancel && !shouldInstall) {
1901                mNeedSyncActiveNotification = false;
1902                sendSyncStateIntent();
1903                mSyncNotificationInfo.isActive = false;
1904            }
1905
1906            if (shouldInstall) {
1907                SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
1908                mNeedSyncActiveNotification = true;
1909                sendSyncStateIntent();
1910                mSyncNotificationInfo.isActive = true;
1911                mSyncNotificationInfo.account = syncOperation.account;
1912                mSyncNotificationInfo.authority = syncOperation.authority;
1913            }
1914        }
1915
1916        /**
1917         * Check if there were any long-lasting errors, if so install the error notification,
1918         * otherwise cancel the error notification.
1919         */
1920        private void manageErrorNotification() {
1921            //
1922            long when = mSyncStorageEngine.getInitialSyncFailureTime();
1923            if ((when > 0) && (when + ERROR_NOTIFICATION_DELAY_MS < System.currentTimeMillis())) {
1924                if (!mErrorNotificationInstalled) {
1925                    mNeedSyncErrorNotification = true;
1926                    sendSyncStateIntent();
1927                }
1928                mErrorNotificationInstalled = true;
1929            } else {
1930                if (mErrorNotificationInstalled) {
1931                    mNeedSyncErrorNotification = false;
1932                    sendSyncStateIntent();
1933                }
1934                mErrorNotificationInstalled = false;
1935            }
1936        }
1937
1938        private void manageSyncAlarm() {
1939            // in each of these cases the sync loop will be kicked, which will cause this
1940            // method to be called again
1941            if (!mDataConnectionIsConnected) return;
1942            if (mAccounts == null) return;
1943            if (mStorageIsLow) return;
1944
1945            final long now = SystemClock.elapsedRealtime();
1946
1947            // Compute the alarm fire time:
1948            // - not syncing: time of the next sync operation
1949            // - syncing, no notification: time from sync start to notification create time
1950            // - syncing, with notification: time till timeout of the active sync operation
1951            Long alarmTime;
1952            ActiveSyncContext activeSyncContext = mActiveSyncContext;
1953            if (activeSyncContext == null) {
1954                synchronized (mSyncQueue) {
1955                    alarmTime = mSyncQueue.nextRunTime(now);
1956                }
1957            } else {
1958                final long notificationTime =
1959                        mSyncHandler.mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY;
1960                final long timeoutTime =
1961                        mActiveSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC;
1962                if (mSyncHandler.mSyncNotificationInfo.isActive) {
1963                    alarmTime = timeoutTime;
1964                } else {
1965                    alarmTime = Math.min(notificationTime, timeoutTime);
1966                }
1967            }
1968
1969            // adjust the alarmTime so that we will wake up when it is time to
1970            // install the error notification
1971            if (!mErrorNotificationInstalled) {
1972                long when = mSyncStorageEngine.getInitialSyncFailureTime();
1973                if (when > 0) {
1974                    when += ERROR_NOTIFICATION_DELAY_MS;
1975                    // convert when fron absolute time to elapsed run time
1976                    long delay = when - System.currentTimeMillis();
1977                    when = now + delay;
1978                    alarmTime = alarmTime != null ? Math.min(alarmTime, when) : when;
1979                }
1980            }
1981
1982            // determine if we need to set or cancel the alarm
1983            boolean shouldSet = false;
1984            boolean shouldCancel = false;
1985            final boolean alarmIsActive = mAlarmScheduleTime != null;
1986            final boolean needAlarm = alarmTime != null;
1987            if (needAlarm) {
1988                if (!alarmIsActive || alarmTime < mAlarmScheduleTime) {
1989                    shouldSet = true;
1990                }
1991            } else {
1992                shouldCancel = alarmIsActive;
1993            }
1994
1995            // set or cancel the alarm as directed
1996            ensureAlarmService();
1997            if (shouldSet) {
1998                mAlarmScheduleTime = alarmTime;
1999                mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime,
2000                        mSyncAlarmIntent);
2001            } else if (shouldCancel) {
2002                mAlarmScheduleTime = null;
2003                mAlarmService.cancel(mSyncAlarmIntent);
2004            }
2005        }
2006
2007        private void sendSyncStateIntent() {
2008            Intent syncStateIntent = new Intent(Intent.ACTION_SYNC_STATE_CHANGED);
2009            syncStateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
2010            syncStateIntent.putExtra("active", mNeedSyncActiveNotification);
2011            syncStateIntent.putExtra("failing", mNeedSyncErrorNotification);
2012            mContext.sendBroadcast(syncStateIntent);
2013        }
2014
2015        private void installHandleTooManyDeletesNotification(Account account, String authority,
2016                long numDeletes) {
2017            if (mNotificationMgr == null) return;
2018
2019            final ProviderInfo providerInfo = mContext.getPackageManager().resolveContentProvider(
2020                    authority, 0 /* flags */);
2021            if (providerInfo == null) {
2022                return;
2023            }
2024            CharSequence authorityName = providerInfo.loadLabel(mContext.getPackageManager());
2025
2026            Intent clickIntent = new Intent();
2027            clickIntent.setClassName("com.android.providers.subscribedfeeds",
2028                    "com.android.settings.SyncActivityTooManyDeletes");
2029            clickIntent.putExtra("account", account);
2030            clickIntent.putExtra("authority", authority);
2031            clickIntent.putExtra("provider", authorityName.toString());
2032            clickIntent.putExtra("numDeletes", numDeletes);
2033
2034            if (!isActivityAvailable(clickIntent)) {
2035                Log.w(TAG, "No activity found to handle too many deletes.");
2036                return;
2037            }
2038
2039            final PendingIntent pendingIntent = PendingIntent
2040                    .getActivity(mContext, 0, clickIntent, PendingIntent.FLAG_CANCEL_CURRENT);
2041
2042            CharSequence tooManyDeletesDescFormat = mContext.getResources().getText(
2043                    R.string.contentServiceTooManyDeletesNotificationDesc);
2044
2045            Notification notification =
2046                new Notification(R.drawable.stat_notify_sync_error,
2047                        mContext.getString(R.string.contentServiceSync),
2048                        System.currentTimeMillis());
2049            notification.setLatestEventInfo(mContext,
2050                    mContext.getString(R.string.contentServiceSyncNotificationTitle),
2051                    String.format(tooManyDeletesDescFormat.toString(), authorityName),
2052                    pendingIntent);
2053            notification.flags |= Notification.FLAG_ONGOING_EVENT;
2054            mNotificationMgr.notify(account.hashCode() ^ authority.hashCode(), notification);
2055        }
2056
2057        /**
2058         * Checks whether an activity exists on the system image for the given intent.
2059         *
2060         * @param intent The intent for an activity.
2061         * @return Whether or not an activity exists.
2062         */
2063        private boolean isActivityAvailable(Intent intent) {
2064            PackageManager pm = mContext.getPackageManager();
2065            List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
2066            int listSize = list.size();
2067            for (int i = 0; i < listSize; i++) {
2068                ResolveInfo resolveInfo = list.get(i);
2069                if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
2070                        != 0) {
2071                    return true;
2072                }
2073            }
2074
2075            return false;
2076        }
2077
2078        public long insertStartSyncEvent(SyncOperation syncOperation) {
2079            final int source = syncOperation.syncSource;
2080            final long now = System.currentTimeMillis();
2081
2082            EventLog.writeEvent(2720, syncOperation.authority,
2083                                SyncStorageEngine.EVENT_START, source,
2084                                syncOperation.account.name.hashCode());
2085
2086            return mSyncStorageEngine.insertStartSyncEvent(
2087                    syncOperation.account, syncOperation.authority, now, source);
2088        }
2089
2090        public void stopSyncEvent(long rowId, SyncOperation syncOperation, String resultMessage,
2091                int upstreamActivity, int downstreamActivity, long elapsedTime) {
2092            EventLog.writeEvent(2720, syncOperation.authority,
2093                                SyncStorageEngine.EVENT_STOP, syncOperation.syncSource,
2094                                syncOperation.account.name.hashCode());
2095
2096            mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime, resultMessage,
2097                    downstreamActivity, upstreamActivity);
2098        }
2099    }
2100
2101}
2102