SyncStorageEngine.java revision b63057e698a01dafcefc7ba09b397b0336bba43d
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.content.pm.PackageManager;
31import android.database.Cursor;
32import android.database.sqlite.SQLiteDatabase;
33import android.database.sqlite.SQLiteException;
34import android.database.sqlite.SQLiteQueryBuilder;
35import android.os.Bundle;
36import android.os.Environment;
37import android.os.Handler;
38import android.os.Message;
39import android.os.Parcel;
40import android.os.RemoteCallbackList;
41import android.os.RemoteException;
42import android.os.UserHandle;
43import android.util.AtomicFile;
44import android.util.Log;
45import android.util.Pair;
46import android.util.Slog;
47import android.util.SparseArray;
48import android.util.ArrayMap;
49import android.util.Xml;
50
51import com.android.internal.annotations.VisibleForTesting;
52import com.android.internal.util.ArrayUtils;
53import com.android.internal.util.FastXmlSerializer;
54
55import org.xmlpull.v1.XmlPullParser;
56import org.xmlpull.v1.XmlPullParserException;
57import org.xmlpull.v1.XmlSerializer;
58
59import java.io.File;
60import java.io.FileInputStream;
61import java.io.FileOutputStream;
62import java.io.IOException;
63import java.nio.charset.StandardCharsets;
64import java.util.ArrayList;
65import java.util.Calendar;
66import java.util.HashMap;
67import java.util.Iterator;
68import java.util.List;
69import java.util.Random;
70import java.util.TimeZone;
71
72/**
73 * Singleton that tracks the sync data and overall sync
74 * history on the device.
75 *
76 * @hide
77 */
78public class SyncStorageEngine extends Handler {
79
80    private static final String TAG = "SyncManager";
81    private static final String TAG_FILE = "SyncManagerFile";
82
83    private static final String XML_ATTR_NEXT_AUTHORITY_ID = "nextAuthorityId";
84    private static final String XML_ATTR_LISTEN_FOR_TICKLES = "listen-for-tickles";
85    private static final String XML_ATTR_SYNC_RANDOM_OFFSET = "offsetInSeconds";
86    private static final String XML_ATTR_ENABLED = "enabled";
87    private static final String XML_ATTR_USER = "user";
88    private static final String XML_TAG_LISTEN_FOR_TICKLES = "listenForTickles";
89
90    /** Default time for a periodic sync. */
91    private static final long DEFAULT_POLL_FREQUENCY_SECONDS = 60 * 60 * 24; // One day
92
93    /** Percentage of period that is flex by default, if no flex is set. */
94    private static final double DEFAULT_FLEX_PERCENT_SYNC = 0.04;
95
96    /** Lower bound on sync time from which we assign a default flex time. */
97    private static final long DEFAULT_MIN_FLEX_ALLOWED_SECS = 5;
98
99    @VisibleForTesting
100    static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4;
101
102    /** Enum value for a sync start event. */
103    public static final int EVENT_START = 0;
104
105    /** Enum value for a sync stop event. */
106    public static final int EVENT_STOP = 1;
107
108    // TODO: i18n -- grab these out of resources.
109    /** String names for the sync event types. */
110    public static final String[] EVENTS = { "START", "STOP" };
111
112    /** Enum value for a server-initiated sync. */
113    public static final int SOURCE_SERVER = 0;
114
115    /** Enum value for a local-initiated sync. */
116    public static final int SOURCE_LOCAL = 1;
117    /** Enum value for a poll-based sync (e.g., upon connection to network) */
118    public static final int SOURCE_POLL = 2;
119
120    /** Enum value for a user-initiated sync. */
121    public static final int SOURCE_USER = 3;
122
123    /** Enum value for a periodic sync. */
124    public static final int SOURCE_PERIODIC = 4;
125
126    /** Enum value for a sync started for a service. */
127    public static final int SOURCE_SERVICE = 5;
128
129    public static final long NOT_IN_BACKOFF_MODE = -1;
130
131    // TODO: i18n -- grab these out of resources.
132    /** String names for the sync source types. */
133    public static final String[] SOURCES = { "SERVER",
134                                             "LOCAL",
135                                             "POLL",
136                                             "USER",
137                                             "PERIODIC",
138                                             "SERVICE"};
139
140    // The MESG column will contain one of these or one of the Error types.
141    public static final String MESG_SUCCESS = "success";
142    public static final String MESG_CANCELED = "canceled";
143
144    public static final int MAX_HISTORY = 100;
145
146    private static final int MSG_WRITE_STATUS = 1;
147    private static final long WRITE_STATUS_DELAY = 1000*60*10; // 10 minutes
148
149    private static final int MSG_WRITE_STATISTICS = 2;
150    private static final long WRITE_STATISTICS_DELAY = 1000*60*30; // 1/2 hour
151
152    private static final boolean SYNC_ENABLED_DEFAULT = false;
153
154    // the version of the accounts xml file format
155    private static final int ACCOUNTS_VERSION = 2;
156
157    private static HashMap<String, String> sAuthorityRenames;
158
159    static {
160        sAuthorityRenames = new HashMap<String, String>();
161        sAuthorityRenames.put("contacts", "com.android.contacts");
162        sAuthorityRenames.put("calendar", "com.android.calendar");
163    }
164
165    public static class PendingOperation {
166        final EndPoint target;
167        final int reason;
168        final int syncSource;
169        final Bundle extras;        // note: read-only.
170        final boolean expedited;
171
172        final int authorityId;
173        // No longer used.
174        // Keep around for sake up updating from pending.bin to pending.xml
175        byte[] flatExtras;
176
177        PendingOperation(AuthorityInfo authority, int reason, int source,
178                 Bundle extras, boolean expedited) {
179            this.target = authority.target;
180            this.syncSource = source;
181            this.reason = reason;
182            this.extras = extras != null ? new Bundle(extras) : extras;
183            this.expedited = expedited;
184            this.authorityId = authority.ident;
185        }
186
187        PendingOperation(PendingOperation other) {
188            this.reason = other.reason;
189            this.syncSource = other.syncSource;
190            this.target = other.target;
191            this.extras = other.extras;
192            this.authorityId = other.authorityId;
193            this.expedited = other.expedited;
194        }
195
196        /**
197         * Considered equal if they target the same sync adapter (A
198         * {@link android.content.SyncService}
199         * is considered an adapter), for the same userId.
200         * @param other PendingOperation to compare.
201         * @return true if the two pending ops are the same.
202         */
203        public boolean equals(PendingOperation other) {
204            return target.matchesSpec(other.target);
205        }
206
207        public String toString() {
208            return "service=" + target.service
209                        + " user=" + target.userId
210                        + " auth=" + target
211                        + " account=" + target.account
212                        + " src=" + syncSource
213                        + " extras=" + extras;
214        }
215    }
216
217    static class AccountInfo {
218        final AccountAndUser accountAndUser;
219        final HashMap<String, AuthorityInfo> authorities =
220                new HashMap<String, AuthorityInfo>();
221
222        AccountInfo(AccountAndUser accountAndUser) {
223            this.accountAndUser = accountAndUser;
224        }
225    }
226
227    /**  Bare bones representation of a sync target. */
228    public static class EndPoint {
229        public final static EndPoint USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL =
230                new EndPoint(null, null, UserHandle.USER_ALL);
231        final ComponentName service;
232        final int serviceUid;           // -1 for "any"
233        final Account account;
234        final int userId;
235        final String provider;
236        final boolean target_service;
237        final boolean target_provider;
238
239        public EndPoint(ComponentName service, int userId, int uid) {
240            this.service = service;
241            this.serviceUid = uid;
242            this.userId = userId;
243            this.account = null;
244            this.provider = null;
245            this.target_service = true;
246            this.target_provider = false;
247        }
248
249        public EndPoint(Account account, String provider, int userId) {
250            this.account = account;
251            this.provider = provider;
252            this.userId = userId;
253            this.service = null;
254            this.serviceUid = -1;
255            this.target_service = false;
256            this.target_provider = true;
257        }
258
259        /**
260         * An Endpoint for a sync matches if it targets the same sync adapter for the same user.
261         *
262         * @param spec the Endpoint to match. If the spec has null fields, they indicate a wildcard
263         * and match any.
264         */
265        public boolean matchesSpec(EndPoint spec) {
266            if (userId != spec.userId
267                    && userId != UserHandle.USER_ALL
268                    && spec.userId != UserHandle.USER_ALL) {
269                return false;
270            }
271            if (target_service && spec.target_service) {
272                if (serviceUid != spec.serviceUid
273                    && serviceUid >= 0
274                    && spec.serviceUid >= 0) {
275                    return false;
276                }
277                return service.equals(spec.service);
278            } else if (target_provider && spec.target_provider) {
279                boolean accountsMatch;
280                if (spec.account == null) {
281                    accountsMatch = true;
282                } else {
283                    accountsMatch = account.equals(spec.account);
284                }
285                boolean providersMatch;
286                if (spec.provider == null) {
287                    providersMatch = true;
288                } else {
289                    providersMatch = provider.equals(spec.provider);
290                }
291                return accountsMatch && providersMatch;
292            }
293            return false;
294        }
295
296        public String toString() {
297            StringBuilder sb = new StringBuilder();
298            if (target_provider) {
299                sb.append(account == null ? "ALL ACCS" : account.name)
300                    .append("/")
301                    .append(provider == null ? "ALL PDRS" : provider);
302            } else if (target_service) {
303                service.appendShortString(sb);
304                sb.append(":");
305                UserHandle.formatUid(sb,serviceUid);
306            } else {
307                sb.append("invalid target");
308            }
309            sb.append(":u" + userId);
310            return sb.toString();
311        }
312    }
313
314    public static class AuthorityInfo {
315        // Legal values of getIsSyncable
316        /**
317         * Default state for a newly installed adapter. An uninitialized adapter will receive an
318         * initialization sync which are governed by a different set of rules to that of regular
319         * syncs.
320         */
321        public static final int NOT_INITIALIZED = -1;
322        /**
323         * The adapter will not receive any syncs. This is behaviourally equivalent to
324         * setSyncAutomatically -> false. However setSyncAutomatically is surfaced to the user
325         * while this is generally meant to be controlled by the developer.
326         */
327        public static final int NOT_SYNCABLE = 0;
328        /**
329         * The adapter is initialized and functioning. This is the normal state for an adapter.
330         */
331        public static final int SYNCABLE = 1;
332        /**
333         * The adapter is syncable but still requires an initialization sync. For example an adapter
334         * than has been restored from a previous device will be in this state. Not meant for
335         * external use.
336         */
337        public static final int SYNCABLE_NOT_INITIALIZED = 2;
338
339        final EndPoint target;
340        final int ident;
341        boolean enabled;
342        int syncable;
343        /** Time at which this sync will run, taking into account backoff. */
344        long backoffTime;
345        /** Amount of delay due to backoff. */
346        long backoffDelay;
347        /** Time offset to add to any requests coming to this target. */
348        long delayUntil;
349
350        final ArrayList<PeriodicSync> periodicSyncs;
351
352        /**
353         * Copy constructor for making deep-ish copies. Only the bundles stored
354         * in periodic syncs can make unexpected changes.
355         *
356         * @param toCopy AuthorityInfo to be copied.
357         */
358        AuthorityInfo(AuthorityInfo toCopy) {
359            target = toCopy.target;
360            ident = toCopy.ident;
361            enabled = toCopy.enabled;
362            syncable = toCopy.syncable;
363            backoffTime = toCopy.backoffTime;
364            backoffDelay = toCopy.backoffDelay;
365            delayUntil = toCopy.delayUntil;
366            periodicSyncs = new ArrayList<PeriodicSync>();
367            for (PeriodicSync sync : toCopy.periodicSyncs) {
368                // Still not a perfect copy, because we are just copying the mappings.
369                periodicSyncs.add(new PeriodicSync(sync));
370            }
371        }
372
373        AuthorityInfo(EndPoint info, int id) {
374            target = info;
375            ident = id;
376            enabled = info.target_provider ?
377                    SYNC_ENABLED_DEFAULT : true;
378            // Service is active by default,
379            if (info.target_service) {
380                this.syncable = 1;
381            }
382            periodicSyncs = new ArrayList<PeriodicSync>();
383            defaultInitialisation();
384        }
385
386        private void defaultInitialisation() {
387            syncable = NOT_INITIALIZED; // default to "unknown"
388            backoffTime = -1; // if < 0 then we aren't in backoff mode
389            backoffDelay = -1; // if < 0 then we aren't in backoff mode
390            PeriodicSync defaultSync;
391            // Old version is one sync a day.
392            if (target.target_provider) {
393                defaultSync =
394                        new PeriodicSync(target.account, target.provider,
395                            new Bundle(),
396                            DEFAULT_POLL_FREQUENCY_SECONDS,
397                            calculateDefaultFlexTime(DEFAULT_POLL_FREQUENCY_SECONDS));
398                periodicSyncs.add(defaultSync);
399            }
400        }
401
402        @Override
403        public String toString() {
404            return target + ", enabled=" + enabled + ", syncable=" + syncable + ", backoff="
405                    + backoffTime + ", delay=" + delayUntil;
406        }
407    }
408
409    public static class SyncHistoryItem {
410        int authorityId;
411        int historyId;
412        long eventTime;
413        long elapsedTime;
414        int source;
415        int event;
416        long upstreamActivity;
417        long downstreamActivity;
418        String mesg;
419        boolean initialization;
420        Bundle extras;
421        int reason;
422    }
423
424    public static class DayStats {
425        public final int day;
426        public int successCount;
427        public long successTime;
428        public int failureCount;
429        public long failureTime;
430
431        public DayStats(int day) {
432            this.day = day;
433        }
434    }
435
436    interface OnSyncRequestListener {
437
438        /** Called when a sync is needed on an account(s) due to some change in state. */
439        public void onSyncRequest(EndPoint info, int reason, Bundle extras);
440    }
441
442    // Primary list of all syncable authorities.  Also our global lock.
443    private final SparseArray<AuthorityInfo> mAuthorities =
444            new SparseArray<AuthorityInfo>();
445
446    private final HashMap<AccountAndUser, AccountInfo> mAccounts
447            = new HashMap<AccountAndUser, AccountInfo>();
448
449    private final ArrayList<PendingOperation> mPendingOperations =
450            new ArrayList<PendingOperation>();
451
452    private final SparseArray<ArrayList<SyncInfo>> mCurrentSyncs
453            = new SparseArray<ArrayList<SyncInfo>>();
454
455    private final SparseArray<SyncStatusInfo> mSyncStatus =
456            new SparseArray<SyncStatusInfo>();
457
458    private final ArrayList<SyncHistoryItem> mSyncHistory =
459            new ArrayList<SyncHistoryItem>();
460
461    private final RemoteCallbackList<ISyncStatusObserver> mChangeListeners
462            = new RemoteCallbackList<ISyncStatusObserver>();
463
464    /** Reverse mapping for component name -> <userid -> target id>. */
465    private final ArrayMap<ComponentName, SparseArray<AuthorityInfo>> mServices =
466            new ArrayMap<ComponentName, SparseArray<AuthorityInfo>>();
467
468    private int mNextAuthorityId = 0;
469
470    // We keep 4 weeks of stats.
471    private final DayStats[] mDayStats = new DayStats[7*4];
472    private final Calendar mCal;
473    private int mYear;
474    private int mYearInDays;
475
476    private final Context mContext;
477
478    private static volatile SyncStorageEngine sSyncStorageEngine = null;
479
480    private int mSyncRandomOffset;
481
482    /**
483     * This file contains the core engine state: all accounts and the
484     * settings for them.  It must never be lost, and should be changed
485     * infrequently, so it is stored as an XML file.
486     */
487    private final AtomicFile mAccountInfoFile;
488
489    /**
490     * This file contains the current sync status.  We would like to retain
491     * it across boots, but its loss is not the end of the world, so we store
492     * this information as binary data.
493     */
494    private final AtomicFile mStatusFile;
495
496    /**
497     * This file contains sync statistics.  This is purely debugging information
498     * so is written infrequently and can be thrown away at any time.
499     */
500    private final AtomicFile mStatisticsFile;
501
502    /**
503     * This file contains the pending sync operations.  It is a binary file,
504     * which must be updated every time an operation is added or removed,
505     * so we have special handling of it.
506     */
507    private final AtomicFile mPendingFile;
508    private static final int PENDING_FINISH_TO_WRITE = 4;
509    private int mNumPendingFinished = 0;
510
511    private int mNextHistoryId = 0;
512    private SparseArray<Boolean> mMasterSyncAutomatically = new SparseArray<Boolean>();
513    private boolean mDefaultMasterSyncAutomatically;
514
515    private OnSyncRequestListener mSyncRequestListener;
516
517    private SyncStorageEngine(Context context, File dataDir) {
518        mContext = context;
519        sSyncStorageEngine = this;
520
521        mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
522
523        mDefaultMasterSyncAutomatically = mContext.getResources().getBoolean(
524               com.android.internal.R.bool.config_syncstorageengine_masterSyncAutomatically);
525
526        File systemDir = new File(dataDir, "system");
527        File syncDir = new File(systemDir, "sync");
528        syncDir.mkdirs();
529
530        maybeDeleteLegacyPendingInfoLocked(syncDir);
531
532        mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
533        mStatusFile = new AtomicFile(new File(syncDir, "status.bin"));
534        mPendingFile = new AtomicFile(new File(syncDir, "pending.xml"));
535        mStatisticsFile = new AtomicFile(new File(syncDir, "stats.bin"));
536
537        readAccountInfoLocked();
538        readStatusLocked();
539        readPendingOperationsLocked();
540        readStatisticsLocked();
541        readAndDeleteLegacyAccountInfoLocked();
542        writeAccountInfoLocked();
543        writeStatusLocked();
544        writePendingOperationsLocked();
545        writeStatisticsLocked();
546    }
547
548    public static SyncStorageEngine newTestInstance(Context context) {
549        return new SyncStorageEngine(context, context.getFilesDir());
550    }
551
552    public static void init(Context context) {
553        if (sSyncStorageEngine != null) {
554            return;
555        }
556        // This call will return the correct directory whether Encrypted File Systems is
557        // enabled or not.
558        File dataDir = Environment.getSecureDataDirectory();
559        sSyncStorageEngine = new SyncStorageEngine(context, dataDir);
560    }
561
562    public static SyncStorageEngine getSingleton() {
563        if (sSyncStorageEngine == null) {
564            throw new IllegalStateException("not initialized");
565        }
566        return sSyncStorageEngine;
567    }
568
569    protected void setOnSyncRequestListener(OnSyncRequestListener listener) {
570        if (mSyncRequestListener == null) {
571            mSyncRequestListener = listener;
572        }
573    }
574
575    @Override public void handleMessage(Message msg) {
576        if (msg.what == MSG_WRITE_STATUS) {
577            synchronized (mAuthorities) {
578                writeStatusLocked();
579            }
580        } else if (msg.what == MSG_WRITE_STATISTICS) {
581            synchronized (mAuthorities) {
582                writeStatisticsLocked();
583            }
584        }
585    }
586
587    public int getSyncRandomOffset() {
588        return mSyncRandomOffset;
589    }
590
591    public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
592        synchronized (mAuthorities) {
593            mChangeListeners.register(callback, mask);
594        }
595    }
596
597    public void removeStatusChangeListener(ISyncStatusObserver callback) {
598        synchronized (mAuthorities) {
599            mChangeListeners.unregister(callback);
600        }
601    }
602
603    /**
604     * Figure out a reasonable flex time for cases where none is provided (old api calls).
605     * @param syncTimeSeconds requested sync time from now.
606     * @return amount of seconds before syncTimeSeconds that the sync can occur.
607     *      I.e.
608     *      earliest_sync_time = syncTimeSeconds - calculateDefaultFlexTime(syncTimeSeconds)
609     * The flex time is capped at a percentage of the {@link #DEFAULT_POLL_FREQUENCY_SECONDS}.
610     */
611    public static long calculateDefaultFlexTime(long syncTimeSeconds) {
612        if (syncTimeSeconds < DEFAULT_MIN_FLEX_ALLOWED_SECS) {
613            // Small enough sync request time that we don't add flex time - developer probably
614            // wants to wait for an operation to occur before syncing so we honour the
615            // request time.
616            return 0L;
617        } else if (syncTimeSeconds < DEFAULT_POLL_FREQUENCY_SECONDS) {
618            return (long) (syncTimeSeconds * DEFAULT_FLEX_PERCENT_SYNC);
619        } else {
620            // Large enough sync request time that we cap the flex time.
621            return (long) (DEFAULT_POLL_FREQUENCY_SECONDS * DEFAULT_FLEX_PERCENT_SYNC);
622        }
623    }
624
625    private void reportChange(int which) {
626        ArrayList<ISyncStatusObserver> reports = null;
627        synchronized (mAuthorities) {
628            int i = mChangeListeners.beginBroadcast();
629            while (i > 0) {
630                i--;
631                Integer mask = (Integer)mChangeListeners.getBroadcastCookie(i);
632                if ((which & mask.intValue()) == 0) {
633                    continue;
634                }
635                if (reports == null) {
636                    reports = new ArrayList<ISyncStatusObserver>(i);
637                }
638                reports.add(mChangeListeners.getBroadcastItem(i));
639            }
640            mChangeListeners.finishBroadcast();
641        }
642
643        if (Log.isLoggable(TAG, Log.VERBOSE)) {
644            Log.v(TAG, "reportChange " + which + " to: " + reports);
645        }
646
647        if (reports != null) {
648            int i = reports.size();
649            while (i > 0) {
650                i--;
651                try {
652                    reports.get(i).onStatusChanged(which);
653                } catch (RemoteException e) {
654                    // The remote callback list will take care of this for us.
655                }
656            }
657        }
658    }
659
660    public boolean getSyncAutomatically(Account account, int userId, String providerName) {
661        synchronized (mAuthorities) {
662            if (account != null) {
663                AuthorityInfo authority = getAuthorityLocked(
664                        new EndPoint(account, providerName, userId),
665                        "getSyncAutomatically");
666                return authority != null && authority.enabled;
667            }
668
669            int i = mAuthorities.size();
670            while (i > 0) {
671                i--;
672                AuthorityInfo authorityInfo = mAuthorities.valueAt(i);
673                if (authorityInfo.target.matchesSpec(new EndPoint(account, providerName, userId))
674                        && authorityInfo.enabled) {
675                    return true;
676                }
677            }
678            return false;
679        }
680    }
681
682    public void setSyncAutomatically(Account account, int userId, String providerName,
683            boolean sync) {
684        if (Log.isLoggable(TAG, Log.VERBOSE)) {
685            Log.d(TAG, "setSyncAutomatically: " + /* account + */" provider " + providerName
686                    + ", user " + userId + " -> " + sync);
687        }
688        synchronized (mAuthorities) {
689            AuthorityInfo authority =
690                    getOrCreateAuthorityLocked(
691                            new EndPoint(account, providerName, userId),
692                            -1 /* ident */,
693                            false);
694            if (authority.enabled == sync) {
695                if (Log.isLoggable(TAG, Log.VERBOSE)) {
696                    Log.d(TAG, "setSyncAutomatically: already set to " + sync + ", doing nothing");
697                }
698                return;
699            }
700            // If the adapter was syncable but missing its initialization sync, set it to
701            // uninitialized now. This is to give it a chance to run any one-time initialization
702            // logic.
703            if (sync && authority.syncable == AuthorityInfo.SYNCABLE_NOT_INITIALIZED) {
704                authority.syncable = AuthorityInfo.NOT_INITIALIZED;
705            }
706            authority.enabled = sync;
707            writeAccountInfoLocked();
708        }
709
710        if (sync) {
711            requestSync(account, userId, SyncOperation.REASON_SYNC_AUTO, providerName,
712                    new Bundle());
713        }
714        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
715        queueBackup();
716    }
717
718    public int getIsSyncable(Account account, int userId, String providerName) {
719        synchronized (mAuthorities) {
720            if (account != null) {
721                AuthorityInfo authority = getAuthorityLocked(
722                        new EndPoint(account, providerName, userId),
723                        "get authority syncable");
724                if (authority == null) {
725                    return AuthorityInfo.NOT_INITIALIZED;
726                }
727                return authority.syncable;
728            }
729
730            int i = mAuthorities.size();
731            while (i > 0) {
732                i--;
733                AuthorityInfo authorityInfo = mAuthorities.valueAt(i);
734                if (authorityInfo.target != null
735                        && authorityInfo.target.provider.equals(providerName)) {
736                    return authorityInfo.syncable;
737                }
738            }
739            return AuthorityInfo.NOT_INITIALIZED;
740        }
741    }
742
743    public void setIsSyncable(Account account, int userId, String providerName, int syncable) {
744        setSyncableStateForEndPoint(new EndPoint(account, providerName, userId), syncable);
745    }
746
747    public boolean getIsTargetServiceActive(ComponentName cname, int userId) {
748        synchronized (mAuthorities) {
749            if (cname != null) {
750                AuthorityInfo authority = getAuthorityLocked(
751                        new EndPoint(cname, userId, -1),
752                        "get service active");
753                if (authority == null) {
754                    return false;
755                }
756                return (authority.syncable == 1);
757            }
758            return false;
759        }
760    }
761
762    public void setIsTargetServiceActive(ComponentName cname, int userId, boolean active) {
763        setSyncableStateForEndPoint(new EndPoint(cname, userId, -1), active ?
764                AuthorityInfo.SYNCABLE : AuthorityInfo.NOT_SYNCABLE);
765    }
766
767    /**
768     * An enabled sync service and a syncable provider's adapter both get resolved to the same
769     * persisted variable - namely the "syncable" attribute for an AuthorityInfo in accounts.xml.
770     * @param target target to set value for.
771     * @param syncable 0 indicates unsyncable, <0 unknown, >0 is active/syncable.
772     */
773    private void setSyncableStateForEndPoint(EndPoint target, int syncable) {
774        AuthorityInfo aInfo;
775        synchronized (mAuthorities) {
776            aInfo = getOrCreateAuthorityLocked(target, -1, false);
777            if (syncable < AuthorityInfo.NOT_INITIALIZED) {
778                syncable = AuthorityInfo.NOT_INITIALIZED;
779            }
780            if (Log.isLoggable(TAG, Log.VERBOSE)) {
781                Log.d(TAG, "setIsSyncable: " + aInfo.toString() + " -> " + syncable);
782            }
783            if (aInfo.syncable == syncable) {
784                if (Log.isLoggable(TAG, Log.VERBOSE)) {
785                    Log.d(TAG, "setIsSyncable: already set to " + syncable + ", doing nothing");
786                }
787                return;
788            }
789            aInfo.syncable = syncable;
790            writeAccountInfoLocked();
791        }
792        if (syncable == AuthorityInfo.SYNCABLE) {
793            requestSync(aInfo, SyncOperation.REASON_IS_SYNCABLE, new Bundle());
794        }
795        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
796    }
797
798    public Pair<Long, Long> getBackoff(EndPoint info) {
799        synchronized (mAuthorities) {
800            AuthorityInfo authority = getAuthorityLocked(info, "getBackoff");
801            if (authority != null) {
802                return Pair.create(authority.backoffTime, authority.backoffDelay);
803            }
804            return null;
805        }
806    }
807
808    /**
809     * Update the backoff for the given endpoint. The endpoint may be for a provider/account and
810     * the account or provider info be null, which signifies all accounts or providers.
811     */
812    public void setBackoff(EndPoint info, long nextSyncTime, long nextDelay) {
813        if (Log.isLoggable(TAG, Log.VERBOSE)) {
814            Log.v(TAG, "setBackoff: " + info
815                    + " -> nextSyncTime " + nextSyncTime + ", nextDelay " + nextDelay);
816        }
817        boolean changed;
818        synchronized (mAuthorities) {
819            if (info.target_provider
820                    && (info.account == null || info.provider == null)) {
821                // Do more work for a provider sync if the provided info has specified all
822                // accounts/providers.
823                changed = setBackoffLocked(
824                        info.account /* may be null */,
825                        info.userId,
826                        info.provider /* may be null */,
827                        nextSyncTime, nextDelay);
828            } else {
829                AuthorityInfo authorityInfo =
830                        getOrCreateAuthorityLocked(info, -1 /* ident */, true);
831                if (authorityInfo.backoffTime == nextSyncTime
832                        && authorityInfo.backoffDelay == nextDelay) {
833                    changed = false;
834                } else {
835                    authorityInfo.backoffTime = nextSyncTime;
836                    authorityInfo.backoffDelay = nextDelay;
837                    changed = true;
838                }
839            }
840        }
841        if (changed) {
842            reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
843        }
844    }
845
846    /**
847     * Either set backoff for a specific authority, or set backoff for all the
848     * accounts on a specific adapter/all adapters.
849     *
850     * @param account account for which to set backoff. Null to specify all accounts.
851     * @param userId id of the user making this request.
852     * @param providerName provider for which to set backoff. Null to specify all providers.
853     * @return true if a change occured.
854     */
855    private boolean setBackoffLocked(Account account, int userId, String providerName,
856            long nextSyncTime, long nextDelay) {
857        boolean changed = false;
858        for (AccountInfo accountInfo : mAccounts.values()) {
859            if (account != null && !account.equals(accountInfo.accountAndUser.account)
860                    && userId != accountInfo.accountAndUser.userId) {
861                continue;
862            }
863            for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) {
864                if (providerName != null
865                        && !providerName.equals(authorityInfo.target.provider)) {
866                    continue;
867                }
868                if (authorityInfo.backoffTime != nextSyncTime
869                        || authorityInfo.backoffDelay != nextDelay) {
870                    authorityInfo.backoffTime = nextSyncTime;
871                    authorityInfo.backoffDelay = nextDelay;
872                    changed = true;
873                }
874            }
875        }
876        return changed;
877    }
878
879    public void clearAllBackoffsLocked(SyncQueue syncQueue) {
880        boolean changed = false;
881        synchronized (mAuthorities) {
882                // Clear backoff for all sync adapters.
883                for (AccountInfo accountInfo : mAccounts.values()) {
884                    for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) {
885                        if (authorityInfo.backoffTime != NOT_IN_BACKOFF_MODE
886                                || authorityInfo.backoffDelay != NOT_IN_BACKOFF_MODE) {
887                            if (Log.isLoggable(TAG, Log.VERBOSE)) {
888                                Log.v(TAG, "clearAllBackoffsLocked:"
889                                        + " authority:" + authorityInfo.target
890                                        + " account:" + accountInfo.accountAndUser.account.name
891                                        + " user:" + accountInfo.accountAndUser.userId
892                                        + " backoffTime was: " + authorityInfo.backoffTime
893                                        + " backoffDelay was: " + authorityInfo.backoffDelay);
894                            }
895                            authorityInfo.backoffTime = NOT_IN_BACKOFF_MODE;
896                            authorityInfo.backoffDelay = NOT_IN_BACKOFF_MODE;
897                            changed = true;
898                        }
899                    }
900                }
901                // Clear backoff for all sync services.
902                for (ComponentName service : mServices.keySet()) {
903                    SparseArray<AuthorityInfo> aInfos = mServices.get(service);
904                    for (int i = 0; i < aInfos.size(); i++) {
905                        AuthorityInfo authorityInfo = aInfos.valueAt(i);
906                        if (authorityInfo.backoffTime != NOT_IN_BACKOFF_MODE
907                                || authorityInfo.backoffDelay != NOT_IN_BACKOFF_MODE) {
908                            authorityInfo.backoffTime = NOT_IN_BACKOFF_MODE;
909                            authorityInfo.backoffDelay = NOT_IN_BACKOFF_MODE;
910                        }
911                    }
912                syncQueue.clearBackoffs();
913            }
914        }
915
916        if (changed) {
917            reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
918        }
919    }
920
921    public long getDelayUntilTime(EndPoint info) {
922        synchronized (mAuthorities) {
923            AuthorityInfo authority = getAuthorityLocked(info, "getDelayUntil");
924            if (authority == null) {
925                return 0;
926            }
927            return authority.delayUntil;
928        }
929    }
930
931    public void setDelayUntilTime(EndPoint info, long delayUntil) {
932        if (Log.isLoggable(TAG, Log.VERBOSE)) {
933            Log.v(TAG, "setDelayUntil: " + info
934                    + " -> delayUntil " + delayUntil);
935        }
936        synchronized (mAuthorities) {
937            AuthorityInfo authority = getOrCreateAuthorityLocked(info, -1, true);
938            if (authority.delayUntil == delayUntil) {
939                return;
940            }
941            authority.delayUntil = delayUntil;
942        }
943        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
944    }
945
946    public void updateOrAddPeriodicSync(EndPoint info, long period, long flextime, Bundle extras) {
947        if (Log.isLoggable(TAG, Log.VERBOSE)) {
948            Log.v(TAG, "addPeriodicSync: " + info
949                    + " -> period " + period + ", flex " + flextime + ", extras "
950                    + extras.toString());
951        }
952        synchronized (mAuthorities) {
953            if (period <= 0) {
954                Log.e(TAG, "period < 0, should never happen in updateOrAddPeriodicSync");
955            }
956            if (extras == null) {
957                Log.e(TAG, "null extras, should never happen in updateOrAddPeriodicSync:");
958            }
959            try {
960                PeriodicSync toUpdate;
961                if (info.target_provider) {
962                    toUpdate = new PeriodicSync(info.account,
963                            info.provider,
964                            extras,
965                            period,
966                            flextime);
967                } else {
968                    return;
969                }
970                AuthorityInfo authority =
971                        getOrCreateAuthorityLocked(info, -1, false);
972                // add this periodic sync if an equivalent periodic doesn't already exist.
973                boolean alreadyPresent = false;
974                for (int i = 0, N = authority.periodicSyncs.size(); i < N; i++) {
975                    PeriodicSync syncInfo = authority.periodicSyncs.get(i);
976                    if (SyncManager.syncExtrasEquals(syncInfo.extras,
977                            extras,
978                            true /* includeSyncSettings*/)) {
979                        if (period == syncInfo.period &&
980                                flextime == syncInfo.flexTime) {
981                            // Absolutely the same.
982                            return;
983                        }
984                        authority.periodicSyncs.set(i, toUpdate);
985                        alreadyPresent = true;
986                        break;
987                    }
988                }
989                // If we added an entry to the periodicSyncs array also add an entry to
990                // the periodic syncs status to correspond to it.
991                if (!alreadyPresent) {
992                    authority.periodicSyncs.add(toUpdate);
993                    SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
994                    // A new periodic sync is initialised as already having been run.
995                    status.setPeriodicSyncTime(
996                            authority.periodicSyncs.size() - 1,
997                            System.currentTimeMillis());
998                }
999            } finally {
1000                writeAccountInfoLocked();
1001                writeStatusLocked();
1002            }
1003        }
1004        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
1005    }
1006
1007    public void removePeriodicSync(EndPoint info, Bundle extras) {
1008        synchronized(mAuthorities) {
1009            try {
1010                AuthorityInfo authority =
1011                        getOrCreateAuthorityLocked(info, -1, false);
1012                // Remove any periodic syncs that match the target and extras.
1013                SyncStatusInfo status = mSyncStatus.get(authority.ident);
1014                boolean changed = false;
1015                Iterator<PeriodicSync> iterator = authority.periodicSyncs.iterator();
1016                int i = 0;
1017                while (iterator.hasNext()) {
1018                    PeriodicSync syncInfo = iterator.next();
1019                    if (SyncManager.syncExtrasEquals(syncInfo.extras,
1020                            extras,
1021                            true /* includeSyncSettings */)) {
1022                        iterator.remove();
1023                        changed = true;
1024                        // If we removed an entry from the periodicSyncs array also
1025                        // remove the corresponding entry from the status
1026                        if (status != null) {
1027                            status.removePeriodicSyncTime(i);
1028                        } else {
1029                            Log.e(TAG, "Tried removing sync status on remove periodic sync but"
1030                                    + " did not find it.");
1031                        }
1032                    } else {
1033                        i++;
1034                    }
1035                }
1036                if (!changed) {
1037                    return;
1038                }
1039            } finally {
1040                writeAccountInfoLocked();
1041                writeStatusLocked();
1042            }
1043        }
1044        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
1045    }
1046
1047    /**
1048     * @return list of periodic syncs for a target. Never null. If no such syncs exist, returns an
1049     * empty list.
1050     */
1051    public List<PeriodicSync> getPeriodicSyncs(EndPoint info) {
1052        synchronized (mAuthorities) {
1053            AuthorityInfo authorityInfo = getAuthorityLocked(info, "getPeriodicSyncs");
1054            ArrayList<PeriodicSync> syncs = new ArrayList<PeriodicSync>();
1055            if (authorityInfo != null) {
1056                for (PeriodicSync item : authorityInfo.periodicSyncs) {
1057                    // Copy and send out. Necessary for thread-safety although it's parceled.
1058                    syncs.add(new PeriodicSync(item));
1059                }
1060            }
1061            return syncs;
1062        }
1063    }
1064
1065    public void setMasterSyncAutomatically(boolean flag, int userId) {
1066        synchronized (mAuthorities) {
1067            Boolean auto = mMasterSyncAutomatically.get(userId);
1068            if (auto != null && auto.equals(flag)) {
1069                return;
1070            }
1071            mMasterSyncAutomatically.put(userId, flag);
1072            writeAccountInfoLocked();
1073        }
1074        if (flag) {
1075            requestSync(null, userId, SyncOperation.REASON_MASTER_SYNC_AUTO, null,
1076                    new Bundle());
1077        }
1078        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
1079        mContext.sendBroadcast(ContentResolver.ACTION_SYNC_CONN_STATUS_CHANGED);
1080        queueBackup();
1081    }
1082
1083    public boolean getMasterSyncAutomatically(int userId) {
1084        synchronized (mAuthorities) {
1085            Boolean auto = mMasterSyncAutomatically.get(userId);
1086            return auto == null ? mDefaultMasterSyncAutomatically : auto;
1087        }
1088    }
1089
1090    public AuthorityInfo getAuthority(int authorityId) {
1091        synchronized (mAuthorities) {
1092            return mAuthorities.get(authorityId);
1093        }
1094    }
1095
1096    /**
1097     * Returns true if there is currently a sync operation being actively processed for the given
1098     * target.
1099     */
1100    public boolean isSyncActive(EndPoint info) {
1101        synchronized (mAuthorities) {
1102            for (SyncInfo syncInfo : getCurrentSyncs(info.userId)) {
1103                AuthorityInfo ainfo = getAuthority(syncInfo.authorityId);
1104                if (ainfo != null && ainfo.target.matchesSpec(info)) {
1105                    return true;
1106                }
1107            }
1108        }
1109        return false;
1110    }
1111
1112    public PendingOperation insertIntoPending(SyncOperation op) {
1113        PendingOperation pop;
1114        synchronized (mAuthorities) {
1115            if (Log.isLoggable(TAG, Log.VERBOSE)) {
1116                Log.v(TAG, "insertIntoPending: authority=" + op.target
1117                        + " extras=" + op.extras);
1118            }
1119            final EndPoint info = op.target;
1120            AuthorityInfo authority =
1121                    getOrCreateAuthorityLocked(info,
1122                            -1 /* desired identifier */,
1123                            true /* write accounts to storage */);
1124            if (authority == null) {
1125                return null;
1126            }
1127
1128            pop = new PendingOperation(authority, op.reason, op.syncSource, op.extras,
1129                    op.isExpedited());
1130            mPendingOperations.add(pop);
1131            appendPendingOperationLocked(pop);
1132
1133            SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
1134            status.pending = true;
1135        }
1136        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
1137        return pop;
1138    }
1139
1140    /**
1141     * Remove from list of pending operations. If successful, search through list for matching
1142     * authorities. If there are no more pending syncs for the same target,
1143     * update the SyncStatusInfo for that target.
1144     * @param op Pending op to delete.
1145     */
1146    public boolean deleteFromPending(PendingOperation op) {
1147        boolean res = false;
1148        synchronized (mAuthorities) {
1149            if (Log.isLoggable(TAG, Log.VERBOSE)) {
1150                Log.v(TAG, "deleteFromPending: account=" + op.toString());
1151            }
1152            if (mPendingOperations.remove(op)) {
1153                if (mPendingOperations.size() == 0
1154                        || mNumPendingFinished >= PENDING_FINISH_TO_WRITE) {
1155                    writePendingOperationsLocked();
1156                    mNumPendingFinished = 0;
1157                } else {
1158                    mNumPendingFinished++;
1159                }
1160                AuthorityInfo authority = getAuthorityLocked(op.target, "deleteFromPending");
1161                if (authority != null) {
1162                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
1163                        Log.v(TAG, "removing - " + authority.toString());
1164                    }
1165                    final int N = mPendingOperations.size();
1166                    boolean morePending = false;
1167                    for (int i = 0; i < N; i++) {
1168                        PendingOperation cur = mPendingOperations.get(i);
1169                        if (cur.equals(op)) {
1170                            morePending = true;
1171                            break;
1172                        }
1173                    }
1174
1175                    if (!morePending) {
1176                        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "no more pending!");
1177                        SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
1178                        status.pending = false;
1179                    }
1180                }
1181                res = true;
1182            }
1183        }
1184
1185        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
1186        return res;
1187    }
1188
1189    /**
1190     * Return a copy of the current array of pending operations.  The
1191     * PendingOperation objects are the real objects stored inside, so that
1192     * they can be used with deleteFromPending().
1193     */
1194    public ArrayList<PendingOperation> getPendingOperations() {
1195        synchronized (mAuthorities) {
1196            return new ArrayList<PendingOperation>(mPendingOperations);
1197        }
1198    }
1199
1200    /**
1201     * Return the number of currently pending operations.
1202     */
1203    public int getPendingOperationCount() {
1204        synchronized (mAuthorities) {
1205            return mPendingOperations.size();
1206        }
1207    }
1208
1209    /**
1210     * Called when the set of account has changed, given the new array of
1211     * active accounts.
1212     */
1213    public void doDatabaseCleanup(Account[] accounts, int userId) {
1214        synchronized (mAuthorities) {
1215            if (Log.isLoggable(TAG, Log.VERBOSE)) {
1216                Log.v(TAG, "Updating for new accounts...");
1217            }
1218            SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>();
1219            Iterator<AccountInfo> accIt = mAccounts.values().iterator();
1220            while (accIt.hasNext()) {
1221                AccountInfo acc = accIt.next();
1222                if (!ArrayUtils.contains(accounts, acc.accountAndUser.account)
1223                        && acc.accountAndUser.userId == userId) {
1224                    // This account no longer exists...
1225                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
1226                        Log.v(TAG, "Account removed: " + acc.accountAndUser);
1227                    }
1228                    for (AuthorityInfo auth : acc.authorities.values()) {
1229                        removing.put(auth.ident, auth);
1230                    }
1231                    accIt.remove();
1232                }
1233            }
1234
1235            // Clean out all data structures.
1236            int i = removing.size();
1237            if (i > 0) {
1238                while (i > 0) {
1239                    i--;
1240                    int ident = removing.keyAt(i);
1241                    mAuthorities.remove(ident);
1242                    int j = mSyncStatus.size();
1243                    while (j > 0) {
1244                        j--;
1245                        if (mSyncStatus.keyAt(j) == ident) {
1246                            mSyncStatus.remove(mSyncStatus.keyAt(j));
1247                        }
1248                    }
1249                    j = mSyncHistory.size();
1250                    while (j > 0) {
1251                        j--;
1252                        if (mSyncHistory.get(j).authorityId == ident) {
1253                            mSyncHistory.remove(j);
1254                        }
1255                    }
1256                }
1257                writeAccountInfoLocked();
1258                writeStatusLocked();
1259                writePendingOperationsLocked();
1260                writeStatisticsLocked();
1261            }
1262        }
1263    }
1264
1265    /**
1266     * Called when a sync is starting. Supply a valid ActiveSyncContext with information
1267     * about the sync.
1268     */
1269    public SyncInfo addActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
1270        final SyncInfo syncInfo;
1271        synchronized (mAuthorities) {
1272            if (Log.isLoggable(TAG, Log.VERBOSE)) {
1273                Log.v(TAG, "setActiveSync: account="
1274                    + " auth=" + activeSyncContext.mSyncOperation.target
1275                    + " src=" + activeSyncContext.mSyncOperation.syncSource
1276                    + " extras=" + activeSyncContext.mSyncOperation.extras);
1277            }
1278            final EndPoint info = activeSyncContext.mSyncOperation.target;
1279            AuthorityInfo authorityInfo = getOrCreateAuthorityLocked(
1280                    info,
1281                    -1 /* assign a new identifier if creating a new target */,
1282                    true /* write to storage if this results in a change */);
1283            syncInfo = new SyncInfo(
1284                    authorityInfo.ident,
1285                    authorityInfo.target.account,
1286                    authorityInfo.target.provider,
1287                    activeSyncContext.mStartTime);
1288            getCurrentSyncs(authorityInfo.target.userId).add(syncInfo);
1289        }
1290        reportActiveChange();
1291        return syncInfo;
1292    }
1293
1294    /**
1295     * Called to indicate that a previously active sync is no longer active.
1296     */
1297    public void removeActiveSync(SyncInfo syncInfo, int userId) {
1298        synchronized (mAuthorities) {
1299            if (Log.isLoggable(TAG, Log.VERBOSE)) {
1300                Log.v(TAG, "removeActiveSync: account=" + syncInfo.account
1301                        + " user=" + userId
1302                        + " auth=" + syncInfo.authority);
1303            }
1304            getCurrentSyncs(userId).remove(syncInfo);
1305        }
1306
1307        reportActiveChange();
1308    }
1309
1310    /**
1311     * To allow others to send active change reports, to poke clients.
1312     */
1313    public void reportActiveChange() {
1314        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE);
1315    }
1316
1317    /**
1318     * Note that sync has started for the given operation.
1319     */
1320    public long insertStartSyncEvent(SyncOperation op, long now) {
1321        long id;
1322        synchronized (mAuthorities) {
1323            if (Log.isLoggable(TAG, Log.VERBOSE)) {
1324                Log.v(TAG, "insertStartSyncEvent: " + op);
1325            }
1326            AuthorityInfo authority = getAuthorityLocked(op.target, "insertStartSyncEvent");
1327            if (authority == null) {
1328                return -1;
1329            }
1330            SyncHistoryItem item = new SyncHistoryItem();
1331            item.initialization = op.isInitialization();
1332            item.authorityId = authority.ident;
1333            item.historyId = mNextHistoryId++;
1334            if (mNextHistoryId < 0) mNextHistoryId = 0;
1335            item.eventTime = now;
1336            item.source = op.syncSource;
1337            item.reason = op.reason;
1338            item.extras = op.extras;
1339            item.event = EVENT_START;
1340            mSyncHistory.add(0, item);
1341            while (mSyncHistory.size() > MAX_HISTORY) {
1342                mSyncHistory.remove(mSyncHistory.size()-1);
1343            }
1344            id = item.historyId;
1345            if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "returning historyId " + id);
1346        }
1347
1348        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
1349        return id;
1350    }
1351
1352    public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage,
1353            long downstreamActivity, long upstreamActivity) {
1354        synchronized (mAuthorities) {
1355            if (Log.isLoggable(TAG, Log.VERBOSE)) {
1356                Log.v(TAG, "stopSyncEvent: historyId=" + historyId);
1357            }
1358            SyncHistoryItem item = null;
1359            int i = mSyncHistory.size();
1360            while (i > 0) {
1361                i--;
1362                item = mSyncHistory.get(i);
1363                if (item.historyId == historyId) {
1364                    break;
1365                }
1366                item = null;
1367            }
1368
1369            if (item == null) {
1370                Log.w(TAG, "stopSyncEvent: no history for id " + historyId);
1371                return;
1372            }
1373
1374            item.elapsedTime = elapsedTime;
1375            item.event = EVENT_STOP;
1376            item.mesg = resultMessage;
1377            item.downstreamActivity = downstreamActivity;
1378            item.upstreamActivity = upstreamActivity;
1379
1380            SyncStatusInfo status = getOrCreateSyncStatusLocked(item.authorityId);
1381
1382            status.numSyncs++;
1383            status.totalElapsedTime += elapsedTime;
1384            switch (item.source) {
1385                case SOURCE_LOCAL:
1386                    status.numSourceLocal++;
1387                    break;
1388                case SOURCE_POLL:
1389                    status.numSourcePoll++;
1390                    break;
1391                case SOURCE_USER:
1392                    status.numSourceUser++;
1393                    break;
1394                case SOURCE_SERVER:
1395                    status.numSourceServer++;
1396                    break;
1397                case SOURCE_PERIODIC:
1398                    status.numSourcePeriodic++;
1399                    break;
1400            }
1401
1402            boolean writeStatisticsNow = false;
1403            int day = getCurrentDayLocked();
1404            if (mDayStats[0] == null) {
1405                mDayStats[0] = new DayStats(day);
1406            } else if (day != mDayStats[0].day) {
1407                System.arraycopy(mDayStats, 0, mDayStats, 1, mDayStats.length-1);
1408                mDayStats[0] = new DayStats(day);
1409                writeStatisticsNow = true;
1410            } else if (mDayStats[0] == null) {
1411            }
1412            final DayStats ds = mDayStats[0];
1413
1414            final long lastSyncTime = (item.eventTime + elapsedTime);
1415            boolean writeStatusNow = false;
1416            if (MESG_SUCCESS.equals(resultMessage)) {
1417                // - if successful, update the successful columns
1418                if (status.lastSuccessTime == 0 || status.lastFailureTime != 0) {
1419                    writeStatusNow = true;
1420                }
1421                status.lastSuccessTime = lastSyncTime;
1422                status.lastSuccessSource = item.source;
1423                status.lastFailureTime = 0;
1424                status.lastFailureSource = -1;
1425                status.lastFailureMesg = null;
1426                status.initialFailureTime = 0;
1427                ds.successCount++;
1428                ds.successTime += elapsedTime;
1429            } else if (!MESG_CANCELED.equals(resultMessage)) {
1430                if (status.lastFailureTime == 0) {
1431                    writeStatusNow = true;
1432                }
1433                status.lastFailureTime = lastSyncTime;
1434                status.lastFailureSource = item.source;
1435                status.lastFailureMesg = resultMessage;
1436                if (status.initialFailureTime == 0) {
1437                    status.initialFailureTime = lastSyncTime;
1438                }
1439                ds.failureCount++;
1440                ds.failureTime += elapsedTime;
1441            }
1442
1443            if (writeStatusNow) {
1444                writeStatusLocked();
1445            } else if (!hasMessages(MSG_WRITE_STATUS)) {
1446                sendMessageDelayed(obtainMessage(MSG_WRITE_STATUS),
1447                        WRITE_STATUS_DELAY);
1448            }
1449            if (writeStatisticsNow) {
1450                writeStatisticsLocked();
1451            } else if (!hasMessages(MSG_WRITE_STATISTICS)) {
1452                sendMessageDelayed(obtainMessage(MSG_WRITE_STATISTICS),
1453                        WRITE_STATISTICS_DELAY);
1454            }
1455        }
1456
1457        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
1458    }
1459
1460    /**
1461     * Return a list of the currently active syncs. Note that the returned
1462     * items are the real, live active sync objects, so be careful what you do
1463     * with it.
1464     */
1465    private List<SyncInfo> getCurrentSyncs(int userId) {
1466        synchronized (mAuthorities) {
1467            return getCurrentSyncsLocked(userId);
1468        }
1469    }
1470
1471    /**
1472     * @param userId Id of user to return current sync info.
1473     * @param canAccessAccounts Determines whether to redact Account information from the result.
1474     * @return a copy of the current syncs data structure. Will not return null.
1475     */
1476    public List<SyncInfo> getCurrentSyncsCopy(int userId, boolean canAccessAccounts) {
1477        synchronized (mAuthorities) {
1478            final List<SyncInfo> syncs = getCurrentSyncsLocked(userId);
1479            final List<SyncInfo> syncsCopy = new ArrayList<SyncInfo>();
1480            for (SyncInfo sync : syncs) {
1481                SyncInfo copy;
1482                if (!canAccessAccounts) {
1483                    copy = SyncInfo.createAccountRedacted(
1484                        sync.authorityId, sync.authority, sync.startTime);
1485                } else {
1486                    copy = new SyncInfo(sync);
1487                }
1488                syncsCopy.add(copy);
1489            }
1490            return syncsCopy;
1491        }
1492    }
1493
1494    private List<SyncInfo> getCurrentSyncsLocked(int userId) {
1495        ArrayList<SyncInfo> syncs = mCurrentSyncs.get(userId);
1496        if (syncs == null) {
1497            syncs = new ArrayList<SyncInfo>();
1498            mCurrentSyncs.put(userId, syncs);
1499        }
1500        return syncs;
1501    }
1502
1503    /**
1504     * Return an array of the current sync status for all authorities.  Note
1505     * that the objects inside the array are the real, live status objects,
1506     * so be careful what you do with them.
1507     */
1508    public ArrayList<SyncStatusInfo> getSyncStatus() {
1509        synchronized (mAuthorities) {
1510            final int N = mSyncStatus.size();
1511            ArrayList<SyncStatusInfo> ops = new ArrayList<SyncStatusInfo>(N);
1512            for (int i=0; i<N; i++) {
1513                ops.add(mSyncStatus.valueAt(i));
1514            }
1515            return ops;
1516        }
1517    }
1518
1519    /**
1520     * Return a copy of the specified target with the corresponding sync status
1521     */
1522    public Pair<AuthorityInfo, SyncStatusInfo> getCopyOfAuthorityWithSyncStatus(EndPoint info) {
1523        synchronized (mAuthorities) {
1524            AuthorityInfo authorityInfo = getOrCreateAuthorityLocked(info,
1525                    -1 /* assign a new identifier if creating a new target */,
1526                    true /* write to storage if this results in a change */);
1527            return createCopyPairOfAuthorityWithSyncStatusLocked(authorityInfo);
1528        }
1529    }
1530
1531    /**
1532     * Return a copy of all authorities with their corresponding sync status
1533     */
1534    public ArrayList<Pair<AuthorityInfo, SyncStatusInfo>> getCopyOfAllAuthoritiesWithSyncStatus() {
1535        synchronized (mAuthorities) {
1536            ArrayList<Pair<AuthorityInfo, SyncStatusInfo>> infos =
1537                    new ArrayList<Pair<AuthorityInfo, SyncStatusInfo>>(mAuthorities.size());
1538            for (int i = 0; i < mAuthorities.size(); i++) {
1539                infos.add(createCopyPairOfAuthorityWithSyncStatusLocked(mAuthorities.valueAt(i)));
1540            }
1541            return infos;
1542        }
1543    }
1544
1545    /**
1546     * Returns the status that matches the target.
1547     *
1548     * @param info the endpoint target we are querying status info for.
1549     * @return the SyncStatusInfo for the endpoint.
1550     */
1551    public SyncStatusInfo getStatusByAuthority(EndPoint info) {
1552        if (info.target_provider && (info.account == null || info.provider == null)) {
1553            return null;
1554        } else if (info.target_service && info.service == null) {
1555            return null;
1556        }
1557        synchronized (mAuthorities) {
1558            final int N = mSyncStatus.size();
1559            for (int i = 0; i < N; i++) {
1560                SyncStatusInfo cur = mSyncStatus.valueAt(i);
1561                AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
1562                if (ainfo != null
1563                        && ainfo.target.matchesSpec(info)) {
1564                  return cur;
1565                }
1566            }
1567            return null;
1568        }
1569    }
1570
1571    /** Return true if the pending status is true of any matching authorities. */
1572    public boolean isSyncPending(EndPoint info) {
1573        synchronized (mAuthorities) {
1574            final int N = mSyncStatus.size();
1575            for (int i = 0; i < N; i++) {
1576                SyncStatusInfo cur = mSyncStatus.valueAt(i);
1577                AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
1578                if (ainfo == null) {
1579                    continue;
1580                }
1581                if (!ainfo.target.matchesSpec(info)) {
1582                    continue;
1583                }
1584                if (cur.pending) {
1585                    return true;
1586                }
1587            }
1588            return false;
1589        }
1590    }
1591
1592    /**
1593     * Return an array of the current sync status for all authorities.  Note
1594     * that the objects inside the array are the real, live status objects,
1595     * so be careful what you do with them.
1596     */
1597    public ArrayList<SyncHistoryItem> getSyncHistory() {
1598        synchronized (mAuthorities) {
1599            final int N = mSyncHistory.size();
1600            ArrayList<SyncHistoryItem> items = new ArrayList<SyncHistoryItem>(N);
1601            for (int i=0; i<N; i++) {
1602                items.add(mSyncHistory.get(i));
1603            }
1604            return items;
1605        }
1606    }
1607
1608    /**
1609     * Return an array of the current per-day statistics.  Note
1610     * that the objects inside the array are the real, live status objects,
1611     * so be careful what you do with them.
1612     */
1613    public DayStats[] getDayStatistics() {
1614        synchronized (mAuthorities) {
1615            DayStats[] ds = new DayStats[mDayStats.length];
1616            System.arraycopy(mDayStats, 0, ds, 0, ds.length);
1617            return ds;
1618        }
1619    }
1620
1621    private Pair<AuthorityInfo, SyncStatusInfo> createCopyPairOfAuthorityWithSyncStatusLocked(
1622            AuthorityInfo authorityInfo) {
1623        SyncStatusInfo syncStatusInfo = getOrCreateSyncStatusLocked(authorityInfo.ident);
1624        return Pair.create(new AuthorityInfo(authorityInfo), new SyncStatusInfo(syncStatusInfo));
1625    }
1626
1627    private int getCurrentDayLocked() {
1628        mCal.setTimeInMillis(System.currentTimeMillis());
1629        final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR);
1630        if (mYear != mCal.get(Calendar.YEAR)) {
1631            mYear = mCal.get(Calendar.YEAR);
1632            mCal.clear();
1633            mCal.set(Calendar.YEAR, mYear);
1634            mYearInDays = (int)(mCal.getTimeInMillis()/86400000);
1635        }
1636        return dayOfYear + mYearInDays;
1637    }
1638
1639    /**
1640     * Retrieve a target's full info, returning null if one does not exist.
1641     *
1642     * @param info info of the target to look up.
1643     * @param tag If non-null, this will be used in a log message if the
1644     * requested target does not exist.
1645     */
1646    private AuthorityInfo getAuthorityLocked(EndPoint info, String tag) {
1647        if (info.target_service) {
1648            SparseArray<AuthorityInfo> aInfo = mServices.get(info.service);
1649            AuthorityInfo authority = null;
1650            if (aInfo != null) {
1651                authority = aInfo.get(info.userId);
1652            }
1653            if (authority == null) {
1654                if (tag != null) {
1655                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
1656                        Log.v(TAG, tag + " No authority info found for " + info.service + " for"
1657                                + " user " + info.userId);
1658                    }
1659                }
1660                return null;
1661            }
1662            return authority;
1663        } else if (info.target_provider){
1664            AccountAndUser au = new AccountAndUser(info.account, info.userId);
1665            AccountInfo accountInfo = mAccounts.get(au);
1666            if (accountInfo == null) {
1667                if (tag != null) {
1668                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
1669                        Log.v(TAG, tag + ": unknown account " + au);
1670                    }
1671                }
1672                return null;
1673            }
1674            AuthorityInfo authority = accountInfo.authorities.get(info.provider);
1675            if (authority == null) {
1676                if (tag != null) {
1677                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
1678                        Log.v(TAG, tag + ": unknown provider " + info.provider);
1679                    }
1680                }
1681                return null;
1682            }
1683            return authority;
1684        } else {
1685            Log.e(TAG, tag + " Authority : " + info + ", invalid target");
1686            return null;
1687        }
1688    }
1689
1690    /**
1691     * @param info info identifying target.
1692     * @param ident unique identifier for target. -1 for none.
1693     * @param doWrite if true, update the accounts.xml file on the disk.
1694     * @return the authority that corresponds to the provided sync target, creating it if none
1695     * exists.
1696     */
1697    private AuthorityInfo getOrCreateAuthorityLocked(EndPoint info, int ident, boolean doWrite) {
1698        AuthorityInfo authority = null;
1699        if (info.target_service) {
1700            SparseArray<AuthorityInfo> aInfo = mServices.get(info.service);
1701            if (aInfo == null) {
1702                aInfo = new SparseArray<AuthorityInfo>();
1703                mServices.put(info.service, aInfo);
1704            }
1705            authority = aInfo.get(info.userId);
1706            if (authority == null) {
1707                authority = createAuthorityLocked(info, ident, doWrite);
1708                aInfo.put(info.userId, authority);
1709            }
1710        } else if (info.target_provider) {
1711            AccountAndUser au = new AccountAndUser(info.account, info.userId);
1712            AccountInfo account = mAccounts.get(au);
1713            if (account == null) {
1714                account = new AccountInfo(au);
1715                mAccounts.put(au, account);
1716            }
1717            authority = account.authorities.get(info.provider);
1718            if (authority == null) {
1719                authority = createAuthorityLocked(info, ident, doWrite);
1720                account.authorities.put(info.provider, authority);
1721            }
1722        }
1723        return authority;
1724    }
1725
1726    private AuthorityInfo createAuthorityLocked(EndPoint info, int ident, boolean doWrite) {
1727        AuthorityInfo authority;
1728        if (ident < 0) {
1729            ident = mNextAuthorityId;
1730            mNextAuthorityId++;
1731            doWrite = true;
1732        }
1733        if (Log.isLoggable(TAG, Log.VERBOSE)) {
1734            Log.v(TAG, "created a new AuthorityInfo for " + info);
1735        }
1736        authority = new AuthorityInfo(info, ident);
1737        mAuthorities.put(ident, authority);
1738        if (doWrite) {
1739            writeAccountInfoLocked();
1740        }
1741        return authority;
1742    }
1743
1744    public void removeAuthority(EndPoint info) {
1745        synchronized (mAuthorities) {
1746            if (info.target_provider) {
1747                removeAuthorityLocked(info.account, info.userId, info.provider, true /* doWrite */);
1748            } else {
1749                SparseArray<AuthorityInfo> aInfos = mServices.get(info.service);
1750                if (aInfos != null) {
1751                    AuthorityInfo authorityInfo = aInfos.get(info.userId);
1752                    if (authorityInfo != null) {
1753                        mAuthorities.remove(authorityInfo.ident);
1754                        aInfos.delete(info.userId);
1755                        writeAccountInfoLocked();
1756                    }
1757                }
1758
1759            }
1760        }
1761    }
1762
1763    /**
1764     * Remove an authority associated with a provider. Needs to be a standalone function for
1765     * backward compatibility.
1766     */
1767    private void removeAuthorityLocked(Account account, int userId, String authorityName,
1768            boolean doWrite) {
1769        AccountInfo accountInfo = mAccounts.get(new AccountAndUser(account, userId));
1770        if (accountInfo != null) {
1771            final AuthorityInfo authorityInfo = accountInfo.authorities.remove(authorityName);
1772            if (authorityInfo != null) {
1773                mAuthorities.remove(authorityInfo.ident);
1774                if (doWrite) {
1775                    writeAccountInfoLocked();
1776                }
1777            }
1778        }
1779    }
1780
1781    /**
1782     * Updates (in a synchronized way) the periodic sync time of the specified
1783     * target id and target periodic sync
1784     */
1785    public void setPeriodicSyncTime(int authorityId, PeriodicSync targetPeriodicSync, long when) {
1786        boolean found = false;
1787        final AuthorityInfo authorityInfo;
1788        synchronized (mAuthorities) {
1789            authorityInfo = mAuthorities.get(authorityId);
1790            for (int i = 0; i < authorityInfo.periodicSyncs.size(); i++) {
1791                PeriodicSync periodicSync = authorityInfo.periodicSyncs.get(i);
1792                if (targetPeriodicSync.equals(periodicSync)) {
1793                    mSyncStatus.get(authorityId).setPeriodicSyncTime(i, when);
1794                    found = true;
1795                    break;
1796                }
1797            }
1798        }
1799        if (!found) {
1800            Log.w(TAG, "Ignoring setPeriodicSyncTime request for a sync that does not exist. " +
1801                    "Authority: " + authorityInfo.target);
1802        }
1803    }
1804
1805    private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) {
1806        SyncStatusInfo status = mSyncStatus.get(authorityId);
1807        if (status == null) {
1808            status = new SyncStatusInfo(authorityId);
1809            mSyncStatus.put(authorityId, status);
1810        }
1811        return status;
1812    }
1813
1814    public void writeAllState() {
1815        synchronized (mAuthorities) {
1816            // Account info is always written so no need to do it here.
1817
1818            if (mNumPendingFinished > 0) {
1819                // Only write these if they are out of date.
1820                writePendingOperationsLocked();
1821            }
1822
1823            // Just always write these...  they are likely out of date.
1824            writeStatusLocked();
1825            writeStatisticsLocked();
1826        }
1827    }
1828
1829    /**
1830     * public for testing
1831     */
1832    public void clearAndReadState() {
1833        synchronized (mAuthorities) {
1834            mAuthorities.clear();
1835            mAccounts.clear();
1836            mServices.clear();
1837            mPendingOperations.clear();
1838            mSyncStatus.clear();
1839            mSyncHistory.clear();
1840
1841            readAccountInfoLocked();
1842            readStatusLocked();
1843            readPendingOperationsLocked();
1844            readStatisticsLocked();
1845            readAndDeleteLegacyAccountInfoLocked();
1846            writeAccountInfoLocked();
1847            writeStatusLocked();
1848            writePendingOperationsLocked();
1849            writeStatisticsLocked();
1850        }
1851    }
1852
1853    /**
1854     * Read all account information back in to the initial engine state.
1855     */
1856    private void readAccountInfoLocked() {
1857        int highestAuthorityId = -1;
1858        FileInputStream fis = null;
1859        try {
1860            fis = mAccountInfoFile.openRead();
1861            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
1862                Log.v(TAG_FILE, "Reading " + mAccountInfoFile.getBaseFile());
1863            }
1864            XmlPullParser parser = Xml.newPullParser();
1865            parser.setInput(fis, StandardCharsets.UTF_8.name());
1866            int eventType = parser.getEventType();
1867            while (eventType != XmlPullParser.START_TAG &&
1868                    eventType != XmlPullParser.END_DOCUMENT) {
1869                eventType = parser.next();
1870            }
1871            if (eventType == XmlPullParser.END_DOCUMENT) {
1872                Log.i(TAG, "No initial accounts");
1873                return;
1874            }
1875
1876            String tagName = parser.getName();
1877            if ("accounts".equals(tagName)) {
1878                String listen = parser.getAttributeValue(null, XML_ATTR_LISTEN_FOR_TICKLES);
1879                String versionString = parser.getAttributeValue(null, "version");
1880                int version;
1881                try {
1882                    version = (versionString == null) ? 0 : Integer.parseInt(versionString);
1883                } catch (NumberFormatException e) {
1884                    version = 0;
1885                }
1886                String nextIdString = parser.getAttributeValue(null, XML_ATTR_NEXT_AUTHORITY_ID);
1887                try {
1888                    int id = (nextIdString == null) ? 0 : Integer.parseInt(nextIdString);
1889                    mNextAuthorityId = Math.max(mNextAuthorityId, id);
1890                } catch (NumberFormatException e) {
1891                    // don't care
1892                }
1893                String offsetString = parser.getAttributeValue(null, XML_ATTR_SYNC_RANDOM_OFFSET);
1894                try {
1895                    mSyncRandomOffset = (offsetString == null) ? 0 : Integer.parseInt(offsetString);
1896                } catch (NumberFormatException e) {
1897                    mSyncRandomOffset = 0;
1898                }
1899                if (mSyncRandomOffset == 0) {
1900                    Random random = new Random(System.currentTimeMillis());
1901                    mSyncRandomOffset = random.nextInt(86400);
1902                }
1903                mMasterSyncAutomatically.put(0, listen == null || Boolean.parseBoolean(listen));
1904                eventType = parser.next();
1905                AuthorityInfo authority = null;
1906                PeriodicSync periodicSync = null;
1907                do {
1908                    if (eventType == XmlPullParser.START_TAG) {
1909                        tagName = parser.getName();
1910                        if (parser.getDepth() == 2) {
1911                            if ("authority".equals(tagName)) {
1912                                authority = parseAuthority(parser, version);
1913                                periodicSync = null;
1914                                if (authority.ident > highestAuthorityId) {
1915                                    highestAuthorityId = authority.ident;
1916                                }
1917                            } else if (XML_TAG_LISTEN_FOR_TICKLES.equals(tagName)) {
1918                                parseListenForTickles(parser);
1919                            }
1920                        } else if (parser.getDepth() == 3) {
1921                            if ("periodicSync".equals(tagName) && authority != null) {
1922                                periodicSync = parsePeriodicSync(parser, authority);
1923                            }
1924                        } else if (parser.getDepth() == 4 && periodicSync != null) {
1925                            if ("extra".equals(tagName)) {
1926                                parseExtra(parser, periodicSync.extras);
1927                            }
1928                        }
1929                    }
1930                    eventType = parser.next();
1931                } while (eventType != XmlPullParser.END_DOCUMENT);
1932            }
1933        } catch (XmlPullParserException e) {
1934            Log.w(TAG, "Error reading accounts", e);
1935            return;
1936        } catch (java.io.IOException e) {
1937            if (fis == null) Log.i(TAG, "No initial accounts");
1938            else Log.w(TAG, "Error reading accounts", e);
1939            return;
1940        } finally {
1941            mNextAuthorityId = Math.max(highestAuthorityId + 1, mNextAuthorityId);
1942            if (fis != null) {
1943                try {
1944                    fis.close();
1945                } catch (java.io.IOException e1) {
1946                }
1947            }
1948        }
1949
1950        maybeMigrateSettingsForRenamedAuthorities();
1951    }
1952
1953    /**
1954     * Ensure the old pending.bin is deleted, as it has been changed to pending.xml.
1955     * pending.xml was used starting in KLP.
1956     * @param syncDir directory where the sync files are located.
1957     */
1958    private void maybeDeleteLegacyPendingInfoLocked(File syncDir) {
1959        File file = new File(syncDir, "pending.bin");
1960        if (!file.exists()) {
1961            return;
1962        } else {
1963            file.delete();
1964        }
1965    }
1966
1967    /**
1968     * some authority names have changed. copy over their settings and delete the old ones
1969     * @return true if a change was made
1970     */
1971    private boolean maybeMigrateSettingsForRenamedAuthorities() {
1972        boolean writeNeeded = false;
1973
1974        ArrayList<AuthorityInfo> authoritiesToRemove = new ArrayList<AuthorityInfo>();
1975        final int N = mAuthorities.size();
1976        for (int i = 0; i < N; i++) {
1977            AuthorityInfo authority = mAuthorities.valueAt(i);
1978            // skip this authority if it doesn't target a provider
1979            if (authority.target.target_service) {
1980                continue;
1981            }
1982            // skip this authority if it isn't one of the renamed ones
1983            final String newAuthorityName = sAuthorityRenames.get(authority.target.provider);
1984            if (newAuthorityName == null) {
1985                continue;
1986            }
1987
1988            // remember this authority so we can remove it later. we can't remove it
1989            // now without messing up this loop iteration
1990            authoritiesToRemove.add(authority);
1991
1992            // this authority isn't enabled, no need to copy it to the new authority name since
1993            // the default is "disabled"
1994            if (!authority.enabled) {
1995                continue;
1996            }
1997
1998            // if we already have a record of this new authority then don't copy over the settings
1999            EndPoint newInfo =
2000                    new EndPoint(authority.target.account,
2001                            newAuthorityName,
2002                            authority.target.userId);
2003            if (getAuthorityLocked(newInfo, "cleanup") != null) {
2004                continue;
2005            }
2006
2007            AuthorityInfo newAuthority =
2008                    getOrCreateAuthorityLocked(newInfo, -1 /* ident */, false /* doWrite */);
2009            newAuthority.enabled = true;
2010            writeNeeded = true;
2011        }
2012
2013        for (AuthorityInfo authorityInfo : authoritiesToRemove) {
2014            removeAuthorityLocked(
2015                    authorityInfo.target.account,
2016                    authorityInfo.target.userId,
2017                    authorityInfo.target.provider,
2018                    false /* doWrite */);
2019            writeNeeded = true;
2020        }
2021
2022        return writeNeeded;
2023    }
2024
2025    private void parseListenForTickles(XmlPullParser parser) {
2026        String user = parser.getAttributeValue(null, XML_ATTR_USER);
2027        int userId = 0;
2028        try {
2029            userId = Integer.parseInt(user);
2030        } catch (NumberFormatException e) {
2031            Log.e(TAG, "error parsing the user for listen-for-tickles", e);
2032        } catch (NullPointerException e) {
2033            Log.e(TAG, "the user in listen-for-tickles is null", e);
2034        }
2035        String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED);
2036        boolean listen = enabled == null || Boolean.parseBoolean(enabled);
2037        mMasterSyncAutomatically.put(userId, listen);
2038    }
2039
2040    private AuthorityInfo parseAuthority(XmlPullParser parser, int version) {
2041        AuthorityInfo authority = null;
2042        int id = -1;
2043        try {
2044            id = Integer.parseInt(parser.getAttributeValue(null, "id"));
2045        } catch (NumberFormatException e) {
2046            Log.e(TAG, "error parsing the id of the authority", e);
2047        } catch (NullPointerException e) {
2048            Log.e(TAG, "the id of the authority is null", e);
2049        }
2050        if (id >= 0) {
2051            String authorityName = parser.getAttributeValue(null, "authority");
2052            String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED);
2053            String syncable = parser.getAttributeValue(null, "syncable");
2054            String accountName = parser.getAttributeValue(null, "account");
2055            String accountType = parser.getAttributeValue(null, "type");
2056            String user = parser.getAttributeValue(null, XML_ATTR_USER);
2057            String packageName = parser.getAttributeValue(null, "package");
2058            String className = parser.getAttributeValue(null, "class");
2059            int userId = user == null ? 0 : Integer.parseInt(user);
2060            if (accountType == null && packageName == null) {
2061                accountType = "com.google";
2062                syncable = String.valueOf(AuthorityInfo.NOT_INITIALIZED);
2063            }
2064            authority = mAuthorities.get(id);
2065            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2066                Log.v(TAG_FILE, "Adding authority:"
2067                        + " account=" + accountName
2068                        + " accountType=" + accountType
2069                        + " auth=" + authorityName
2070                        + " package=" + packageName
2071                        + " class=" + className
2072                        + " user=" + userId
2073                        + " enabled=" + enabled
2074                        + " syncable=" + syncable);
2075            }
2076            if (authority == null) {
2077                if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2078                    Log.v(TAG_FILE, "Creating authority entry");
2079                }
2080                EndPoint info;
2081                if (accountName != null && authorityName != null) {
2082                    info = new EndPoint(
2083                            new Account(accountName, accountType),
2084                            authorityName, userId);
2085                } else {
2086                    final ComponentName cname = new ComponentName(packageName, className);
2087                    android.content.pm.ServiceInfo sinfo = null;
2088                    try {
2089                        sinfo = mContext.getPackageManager().getServiceInfo(cname, userId);
2090                    } catch (PackageManager.NameNotFoundException e) {
2091                        Slog.w(TAG, "Not restoring sync " + cname
2092                                + " -- can't find service for user " + userId);
2093                    }
2094                    if (sinfo != null) {
2095                        info = new EndPoint(cname, userId, sinfo.applicationInfo.uid);
2096                    } else {
2097                        info = null;
2098                    }
2099                }
2100                if (info != null) {
2101                    authority = getOrCreateAuthorityLocked(info, id, false);
2102                    // If the version is 0 then we are upgrading from a file format that did not
2103                    // know about periodic syncs. In that case don't clear the list since we
2104                    // want the default, which is a daily periodic sync.
2105                    // Otherwise clear out this default list since we will populate it later with
2106                    // the periodic sync descriptions that are read from the configuration file.
2107                    if (version > 0) {
2108                        authority.periodicSyncs.clear();
2109                    }
2110                }
2111            }
2112            if (authority != null) {
2113                authority.enabled = enabled == null || Boolean.parseBoolean(enabled);
2114                try {
2115                    authority.syncable = (syncable == null) ?
2116                            AuthorityInfo.NOT_INITIALIZED : Integer.parseInt(syncable);
2117                } catch (NumberFormatException e) {
2118                    // On L we stored this as {"unknown", "true", "false"} so fall back to this
2119                    // format.
2120                    if ("unknown".equals(syncable)) {
2121                        authority.syncable = AuthorityInfo.NOT_INITIALIZED;
2122                    } else {
2123                        authority.syncable = Boolean.parseBoolean(syncable) ?
2124                                AuthorityInfo.SYNCABLE : AuthorityInfo.NOT_SYNCABLE;
2125                    }
2126
2127                }
2128            } else {
2129                Log.w(TAG, "Failure adding authority: account="
2130                        + accountName + " auth=" + authorityName
2131                        + " enabled=" + enabled
2132                        + " syncable=" + syncable);
2133            }
2134        }
2135        return authority;
2136    }
2137
2138    /**
2139     * Parse a periodic sync from accounts.xml. Sets the bundle to be empty.
2140     */
2141    private PeriodicSync parsePeriodicSync(XmlPullParser parser, AuthorityInfo authorityInfo) {
2142        Bundle extras = new Bundle(); // Gets filled in later.
2143        String periodValue = parser.getAttributeValue(null, "period");
2144        String flexValue = parser.getAttributeValue(null, "flex");
2145        final long period;
2146        long flextime;
2147        try {
2148            period = Long.parseLong(periodValue);
2149        } catch (NumberFormatException e) {
2150            Log.e(TAG, "error parsing the period of a periodic sync", e);
2151            return null;
2152        } catch (NullPointerException e) {
2153            Log.e(TAG, "the period of a periodic sync is null", e);
2154            return null;
2155        }
2156        try {
2157            flextime = Long.parseLong(flexValue);
2158        } catch (NumberFormatException e) {
2159            flextime = calculateDefaultFlexTime(period);
2160            Log.e(TAG, "Error formatting value parsed for periodic sync flex: " + flexValue
2161                    + ", using default: "
2162                    + flextime);
2163        } catch (NullPointerException expected) {
2164            flextime = calculateDefaultFlexTime(period);
2165            Log.d(TAG, "No flex time specified for this sync, using a default. period: "
2166            + period + " flex: " + flextime);
2167        }
2168        PeriodicSync periodicSync;
2169        if (authorityInfo.target.target_provider) {
2170            periodicSync =
2171                new PeriodicSync(authorityInfo.target.account,
2172                        authorityInfo.target.provider,
2173                        extras,
2174                        period, flextime);
2175        } else {
2176            Log.e(TAG, "Unknown target.");
2177            return null;
2178        }
2179        authorityInfo.periodicSyncs.add(periodicSync);
2180        return periodicSync;
2181    }
2182
2183    private void parseExtra(XmlPullParser parser, Bundle extras) {
2184        String name = parser.getAttributeValue(null, "name");
2185        String type = parser.getAttributeValue(null, "type");
2186        String value1 = parser.getAttributeValue(null, "value1");
2187        String value2 = parser.getAttributeValue(null, "value2");
2188
2189        try {
2190            if ("long".equals(type)) {
2191                extras.putLong(name, Long.parseLong(value1));
2192            } else if ("integer".equals(type)) {
2193                extras.putInt(name, Integer.parseInt(value1));
2194            } else if ("double".equals(type)) {
2195                extras.putDouble(name, Double.parseDouble(value1));
2196            } else if ("float".equals(type)) {
2197                extras.putFloat(name, Float.parseFloat(value1));
2198            } else if ("boolean".equals(type)) {
2199                extras.putBoolean(name, Boolean.parseBoolean(value1));
2200            } else if ("string".equals(type)) {
2201                extras.putString(name, value1);
2202            } else if ("account".equals(type)) {
2203                extras.putParcelable(name, new Account(value1, value2));
2204            }
2205        } catch (NumberFormatException e) {
2206            Log.e(TAG, "error parsing bundle value", e);
2207        } catch (NullPointerException e) {
2208            Log.e(TAG, "error parsing bundle value", e);
2209        }
2210    }
2211
2212    /**
2213     * Write all account information to the account file.
2214     */
2215    private void writeAccountInfoLocked() {
2216        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2217            Log.v(TAG_FILE, "Writing new " + mAccountInfoFile.getBaseFile());
2218        }
2219        FileOutputStream fos = null;
2220
2221        try {
2222            fos = mAccountInfoFile.startWrite();
2223            XmlSerializer out = new FastXmlSerializer();
2224            out.setOutput(fos, StandardCharsets.UTF_8.name());
2225            out.startDocument(null, true);
2226            out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
2227
2228            out.startTag(null, "accounts");
2229            out.attribute(null, "version", Integer.toString(ACCOUNTS_VERSION));
2230            out.attribute(null, XML_ATTR_NEXT_AUTHORITY_ID, Integer.toString(mNextAuthorityId));
2231            out.attribute(null, XML_ATTR_SYNC_RANDOM_OFFSET, Integer.toString(mSyncRandomOffset));
2232
2233            // Write the Sync Automatically flags for each user
2234            final int M = mMasterSyncAutomatically.size();
2235            for (int m = 0; m < M; m++) {
2236                int userId = mMasterSyncAutomatically.keyAt(m);
2237                Boolean listen = mMasterSyncAutomatically.valueAt(m);
2238                out.startTag(null, XML_TAG_LISTEN_FOR_TICKLES);
2239                out.attribute(null, XML_ATTR_USER, Integer.toString(userId));
2240                out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(listen));
2241                out.endTag(null, XML_TAG_LISTEN_FOR_TICKLES);
2242            }
2243
2244            final int N = mAuthorities.size();
2245            for (int i = 0; i < N; i++) {
2246                AuthorityInfo authority = mAuthorities.valueAt(i);
2247                EndPoint info = authority.target;
2248                out.startTag(null, "authority");
2249                out.attribute(null, "id", Integer.toString(authority.ident));
2250                out.attribute(null, XML_ATTR_USER, Integer.toString(info.userId));
2251                out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(authority.enabled));
2252                if (info.service == null) {
2253                    out.attribute(null, "account", info.account.name);
2254                    out.attribute(null, "type", info.account.type);
2255                    out.attribute(null, "authority", info.provider);
2256                } else {
2257                    out.attribute(null, "package", info.service.getPackageName());
2258                    out.attribute(null, "class", info.service.getClassName());
2259                }
2260                out.attribute(null, "syncable", Integer.toString(authority.syncable));
2261                for (PeriodicSync periodicSync : authority.periodicSyncs) {
2262                    out.startTag(null, "periodicSync");
2263                    out.attribute(null, "period", Long.toString(periodicSync.period));
2264                    out.attribute(null, "flex", Long.toString(periodicSync.flexTime));
2265                    final Bundle extras = periodicSync.extras;
2266                    extrasToXml(out, extras);
2267                    out.endTag(null, "periodicSync");
2268                }
2269                out.endTag(null, "authority");
2270            }
2271            out.endTag(null, "accounts");
2272            out.endDocument();
2273            mAccountInfoFile.finishWrite(fos);
2274        } catch (java.io.IOException e1) {
2275            Log.w(TAG, "Error writing accounts", e1);
2276            if (fos != null) {
2277                mAccountInfoFile.failWrite(fos);
2278            }
2279        }
2280    }
2281
2282    static int getIntColumn(Cursor c, String name) {
2283        return c.getInt(c.getColumnIndex(name));
2284    }
2285
2286    static long getLongColumn(Cursor c, String name) {
2287        return c.getLong(c.getColumnIndex(name));
2288    }
2289
2290    /**
2291     * Load sync engine state from the old syncmanager database, and then
2292     * erase it.  Note that we don't deal with pending operations, active
2293     * sync, or history.
2294     */
2295    private void readAndDeleteLegacyAccountInfoLocked() {
2296        // Look for old database to initialize from.
2297        File file = mContext.getDatabasePath("syncmanager.db");
2298        if (!file.exists()) {
2299            return;
2300        }
2301        String path = file.getPath();
2302        SQLiteDatabase db = null;
2303        try {
2304            db = SQLiteDatabase.openDatabase(path, null,
2305                    SQLiteDatabase.OPEN_READONLY);
2306        } catch (SQLiteException e) {
2307        }
2308
2309        if (db != null) {
2310            final boolean hasType = db.getVersion() >= 11;
2311
2312            // Copy in all of the status information, as well as accounts.
2313            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2314                Log.v(TAG_FILE, "Reading legacy sync accounts db");
2315            }
2316            SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
2317            qb.setTables("stats, status");
2318            HashMap<String,String> map = new HashMap<String,String>();
2319            map.put("_id", "status._id as _id");
2320            map.put("account", "stats.account as account");
2321            if (hasType) {
2322                map.put("account_type", "stats.account_type as account_type");
2323            }
2324            map.put("authority", "stats.authority as authority");
2325            map.put("totalElapsedTime", "totalElapsedTime");
2326            map.put("numSyncs", "numSyncs");
2327            map.put("numSourceLocal", "numSourceLocal");
2328            map.put("numSourcePoll", "numSourcePoll");
2329            map.put("numSourceServer", "numSourceServer");
2330            map.put("numSourceUser", "numSourceUser");
2331            map.put("lastSuccessSource", "lastSuccessSource");
2332            map.put("lastSuccessTime", "lastSuccessTime");
2333            map.put("lastFailureSource", "lastFailureSource");
2334            map.put("lastFailureTime", "lastFailureTime");
2335            map.put("lastFailureMesg", "lastFailureMesg");
2336            map.put("pending", "pending");
2337            qb.setProjectionMap(map);
2338            qb.appendWhere("stats._id = status.stats_id");
2339            Cursor c = qb.query(db, null, null, null, null, null, null);
2340            while (c.moveToNext()) {
2341                String accountName = c.getString(c.getColumnIndex("account"));
2342                String accountType = hasType
2343                        ? c.getString(c.getColumnIndex("account_type")) : null;
2344                if (accountType == null) {
2345                    accountType = "com.google";
2346                }
2347                String authorityName = c.getString(c.getColumnIndex("authority"));
2348                AuthorityInfo authority =
2349                        this.getOrCreateAuthorityLocked(
2350                                new EndPoint(new Account(accountName, accountType),
2351                                        authorityName,
2352                                        0 /* legacy is single-user */)
2353                                , -1,
2354                                false);
2355                if (authority != null) {
2356                    int i = mSyncStatus.size();
2357                    boolean found = false;
2358                    SyncStatusInfo st = null;
2359                    while (i > 0) {
2360                        i--;
2361                        st = mSyncStatus.valueAt(i);
2362                        if (st.authorityId == authority.ident) {
2363                            found = true;
2364                            break;
2365                        }
2366                    }
2367                    if (!found) {
2368                        st = new SyncStatusInfo(authority.ident);
2369                        mSyncStatus.put(authority.ident, st);
2370                    }
2371                    st.totalElapsedTime = getLongColumn(c, "totalElapsedTime");
2372                    st.numSyncs = getIntColumn(c, "numSyncs");
2373                    st.numSourceLocal = getIntColumn(c, "numSourceLocal");
2374                    st.numSourcePoll = getIntColumn(c, "numSourcePoll");
2375                    st.numSourceServer = getIntColumn(c, "numSourceServer");
2376                    st.numSourceUser = getIntColumn(c, "numSourceUser");
2377                    st.numSourcePeriodic = 0;
2378                    st.lastSuccessSource = getIntColumn(c, "lastSuccessSource");
2379                    st.lastSuccessTime = getLongColumn(c, "lastSuccessTime");
2380                    st.lastFailureSource = getIntColumn(c, "lastFailureSource");
2381                    st.lastFailureTime = getLongColumn(c, "lastFailureTime");
2382                    st.lastFailureMesg = c.getString(c.getColumnIndex("lastFailureMesg"));
2383                    st.pending = getIntColumn(c, "pending") != 0;
2384                }
2385            }
2386
2387            c.close();
2388
2389            // Retrieve the settings.
2390            qb = new SQLiteQueryBuilder();
2391            qb.setTables("settings");
2392            c = qb.query(db, null, null, null, null, null, null);
2393            while (c.moveToNext()) {
2394                String name = c.getString(c.getColumnIndex("name"));
2395                String value = c.getString(c.getColumnIndex("value"));
2396                if (name == null) continue;
2397                if (name.equals("listen_for_tickles")) {
2398                    setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value), 0);
2399                } else if (name.startsWith("sync_provider_")) {
2400                    String provider = name.substring("sync_provider_".length(),
2401                            name.length());
2402                    int i = mAuthorities.size();
2403                    while (i > 0) {
2404                        i--;
2405                        AuthorityInfo authority = mAuthorities.valueAt(i);
2406                        if (authority.target.provider.equals(provider)) {
2407                            authority.enabled = value == null || Boolean.parseBoolean(value);
2408                            authority.syncable = 1;
2409                        }
2410                    }
2411                }
2412            }
2413
2414            c.close();
2415
2416            db.close();
2417
2418            (new File(path)).delete();
2419        }
2420    }
2421
2422    public static final int STATUS_FILE_END = 0;
2423    public static final int STATUS_FILE_ITEM = 100;
2424
2425    /**
2426     * Read all sync status back in to the initial engine state.
2427     */
2428    private void readStatusLocked() {
2429        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2430            Log.v(TAG_FILE, "Reading " + mStatusFile.getBaseFile());
2431        }
2432        try {
2433            byte[] data = mStatusFile.readFully();
2434            Parcel in = Parcel.obtain();
2435            in.unmarshall(data, 0, data.length);
2436            in.setDataPosition(0);
2437            int token;
2438            while ((token=in.readInt()) != STATUS_FILE_END) {
2439                if (token == STATUS_FILE_ITEM) {
2440                    SyncStatusInfo status = new SyncStatusInfo(in);
2441                    if (mAuthorities.indexOfKey(status.authorityId) >= 0) {
2442                        status.pending = false;
2443                        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2444                            Log.v(TAG_FILE, "Adding status for id " + status.authorityId);
2445                        }
2446                        mSyncStatus.put(status.authorityId, status);
2447                    }
2448                } else {
2449                    // Ooops.
2450                    Log.w(TAG, "Unknown status token: " + token);
2451                    break;
2452                }
2453            }
2454        } catch (java.io.IOException e) {
2455            Log.i(TAG, "No initial status");
2456        }
2457    }
2458
2459    /**
2460     * Write all sync status to the sync status file.
2461     */
2462    private void writeStatusLocked() {
2463        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2464            Log.v(TAG_FILE, "Writing new " + mStatusFile.getBaseFile());
2465        }
2466
2467        // The file is being written, so we don't need to have a scheduled
2468        // write until the next change.
2469        removeMessages(MSG_WRITE_STATUS);
2470
2471        FileOutputStream fos = null;
2472        try {
2473            fos = mStatusFile.startWrite();
2474            Parcel out = Parcel.obtain();
2475            final int N = mSyncStatus.size();
2476            for (int i=0; i<N; i++) {
2477                SyncStatusInfo status = mSyncStatus.valueAt(i);
2478                out.writeInt(STATUS_FILE_ITEM);
2479                status.writeToParcel(out, 0);
2480            }
2481            out.writeInt(STATUS_FILE_END);
2482            fos.write(out.marshall());
2483            out.recycle();
2484
2485            mStatusFile.finishWrite(fos);
2486        } catch (java.io.IOException e1) {
2487            Log.w(TAG, "Error writing status", e1);
2488            if (fos != null) {
2489                mStatusFile.failWrite(fos);
2490            }
2491        }
2492    }
2493
2494    public static final int PENDING_OPERATION_VERSION = 3;
2495
2496    /** Read all pending operations back in to the initial engine state. */
2497    private void readPendingOperationsLocked() {
2498        FileInputStream fis = null;
2499        if (!mPendingFile.getBaseFile().exists()) {
2500            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2501                Log.v(TAG_FILE, "No pending operation file.");
2502            }
2503            return;
2504        }
2505        try {
2506            fis = mPendingFile.openRead();
2507            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2508                Log.v(TAG_FILE, "Reading " + mPendingFile.getBaseFile());
2509            }
2510            XmlPullParser parser;
2511            parser = Xml.newPullParser();
2512            parser.setInput(fis, StandardCharsets.UTF_8.name());
2513
2514            int eventType = parser.getEventType();
2515            while (eventType != XmlPullParser.START_TAG &&
2516                    eventType != XmlPullParser.END_DOCUMENT) {
2517                eventType = parser.next();
2518            }
2519            if (eventType == XmlPullParser.END_DOCUMENT) return; // Nothing to read.
2520
2521            do {
2522                PendingOperation pop = null;
2523                if (eventType == XmlPullParser.START_TAG) {
2524                    try {
2525                        String tagName = parser.getName();
2526                        if (parser.getDepth() == 1 && "op".equals(tagName)) {
2527                            // Verify version.
2528                            String versionString =
2529                                    parser.getAttributeValue(null, XML_ATTR_VERSION);
2530                            if (versionString == null ||
2531                                    Integer.parseInt(versionString) != PENDING_OPERATION_VERSION) {
2532                                Log.w(TAG, "Unknown pending operation version " + versionString);
2533                                throw new java.io.IOException("Unknown version.");
2534                            }
2535                            int authorityId = Integer.valueOf(parser.getAttributeValue(
2536                                    null, XML_ATTR_AUTHORITYID));
2537                            boolean expedited = Boolean.valueOf(parser.getAttributeValue(
2538                                    null, XML_ATTR_EXPEDITED));
2539                            int syncSource = Integer.valueOf(parser.getAttributeValue(
2540                                    null, XML_ATTR_SOURCE));
2541                            int reason = Integer.valueOf(parser.getAttributeValue(
2542                                    null, XML_ATTR_REASON));
2543                            AuthorityInfo authority = mAuthorities.get(authorityId);
2544                            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2545                                Log.v(TAG_FILE, authorityId + " " + expedited + " " + syncSource + " "
2546                                        + reason);
2547                            }
2548                            if (authority != null) {
2549                                pop = new PendingOperation(
2550                                        authority, reason, syncSource, new Bundle(), expedited);
2551                                pop.flatExtras = null; // No longer used.
2552                                mPendingOperations.add(pop);
2553                                if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2554                                    Log.v(TAG_FILE, "Adding pending op: "
2555                                            + pop.target
2556                                            + " src=" + pop.syncSource
2557                                            + " reason=" + pop.reason
2558                                            + " expedited=" + pop.expedited);
2559                                    }
2560                            } else {
2561                                // Skip non-existent authority.
2562                                pop = null;
2563                                if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2564                                    Log.v(TAG_FILE, "No authority found for " + authorityId
2565                                            + ", skipping");
2566                                }
2567                            }
2568                        } else if (parser.getDepth() == 2 &&
2569                                pop != null &&
2570                                "extra".equals(tagName)) {
2571                            parseExtra(parser, pop.extras);
2572                        }
2573                    } catch (NumberFormatException e) {
2574                        Log.d(TAG, "Invalid data in xml file.", e);
2575                    }
2576                }
2577                eventType = parser.next();
2578            } while(eventType != XmlPullParser.END_DOCUMENT);
2579        } catch (java.io.IOException e) {
2580            Log.w(TAG_FILE, "Error reading pending data.", e);
2581        } catch (XmlPullParserException e) {
2582            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2583                Log.w(TAG_FILE, "Error parsing pending ops xml.", e);
2584            }
2585        } finally {
2586            if (fis != null) {
2587                try {
2588                    fis.close();
2589                } catch (java.io.IOException e1) {}
2590            }
2591        }
2592    }
2593
2594    static private byte[] flattenBundle(Bundle bundle) {
2595        byte[] flatData = null;
2596        Parcel parcel = Parcel.obtain();
2597        try {
2598            bundle.writeToParcel(parcel, 0);
2599            flatData = parcel.marshall();
2600        } finally {
2601            parcel.recycle();
2602        }
2603        return flatData;
2604    }
2605
2606    static private Bundle unflattenBundle(byte[] flatData) {
2607        Bundle bundle;
2608        Parcel parcel = Parcel.obtain();
2609        try {
2610            parcel.unmarshall(flatData, 0, flatData.length);
2611            parcel.setDataPosition(0);
2612            bundle = parcel.readBundle();
2613        } catch (RuntimeException e) {
2614            // A RuntimeException is thrown if we were unable to parse the parcel.
2615            // Create an empty parcel in this case.
2616            bundle = new Bundle();
2617        } finally {
2618            parcel.recycle();
2619        }
2620        return bundle;
2621    }
2622
2623    private static final String XML_ATTR_VERSION = "version";
2624    private static final String XML_ATTR_AUTHORITYID = "authority_id";
2625    private static final String XML_ATTR_SOURCE = "source";
2626    private static final String XML_ATTR_EXPEDITED = "expedited";
2627    private static final String XML_ATTR_REASON = "reason";
2628
2629    /**
2630     * Write all currently pending ops to the pending ops file.
2631     */
2632    private void writePendingOperationsLocked() {
2633        final int N = mPendingOperations.size();
2634        FileOutputStream fos = null;
2635        try {
2636            if (N == 0) {
2637                if (Log.isLoggable(TAG_FILE, Log.VERBOSE)){
2638                    Log.v(TAG, "Truncating " + mPendingFile.getBaseFile());
2639                }
2640                mPendingFile.truncate();
2641                return;
2642            }
2643            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2644                Log.v(TAG, "Writing new " + mPendingFile.getBaseFile());
2645            }
2646            fos = mPendingFile.startWrite();
2647            XmlSerializer out = new FastXmlSerializer();
2648            out.setOutput(fos, StandardCharsets.UTF_8.name());
2649
2650            for (int i = 0; i < N; i++) {
2651                PendingOperation pop = mPendingOperations.get(i);
2652                writePendingOperationLocked(pop, out);
2653             }
2654             out.endDocument();
2655             mPendingFile.finishWrite(fos);
2656        } catch (java.io.IOException e1) {
2657            Log.w(TAG, "Error writing pending operations", e1);
2658            if (fos != null) {
2659                mPendingFile.failWrite(fos);
2660            }
2661        }
2662    }
2663
2664    /** Write all currently pending ops to the pending ops file. */
2665     private void writePendingOperationLocked(PendingOperation pop, XmlSerializer out)
2666             throws IOException {
2667         // Pending operation.
2668         out.startTag(null, "op");
2669
2670         out.attribute(null, XML_ATTR_VERSION, Integer.toString(PENDING_OPERATION_VERSION));
2671         out.attribute(null, XML_ATTR_AUTHORITYID, Integer.toString(pop.authorityId));
2672         out.attribute(null, XML_ATTR_SOURCE, Integer.toString(pop.syncSource));
2673         out.attribute(null, XML_ATTR_EXPEDITED, Boolean.toString(pop.expedited));
2674         out.attribute(null, XML_ATTR_REASON, Integer.toString(pop.reason));
2675         extrasToXml(out, pop.extras);
2676
2677         out.endTag(null, "op");
2678     }
2679
2680    /**
2681     * Append the given operation to the pending ops file; if unable to,
2682     * write all pending ops.
2683     */
2684    private void appendPendingOperationLocked(PendingOperation op) {
2685        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2686            Log.v(TAG, "Appending to " + mPendingFile.getBaseFile());
2687        }
2688        FileOutputStream fos = null;
2689        try {
2690            fos = mPendingFile.openAppend();
2691        } catch (java.io.IOException e) {
2692            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2693                Log.v(TAG, "Failed append; writing full file");
2694            }
2695            writePendingOperationsLocked();
2696            return;
2697        }
2698
2699        try {
2700            XmlSerializer out = new FastXmlSerializer();
2701            out.setOutput(fos, StandardCharsets.UTF_8.name());
2702            writePendingOperationLocked(op, out);
2703            out.endDocument();
2704            mPendingFile.finishWrite(fos);
2705        } catch (java.io.IOException e1) {
2706            Log.w(TAG, "Error writing appending operation", e1);
2707            mPendingFile.failWrite(fos);
2708        } finally {
2709            try {
2710                fos.close();
2711            } catch (IOException e) {}
2712        }
2713    }
2714
2715    private void extrasToXml(XmlSerializer out, Bundle extras) throws java.io.IOException {
2716        for (String key : extras.keySet()) {
2717            out.startTag(null, "extra");
2718            out.attribute(null, "name", key);
2719            final Object value = extras.get(key);
2720            if (value instanceof Long) {
2721                out.attribute(null, "type", "long");
2722                out.attribute(null, "value1", value.toString());
2723            } else if (value instanceof Integer) {
2724                out.attribute(null, "type", "integer");
2725                out.attribute(null, "value1", value.toString());
2726            } else if (value instanceof Boolean) {
2727                out.attribute(null, "type", "boolean");
2728                out.attribute(null, "value1", value.toString());
2729            } else if (value instanceof Float) {
2730                out.attribute(null, "type", "float");
2731                out.attribute(null, "value1", value.toString());
2732            } else if (value instanceof Double) {
2733                out.attribute(null, "type", "double");
2734                out.attribute(null, "value1", value.toString());
2735            } else if (value instanceof String) {
2736                out.attribute(null, "type", "string");
2737                out.attribute(null, "value1", value.toString());
2738            } else if (value instanceof Account) {
2739                out.attribute(null, "type", "account");
2740                out.attribute(null, "value1", ((Account)value).name);
2741                out.attribute(null, "value2", ((Account)value).type);
2742            }
2743            out.endTag(null, "extra");
2744        }
2745    }
2746
2747    private void requestSync(AuthorityInfo authorityInfo, int reason, Bundle extras) {
2748        if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID
2749                && mSyncRequestListener != null) {
2750            mSyncRequestListener.onSyncRequest(authorityInfo.target, reason, extras);
2751        } else {
2752            SyncRequest.Builder req =
2753                    new SyncRequest.Builder()
2754                            .syncOnce()
2755                            .setExtras(extras);
2756            if (authorityInfo.target.target_provider) {
2757                req.setSyncAdapter(authorityInfo.target.account, authorityInfo.target.provider);
2758            } else {
2759                if (Log.isLoggable(TAG, Log.DEBUG)) {
2760                    Log.d(TAG, "Unknown target, skipping sync request.");
2761                }
2762                return;
2763            }
2764            ContentResolver.requestSync(req.build());
2765        }
2766    }
2767
2768    private void requestSync(Account account, int userId, int reason, String authority,
2769            Bundle extras) {
2770        // If this is happening in the system process, then call the syncrequest listener
2771        // to make a request back to the SyncManager directly.
2772        // If this is probably a test instance, then call back through the ContentResolver
2773        // which will know which userId to apply based on the Binder id.
2774        if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID
2775                && mSyncRequestListener != null) {
2776            mSyncRequestListener.onSyncRequest(
2777                new EndPoint(account, authority, userId),
2778                reason,
2779                extras);
2780        } else {
2781            ContentResolver.requestSync(account, authority, extras);
2782        }
2783    }
2784
2785    public static final int STATISTICS_FILE_END = 0;
2786    public static final int STATISTICS_FILE_ITEM_OLD = 100;
2787    public static final int STATISTICS_FILE_ITEM = 101;
2788
2789    /**
2790     * Read all sync statistics back in to the initial engine state.
2791     */
2792    private void readStatisticsLocked() {
2793        try {
2794            byte[] data = mStatisticsFile.readFully();
2795            Parcel in = Parcel.obtain();
2796            in.unmarshall(data, 0, data.length);
2797            in.setDataPosition(0);
2798            int token;
2799            int index = 0;
2800            while ((token=in.readInt()) != STATISTICS_FILE_END) {
2801                if (token == STATISTICS_FILE_ITEM
2802                        || token == STATISTICS_FILE_ITEM_OLD) {
2803                    int day = in.readInt();
2804                    if (token == STATISTICS_FILE_ITEM_OLD) {
2805                        day = day - 2009 + 14245;  // Magic!
2806                    }
2807                    DayStats ds = new DayStats(day);
2808                    ds.successCount = in.readInt();
2809                    ds.successTime = in.readLong();
2810                    ds.failureCount = in.readInt();
2811                    ds.failureTime = in.readLong();
2812                    if (index < mDayStats.length) {
2813                        mDayStats[index] = ds;
2814                        index++;
2815                    }
2816                } else {
2817                    // Ooops.
2818                    Log.w(TAG, "Unknown stats token: " + token);
2819                    break;
2820                }
2821            }
2822        } catch (java.io.IOException e) {
2823            Log.i(TAG, "No initial statistics");
2824        }
2825    }
2826
2827    /**
2828     * Write all sync statistics to the sync status file.
2829     */
2830    private void writeStatisticsLocked() {
2831        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2832            Log.v(TAG, "Writing new " + mStatisticsFile.getBaseFile());
2833        }
2834
2835        // The file is being written, so we don't need to have a scheduled
2836        // write until the next change.
2837        removeMessages(MSG_WRITE_STATISTICS);
2838
2839        FileOutputStream fos = null;
2840        try {
2841            fos = mStatisticsFile.startWrite();
2842            Parcel out = Parcel.obtain();
2843            final int N = mDayStats.length;
2844            for (int i=0; i<N; i++) {
2845                DayStats ds = mDayStats[i];
2846                if (ds == null) {
2847                    break;
2848                }
2849                out.writeInt(STATISTICS_FILE_ITEM);
2850                out.writeInt(ds.day);
2851                out.writeInt(ds.successCount);
2852                out.writeLong(ds.successTime);
2853                out.writeInt(ds.failureCount);
2854                out.writeLong(ds.failureTime);
2855            }
2856            out.writeInt(STATISTICS_FILE_END);
2857            fos.write(out.marshall());
2858            out.recycle();
2859
2860            mStatisticsFile.finishWrite(fos);
2861        } catch (java.io.IOException e1) {
2862            Log.w(TAG, "Error writing stats", e1);
2863            if (fos != null) {
2864                mStatisticsFile.failWrite(fos);
2865            }
2866        }
2867    }
2868
2869    /**
2870     * Dump state of PendingOperations.
2871     */
2872    public void dumpPendingOperations(StringBuilder sb) {
2873        sb.append("Pending Ops: ").append(mPendingOperations.size()).append(" operation(s)\n");
2874        for (PendingOperation pop : mPendingOperations) {
2875            sb.append("(info: " + pop.target.toString())
2876                .append(", extras: " + pop.extras)
2877                .append(")\n");
2878        }
2879    }
2880
2881    /**
2882     * Let the BackupManager know that account sync settings have changed. This will trigger
2883     * {@link com.android.server.backup.SystemBackupAgent} to run.
2884     */
2885    public void queueBackup() {
2886        BackupManager.dataChanged("android");
2887    }
2888}
2889