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