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