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