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