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