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