SyncStorageEngine.java revision bf8fdcdd4c759cf1c34838a064d9e11ccafd51fa
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 != null) {
1915                                    if (authority.ident > highestAuthorityId) {
1916                                        highestAuthorityId = authority.ident;
1917                                    }
1918                                }
1919                            } else if (XML_TAG_LISTEN_FOR_TICKLES.equals(tagName)) {
1920                                parseListenForTickles(parser);
1921                            }
1922                        } else if (parser.getDepth() == 3) {
1923                            if ("periodicSync".equals(tagName) && authority != null) {
1924                                periodicSync = parsePeriodicSync(parser, authority);
1925                            }
1926                        } else if (parser.getDepth() == 4 && periodicSync != null) {
1927                            if ("extra".equals(tagName)) {
1928                                parseExtra(parser, periodicSync.extras);
1929                            }
1930                        }
1931                    }
1932                    eventType = parser.next();
1933                } while (eventType != XmlPullParser.END_DOCUMENT);
1934            }
1935        } catch (XmlPullParserException e) {
1936            Log.w(TAG, "Error reading accounts", e);
1937            return;
1938        } catch (java.io.IOException e) {
1939            if (fis == null) Log.i(TAG, "No initial accounts");
1940            else Log.w(TAG, "Error reading accounts", e);
1941            return;
1942        } finally {
1943            mNextAuthorityId = Math.max(highestAuthorityId + 1, mNextAuthorityId);
1944            if (fis != null) {
1945                try {
1946                    fis.close();
1947                } catch (java.io.IOException e1) {
1948                }
1949            }
1950        }
1951
1952        maybeMigrateSettingsForRenamedAuthorities();
1953    }
1954
1955    /**
1956     * Ensure the old pending.bin is deleted, as it has been changed to pending.xml.
1957     * pending.xml was used starting in KLP.
1958     * @param syncDir directory where the sync files are located.
1959     */
1960    private void maybeDeleteLegacyPendingInfoLocked(File syncDir) {
1961        File file = new File(syncDir, "pending.bin");
1962        if (!file.exists()) {
1963            return;
1964        } else {
1965            file.delete();
1966        }
1967    }
1968
1969    /**
1970     * some authority names have changed. copy over their settings and delete the old ones
1971     * @return true if a change was made
1972     */
1973    private boolean maybeMigrateSettingsForRenamedAuthorities() {
1974        boolean writeNeeded = false;
1975
1976        ArrayList<AuthorityInfo> authoritiesToRemove = new ArrayList<AuthorityInfo>();
1977        final int N = mAuthorities.size();
1978        for (int i = 0; i < N; i++) {
1979            AuthorityInfo authority = mAuthorities.valueAt(i);
1980            // skip this authority if it doesn't target a provider
1981            if (authority.target.target_service) {
1982                continue;
1983            }
1984            // skip this authority if it isn't one of the renamed ones
1985            final String newAuthorityName = sAuthorityRenames.get(authority.target.provider);
1986            if (newAuthorityName == null) {
1987                continue;
1988            }
1989
1990            // remember this authority so we can remove it later. we can't remove it
1991            // now without messing up this loop iteration
1992            authoritiesToRemove.add(authority);
1993
1994            // this authority isn't enabled, no need to copy it to the new authority name since
1995            // the default is "disabled"
1996            if (!authority.enabled) {
1997                continue;
1998            }
1999
2000            // if we already have a record of this new authority then don't copy over the settings
2001            EndPoint newInfo =
2002                    new EndPoint(authority.target.account,
2003                            newAuthorityName,
2004                            authority.target.userId);
2005            if (getAuthorityLocked(newInfo, "cleanup") != null) {
2006                continue;
2007            }
2008
2009            AuthorityInfo newAuthority =
2010                    getOrCreateAuthorityLocked(newInfo, -1 /* ident */, false /* doWrite */);
2011            newAuthority.enabled = true;
2012            writeNeeded = true;
2013        }
2014
2015        for (AuthorityInfo authorityInfo : authoritiesToRemove) {
2016            removeAuthorityLocked(
2017                    authorityInfo.target.account,
2018                    authorityInfo.target.userId,
2019                    authorityInfo.target.provider,
2020                    false /* doWrite */);
2021            writeNeeded = true;
2022        }
2023
2024        return writeNeeded;
2025    }
2026
2027    private void parseListenForTickles(XmlPullParser parser) {
2028        String user = parser.getAttributeValue(null, XML_ATTR_USER);
2029        int userId = 0;
2030        try {
2031            userId = Integer.parseInt(user);
2032        } catch (NumberFormatException e) {
2033            Log.e(TAG, "error parsing the user for listen-for-tickles", e);
2034        } catch (NullPointerException e) {
2035            Log.e(TAG, "the user in listen-for-tickles is null", e);
2036        }
2037        String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED);
2038        boolean listen = enabled == null || Boolean.parseBoolean(enabled);
2039        mMasterSyncAutomatically.put(userId, listen);
2040    }
2041
2042    private AuthorityInfo parseAuthority(XmlPullParser parser, int version) {
2043        AuthorityInfo authority = null;
2044        int id = -1;
2045        try {
2046            id = Integer.parseInt(parser.getAttributeValue(null, "id"));
2047        } catch (NumberFormatException e) {
2048            Log.e(TAG, "error parsing the id of the authority", e);
2049        } catch (NullPointerException e) {
2050            Log.e(TAG, "the id of the authority is null", e);
2051        }
2052        if (id >= 0) {
2053            String authorityName = parser.getAttributeValue(null, "authority");
2054            String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED);
2055            String syncable = parser.getAttributeValue(null, "syncable");
2056            String accountName = parser.getAttributeValue(null, "account");
2057            String accountType = parser.getAttributeValue(null, "type");
2058            String user = parser.getAttributeValue(null, XML_ATTR_USER);
2059            String packageName = parser.getAttributeValue(null, "package");
2060            String className = parser.getAttributeValue(null, "class");
2061            int userId = user == null ? 0 : Integer.parseInt(user);
2062            if (accountType == null && packageName == null) {
2063                accountType = "com.google";
2064                syncable = String.valueOf(AuthorityInfo.NOT_INITIALIZED);
2065            }
2066            authority = mAuthorities.get(id);
2067            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2068                Log.v(TAG_FILE, "Adding authority:"
2069                        + " account=" + accountName
2070                        + " accountType=" + accountType
2071                        + " auth=" + authorityName
2072                        + " package=" + packageName
2073                        + " class=" + className
2074                        + " user=" + userId
2075                        + " enabled=" + enabled
2076                        + " syncable=" + syncable);
2077            }
2078            if (authority == null) {
2079                if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2080                    Log.v(TAG_FILE, "Creating authority entry");
2081                }
2082                EndPoint info;
2083                if (accountName != null && authorityName != null) {
2084                    info = new EndPoint(
2085                            new Account(accountName, accountType),
2086                            authorityName, userId);
2087                } else {
2088                    final ComponentName cname = new ComponentName(packageName, className);
2089                    android.content.pm.ServiceInfo sinfo = null;
2090                    try {
2091                        sinfo = mContext.getPackageManager().getServiceInfo(cname, userId);
2092                    } catch (PackageManager.NameNotFoundException e) {
2093                        Slog.w(TAG, "Not restoring sync " + cname
2094                                + " -- can't find service for user " + userId);
2095                    }
2096                    if (sinfo != null) {
2097                        info = new EndPoint(cname, userId, sinfo.applicationInfo.uid);
2098                    } else {
2099                        info = null;
2100                    }
2101                }
2102                if (info != null) {
2103                    authority = getOrCreateAuthorityLocked(info, id, false);
2104                    // If the version is 0 then we are upgrading from a file format that did not
2105                    // know about periodic syncs. In that case don't clear the list since we
2106                    // want the default, which is a daily periodic sync.
2107                    // Otherwise clear out this default list since we will populate it later with
2108                    // the periodic sync descriptions that are read from the configuration file.
2109                    if (version > 0) {
2110                        authority.periodicSyncs.clear();
2111                    }
2112                }
2113            }
2114            if (authority != null) {
2115                authority.enabled = enabled == null || Boolean.parseBoolean(enabled);
2116                try {
2117                    authority.syncable = (syncable == null) ?
2118                            AuthorityInfo.NOT_INITIALIZED : Integer.parseInt(syncable);
2119                } catch (NumberFormatException e) {
2120                    // On L we stored this as {"unknown", "true", "false"} so fall back to this
2121                    // format.
2122                    if ("unknown".equals(syncable)) {
2123                        authority.syncable = AuthorityInfo.NOT_INITIALIZED;
2124                    } else {
2125                        authority.syncable = Boolean.parseBoolean(syncable) ?
2126                                AuthorityInfo.SYNCABLE : AuthorityInfo.NOT_SYNCABLE;
2127                    }
2128
2129                }
2130            } else {
2131                Log.w(TAG, "Failure adding authority: account="
2132                        + accountName + " auth=" + authorityName
2133                        + " enabled=" + enabled
2134                        + " syncable=" + syncable);
2135            }
2136        }
2137        return authority;
2138    }
2139
2140    /**
2141     * Parse a periodic sync from accounts.xml. Sets the bundle to be empty.
2142     */
2143    private PeriodicSync parsePeriodicSync(XmlPullParser parser, AuthorityInfo authorityInfo) {
2144        Bundle extras = new Bundle(); // Gets filled in later.
2145        String periodValue = parser.getAttributeValue(null, "period");
2146        String flexValue = parser.getAttributeValue(null, "flex");
2147        final long period;
2148        long flextime;
2149        try {
2150            period = Long.parseLong(periodValue);
2151        } catch (NumberFormatException e) {
2152            Log.e(TAG, "error parsing the period of a periodic sync", e);
2153            return null;
2154        } catch (NullPointerException e) {
2155            Log.e(TAG, "the period of a periodic sync is null", e);
2156            return null;
2157        }
2158        try {
2159            flextime = Long.parseLong(flexValue);
2160        } catch (NumberFormatException e) {
2161            flextime = calculateDefaultFlexTime(period);
2162            Log.e(TAG, "Error formatting value parsed for periodic sync flex: " + flexValue
2163                    + ", using default: "
2164                    + flextime);
2165        } catch (NullPointerException expected) {
2166            flextime = calculateDefaultFlexTime(period);
2167            Log.d(TAG, "No flex time specified for this sync, using a default. period: "
2168            + period + " flex: " + flextime);
2169        }
2170        PeriodicSync periodicSync;
2171        if (authorityInfo.target.target_provider) {
2172            periodicSync =
2173                new PeriodicSync(authorityInfo.target.account,
2174                        authorityInfo.target.provider,
2175                        extras,
2176                        period, flextime);
2177        } else {
2178            Log.e(TAG, "Unknown target.");
2179            return null;
2180        }
2181        authorityInfo.periodicSyncs.add(periodicSync);
2182        return periodicSync;
2183    }
2184
2185    private void parseExtra(XmlPullParser parser, Bundle extras) {
2186        String name = parser.getAttributeValue(null, "name");
2187        String type = parser.getAttributeValue(null, "type");
2188        String value1 = parser.getAttributeValue(null, "value1");
2189        String value2 = parser.getAttributeValue(null, "value2");
2190
2191        try {
2192            if ("long".equals(type)) {
2193                extras.putLong(name, Long.parseLong(value1));
2194            } else if ("integer".equals(type)) {
2195                extras.putInt(name, Integer.parseInt(value1));
2196            } else if ("double".equals(type)) {
2197                extras.putDouble(name, Double.parseDouble(value1));
2198            } else if ("float".equals(type)) {
2199                extras.putFloat(name, Float.parseFloat(value1));
2200            } else if ("boolean".equals(type)) {
2201                extras.putBoolean(name, Boolean.parseBoolean(value1));
2202            } else if ("string".equals(type)) {
2203                extras.putString(name, value1);
2204            } else if ("account".equals(type)) {
2205                extras.putParcelable(name, new Account(value1, value2));
2206            }
2207        } catch (NumberFormatException e) {
2208            Log.e(TAG, "error parsing bundle value", e);
2209        } catch (NullPointerException e) {
2210            Log.e(TAG, "error parsing bundle value", e);
2211        }
2212    }
2213
2214    /**
2215     * Write all account information to the account file.
2216     */
2217    private void writeAccountInfoLocked() {
2218        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2219            Log.v(TAG_FILE, "Writing new " + mAccountInfoFile.getBaseFile());
2220        }
2221        FileOutputStream fos = null;
2222
2223        try {
2224            fos = mAccountInfoFile.startWrite();
2225            XmlSerializer out = new FastXmlSerializer();
2226            out.setOutput(fos, StandardCharsets.UTF_8.name());
2227            out.startDocument(null, true);
2228            out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
2229
2230            out.startTag(null, "accounts");
2231            out.attribute(null, "version", Integer.toString(ACCOUNTS_VERSION));
2232            out.attribute(null, XML_ATTR_NEXT_AUTHORITY_ID, Integer.toString(mNextAuthorityId));
2233            out.attribute(null, XML_ATTR_SYNC_RANDOM_OFFSET, Integer.toString(mSyncRandomOffset));
2234
2235            // Write the Sync Automatically flags for each user
2236            final int M = mMasterSyncAutomatically.size();
2237            for (int m = 0; m < M; m++) {
2238                int userId = mMasterSyncAutomatically.keyAt(m);
2239                Boolean listen = mMasterSyncAutomatically.valueAt(m);
2240                out.startTag(null, XML_TAG_LISTEN_FOR_TICKLES);
2241                out.attribute(null, XML_ATTR_USER, Integer.toString(userId));
2242                out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(listen));
2243                out.endTag(null, XML_TAG_LISTEN_FOR_TICKLES);
2244            }
2245
2246            final int N = mAuthorities.size();
2247            for (int i = 0; i < N; i++) {
2248                AuthorityInfo authority = mAuthorities.valueAt(i);
2249                EndPoint info = authority.target;
2250                out.startTag(null, "authority");
2251                out.attribute(null, "id", Integer.toString(authority.ident));
2252                out.attribute(null, XML_ATTR_USER, Integer.toString(info.userId));
2253                out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(authority.enabled));
2254                if (info.service == null) {
2255                    out.attribute(null, "account", info.account.name);
2256                    out.attribute(null, "type", info.account.type);
2257                    out.attribute(null, "authority", info.provider);
2258                } else {
2259                    out.attribute(null, "package", info.service.getPackageName());
2260                    out.attribute(null, "class", info.service.getClassName());
2261                }
2262                out.attribute(null, "syncable", Integer.toString(authority.syncable));
2263                for (PeriodicSync periodicSync : authority.periodicSyncs) {
2264                    out.startTag(null, "periodicSync");
2265                    out.attribute(null, "period", Long.toString(periodicSync.period));
2266                    out.attribute(null, "flex", Long.toString(periodicSync.flexTime));
2267                    final Bundle extras = periodicSync.extras;
2268                    extrasToXml(out, extras);
2269                    out.endTag(null, "periodicSync");
2270                }
2271                out.endTag(null, "authority");
2272            }
2273            out.endTag(null, "accounts");
2274            out.endDocument();
2275            mAccountInfoFile.finishWrite(fos);
2276        } catch (java.io.IOException e1) {
2277            Log.w(TAG, "Error writing accounts", e1);
2278            if (fos != null) {
2279                mAccountInfoFile.failWrite(fos);
2280            }
2281        }
2282    }
2283
2284    static int getIntColumn(Cursor c, String name) {
2285        return c.getInt(c.getColumnIndex(name));
2286    }
2287
2288    static long getLongColumn(Cursor c, String name) {
2289        return c.getLong(c.getColumnIndex(name));
2290    }
2291
2292    /**
2293     * Load sync engine state from the old syncmanager database, and then
2294     * erase it.  Note that we don't deal with pending operations, active
2295     * sync, or history.
2296     */
2297    private void readAndDeleteLegacyAccountInfoLocked() {
2298        // Look for old database to initialize from.
2299        File file = mContext.getDatabasePath("syncmanager.db");
2300        if (!file.exists()) {
2301            return;
2302        }
2303        String path = file.getPath();
2304        SQLiteDatabase db = null;
2305        try {
2306            db = SQLiteDatabase.openDatabase(path, null,
2307                    SQLiteDatabase.OPEN_READONLY);
2308        } catch (SQLiteException e) {
2309        }
2310
2311        if (db != null) {
2312            final boolean hasType = db.getVersion() >= 11;
2313
2314            // Copy in all of the status information, as well as accounts.
2315            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2316                Log.v(TAG_FILE, "Reading legacy sync accounts db");
2317            }
2318            SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
2319            qb.setTables("stats, status");
2320            HashMap<String,String> map = new HashMap<String,String>();
2321            map.put("_id", "status._id as _id");
2322            map.put("account", "stats.account as account");
2323            if (hasType) {
2324                map.put("account_type", "stats.account_type as account_type");
2325            }
2326            map.put("authority", "stats.authority as authority");
2327            map.put("totalElapsedTime", "totalElapsedTime");
2328            map.put("numSyncs", "numSyncs");
2329            map.put("numSourceLocal", "numSourceLocal");
2330            map.put("numSourcePoll", "numSourcePoll");
2331            map.put("numSourceServer", "numSourceServer");
2332            map.put("numSourceUser", "numSourceUser");
2333            map.put("lastSuccessSource", "lastSuccessSource");
2334            map.put("lastSuccessTime", "lastSuccessTime");
2335            map.put("lastFailureSource", "lastFailureSource");
2336            map.put("lastFailureTime", "lastFailureTime");
2337            map.put("lastFailureMesg", "lastFailureMesg");
2338            map.put("pending", "pending");
2339            qb.setProjectionMap(map);
2340            qb.appendWhere("stats._id = status.stats_id");
2341            Cursor c = qb.query(db, null, null, null, null, null, null);
2342            while (c.moveToNext()) {
2343                String accountName = c.getString(c.getColumnIndex("account"));
2344                String accountType = hasType
2345                        ? c.getString(c.getColumnIndex("account_type")) : null;
2346                if (accountType == null) {
2347                    accountType = "com.google";
2348                }
2349                String authorityName = c.getString(c.getColumnIndex("authority"));
2350                AuthorityInfo authority =
2351                        this.getOrCreateAuthorityLocked(
2352                                new EndPoint(new Account(accountName, accountType),
2353                                        authorityName,
2354                                        0 /* legacy is single-user */)
2355                                , -1,
2356                                false);
2357                if (authority != null) {
2358                    int i = mSyncStatus.size();
2359                    boolean found = false;
2360                    SyncStatusInfo st = null;
2361                    while (i > 0) {
2362                        i--;
2363                        st = mSyncStatus.valueAt(i);
2364                        if (st.authorityId == authority.ident) {
2365                            found = true;
2366                            break;
2367                        }
2368                    }
2369                    if (!found) {
2370                        st = new SyncStatusInfo(authority.ident);
2371                        mSyncStatus.put(authority.ident, st);
2372                    }
2373                    st.totalElapsedTime = getLongColumn(c, "totalElapsedTime");
2374                    st.numSyncs = getIntColumn(c, "numSyncs");
2375                    st.numSourceLocal = getIntColumn(c, "numSourceLocal");
2376                    st.numSourcePoll = getIntColumn(c, "numSourcePoll");
2377                    st.numSourceServer = getIntColumn(c, "numSourceServer");
2378                    st.numSourceUser = getIntColumn(c, "numSourceUser");
2379                    st.numSourcePeriodic = 0;
2380                    st.lastSuccessSource = getIntColumn(c, "lastSuccessSource");
2381                    st.lastSuccessTime = getLongColumn(c, "lastSuccessTime");
2382                    st.lastFailureSource = getIntColumn(c, "lastFailureSource");
2383                    st.lastFailureTime = getLongColumn(c, "lastFailureTime");
2384                    st.lastFailureMesg = c.getString(c.getColumnIndex("lastFailureMesg"));
2385                    st.pending = getIntColumn(c, "pending") != 0;
2386                }
2387            }
2388
2389            c.close();
2390
2391            // Retrieve the settings.
2392            qb = new SQLiteQueryBuilder();
2393            qb.setTables("settings");
2394            c = qb.query(db, null, null, null, null, null, null);
2395            while (c.moveToNext()) {
2396                String name = c.getString(c.getColumnIndex("name"));
2397                String value = c.getString(c.getColumnIndex("value"));
2398                if (name == null) continue;
2399                if (name.equals("listen_for_tickles")) {
2400                    setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value), 0);
2401                } else if (name.startsWith("sync_provider_")) {
2402                    String provider = name.substring("sync_provider_".length(),
2403                            name.length());
2404                    int i = mAuthorities.size();
2405                    while (i > 0) {
2406                        i--;
2407                        AuthorityInfo authority = mAuthorities.valueAt(i);
2408                        if (authority.target.provider.equals(provider)) {
2409                            authority.enabled = value == null || Boolean.parseBoolean(value);
2410                            authority.syncable = 1;
2411                        }
2412                    }
2413                }
2414            }
2415
2416            c.close();
2417
2418            db.close();
2419
2420            (new File(path)).delete();
2421        }
2422    }
2423
2424    public static final int STATUS_FILE_END = 0;
2425    public static final int STATUS_FILE_ITEM = 100;
2426
2427    /**
2428     * Read all sync status back in to the initial engine state.
2429     */
2430    private void readStatusLocked() {
2431        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2432            Log.v(TAG_FILE, "Reading " + mStatusFile.getBaseFile());
2433        }
2434        try {
2435            byte[] data = mStatusFile.readFully();
2436            Parcel in = Parcel.obtain();
2437            in.unmarshall(data, 0, data.length);
2438            in.setDataPosition(0);
2439            int token;
2440            while ((token=in.readInt()) != STATUS_FILE_END) {
2441                if (token == STATUS_FILE_ITEM) {
2442                    SyncStatusInfo status = new SyncStatusInfo(in);
2443                    if (mAuthorities.indexOfKey(status.authorityId) >= 0) {
2444                        status.pending = false;
2445                        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2446                            Log.v(TAG_FILE, "Adding status for id " + status.authorityId);
2447                        }
2448                        mSyncStatus.put(status.authorityId, status);
2449                    }
2450                } else {
2451                    // Ooops.
2452                    Log.w(TAG, "Unknown status token: " + token);
2453                    break;
2454                }
2455            }
2456        } catch (java.io.IOException e) {
2457            Log.i(TAG, "No initial status");
2458        }
2459    }
2460
2461    /**
2462     * Write all sync status to the sync status file.
2463     */
2464    private void writeStatusLocked() {
2465        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2466            Log.v(TAG_FILE, "Writing new " + mStatusFile.getBaseFile());
2467        }
2468
2469        // The file is being written, so we don't need to have a scheduled
2470        // write until the next change.
2471        removeMessages(MSG_WRITE_STATUS);
2472
2473        FileOutputStream fos = null;
2474        try {
2475            fos = mStatusFile.startWrite();
2476            Parcel out = Parcel.obtain();
2477            final int N = mSyncStatus.size();
2478            for (int i=0; i<N; i++) {
2479                SyncStatusInfo status = mSyncStatus.valueAt(i);
2480                out.writeInt(STATUS_FILE_ITEM);
2481                status.writeToParcel(out, 0);
2482            }
2483            out.writeInt(STATUS_FILE_END);
2484            fos.write(out.marshall());
2485            out.recycle();
2486
2487            mStatusFile.finishWrite(fos);
2488        } catch (java.io.IOException e1) {
2489            Log.w(TAG, "Error writing status", e1);
2490            if (fos != null) {
2491                mStatusFile.failWrite(fos);
2492            }
2493        }
2494    }
2495
2496    public static final int PENDING_OPERATION_VERSION = 3;
2497
2498    /** Read all pending operations back in to the initial engine state. */
2499    private void readPendingOperationsLocked() {
2500        FileInputStream fis = null;
2501        if (!mPendingFile.getBaseFile().exists()) {
2502            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2503                Log.v(TAG_FILE, "No pending operation file.");
2504            }
2505            return;
2506        }
2507        try {
2508            fis = mPendingFile.openRead();
2509            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2510                Log.v(TAG_FILE, "Reading " + mPendingFile.getBaseFile());
2511            }
2512            XmlPullParser parser;
2513            parser = Xml.newPullParser();
2514            parser.setInput(fis, StandardCharsets.UTF_8.name());
2515
2516            int eventType = parser.getEventType();
2517            while (eventType != XmlPullParser.START_TAG &&
2518                    eventType != XmlPullParser.END_DOCUMENT) {
2519                eventType = parser.next();
2520            }
2521            if (eventType == XmlPullParser.END_DOCUMENT) return; // Nothing to read.
2522
2523            do {
2524                PendingOperation pop = null;
2525                if (eventType == XmlPullParser.START_TAG) {
2526                    try {
2527                        String tagName = parser.getName();
2528                        if (parser.getDepth() == 1 && "op".equals(tagName)) {
2529                            // Verify version.
2530                            String versionString =
2531                                    parser.getAttributeValue(null, XML_ATTR_VERSION);
2532                            if (versionString == null ||
2533                                    Integer.parseInt(versionString) != PENDING_OPERATION_VERSION) {
2534                                Log.w(TAG, "Unknown pending operation version " + versionString);
2535                                throw new java.io.IOException("Unknown version.");
2536                            }
2537                            int authorityId = Integer.valueOf(parser.getAttributeValue(
2538                                    null, XML_ATTR_AUTHORITYID));
2539                            boolean expedited = Boolean.valueOf(parser.getAttributeValue(
2540                                    null, XML_ATTR_EXPEDITED));
2541                            int syncSource = Integer.valueOf(parser.getAttributeValue(
2542                                    null, XML_ATTR_SOURCE));
2543                            int reason = Integer.valueOf(parser.getAttributeValue(
2544                                    null, XML_ATTR_REASON));
2545                            AuthorityInfo authority = mAuthorities.get(authorityId);
2546                            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2547                                Log.v(TAG_FILE, authorityId + " " + expedited + " " + syncSource + " "
2548                                        + reason);
2549                            }
2550                            if (authority != null) {
2551                                pop = new PendingOperation(
2552                                        authority, reason, syncSource, new Bundle(), expedited);
2553                                pop.flatExtras = null; // No longer used.
2554                                mPendingOperations.add(pop);
2555                                if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2556                                    Log.v(TAG_FILE, "Adding pending op: "
2557                                            + pop.target
2558                                            + " src=" + pop.syncSource
2559                                            + " reason=" + pop.reason
2560                                            + " expedited=" + pop.expedited);
2561                                    }
2562                            } else {
2563                                // Skip non-existent authority.
2564                                pop = null;
2565                                if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2566                                    Log.v(TAG_FILE, "No authority found for " + authorityId
2567                                            + ", skipping");
2568                                }
2569                            }
2570                        } else if (parser.getDepth() == 2 &&
2571                                pop != null &&
2572                                "extra".equals(tagName)) {
2573                            parseExtra(parser, pop.extras);
2574                        }
2575                    } catch (NumberFormatException e) {
2576                        Log.d(TAG, "Invalid data in xml file.", e);
2577                    }
2578                }
2579                eventType = parser.next();
2580            } while(eventType != XmlPullParser.END_DOCUMENT);
2581        } catch (java.io.IOException e) {
2582            Log.w(TAG_FILE, "Error reading pending data.", e);
2583        } catch (XmlPullParserException e) {
2584            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2585                Log.w(TAG_FILE, "Error parsing pending ops xml.", e);
2586            }
2587        } finally {
2588            if (fis != null) {
2589                try {
2590                    fis.close();
2591                } catch (java.io.IOException e1) {}
2592            }
2593        }
2594    }
2595
2596    static private byte[] flattenBundle(Bundle bundle) {
2597        byte[] flatData = null;
2598        Parcel parcel = Parcel.obtain();
2599        try {
2600            bundle.writeToParcel(parcel, 0);
2601            flatData = parcel.marshall();
2602        } finally {
2603            parcel.recycle();
2604        }
2605        return flatData;
2606    }
2607
2608    static private Bundle unflattenBundle(byte[] flatData) {
2609        Bundle bundle;
2610        Parcel parcel = Parcel.obtain();
2611        try {
2612            parcel.unmarshall(flatData, 0, flatData.length);
2613            parcel.setDataPosition(0);
2614            bundle = parcel.readBundle();
2615        } catch (RuntimeException e) {
2616            // A RuntimeException is thrown if we were unable to parse the parcel.
2617            // Create an empty parcel in this case.
2618            bundle = new Bundle();
2619        } finally {
2620            parcel.recycle();
2621        }
2622        return bundle;
2623    }
2624
2625    private static final String XML_ATTR_VERSION = "version";
2626    private static final String XML_ATTR_AUTHORITYID = "authority_id";
2627    private static final String XML_ATTR_SOURCE = "source";
2628    private static final String XML_ATTR_EXPEDITED = "expedited";
2629    private static final String XML_ATTR_REASON = "reason";
2630
2631    /**
2632     * Write all currently pending ops to the pending ops file.
2633     */
2634    private void writePendingOperationsLocked() {
2635        final int N = mPendingOperations.size();
2636        FileOutputStream fos = null;
2637        try {
2638            if (N == 0) {
2639                if (Log.isLoggable(TAG_FILE, Log.VERBOSE)){
2640                    Log.v(TAG, "Truncating " + mPendingFile.getBaseFile());
2641                }
2642                mPendingFile.truncate();
2643                return;
2644            }
2645            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2646                Log.v(TAG, "Writing new " + mPendingFile.getBaseFile());
2647            }
2648            fos = mPendingFile.startWrite();
2649            XmlSerializer out = new FastXmlSerializer();
2650            out.setOutput(fos, StandardCharsets.UTF_8.name());
2651
2652            for (int i = 0; i < N; i++) {
2653                PendingOperation pop = mPendingOperations.get(i);
2654                writePendingOperationLocked(pop, out);
2655             }
2656             out.endDocument();
2657             mPendingFile.finishWrite(fos);
2658        } catch (java.io.IOException e1) {
2659            Log.w(TAG, "Error writing pending operations", e1);
2660            if (fos != null) {
2661                mPendingFile.failWrite(fos);
2662            }
2663        }
2664    }
2665
2666    /** Write all currently pending ops to the pending ops file. */
2667     private void writePendingOperationLocked(PendingOperation pop, XmlSerializer out)
2668             throws IOException {
2669         // Pending operation.
2670         out.startTag(null, "op");
2671
2672         out.attribute(null, XML_ATTR_VERSION, Integer.toString(PENDING_OPERATION_VERSION));
2673         out.attribute(null, XML_ATTR_AUTHORITYID, Integer.toString(pop.authorityId));
2674         out.attribute(null, XML_ATTR_SOURCE, Integer.toString(pop.syncSource));
2675         out.attribute(null, XML_ATTR_EXPEDITED, Boolean.toString(pop.expedited));
2676         out.attribute(null, XML_ATTR_REASON, Integer.toString(pop.reason));
2677         extrasToXml(out, pop.extras);
2678
2679         out.endTag(null, "op");
2680     }
2681
2682    /**
2683     * Append the given operation to the pending ops file; if unable to,
2684     * write all pending ops.
2685     */
2686    private void appendPendingOperationLocked(PendingOperation op) {
2687        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2688            Log.v(TAG, "Appending to " + mPendingFile.getBaseFile());
2689        }
2690        FileOutputStream fos = null;
2691        try {
2692            fos = mPendingFile.openAppend();
2693        } catch (java.io.IOException e) {
2694            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2695                Log.v(TAG, "Failed append; writing full file");
2696            }
2697            writePendingOperationsLocked();
2698            return;
2699        }
2700
2701        try {
2702            XmlSerializer out = new FastXmlSerializer();
2703            out.setOutput(fos, StandardCharsets.UTF_8.name());
2704            writePendingOperationLocked(op, out);
2705            out.endDocument();
2706            mPendingFile.finishWrite(fos);
2707        } catch (java.io.IOException e1) {
2708            Log.w(TAG, "Error writing appending operation", e1);
2709            mPendingFile.failWrite(fos);
2710        } finally {
2711            try {
2712                fos.close();
2713            } catch (IOException e) {}
2714        }
2715    }
2716
2717    private void extrasToXml(XmlSerializer out, Bundle extras) throws java.io.IOException {
2718        for (String key : extras.keySet()) {
2719            out.startTag(null, "extra");
2720            out.attribute(null, "name", key);
2721            final Object value = extras.get(key);
2722            if (value instanceof Long) {
2723                out.attribute(null, "type", "long");
2724                out.attribute(null, "value1", value.toString());
2725            } else if (value instanceof Integer) {
2726                out.attribute(null, "type", "integer");
2727                out.attribute(null, "value1", value.toString());
2728            } else if (value instanceof Boolean) {
2729                out.attribute(null, "type", "boolean");
2730                out.attribute(null, "value1", value.toString());
2731            } else if (value instanceof Float) {
2732                out.attribute(null, "type", "float");
2733                out.attribute(null, "value1", value.toString());
2734            } else if (value instanceof Double) {
2735                out.attribute(null, "type", "double");
2736                out.attribute(null, "value1", value.toString());
2737            } else if (value instanceof String) {
2738                out.attribute(null, "type", "string");
2739                out.attribute(null, "value1", value.toString());
2740            } else if (value instanceof Account) {
2741                out.attribute(null, "type", "account");
2742                out.attribute(null, "value1", ((Account)value).name);
2743                out.attribute(null, "value2", ((Account)value).type);
2744            }
2745            out.endTag(null, "extra");
2746        }
2747    }
2748
2749    private void requestSync(AuthorityInfo authorityInfo, int reason, Bundle extras) {
2750        if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID
2751                && mSyncRequestListener != null) {
2752            mSyncRequestListener.onSyncRequest(authorityInfo.target, reason, extras);
2753        } else {
2754            SyncRequest.Builder req =
2755                    new SyncRequest.Builder()
2756                            .syncOnce()
2757                            .setExtras(extras);
2758            if (authorityInfo.target.target_provider) {
2759                req.setSyncAdapter(authorityInfo.target.account, authorityInfo.target.provider);
2760            } else {
2761                if (Log.isLoggable(TAG, Log.DEBUG)) {
2762                    Log.d(TAG, "Unknown target, skipping sync request.");
2763                }
2764                return;
2765            }
2766            ContentResolver.requestSync(req.build());
2767        }
2768    }
2769
2770    private void requestSync(Account account, int userId, int reason, String authority,
2771            Bundle extras) {
2772        // If this is happening in the system process, then call the syncrequest listener
2773        // to make a request back to the SyncManager directly.
2774        // If this is probably a test instance, then call back through the ContentResolver
2775        // which will know which userId to apply based on the Binder id.
2776        if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID
2777                && mSyncRequestListener != null) {
2778            mSyncRequestListener.onSyncRequest(
2779                new EndPoint(account, authority, userId),
2780                reason,
2781                extras);
2782        } else {
2783            ContentResolver.requestSync(account, authority, extras);
2784        }
2785    }
2786
2787    public static final int STATISTICS_FILE_END = 0;
2788    public static final int STATISTICS_FILE_ITEM_OLD = 100;
2789    public static final int STATISTICS_FILE_ITEM = 101;
2790
2791    /**
2792     * Read all sync statistics back in to the initial engine state.
2793     */
2794    private void readStatisticsLocked() {
2795        try {
2796            byte[] data = mStatisticsFile.readFully();
2797            Parcel in = Parcel.obtain();
2798            in.unmarshall(data, 0, data.length);
2799            in.setDataPosition(0);
2800            int token;
2801            int index = 0;
2802            while ((token=in.readInt()) != STATISTICS_FILE_END) {
2803                if (token == STATISTICS_FILE_ITEM
2804                        || token == STATISTICS_FILE_ITEM_OLD) {
2805                    int day = in.readInt();
2806                    if (token == STATISTICS_FILE_ITEM_OLD) {
2807                        day = day - 2009 + 14245;  // Magic!
2808                    }
2809                    DayStats ds = new DayStats(day);
2810                    ds.successCount = in.readInt();
2811                    ds.successTime = in.readLong();
2812                    ds.failureCount = in.readInt();
2813                    ds.failureTime = in.readLong();
2814                    if (index < mDayStats.length) {
2815                        mDayStats[index] = ds;
2816                        index++;
2817                    }
2818                } else {
2819                    // Ooops.
2820                    Log.w(TAG, "Unknown stats token: " + token);
2821                    break;
2822                }
2823            }
2824        } catch (java.io.IOException e) {
2825            Log.i(TAG, "No initial statistics");
2826        }
2827    }
2828
2829    /**
2830     * Write all sync statistics to the sync status file.
2831     */
2832    private void writeStatisticsLocked() {
2833        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2834            Log.v(TAG, "Writing new " + mStatisticsFile.getBaseFile());
2835        }
2836
2837        // The file is being written, so we don't need to have a scheduled
2838        // write until the next change.
2839        removeMessages(MSG_WRITE_STATISTICS);
2840
2841        FileOutputStream fos = null;
2842        try {
2843            fos = mStatisticsFile.startWrite();
2844            Parcel out = Parcel.obtain();
2845            final int N = mDayStats.length;
2846            for (int i=0; i<N; i++) {
2847                DayStats ds = mDayStats[i];
2848                if (ds == null) {
2849                    break;
2850                }
2851                out.writeInt(STATISTICS_FILE_ITEM);
2852                out.writeInt(ds.day);
2853                out.writeInt(ds.successCount);
2854                out.writeLong(ds.successTime);
2855                out.writeInt(ds.failureCount);
2856                out.writeLong(ds.failureTime);
2857            }
2858            out.writeInt(STATISTICS_FILE_END);
2859            fos.write(out.marshall());
2860            out.recycle();
2861
2862            mStatisticsFile.finishWrite(fos);
2863        } catch (java.io.IOException e1) {
2864            Log.w(TAG, "Error writing stats", e1);
2865            if (fos != null) {
2866                mStatisticsFile.failWrite(fos);
2867            }
2868        }
2869    }
2870
2871    /**
2872     * Dump state of PendingOperations.
2873     */
2874    public void dumpPendingOperations(StringBuilder sb) {
2875        sb.append("Pending Ops: ").append(mPendingOperations.size()).append(" operation(s)\n");
2876        for (PendingOperation pop : mPendingOperations) {
2877            sb.append("(info: " + pop.target.toString())
2878                .append(", extras: " + pop.extras)
2879                .append(")\n");
2880        }
2881    }
2882
2883    /**
2884     * Let the BackupManager know that account sync settings have changed. This will trigger
2885     * {@link com.android.server.backup.SystemBackupAgent} to run.
2886     */
2887    public void queueBackup() {
2888        BackupManager.dataChanged("android");
2889    }
2890}
2891