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