SyncStorageEngine.java revision a42880749b368e60caee77dd682d434e48ca96bd
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 SyncInfo mCurrentSync;
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 in the pending list, or actively being processed.
694     */
695    public boolean isSyncActive(Account account, String authority) {
696        synchronized (mAuthorities) {
697            int i = mPendingOperations.size();
698            while (i > 0) {
699                i--;
700                // TODO(fredq): this probably shouldn't be considering
701                // pending operations.
702                PendingOperation op = mPendingOperations.get(i);
703                if (op.account.equals(account) && op.authority.equals(authority)) {
704                    return true;
705                }
706            }
707
708            if (mCurrentSync != null) {
709                AuthorityInfo ainfo = getAuthority(mCurrentSync.authorityId);
710                if (ainfo != null && ainfo.account.equals(account)
711                        && ainfo.authority.equals(authority)) {
712                    return true;
713                }
714            }
715        }
716
717        return false;
718    }
719
720    public PendingOperation insertIntoPending(PendingOperation op) {
721        synchronized (mAuthorities) {
722            if (Log.isLoggable(TAG, Log.VERBOSE)) {
723                Log.v(TAG, "insertIntoPending: account=" + op.account
724                    + " auth=" + op.authority
725                    + " src=" + op.syncSource
726                    + " extras=" + op.extras);
727            }
728
729            AuthorityInfo authority = getOrCreateAuthorityLocked(op.account,
730                    op.authority,
731                    -1 /* desired identifier */,
732                    true /* write accounts to storage */);
733            if (authority == null) {
734                return null;
735            }
736
737            op = new PendingOperation(op);
738            op.authorityId = authority.ident;
739            mPendingOperations.add(op);
740            appendPendingOperationLocked(op);
741
742            SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
743            status.pending = true;
744        }
745
746        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
747        return op;
748    }
749
750    public boolean deleteFromPending(PendingOperation op) {
751        boolean res = false;
752        synchronized (mAuthorities) {
753            if (Log.isLoggable(TAG, Log.VERBOSE)) {
754                Log.v(TAG, "deleteFromPending: account=" + op.account
755                    + " auth=" + op.authority
756                    + " src=" + op.syncSource
757                    + " extras=" + op.extras);
758            }
759            if (mPendingOperations.remove(op)) {
760                if (mPendingOperations.size() == 0
761                        || mNumPendingFinished >= PENDING_FINISH_TO_WRITE) {
762                    writePendingOperationsLocked();
763                    mNumPendingFinished = 0;
764                } else {
765                    mNumPendingFinished++;
766                }
767
768                AuthorityInfo authority = getAuthorityLocked(op.account, op.authority,
769                        "deleteFromPending");
770                if (authority != null) {
771                    if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "removing - " + authority);
772                    final int N = mPendingOperations.size();
773                    boolean morePending = false;
774                    for (int i=0; i<N; i++) {
775                        PendingOperation cur = mPendingOperations.get(i);
776                        if (cur.account.equals(op.account)
777                                && cur.authority.equals(op.authority)) {
778                            morePending = true;
779                            break;
780                        }
781                    }
782
783                    if (!morePending) {
784                        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "no more pending!");
785                        SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
786                        status.pending = false;
787                    }
788                }
789
790                res = true;
791            }
792        }
793
794        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
795        return res;
796    }
797
798    public int clearPending() {
799        int num;
800        synchronized (mAuthorities) {
801            if (Log.isLoggable(TAG, Log.VERBOSE)) {
802                Log.v(TAG, "clearPending");
803            }
804            num = mPendingOperations.size();
805            mPendingOperations.clear();
806            final int N = mSyncStatus.size();
807            for (int i=0; i<N; i++) {
808                mSyncStatus.valueAt(i).pending = false;
809            }
810            writePendingOperationsLocked();
811        }
812        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
813        return num;
814    }
815
816    /**
817     * Return a copy of the current array of pending operations.  The
818     * PendingOperation objects are the real objects stored inside, so that
819     * they can be used with deleteFromPending().
820     */
821    public ArrayList<PendingOperation> getPendingOperations() {
822        synchronized (mAuthorities) {
823            return new ArrayList<PendingOperation>(mPendingOperations);
824        }
825    }
826
827    /**
828     * Return the number of currently pending operations.
829     */
830    public int getPendingOperationCount() {
831        synchronized (mAuthorities) {
832            return mPendingOperations.size();
833        }
834    }
835
836    /**
837     * Called when the set of account has changed, given the new array of
838     * active accounts.
839     */
840    public void doDatabaseCleanup(Account[] accounts) {
841        synchronized (mAuthorities) {
842            if (Log.isLoggable(TAG, Log.VERBOSE)) Log.w(TAG, "Updating for new accounts...");
843            SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>();
844            Iterator<AccountInfo> accIt = mAccounts.values().iterator();
845            while (accIt.hasNext()) {
846                AccountInfo acc = accIt.next();
847                if (!ArrayUtils.contains(accounts, acc.account)) {
848                    // This account no longer exists...
849                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
850                        Log.w(TAG, "Account removed: " + acc.account);
851                    }
852                    for (AuthorityInfo auth : acc.authorities.values()) {
853                        removing.put(auth.ident, auth);
854                    }
855                    accIt.remove();
856                }
857            }
858
859            // Clean out all data structures.
860            int i = removing.size();
861            if (i > 0) {
862                while (i > 0) {
863                    i--;
864                    int ident = removing.keyAt(i);
865                    mAuthorities.remove(ident);
866                    int j = mSyncStatus.size();
867                    while (j > 0) {
868                        j--;
869                        if (mSyncStatus.keyAt(j) == ident) {
870                            mSyncStatus.remove(mSyncStatus.keyAt(j));
871                        }
872                    }
873                    j = mSyncHistory.size();
874                    while (j > 0) {
875                        j--;
876                        if (mSyncHistory.get(j).authorityId == ident) {
877                            mSyncHistory.remove(j);
878                        }
879                    }
880                }
881                writeAccountInfoLocked();
882                writeStatusLocked();
883                writePendingOperationsLocked();
884                writeStatisticsLocked();
885            }
886        }
887    }
888
889    /**
890     * Called when the currently active sync is changing (there can only be
891     * one at a time).  Either supply a valid ActiveSyncContext with information
892     * about the sync, or null to stop the currently active sync.
893     */
894    public void setActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
895        synchronized (mAuthorities) {
896            if (activeSyncContext != null) {
897                if (Log.isLoggable(TAG, Log.VERBOSE)) {
898                    Log.v(TAG, "setActiveSync: account="
899                        + activeSyncContext.mSyncOperation.account
900                        + " auth=" + activeSyncContext.mSyncOperation.authority
901                        + " src=" + activeSyncContext.mSyncOperation.syncSource
902                        + " extras=" + activeSyncContext.mSyncOperation.extras);
903                }
904                if (mCurrentSync != null) {
905                    Log.w(TAG, "setActiveSync called with existing active sync!");
906                }
907                AuthorityInfo authority = getAuthorityLocked(
908                        activeSyncContext.mSyncOperation.account,
909                        activeSyncContext.mSyncOperation.authority,
910                        "setActiveSync");
911                if (authority == null) {
912                    return;
913                }
914                mCurrentSync = new SyncInfo(authority.ident,
915                        authority.account, authority.authority,
916                        activeSyncContext.mStartTime);
917            } else {
918                if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "setActiveSync: null");
919                mCurrentSync = null;
920            }
921        }
922
923        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE);
924    }
925
926    /**
927     * To allow others to send active change reports, to poke clients.
928     */
929    public void reportActiveChange() {
930        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE);
931    }
932
933    /**
934     * Note that sync has started for the given account and authority.
935     */
936    public long insertStartSyncEvent(Account accountName, String authorityName,
937            long now, int source) {
938        long id;
939        synchronized (mAuthorities) {
940            if (Log.isLoggable(TAG, Log.VERBOSE)) {
941                Log.v(TAG, "insertStartSyncEvent: account=" + accountName
942                    + " auth=" + authorityName + " source=" + source);
943            }
944            AuthorityInfo authority = getAuthorityLocked(accountName, authorityName,
945                    "insertStartSyncEvent");
946            if (authority == null) {
947                return -1;
948            }
949            SyncHistoryItem item = new SyncHistoryItem();
950            item.authorityId = authority.ident;
951            item.historyId = mNextHistoryId++;
952            if (mNextHistoryId < 0) mNextHistoryId = 0;
953            item.eventTime = now;
954            item.source = source;
955            item.event = EVENT_START;
956            mSyncHistory.add(0, item);
957            while (mSyncHistory.size() > MAX_HISTORY) {
958                mSyncHistory.remove(mSyncHistory.size()-1);
959            }
960            id = item.historyId;
961            if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "returning historyId " + id);
962        }
963
964        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
965        return id;
966    }
967
968    public static boolean equals(Bundle b1, Bundle b2) {
969        if (b1.size() != b2.size()) {
970            return false;
971        }
972        if (b1.isEmpty()) {
973            return true;
974        }
975        for (String key : b1.keySet()) {
976            if (!b2.containsKey(key)) {
977                return false;
978            }
979            if (!b1.get(key).equals(b2.get(key))) {
980                return false;
981            }
982        }
983        return true;
984    }
985
986    public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage,
987            long downstreamActivity, long upstreamActivity) {
988        synchronized (mAuthorities) {
989            if (Log.isLoggable(TAG, Log.VERBOSE)) {
990                Log.v(TAG, "stopSyncEvent: historyId=" + historyId);
991            }
992            SyncHistoryItem item = null;
993            int i = mSyncHistory.size();
994            while (i > 0) {
995                i--;
996                item = mSyncHistory.get(i);
997                if (item.historyId == historyId) {
998                    break;
999                }
1000                item = null;
1001            }
1002
1003            if (item == null) {
1004                Log.w(TAG, "stopSyncEvent: no history for id " + historyId);
1005                return;
1006            }
1007
1008            item.elapsedTime = elapsedTime;
1009            item.event = EVENT_STOP;
1010            item.mesg = resultMessage;
1011            item.downstreamActivity = downstreamActivity;
1012            item.upstreamActivity = upstreamActivity;
1013
1014            SyncStatusInfo status = getOrCreateSyncStatusLocked(item.authorityId);
1015
1016            status.numSyncs++;
1017            status.totalElapsedTime += elapsedTime;
1018            switch (item.source) {
1019                case SOURCE_LOCAL:
1020                    status.numSourceLocal++;
1021                    break;
1022                case SOURCE_POLL:
1023                    status.numSourcePoll++;
1024                    break;
1025                case SOURCE_USER:
1026                    status.numSourceUser++;
1027                    break;
1028                case SOURCE_SERVER:
1029                    status.numSourceServer++;
1030                    break;
1031                case SOURCE_PERIODIC:
1032                    status.numSourcePeriodic++;
1033                    break;
1034            }
1035
1036            boolean writeStatisticsNow = false;
1037            int day = getCurrentDayLocked();
1038            if (mDayStats[0] == null) {
1039                mDayStats[0] = new DayStats(day);
1040            } else if (day != mDayStats[0].day) {
1041                System.arraycopy(mDayStats, 0, mDayStats, 1, mDayStats.length-1);
1042                mDayStats[0] = new DayStats(day);
1043                writeStatisticsNow = true;
1044            } else if (mDayStats[0] == null) {
1045            }
1046            final DayStats ds = mDayStats[0];
1047
1048            final long lastSyncTime = (item.eventTime + elapsedTime);
1049            boolean writeStatusNow = false;
1050            if (MESG_SUCCESS.equals(resultMessage)) {
1051                // - if successful, update the successful columns
1052                if (status.lastSuccessTime == 0 || status.lastFailureTime != 0) {
1053                    writeStatusNow = true;
1054                }
1055                status.lastSuccessTime = lastSyncTime;
1056                status.lastSuccessSource = item.source;
1057                status.lastFailureTime = 0;
1058                status.lastFailureSource = -1;
1059                status.lastFailureMesg = null;
1060                status.initialFailureTime = 0;
1061                ds.successCount++;
1062                ds.successTime += elapsedTime;
1063            } else if (!MESG_CANCELED.equals(resultMessage)) {
1064                if (status.lastFailureTime == 0) {
1065                    writeStatusNow = true;
1066                }
1067                status.lastFailureTime = lastSyncTime;
1068                status.lastFailureSource = item.source;
1069                status.lastFailureMesg = resultMessage;
1070                if (status.initialFailureTime == 0) {
1071                    status.initialFailureTime = lastSyncTime;
1072                }
1073                ds.failureCount++;
1074                ds.failureTime += elapsedTime;
1075            }
1076
1077            if (writeStatusNow) {
1078                writeStatusLocked();
1079            } else if (!hasMessages(MSG_WRITE_STATUS)) {
1080                sendMessageDelayed(obtainMessage(MSG_WRITE_STATUS),
1081                        WRITE_STATUS_DELAY);
1082            }
1083            if (writeStatisticsNow) {
1084                writeStatisticsLocked();
1085            } else if (!hasMessages(MSG_WRITE_STATISTICS)) {
1086                sendMessageDelayed(obtainMessage(MSG_WRITE_STATISTICS),
1087                        WRITE_STATISTICS_DELAY);
1088            }
1089        }
1090
1091        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
1092    }
1093
1094    /**
1095     * Return the currently active sync information, or null if there is no
1096     * active sync.  Note that the returned object is the real, live active
1097     * sync object, so be careful what you do with it.
1098     */
1099    public SyncInfo getCurrentSync() {
1100        synchronized (mAuthorities) {
1101            return mCurrentSync;
1102        }
1103    }
1104
1105    /**
1106     * Return an array of the current sync status for all authorities.  Note
1107     * that the objects inside the array are the real, live status objects,
1108     * so be careful what you do with them.
1109     */
1110    public ArrayList<SyncStatusInfo> getSyncStatus() {
1111        synchronized (mAuthorities) {
1112            final int N = mSyncStatus.size();
1113            ArrayList<SyncStatusInfo> ops = new ArrayList<SyncStatusInfo>(N);
1114            for (int i=0; i<N; i++) {
1115                ops.add(mSyncStatus.valueAt(i));
1116            }
1117            return ops;
1118        }
1119    }
1120
1121    /**
1122     * Return an array of the current authorities. Note
1123     * that the objects inside the array are the real, live objects,
1124     * so be careful what you do with them.
1125     */
1126    public ArrayList<AuthorityInfo> getAuthorities() {
1127        synchronized (mAuthorities) {
1128            final int N = mAuthorities.size();
1129            ArrayList<AuthorityInfo> infos = new ArrayList<AuthorityInfo>(N);
1130            for (int i=0; i<N; i++) {
1131                infos.add(mAuthorities.valueAt(i));
1132            }
1133            return infos;
1134        }
1135    }
1136
1137    /**
1138     * Returns the status that matches the authority and account.
1139     *
1140     * @param account the account we want to check
1141     * @param authority the authority whose row should be selected
1142     * @return the SyncStatusInfo for the authority
1143     */
1144    public SyncStatusInfo getStatusByAccountAndAuthority(Account account, String authority) {
1145        if (account == null || authority == null) {
1146          throw new IllegalArgumentException();
1147        }
1148        synchronized (mAuthorities) {
1149            final int N = mSyncStatus.size();
1150            for (int i=0; i<N; i++) {
1151                SyncStatusInfo cur = mSyncStatus.valueAt(i);
1152                AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
1153
1154                if (ainfo != null && ainfo.authority.equals(authority) &&
1155                    account.equals(ainfo.account)) {
1156                  return cur;
1157                }
1158            }
1159            return null;
1160        }
1161    }
1162
1163    /**
1164     * Return true if the pending status is true of any matching authorities.
1165     */
1166    public boolean isSyncPending(Account account, String authority) {
1167        synchronized (mAuthorities) {
1168            final int N = mSyncStatus.size();
1169            for (int i=0; i<N; i++) {
1170                SyncStatusInfo cur = mSyncStatus.valueAt(i);
1171                AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
1172                if (ainfo == null) {
1173                    continue;
1174                }
1175                if (account != null && !ainfo.account.equals(account)) {
1176                    continue;
1177                }
1178                if (ainfo.authority.equals(authority) && cur.pending) {
1179                    return true;
1180                }
1181            }
1182            return false;
1183        }
1184    }
1185
1186    /**
1187     * Return an array of the current sync status for all authorities.  Note
1188     * that the objects inside the array are the real, live status objects,
1189     * so be careful what you do with them.
1190     */
1191    public ArrayList<SyncHistoryItem> getSyncHistory() {
1192        synchronized (mAuthorities) {
1193            final int N = mSyncHistory.size();
1194            ArrayList<SyncHistoryItem> items = new ArrayList<SyncHistoryItem>(N);
1195            for (int i=0; i<N; i++) {
1196                items.add(mSyncHistory.get(i));
1197            }
1198            return items;
1199        }
1200    }
1201
1202    /**
1203     * Return an array of the current per-day statistics.  Note
1204     * that the objects inside the array are the real, live status objects,
1205     * so be careful what you do with them.
1206     */
1207    public DayStats[] getDayStatistics() {
1208        synchronized (mAuthorities) {
1209            DayStats[] ds = new DayStats[mDayStats.length];
1210            System.arraycopy(mDayStats, 0, ds, 0, ds.length);
1211            return ds;
1212        }
1213    }
1214
1215    /**
1216     * If sync is failing for any of the provider/accounts then determine the time at which it
1217     * started failing and return the earliest time over all the provider/accounts. If none are
1218     * failing then return 0.
1219     */
1220    public long getInitialSyncFailureTime() {
1221        synchronized (mAuthorities) {
1222            if (!mMasterSyncAutomatically) {
1223                return 0;
1224            }
1225
1226            long oldest = 0;
1227            int i = mSyncStatus.size();
1228            while (i > 0) {
1229                i--;
1230                SyncStatusInfo stats = mSyncStatus.valueAt(i);
1231                AuthorityInfo authority = mAuthorities.get(stats.authorityId);
1232                if (authority != null && authority.enabled) {
1233                    if (oldest == 0 || stats.initialFailureTime < oldest) {
1234                        oldest = stats.initialFailureTime;
1235                    }
1236                }
1237            }
1238
1239            return oldest;
1240        }
1241    }
1242
1243    private int getCurrentDayLocked() {
1244        mCal.setTimeInMillis(System.currentTimeMillis());
1245        final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR);
1246        if (mYear != mCal.get(Calendar.YEAR)) {
1247            mYear = mCal.get(Calendar.YEAR);
1248            mCal.clear();
1249            mCal.set(Calendar.YEAR, mYear);
1250            mYearInDays = (int)(mCal.getTimeInMillis()/86400000);
1251        }
1252        return dayOfYear + mYearInDays;
1253    }
1254
1255    /**
1256     * Retrieve an authority, returning null if one does not exist.
1257     *
1258     * @param accountName The name of the account for the authority.
1259     * @param authorityName The name of the authority itself.
1260     * @param tag If non-null, this will be used in a log message if the
1261     * requested authority does not exist.
1262     */
1263    private AuthorityInfo getAuthorityLocked(Account accountName, String authorityName,
1264            String tag) {
1265        AccountInfo account = mAccounts.get(accountName);
1266        if (account == null) {
1267            if (tag != null) {
1268                if (Log.isLoggable(TAG, Log.VERBOSE)) {
1269                    Log.v(TAG, tag + ": unknown account " + accountName);
1270                }
1271            }
1272            return null;
1273        }
1274        AuthorityInfo authority = account.authorities.get(authorityName);
1275        if (authority == null) {
1276            if (tag != null) {
1277                if (Log.isLoggable(TAG, Log.VERBOSE)) {
1278                    Log.v(TAG, tag + ": unknown authority " + authorityName);
1279                }
1280            }
1281            return null;
1282        }
1283
1284        return authority;
1285    }
1286
1287    private AuthorityInfo getOrCreateAuthorityLocked(Account accountName,
1288            String authorityName, int ident, boolean doWrite) {
1289        AccountInfo account = mAccounts.get(accountName);
1290        if (account == null) {
1291            account = new AccountInfo(accountName);
1292            mAccounts.put(accountName, account);
1293        }
1294        AuthorityInfo authority = account.authorities.get(authorityName);
1295        if (authority == null) {
1296            if (ident < 0) {
1297                ident = mNextAuthorityId;
1298                mNextAuthorityId++;
1299                doWrite = true;
1300            }
1301            if (Log.isLoggable(TAG, Log.VERBOSE)) {
1302                Log.v(TAG, "created a new AuthorityInfo for " + accountName
1303                    + ", provider " + authorityName);
1304            }
1305            authority = new AuthorityInfo(accountName, authorityName, ident);
1306            account.authorities.put(authorityName, authority);
1307            mAuthorities.put(ident, authority);
1308            if (doWrite) {
1309                writeAccountInfoLocked();
1310            }
1311        }
1312
1313        return authority;
1314    }
1315
1316    private void removeAuthorityLocked(Account account, String authorityName, boolean doWrite) {
1317        AccountInfo accountInfo = mAccounts.get(account);
1318        if (accountInfo != null) {
1319            final AuthorityInfo authorityInfo = accountInfo.authorities.remove(authorityName);
1320            if (authorityInfo != null) {
1321                mAuthorities.remove(authorityInfo.ident);
1322                if (doWrite) {
1323                    writeAccountInfoLocked();
1324                }
1325            }
1326        }
1327    }
1328
1329    public SyncStatusInfo getOrCreateSyncStatus(AuthorityInfo authority) {
1330        synchronized (mAuthorities) {
1331            return getOrCreateSyncStatusLocked(authority.ident);
1332        }
1333    }
1334
1335    private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) {
1336        SyncStatusInfo status = mSyncStatus.get(authorityId);
1337        if (status == null) {
1338            status = new SyncStatusInfo(authorityId);
1339            mSyncStatus.put(authorityId, status);
1340        }
1341        return status;
1342    }
1343
1344    public void writeAllState() {
1345        synchronized (mAuthorities) {
1346            // Account info is always written so no need to do it here.
1347
1348            if (mNumPendingFinished > 0) {
1349                // Only write these if they are out of date.
1350                writePendingOperationsLocked();
1351            }
1352
1353            // Just always write these...  they are likely out of date.
1354            writeStatusLocked();
1355            writeStatisticsLocked();
1356        }
1357    }
1358
1359    /**
1360     * public for testing
1361     */
1362    public void clearAndReadState() {
1363        synchronized (mAuthorities) {
1364            mAuthorities.clear();
1365            mAccounts.clear();
1366            mPendingOperations.clear();
1367            mSyncStatus.clear();
1368            mSyncHistory.clear();
1369
1370            readAccountInfoLocked();
1371            readStatusLocked();
1372            readPendingOperationsLocked();
1373            readStatisticsLocked();
1374            readAndDeleteLegacyAccountInfoLocked();
1375            writeAccountInfoLocked();
1376            writeStatusLocked();
1377            writePendingOperationsLocked();
1378            writeStatisticsLocked();
1379        }
1380    }
1381
1382    /**
1383     * Read all account information back in to the initial engine state.
1384     */
1385    private void readAccountInfoLocked() {
1386        int highestAuthorityId = -1;
1387        FileInputStream fis = null;
1388        try {
1389            fis = mAccountInfoFile.openRead();
1390            if (DEBUG_FILE) Log.v(TAG, "Reading " + mAccountInfoFile.getBaseFile());
1391            XmlPullParser parser = Xml.newPullParser();
1392            parser.setInput(fis, null);
1393            int eventType = parser.getEventType();
1394            while (eventType != XmlPullParser.START_TAG) {
1395                eventType = parser.next();
1396            }
1397            String tagName = parser.getName();
1398            if ("accounts".equals(tagName)) {
1399                String listen = parser.getAttributeValue(
1400                        null, "listen-for-tickles");
1401                String versionString = parser.getAttributeValue(null, "version");
1402                int version;
1403                try {
1404                    version = (versionString == null) ? 0 : Integer.parseInt(versionString);
1405                } catch (NumberFormatException e) {
1406                    version = 0;
1407                }
1408                String nextIdString = parser.getAttributeValue(null, "nextAuthorityId");
1409                try {
1410                    int id = (nextIdString == null) ? 0 : Integer.parseInt(nextIdString);
1411                    mNextAuthorityId = Math.max(mNextAuthorityId, id);
1412                } catch (NumberFormatException e) {
1413                    // don't care
1414                }
1415                mMasterSyncAutomatically = listen == null || Boolean.parseBoolean(listen);
1416                eventType = parser.next();
1417                AuthorityInfo authority = null;
1418                Pair<Bundle, Long> periodicSync = null;
1419                do {
1420                    if (eventType == XmlPullParser.START_TAG) {
1421                        tagName = parser.getName();
1422                        if (parser.getDepth() == 2) {
1423                            if ("authority".equals(tagName)) {
1424                                authority = parseAuthority(parser, version);
1425                                periodicSync = null;
1426                                if (authority.ident > highestAuthorityId) {
1427                                    highestAuthorityId = authority.ident;
1428                                }
1429                            }
1430                        } else if (parser.getDepth() == 3) {
1431                            if ("periodicSync".equals(tagName) && authority != null) {
1432                                periodicSync = parsePeriodicSync(parser, authority);
1433                            }
1434                        } else if (parser.getDepth() == 4 && periodicSync != null) {
1435                            if ("extra".equals(tagName)) {
1436                                parseExtra(parser, periodicSync);
1437                            }
1438                        }
1439                    }
1440                    eventType = parser.next();
1441                } while (eventType != XmlPullParser.END_DOCUMENT);
1442            }
1443        } catch (XmlPullParserException e) {
1444            Log.w(TAG, "Error reading accounts", e);
1445            return;
1446        } catch (java.io.IOException e) {
1447            if (fis == null) Log.i(TAG, "No initial accounts");
1448            else Log.w(TAG, "Error reading accounts", e);
1449            return;
1450        } finally {
1451            mNextAuthorityId = Math.max(highestAuthorityId + 1, mNextAuthorityId);
1452            if (fis != null) {
1453                try {
1454                    fis.close();
1455                } catch (java.io.IOException e1) {
1456                }
1457            }
1458        }
1459
1460        maybeMigrateSettingsForRenamedAuthorities();
1461    }
1462
1463    /**
1464     * some authority names have changed. copy over their settings and delete the old ones
1465     * @return true if a change was made
1466     */
1467    private boolean maybeMigrateSettingsForRenamedAuthorities() {
1468        boolean writeNeeded = false;
1469
1470        ArrayList<AuthorityInfo> authoritiesToRemove = new ArrayList<AuthorityInfo>();
1471        final int N = mAuthorities.size();
1472        for (int i=0; i<N; i++) {
1473            AuthorityInfo authority = mAuthorities.valueAt(i);
1474            // skip this authority if it isn't one of the renamed ones
1475            final String newAuthorityName = sAuthorityRenames.get(authority.authority);
1476            if (newAuthorityName == null) {
1477                continue;
1478            }
1479
1480            // remember this authority so we can remove it later. we can't remove it
1481            // now without messing up this loop iteration
1482            authoritiesToRemove.add(authority);
1483
1484            // this authority isn't enabled, no need to copy it to the new authority name since
1485            // the default is "disabled"
1486            if (!authority.enabled) {
1487                continue;
1488            }
1489
1490            // if we already have a record of this new authority then don't copy over the settings
1491            if (getAuthorityLocked(authority.account, newAuthorityName, "cleanup") != null) {
1492                continue;
1493            }
1494
1495            AuthorityInfo newAuthority = getOrCreateAuthorityLocked(authority.account,
1496                    newAuthorityName, -1 /* ident */, false /* doWrite */);
1497            newAuthority.enabled = true;
1498            writeNeeded = true;
1499        }
1500
1501        for (AuthorityInfo authorityInfo : authoritiesToRemove) {
1502            removeAuthorityLocked(authorityInfo.account, authorityInfo.authority,
1503                    false /* doWrite */);
1504            writeNeeded = true;
1505        }
1506
1507        return writeNeeded;
1508    }
1509
1510    private AuthorityInfo parseAuthority(XmlPullParser parser, int version) {
1511        AuthorityInfo authority = null;
1512        int id = -1;
1513        try {
1514            id = Integer.parseInt(parser.getAttributeValue(
1515                    null, "id"));
1516        } catch (NumberFormatException e) {
1517            Log.e(TAG, "error parsing the id of the authority", e);
1518        } catch (NullPointerException e) {
1519            Log.e(TAG, "the id of the authority is null", e);
1520        }
1521        if (id >= 0) {
1522            String authorityName = parser.getAttributeValue(null, "authority");
1523            String enabled = parser.getAttributeValue(null, "enabled");
1524            String syncable = parser.getAttributeValue(null, "syncable");
1525            String accountName = parser.getAttributeValue(null, "account");
1526            String accountType = parser.getAttributeValue(null, "type");
1527            if (accountType == null) {
1528                accountType = "com.google";
1529                syncable = "unknown";
1530            }
1531            authority = mAuthorities.get(id);
1532            if (DEBUG_FILE) Log.v(TAG, "Adding authority: account="
1533                    + accountName + " auth=" + authorityName
1534                    + " enabled=" + enabled
1535                    + " syncable=" + syncable);
1536            if (authority == null) {
1537                if (DEBUG_FILE) Log.v(TAG, "Creating entry");
1538                authority = getOrCreateAuthorityLocked(
1539                        new Account(accountName, accountType), authorityName, id, false);
1540                // If the version is 0 then we are upgrading from a file format that did not
1541                // know about periodic syncs. In that case don't clear the list since we
1542                // want the default, which is a daily periodioc sync.
1543                // Otherwise clear out this default list since we will populate it later with
1544                // the periodic sync descriptions that are read from the configuration file.
1545                if (version > 0) {
1546                    authority.periodicSyncs.clear();
1547                }
1548            }
1549            if (authority != null) {
1550                authority.enabled = enabled == null || Boolean.parseBoolean(enabled);
1551                if ("unknown".equals(syncable)) {
1552                    authority.syncable = -1;
1553                } else {
1554                    authority.syncable =
1555                            (syncable == null || Boolean.parseBoolean(syncable)) ? 1 : 0;
1556                }
1557            } else {
1558                Log.w(TAG, "Failure adding authority: account="
1559                        + accountName + " auth=" + authorityName
1560                        + " enabled=" + enabled
1561                        + " syncable=" + syncable);
1562            }
1563        }
1564
1565        return authority;
1566    }
1567
1568    private Pair<Bundle, Long> parsePeriodicSync(XmlPullParser parser, AuthorityInfo authority) {
1569        Bundle extras = new Bundle();
1570        String periodValue = parser.getAttributeValue(null, "period");
1571        final long period;
1572        try {
1573            period = Long.parseLong(periodValue);
1574        } catch (NumberFormatException e) {
1575            Log.e(TAG, "error parsing the period of a periodic sync", e);
1576            return null;
1577        } catch (NullPointerException e) {
1578            Log.e(TAG, "the period of a periodic sync is null", e);
1579            return null;
1580        }
1581        final Pair<Bundle, Long> periodicSync = Pair.create(extras, period);
1582        authority.periodicSyncs.add(periodicSync);
1583
1584        return periodicSync;
1585    }
1586
1587    private void parseExtra(XmlPullParser parser, Pair<Bundle, Long> periodicSync) {
1588        final Bundle extras = periodicSync.first;
1589        String name = parser.getAttributeValue(null, "name");
1590        String type = parser.getAttributeValue(null, "type");
1591        String value1 = parser.getAttributeValue(null, "value1");
1592        String value2 = parser.getAttributeValue(null, "value2");
1593
1594        try {
1595            if ("long".equals(type)) {
1596                extras.putLong(name, Long.parseLong(value1));
1597            } else if ("integer".equals(type)) {
1598                extras.putInt(name, Integer.parseInt(value1));
1599            } else if ("double".equals(type)) {
1600                extras.putDouble(name, Double.parseDouble(value1));
1601            } else if ("float".equals(type)) {
1602                extras.putFloat(name, Float.parseFloat(value1));
1603            } else if ("boolean".equals(type)) {
1604                extras.putBoolean(name, Boolean.parseBoolean(value1));
1605            } else if ("string".equals(type)) {
1606                extras.putString(name, value1);
1607            } else if ("account".equals(type)) {
1608                extras.putParcelable(name, new Account(value1, value2));
1609            }
1610        } catch (NumberFormatException e) {
1611            Log.e(TAG, "error parsing bundle value", e);
1612        } catch (NullPointerException e) {
1613            Log.e(TAG, "error parsing bundle value", e);
1614        }
1615    }
1616
1617    /**
1618     * Write all account information to the account file.
1619     */
1620    private void writeAccountInfoLocked() {
1621        if (DEBUG_FILE) Log.v(TAG, "Writing new " + mAccountInfoFile.getBaseFile());
1622        FileOutputStream fos = null;
1623
1624        try {
1625            fos = mAccountInfoFile.startWrite();
1626            XmlSerializer out = new FastXmlSerializer();
1627            out.setOutput(fos, "utf-8");
1628            out.startDocument(null, true);
1629            out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
1630
1631            out.startTag(null, "accounts");
1632            out.attribute(null, "version", Integer.toString(ACCOUNTS_VERSION));
1633            out.attribute(null, "nextAuthorityId", Integer.toString(mNextAuthorityId));
1634            if (!mMasterSyncAutomatically) {
1635                out.attribute(null, "listen-for-tickles", "false");
1636            }
1637
1638            final int N = mAuthorities.size();
1639            for (int i=0; i<N; i++) {
1640                AuthorityInfo authority = mAuthorities.valueAt(i);
1641                out.startTag(null, "authority");
1642                out.attribute(null, "id", Integer.toString(authority.ident));
1643                out.attribute(null, "account", authority.account.name);
1644                out.attribute(null, "type", authority.account.type);
1645                out.attribute(null, "authority", authority.authority);
1646                out.attribute(null, "enabled", Boolean.toString(authority.enabled));
1647                if (authority.syncable < 0) {
1648                    out.attribute(null, "syncable", "unknown");
1649                } else {
1650                    out.attribute(null, "syncable", Boolean.toString(authority.syncable != 0));
1651                }
1652                for (Pair<Bundle, Long> periodicSync : authority.periodicSyncs) {
1653                    out.startTag(null, "periodicSync");
1654                    out.attribute(null, "period", Long.toString(periodicSync.second));
1655                    final Bundle extras = periodicSync.first;
1656                    for (String key : extras.keySet()) {
1657                        out.startTag(null, "extra");
1658                        out.attribute(null, "name", key);
1659                        final Object value = extras.get(key);
1660                        if (value instanceof Long) {
1661                            out.attribute(null, "type", "long");
1662                            out.attribute(null, "value1", value.toString());
1663                        } else if (value instanceof Integer) {
1664                            out.attribute(null, "type", "integer");
1665                            out.attribute(null, "value1", value.toString());
1666                        } else if (value instanceof Boolean) {
1667                            out.attribute(null, "type", "boolean");
1668                            out.attribute(null, "value1", value.toString());
1669                        } else if (value instanceof Float) {
1670                            out.attribute(null, "type", "float");
1671                            out.attribute(null, "value1", value.toString());
1672                        } else if (value instanceof Double) {
1673                            out.attribute(null, "type", "double");
1674                            out.attribute(null, "value1", value.toString());
1675                        } else if (value instanceof String) {
1676                            out.attribute(null, "type", "string");
1677                            out.attribute(null, "value1", value.toString());
1678                        } else if (value instanceof Account) {
1679                            out.attribute(null, "type", "account");
1680                            out.attribute(null, "value1", ((Account)value).name);
1681                            out.attribute(null, "value2", ((Account)value).type);
1682                        }
1683                        out.endTag(null, "extra");
1684                    }
1685                    out.endTag(null, "periodicSync");
1686                }
1687                out.endTag(null, "authority");
1688            }
1689
1690            out.endTag(null, "accounts");
1691
1692            out.endDocument();
1693
1694            mAccountInfoFile.finishWrite(fos);
1695        } catch (java.io.IOException e1) {
1696            Log.w(TAG, "Error writing accounts", e1);
1697            if (fos != null) {
1698                mAccountInfoFile.failWrite(fos);
1699            }
1700        }
1701    }
1702
1703    static int getIntColumn(Cursor c, String name) {
1704        return c.getInt(c.getColumnIndex(name));
1705    }
1706
1707    static long getLongColumn(Cursor c, String name) {
1708        return c.getLong(c.getColumnIndex(name));
1709    }
1710
1711    /**
1712     * Load sync engine state from the old syncmanager database, and then
1713     * erase it.  Note that we don't deal with pending operations, active
1714     * sync, or history.
1715     */
1716    private void readAndDeleteLegacyAccountInfoLocked() {
1717        // Look for old database to initialize from.
1718        File file = mContext.getDatabasePath("syncmanager.db");
1719        if (!file.exists()) {
1720            return;
1721        }
1722        String path = file.getPath();
1723        SQLiteDatabase db = null;
1724        try {
1725            db = SQLiteDatabase.openDatabase(path, null,
1726                    SQLiteDatabase.OPEN_READONLY);
1727        } catch (SQLiteException e) {
1728        }
1729
1730        if (db != null) {
1731            final boolean hasType = db.getVersion() >= 11;
1732
1733            // Copy in all of the status information, as well as accounts.
1734            if (DEBUG_FILE) Log.v(TAG, "Reading legacy sync accounts db");
1735            SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
1736            qb.setTables("stats, status");
1737            HashMap<String,String> map = new HashMap<String,String>();
1738            map.put("_id", "status._id as _id");
1739            map.put("account", "stats.account as account");
1740            if (hasType) {
1741                map.put("account_type", "stats.account_type as account_type");
1742            }
1743            map.put("authority", "stats.authority as authority");
1744            map.put("totalElapsedTime", "totalElapsedTime");
1745            map.put("numSyncs", "numSyncs");
1746            map.put("numSourceLocal", "numSourceLocal");
1747            map.put("numSourcePoll", "numSourcePoll");
1748            map.put("numSourceServer", "numSourceServer");
1749            map.put("numSourceUser", "numSourceUser");
1750            map.put("lastSuccessSource", "lastSuccessSource");
1751            map.put("lastSuccessTime", "lastSuccessTime");
1752            map.put("lastFailureSource", "lastFailureSource");
1753            map.put("lastFailureTime", "lastFailureTime");
1754            map.put("lastFailureMesg", "lastFailureMesg");
1755            map.put("pending", "pending");
1756            qb.setProjectionMap(map);
1757            qb.appendWhere("stats._id = status.stats_id");
1758            Cursor c = qb.query(db, null, null, null, null, null, null);
1759            while (c.moveToNext()) {
1760                String accountName = c.getString(c.getColumnIndex("account"));
1761                String accountType = hasType
1762                        ? c.getString(c.getColumnIndex("account_type")) : null;
1763                if (accountType == null) {
1764                    accountType = "com.google";
1765                }
1766                String authorityName = c.getString(c.getColumnIndex("authority"));
1767                AuthorityInfo authority = this.getOrCreateAuthorityLocked(
1768                        new Account(accountName, accountType),
1769                        authorityName, -1, false);
1770                if (authority != null) {
1771                    int i = mSyncStatus.size();
1772                    boolean found = false;
1773                    SyncStatusInfo st = null;
1774                    while (i > 0) {
1775                        i--;
1776                        st = mSyncStatus.valueAt(i);
1777                        if (st.authorityId == authority.ident) {
1778                            found = true;
1779                            break;
1780                        }
1781                    }
1782                    if (!found) {
1783                        st = new SyncStatusInfo(authority.ident);
1784                        mSyncStatus.put(authority.ident, st);
1785                    }
1786                    st.totalElapsedTime = getLongColumn(c, "totalElapsedTime");
1787                    st.numSyncs = getIntColumn(c, "numSyncs");
1788                    st.numSourceLocal = getIntColumn(c, "numSourceLocal");
1789                    st.numSourcePoll = getIntColumn(c, "numSourcePoll");
1790                    st.numSourceServer = getIntColumn(c, "numSourceServer");
1791                    st.numSourceUser = getIntColumn(c, "numSourceUser");
1792                    st.numSourcePeriodic = 0;
1793                    st.lastSuccessSource = getIntColumn(c, "lastSuccessSource");
1794                    st.lastSuccessTime = getLongColumn(c, "lastSuccessTime");
1795                    st.lastFailureSource = getIntColumn(c, "lastFailureSource");
1796                    st.lastFailureTime = getLongColumn(c, "lastFailureTime");
1797                    st.lastFailureMesg = c.getString(c.getColumnIndex("lastFailureMesg"));
1798                    st.pending = getIntColumn(c, "pending") != 0;
1799                }
1800            }
1801
1802            c.close();
1803
1804            // Retrieve the settings.
1805            qb = new SQLiteQueryBuilder();
1806            qb.setTables("settings");
1807            c = qb.query(db, null, null, null, null, null, null);
1808            while (c.moveToNext()) {
1809                String name = c.getString(c.getColumnIndex("name"));
1810                String value = c.getString(c.getColumnIndex("value"));
1811                if (name == null) continue;
1812                if (name.equals("listen_for_tickles")) {
1813                    setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value));
1814                } else if (name.startsWith("sync_provider_")) {
1815                    String provider = name.substring("sync_provider_".length(),
1816                            name.length());
1817                    int i = mAuthorities.size();
1818                    while (i > 0) {
1819                        i--;
1820                        AuthorityInfo authority = mAuthorities.valueAt(i);
1821                        if (authority.authority.equals(provider)) {
1822                            authority.enabled = value == null || Boolean.parseBoolean(value);
1823                            authority.syncable = 1;
1824                        }
1825                    }
1826                }
1827            }
1828
1829            c.close();
1830
1831            db.close();
1832
1833            (new File(path)).delete();
1834        }
1835    }
1836
1837    public static final int STATUS_FILE_END = 0;
1838    public static final int STATUS_FILE_ITEM = 100;
1839
1840    /**
1841     * Read all sync status back in to the initial engine state.
1842     */
1843    private void readStatusLocked() {
1844        if (DEBUG_FILE) Log.v(TAG, "Reading " + mStatusFile.getBaseFile());
1845        try {
1846            byte[] data = mStatusFile.readFully();
1847            Parcel in = Parcel.obtain();
1848            in.unmarshall(data, 0, data.length);
1849            in.setDataPosition(0);
1850            int token;
1851            while ((token=in.readInt()) != STATUS_FILE_END) {
1852                if (token == STATUS_FILE_ITEM) {
1853                    SyncStatusInfo status = new SyncStatusInfo(in);
1854                    if (mAuthorities.indexOfKey(status.authorityId) >= 0) {
1855                        status.pending = false;
1856                        if (DEBUG_FILE) Log.v(TAG, "Adding status for id "
1857                                + status.authorityId);
1858                        mSyncStatus.put(status.authorityId, status);
1859                    }
1860                } else {
1861                    // Ooops.
1862                    Log.w(TAG, "Unknown status token: " + token);
1863                    break;
1864                }
1865            }
1866        } catch (java.io.IOException e) {
1867            Log.i(TAG, "No initial status");
1868        }
1869    }
1870
1871    /**
1872     * Write all sync status to the sync status file.
1873     */
1874    private void writeStatusLocked() {
1875        if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatusFile.getBaseFile());
1876
1877        // The file is being written, so we don't need to have a scheduled
1878        // write until the next change.
1879        removeMessages(MSG_WRITE_STATUS);
1880
1881        FileOutputStream fos = null;
1882        try {
1883            fos = mStatusFile.startWrite();
1884            Parcel out = Parcel.obtain();
1885            final int N = mSyncStatus.size();
1886            for (int i=0; i<N; i++) {
1887                SyncStatusInfo status = mSyncStatus.valueAt(i);
1888                out.writeInt(STATUS_FILE_ITEM);
1889                status.writeToParcel(out, 0);
1890            }
1891            out.writeInt(STATUS_FILE_END);
1892            fos.write(out.marshall());
1893            out.recycle();
1894
1895            mStatusFile.finishWrite(fos);
1896        } catch (java.io.IOException e1) {
1897            Log.w(TAG, "Error writing status", e1);
1898            if (fos != null) {
1899                mStatusFile.failWrite(fos);
1900            }
1901        }
1902    }
1903
1904    public static final int PENDING_OPERATION_VERSION = 2;
1905
1906    /**
1907     * Read all pending operations back in to the initial engine state.
1908     */
1909    private void readPendingOperationsLocked() {
1910        if (DEBUG_FILE) Log.v(TAG, "Reading " + mPendingFile.getBaseFile());
1911        try {
1912            byte[] data = mPendingFile.readFully();
1913            Parcel in = Parcel.obtain();
1914            in.unmarshall(data, 0, data.length);
1915            in.setDataPosition(0);
1916            final int SIZE = in.dataSize();
1917            while (in.dataPosition() < SIZE) {
1918                int version = in.readInt();
1919                if (version != PENDING_OPERATION_VERSION && version != 1) {
1920                    Log.w(TAG, "Unknown pending operation version "
1921                            + version + "; dropping all ops");
1922                    break;
1923                }
1924                int authorityId = in.readInt();
1925                int syncSource = in.readInt();
1926                byte[] flatExtras = in.createByteArray();
1927                boolean expedited;
1928                if (version == PENDING_OPERATION_VERSION) {
1929                    expedited = in.readInt() != 0;
1930                } else {
1931                    expedited = false;
1932                }
1933                AuthorityInfo authority = mAuthorities.get(authorityId);
1934                if (authority != null) {
1935                    Bundle extras = null;
1936                    if (flatExtras != null) {
1937                        extras = unflattenBundle(flatExtras);
1938                    }
1939                    PendingOperation op = new PendingOperation(
1940                            authority.account, syncSource,
1941                            authority.authority, extras, expedited);
1942                    op.authorityId = authorityId;
1943                    op.flatExtras = flatExtras;
1944                    if (DEBUG_FILE) Log.v(TAG, "Adding pending op: account=" + op.account
1945                            + " auth=" + op.authority
1946                            + " src=" + op.syncSource
1947                            + " expedited=" + op.expedited
1948                            + " extras=" + op.extras);
1949                    mPendingOperations.add(op);
1950                }
1951            }
1952        } catch (java.io.IOException e) {
1953            Log.i(TAG, "No initial pending operations");
1954        }
1955    }
1956
1957    private void writePendingOperationLocked(PendingOperation op, Parcel out) {
1958        out.writeInt(PENDING_OPERATION_VERSION);
1959        out.writeInt(op.authorityId);
1960        out.writeInt(op.syncSource);
1961        if (op.flatExtras == null && op.extras != null) {
1962            op.flatExtras = flattenBundle(op.extras);
1963        }
1964        out.writeByteArray(op.flatExtras);
1965        out.writeInt(op.expedited ? 1 : 0);
1966    }
1967
1968    /**
1969     * Write all currently pending ops to the pending ops file.
1970     */
1971    private void writePendingOperationsLocked() {
1972        final int N = mPendingOperations.size();
1973        FileOutputStream fos = null;
1974        try {
1975            if (N == 0) {
1976                if (DEBUG_FILE) Log.v(TAG, "Truncating " + mPendingFile.getBaseFile());
1977                mPendingFile.truncate();
1978                return;
1979            }
1980
1981            if (DEBUG_FILE) Log.v(TAG, "Writing new " + mPendingFile.getBaseFile());
1982            fos = mPendingFile.startWrite();
1983
1984            Parcel out = Parcel.obtain();
1985            for (int i=0; i<N; i++) {
1986                PendingOperation op = mPendingOperations.get(i);
1987                writePendingOperationLocked(op, out);
1988            }
1989            fos.write(out.marshall());
1990            out.recycle();
1991
1992            mPendingFile.finishWrite(fos);
1993        } catch (java.io.IOException e1) {
1994            Log.w(TAG, "Error writing pending operations", e1);
1995            if (fos != null) {
1996                mPendingFile.failWrite(fos);
1997            }
1998        }
1999    }
2000
2001    /**
2002     * Append the given operation to the pending ops file; if unable to,
2003     * write all pending ops.
2004     */
2005    private void appendPendingOperationLocked(PendingOperation op) {
2006        if (DEBUG_FILE) Log.v(TAG, "Appending to " + mPendingFile.getBaseFile());
2007        FileOutputStream fos = null;
2008        try {
2009            fos = mPendingFile.openAppend();
2010        } catch (java.io.IOException e) {
2011            if (DEBUG_FILE) Log.v(TAG, "Failed append; writing full file");
2012            writePendingOperationsLocked();
2013            return;
2014        }
2015
2016        try {
2017            Parcel out = Parcel.obtain();
2018            writePendingOperationLocked(op, out);
2019            fos.write(out.marshall());
2020            out.recycle();
2021        } catch (java.io.IOException e1) {
2022            Log.w(TAG, "Error writing pending operations", e1);
2023        } finally {
2024            try {
2025                fos.close();
2026            } catch (java.io.IOException e2) {
2027            }
2028        }
2029    }
2030
2031    static private byte[] flattenBundle(Bundle bundle) {
2032        byte[] flatData = null;
2033        Parcel parcel = Parcel.obtain();
2034        try {
2035            bundle.writeToParcel(parcel, 0);
2036            flatData = parcel.marshall();
2037        } finally {
2038            parcel.recycle();
2039        }
2040        return flatData;
2041    }
2042
2043    static private Bundle unflattenBundle(byte[] flatData) {
2044        Bundle bundle;
2045        Parcel parcel = Parcel.obtain();
2046        try {
2047            parcel.unmarshall(flatData, 0, flatData.length);
2048            parcel.setDataPosition(0);
2049            bundle = parcel.readBundle();
2050        } catch (RuntimeException e) {
2051            // A RuntimeException is thrown if we were unable to parse the parcel.
2052            // Create an empty parcel in this case.
2053            bundle = new Bundle();
2054        } finally {
2055            parcel.recycle();
2056        }
2057        return bundle;
2058    }
2059
2060    public static final int STATISTICS_FILE_END = 0;
2061    public static final int STATISTICS_FILE_ITEM_OLD = 100;
2062    public static final int STATISTICS_FILE_ITEM = 101;
2063
2064    /**
2065     * Read all sync statistics back in to the initial engine state.
2066     */
2067    private void readStatisticsLocked() {
2068        try {
2069            byte[] data = mStatisticsFile.readFully();
2070            Parcel in = Parcel.obtain();
2071            in.unmarshall(data, 0, data.length);
2072            in.setDataPosition(0);
2073            int token;
2074            int index = 0;
2075            while ((token=in.readInt()) != STATISTICS_FILE_END) {
2076                if (token == STATISTICS_FILE_ITEM
2077                        || token == STATISTICS_FILE_ITEM_OLD) {
2078                    int day = in.readInt();
2079                    if (token == STATISTICS_FILE_ITEM_OLD) {
2080                        day = day - 2009 + 14245;  // Magic!
2081                    }
2082                    DayStats ds = new DayStats(day);
2083                    ds.successCount = in.readInt();
2084                    ds.successTime = in.readLong();
2085                    ds.failureCount = in.readInt();
2086                    ds.failureTime = in.readLong();
2087                    if (index < mDayStats.length) {
2088                        mDayStats[index] = ds;
2089                        index++;
2090                    }
2091                } else {
2092                    // Ooops.
2093                    Log.w(TAG, "Unknown stats token: " + token);
2094                    break;
2095                }
2096            }
2097        } catch (java.io.IOException e) {
2098            Log.i(TAG, "No initial statistics");
2099        }
2100    }
2101
2102    /**
2103     * Write all sync statistics to the sync status file.
2104     */
2105    private void writeStatisticsLocked() {
2106        if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatisticsFile.getBaseFile());
2107
2108        // The file is being written, so we don't need to have a scheduled
2109        // write until the next change.
2110        removeMessages(MSG_WRITE_STATISTICS);
2111
2112        FileOutputStream fos = null;
2113        try {
2114            fos = mStatisticsFile.startWrite();
2115            Parcel out = Parcel.obtain();
2116            final int N = mDayStats.length;
2117            for (int i=0; i<N; i++) {
2118                DayStats ds = mDayStats[i];
2119                if (ds == null) {
2120                    break;
2121                }
2122                out.writeInt(STATISTICS_FILE_ITEM);
2123                out.writeInt(ds.day);
2124                out.writeInt(ds.successCount);
2125                out.writeLong(ds.successTime);
2126                out.writeInt(ds.failureCount);
2127                out.writeLong(ds.failureTime);
2128            }
2129            out.writeInt(STATISTICS_FILE_END);
2130            fos.write(out.marshall());
2131            out.recycle();
2132
2133            mStatisticsFile.finishWrite(fos);
2134        } catch (java.io.IOException e1) {
2135            Log.w(TAG, "Error writing stats", e1);
2136            if (fos != null) {
2137                mStatisticsFile.failWrite(fos);
2138            }
2139        }
2140    }
2141}
2142