1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.content;
18
19import android.accounts.Account;
20import android.accounts.AccountAndUser;
21import android.app.backup.BackupManager;
22import android.content.ComponentName;
23import android.content.ContentResolver;
24import android.content.Context;
25import android.content.ISyncStatusObserver;
26import android.content.PeriodicSync;
27import android.content.SyncInfo;
28import android.content.SyncRequest;
29import android.content.SyncStatusInfo;
30import android.database.Cursor;
31import android.database.sqlite.SQLiteDatabase;
32import android.database.sqlite.SQLiteException;
33import android.database.sqlite.SQLiteQueryBuilder;
34import android.os.Bundle;
35import android.os.Environment;
36import android.os.Handler;
37import android.os.Message;
38import android.os.Parcel;
39import android.os.RemoteCallbackList;
40import android.os.RemoteException;
41import android.os.UserHandle;
42import android.util.*;
43
44import com.android.internal.annotations.VisibleForTesting;
45import com.android.internal.util.ArrayUtils;
46import com.android.internal.util.FastXmlSerializer;
47
48import org.xmlpull.v1.XmlPullParser;
49import org.xmlpull.v1.XmlPullParserException;
50import org.xmlpull.v1.XmlSerializer;
51
52import java.io.File;
53import java.io.FileInputStream;
54import java.io.FileOutputStream;
55import java.nio.charset.StandardCharsets;
56import java.util.ArrayList;
57import java.util.Calendar;
58import java.util.HashMap;
59import java.util.Iterator;
60import java.util.List;
61import java.util.Random;
62import java.util.TimeZone;
63
64/**
65 * Singleton that tracks the sync data and overall sync
66 * history on the device.
67 *
68 * @hide
69 */
70public class SyncStorageEngine extends Handler {
71
72    private static final String TAG = "SyncManager";
73    private static final String TAG_FILE = "SyncManagerFile";
74
75    private static final String XML_ATTR_NEXT_AUTHORITY_ID = "nextAuthorityId";
76    private static final String XML_ATTR_LISTEN_FOR_TICKLES = "listen-for-tickles";
77    private static final String XML_ATTR_SYNC_RANDOM_OFFSET = "offsetInSeconds";
78    private static final String XML_ATTR_ENABLED = "enabled";
79    private static final String XML_ATTR_USER = "user";
80    private static final String XML_TAG_LISTEN_FOR_TICKLES = "listenForTickles";
81
82    /** Default time for a periodic sync. */
83    private static final long DEFAULT_POLL_FREQUENCY_SECONDS = 60 * 60 * 24; // One day
84
85    /** Percentage of period that is flex by default, if no flexMillis is set. */
86    private static final double DEFAULT_FLEX_PERCENT_SYNC = 0.04;
87
88    /** Lower bound on sync time from which we assign a default flex time. */
89    private static final long DEFAULT_MIN_FLEX_ALLOWED_SECS = 5;
90
91    @VisibleForTesting
92    static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4;
93
94    /** Enum value for a sync start event. */
95    public static final int EVENT_START = 0;
96
97    /** Enum value for a sync stop event. */
98    public static final int EVENT_STOP = 1;
99
100    /** Enum value for a server-initiated sync. */
101    public static final int SOURCE_SERVER = 0;
102
103    /** Enum value for a local-initiated sync. */
104    public static final int SOURCE_LOCAL = 1;
105    /** Enum value for a poll-based sync (e.g., upon connection to network) */
106    public static final int SOURCE_POLL = 2;
107
108    /** Enum value for a user-initiated sync. */
109    public static final int SOURCE_USER = 3;
110
111    /** Enum value for a periodic sync. */
112    public static final int SOURCE_PERIODIC = 4;
113
114    public static final long NOT_IN_BACKOFF_MODE = -1;
115
116    // TODO: i18n -- grab these out of resources.
117    /** String names for the sync source types. */
118    public static final String[] SOURCES = { "SERVER",
119            "LOCAL",
120            "POLL",
121            "USER",
122            "PERIODIC",
123            "SERVICE"};
124
125    // The MESG column will contain one of these or one of the Error types.
126    public static final String MESG_SUCCESS = "success";
127    public static final String MESG_CANCELED = "canceled";
128
129    public static final int MAX_HISTORY = 100;
130
131    private static final int MSG_WRITE_STATUS = 1;
132    private static final long WRITE_STATUS_DELAY = 1000*60*10; // 10 minutes
133
134    private static final int MSG_WRITE_STATISTICS = 2;
135    private static final long WRITE_STATISTICS_DELAY = 1000*60*30; // 1/2 hour
136
137    private static final boolean SYNC_ENABLED_DEFAULT = false;
138
139    // the version of the accounts xml file format
140    private static final int ACCOUNTS_VERSION = 3;
141
142    private static HashMap<String, String> sAuthorityRenames;
143    private static PeriodicSyncAddedListener mPeriodicSyncAddedListener;
144
145    static {
146        sAuthorityRenames = new HashMap<String, String>();
147        sAuthorityRenames.put("contacts", "com.android.contacts");
148        sAuthorityRenames.put("calendar", "com.android.calendar");
149    }
150
151    static class AccountInfo {
152        final AccountAndUser accountAndUser;
153        final HashMap<String, AuthorityInfo> authorities =
154                new HashMap<String, AuthorityInfo>();
155
156        AccountInfo(AccountAndUser accountAndUser) {
157            this.accountAndUser = accountAndUser;
158        }
159    }
160
161    /**  Bare bones representation of a sync target. */
162    public static class EndPoint {
163        public final static EndPoint USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL =
164                new EndPoint(null, null, UserHandle.USER_ALL);
165        final Account account;
166        final int userId;
167        final String provider;
168
169        public EndPoint(Account account, String provider, int userId) {
170            this.account = account;
171            this.provider = provider;
172            this.userId = userId;
173        }
174
175        /**
176         * An Endpoint for a sync matches if it targets the same sync adapter for the same user.
177         *
178         * @param spec the Endpoint to match. If the spec has null fields, they indicate a wildcard
179         * and match any.
180         */
181        public boolean matchesSpec(EndPoint spec) {
182            if (userId != spec.userId
183                    && userId != UserHandle.USER_ALL
184                    && spec.userId != UserHandle.USER_ALL) {
185                return false;
186            }
187            boolean accountsMatch;
188            if (spec.account == null) {
189                accountsMatch = true;
190            } else {
191                accountsMatch = account.equals(spec.account);
192            }
193            boolean providersMatch;
194            if (spec.provider == null) {
195                providersMatch = true;
196            } else {
197                providersMatch = provider.equals(spec.provider);
198            }
199            return accountsMatch && providersMatch;
200        }
201
202        public String toString() {
203            StringBuilder sb = new StringBuilder();
204            sb.append(account == null ? "ALL ACCS" : account.name)
205                    .append("/")
206                    .append(provider == null ? "ALL PDRS" : provider);
207            sb.append(":u" + userId);
208            return sb.toString();
209        }
210    }
211
212    public static class AuthorityInfo {
213        // Legal values of getIsSyncable
214
215        /**
216         * The syncable state is undefined.
217         */
218        public static final int UNDEFINED = -2;
219
220        /**
221         * Default state for a newly installed adapter. An uninitialized adapter will receive an
222         * initialization sync which are governed by a different set of rules to that of regular
223         * syncs.
224         */
225        public static final int NOT_INITIALIZED = -1;
226        /**
227         * The adapter will not receive any syncs. This is behaviourally equivalent to
228         * setSyncAutomatically -> false. However setSyncAutomatically is surfaced to the user
229         * while this is generally meant to be controlled by the developer.
230         */
231        public static final int NOT_SYNCABLE = 0;
232        /**
233         * The adapter is initialized and functioning. This is the normal state for an adapter.
234         */
235        public static final int SYNCABLE = 1;
236        /**
237         * The adapter is syncable but still requires an initialization sync. For example an adapter
238         * than has been restored from a previous device will be in this state. Not meant for
239         * external use.
240         */
241        public static final int SYNCABLE_NOT_INITIALIZED = 2;
242
243        /**
244         * The adapter is syncable but does not have access to the synced account and needs a
245         * user access approval.
246         */
247        public static final int SYNCABLE_NO_ACCOUNT_ACCESS = 3;
248
249        final EndPoint target;
250        final int ident;
251        boolean enabled;
252        int syncable;
253        /** Time at which this sync will run, taking into account backoff. */
254        long backoffTime;
255        /** Amount of delay due to backoff. */
256        long backoffDelay;
257        /** Time offset to add to any requests coming to this target. */
258        long delayUntil;
259
260        final ArrayList<PeriodicSync> periodicSyncs;
261
262        /**
263         * Copy constructor for making deep-ish copies. Only the bundles stored
264         * in periodic syncs can make unexpected changes.
265         *
266         * @param toCopy AuthorityInfo to be copied.
267         */
268        AuthorityInfo(AuthorityInfo toCopy) {
269            target = toCopy.target;
270            ident = toCopy.ident;
271            enabled = toCopy.enabled;
272            syncable = toCopy.syncable;
273            backoffTime = toCopy.backoffTime;
274            backoffDelay = toCopy.backoffDelay;
275            delayUntil = toCopy.delayUntil;
276            periodicSyncs = new ArrayList<PeriodicSync>();
277            for (PeriodicSync sync : toCopy.periodicSyncs) {
278                // Still not a perfect copy, because we are just copying the mappings.
279                periodicSyncs.add(new PeriodicSync(sync));
280            }
281        }
282
283        AuthorityInfo(EndPoint info, int id) {
284            target = info;
285            ident = id;
286            enabled = SYNC_ENABLED_DEFAULT;
287            periodicSyncs = new ArrayList<PeriodicSync>();
288            defaultInitialisation();
289        }
290
291        private void defaultInitialisation() {
292            syncable = NOT_INITIALIZED; // default to "unknown"
293            backoffTime = -1; // if < 0 then we aren't in backoff mode
294            backoffDelay = -1; // if < 0 then we aren't in backoff mode
295
296            if (mPeriodicSyncAddedListener != null) {
297                mPeriodicSyncAddedListener.onPeriodicSyncAdded(target, new Bundle(),
298                        DEFAULT_POLL_FREQUENCY_SECONDS,
299                        calculateDefaultFlexTime(DEFAULT_POLL_FREQUENCY_SECONDS));
300            }
301        }
302
303        @Override
304        public String toString() {
305            return target + ", enabled=" + enabled + ", syncable=" + syncable + ", backoff="
306                    + backoffTime + ", delay=" + delayUntil;
307        }
308    }
309
310    public static class SyncHistoryItem {
311        int authorityId;
312        int historyId;
313        long eventTime;
314        long elapsedTime;
315        int source;
316        int event;
317        long upstreamActivity;
318        long downstreamActivity;
319        String mesg;
320        boolean initialization;
321        Bundle extras;
322        int reason;
323    }
324
325    public static class DayStats {
326        public final int day;
327        public int successCount;
328        public long successTime;
329        public int failureCount;
330        public long failureTime;
331
332        public DayStats(int day) {
333            this.day = day;
334        }
335    }
336
337    interface OnSyncRequestListener {
338
339        /** Called when a sync is needed on an account(s) due to some change in state. */
340        public void onSyncRequest(EndPoint info, int reason, Bundle extras);
341    }
342
343    interface PeriodicSyncAddedListener {
344        /** Called when a periodic sync is added. */
345        void onPeriodicSyncAdded(EndPoint target, Bundle extras, long pollFrequency, long flex);
346    }
347
348    interface OnAuthorityRemovedListener {
349        /** Called when an authority is removed. */
350        void onAuthorityRemoved(EndPoint removedAuthority);
351    }
352
353    // Primary list of all syncable authorities.  Also our global lock.
354    private final SparseArray<AuthorityInfo> mAuthorities =
355            new SparseArray<AuthorityInfo>();
356
357    private final HashMap<AccountAndUser, AccountInfo> mAccounts
358            = new HashMap<AccountAndUser, AccountInfo>();
359
360    private final SparseArray<ArrayList<SyncInfo>> mCurrentSyncs
361            = new SparseArray<ArrayList<SyncInfo>>();
362
363    private final SparseArray<SyncStatusInfo> mSyncStatus =
364            new SparseArray<SyncStatusInfo>();
365
366    private final ArrayList<SyncHistoryItem> mSyncHistory =
367            new ArrayList<SyncHistoryItem>();
368
369    private final RemoteCallbackList<ISyncStatusObserver> mChangeListeners
370            = new RemoteCallbackList<ISyncStatusObserver>();
371
372    /** Reverse mapping for component name -> <userid -> target id>. */
373    private final ArrayMap<ComponentName, SparseArray<AuthorityInfo>> mServices =
374            new ArrayMap<ComponentName, SparseArray<AuthorityInfo>>();
375
376    private int mNextAuthorityId = 0;
377
378    // We keep 4 weeks of stats.
379    private final DayStats[] mDayStats = new DayStats[7*4];
380    private final Calendar mCal;
381    private int mYear;
382    private int mYearInDays;
383
384    private final Context mContext;
385
386    private static volatile SyncStorageEngine sSyncStorageEngine = null;
387
388    private int mSyncRandomOffset;
389
390    /**
391     * This file contains the core engine state: all accounts and the
392     * settings for them.  It must never be lost, and should be changed
393     * infrequently, so it is stored as an XML file.
394     */
395    private final AtomicFile mAccountInfoFile;
396
397    /**
398     * This file contains the current sync status.  We would like to retain
399     * it across boots, but its loss is not the end of the world, so we store
400     * this information as binary data.
401     */
402    private final AtomicFile mStatusFile;
403
404    /**
405     * This file contains sync statistics.  This is purely debugging information
406     * so is written infrequently and can be thrown away at any time.
407     */
408    private final AtomicFile mStatisticsFile;
409
410    private int mNextHistoryId = 0;
411    private SparseArray<Boolean> mMasterSyncAutomatically = new SparseArray<Boolean>();
412    private boolean mDefaultMasterSyncAutomatically;
413
414    private OnSyncRequestListener mSyncRequestListener;
415    private OnAuthorityRemovedListener mAuthorityRemovedListener;
416
417    private boolean mGrantSyncAdaptersAccountAccess;
418
419    private SyncStorageEngine(Context context, File dataDir) {
420        mContext = context;
421        sSyncStorageEngine = this;
422
423        mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
424
425        mDefaultMasterSyncAutomatically = mContext.getResources().getBoolean(
426                com.android.internal.R.bool.config_syncstorageengine_masterSyncAutomatically);
427
428        File systemDir = new File(dataDir, "system");
429        File syncDir = new File(systemDir, "sync");
430        syncDir.mkdirs();
431
432        maybeDeleteLegacyPendingInfoLocked(syncDir);
433
434        mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
435        mStatusFile = new AtomicFile(new File(syncDir, "status.bin"));
436        mStatisticsFile = new AtomicFile(new File(syncDir, "stats.bin"));
437
438        readAccountInfoLocked();
439        readStatusLocked();
440        readStatisticsLocked();
441        readAndDeleteLegacyAccountInfoLocked();
442        writeAccountInfoLocked();
443        writeStatusLocked();
444        writeStatisticsLocked();
445    }
446
447    public static SyncStorageEngine newTestInstance(Context context) {
448        return new SyncStorageEngine(context, context.getFilesDir());
449    }
450
451    public static void init(Context context) {
452        if (sSyncStorageEngine != null) {
453            return;
454        }
455        File dataDir = Environment.getDataDirectory();
456        sSyncStorageEngine = new SyncStorageEngine(context, dataDir);
457    }
458
459    public static SyncStorageEngine getSingleton() {
460        if (sSyncStorageEngine == null) {
461            throw new IllegalStateException("not initialized");
462        }
463        return sSyncStorageEngine;
464    }
465
466    protected void setOnSyncRequestListener(OnSyncRequestListener listener) {
467        if (mSyncRequestListener == null) {
468            mSyncRequestListener = listener;
469        }
470    }
471
472    protected void setOnAuthorityRemovedListener(OnAuthorityRemovedListener listener) {
473        if (mAuthorityRemovedListener == null) {
474            mAuthorityRemovedListener = listener;
475        }
476    }
477
478    protected void setPeriodicSyncAddedListener(PeriodicSyncAddedListener listener) {
479        if (mPeriodicSyncAddedListener == null) {
480            mPeriodicSyncAddedListener = listener;
481        }
482    }
483
484    @Override public void handleMessage(Message msg) {
485        if (msg.what == MSG_WRITE_STATUS) {
486            synchronized (mAuthorities) {
487                writeStatusLocked();
488            }
489        } else if (msg.what == MSG_WRITE_STATISTICS) {
490            synchronized (mAuthorities) {
491                writeStatisticsLocked();
492            }
493        }
494    }
495
496    public int getSyncRandomOffset() {
497        return mSyncRandomOffset;
498    }
499
500    public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
501        synchronized (mAuthorities) {
502            mChangeListeners.register(callback, mask);
503        }
504    }
505
506    public void removeStatusChangeListener(ISyncStatusObserver callback) {
507        synchronized (mAuthorities) {
508            mChangeListeners.unregister(callback);
509        }
510    }
511
512    /**
513     * Figure out a reasonable flex time for cases where none is provided (old api calls).
514     * @param syncTimeSeconds requested sync time from now.
515     * @return amount of seconds before syncTimeSeconds that the sync can occur.
516     *      I.e.
517     *      earliest_sync_time = syncTimeSeconds - calculateDefaultFlexTime(syncTimeSeconds)
518     * The flex time is capped at a percentage of the {@link #DEFAULT_POLL_FREQUENCY_SECONDS}.
519     */
520    public static long calculateDefaultFlexTime(long syncTimeSeconds) {
521        if (syncTimeSeconds < DEFAULT_MIN_FLEX_ALLOWED_SECS) {
522            // Small enough sync request time that we don't add flex time - developer probably
523            // wants to wait for an operation to occur before syncing so we honour the
524            // request time.
525            return 0L;
526        } else if (syncTimeSeconds < DEFAULT_POLL_FREQUENCY_SECONDS) {
527            return (long) (syncTimeSeconds * DEFAULT_FLEX_PERCENT_SYNC);
528        } else {
529            // Large enough sync request time that we cap the flex time.
530            return (long) (DEFAULT_POLL_FREQUENCY_SECONDS * DEFAULT_FLEX_PERCENT_SYNC);
531        }
532    }
533
534    void reportChange(int which) {
535        ArrayList<ISyncStatusObserver> reports = null;
536        synchronized (mAuthorities) {
537            int i = mChangeListeners.beginBroadcast();
538            while (i > 0) {
539                i--;
540                Integer mask = (Integer)mChangeListeners.getBroadcastCookie(i);
541                if ((which & mask.intValue()) == 0) {
542                    continue;
543                }
544                if (reports == null) {
545                    reports = new ArrayList<ISyncStatusObserver>(i);
546                }
547                reports.add(mChangeListeners.getBroadcastItem(i));
548            }
549            mChangeListeners.finishBroadcast();
550        }
551
552        if (Log.isLoggable(TAG, Log.VERBOSE)) {
553            Slog.v(TAG, "reportChange " + which + " to: " + reports);
554        }
555
556        if (reports != null) {
557            int i = reports.size();
558            while (i > 0) {
559                i--;
560                try {
561                    reports.get(i).onStatusChanged(which);
562                } catch (RemoteException e) {
563                    // The remote callback list will take care of this for us.
564                }
565            }
566        }
567    }
568
569    public boolean getSyncAutomatically(Account account, int userId, String providerName) {
570        synchronized (mAuthorities) {
571            if (account != null) {
572                AuthorityInfo authority = getAuthorityLocked(
573                        new EndPoint(account, providerName, userId),
574                        "getSyncAutomatically");
575                return authority != null && authority.enabled;
576            }
577
578            int i = mAuthorities.size();
579            while (i > 0) {
580                i--;
581                AuthorityInfo authorityInfo = mAuthorities.valueAt(i);
582                if (authorityInfo.target.matchesSpec(new EndPoint(account, providerName, userId))
583                        && authorityInfo.enabled) {
584                    return true;
585                }
586            }
587            return false;
588        }
589    }
590
591    public void setSyncAutomatically(Account account, int userId, String providerName,
592                                     boolean sync) {
593        if (Log.isLoggable(TAG, Log.VERBOSE)) {
594            Slog.d(TAG, "setSyncAutomatically: " + /* account + */" provider " + providerName
595                    + ", user " + userId + " -> " + sync);
596        }
597        synchronized (mAuthorities) {
598            AuthorityInfo authority =
599                    getOrCreateAuthorityLocked(
600                            new EndPoint(account, providerName, userId),
601                            -1 /* ident */,
602                            false);
603            if (authority.enabled == sync) {
604                if (Log.isLoggable(TAG, Log.VERBOSE)) {
605                    Slog.d(TAG, "setSyncAutomatically: already set to " + sync + ", doing nothing");
606                }
607                return;
608            }
609            // If the adapter was syncable but missing its initialization sync, set it to
610            // uninitialized now. This is to give it a chance to run any one-time initialization
611            // logic.
612            if (sync && authority.syncable == AuthorityInfo.SYNCABLE_NOT_INITIALIZED) {
613                authority.syncable = AuthorityInfo.NOT_INITIALIZED;
614            }
615            authority.enabled = sync;
616            writeAccountInfoLocked();
617        }
618
619        if (sync) {
620            requestSync(account, userId, SyncOperation.REASON_SYNC_AUTO, providerName,
621                    new Bundle());
622        }
623        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
624        queueBackup();
625    }
626
627    public int getIsSyncable(Account account, int userId, String providerName) {
628        synchronized (mAuthorities) {
629            if (account != null) {
630                AuthorityInfo authority = getAuthorityLocked(
631                        new EndPoint(account, providerName, userId),
632                        "get authority syncable");
633                if (authority == null) {
634                    return AuthorityInfo.NOT_INITIALIZED;
635                }
636                return authority.syncable;
637            }
638
639            int i = mAuthorities.size();
640            while (i > 0) {
641                i--;
642                AuthorityInfo authorityInfo = mAuthorities.valueAt(i);
643                if (authorityInfo.target != null
644                        && authorityInfo.target.provider.equals(providerName)) {
645                    return authorityInfo.syncable;
646                }
647            }
648            return AuthorityInfo.NOT_INITIALIZED;
649        }
650    }
651
652    public void setIsSyncable(Account account, int userId, String providerName, int syncable) {
653        setSyncableStateForEndPoint(new EndPoint(account, providerName, userId), syncable);
654    }
655
656    /**
657     * An enabled sync service and a syncable provider's adapter both get resolved to the same
658     * persisted variable - namely the "syncable" attribute for an AuthorityInfo in accounts.xml.
659     * @param target target to set value for.
660     * @param syncable 0 indicates unsyncable, <0 unknown, >0 is active/syncable.
661     */
662    private void setSyncableStateForEndPoint(EndPoint target, int syncable) {
663        AuthorityInfo aInfo;
664        synchronized (mAuthorities) {
665            aInfo = getOrCreateAuthorityLocked(target, -1, false);
666            if (syncable < AuthorityInfo.NOT_INITIALIZED) {
667                syncable = AuthorityInfo.NOT_INITIALIZED;
668            }
669            if (Log.isLoggable(TAG, Log.VERBOSE)) {
670                Slog.d(TAG, "setIsSyncable: " + aInfo.toString() + " -> " + syncable);
671            }
672            if (aInfo.syncable == syncable) {
673                if (Log.isLoggable(TAG, Log.VERBOSE)) {
674                    Slog.d(TAG, "setIsSyncable: already set to " + syncable + ", doing nothing");
675                }
676                return;
677            }
678            aInfo.syncable = syncable;
679            writeAccountInfoLocked();
680        }
681        if (syncable == AuthorityInfo.SYNCABLE) {
682            requestSync(aInfo, SyncOperation.REASON_IS_SYNCABLE, new Bundle());
683        }
684        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
685    }
686
687    public Pair<Long, Long> getBackoff(EndPoint info) {
688        synchronized (mAuthorities) {
689            AuthorityInfo authority = getAuthorityLocked(info, "getBackoff");
690            if (authority != null) {
691                return Pair.create(authority.backoffTime, authority.backoffDelay);
692            }
693            return null;
694        }
695    }
696
697    /**
698     * Update the backoff for the given endpoint. The endpoint may be for a provider/account and
699     * the account or provider info be null, which signifies all accounts or providers.
700     */
701    public void setBackoff(EndPoint info, long nextSyncTime, long nextDelay) {
702        if (Log.isLoggable(TAG, Log.VERBOSE)) {
703            Slog.v(TAG, "setBackoff: " + info
704                    + " -> nextSyncTime " + nextSyncTime + ", nextDelay " + nextDelay);
705        }
706        boolean changed;
707        synchronized (mAuthorities) {
708            if (info.account == null || info.provider == null) {
709                // Do more work for a provider sync if the provided info has specified all
710                // accounts/providers.
711                changed = setBackoffLocked(
712                        info.account /* may be null */,
713                        info.userId,
714                        info.provider /* may be null */,
715                        nextSyncTime, nextDelay);
716            } else {
717                AuthorityInfo authorityInfo =
718                        getOrCreateAuthorityLocked(info, -1 /* ident */, true);
719                if (authorityInfo.backoffTime == nextSyncTime
720                        && authorityInfo.backoffDelay == nextDelay) {
721                    changed = false;
722                } else {
723                    authorityInfo.backoffTime = nextSyncTime;
724                    authorityInfo.backoffDelay = nextDelay;
725                    changed = true;
726                }
727            }
728        }
729        if (changed) {
730            reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
731        }
732    }
733
734    /**
735     * Either set backoff for a specific authority, or set backoff for all the
736     * accounts on a specific adapter/all adapters.
737     *
738     * @param account account for which to set backoff. Null to specify all accounts.
739     * @param userId id of the user making this request.
740     * @param providerName provider for which to set backoff. Null to specify all providers.
741     * @return true if a change occured.
742     */
743    private boolean setBackoffLocked(Account account, int userId, String providerName,
744                                     long nextSyncTime, long nextDelay) {
745        boolean changed = false;
746        for (AccountInfo accountInfo : mAccounts.values()) {
747            if (account != null && !account.equals(accountInfo.accountAndUser.account)
748                    && userId != accountInfo.accountAndUser.userId) {
749                continue;
750            }
751            for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) {
752                if (providerName != null
753                        && !providerName.equals(authorityInfo.target.provider)) {
754                    continue;
755                }
756                if (authorityInfo.backoffTime != nextSyncTime
757                        || authorityInfo.backoffDelay != nextDelay) {
758                    authorityInfo.backoffTime = nextSyncTime;
759                    authorityInfo.backoffDelay = nextDelay;
760                    changed = true;
761                }
762            }
763        }
764        return changed;
765    }
766
767    public void clearAllBackoffsLocked() {
768        boolean changed = false;
769        synchronized (mAuthorities) {
770            // Clear backoff for all sync adapters.
771            for (AccountInfo accountInfo : mAccounts.values()) {
772                for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) {
773                    if (authorityInfo.backoffTime != NOT_IN_BACKOFF_MODE
774                            || authorityInfo.backoffDelay != NOT_IN_BACKOFF_MODE) {
775                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
776                            Slog.v(TAG, "clearAllBackoffsLocked:"
777                                    + " authority:" + authorityInfo.target
778                                    + " account:" + accountInfo.accountAndUser.account.name
779                                    + " user:" + accountInfo.accountAndUser.userId
780                                    + " backoffTime was: " + authorityInfo.backoffTime
781                                    + " backoffDelay was: " + authorityInfo.backoffDelay);
782                        }
783                        authorityInfo.backoffTime = NOT_IN_BACKOFF_MODE;
784                        authorityInfo.backoffDelay = NOT_IN_BACKOFF_MODE;
785                        changed = true;
786                    }
787                }
788            }
789        }
790
791        if (changed) {
792            reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
793        }
794    }
795
796    public long getDelayUntilTime(EndPoint info) {
797        synchronized (mAuthorities) {
798            AuthorityInfo authority = getAuthorityLocked(info, "getDelayUntil");
799            if (authority == null) {
800                return 0;
801            }
802            return authority.delayUntil;
803        }
804    }
805
806    public void setDelayUntilTime(EndPoint info, long delayUntil) {
807        if (Log.isLoggable(TAG, Log.VERBOSE)) {
808            Slog.v(TAG, "setDelayUntil: " + info
809                    + " -> delayUntil " + delayUntil);
810        }
811        synchronized (mAuthorities) {
812            AuthorityInfo authority = getOrCreateAuthorityLocked(info, -1, true);
813            if (authority.delayUntil == delayUntil) {
814                return;
815            }
816            authority.delayUntil = delayUntil;
817        }
818        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
819    }
820
821    /**
822     * Restore all periodic syncs read from persisted files. Used to restore periodic syncs
823     * after an OS update.
824     */
825    boolean restoreAllPeriodicSyncs() {
826        if (mPeriodicSyncAddedListener == null) {
827            return false;
828        }
829        synchronized (mAuthorities) {
830            for (int i=0; i<mAuthorities.size(); i++) {
831                AuthorityInfo authority = mAuthorities.valueAt(i);
832                for (PeriodicSync periodicSync: authority.periodicSyncs) {
833                    mPeriodicSyncAddedListener.onPeriodicSyncAdded(authority.target,
834                            periodicSync.extras, periodicSync.period, periodicSync.flexTime);
835                }
836                authority.periodicSyncs.clear();
837            }
838            writeAccountInfoLocked();
839        }
840        return true;
841    }
842
843    public void setMasterSyncAutomatically(boolean flag, int userId) {
844        synchronized (mAuthorities) {
845            Boolean auto = mMasterSyncAutomatically.get(userId);
846            if (auto != null && auto.equals(flag)) {
847                return;
848            }
849            mMasterSyncAutomatically.put(userId, flag);
850            writeAccountInfoLocked();
851        }
852        if (flag) {
853            requestSync(null, userId, SyncOperation.REASON_MASTER_SYNC_AUTO, null,
854                    new Bundle());
855        }
856        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
857        mContext.sendBroadcast(ContentResolver.ACTION_SYNC_CONN_STATUS_CHANGED);
858        queueBackup();
859    }
860
861    public boolean getMasterSyncAutomatically(int userId) {
862        synchronized (mAuthorities) {
863            Boolean auto = mMasterSyncAutomatically.get(userId);
864            return auto == null ? mDefaultMasterSyncAutomatically : auto;
865        }
866    }
867
868    public AuthorityInfo getAuthority(int authorityId) {
869        synchronized (mAuthorities) {
870            return mAuthorities.get(authorityId);
871        }
872    }
873
874    /**
875     * Returns true if there is currently a sync operation being actively processed for the given
876     * target.
877     */
878    public boolean isSyncActive(EndPoint info) {
879        synchronized (mAuthorities) {
880            for (SyncInfo syncInfo : getCurrentSyncs(info.userId)) {
881                AuthorityInfo ainfo = getAuthority(syncInfo.authorityId);
882                if (ainfo != null && ainfo.target.matchesSpec(info)) {
883                    return true;
884                }
885            }
886        }
887        return false;
888    }
889
890    public void markPending(EndPoint info, boolean pendingValue) {
891        synchronized (mAuthorities) {
892            AuthorityInfo authority = getOrCreateAuthorityLocked(info,
893                    -1 /* desired identifier */,
894                    true /* write accounts to storage */);
895            if (authority == null) {
896                return;
897            }
898            SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
899            status.pending = pendingValue;
900        }
901        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
902    }
903
904    /**
905     * Called when the set of account has changed, given the new array of
906     * active accounts.
907     */
908    public void doDatabaseCleanup(Account[] accounts, int userId) {
909        synchronized (mAuthorities) {
910            if (Log.isLoggable(TAG, Log.VERBOSE)) {
911                Slog.v(TAG, "Updating for new accounts...");
912            }
913            SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>();
914            Iterator<AccountInfo> accIt = mAccounts.values().iterator();
915            while (accIt.hasNext()) {
916                AccountInfo acc = accIt.next();
917                if (!ArrayUtils.contains(accounts, acc.accountAndUser.account)
918                        && acc.accountAndUser.userId == userId) {
919                    // This account no longer exists...
920                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
921                        Slog.v(TAG, "Account removed: " + acc.accountAndUser);
922                    }
923                    for (AuthorityInfo auth : acc.authorities.values()) {
924                        removing.put(auth.ident, auth);
925                    }
926                    accIt.remove();
927                }
928            }
929
930            // Clean out all data structures.
931            int i = removing.size();
932            if (i > 0) {
933                while (i > 0) {
934                    i--;
935                    int ident = removing.keyAt(i);
936                    AuthorityInfo auth = removing.valueAt(i);
937                    if (mAuthorityRemovedListener != null) {
938                        mAuthorityRemovedListener.onAuthorityRemoved(auth.target);
939                    }
940                    mAuthorities.remove(ident);
941                    int j = mSyncStatus.size();
942                    while (j > 0) {
943                        j--;
944                        if (mSyncStatus.keyAt(j) == ident) {
945                            mSyncStatus.remove(mSyncStatus.keyAt(j));
946                        }
947                    }
948                    j = mSyncHistory.size();
949                    while (j > 0) {
950                        j--;
951                        if (mSyncHistory.get(j).authorityId == ident) {
952                            mSyncHistory.remove(j);
953                        }
954                    }
955                }
956                writeAccountInfoLocked();
957                writeStatusLocked();
958                writeStatisticsLocked();
959            }
960        }
961    }
962
963    /**
964     * Called when a sync is starting. Supply a valid ActiveSyncContext with information
965     * about the sync.
966     */
967    public SyncInfo addActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
968        final SyncInfo syncInfo;
969        synchronized (mAuthorities) {
970            if (Log.isLoggable(TAG, Log.VERBOSE)) {
971                Slog.v(TAG, "setActiveSync: account="
972                        + " auth=" + activeSyncContext.mSyncOperation.target
973                        + " src=" + activeSyncContext.mSyncOperation.syncSource
974                        + " extras=" + activeSyncContext.mSyncOperation.extras);
975            }
976            final EndPoint info = activeSyncContext.mSyncOperation.target;
977            AuthorityInfo authorityInfo = getOrCreateAuthorityLocked(
978                    info,
979                    -1 /* assign a new identifier if creating a new target */,
980                    true /* write to storage if this results in a change */);
981            syncInfo = new SyncInfo(
982                    authorityInfo.ident,
983                    authorityInfo.target.account,
984                    authorityInfo.target.provider,
985                    activeSyncContext.mStartTime);
986            getCurrentSyncs(authorityInfo.target.userId).add(syncInfo);
987        }
988        reportActiveChange();
989        return syncInfo;
990    }
991
992    /**
993     * Called to indicate that a previously active sync is no longer active.
994     */
995    public void removeActiveSync(SyncInfo syncInfo, int userId) {
996        synchronized (mAuthorities) {
997            if (Log.isLoggable(TAG, Log.VERBOSE)) {
998                Slog.v(TAG, "removeActiveSync: account=" + syncInfo.account
999                        + " user=" + userId
1000                        + " auth=" + syncInfo.authority);
1001            }
1002            getCurrentSyncs(userId).remove(syncInfo);
1003        }
1004
1005        reportActiveChange();
1006    }
1007
1008    /**
1009     * To allow others to send active change reports, to poke clients.
1010     */
1011    public void reportActiveChange() {
1012        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE);
1013    }
1014
1015    /**
1016     * Note that sync has started for the given operation.
1017     */
1018    public long insertStartSyncEvent(SyncOperation op, long now) {
1019        long id;
1020        synchronized (mAuthorities) {
1021            if (Log.isLoggable(TAG, Log.VERBOSE)) {
1022                Slog.v(TAG, "insertStartSyncEvent: " + op);
1023            }
1024            AuthorityInfo authority = getAuthorityLocked(op.target, "insertStartSyncEvent");
1025            if (authority == null) {
1026                return -1;
1027            }
1028            SyncHistoryItem item = new SyncHistoryItem();
1029            item.initialization = op.isInitialization();
1030            item.authorityId = authority.ident;
1031            item.historyId = mNextHistoryId++;
1032            if (mNextHistoryId < 0) mNextHistoryId = 0;
1033            item.eventTime = now;
1034            item.source = op.syncSource;
1035            item.reason = op.reason;
1036            item.extras = op.extras;
1037            item.event = EVENT_START;
1038            mSyncHistory.add(0, item);
1039            while (mSyncHistory.size() > MAX_HISTORY) {
1040                mSyncHistory.remove(mSyncHistory.size()-1);
1041            }
1042            id = item.historyId;
1043            if (Log.isLoggable(TAG, Log.VERBOSE)) Slog.v(TAG, "returning historyId " + id);
1044        }
1045
1046        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
1047        return id;
1048    }
1049
1050    public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage,
1051                              long downstreamActivity, long upstreamActivity) {
1052        synchronized (mAuthorities) {
1053            if (Log.isLoggable(TAG, Log.VERBOSE)) {
1054                Slog.v(TAG, "stopSyncEvent: historyId=" + historyId);
1055            }
1056            SyncHistoryItem item = null;
1057            int i = mSyncHistory.size();
1058            while (i > 0) {
1059                i--;
1060                item = mSyncHistory.get(i);
1061                if (item.historyId == historyId) {
1062                    break;
1063                }
1064                item = null;
1065            }
1066
1067            if (item == null) {
1068                Slog.w(TAG, "stopSyncEvent: no history for id " + historyId);
1069                return;
1070            }
1071
1072            item.elapsedTime = elapsedTime;
1073            item.event = EVENT_STOP;
1074            item.mesg = resultMessage;
1075            item.downstreamActivity = downstreamActivity;
1076            item.upstreamActivity = upstreamActivity;
1077
1078            SyncStatusInfo status = getOrCreateSyncStatusLocked(item.authorityId);
1079
1080            status.numSyncs++;
1081            status.totalElapsedTime += elapsedTime;
1082            switch (item.source) {
1083                case SOURCE_LOCAL:
1084                    status.numSourceLocal++;
1085                    break;
1086                case SOURCE_POLL:
1087                    status.numSourcePoll++;
1088                    break;
1089                case SOURCE_USER:
1090                    status.numSourceUser++;
1091                    break;
1092                case SOURCE_SERVER:
1093                    status.numSourceServer++;
1094                    break;
1095                case SOURCE_PERIODIC:
1096                    status.numSourcePeriodic++;
1097                    break;
1098            }
1099
1100            boolean writeStatisticsNow = false;
1101            int day = getCurrentDayLocked();
1102            if (mDayStats[0] == null) {
1103                mDayStats[0] = new DayStats(day);
1104            } else if (day != mDayStats[0].day) {
1105                System.arraycopy(mDayStats, 0, mDayStats, 1, mDayStats.length-1);
1106                mDayStats[0] = new DayStats(day);
1107                writeStatisticsNow = true;
1108            } else if (mDayStats[0] == null) {
1109            }
1110            final DayStats ds = mDayStats[0];
1111
1112            final long lastSyncTime = (item.eventTime + elapsedTime);
1113            boolean writeStatusNow = false;
1114            if (MESG_SUCCESS.equals(resultMessage)) {
1115                // - if successful, update the successful columns
1116                if (status.lastSuccessTime == 0 || status.lastFailureTime != 0) {
1117                    writeStatusNow = true;
1118                }
1119                status.lastSuccessTime = lastSyncTime;
1120                status.lastSuccessSource = item.source;
1121                status.lastFailureTime = 0;
1122                status.lastFailureSource = -1;
1123                status.lastFailureMesg = null;
1124                status.initialFailureTime = 0;
1125                ds.successCount++;
1126                ds.successTime += elapsedTime;
1127            } else if (!MESG_CANCELED.equals(resultMessage)) {
1128                if (status.lastFailureTime == 0) {
1129                    writeStatusNow = true;
1130                }
1131                status.lastFailureTime = lastSyncTime;
1132                status.lastFailureSource = item.source;
1133                status.lastFailureMesg = resultMessage;
1134                if (status.initialFailureTime == 0) {
1135                    status.initialFailureTime = lastSyncTime;
1136                }
1137                ds.failureCount++;
1138                ds.failureTime += elapsedTime;
1139            }
1140
1141            if (writeStatusNow) {
1142                writeStatusLocked();
1143            } else if (!hasMessages(MSG_WRITE_STATUS)) {
1144                sendMessageDelayed(obtainMessage(MSG_WRITE_STATUS),
1145                        WRITE_STATUS_DELAY);
1146            }
1147            if (writeStatisticsNow) {
1148                writeStatisticsLocked();
1149            } else if (!hasMessages(MSG_WRITE_STATISTICS)) {
1150                sendMessageDelayed(obtainMessage(MSG_WRITE_STATISTICS),
1151                        WRITE_STATISTICS_DELAY);
1152            }
1153        }
1154
1155        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
1156    }
1157
1158    /**
1159     * Return a list of the currently active syncs. Note that the returned
1160     * items are the real, live active sync objects, so be careful what you do
1161     * with it.
1162     */
1163    private List<SyncInfo> getCurrentSyncs(int userId) {
1164        synchronized (mAuthorities) {
1165            return getCurrentSyncsLocked(userId);
1166        }
1167    }
1168
1169    /**
1170     * @param userId Id of user to return current sync info.
1171     * @param canAccessAccounts Determines whether to redact Account information from the result.
1172     * @return a copy of the current syncs data structure. Will not return null.
1173     */
1174    public List<SyncInfo> getCurrentSyncsCopy(int userId, boolean canAccessAccounts) {
1175        synchronized (mAuthorities) {
1176            final List<SyncInfo> syncs = getCurrentSyncsLocked(userId);
1177            final List<SyncInfo> syncsCopy = new ArrayList<SyncInfo>();
1178            for (SyncInfo sync : syncs) {
1179                SyncInfo copy;
1180                if (!canAccessAccounts) {
1181                    copy = SyncInfo.createAccountRedacted(
1182                        sync.authorityId, sync.authority, sync.startTime);
1183                } else {
1184                    copy = new SyncInfo(sync);
1185                }
1186                syncsCopy.add(copy);
1187            }
1188            return syncsCopy;
1189        }
1190    }
1191
1192    private List<SyncInfo> getCurrentSyncsLocked(int userId) {
1193        ArrayList<SyncInfo> syncs = mCurrentSyncs.get(userId);
1194        if (syncs == null) {
1195            syncs = new ArrayList<SyncInfo>();
1196            mCurrentSyncs.put(userId, syncs);
1197        }
1198        return syncs;
1199    }
1200
1201    /**
1202     * Return a copy of the specified target with the corresponding sync status
1203     */
1204    public Pair<AuthorityInfo, SyncStatusInfo> getCopyOfAuthorityWithSyncStatus(EndPoint info) {
1205        synchronized (mAuthorities) {
1206            AuthorityInfo authorityInfo = getOrCreateAuthorityLocked(info,
1207                    -1 /* assign a new identifier if creating a new target */,
1208                    true /* write to storage if this results in a change */);
1209            return createCopyPairOfAuthorityWithSyncStatusLocked(authorityInfo);
1210        }
1211    }
1212
1213    /**
1214     * Returns the status that matches the target.
1215     *
1216     * @param info the endpoint target we are querying status info for.
1217     * @return the SyncStatusInfo for the endpoint.
1218     */
1219    public SyncStatusInfo getStatusByAuthority(EndPoint info) {
1220        if (info.account == null || info.provider == null) {
1221            return null;
1222        }
1223        synchronized (mAuthorities) {
1224            final int N = mSyncStatus.size();
1225            for (int i = 0; i < N; i++) {
1226                SyncStatusInfo cur = mSyncStatus.valueAt(i);
1227                AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
1228                if (ainfo != null
1229                        && ainfo.target.matchesSpec(info)) {
1230                    return cur;
1231                }
1232            }
1233            return null;
1234        }
1235    }
1236
1237    /** Return true if the pending status is true of any matching authorities. */
1238    public boolean isSyncPending(EndPoint info) {
1239        synchronized (mAuthorities) {
1240            final int N = mSyncStatus.size();
1241            for (int i = 0; i < N; i++) {
1242                SyncStatusInfo cur = mSyncStatus.valueAt(i);
1243                AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
1244                if (ainfo == null) {
1245                    continue;
1246                }
1247                if (!ainfo.target.matchesSpec(info)) {
1248                    continue;
1249                }
1250                if (cur.pending) {
1251                    return true;
1252                }
1253            }
1254            return false;
1255        }
1256    }
1257
1258    /**
1259     * Return an array of the current sync status for all authorities.  Note
1260     * that the objects inside the array are the real, live status objects,
1261     * so be careful what you do with them.
1262     */
1263    public ArrayList<SyncHistoryItem> getSyncHistory() {
1264        synchronized (mAuthorities) {
1265            final int N = mSyncHistory.size();
1266            ArrayList<SyncHistoryItem> items = new ArrayList<SyncHistoryItem>(N);
1267            for (int i=0; i<N; i++) {
1268                items.add(mSyncHistory.get(i));
1269            }
1270            return items;
1271        }
1272    }
1273
1274    /**
1275     * Return an array of the current per-day statistics.  Note
1276     * that the objects inside the array are the real, live status objects,
1277     * so be careful what you do with them.
1278     */
1279    public DayStats[] getDayStatistics() {
1280        synchronized (mAuthorities) {
1281            DayStats[] ds = new DayStats[mDayStats.length];
1282            System.arraycopy(mDayStats, 0, ds, 0, ds.length);
1283            return ds;
1284        }
1285    }
1286
1287    private Pair<AuthorityInfo, SyncStatusInfo> createCopyPairOfAuthorityWithSyncStatusLocked(
1288            AuthorityInfo authorityInfo) {
1289        SyncStatusInfo syncStatusInfo = getOrCreateSyncStatusLocked(authorityInfo.ident);
1290        return Pair.create(new AuthorityInfo(authorityInfo), new SyncStatusInfo(syncStatusInfo));
1291    }
1292
1293    private int getCurrentDayLocked() {
1294        mCal.setTimeInMillis(System.currentTimeMillis());
1295        final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR);
1296        if (mYear != mCal.get(Calendar.YEAR)) {
1297            mYear = mCal.get(Calendar.YEAR);
1298            mCal.clear();
1299            mCal.set(Calendar.YEAR, mYear);
1300            mYearInDays = (int)(mCal.getTimeInMillis()/86400000);
1301        }
1302        return dayOfYear + mYearInDays;
1303    }
1304
1305    /**
1306     * Retrieve a target's full info, returning null if one does not exist.
1307     *
1308     * @param info info of the target to look up.
1309     * @param tag If non-null, this will be used in a log message if the
1310     * requested target does not exist.
1311     */
1312    private AuthorityInfo getAuthorityLocked(EndPoint info, String tag) {
1313        AccountAndUser au = new AccountAndUser(info.account, info.userId);
1314        AccountInfo accountInfo = mAccounts.get(au);
1315        if (accountInfo == null) {
1316            if (tag != null) {
1317                if (Log.isLoggable(TAG, Log.VERBOSE)) {
1318                    Slog.v(TAG, tag + ": unknown account " + au);
1319                }
1320            }
1321            return null;
1322        }
1323        AuthorityInfo authority = accountInfo.authorities.get(info.provider);
1324        if (authority == null) {
1325            if (tag != null) {
1326                if (Log.isLoggable(TAG, Log.VERBOSE)) {
1327                    Slog.v(TAG, tag + ": unknown provider " + info.provider);
1328                }
1329            }
1330            return null;
1331        }
1332        return authority;
1333    }
1334
1335    /**
1336     * @param info info identifying target.
1337     * @param ident unique identifier for target. -1 for none.
1338     * @param doWrite if true, update the accounts.xml file on the disk.
1339     * @return the authority that corresponds to the provided sync target, creating it if none
1340     * exists.
1341     */
1342    private AuthorityInfo getOrCreateAuthorityLocked(EndPoint info, int ident, boolean doWrite) {
1343        AuthorityInfo authority = null;
1344        AccountAndUser au = new AccountAndUser(info.account, info.userId);
1345        AccountInfo account = mAccounts.get(au);
1346        if (account == null) {
1347            account = new AccountInfo(au);
1348            mAccounts.put(au, account);
1349        }
1350        authority = account.authorities.get(info.provider);
1351        if (authority == null) {
1352            authority = createAuthorityLocked(info, ident, doWrite);
1353            account.authorities.put(info.provider, authority);
1354        }
1355        return authority;
1356    }
1357
1358    private AuthorityInfo createAuthorityLocked(EndPoint info, int ident, boolean doWrite) {
1359        AuthorityInfo authority;
1360        if (ident < 0) {
1361            ident = mNextAuthorityId;
1362            mNextAuthorityId++;
1363            doWrite = true;
1364        }
1365        if (Log.isLoggable(TAG, Log.VERBOSE)) {
1366            Slog.v(TAG, "created a new AuthorityInfo for " + info);
1367        }
1368        authority = new AuthorityInfo(info, ident);
1369        mAuthorities.put(ident, authority);
1370        if (doWrite) {
1371            writeAccountInfoLocked();
1372        }
1373        return authority;
1374    }
1375
1376    public void removeAuthority(EndPoint info) {
1377        synchronized (mAuthorities) {
1378            removeAuthorityLocked(info.account, info.userId, info.provider, true /* doWrite */);
1379        }
1380    }
1381
1382
1383    /**
1384     * Remove an authority associated with a provider. Needs to be a standalone function for
1385     * backward compatibility.
1386     */
1387    private void removeAuthorityLocked(Account account, int userId, String authorityName,
1388                                       boolean doWrite) {
1389        AccountInfo accountInfo = mAccounts.get(new AccountAndUser(account, userId));
1390        if (accountInfo != null) {
1391            final AuthorityInfo authorityInfo = accountInfo.authorities.remove(authorityName);
1392            if (authorityInfo != null) {
1393                if (mAuthorityRemovedListener != null) {
1394                    mAuthorityRemovedListener.onAuthorityRemoved(authorityInfo.target);
1395                }
1396                mAuthorities.remove(authorityInfo.ident);
1397                if (doWrite) {
1398                    writeAccountInfoLocked();
1399                }
1400            }
1401        }
1402    }
1403
1404    private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) {
1405        SyncStatusInfo status = mSyncStatus.get(authorityId);
1406        if (status == null) {
1407            status = new SyncStatusInfo(authorityId);
1408            mSyncStatus.put(authorityId, status);
1409        }
1410        return status;
1411    }
1412
1413    public void writeAllState() {
1414        synchronized (mAuthorities) {
1415            // Account info is always written so no need to do it here.
1416            writeStatusLocked();
1417            writeStatisticsLocked();
1418        }
1419    }
1420
1421    public boolean shouldGrantSyncAdaptersAccountAccess() {
1422        return mGrantSyncAdaptersAccountAccess;
1423    }
1424
1425    /**
1426     * public for testing
1427     */
1428    public void clearAndReadState() {
1429        synchronized (mAuthorities) {
1430            mAuthorities.clear();
1431            mAccounts.clear();
1432            mServices.clear();
1433            mSyncStatus.clear();
1434            mSyncHistory.clear();
1435
1436            readAccountInfoLocked();
1437            readStatusLocked();
1438            readStatisticsLocked();
1439            readAndDeleteLegacyAccountInfoLocked();
1440            writeAccountInfoLocked();
1441            writeStatusLocked();
1442            writeStatisticsLocked();
1443        }
1444    }
1445
1446    /**
1447     * Read all account information back in to the initial engine state.
1448     */
1449    private void readAccountInfoLocked() {
1450        int highestAuthorityId = -1;
1451        FileInputStream fis = null;
1452        try {
1453            fis = mAccountInfoFile.openRead();
1454            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
1455                Slog.v(TAG_FILE, "Reading " + mAccountInfoFile.getBaseFile());
1456            }
1457            XmlPullParser parser = Xml.newPullParser();
1458            parser.setInput(fis, StandardCharsets.UTF_8.name());
1459            int eventType = parser.getEventType();
1460            while (eventType != XmlPullParser.START_TAG &&
1461                    eventType != XmlPullParser.END_DOCUMENT) {
1462                eventType = parser.next();
1463            }
1464            if (eventType == XmlPullParser.END_DOCUMENT) {
1465                Slog.i(TAG, "No initial accounts");
1466                return;
1467            }
1468
1469            String tagName = parser.getName();
1470            if ("accounts".equals(tagName)) {
1471                String listen = parser.getAttributeValue(null, XML_ATTR_LISTEN_FOR_TICKLES);
1472                String versionString = parser.getAttributeValue(null, "version");
1473                int version;
1474                try {
1475                    version = (versionString == null) ? 0 : Integer.parseInt(versionString);
1476                } catch (NumberFormatException e) {
1477                    version = 0;
1478                }
1479
1480                if (version < 3) {
1481                    mGrantSyncAdaptersAccountAccess = true;
1482                }
1483
1484                String nextIdString = parser.getAttributeValue(null, XML_ATTR_NEXT_AUTHORITY_ID);
1485                try {
1486                    int id = (nextIdString == null) ? 0 : Integer.parseInt(nextIdString);
1487                    mNextAuthorityId = Math.max(mNextAuthorityId, id);
1488                } catch (NumberFormatException e) {
1489                    // don't care
1490                }
1491                String offsetString = parser.getAttributeValue(null, XML_ATTR_SYNC_RANDOM_OFFSET);
1492                try {
1493                    mSyncRandomOffset = (offsetString == null) ? 0 : Integer.parseInt(offsetString);
1494                } catch (NumberFormatException e) {
1495                    mSyncRandomOffset = 0;
1496                }
1497                if (mSyncRandomOffset == 0) {
1498                    Random random = new Random(System.currentTimeMillis());
1499                    mSyncRandomOffset = random.nextInt(86400);
1500                }
1501                mMasterSyncAutomatically.put(0, listen == null || Boolean.parseBoolean(listen));
1502                eventType = parser.next();
1503                AuthorityInfo authority = null;
1504                PeriodicSync periodicSync = null;
1505                do {
1506                    if (eventType == XmlPullParser.START_TAG) {
1507                        tagName = parser.getName();
1508                        if (parser.getDepth() == 2) {
1509                            if ("authority".equals(tagName)) {
1510                                authority = parseAuthority(parser, version);
1511                                periodicSync = null;
1512                                if (authority != null) {
1513                                    if (authority.ident > highestAuthorityId) {
1514                                        highestAuthorityId = authority.ident;
1515                                    }
1516                                } else {
1517                                    EventLog.writeEvent(0x534e4554, "26513719", -1,
1518                                            "Malformed authority");
1519                                }
1520                            } else if (XML_TAG_LISTEN_FOR_TICKLES.equals(tagName)) {
1521                                parseListenForTickles(parser);
1522                            }
1523                        } else if (parser.getDepth() == 3) {
1524                            if ("periodicSync".equals(tagName) && authority != null) {
1525                                periodicSync = parsePeriodicSync(parser, authority);
1526                            }
1527                        } else if (parser.getDepth() == 4 && periodicSync != null) {
1528                            if ("extra".equals(tagName)) {
1529                                parseExtra(parser, periodicSync.extras);
1530                            }
1531                        }
1532                    }
1533                    eventType = parser.next();
1534                } while (eventType != XmlPullParser.END_DOCUMENT);
1535            }
1536        } catch (XmlPullParserException e) {
1537            Slog.w(TAG, "Error reading accounts", e);
1538            return;
1539        } catch (java.io.IOException e) {
1540            if (fis == null) Slog.i(TAG, "No initial accounts");
1541            else Slog.w(TAG, "Error reading accounts", e);
1542            return;
1543        } finally {
1544            mNextAuthorityId = Math.max(highestAuthorityId + 1, mNextAuthorityId);
1545            if (fis != null) {
1546                try {
1547                    fis.close();
1548                } catch (java.io.IOException e1) {
1549                }
1550            }
1551        }
1552
1553        maybeMigrateSettingsForRenamedAuthorities();
1554    }
1555
1556    /**
1557     * Ensure the old pending.bin is deleted, as it has been changed to pending.xml.
1558     * pending.xml was used starting in KLP.
1559     * @param syncDir directory where the sync files are located.
1560     */
1561    private void maybeDeleteLegacyPendingInfoLocked(File syncDir) {
1562        File file = new File(syncDir, "pending.bin");
1563        if (!file.exists()) {
1564            return;
1565        } else {
1566            file.delete();
1567        }
1568    }
1569
1570    /**
1571     * some authority names have changed. copy over their settings and delete the old ones
1572     * @return true if a change was made
1573     */
1574    private boolean maybeMigrateSettingsForRenamedAuthorities() {
1575        boolean writeNeeded = false;
1576
1577        ArrayList<AuthorityInfo> authoritiesToRemove = new ArrayList<AuthorityInfo>();
1578        final int N = mAuthorities.size();
1579        for (int i = 0; i < N; i++) {
1580            AuthorityInfo authority = mAuthorities.valueAt(i);
1581            // skip this authority if it isn't one of the renamed ones
1582            final String newAuthorityName = sAuthorityRenames.get(authority.target.provider);
1583            if (newAuthorityName == null) {
1584                continue;
1585            }
1586
1587            // remember this authority so we can remove it later. we can't remove it
1588            // now without messing up this loop iteration
1589            authoritiesToRemove.add(authority);
1590
1591            // this authority isn't enabled, no need to copy it to the new authority name since
1592            // the default is "disabled"
1593            if (!authority.enabled) {
1594                continue;
1595            }
1596
1597            // if we already have a record of this new authority then don't copy over the settings
1598            EndPoint newInfo =
1599                    new EndPoint(authority.target.account,
1600                            newAuthorityName,
1601                            authority.target.userId);
1602            if (getAuthorityLocked(newInfo, "cleanup") != null) {
1603                continue;
1604            }
1605
1606            AuthorityInfo newAuthority =
1607                    getOrCreateAuthorityLocked(newInfo, -1 /* ident */, false /* doWrite */);
1608            newAuthority.enabled = true;
1609            writeNeeded = true;
1610        }
1611
1612        for (AuthorityInfo authorityInfo : authoritiesToRemove) {
1613            removeAuthorityLocked(
1614                    authorityInfo.target.account,
1615                    authorityInfo.target.userId,
1616                    authorityInfo.target.provider,
1617                    false /* doWrite */);
1618            writeNeeded = true;
1619        }
1620
1621        return writeNeeded;
1622    }
1623
1624    private void parseListenForTickles(XmlPullParser parser) {
1625        String user = parser.getAttributeValue(null, XML_ATTR_USER);
1626        int userId = 0;
1627        try {
1628            userId = Integer.parseInt(user);
1629        } catch (NumberFormatException e) {
1630            Slog.e(TAG, "error parsing the user for listen-for-tickles", e);
1631        } catch (NullPointerException e) {
1632            Slog.e(TAG, "the user in listen-for-tickles is null", e);
1633        }
1634        String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED);
1635        boolean listen = enabled == null || Boolean.parseBoolean(enabled);
1636        mMasterSyncAutomatically.put(userId, listen);
1637    }
1638
1639    private AuthorityInfo parseAuthority(XmlPullParser parser, int version) {
1640        AuthorityInfo authority = null;
1641        int id = -1;
1642        try {
1643            id = Integer.parseInt(parser.getAttributeValue(null, "id"));
1644        } catch (NumberFormatException e) {
1645            Slog.e(TAG, "error parsing the id of the authority", e);
1646        } catch (NullPointerException e) {
1647            Slog.e(TAG, "the id of the authority is null", e);
1648        }
1649        if (id >= 0) {
1650            String authorityName = parser.getAttributeValue(null, "authority");
1651            String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED);
1652            String syncable = parser.getAttributeValue(null, "syncable");
1653            String accountName = parser.getAttributeValue(null, "account");
1654            String accountType = parser.getAttributeValue(null, "type");
1655            String user = parser.getAttributeValue(null, XML_ATTR_USER);
1656            String packageName = parser.getAttributeValue(null, "package");
1657            String className = parser.getAttributeValue(null, "class");
1658            int userId = user == null ? 0 : Integer.parseInt(user);
1659            if (accountType == null && packageName == null) {
1660                accountType = "com.google";
1661                syncable = String.valueOf(AuthorityInfo.NOT_INITIALIZED);
1662            }
1663            authority = mAuthorities.get(id);
1664            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
1665                Slog.v(TAG_FILE, "Adding authority:"
1666                        + " account=" + accountName
1667                        + " accountType=" + accountType
1668                        + " auth=" + authorityName
1669                        + " package=" + packageName
1670                        + " class=" + className
1671                        + " user=" + userId
1672                        + " enabled=" + enabled
1673                        + " syncable=" + syncable);
1674            }
1675            if (authority == null) {
1676                if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
1677                    Slog.v(TAG_FILE, "Creating authority entry");
1678                }
1679                EndPoint info = null;
1680                if (accountName != null && authorityName != null) {
1681                    info = new EndPoint(
1682                            new Account(accountName, accountType),
1683                            authorityName, userId);
1684                }
1685                if (info != null) {
1686                    authority = getOrCreateAuthorityLocked(info, id, false);
1687                    // If the version is 0 then we are upgrading from a file format that did not
1688                    // know about periodic syncs. In that case don't clear the list since we
1689                    // want the default, which is a daily periodic sync.
1690                    // Otherwise clear out this default list since we will populate it later with
1691                    // the periodic sync descriptions that are read from the configuration file.
1692                    if (version > 0) {
1693                        authority.periodicSyncs.clear();
1694                    }
1695                }
1696            }
1697            if (authority != null) {
1698                authority.enabled = enabled == null || Boolean.parseBoolean(enabled);
1699                try {
1700                    authority.syncable = (syncable == null) ?
1701                            AuthorityInfo.NOT_INITIALIZED : Integer.parseInt(syncable);
1702                } catch (NumberFormatException e) {
1703                    // On L we stored this as {"unknown", "true", "false"} so fall back to this
1704                    // format.
1705                    if ("unknown".equals(syncable)) {
1706                        authority.syncable = AuthorityInfo.NOT_INITIALIZED;
1707                    } else {
1708                        authority.syncable = Boolean.parseBoolean(syncable) ?
1709                                AuthorityInfo.SYNCABLE : AuthorityInfo.NOT_SYNCABLE;
1710                    }
1711
1712                }
1713            } else {
1714                Slog.w(TAG, "Failure adding authority: account="
1715                        + accountName + " auth=" + authorityName
1716                        + " enabled=" + enabled
1717                        + " syncable=" + syncable);
1718            }
1719        }
1720        return authority;
1721    }
1722
1723    /**
1724     * Parse a periodic sync from accounts.xml. Sets the bundle to be empty.
1725     */
1726    private PeriodicSync parsePeriodicSync(XmlPullParser parser, AuthorityInfo authorityInfo) {
1727        Bundle extras = new Bundle(); // Gets filled in later.
1728        String periodValue = parser.getAttributeValue(null, "period");
1729        String flexValue = parser.getAttributeValue(null, "flex");
1730        final long period;
1731        long flextime;
1732        try {
1733            period = Long.parseLong(periodValue);
1734        } catch (NumberFormatException e) {
1735            Slog.e(TAG, "error parsing the period of a periodic sync", e);
1736            return null;
1737        } catch (NullPointerException e) {
1738            Slog.e(TAG, "the period of a periodic sync is null", e);
1739            return null;
1740        }
1741        try {
1742            flextime = Long.parseLong(flexValue);
1743        } catch (NumberFormatException e) {
1744            flextime = calculateDefaultFlexTime(period);
1745            Slog.e(TAG, "Error formatting value parsed for periodic sync flex: " + flexValue
1746                    + ", using default: "
1747                    + flextime);
1748        } catch (NullPointerException expected) {
1749            flextime = calculateDefaultFlexTime(period);
1750            Slog.d(TAG, "No flex time specified for this sync, using a default. period: "
1751                    + period + " flex: " + flextime);
1752        }
1753        PeriodicSync periodicSync;
1754        periodicSync =
1755                new PeriodicSync(authorityInfo.target.account,
1756                        authorityInfo.target.provider,
1757                        extras,
1758                        period, flextime);
1759        authorityInfo.periodicSyncs.add(periodicSync);
1760        return periodicSync;
1761    }
1762
1763    private void parseExtra(XmlPullParser parser, Bundle extras) {
1764        String name = parser.getAttributeValue(null, "name");
1765        String type = parser.getAttributeValue(null, "type");
1766        String value1 = parser.getAttributeValue(null, "value1");
1767        String value2 = parser.getAttributeValue(null, "value2");
1768
1769        try {
1770            if ("long".equals(type)) {
1771                extras.putLong(name, Long.parseLong(value1));
1772            } else if ("integer".equals(type)) {
1773                extras.putInt(name, Integer.parseInt(value1));
1774            } else if ("double".equals(type)) {
1775                extras.putDouble(name, Double.parseDouble(value1));
1776            } else if ("float".equals(type)) {
1777                extras.putFloat(name, Float.parseFloat(value1));
1778            } else if ("boolean".equals(type)) {
1779                extras.putBoolean(name, Boolean.parseBoolean(value1));
1780            } else if ("string".equals(type)) {
1781                extras.putString(name, value1);
1782            } else if ("account".equals(type)) {
1783                extras.putParcelable(name, new Account(value1, value2));
1784            }
1785        } catch (NumberFormatException e) {
1786            Slog.e(TAG, "error parsing bundle value", e);
1787        } catch (NullPointerException e) {
1788            Slog.e(TAG, "error parsing bundle value", e);
1789        }
1790    }
1791
1792    /**
1793     * Write all account information to the account file.
1794     */
1795    private void writeAccountInfoLocked() {
1796        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
1797            Slog.v(TAG_FILE, "Writing new " + mAccountInfoFile.getBaseFile());
1798        }
1799        FileOutputStream fos = null;
1800
1801        try {
1802            fos = mAccountInfoFile.startWrite();
1803            XmlSerializer out = new FastXmlSerializer();
1804            out.setOutput(fos, StandardCharsets.UTF_8.name());
1805            out.startDocument(null, true);
1806            out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
1807
1808            out.startTag(null, "accounts");
1809            out.attribute(null, "version", Integer.toString(ACCOUNTS_VERSION));
1810            out.attribute(null, XML_ATTR_NEXT_AUTHORITY_ID, Integer.toString(mNextAuthorityId));
1811            out.attribute(null, XML_ATTR_SYNC_RANDOM_OFFSET, Integer.toString(mSyncRandomOffset));
1812
1813            // Write the Sync Automatically flags for each user
1814            final int M = mMasterSyncAutomatically.size();
1815            for (int m = 0; m < M; m++) {
1816                int userId = mMasterSyncAutomatically.keyAt(m);
1817                Boolean listen = mMasterSyncAutomatically.valueAt(m);
1818                out.startTag(null, XML_TAG_LISTEN_FOR_TICKLES);
1819                out.attribute(null, XML_ATTR_USER, Integer.toString(userId));
1820                out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(listen));
1821                out.endTag(null, XML_TAG_LISTEN_FOR_TICKLES);
1822            }
1823
1824            final int N = mAuthorities.size();
1825            for (int i = 0; i < N; i++) {
1826                AuthorityInfo authority = mAuthorities.valueAt(i);
1827                EndPoint info = authority.target;
1828                out.startTag(null, "authority");
1829                out.attribute(null, "id", Integer.toString(authority.ident));
1830                out.attribute(null, XML_ATTR_USER, Integer.toString(info.userId));
1831                out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(authority.enabled));
1832                out.attribute(null, "account", info.account.name);
1833                out.attribute(null, "type", info.account.type);
1834                out.attribute(null, "authority", info.provider);
1835                out.attribute(null, "syncable", Integer.toString(authority.syncable));
1836                out.endTag(null, "authority");
1837            }
1838            out.endTag(null, "accounts");
1839            out.endDocument();
1840            mAccountInfoFile.finishWrite(fos);
1841        } catch (java.io.IOException e1) {
1842            Slog.w(TAG, "Error writing accounts", e1);
1843            if (fos != null) {
1844                mAccountInfoFile.failWrite(fos);
1845            }
1846        }
1847    }
1848
1849    static int getIntColumn(Cursor c, String name) {
1850        return c.getInt(c.getColumnIndex(name));
1851    }
1852
1853    static long getLongColumn(Cursor c, String name) {
1854        return c.getLong(c.getColumnIndex(name));
1855    }
1856
1857    /**
1858     * Load sync engine state from the old syncmanager database, and then
1859     * erase it.  Note that we don't deal with pending operations, active
1860     * sync, or history.
1861     */
1862    private void readAndDeleteLegacyAccountInfoLocked() {
1863        // Look for old database to initialize from.
1864        File file = mContext.getDatabasePath("syncmanager.db");
1865        if (!file.exists()) {
1866            return;
1867        }
1868        String path = file.getPath();
1869        SQLiteDatabase db = null;
1870        try {
1871            db = SQLiteDatabase.openDatabase(path, null,
1872                    SQLiteDatabase.OPEN_READONLY);
1873        } catch (SQLiteException e) {
1874        }
1875
1876        if (db != null) {
1877            final boolean hasType = db.getVersion() >= 11;
1878
1879            // Copy in all of the status information, as well as accounts.
1880            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
1881                Slog.v(TAG_FILE, "Reading legacy sync accounts db");
1882            }
1883            SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
1884            qb.setTables("stats, status");
1885            HashMap<String,String> map = new HashMap<String,String>();
1886            map.put("_id", "status._id as _id");
1887            map.put("account", "stats.account as account");
1888            if (hasType) {
1889                map.put("account_type", "stats.account_type as account_type");
1890            }
1891            map.put("authority", "stats.authority as authority");
1892            map.put("totalElapsedTime", "totalElapsedTime");
1893            map.put("numSyncs", "numSyncs");
1894            map.put("numSourceLocal", "numSourceLocal");
1895            map.put("numSourcePoll", "numSourcePoll");
1896            map.put("numSourceServer", "numSourceServer");
1897            map.put("numSourceUser", "numSourceUser");
1898            map.put("lastSuccessSource", "lastSuccessSource");
1899            map.put("lastSuccessTime", "lastSuccessTime");
1900            map.put("lastFailureSource", "lastFailureSource");
1901            map.put("lastFailureTime", "lastFailureTime");
1902            map.put("lastFailureMesg", "lastFailureMesg");
1903            map.put("pending", "pending");
1904            qb.setProjectionMap(map);
1905            qb.appendWhere("stats._id = status.stats_id");
1906            Cursor c = qb.query(db, null, null, null, null, null, null);
1907            while (c.moveToNext()) {
1908                String accountName = c.getString(c.getColumnIndex("account"));
1909                String accountType = hasType
1910                        ? c.getString(c.getColumnIndex("account_type")) : null;
1911                if (accountType == null) {
1912                    accountType = "com.google";
1913                }
1914                String authorityName = c.getString(c.getColumnIndex("authority"));
1915                AuthorityInfo authority =
1916                        this.getOrCreateAuthorityLocked(
1917                                new EndPoint(new Account(accountName, accountType),
1918                                        authorityName,
1919                                        0 /* legacy is single-user */)
1920                                , -1,
1921                                false);
1922                if (authority != null) {
1923                    int i = mSyncStatus.size();
1924                    boolean found = false;
1925                    SyncStatusInfo st = null;
1926                    while (i > 0) {
1927                        i--;
1928                        st = mSyncStatus.valueAt(i);
1929                        if (st.authorityId == authority.ident) {
1930                            found = true;
1931                            break;
1932                        }
1933                    }
1934                    if (!found) {
1935                        st = new SyncStatusInfo(authority.ident);
1936                        mSyncStatus.put(authority.ident, st);
1937                    }
1938                    st.totalElapsedTime = getLongColumn(c, "totalElapsedTime");
1939                    st.numSyncs = getIntColumn(c, "numSyncs");
1940                    st.numSourceLocal = getIntColumn(c, "numSourceLocal");
1941                    st.numSourcePoll = getIntColumn(c, "numSourcePoll");
1942                    st.numSourceServer = getIntColumn(c, "numSourceServer");
1943                    st.numSourceUser = getIntColumn(c, "numSourceUser");
1944                    st.numSourcePeriodic = 0;
1945                    st.lastSuccessSource = getIntColumn(c, "lastSuccessSource");
1946                    st.lastSuccessTime = getLongColumn(c, "lastSuccessTime");
1947                    st.lastFailureSource = getIntColumn(c, "lastFailureSource");
1948                    st.lastFailureTime = getLongColumn(c, "lastFailureTime");
1949                    st.lastFailureMesg = c.getString(c.getColumnIndex("lastFailureMesg"));
1950                    st.pending = getIntColumn(c, "pending") != 0;
1951                }
1952            }
1953
1954            c.close();
1955
1956            // Retrieve the settings.
1957            qb = new SQLiteQueryBuilder();
1958            qb.setTables("settings");
1959            c = qb.query(db, null, null, null, null, null, null);
1960            while (c.moveToNext()) {
1961                String name = c.getString(c.getColumnIndex("name"));
1962                String value = c.getString(c.getColumnIndex("value"));
1963                if (name == null) continue;
1964                if (name.equals("listen_for_tickles")) {
1965                    setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value), 0);
1966                } else if (name.startsWith("sync_provider_")) {
1967                    String provider = name.substring("sync_provider_".length(),
1968                            name.length());
1969                    int i = mAuthorities.size();
1970                    while (i > 0) {
1971                        i--;
1972                        AuthorityInfo authority = mAuthorities.valueAt(i);
1973                        if (authority.target.provider.equals(provider)) {
1974                            authority.enabled = value == null || Boolean.parseBoolean(value);
1975                            authority.syncable = 1;
1976                        }
1977                    }
1978                }
1979            }
1980
1981            c.close();
1982
1983            db.close();
1984
1985            (new File(path)).delete();
1986        }
1987    }
1988
1989    public static final int STATUS_FILE_END = 0;
1990    public static final int STATUS_FILE_ITEM = 100;
1991
1992    /**
1993     * Read all sync status back in to the initial engine state.
1994     */
1995    private void readStatusLocked() {
1996        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
1997            Slog.v(TAG_FILE, "Reading " + mStatusFile.getBaseFile());
1998        }
1999        try {
2000            byte[] data = mStatusFile.readFully();
2001            Parcel in = Parcel.obtain();
2002            in.unmarshall(data, 0, data.length);
2003            in.setDataPosition(0);
2004            int token;
2005            while ((token=in.readInt()) != STATUS_FILE_END) {
2006                if (token == STATUS_FILE_ITEM) {
2007                    SyncStatusInfo status = new SyncStatusInfo(in);
2008                    if (mAuthorities.indexOfKey(status.authorityId) >= 0) {
2009                        status.pending = false;
2010                        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2011                            Slog.v(TAG_FILE, "Adding status for id " + status.authorityId);
2012                        }
2013                        mSyncStatus.put(status.authorityId, status);
2014                    }
2015                } else {
2016                    // Ooops.
2017                    Slog.w(TAG, "Unknown status token: " + token);
2018                    break;
2019                }
2020            }
2021        } catch (java.io.IOException e) {
2022            Slog.i(TAG, "No initial status");
2023        }
2024    }
2025
2026    /**
2027     * Write all sync status to the sync status file.
2028     */
2029    private void writeStatusLocked() {
2030        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2031            Slog.v(TAG_FILE, "Writing new " + mStatusFile.getBaseFile());
2032        }
2033
2034        // The file is being written, so we don't need to have a scheduled
2035        // write until the next change.
2036        removeMessages(MSG_WRITE_STATUS);
2037
2038        FileOutputStream fos = null;
2039        try {
2040            fos = mStatusFile.startWrite();
2041            Parcel out = Parcel.obtain();
2042            final int N = mSyncStatus.size();
2043            for (int i=0; i<N; i++) {
2044                SyncStatusInfo status = mSyncStatus.valueAt(i);
2045                out.writeInt(STATUS_FILE_ITEM);
2046                status.writeToParcel(out, 0);
2047            }
2048            out.writeInt(STATUS_FILE_END);
2049            fos.write(out.marshall());
2050            out.recycle();
2051
2052            mStatusFile.finishWrite(fos);
2053        } catch (java.io.IOException e1) {
2054            Slog.w(TAG, "Error writing status", e1);
2055            if (fos != null) {
2056                mStatusFile.failWrite(fos);
2057            }
2058        }
2059    }
2060
2061    private void requestSync(AuthorityInfo authorityInfo, int reason, Bundle extras) {
2062        if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID
2063                && mSyncRequestListener != null) {
2064            mSyncRequestListener.onSyncRequest(authorityInfo.target, reason, extras);
2065        } else {
2066            SyncRequest.Builder req =
2067                    new SyncRequest.Builder()
2068                            .syncOnce()
2069                            .setExtras(extras);
2070            req.setSyncAdapter(authorityInfo.target.account, authorityInfo.target.provider);
2071            ContentResolver.requestSync(req.build());
2072        }
2073    }
2074
2075    private void requestSync(Account account, int userId, int reason, String authority,
2076                             Bundle extras) {
2077        // If this is happening in the system process, then call the syncrequest listener
2078        // to make a request back to the SyncManager directly.
2079        // If this is probably a test instance, then call back through the ContentResolver
2080        // which will know which userId to apply based on the Binder id.
2081        if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID
2082                && mSyncRequestListener != null) {
2083            mSyncRequestListener.onSyncRequest(
2084                    new EndPoint(account, authority, userId),
2085                    reason,
2086                    extras);
2087        } else {
2088            ContentResolver.requestSync(account, authority, extras);
2089        }
2090    }
2091
2092    public static final int STATISTICS_FILE_END = 0;
2093    public static final int STATISTICS_FILE_ITEM_OLD = 100;
2094    public static final int STATISTICS_FILE_ITEM = 101;
2095
2096    /**
2097     * Read all sync statistics back in to the initial engine state.
2098     */
2099    private void readStatisticsLocked() {
2100        try {
2101            byte[] data = mStatisticsFile.readFully();
2102            Parcel in = Parcel.obtain();
2103            in.unmarshall(data, 0, data.length);
2104            in.setDataPosition(0);
2105            int token;
2106            int index = 0;
2107            while ((token=in.readInt()) != STATISTICS_FILE_END) {
2108                if (token == STATISTICS_FILE_ITEM
2109                        || token == STATISTICS_FILE_ITEM_OLD) {
2110                    int day = in.readInt();
2111                    if (token == STATISTICS_FILE_ITEM_OLD) {
2112                        day = day - 2009 + 14245;  // Magic!
2113                    }
2114                    DayStats ds = new DayStats(day);
2115                    ds.successCount = in.readInt();
2116                    ds.successTime = in.readLong();
2117                    ds.failureCount = in.readInt();
2118                    ds.failureTime = in.readLong();
2119                    if (index < mDayStats.length) {
2120                        mDayStats[index] = ds;
2121                        index++;
2122                    }
2123                } else {
2124                    // Ooops.
2125                    Slog.w(TAG, "Unknown stats token: " + token);
2126                    break;
2127                }
2128            }
2129        } catch (java.io.IOException e) {
2130            Slog.i(TAG, "No initial statistics");
2131        }
2132    }
2133
2134    /**
2135     * Write all sync statistics to the sync status file.
2136     */
2137    private void writeStatisticsLocked() {
2138        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2139            Slog.v(TAG, "Writing new " + mStatisticsFile.getBaseFile());
2140        }
2141
2142        // The file is being written, so we don't need to have a scheduled
2143        // write until the next change.
2144        removeMessages(MSG_WRITE_STATISTICS);
2145
2146        FileOutputStream fos = null;
2147        try {
2148            fos = mStatisticsFile.startWrite();
2149            Parcel out = Parcel.obtain();
2150            final int N = mDayStats.length;
2151            for (int i=0; i<N; i++) {
2152                DayStats ds = mDayStats[i];
2153                if (ds == null) {
2154                    break;
2155                }
2156                out.writeInt(STATISTICS_FILE_ITEM);
2157                out.writeInt(ds.day);
2158                out.writeInt(ds.successCount);
2159                out.writeLong(ds.successTime);
2160                out.writeInt(ds.failureCount);
2161                out.writeLong(ds.failureTime);
2162            }
2163            out.writeInt(STATISTICS_FILE_END);
2164            fos.write(out.marshall());
2165            out.recycle();
2166
2167            mStatisticsFile.finishWrite(fos);
2168        } catch (java.io.IOException e1) {
2169            Slog.w(TAG, "Error writing stats", e1);
2170            if (fos != null) {
2171                mStatisticsFile.failWrite(fos);
2172            }
2173        }
2174    }
2175
2176    /**
2177     * Let the BackupManager know that account sync settings have changed. This will trigger
2178     * {@link com.android.server.backup.SystemBackupAgent} to run.
2179     */
2180    public void queueBackup() {
2181        BackupManager.dataChanged("android");
2182    }
2183}
2184