SyncStorageEngine.java revision 6428046767ee4195617fb41b5639eefa2ca7a939
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                    toUpdate = new PeriodicSync(info.service,
929                            extras,
930                            period,
931                            flextime);
932                }
933                AuthorityInfo authority =
934                        getOrCreateAuthorityLocked(info, -1, false);
935                // add this periodic sync if an equivalent periodic doesn't already exist.
936                boolean alreadyPresent = false;
937                for (int i = 0, N = authority.periodicSyncs.size(); i < N; i++) {
938                    PeriodicSync syncInfo = authority.periodicSyncs.get(i);
939                    if (SyncManager.syncExtrasEquals(syncInfo.extras,
940                            extras,
941                            true /* includeSyncSettings*/)) {
942                        if (period == syncInfo.period &&
943                                flextime == syncInfo.flexTime) {
944                            // Absolutely the same.
945                            return;
946                        }
947                        authority.periodicSyncs.set(i, toUpdate);
948                        alreadyPresent = true;
949                        break;
950                    }
951                }
952                // If we added an entry to the periodicSyncs array also add an entry to
953                // the periodic syncs status to correspond to it.
954                if (!alreadyPresent) {
955                    authority.periodicSyncs.add(toUpdate);
956                    SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
957                    // A new periodic sync is initialised as already having been run.
958                    status.setPeriodicSyncTime(
959                            authority.periodicSyncs.size() - 1,
960                            System.currentTimeMillis());
961                }
962            } finally {
963                writeAccountInfoLocked();
964                writeStatusLocked();
965            }
966        }
967        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
968    }
969
970    public void removePeriodicSync(EndPoint info, Bundle extras) {
971        synchronized(mAuthorities) {
972            try {
973                AuthorityInfo authority =
974                        getOrCreateAuthorityLocked(info, -1, false);
975                // Remove any periodic syncs that match the target and extras.
976                SyncStatusInfo status = mSyncStatus.get(authority.ident);
977                boolean changed = false;
978                Iterator<PeriodicSync> iterator = authority.periodicSyncs.iterator();
979                int i = 0;
980                while (iterator.hasNext()) {
981                    PeriodicSync syncInfo = iterator.next();
982                    if (SyncManager.syncExtrasEquals(syncInfo.extras,
983                            extras,
984                            true /* includeSyncSettings */)) {
985                        iterator.remove();
986                        changed = true;
987                        // If we removed an entry from the periodicSyncs array also
988                        // remove the corresponding entry from the status
989                        if (status != null) {
990                            status.removePeriodicSyncTime(i);
991                        } else {
992                            Log.e(TAG, "Tried removing sync status on remove periodic sync but"
993                                    + " did not find it.");
994                        }
995                    } else {
996                        i++;
997                    }
998                }
999                if (!changed) {
1000                    return;
1001                }
1002            } finally {
1003                writeAccountInfoLocked();
1004                writeStatusLocked();
1005            }
1006        }
1007        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
1008    }
1009
1010    /**
1011     * @return list of periodic syncs for a target. Never null. If no such syncs exist, returns an
1012     * empty list.
1013     */
1014    public List<PeriodicSync> getPeriodicSyncs(EndPoint info) {
1015        synchronized (mAuthorities) {
1016            AuthorityInfo authorityInfo = getAuthorityLocked(info, "getPeriodicSyncs");
1017            ArrayList<PeriodicSync> syncs = new ArrayList<PeriodicSync>();
1018            if (authorityInfo != null) {
1019                for (PeriodicSync item : authorityInfo.periodicSyncs) {
1020                    // Copy and send out. Necessary for thread-safety although it's parceled.
1021                    syncs.add(new PeriodicSync(item));
1022                }
1023            }
1024            return syncs;
1025        }
1026    }
1027
1028    public void setMasterSyncAutomatically(boolean flag, int userId) {
1029        synchronized (mAuthorities) {
1030            Boolean auto = mMasterSyncAutomatically.get(userId);
1031            if (auto != null && auto.equals(flag)) {
1032                return;
1033            }
1034            mMasterSyncAutomatically.put(userId, flag);
1035            writeAccountInfoLocked();
1036        }
1037        if (flag) {
1038            requestSync(null, userId, SyncOperation.REASON_MASTER_SYNC_AUTO, null,
1039                    new Bundle());
1040        }
1041        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
1042        mContext.sendBroadcast(ContentResolver.ACTION_SYNC_CONN_STATUS_CHANGED);
1043    }
1044
1045    public boolean getMasterSyncAutomatically(int userId) {
1046        synchronized (mAuthorities) {
1047            Boolean auto = mMasterSyncAutomatically.get(userId);
1048            return auto == null ? mDefaultMasterSyncAutomatically : auto;
1049        }
1050    }
1051
1052    public AuthorityInfo getAuthority(int authorityId) {
1053        synchronized (mAuthorities) {
1054            return mAuthorities.get(authorityId);
1055        }
1056    }
1057
1058    /**
1059     * Returns true if there is currently a sync operation being actively processed for the given
1060     * target.
1061     */
1062    public boolean isSyncActive(EndPoint info) {
1063        synchronized (mAuthorities) {
1064            for (SyncInfo syncInfo : getCurrentSyncs(info.userId)) {
1065                AuthorityInfo ainfo = getAuthority(syncInfo.authorityId);
1066                if (ainfo != null && ainfo.target.matchesSpec(info)) {
1067                    return true;
1068                }
1069            }
1070        }
1071        return false;
1072    }
1073
1074    public PendingOperation insertIntoPending(SyncOperation op) {
1075        PendingOperation pop;
1076        synchronized (mAuthorities) {
1077            if (Log.isLoggable(TAG, Log.VERBOSE)) {
1078                Log.v(TAG, "insertIntoPending: authority=" + op.target
1079                        + " extras=" + op.extras);
1080            }
1081            final EndPoint info = op.target;
1082            AuthorityInfo authority =
1083                    getOrCreateAuthorityLocked(info,
1084                            -1 /* desired identifier */,
1085                            true /* write accounts to storage */);
1086            if (authority == null) {
1087                return null;
1088            }
1089
1090            pop = new PendingOperation(authority, op.reason, op.syncSource, op.extras,
1091                    op.isExpedited());
1092            mPendingOperations.add(pop);
1093            appendPendingOperationLocked(pop);
1094
1095            SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
1096            status.pending = true;
1097        }
1098        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
1099        return pop;
1100    }
1101
1102    /**
1103     * Remove from list of pending operations. If successful, search through list for matching
1104     * authorities. If there are no more pending syncs for the same target,
1105     * update the SyncStatusInfo for that target.
1106     * @param op Pending op to delete.
1107     */
1108    public boolean deleteFromPending(PendingOperation op) {
1109        boolean res = false;
1110        synchronized (mAuthorities) {
1111            if (Log.isLoggable(TAG, Log.VERBOSE)) {
1112                Log.v(TAG, "deleteFromPending: account=" + op.toString());
1113            }
1114            if (mPendingOperations.remove(op)) {
1115                if (mPendingOperations.size() == 0
1116                        || mNumPendingFinished >= PENDING_FINISH_TO_WRITE) {
1117                    writePendingOperationsLocked();
1118                    mNumPendingFinished = 0;
1119                } else {
1120                    mNumPendingFinished++;
1121                }
1122                AuthorityInfo authority = getAuthorityLocked(op.target, "deleteFromPending");
1123                if (authority != null) {
1124                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
1125                        Log.v(TAG, "removing - " + authority.toString());
1126                    }
1127                    final int N = mPendingOperations.size();
1128                    boolean morePending = false;
1129                    for (int i = 0; i < N; i++) {
1130                        PendingOperation cur = mPendingOperations.get(i);
1131                        if (cur.equals(op)) {
1132                            morePending = true;
1133                            break;
1134                        }
1135                    }
1136
1137                    if (!morePending) {
1138                        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "no more pending!");
1139                        SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
1140                        status.pending = false;
1141                    }
1142                }
1143                res = true;
1144            }
1145        }
1146
1147        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
1148        return res;
1149    }
1150
1151    /**
1152     * Return a copy of the current array of pending operations.  The
1153     * PendingOperation objects are the real objects stored inside, so that
1154     * they can be used with deleteFromPending().
1155     */
1156    public ArrayList<PendingOperation> getPendingOperations() {
1157        synchronized (mAuthorities) {
1158            return new ArrayList<PendingOperation>(mPendingOperations);
1159        }
1160    }
1161
1162    /**
1163     * Return the number of currently pending operations.
1164     */
1165    public int getPendingOperationCount() {
1166        synchronized (mAuthorities) {
1167            return mPendingOperations.size();
1168        }
1169    }
1170
1171    /**
1172     * Called when the set of account has changed, given the new array of
1173     * active accounts.
1174     */
1175    public void doDatabaseCleanup(Account[] accounts, int userId) {
1176        synchronized (mAuthorities) {
1177            if (Log.isLoggable(TAG, Log.VERBOSE)) {
1178                Log.v(TAG, "Updating for new accounts...");
1179            }
1180            SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>();
1181            Iterator<AccountInfo> accIt = mAccounts.values().iterator();
1182            while (accIt.hasNext()) {
1183                AccountInfo acc = accIt.next();
1184                if (!ArrayUtils.contains(accounts, acc.accountAndUser.account)
1185                        && acc.accountAndUser.userId == userId) {
1186                    // This account no longer exists...
1187                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
1188                        Log.v(TAG, "Account removed: " + acc.accountAndUser);
1189                    }
1190                    for (AuthorityInfo auth : acc.authorities.values()) {
1191                        removing.put(auth.ident, auth);
1192                    }
1193                    accIt.remove();
1194                }
1195            }
1196
1197            // Clean out all data structures.
1198            int i = removing.size();
1199            if (i > 0) {
1200                while (i > 0) {
1201                    i--;
1202                    int ident = removing.keyAt(i);
1203                    mAuthorities.remove(ident);
1204                    int j = mSyncStatus.size();
1205                    while (j > 0) {
1206                        j--;
1207                        if (mSyncStatus.keyAt(j) == ident) {
1208                            mSyncStatus.remove(mSyncStatus.keyAt(j));
1209                        }
1210                    }
1211                    j = mSyncHistory.size();
1212                    while (j > 0) {
1213                        j--;
1214                        if (mSyncHistory.get(j).authorityId == ident) {
1215                            mSyncHistory.remove(j);
1216                        }
1217                    }
1218                }
1219                writeAccountInfoLocked();
1220                writeStatusLocked();
1221                writePendingOperationsLocked();
1222                writeStatisticsLocked();
1223            }
1224        }
1225    }
1226
1227    /**
1228     * Called when a sync is starting. Supply a valid ActiveSyncContext with information
1229     * about the sync.
1230     */
1231    public SyncInfo addActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
1232        final SyncInfo syncInfo;
1233        synchronized (mAuthorities) {
1234            if (Log.isLoggable(TAG, Log.VERBOSE)) {
1235                Log.v(TAG, "setActiveSync: account="
1236                    + " auth=" + activeSyncContext.mSyncOperation.target
1237                    + " src=" + activeSyncContext.mSyncOperation.syncSource
1238                    + " extras=" + activeSyncContext.mSyncOperation.extras);
1239            }
1240            final EndPoint info = activeSyncContext.mSyncOperation.target;
1241            AuthorityInfo authorityInfo = getOrCreateAuthorityLocked(
1242                    info,
1243                    -1 /* assign a new identifier if creating a new target */,
1244                    true /* write to storage if this results in a change */);
1245            syncInfo = new SyncInfo(
1246                    authorityInfo.ident,
1247                    authorityInfo.target.account,
1248                    authorityInfo.target.provider,
1249                    authorityInfo.target.service,
1250                    activeSyncContext.mStartTime);
1251            getCurrentSyncs(authorityInfo.target.userId).add(syncInfo);
1252        }
1253        reportActiveChange();
1254        return syncInfo;
1255    }
1256
1257    /**
1258     * Called to indicate that a previously active sync is no longer active.
1259     */
1260    public void removeActiveSync(SyncInfo syncInfo, int userId) {
1261        synchronized (mAuthorities) {
1262            if (Log.isLoggable(TAG, Log.VERBOSE)) {
1263                Log.v(TAG, "removeActiveSync: account=" + syncInfo.account
1264                        + " user=" + userId
1265                        + " auth=" + syncInfo.authority
1266                        + " service=" + syncInfo.service);
1267            }
1268            getCurrentSyncs(userId).remove(syncInfo);
1269        }
1270
1271        reportActiveChange();
1272    }
1273
1274    /**
1275     * To allow others to send active change reports, to poke clients.
1276     */
1277    public void reportActiveChange() {
1278        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE);
1279    }
1280
1281    /**
1282     * Note that sync has started for the given operation.
1283     */
1284    public long insertStartSyncEvent(SyncOperation op, long now) {
1285        long id;
1286        synchronized (mAuthorities) {
1287            if (Log.isLoggable(TAG, Log.VERBOSE)) {
1288                Log.v(TAG, "insertStartSyncEvent: " + op);
1289            }
1290            AuthorityInfo authority = getAuthorityLocked(op.target, "insertStartSyncEvent");
1291            if (authority == null) {
1292                return -1;
1293            }
1294            SyncHistoryItem item = new SyncHistoryItem();
1295            item.initialization = op.isInitialization();
1296            item.authorityId = authority.ident;
1297            item.historyId = mNextHistoryId++;
1298            if (mNextHistoryId < 0) mNextHistoryId = 0;
1299            item.eventTime = now;
1300            item.source = op.syncSource;
1301            item.reason = op.reason;
1302            item.extras = op.extras;
1303            item.event = EVENT_START;
1304            mSyncHistory.add(0, item);
1305            while (mSyncHistory.size() > MAX_HISTORY) {
1306                mSyncHistory.remove(mSyncHistory.size()-1);
1307            }
1308            id = item.historyId;
1309            if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "returning historyId " + id);
1310        }
1311
1312        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
1313        return id;
1314    }
1315
1316    public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage,
1317            long downstreamActivity, long upstreamActivity) {
1318        synchronized (mAuthorities) {
1319            if (Log.isLoggable(TAG, Log.VERBOSE)) {
1320                Log.v(TAG, "stopSyncEvent: historyId=" + historyId);
1321            }
1322            SyncHistoryItem item = null;
1323            int i = mSyncHistory.size();
1324            while (i > 0) {
1325                i--;
1326                item = mSyncHistory.get(i);
1327                if (item.historyId == historyId) {
1328                    break;
1329                }
1330                item = null;
1331            }
1332
1333            if (item == null) {
1334                Log.w(TAG, "stopSyncEvent: no history for id " + historyId);
1335                return;
1336            }
1337
1338            item.elapsedTime = elapsedTime;
1339            item.event = EVENT_STOP;
1340            item.mesg = resultMessage;
1341            item.downstreamActivity = downstreamActivity;
1342            item.upstreamActivity = upstreamActivity;
1343
1344            SyncStatusInfo status = getOrCreateSyncStatusLocked(item.authorityId);
1345
1346            status.numSyncs++;
1347            status.totalElapsedTime += elapsedTime;
1348            switch (item.source) {
1349                case SOURCE_LOCAL:
1350                    status.numSourceLocal++;
1351                    break;
1352                case SOURCE_POLL:
1353                    status.numSourcePoll++;
1354                    break;
1355                case SOURCE_USER:
1356                    status.numSourceUser++;
1357                    break;
1358                case SOURCE_SERVER:
1359                    status.numSourceServer++;
1360                    break;
1361                case SOURCE_PERIODIC:
1362                    status.numSourcePeriodic++;
1363                    break;
1364            }
1365
1366            boolean writeStatisticsNow = false;
1367            int day = getCurrentDayLocked();
1368            if (mDayStats[0] == null) {
1369                mDayStats[0] = new DayStats(day);
1370            } else if (day != mDayStats[0].day) {
1371                System.arraycopy(mDayStats, 0, mDayStats, 1, mDayStats.length-1);
1372                mDayStats[0] = new DayStats(day);
1373                writeStatisticsNow = true;
1374            } else if (mDayStats[0] == null) {
1375            }
1376            final DayStats ds = mDayStats[0];
1377
1378            final long lastSyncTime = (item.eventTime + elapsedTime);
1379            boolean writeStatusNow = false;
1380            if (MESG_SUCCESS.equals(resultMessage)) {
1381                // - if successful, update the successful columns
1382                if (status.lastSuccessTime == 0 || status.lastFailureTime != 0) {
1383                    writeStatusNow = true;
1384                }
1385                status.lastSuccessTime = lastSyncTime;
1386                status.lastSuccessSource = item.source;
1387                status.lastFailureTime = 0;
1388                status.lastFailureSource = -1;
1389                status.lastFailureMesg = null;
1390                status.initialFailureTime = 0;
1391                ds.successCount++;
1392                ds.successTime += elapsedTime;
1393            } else if (!MESG_CANCELED.equals(resultMessage)) {
1394                if (status.lastFailureTime == 0) {
1395                    writeStatusNow = true;
1396                }
1397                status.lastFailureTime = lastSyncTime;
1398                status.lastFailureSource = item.source;
1399                status.lastFailureMesg = resultMessage;
1400                if (status.initialFailureTime == 0) {
1401                    status.initialFailureTime = lastSyncTime;
1402                }
1403                ds.failureCount++;
1404                ds.failureTime += elapsedTime;
1405            }
1406
1407            if (writeStatusNow) {
1408                writeStatusLocked();
1409            } else if (!hasMessages(MSG_WRITE_STATUS)) {
1410                sendMessageDelayed(obtainMessage(MSG_WRITE_STATUS),
1411                        WRITE_STATUS_DELAY);
1412            }
1413            if (writeStatisticsNow) {
1414                writeStatisticsLocked();
1415            } else if (!hasMessages(MSG_WRITE_STATISTICS)) {
1416                sendMessageDelayed(obtainMessage(MSG_WRITE_STATISTICS),
1417                        WRITE_STATISTICS_DELAY);
1418            }
1419        }
1420
1421        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
1422    }
1423
1424    /**
1425     * Return a list of the currently active syncs. Note that the returned
1426     * items are the real, live active sync objects, so be careful what you do
1427     * with it.
1428     */
1429    private List<SyncInfo> getCurrentSyncs(int userId) {
1430        synchronized (mAuthorities) {
1431            return getCurrentSyncsLocked(userId);
1432        }
1433    }
1434
1435    /**
1436     * @return a copy of the current syncs data structure. Will not return
1437     * null.
1438     */
1439    public List<SyncInfo> getCurrentSyncsCopy(int userId) {
1440        synchronized (mAuthorities) {
1441            final List<SyncInfo> syncs = getCurrentSyncsLocked(userId);
1442            final List<SyncInfo> syncsCopy = new ArrayList<SyncInfo>();
1443            for (SyncInfo sync : syncs) {
1444                syncsCopy.add(new SyncInfo(sync));
1445            }
1446            return syncsCopy;
1447        }
1448    }
1449
1450    private List<SyncInfo> getCurrentSyncsLocked(int userId) {
1451        ArrayList<SyncInfo> syncs = mCurrentSyncs.get(userId);
1452        if (syncs == null) {
1453            syncs = new ArrayList<SyncInfo>();
1454            mCurrentSyncs.put(userId, syncs);
1455        }
1456        return syncs;
1457    }
1458
1459    /**
1460     * Return an array of the current sync status for all authorities.  Note
1461     * that the objects inside the array are the real, live status objects,
1462     * so be careful what you do with them.
1463     */
1464    public ArrayList<SyncStatusInfo> getSyncStatus() {
1465        synchronized (mAuthorities) {
1466            final int N = mSyncStatus.size();
1467            ArrayList<SyncStatusInfo> ops = new ArrayList<SyncStatusInfo>(N);
1468            for (int i=0; i<N; i++) {
1469                ops.add(mSyncStatus.valueAt(i));
1470            }
1471            return ops;
1472        }
1473    }
1474
1475    /**
1476     * Return a copy of the specified target with the corresponding sync status
1477     */
1478    public Pair<AuthorityInfo, SyncStatusInfo> getCopyOfAuthorityWithSyncStatus(EndPoint info) {
1479        synchronized (mAuthorities) {
1480            AuthorityInfo authorityInfo = getOrCreateAuthorityLocked(info,
1481                    -1 /* assign a new identifier if creating a new target */,
1482                    true /* write to storage if this results in a change */);
1483            return createCopyPairOfAuthorityWithSyncStatusLocked(authorityInfo);
1484        }
1485    }
1486
1487    /**
1488     * Return a copy of all authorities with their corresponding sync status
1489     */
1490    public ArrayList<Pair<AuthorityInfo, SyncStatusInfo>> getCopyOfAllAuthoritiesWithSyncStatus() {
1491        synchronized (mAuthorities) {
1492            ArrayList<Pair<AuthorityInfo, SyncStatusInfo>> infos =
1493                    new ArrayList<Pair<AuthorityInfo, SyncStatusInfo>>(mAuthorities.size());
1494            for (int i = 0; i < mAuthorities.size(); i++) {
1495                infos.add(createCopyPairOfAuthorityWithSyncStatusLocked(mAuthorities.valueAt(i)));
1496            }
1497            return infos;
1498        }
1499    }
1500
1501    /**
1502     * Returns the status that matches the target.
1503     *
1504     * @param info the endpoint target we are querying status info for.
1505     * @return the SyncStatusInfo for the endpoint.
1506     */
1507    public SyncStatusInfo getStatusByAuthority(EndPoint info) {
1508        if (info.target_provider && (info.account == null || info.provider == null)) {
1509            return null;
1510        } else if (info.target_service && info.service == null) {
1511            return null;
1512        }
1513        synchronized (mAuthorities) {
1514            final int N = mSyncStatus.size();
1515            for (int i = 0; i < N; i++) {
1516                SyncStatusInfo cur = mSyncStatus.valueAt(i);
1517                AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
1518                if (ainfo != null
1519                        && ainfo.target.matchesSpec(info)) {
1520                  return cur;
1521                }
1522            }
1523            return null;
1524        }
1525    }
1526
1527    /** Return true if the pending status is true of any matching authorities. */
1528    public boolean isSyncPending(EndPoint info) {
1529        synchronized (mAuthorities) {
1530            final int N = mSyncStatus.size();
1531            for (int i = 0; i < N; i++) {
1532                SyncStatusInfo cur = mSyncStatus.valueAt(i);
1533                AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
1534                if (ainfo == null) {
1535                    continue;
1536                }
1537                if (!ainfo.target.matchesSpec(info)) {
1538                    continue;
1539                }
1540                if (cur.pending) {
1541                    return true;
1542                }
1543            }
1544            return false;
1545        }
1546    }
1547
1548    /**
1549     * Return an array of the current sync status for all authorities.  Note
1550     * that the objects inside the array are the real, live status objects,
1551     * so be careful what you do with them.
1552     */
1553    public ArrayList<SyncHistoryItem> getSyncHistory() {
1554        synchronized (mAuthorities) {
1555            final int N = mSyncHistory.size();
1556            ArrayList<SyncHistoryItem> items = new ArrayList<SyncHistoryItem>(N);
1557            for (int i=0; i<N; i++) {
1558                items.add(mSyncHistory.get(i));
1559            }
1560            return items;
1561        }
1562    }
1563
1564    /**
1565     * Return an array of the current per-day statistics.  Note
1566     * that the objects inside the array are the real, live status objects,
1567     * so be careful what you do with them.
1568     */
1569    public DayStats[] getDayStatistics() {
1570        synchronized (mAuthorities) {
1571            DayStats[] ds = new DayStats[mDayStats.length];
1572            System.arraycopy(mDayStats, 0, ds, 0, ds.length);
1573            return ds;
1574        }
1575    }
1576
1577    private Pair<AuthorityInfo, SyncStatusInfo> createCopyPairOfAuthorityWithSyncStatusLocked(
1578            AuthorityInfo authorityInfo) {
1579        SyncStatusInfo syncStatusInfo = getOrCreateSyncStatusLocked(authorityInfo.ident);
1580        return Pair.create(new AuthorityInfo(authorityInfo), new SyncStatusInfo(syncStatusInfo));
1581    }
1582
1583    private int getCurrentDayLocked() {
1584        mCal.setTimeInMillis(System.currentTimeMillis());
1585        final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR);
1586        if (mYear != mCal.get(Calendar.YEAR)) {
1587            mYear = mCal.get(Calendar.YEAR);
1588            mCal.clear();
1589            mCal.set(Calendar.YEAR, mYear);
1590            mYearInDays = (int)(mCal.getTimeInMillis()/86400000);
1591        }
1592        return dayOfYear + mYearInDays;
1593    }
1594
1595    /**
1596     * Retrieve a target's full info, returning null if one does not exist.
1597     *
1598     * @param info info of the target to look up.
1599     * @param tag If non-null, this will be used in a log message if the
1600     * requested target does not exist.
1601     */
1602    private AuthorityInfo getAuthorityLocked(EndPoint info, String tag) {
1603        if (info.target_service) {
1604            SparseArray<AuthorityInfo> aInfo = mServices.get(info.service);
1605            AuthorityInfo authority = null;
1606            if (aInfo != null) {
1607                authority = aInfo.get(info.userId);
1608            }
1609            if (authority == null) {
1610                if (tag != null) {
1611                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
1612                        Log.v(TAG, tag + " No authority info found for " + info.service + " for"
1613                                + " user " + info.userId);
1614                    }
1615                }
1616                return null;
1617            }
1618            return authority;
1619        } else if (info.target_provider){
1620            AccountAndUser au = new AccountAndUser(info.account, info.userId);
1621            AccountInfo accountInfo = mAccounts.get(au);
1622            if (accountInfo == null) {
1623                if (tag != null) {
1624                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
1625                        Log.v(TAG, tag + ": unknown account " + au);
1626                    }
1627                }
1628                return null;
1629            }
1630            AuthorityInfo authority = accountInfo.authorities.get(info.provider);
1631            if (authority == null) {
1632                if (tag != null) {
1633                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
1634                        Log.v(TAG, tag + ": unknown provider " + info.provider);
1635                    }
1636                }
1637                return null;
1638            }
1639            return authority;
1640        } else {
1641            Log.e(TAG, tag + " Authority : " + info + ", invalid target");
1642            return null;
1643        }
1644    }
1645
1646    /**
1647     * @param info info identifying target.
1648     * @param ident unique identifier for target. -1 for none.
1649     * @param doWrite if true, update the accounts.xml file on the disk.
1650     * @return the authority that corresponds to the provided sync target, creating it if none
1651     * exists.
1652     */
1653    private AuthorityInfo getOrCreateAuthorityLocked(EndPoint info, int ident, boolean doWrite) {
1654        AuthorityInfo authority = null;
1655        if (info.target_service) {
1656            SparseArray<AuthorityInfo> aInfo = mServices.get(info.service);
1657            if (aInfo == null) {
1658                aInfo = new SparseArray<AuthorityInfo>();
1659                mServices.put(info.service, aInfo);
1660            }
1661            authority = aInfo.get(info.userId);
1662            if (authority == null) {
1663                authority = createAuthorityLocked(info, ident, doWrite);
1664                aInfo.put(info.userId, authority);
1665            }
1666        } else if (info.target_provider) {
1667            AccountAndUser au = new AccountAndUser(info.account, info.userId);
1668            AccountInfo account = mAccounts.get(au);
1669            if (account == null) {
1670                account = new AccountInfo(au);
1671                mAccounts.put(au, account);
1672            }
1673            authority = account.authorities.get(info.provider);
1674            if (authority == null) {
1675                authority = createAuthorityLocked(info, ident, doWrite);
1676                account.authorities.put(info.provider, authority);
1677            }
1678        }
1679        return authority;
1680    }
1681
1682    private AuthorityInfo createAuthorityLocked(EndPoint info, int ident, boolean doWrite) {
1683        AuthorityInfo authority;
1684        if (ident < 0) {
1685            ident = mNextAuthorityId;
1686            mNextAuthorityId++;
1687            doWrite = true;
1688        }
1689        if (Log.isLoggable(TAG, Log.VERBOSE)) {
1690            Log.v(TAG, "created a new AuthorityInfo for " + info);
1691        }
1692        authority = new AuthorityInfo(info, ident);
1693        mAuthorities.put(ident, authority);
1694        if (doWrite) {
1695            writeAccountInfoLocked();
1696        }
1697        return authority;
1698    }
1699
1700    public void removeAuthority(EndPoint info) {
1701        synchronized (mAuthorities) {
1702            if (info.target_provider) {
1703                removeAuthorityLocked(info.account, info.userId, info.provider, true /* doWrite */);
1704            } else {
1705                SparseArray<AuthorityInfo> aInfos = mServices.get(info.service);
1706                if (aInfos != null) {
1707                    AuthorityInfo authorityInfo = aInfos.get(info.userId);
1708                    if (authorityInfo != null) {
1709                        mAuthorities.remove(authorityInfo.ident);
1710                        aInfos.delete(info.userId);
1711                        writeAccountInfoLocked();
1712                    }
1713                }
1714
1715            }
1716        }
1717    }
1718
1719    /**
1720     * Remove an authority associated with a provider. Needs to be a standalone function for
1721     * backward compatibility.
1722     */
1723    private void removeAuthorityLocked(Account account, int userId, String authorityName,
1724            boolean doWrite) {
1725        AccountInfo accountInfo = mAccounts.get(new AccountAndUser(account, userId));
1726        if (accountInfo != null) {
1727            final AuthorityInfo authorityInfo = accountInfo.authorities.remove(authorityName);
1728            if (authorityInfo != null) {
1729                mAuthorities.remove(authorityInfo.ident);
1730                if (doWrite) {
1731                    writeAccountInfoLocked();
1732                }
1733            }
1734        }
1735    }
1736
1737    /**
1738     * Updates (in a synchronized way) the periodic sync time of the specified
1739     * target id and target periodic sync
1740     */
1741    public void setPeriodicSyncTime(int authorityId, PeriodicSync targetPeriodicSync, long when) {
1742        boolean found = false;
1743        final AuthorityInfo authorityInfo;
1744        synchronized (mAuthorities) {
1745            authorityInfo = mAuthorities.get(authorityId);
1746            for (int i = 0; i < authorityInfo.periodicSyncs.size(); i++) {
1747                PeriodicSync periodicSync = authorityInfo.periodicSyncs.get(i);
1748                if (targetPeriodicSync.equals(periodicSync)) {
1749                    mSyncStatus.get(authorityId).setPeriodicSyncTime(i, when);
1750                    found = true;
1751                    break;
1752                }
1753            }
1754        }
1755        if (!found) {
1756            Log.w(TAG, "Ignoring setPeriodicSyncTime request for a sync that does not exist. " +
1757                    "Authority: " + authorityInfo.target);
1758        }
1759    }
1760
1761    private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) {
1762        SyncStatusInfo status = mSyncStatus.get(authorityId);
1763        if (status == null) {
1764            status = new SyncStatusInfo(authorityId);
1765            mSyncStatus.put(authorityId, status);
1766        }
1767        return status;
1768    }
1769
1770    public void writeAllState() {
1771        synchronized (mAuthorities) {
1772            // Account info is always written so no need to do it here.
1773
1774            if (mNumPendingFinished > 0) {
1775                // Only write these if they are out of date.
1776                writePendingOperationsLocked();
1777            }
1778
1779            // Just always write these...  they are likely out of date.
1780            writeStatusLocked();
1781            writeStatisticsLocked();
1782        }
1783    }
1784
1785    /**
1786     * public for testing
1787     */
1788    public void clearAndReadState() {
1789        synchronized (mAuthorities) {
1790            mAuthorities.clear();
1791            mAccounts.clear();
1792            mServices.clear();
1793            mPendingOperations.clear();
1794            mSyncStatus.clear();
1795            mSyncHistory.clear();
1796
1797            readAccountInfoLocked();
1798            readStatusLocked();
1799            readPendingOperationsLocked();
1800            readStatisticsLocked();
1801            readAndDeleteLegacyAccountInfoLocked();
1802            writeAccountInfoLocked();
1803            writeStatusLocked();
1804            writePendingOperationsLocked();
1805            writeStatisticsLocked();
1806        }
1807    }
1808
1809    /**
1810     * Read all account information back in to the initial engine state.
1811     */
1812    private void readAccountInfoLocked() {
1813        int highestAuthorityId = -1;
1814        FileInputStream fis = null;
1815        try {
1816            fis = mAccountInfoFile.openRead();
1817            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
1818                Log.v(TAG_FILE, "Reading " + mAccountInfoFile.getBaseFile());
1819            }
1820            XmlPullParser parser = Xml.newPullParser();
1821            parser.setInput(fis, null);
1822            int eventType = parser.getEventType();
1823            while (eventType != XmlPullParser.START_TAG &&
1824                    eventType != XmlPullParser.END_DOCUMENT) {
1825                eventType = parser.next();
1826            }
1827            if (eventType == XmlPullParser.END_DOCUMENT) {
1828                Log.i(TAG, "No initial accounts");
1829                return;
1830            }
1831
1832            String tagName = parser.getName();
1833            if ("accounts".equals(tagName)) {
1834                String listen = parser.getAttributeValue(null, XML_ATTR_LISTEN_FOR_TICKLES);
1835                String versionString = parser.getAttributeValue(null, "version");
1836                int version;
1837                try {
1838                    version = (versionString == null) ? 0 : Integer.parseInt(versionString);
1839                } catch (NumberFormatException e) {
1840                    version = 0;
1841                }
1842                String nextIdString = parser.getAttributeValue(null, XML_ATTR_NEXT_AUTHORITY_ID);
1843                try {
1844                    int id = (nextIdString == null) ? 0 : Integer.parseInt(nextIdString);
1845                    mNextAuthorityId = Math.max(mNextAuthorityId, id);
1846                } catch (NumberFormatException e) {
1847                    // don't care
1848                }
1849                String offsetString = parser.getAttributeValue(null, XML_ATTR_SYNC_RANDOM_OFFSET);
1850                try {
1851                    mSyncRandomOffset = (offsetString == null) ? 0 : Integer.parseInt(offsetString);
1852                } catch (NumberFormatException e) {
1853                    mSyncRandomOffset = 0;
1854                }
1855                if (mSyncRandomOffset == 0) {
1856                    Random random = new Random(System.currentTimeMillis());
1857                    mSyncRandomOffset = random.nextInt(86400);
1858                }
1859                mMasterSyncAutomatically.put(0, listen == null || Boolean.parseBoolean(listen));
1860                eventType = parser.next();
1861                AuthorityInfo authority = null;
1862                PeriodicSync periodicSync = null;
1863                do {
1864                    if (eventType == XmlPullParser.START_TAG) {
1865                        tagName = parser.getName();
1866                        if (parser.getDepth() == 2) {
1867                            if ("authority".equals(tagName)) {
1868                                authority = parseAuthority(parser, version);
1869                                periodicSync = null;
1870                                if (authority.ident > highestAuthorityId) {
1871                                    highestAuthorityId = authority.ident;
1872                                }
1873                            } else if (XML_TAG_LISTEN_FOR_TICKLES.equals(tagName)) {
1874                                parseListenForTickles(parser);
1875                            }
1876                        } else if (parser.getDepth() == 3) {
1877                            if ("periodicSync".equals(tagName) && authority != null) {
1878                                periodicSync = parsePeriodicSync(parser, authority);
1879                            }
1880                        } else if (parser.getDepth() == 4 && periodicSync != null) {
1881                            if ("extra".equals(tagName)) {
1882                                parseExtra(parser, periodicSync.extras);
1883                            }
1884                        }
1885                    }
1886                    eventType = parser.next();
1887                } while (eventType != XmlPullParser.END_DOCUMENT);
1888            }
1889        } catch (XmlPullParserException e) {
1890            Log.w(TAG, "Error reading accounts", e);
1891            return;
1892        } catch (java.io.IOException e) {
1893            if (fis == null) Log.i(TAG, "No initial accounts");
1894            else Log.w(TAG, "Error reading accounts", e);
1895            return;
1896        } finally {
1897            mNextAuthorityId = Math.max(highestAuthorityId + 1, mNextAuthorityId);
1898            if (fis != null) {
1899                try {
1900                    fis.close();
1901                } catch (java.io.IOException e1) {
1902                }
1903            }
1904        }
1905
1906        maybeMigrateSettingsForRenamedAuthorities();
1907    }
1908
1909    /**
1910     * Ensure the old pending.bin is deleted, as it has been changed to pending.xml.
1911     * pending.xml was used starting in KLP.
1912     * @param syncDir directory where the sync files are located.
1913     */
1914    private void maybeDeleteLegacyPendingInfoLocked(File syncDir) {
1915        File file = new File(syncDir, "pending.bin");
1916        if (!file.exists()) {
1917            return;
1918        } else {
1919            file.delete();
1920        }
1921    }
1922
1923    /**
1924     * some authority names have changed. copy over their settings and delete the old ones
1925     * @return true if a change was made
1926     */
1927    private boolean maybeMigrateSettingsForRenamedAuthorities() {
1928        boolean writeNeeded = false;
1929
1930        ArrayList<AuthorityInfo> authoritiesToRemove = new ArrayList<AuthorityInfo>();
1931        final int N = mAuthorities.size();
1932        for (int i = 0; i < N; i++) {
1933            AuthorityInfo authority = mAuthorities.valueAt(i);
1934            // skip this authority if it doesn't target a provider
1935            if (authority.target.target_service) {
1936                continue;
1937            }
1938            // skip this authority if it isn't one of the renamed ones
1939            final String newAuthorityName = sAuthorityRenames.get(authority.target.provider);
1940            if (newAuthorityName == null) {
1941                continue;
1942            }
1943
1944            // remember this authority so we can remove it later. we can't remove it
1945            // now without messing up this loop iteration
1946            authoritiesToRemove.add(authority);
1947
1948            // this authority isn't enabled, no need to copy it to the new authority name since
1949            // the default is "disabled"
1950            if (!authority.enabled) {
1951                continue;
1952            }
1953
1954            // if we already have a record of this new authority then don't copy over the settings
1955            EndPoint newInfo =
1956                    new EndPoint(authority.target.account,
1957                            newAuthorityName,
1958                            authority.target.userId);
1959            if (getAuthorityLocked(newInfo, "cleanup") != null) {
1960                continue;
1961            }
1962
1963            AuthorityInfo newAuthority =
1964                    getOrCreateAuthorityLocked(newInfo, -1 /* ident */, false /* doWrite */);
1965            newAuthority.enabled = true;
1966            writeNeeded = true;
1967        }
1968
1969        for (AuthorityInfo authorityInfo : authoritiesToRemove) {
1970            removeAuthorityLocked(
1971                    authorityInfo.target.account,
1972                    authorityInfo.target.userId,
1973                    authorityInfo.target.provider,
1974                    false /* doWrite */);
1975            writeNeeded = true;
1976        }
1977
1978        return writeNeeded;
1979    }
1980
1981    private void parseListenForTickles(XmlPullParser parser) {
1982        String user = parser.getAttributeValue(null, XML_ATTR_USER);
1983        int userId = 0;
1984        try {
1985            userId = Integer.parseInt(user);
1986        } catch (NumberFormatException e) {
1987            Log.e(TAG, "error parsing the user for listen-for-tickles", e);
1988        } catch (NullPointerException e) {
1989            Log.e(TAG, "the user in listen-for-tickles is null", e);
1990        }
1991        String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED);
1992        boolean listen = enabled == null || Boolean.parseBoolean(enabled);
1993        mMasterSyncAutomatically.put(userId, listen);
1994    }
1995
1996    private AuthorityInfo parseAuthority(XmlPullParser parser, int version) {
1997        AuthorityInfo authority = null;
1998        int id = -1;
1999        try {
2000            id = Integer.parseInt(parser.getAttributeValue(null, "id"));
2001        } catch (NumberFormatException e) {
2002            Log.e(TAG, "error parsing the id of the authority", e);
2003        } catch (NullPointerException e) {
2004            Log.e(TAG, "the id of the authority is null", e);
2005        }
2006        if (id >= 0) {
2007            String authorityName = parser.getAttributeValue(null, "authority");
2008            String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED);
2009            String syncable = parser.getAttributeValue(null, "syncable");
2010            String accountName = parser.getAttributeValue(null, "account");
2011            String accountType = parser.getAttributeValue(null, "type");
2012            String user = parser.getAttributeValue(null, XML_ATTR_USER);
2013            String packageName = parser.getAttributeValue(null, "package");
2014            String className = parser.getAttributeValue(null, "class");
2015            int userId = user == null ? 0 : Integer.parseInt(user);
2016            if (accountType == null && packageName == null) {
2017                accountType = "com.google";
2018                syncable = "unknown";
2019            }
2020            authority = mAuthorities.get(id);
2021            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2022                Log.v(TAG_FILE, "Adding authority:"
2023                        + " account=" + accountName
2024                        + " accountType=" + accountType
2025                        + " auth=" + authorityName
2026                        + " package=" + packageName
2027                        + " class=" + className
2028                        + " user=" + userId
2029                        + " enabled=" + enabled
2030                        + " syncable=" + syncable);
2031            }
2032            if (authority == null) {
2033                if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2034                    Log.v(TAG_FILE, "Creating authority entry");
2035                }
2036                EndPoint info;
2037                if (accountName != null && authorityName != null) {
2038                    info = new EndPoint(
2039                            new Account(accountName, accountType),
2040                            authorityName, userId);
2041                } else {
2042                    info = new EndPoint(
2043                            new ComponentName(packageName, className),
2044                            userId);
2045                }
2046                authority = getOrCreateAuthorityLocked(info, id, false);
2047                // If the version is 0 then we are upgrading from a file format that did not
2048                // know about periodic syncs. In that case don't clear the list since we
2049                // want the default, which is a daily periodic sync.
2050                // Otherwise clear out this default list since we will populate it later with
2051                // the periodic sync descriptions that are read from the configuration file.
2052                if (version > 0) {
2053                    authority.periodicSyncs.clear();
2054                }
2055            }
2056            if (authority != null) {
2057                authority.enabled = enabled == null || Boolean.parseBoolean(enabled);
2058                if ("unknown".equals(syncable)) {
2059                    authority.syncable = -1;
2060                } else {
2061                    authority.syncable =
2062                            (syncable == null || Boolean.parseBoolean(syncable)) ? 1 : 0;
2063                }
2064            } else {
2065                Log.w(TAG, "Failure adding authority: account="
2066                        + accountName + " auth=" + authorityName
2067                        + " enabled=" + enabled
2068                        + " syncable=" + syncable);
2069            }
2070        }
2071        return authority;
2072    }
2073
2074    /**
2075     * Parse a periodic sync from accounts.xml. Sets the bundle to be empty.
2076     */
2077    private PeriodicSync parsePeriodicSync(XmlPullParser parser, AuthorityInfo authorityInfo) {
2078        Bundle extras = new Bundle(); // Gets filled in later.
2079        String periodValue = parser.getAttributeValue(null, "period");
2080        String flexValue = parser.getAttributeValue(null, "flex");
2081        final long period;
2082        long flextime;
2083        try {
2084            period = Long.parseLong(periodValue);
2085        } catch (NumberFormatException e) {
2086            Log.e(TAG, "error parsing the period of a periodic sync", e);
2087            return null;
2088        } catch (NullPointerException e) {
2089            Log.e(TAG, "the period of a periodic sync is null", e);
2090            return null;
2091        }
2092        try {
2093            flextime = Long.parseLong(flexValue);
2094        } catch (NumberFormatException e) {
2095            flextime = calculateDefaultFlexTime(period);
2096            Log.e(TAG, "Error formatting value parsed for periodic sync flex: " + flexValue
2097                    + ", using default: "
2098                    + flextime);
2099        } catch (NullPointerException expected) {
2100            flextime = calculateDefaultFlexTime(period);
2101            Log.d(TAG, "No flex time specified for this sync, using a default. period: "
2102            + period + " flex: " + flextime);
2103        }
2104        PeriodicSync periodicSync;
2105        if (authorityInfo.target.target_provider) {
2106            periodicSync =
2107                new PeriodicSync(authorityInfo.target.account,
2108                        authorityInfo.target.provider,
2109                        extras,
2110                        period, flextime);
2111        } else {
2112            periodicSync =
2113                    new PeriodicSync(
2114                            authorityInfo.target.service,
2115                            extras,
2116                            period,
2117                            flextime);
2118        }
2119        authorityInfo.periodicSyncs.add(periodicSync);
2120        return periodicSync;
2121    }
2122
2123    private void parseExtra(XmlPullParser parser, Bundle extras) {
2124        String name = parser.getAttributeValue(null, "name");
2125        String type = parser.getAttributeValue(null, "type");
2126        String value1 = parser.getAttributeValue(null, "value1");
2127        String value2 = parser.getAttributeValue(null, "value2");
2128
2129        try {
2130            if ("long".equals(type)) {
2131                extras.putLong(name, Long.parseLong(value1));
2132            } else if ("integer".equals(type)) {
2133                extras.putInt(name, Integer.parseInt(value1));
2134            } else if ("double".equals(type)) {
2135                extras.putDouble(name, Double.parseDouble(value1));
2136            } else if ("float".equals(type)) {
2137                extras.putFloat(name, Float.parseFloat(value1));
2138            } else if ("boolean".equals(type)) {
2139                extras.putBoolean(name, Boolean.parseBoolean(value1));
2140            } else if ("string".equals(type)) {
2141                extras.putString(name, value1);
2142            } else if ("account".equals(type)) {
2143                extras.putParcelable(name, new Account(value1, value2));
2144            }
2145        } catch (NumberFormatException e) {
2146            Log.e(TAG, "error parsing bundle value", e);
2147        } catch (NullPointerException e) {
2148            Log.e(TAG, "error parsing bundle value", e);
2149        }
2150    }
2151
2152    /**
2153     * Write all account information to the account file.
2154     */
2155    private void writeAccountInfoLocked() {
2156        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2157            Log.v(TAG_FILE, "Writing new " + mAccountInfoFile.getBaseFile());
2158        }
2159        FileOutputStream fos = null;
2160
2161        try {
2162            fos = mAccountInfoFile.startWrite();
2163            XmlSerializer out = new FastXmlSerializer();
2164            out.setOutput(fos, "utf-8");
2165            out.startDocument(null, true);
2166            out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
2167
2168            out.startTag(null, "accounts");
2169            out.attribute(null, "version", Integer.toString(ACCOUNTS_VERSION));
2170            out.attribute(null, XML_ATTR_NEXT_AUTHORITY_ID, Integer.toString(mNextAuthorityId));
2171            out.attribute(null, XML_ATTR_SYNC_RANDOM_OFFSET, Integer.toString(mSyncRandomOffset));
2172
2173            // Write the Sync Automatically flags for each user
2174            final int M = mMasterSyncAutomatically.size();
2175            for (int m = 0; m < M; m++) {
2176                int userId = mMasterSyncAutomatically.keyAt(m);
2177                Boolean listen = mMasterSyncAutomatically.valueAt(m);
2178                out.startTag(null, XML_TAG_LISTEN_FOR_TICKLES);
2179                out.attribute(null, XML_ATTR_USER, Integer.toString(userId));
2180                out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(listen));
2181                out.endTag(null, XML_TAG_LISTEN_FOR_TICKLES);
2182            }
2183
2184            final int N = mAuthorities.size();
2185            for (int i = 0; i < N; i++) {
2186                AuthorityInfo authority = mAuthorities.valueAt(i);
2187                EndPoint info = authority.target;
2188                out.startTag(null, "authority");
2189                out.attribute(null, "id", Integer.toString(authority.ident));
2190                out.attribute(null, XML_ATTR_USER, Integer.toString(info.userId));
2191                out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(authority.enabled));
2192                if (info.service == null) {
2193                    out.attribute(null, "account", info.account.name);
2194                    out.attribute(null, "type", info.account.type);
2195                    out.attribute(null, "authority", info.provider);
2196                } else {
2197                    out.attribute(null, "package", info.service.getPackageName());
2198                    out.attribute(null, "class", info.service.getClassName());
2199                }
2200                if (authority.syncable < 0) {
2201                    out.attribute(null, "syncable", "unknown");
2202                } else {
2203                    out.attribute(null, "syncable", Boolean.toString(authority.syncable != 0));
2204                }
2205                for (PeriodicSync periodicSync : authority.periodicSyncs) {
2206                    out.startTag(null, "periodicSync");
2207                    out.attribute(null, "period", Long.toString(periodicSync.period));
2208                    out.attribute(null, "flex", Long.toString(periodicSync.flexTime));
2209                    final Bundle extras = periodicSync.extras;
2210                    extrasToXml(out, extras);
2211                    out.endTag(null, "periodicSync");
2212                }
2213                out.endTag(null, "authority");
2214            }
2215            out.endTag(null, "accounts");
2216            out.endDocument();
2217            mAccountInfoFile.finishWrite(fos);
2218        } catch (java.io.IOException e1) {
2219            Log.w(TAG, "Error writing accounts", e1);
2220            if (fos != null) {
2221                mAccountInfoFile.failWrite(fos);
2222            }
2223        }
2224    }
2225
2226    static int getIntColumn(Cursor c, String name) {
2227        return c.getInt(c.getColumnIndex(name));
2228    }
2229
2230    static long getLongColumn(Cursor c, String name) {
2231        return c.getLong(c.getColumnIndex(name));
2232    }
2233
2234    /**
2235     * Load sync engine state from the old syncmanager database, and then
2236     * erase it.  Note that we don't deal with pending operations, active
2237     * sync, or history.
2238     */
2239    private void readAndDeleteLegacyAccountInfoLocked() {
2240        // Look for old database to initialize from.
2241        File file = mContext.getDatabasePath("syncmanager.db");
2242        if (!file.exists()) {
2243            return;
2244        }
2245        String path = file.getPath();
2246        SQLiteDatabase db = null;
2247        try {
2248            db = SQLiteDatabase.openDatabase(path, null,
2249                    SQLiteDatabase.OPEN_READONLY);
2250        } catch (SQLiteException e) {
2251        }
2252
2253        if (db != null) {
2254            final boolean hasType = db.getVersion() >= 11;
2255
2256            // Copy in all of the status information, as well as accounts.
2257            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2258                Log.v(TAG_FILE, "Reading legacy sync accounts db");
2259            }
2260            SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
2261            qb.setTables("stats, status");
2262            HashMap<String,String> map = new HashMap<String,String>();
2263            map.put("_id", "status._id as _id");
2264            map.put("account", "stats.account as account");
2265            if (hasType) {
2266                map.put("account_type", "stats.account_type as account_type");
2267            }
2268            map.put("authority", "stats.authority as authority");
2269            map.put("totalElapsedTime", "totalElapsedTime");
2270            map.put("numSyncs", "numSyncs");
2271            map.put("numSourceLocal", "numSourceLocal");
2272            map.put("numSourcePoll", "numSourcePoll");
2273            map.put("numSourceServer", "numSourceServer");
2274            map.put("numSourceUser", "numSourceUser");
2275            map.put("lastSuccessSource", "lastSuccessSource");
2276            map.put("lastSuccessTime", "lastSuccessTime");
2277            map.put("lastFailureSource", "lastFailureSource");
2278            map.put("lastFailureTime", "lastFailureTime");
2279            map.put("lastFailureMesg", "lastFailureMesg");
2280            map.put("pending", "pending");
2281            qb.setProjectionMap(map);
2282            qb.appendWhere("stats._id = status.stats_id");
2283            Cursor c = qb.query(db, null, null, null, null, null, null);
2284            while (c.moveToNext()) {
2285                String accountName = c.getString(c.getColumnIndex("account"));
2286                String accountType = hasType
2287                        ? c.getString(c.getColumnIndex("account_type")) : null;
2288                if (accountType == null) {
2289                    accountType = "com.google";
2290                }
2291                String authorityName = c.getString(c.getColumnIndex("authority"));
2292                AuthorityInfo authority =
2293                        this.getOrCreateAuthorityLocked(
2294                                new EndPoint(new Account(accountName, accountType),
2295                                        authorityName,
2296                                        0 /* legacy is single-user */)
2297                                , -1,
2298                                false);
2299                if (authority != null) {
2300                    int i = mSyncStatus.size();
2301                    boolean found = false;
2302                    SyncStatusInfo st = null;
2303                    while (i > 0) {
2304                        i--;
2305                        st = mSyncStatus.valueAt(i);
2306                        if (st.authorityId == authority.ident) {
2307                            found = true;
2308                            break;
2309                        }
2310                    }
2311                    if (!found) {
2312                        st = new SyncStatusInfo(authority.ident);
2313                        mSyncStatus.put(authority.ident, st);
2314                    }
2315                    st.totalElapsedTime = getLongColumn(c, "totalElapsedTime");
2316                    st.numSyncs = getIntColumn(c, "numSyncs");
2317                    st.numSourceLocal = getIntColumn(c, "numSourceLocal");
2318                    st.numSourcePoll = getIntColumn(c, "numSourcePoll");
2319                    st.numSourceServer = getIntColumn(c, "numSourceServer");
2320                    st.numSourceUser = getIntColumn(c, "numSourceUser");
2321                    st.numSourcePeriodic = 0;
2322                    st.lastSuccessSource = getIntColumn(c, "lastSuccessSource");
2323                    st.lastSuccessTime = getLongColumn(c, "lastSuccessTime");
2324                    st.lastFailureSource = getIntColumn(c, "lastFailureSource");
2325                    st.lastFailureTime = getLongColumn(c, "lastFailureTime");
2326                    st.lastFailureMesg = c.getString(c.getColumnIndex("lastFailureMesg"));
2327                    st.pending = getIntColumn(c, "pending") != 0;
2328                }
2329            }
2330
2331            c.close();
2332
2333            // Retrieve the settings.
2334            qb = new SQLiteQueryBuilder();
2335            qb.setTables("settings");
2336            c = qb.query(db, null, null, null, null, null, null);
2337            while (c.moveToNext()) {
2338                String name = c.getString(c.getColumnIndex("name"));
2339                String value = c.getString(c.getColumnIndex("value"));
2340                if (name == null) continue;
2341                if (name.equals("listen_for_tickles")) {
2342                    setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value), 0);
2343                } else if (name.startsWith("sync_provider_")) {
2344                    String provider = name.substring("sync_provider_".length(),
2345                            name.length());
2346                    int i = mAuthorities.size();
2347                    while (i > 0) {
2348                        i--;
2349                        AuthorityInfo authority = mAuthorities.valueAt(i);
2350                        if (authority.target.provider.equals(provider)) {
2351                            authority.enabled = value == null || Boolean.parseBoolean(value);
2352                            authority.syncable = 1;
2353                        }
2354                    }
2355                }
2356            }
2357
2358            c.close();
2359
2360            db.close();
2361
2362            (new File(path)).delete();
2363        }
2364    }
2365
2366    public static final int STATUS_FILE_END = 0;
2367    public static final int STATUS_FILE_ITEM = 100;
2368
2369    /**
2370     * Read all sync status back in to the initial engine state.
2371     */
2372    private void readStatusLocked() {
2373        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2374            Log.v(TAG_FILE, "Reading " + mStatusFile.getBaseFile());
2375        }
2376        try {
2377            byte[] data = mStatusFile.readFully();
2378            Parcel in = Parcel.obtain();
2379            in.unmarshall(data, 0, data.length);
2380            in.setDataPosition(0);
2381            int token;
2382            while ((token=in.readInt()) != STATUS_FILE_END) {
2383                if (token == STATUS_FILE_ITEM) {
2384                    SyncStatusInfo status = new SyncStatusInfo(in);
2385                    if (mAuthorities.indexOfKey(status.authorityId) >= 0) {
2386                        status.pending = false;
2387                        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2388                            Log.v(TAG_FILE, "Adding status for id " + status.authorityId);
2389                        }
2390                        mSyncStatus.put(status.authorityId, status);
2391                    }
2392                } else {
2393                    // Ooops.
2394                    Log.w(TAG, "Unknown status token: " + token);
2395                    break;
2396                }
2397            }
2398        } catch (java.io.IOException e) {
2399            Log.i(TAG, "No initial status");
2400        }
2401    }
2402
2403    /**
2404     * Write all sync status to the sync status file.
2405     */
2406    private void writeStatusLocked() {
2407        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2408            Log.v(TAG_FILE, "Writing new " + mStatusFile.getBaseFile());
2409        }
2410
2411        // The file is being written, so we don't need to have a scheduled
2412        // write until the next change.
2413        removeMessages(MSG_WRITE_STATUS);
2414
2415        FileOutputStream fos = null;
2416        try {
2417            fos = mStatusFile.startWrite();
2418            Parcel out = Parcel.obtain();
2419            final int N = mSyncStatus.size();
2420            for (int i=0; i<N; i++) {
2421                SyncStatusInfo status = mSyncStatus.valueAt(i);
2422                out.writeInt(STATUS_FILE_ITEM);
2423                status.writeToParcel(out, 0);
2424            }
2425            out.writeInt(STATUS_FILE_END);
2426            fos.write(out.marshall());
2427            out.recycle();
2428
2429            mStatusFile.finishWrite(fos);
2430        } catch (java.io.IOException e1) {
2431            Log.w(TAG, "Error writing status", e1);
2432            if (fos != null) {
2433                mStatusFile.failWrite(fos);
2434            }
2435        }
2436    }
2437
2438    public static final int PENDING_OPERATION_VERSION = 3;
2439
2440    /** Read all pending operations back in to the initial engine state. */
2441    private void readPendingOperationsLocked() {
2442        FileInputStream fis = null;
2443        if (!mPendingFile.getBaseFile().exists()) {
2444            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2445                Log.v(TAG_FILE, "No pending operation file.");
2446                return;
2447            }
2448        }
2449        try {
2450            fis = mPendingFile.openRead();
2451            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2452                Log.v(TAG_FILE, "Reading " + mPendingFile.getBaseFile());
2453            }
2454            XmlPullParser parser;
2455            parser = Xml.newPullParser();
2456            parser.setInput(fis, null);
2457
2458            int eventType = parser.getEventType();
2459            while (eventType != XmlPullParser.START_TAG &&
2460                    eventType != XmlPullParser.END_DOCUMENT) {
2461                eventType = parser.next();
2462            }
2463            if (eventType == XmlPullParser.END_DOCUMENT) return; // Nothing to read.
2464
2465            do {
2466                PendingOperation pop = null;
2467                if (eventType == XmlPullParser.START_TAG) {
2468                    try {
2469                        String tagName = parser.getName();
2470                        if (parser.getDepth() == 1 && "op".equals(tagName)) {
2471                            // Verify version.
2472                            String versionString =
2473                                    parser.getAttributeValue(null, XML_ATTR_VERSION);
2474                            if (versionString == null ||
2475                                    Integer.parseInt(versionString) != PENDING_OPERATION_VERSION) {
2476                                Log.w(TAG, "Unknown pending operation version " + versionString);
2477                                throw new java.io.IOException("Unknown version.");
2478                            }
2479                            int authorityId = Integer.valueOf(parser.getAttributeValue(
2480                                    null, XML_ATTR_AUTHORITYID));
2481                            boolean expedited = Boolean.valueOf(parser.getAttributeValue(
2482                                    null, XML_ATTR_EXPEDITED));
2483                            int syncSource = Integer.valueOf(parser.getAttributeValue(
2484                                    null, XML_ATTR_SOURCE));
2485                            int reason = Integer.valueOf(parser.getAttributeValue(
2486                                    null, XML_ATTR_REASON));
2487                            AuthorityInfo authority = mAuthorities.get(authorityId);
2488                            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2489                                Log.v(TAG_FILE, authorityId + " " + expedited + " " + syncSource + " "
2490                                        + reason);
2491                            }
2492                            if (authority != null) {
2493                                pop = new PendingOperation(
2494                                        authority, reason, syncSource, new Bundle(), expedited);
2495                                pop.flatExtras = null; // No longer used.
2496                                mPendingOperations.add(pop);
2497                                if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2498                                    Log.v(TAG_FILE, "Adding pending op: "
2499                                            + pop.target
2500                                            + " src=" + pop.syncSource
2501                                            + " reason=" + pop.reason
2502                                            + " expedited=" + pop.expedited);
2503                                    }
2504                            } else {
2505                                // Skip non-existent authority.
2506                                pop = null;
2507                                if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2508                                    Log.v(TAG_FILE, "No authority found for " + authorityId
2509                                            + ", skipping");
2510                                }
2511                            }
2512                        } else if (parser.getDepth() == 2 &&
2513                                pop != null &&
2514                                "extra".equals(tagName)) {
2515                            parseExtra(parser, pop.extras);
2516                        }
2517                    } catch (NumberFormatException e) {
2518                        Log.d(TAG, "Invalid data in xml file.", e);
2519                    }
2520                }
2521                eventType = parser.next();
2522            } while(eventType != XmlPullParser.END_DOCUMENT);
2523        } catch (java.io.IOException e) {
2524            Log.w(TAG_FILE, "Error reading pending data.", e);
2525        } catch (XmlPullParserException e) {
2526            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2527                Log.w(TAG_FILE, "Error parsing pending ops xml.", e);
2528            }
2529        } finally {
2530            if (fis != null) {
2531                try {
2532                    fis.close();
2533                } catch (java.io.IOException e1) {}
2534            }
2535        }
2536    }
2537
2538    static private byte[] flattenBundle(Bundle bundle) {
2539        byte[] flatData = null;
2540        Parcel parcel = Parcel.obtain();
2541        try {
2542            bundle.writeToParcel(parcel, 0);
2543            flatData = parcel.marshall();
2544        } finally {
2545            parcel.recycle();
2546        }
2547        return flatData;
2548    }
2549
2550    static private Bundle unflattenBundle(byte[] flatData) {
2551        Bundle bundle;
2552        Parcel parcel = Parcel.obtain();
2553        try {
2554            parcel.unmarshall(flatData, 0, flatData.length);
2555            parcel.setDataPosition(0);
2556            bundle = parcel.readBundle();
2557        } catch (RuntimeException e) {
2558            // A RuntimeException is thrown if we were unable to parse the parcel.
2559            // Create an empty parcel in this case.
2560            bundle = new Bundle();
2561        } finally {
2562            parcel.recycle();
2563        }
2564        return bundle;
2565    }
2566
2567    private static final String XML_ATTR_VERSION = "version";
2568    private static final String XML_ATTR_AUTHORITYID = "authority_id";
2569    private static final String XML_ATTR_SOURCE = "source";
2570    private static final String XML_ATTR_EXPEDITED = "expedited";
2571    private static final String XML_ATTR_REASON = "reason";
2572
2573    /**
2574     * Write all currently pending ops to the pending ops file.
2575     */
2576    private void writePendingOperationsLocked() {
2577        final int N = mPendingOperations.size();
2578        FileOutputStream fos = null;
2579        try {
2580            if (N == 0) {
2581                if (Log.isLoggable(TAG_FILE, Log.VERBOSE)){
2582                    Log.v(TAG, "Truncating " + mPendingFile.getBaseFile());
2583                }
2584                mPendingFile.truncate();
2585                return;
2586            }
2587            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2588                Log.v(TAG, "Writing new " + mPendingFile.getBaseFile());
2589            }
2590            fos = mPendingFile.startWrite();
2591            XmlSerializer out = new FastXmlSerializer();
2592            out.setOutput(fos, "utf-8");
2593
2594            for (int i = 0; i < N; i++) {
2595                PendingOperation pop = mPendingOperations.get(i);
2596                writePendingOperationLocked(pop, out);
2597             }
2598             out.endDocument();
2599             mPendingFile.finishWrite(fos);
2600        } catch (java.io.IOException e1) {
2601            Log.w(TAG, "Error writing pending operations", e1);
2602            if (fos != null) {
2603                mPendingFile.failWrite(fos);
2604            }
2605        }
2606    }
2607
2608    /** Write all currently pending ops to the pending ops file. */
2609     private void writePendingOperationLocked(PendingOperation pop, XmlSerializer out)
2610             throws IOException {
2611         // Pending operation.
2612         out.startTag(null, "op");
2613
2614         out.attribute(null, XML_ATTR_VERSION, Integer.toString(PENDING_OPERATION_VERSION));
2615         out.attribute(null, XML_ATTR_AUTHORITYID, Integer.toString(pop.authorityId));
2616         out.attribute(null, XML_ATTR_SOURCE, Integer.toString(pop.syncSource));
2617         out.attribute(null, XML_ATTR_EXPEDITED, Boolean.toString(pop.expedited));
2618         out.attribute(null, XML_ATTR_REASON, Integer.toString(pop.reason));
2619         extrasToXml(out, pop.extras);
2620
2621         out.endTag(null, "op");
2622     }
2623
2624    /**
2625     * Append the given operation to the pending ops file; if unable to,
2626     * write all pending ops.
2627     */
2628    private void appendPendingOperationLocked(PendingOperation op) {
2629        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2630            Log.v(TAG, "Appending to " + mPendingFile.getBaseFile());
2631        }
2632        FileOutputStream fos = null;
2633        try {
2634            fos = mPendingFile.openAppend();
2635        } catch (java.io.IOException e) {
2636            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2637                Log.v(TAG, "Failed append; writing full file");
2638            }
2639            writePendingOperationsLocked();
2640            return;
2641        }
2642
2643        try {
2644            XmlSerializer out = new FastXmlSerializer();
2645            out.setOutput(fos, "utf-8");
2646            writePendingOperationLocked(op, out);
2647            out.endDocument();
2648            mPendingFile.finishWrite(fos);
2649        } catch (java.io.IOException e1) {
2650            Log.w(TAG, "Error writing appending operation", e1);
2651            mPendingFile.failWrite(fos);
2652        } finally {
2653            try {
2654                fos.close();
2655            } catch (IOException e) {}
2656        }
2657    }
2658
2659    private void extrasToXml(XmlSerializer out, Bundle extras) throws java.io.IOException {
2660        for (String key : extras.keySet()) {
2661            out.startTag(null, "extra");
2662            out.attribute(null, "name", key);
2663            final Object value = extras.get(key);
2664            if (value instanceof Long) {
2665                out.attribute(null, "type", "long");
2666                out.attribute(null, "value1", value.toString());
2667            } else if (value instanceof Integer) {
2668                out.attribute(null, "type", "integer");
2669                out.attribute(null, "value1", value.toString());
2670            } else if (value instanceof Boolean) {
2671                out.attribute(null, "type", "boolean");
2672                out.attribute(null, "value1", value.toString());
2673            } else if (value instanceof Float) {
2674                out.attribute(null, "type", "float");
2675                out.attribute(null, "value1", value.toString());
2676            } else if (value instanceof Double) {
2677                out.attribute(null, "type", "double");
2678                out.attribute(null, "value1", value.toString());
2679            } else if (value instanceof String) {
2680                out.attribute(null, "type", "string");
2681                out.attribute(null, "value1", value.toString());
2682            } else if (value instanceof Account) {
2683                out.attribute(null, "type", "account");
2684                out.attribute(null, "value1", ((Account)value).name);
2685                out.attribute(null, "value2", ((Account)value).type);
2686            }
2687            out.endTag(null, "extra");
2688        }
2689    }
2690
2691    private void requestSync(AuthorityInfo authorityInfo, int reason, Bundle extras) {
2692        if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID
2693                && mSyncRequestListener != null) {
2694            mSyncRequestListener.onSyncRequest(authorityInfo.target, reason, extras);
2695        } else {
2696            SyncRequest.Builder req =
2697                    new SyncRequest.Builder()
2698                            .syncOnce()
2699                            .setExtras(extras);
2700            if (authorityInfo.target.target_provider) {
2701                req.setSyncAdapter(authorityInfo.target.account, authorityInfo.target.provider);
2702            } else {
2703                req.setSyncAdapter(authorityInfo.target.service);
2704            }
2705            ContentResolver.requestSync(req.build());
2706        }
2707    }
2708
2709    private void requestSync(Account account, int userId, int reason, String authority,
2710            Bundle extras) {
2711        // If this is happening in the system process, then call the syncrequest listener
2712        // to make a request back to the SyncManager directly.
2713        // If this is probably a test instance, then call back through the ContentResolver
2714        // which will know which userId to apply based on the Binder id.
2715        if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID
2716                && mSyncRequestListener != null) {
2717            mSyncRequestListener.onSyncRequest(
2718                new EndPoint(account, authority, userId),
2719                reason,
2720                extras);
2721        } else {
2722            ContentResolver.requestSync(account, authority, extras);
2723        }
2724    }
2725
2726    public static final int STATISTICS_FILE_END = 0;
2727    public static final int STATISTICS_FILE_ITEM_OLD = 100;
2728    public static final int STATISTICS_FILE_ITEM = 101;
2729
2730    /**
2731     * Read all sync statistics back in to the initial engine state.
2732     */
2733    private void readStatisticsLocked() {
2734        try {
2735            byte[] data = mStatisticsFile.readFully();
2736            Parcel in = Parcel.obtain();
2737            in.unmarshall(data, 0, data.length);
2738            in.setDataPosition(0);
2739            int token;
2740            int index = 0;
2741            while ((token=in.readInt()) != STATISTICS_FILE_END) {
2742                if (token == STATISTICS_FILE_ITEM
2743                        || token == STATISTICS_FILE_ITEM_OLD) {
2744                    int day = in.readInt();
2745                    if (token == STATISTICS_FILE_ITEM_OLD) {
2746                        day = day - 2009 + 14245;  // Magic!
2747                    }
2748                    DayStats ds = new DayStats(day);
2749                    ds.successCount = in.readInt();
2750                    ds.successTime = in.readLong();
2751                    ds.failureCount = in.readInt();
2752                    ds.failureTime = in.readLong();
2753                    if (index < mDayStats.length) {
2754                        mDayStats[index] = ds;
2755                        index++;
2756                    }
2757                } else {
2758                    // Ooops.
2759                    Log.w(TAG, "Unknown stats token: " + token);
2760                    break;
2761                }
2762            }
2763        } catch (java.io.IOException e) {
2764            Log.i(TAG, "No initial statistics");
2765        }
2766    }
2767
2768    /**
2769     * Write all sync statistics to the sync status file.
2770     */
2771    private void writeStatisticsLocked() {
2772        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
2773            Log.v(TAG, "Writing new " + mStatisticsFile.getBaseFile());
2774        }
2775
2776        // The file is being written, so we don't need to have a scheduled
2777        // write until the next change.
2778        removeMessages(MSG_WRITE_STATISTICS);
2779
2780        FileOutputStream fos = null;
2781        try {
2782            fos = mStatisticsFile.startWrite();
2783            Parcel out = Parcel.obtain();
2784            final int N = mDayStats.length;
2785            for (int i=0; i<N; i++) {
2786                DayStats ds = mDayStats[i];
2787                if (ds == null) {
2788                    break;
2789                }
2790                out.writeInt(STATISTICS_FILE_ITEM);
2791                out.writeInt(ds.day);
2792                out.writeInt(ds.successCount);
2793                out.writeLong(ds.successTime);
2794                out.writeInt(ds.failureCount);
2795                out.writeLong(ds.failureTime);
2796            }
2797            out.writeInt(STATISTICS_FILE_END);
2798            fos.write(out.marshall());
2799            out.recycle();
2800
2801            mStatisticsFile.finishWrite(fos);
2802        } catch (java.io.IOException e1) {
2803            Log.w(TAG, "Error writing stats", e1);
2804            if (fos != null) {
2805                mStatisticsFile.failWrite(fos);
2806            }
2807        }
2808    }
2809
2810    /**
2811     * Dump state of PendingOperations.
2812     */
2813    public void dumpPendingOperations(StringBuilder sb) {
2814        sb.append("Pending Ops: ").append(mPendingOperations.size()).append(" operation(s)\n");
2815        for (PendingOperation pop : mPendingOperations) {
2816            sb.append("(info: " + pop.target.toString())
2817                .append(", extras: " + pop.extras)
2818                .append(")\n");
2819        }
2820    }
2821}
2822