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