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