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