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