• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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 android.accounts.Account;
20 import android.app.job.JobInfo;
21 import android.content.ContentResolver;
22 import android.content.ContentResolver.SyncExemption;
23 import android.content.pm.PackageManager;
24 import android.os.Bundle;
25 import android.os.PersistableBundle;
26 import android.os.SystemClock;
27 import android.os.UserHandle;
28 import android.util.Slog;
29 
30 /**
31  * Value type that represents a sync operation.
32  * This holds all information related to a sync operation - both one off and periodic.
33  * Data stored in this is used to schedule a job with the JobScheduler.
34  * {@hide}
35  */
36 public class SyncOperation {
37     public static final String TAG = "SyncManager";
38 
39     /**
40      * This is used in the {@link #sourcePeriodicId} field if the operation is not initiated by a failed
41      * periodic sync.
42      */
43     public static final int NO_JOB_ID = -1;
44 
45     public static final int REASON_BACKGROUND_DATA_SETTINGS_CHANGED = -1;
46     public static final int REASON_ACCOUNTS_UPDATED = -2;
47     public static final int REASON_SERVICE_CHANGED = -3;
48     public static final int REASON_PERIODIC = -4;
49     /** Sync started because it has just been set to isSyncable. */
50     public static final int REASON_IS_SYNCABLE = -5;
51     /** Sync started because it has just been set to sync automatically. */
52     public static final int REASON_SYNC_AUTO = -6;
53     /** Sync started because global sync automatically has been set to true. */
54     public static final int REASON_MASTER_SYNC_AUTO = -7;
55     public static final int REASON_USER_START = -8;
56 
57     private static String[] REASON_NAMES = new String[] {
58             "DataSettingsChanged",
59             "AccountsUpdated",
60             "ServiceChanged",
61             "Periodic",
62             "IsSyncable",
63             "AutoSync",
64             "MasterSyncAuto",
65             "UserStart",
66     };
67 
68     /** Identifying info for the target for this operation. */
69     public final SyncStorageEngine.EndPoint target;
70     public final int owningUid;
71     public final String owningPackage;
72     /** Why this sync was kicked off. {@link #REASON_NAMES} */
73     public final int reason;
74     /** Where this sync was initiated. */
75     public final int syncSource;
76     public final boolean allowParallelSyncs;
77 
78     /**
79      * Sync extras. Note, DO NOT modify this bundle directly. When changing the content, always
80      * create a copy, update it, set it in this field. This is to avoid concurrent modifications
81      * when other threads are reading it.
82      */
83     private volatile Bundle mImmutableExtras;
84 
85     public final boolean isPeriodic;
86     /** jobId of the periodic SyncOperation that initiated this one */
87     public final int sourcePeriodicId;
88     /** Operations are considered duplicates if keys are equal */
89     public final String key;
90 
91     /** Poll frequency of periodic sync in milliseconds */
92     public final long periodMillis;
93     /** Flex time of periodic sync in milliseconds */
94     public final long flexMillis;
95     /** Descriptive string key for this operation */
96     public String wakeLockName;
97     /**
98      * Used when duplicate pending syncs are present. The one with the lowest expectedRuntime
99      * is kept, others are discarded.
100      */
101     public long expectedRuntime;
102 
103     /** Stores the number of times this sync operation failed and had to be retried. */
104     int retries;
105 
106     /**
107      * Indicates if a sync that was originally scheduled as an EJ is being re-scheduled as a
108      * regular job. Specifically, this will be {@code true} if a sync is being backed-off but
109      * {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF} is not set.
110      */
111     boolean scheduleEjAsRegularJob;
112 
113     /** jobId of the JobScheduler job corresponding to this sync */
114     public int jobId;
115 
116     @SyncExemption
117     public int syncExemptionFlag;
118 
SyncOperation(Account account, int userId, int owningUid, String owningPackage, int reason, int source, String provider, Bundle extras, boolean allowParallelSyncs, @SyncExemption int syncExemptionFlag)119     public SyncOperation(Account account, int userId, int owningUid, String owningPackage,
120                          int reason, int source, String provider, Bundle extras,
121                          boolean allowParallelSyncs, @SyncExemption int syncExemptionFlag) {
122         this(new SyncStorageEngine.EndPoint(account, provider, userId), owningUid, owningPackage,
123                 reason, source, extras, allowParallelSyncs, syncExemptionFlag);
124     }
125 
SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage, int reason, int source, Bundle extras, boolean allowParallelSyncs, @SyncExemption int syncExemptionFlag)126     private SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage,
127             int reason, int source, Bundle extras, boolean allowParallelSyncs,
128             @SyncExemption int syncExemptionFlag) {
129         this(info, owningUid, owningPackage, reason, source, extras, allowParallelSyncs, false,
130                 NO_JOB_ID, 0, 0, syncExemptionFlag);
131     }
132 
SyncOperation(SyncOperation op, long periodMillis, long flexMillis)133     public SyncOperation(SyncOperation op, long periodMillis, long flexMillis) {
134         this(op.target, op.owningUid, op.owningPackage, op.reason, op.syncSource,
135                 op.mImmutableExtras, op.allowParallelSyncs, op.isPeriodic, op.sourcePeriodicId,
136                 periodMillis, flexMillis, ContentResolver.SYNC_EXEMPTION_NONE);
137     }
138 
SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage, int reason, int source, Bundle extras, boolean allowParallelSyncs, boolean isPeriodic, int sourcePeriodicId, long periodMillis, long flexMillis, @SyncExemption int syncExemptionFlag)139     public SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage,
140             int reason, int source, Bundle extras,
141             boolean allowParallelSyncs,
142             boolean isPeriodic, int sourcePeriodicId, long periodMillis,
143             long flexMillis, @SyncExemption int syncExemptionFlag) {
144         this.target = info;
145         this.owningUid = owningUid;
146         this.owningPackage = owningPackage;
147         this.reason = reason;
148         this.syncSource = source;
149         this.mImmutableExtras = new Bundle(extras);
150         this.allowParallelSyncs = allowParallelSyncs;
151         this.isPeriodic = isPeriodic;
152         this.sourcePeriodicId = sourcePeriodicId;
153         this.periodMillis = periodMillis;
154         this.flexMillis = flexMillis;
155         this.jobId = NO_JOB_ID;
156         this.key = toKey();
157         this.syncExemptionFlag = syncExemptionFlag;
158     }
159 
160     /* Get a one off sync operation instance from a periodic sync. */
createOneTimeSyncOperation()161     public SyncOperation createOneTimeSyncOperation() {
162         if (!isPeriodic) {
163             return null;
164         }
165         SyncOperation op = new SyncOperation(target, owningUid, owningPackage, reason, syncSource,
166                 mImmutableExtras, allowParallelSyncs, false, jobId /* sourcePeriodicId */,
167                 periodMillis, flexMillis, ContentResolver.SYNC_EXEMPTION_NONE);
168         return op;
169     }
170 
SyncOperation(SyncOperation other)171     public SyncOperation(SyncOperation other) {
172         target = other.target;
173         owningUid = other.owningUid;
174         owningPackage = other.owningPackage;
175         reason = other.reason;
176         syncSource = other.syncSource;
177         allowParallelSyncs = other.allowParallelSyncs;
178 
179         // Since we treat this field as immutable, it's okay to use a shallow copy here.
180         // No need to create a copy.
181         mImmutableExtras = other.mImmutableExtras;
182         wakeLockName = other.wakeLockName();
183         isPeriodic = other.isPeriodic;
184         sourcePeriodicId = other.sourcePeriodicId;
185         periodMillis = other.periodMillis;
186         flexMillis = other.flexMillis;
187         this.key = other.key;
188         syncExemptionFlag = other.syncExemptionFlag;
189     }
190 
191     /**
192      * All fields are stored in a corresponding key in the persistable bundle.
193      *
194      * {@link #mImmutableExtras} is a Bundle and can contain parcelable objects.
195      * But only the type Account
196      * is allowed {@link ContentResolver#validateSyncExtrasBundle(Bundle)} that can't be stored in
197      * a PersistableBundle. For every value of type Account with key 'key', we store a
198      * PersistableBundle containing account information at key 'ACCOUNT:key'. The Account object
199      * can be reconstructed using this.
200      *
201      * We put a flag with key 'SyncManagerJob', to identify while reconstructing a sync operation
202      * from a bundle whether the bundle actually contains information about a sync.
203      * @return A persistable bundle containing all information to re-construct the sync operation.
204      */
toJobInfoExtras()205     PersistableBundle toJobInfoExtras() {
206         // This will be passed as extras bundle to a JobScheduler job.
207         PersistableBundle jobInfoExtras = new PersistableBundle();
208 
209         PersistableBundle syncExtrasBundle = new PersistableBundle();
210 
211         final Bundle extras = mImmutableExtras;
212         for (String key : extras.keySet()) {
213             Object value = extras.get(key);
214             if (value instanceof Account) {
215                 Account account = (Account) value;
216                 PersistableBundle accountBundle = new PersistableBundle();
217                 accountBundle.putString("accountName", account.name);
218                 accountBundle.putString("accountType", account.type);
219                 // This is stored in jobInfoExtras so that we don't override a user specified
220                 // sync extra with the same key.
221                 jobInfoExtras.putPersistableBundle("ACCOUNT:" + key, accountBundle);
222             } else if (value instanceof Long) {
223                 syncExtrasBundle.putLong(key, (Long) value);
224             } else if (value instanceof Integer) {
225                 syncExtrasBundle.putInt(key, (Integer) value);
226             } else if (value instanceof Boolean) {
227                 syncExtrasBundle.putBoolean(key, (Boolean) value);
228             } else if (value instanceof Float) {
229                 syncExtrasBundle.putDouble(key, (double) (float) value);
230             } else if (value instanceof Double) {
231                 syncExtrasBundle.putDouble(key, (Double) value);
232             } else if (value instanceof String) {
233                 syncExtrasBundle.putString(key, (String) value);
234             } else if (value == null) {
235                 syncExtrasBundle.putString(key, null);
236             } else {
237                 Slog.e(TAG, "Unknown extra type.");
238             }
239         }
240         jobInfoExtras.putPersistableBundle("syncExtras", syncExtrasBundle);
241 
242         jobInfoExtras.putBoolean("SyncManagerJob", true);
243 
244         jobInfoExtras.putString("provider", target.provider);
245         jobInfoExtras.putString("accountName", target.account.name);
246         jobInfoExtras.putString("accountType", target.account.type);
247         jobInfoExtras.putInt("userId", target.userId);
248         jobInfoExtras.putInt("owningUid", owningUid);
249         jobInfoExtras.putString("owningPackage", owningPackage);
250         jobInfoExtras.putInt("reason", reason);
251         jobInfoExtras.putInt("source", syncSource);
252         jobInfoExtras.putBoolean("allowParallelSyncs", allowParallelSyncs);
253         jobInfoExtras.putInt("jobId", jobId);
254         jobInfoExtras.putBoolean("isPeriodic", isPeriodic);
255         jobInfoExtras.putInt("sourcePeriodicId", sourcePeriodicId);
256         jobInfoExtras.putLong("periodMillis", periodMillis);
257         jobInfoExtras.putLong("flexMillis", flexMillis);
258         jobInfoExtras.putLong("expectedRuntime", expectedRuntime);
259         jobInfoExtras.putInt("retries", retries);
260         jobInfoExtras.putInt("syncExemptionFlag", syncExemptionFlag);
261         jobInfoExtras.putBoolean("ejDowngradedToRegular", scheduleEjAsRegularJob);
262         return jobInfoExtras;
263     }
264 
265     /**
266      * Reconstructs a sync operation from an extras Bundle. Returns null if the bundle doesn't
267      * contain a valid sync operation.
268      */
maybeCreateFromJobExtras(PersistableBundle jobExtras)269     static SyncOperation maybeCreateFromJobExtras(PersistableBundle jobExtras) {
270         if (jobExtras == null) {
271             return null;
272         }
273         String accountName, accountType;
274         String provider;
275         int userId, owningUid;
276         String owningPackage;
277         int reason, source;
278         int initiatedBy;
279         Bundle extras;
280         boolean allowParallelSyncs, isPeriodic;
281         long periodMillis, flexMillis;
282         int syncExemptionFlag;
283 
284         if (!jobExtras.getBoolean("SyncManagerJob", false)) {
285             return null;
286         }
287 
288         accountName = jobExtras.getString("accountName");
289         accountType = jobExtras.getString("accountType");
290         provider = jobExtras.getString("provider");
291         userId = jobExtras.getInt("userId", Integer.MAX_VALUE);
292         owningUid = jobExtras.getInt("owningUid");
293         owningPackage = jobExtras.getString("owningPackage");
294         reason = jobExtras.getInt("reason", Integer.MAX_VALUE);
295         source = jobExtras.getInt("source", Integer.MAX_VALUE);
296         allowParallelSyncs = jobExtras.getBoolean("allowParallelSyncs", false);
297         isPeriodic = jobExtras.getBoolean("isPeriodic", false);
298         initiatedBy = jobExtras.getInt("sourcePeriodicId", NO_JOB_ID);
299         periodMillis = jobExtras.getLong("periodMillis");
300         flexMillis = jobExtras.getLong("flexMillis");
301         syncExemptionFlag = jobExtras.getInt("syncExemptionFlag",
302                 ContentResolver.SYNC_EXEMPTION_NONE);
303         extras = new Bundle();
304 
305         PersistableBundle syncExtras = jobExtras.getPersistableBundle("syncExtras");
306         if (syncExtras != null) {
307             extras.putAll(syncExtras);
308         }
309 
310         for (String key: jobExtras.keySet()) {
311             if (key!= null && key.startsWith("ACCOUNT:")) {
312                 String newKey = key.substring(8); // Strip off the 'ACCOUNT:' prefix.
313                 PersistableBundle accountsBundle = jobExtras.getPersistableBundle(key);
314                 Account account = new Account(accountsBundle.getString("accountName"),
315                         accountsBundle.getString("accountType"));
316                 extras.putParcelable(newKey, account);
317             }
318         }
319 
320         Account account = new Account(accountName, accountType);
321         SyncStorageEngine.EndPoint target =
322                 new SyncStorageEngine.EndPoint(account, provider, userId);
323         SyncOperation op = new SyncOperation(target, owningUid, owningPackage, reason, source,
324                 extras, allowParallelSyncs, isPeriodic, initiatedBy, periodMillis, flexMillis,
325                 syncExemptionFlag);
326         op.jobId = jobExtras.getInt("jobId");
327         op.expectedRuntime = jobExtras.getLong("expectedRuntime");
328         op.retries = jobExtras.getInt("retries");
329         op.scheduleEjAsRegularJob = jobExtras.getBoolean("ejDowngradedToRegular");
330         return op;
331     }
332 
333     /**
334      * Determine whether if this sync operation is running, the provided operation would conflict
335      * with it.
336      * Parallel syncs allow multiple accounts to be synced at the same time.
337      */
isConflict(SyncOperation toRun)338     boolean isConflict(SyncOperation toRun) {
339         final SyncStorageEngine.EndPoint other = toRun.target;
340         return target.account.type.equals(other.account.type)
341                 && target.provider.equals(other.provider)
342                 && target.userId == other.userId
343                 && (!allowParallelSyncs
344                 || target.account.name.equals(other.account.name));
345     }
346 
isReasonPeriodic()347     boolean isReasonPeriodic() {
348         return reason == REASON_PERIODIC;
349     }
350 
matchesPeriodicOperation(SyncOperation other)351     boolean matchesPeriodicOperation(SyncOperation other) {
352         return target.matchesSpec(other.target)
353                 && SyncManager.syncExtrasEquals(mImmutableExtras, other.mImmutableExtras, true)
354                 && periodMillis == other.periodMillis && flexMillis == other.flexMillis;
355     }
356 
isDerivedFromFailedPeriodicSync()357     boolean isDerivedFromFailedPeriodicSync() {
358         return sourcePeriodicId != NO_JOB_ID;
359     }
360 
findPriority()361     int findPriority() {
362         if (isInitialization()) {
363             return JobInfo.PRIORITY_SYNC_INITIALIZATION;
364         } else if (isExpedited()) {
365             return JobInfo.PRIORITY_SYNC_EXPEDITED;
366         }
367         return JobInfo.PRIORITY_DEFAULT;
368     }
369 
toKey()370     private String toKey() {
371         final Bundle extras = mImmutableExtras;
372         StringBuilder sb = new StringBuilder();
373         sb.append("provider: ").append(target.provider);
374         sb.append(" account {name=" + target.account.name
375                 + ", user="
376                 + target.userId
377                 + ", type="
378                 + target.account.type
379                 + "}");
380         sb.append(" isPeriodic: ").append(isPeriodic);
381         sb.append(" period: ").append(periodMillis);
382         sb.append(" flex: ").append(flexMillis);
383         sb.append(" extras: ");
384         extrasToStringBuilder(extras, sb);
385         return sb.toString();
386     }
387 
388     @Override
toString()389     public String toString() {
390         return dump(null, true, null, false);
391     }
392 
toSafeString()393     public String toSafeString() {
394         return dump(null, true, null, true);
395     }
396 
dump(PackageManager pm, boolean shorter, SyncAdapterStateFetcher appStates, boolean logSafe)397     String dump(PackageManager pm, boolean shorter, SyncAdapterStateFetcher appStates,
398             boolean logSafe) {
399         final Bundle extras = mImmutableExtras;
400         StringBuilder sb = new StringBuilder();
401         sb.append("JobId=").append(jobId)
402                 .append(" ")
403                 .append(logSafe ? "***" : target.account.name)
404                 .append("/")
405                 .append(target.account.type)
406                 .append(" u")
407                 .append(target.userId)
408                 .append(" [")
409                 .append(target.provider)
410                 .append("] ");
411         sb.append(SyncStorageEngine.SOURCES[syncSource]);
412         if (expectedRuntime != 0) {
413             sb.append(" ExpectedIn=");
414             SyncManager.formatDurationHMS(sb,
415                     (expectedRuntime - SystemClock.elapsedRealtime()));
416         }
417         if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) {
418             sb.append(" EXPEDITED");
419         }
420         if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB, false)) {
421             sb.append(" EXPEDITED-JOB");
422             if (scheduleEjAsRegularJob) {
423                 sb.append("(scheduled-as-regular)");
424             }
425         }
426         switch (syncExemptionFlag) {
427             case ContentResolver.SYNC_EXEMPTION_NONE:
428                 break;
429             case ContentResolver.SYNC_EXEMPTION_PROMOTE_BUCKET:
430                 sb.append(" STANDBY-EXEMPTED");
431                 break;
432             case ContentResolver.SYNC_EXEMPTION_PROMOTE_BUCKET_WITH_TEMP:
433                 sb.append(" STANDBY-EXEMPTED(TOP)");
434                 break;
435             default:
436                 sb.append(" ExemptionFlag=" + syncExemptionFlag);
437                 break;
438         }
439         sb.append(" Reason=");
440         sb.append(reasonToString(pm, reason));
441         if (isPeriodic) {
442             sb.append(" (period=");
443             SyncManager.formatDurationHMS(sb, periodMillis);
444             sb.append(" flex=");
445             SyncManager.formatDurationHMS(sb, flexMillis);
446             sb.append(")");
447         }
448         if (retries > 0) {
449             sb.append(" Retries=");
450             sb.append(retries);
451         }
452         if (!shorter) {
453             sb.append(" Owner={");
454             UserHandle.formatUid(sb, owningUid);
455             sb.append(" ");
456             sb.append(owningPackage);
457             if (appStates != null) {
458                 sb.append(" [");
459                 sb.append(appStates.getStandbyBucket(
460                         UserHandle.getUserId(owningUid), owningPackage));
461                 sb.append("]");
462 
463                 if (appStates.isAppActive(owningUid)) {
464                     sb.append(" [ACTIVE]");
465                 }
466             }
467             sb.append("}");
468             if (!extras.keySet().isEmpty()) {
469                 sb.append(" ");
470                 extrasToStringBuilder(extras, sb);
471             }
472         }
473         return sb.toString();
474     }
475 
reasonToString(PackageManager pm, int reason)476     static String reasonToString(PackageManager pm, int reason) {
477         if (reason >= 0) {
478             if (pm != null) {
479                 final String[] packages = pm.getPackagesForUid(reason);
480                 if (packages != null && packages.length == 1) {
481                     return packages[0];
482                 }
483                 final String name = pm.getNameForUid(reason);
484                 if (name != null) {
485                     return name;
486                 }
487                 return String.valueOf(reason);
488             } else {
489                 return String.valueOf(reason);
490             }
491         } else {
492             final int index = -reason - 1;
493             if (index >= REASON_NAMES.length) {
494                 return String.valueOf(reason);
495             } else {
496                 return REASON_NAMES[index];
497             }
498         }
499     }
500 
isInitialization()501     boolean isInitialization() {
502         return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
503     }
504 
isExpedited()505     boolean isExpedited() {
506         return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
507     }
508 
isUpload()509     boolean isUpload() {
510         return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
511     }
512 
513     /**
514      * Disable SYNC_EXTRAS_UPLOAD, so it will be a two-way (normal) sync.
515      */
enableTwoWaySync()516     void enableTwoWaySync() {
517         removeExtra(ContentResolver.SYNC_EXTRAS_UPLOAD);
518     }
519 
hasIgnoreBackoff()520     boolean hasIgnoreBackoff() {
521         return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
522     }
523 
524     /**
525      * Disable SYNC_EXTRAS_IGNORE_BACKOFF.
526      *
527      * The SYNC_EXTRAS_IGNORE_BACKOFF only applies to the first attempt to sync a given
528      * request. Retries of the request will always honor the backoff, so clear the
529      * flag in case we retry this request.
530      */
enableBackoff()531     void enableBackoff() {
532         removeExtra(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
533     }
534 
hasDoNotRetry()535     boolean hasDoNotRetry() {
536         return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false);
537     }
538 
isNotAllowedOnMetered()539     boolean isNotAllowedOnMetered() {
540         return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, false);
541     }
542 
isManual()543     boolean isManual() {
544         return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
545     }
546 
isIgnoreSettings()547     boolean isIgnoreSettings() {
548         return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
549     }
550 
hasRequireCharging()551     boolean hasRequireCharging() {
552         return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_REQUIRE_CHARGING, false);
553     }
554 
isScheduledAsExpeditedJob()555     boolean isScheduledAsExpeditedJob() {
556         return mImmutableExtras.getBoolean(
557                 ContentResolver.SYNC_EXTRAS_SCHEDULE_AS_EXPEDITED_JOB, false);
558     }
559 
isAppStandbyExempted()560     boolean isAppStandbyExempted() {
561         return syncExemptionFlag != ContentResolver.SYNC_EXEMPTION_NONE;
562     }
563 
areExtrasEqual(Bundle other, boolean includeSyncSettings)564     boolean areExtrasEqual(Bundle other, boolean includeSyncSettings) {
565         return SyncManager.syncExtrasEquals(mImmutableExtras, other, includeSyncSettings);
566     }
567 
extrasToStringBuilder(Bundle bundle, StringBuilder sb)568     static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
569         if (bundle == null) {
570             sb.append("null");
571             return;
572         }
573         sb.append("[");
574         for (String key : bundle.keySet()) {
575             sb.append(key).append("=").append(bundle.get(key)).append(" ");
576         }
577         sb.append("]");
578     }
579 
extrasToString(Bundle bundle)580     private static String extrasToString(Bundle bundle) {
581         final StringBuilder sb = new StringBuilder();
582         extrasToStringBuilder(bundle, sb);
583         return sb.toString();
584     }
585 
wakeLockName()586     String wakeLockName() {
587         if (wakeLockName != null) {
588             return wakeLockName;
589         }
590         return (wakeLockName = target.provider
591                 + "/" + target.account.type
592                 + "/" + target.account.name);
593     }
594 
595     // TODO: Test this to make sure that casting to object doesn't lose the type info for EventLog.
toEventLog(int event)596     public Object[] toEventLog(int event) {
597         Object[] logArray = new Object[4];
598         logArray[1] = event;
599         logArray[2] = syncSource;
600         logArray[0] = target.provider;
601         logArray[3] = target.account.name.hashCode();
602         return logArray;
603     }
604 
605     /**
606      * Removes a sync extra. Note do not call it from multiple threads simultaneously.
607      */
removeExtra(String key)608     private void removeExtra(String key) {
609         final Bundle b = mImmutableExtras;
610         if (!b.containsKey(key)) {
611             return;
612         }
613         final Bundle clone = new Bundle(b);
614         clone.remove(key);
615         mImmutableExtras = clone;
616     }
617 
getClonedExtras()618     public Bundle getClonedExtras() {
619         return new Bundle(mImmutableExtras);
620     }
621 
getExtrasAsString()622     public String getExtrasAsString() {
623         return extrasToString(mImmutableExtras);
624     }
625 }
626