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