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