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