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