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