• 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.pm.PackageManager;
23 import android.os.Bundle;
24 import android.os.PersistableBundle;
25 import android.os.SystemClock;
26 import android.os.UserHandle;
27 import android.util.Slog;
28 
29 /**
30  * Value type that represents a sync operation.
31  * This holds all information related to a sync operation - both one off and periodic.
32  * Data stored in this is used to schedule a job with the JobScheduler.
33  * {@hide}
34  */
35 public class SyncOperation {
36     public static final String TAG = "SyncManager";
37 
38     /**
39      * This is used in the {@link #sourcePeriodicId} field if the operation is not initiated by a failed
40      * periodic sync.
41      */
42     public static final int NO_JOB_ID = -1;
43 
44     public static final int REASON_BACKGROUND_DATA_SETTINGS_CHANGED = -1;
45     public static final int REASON_ACCOUNTS_UPDATED = -2;
46     public static final int REASON_SERVICE_CHANGED = -3;
47     public static final int REASON_PERIODIC = -4;
48     /** Sync started because it has just been set to isSyncable. */
49     public static final int REASON_IS_SYNCABLE = -5;
50     /** Sync started because it has just been set to sync automatically. */
51     public static final int REASON_SYNC_AUTO = -6;
52     /** Sync started because master sync automatically has been set to true. */
53     public static final int REASON_MASTER_SYNC_AUTO = -7;
54     public static final int REASON_USER_START = -8;
55 
56     private static String[] REASON_NAMES = new String[] {
57             "DataSettingsChanged",
58             "AccountsUpdated",
59             "ServiceChanged",
60             "Periodic",
61             "IsSyncable",
62             "AutoSync",
63             "MasterSyncAuto",
64             "UserStart",
65     };
66 
67     /** Identifying info for the target for this operation. */
68     public final SyncStorageEngine.EndPoint target;
69     public final int owningUid;
70     public final String owningPackage;
71     /** Why this sync was kicked off. {@link #REASON_NAMES} */
72     public final int reason;
73     /** Where this sync was initiated. */
74     public final int syncSource;
75     public final boolean allowParallelSyncs;
76     public final Bundle extras;
77     public final boolean isPeriodic;
78     /** jobId of the periodic SyncOperation that initiated this one */
79     public final int sourcePeriodicId;
80     /** Operations are considered duplicates if keys are equal */
81     public final String key;
82 
83     /** Poll frequency of periodic sync in milliseconds */
84     public final long periodMillis;
85     /** Flex time of periodic sync in milliseconds */
86     public final long flexMillis;
87     /** Descriptive string key for this operation */
88     public String wakeLockName;
89     /**
90      * Used when duplicate pending syncs are present. The one with the lowest expectedRuntime
91      * is kept, others are discarded.
92      */
93     public long expectedRuntime;
94 
95     /** Stores the number of times this sync operation failed and had to be retried. */
96     int retries;
97 
98     /** jobId of the JobScheduler job corresponding to this sync */
99     public int jobId;
100 
SyncOperation(Account account, int userId, int owningUid, String owningPackage, int reason, int source, String provider, Bundle extras, boolean allowParallelSyncs)101     public SyncOperation(Account account, int userId, int owningUid, String owningPackage,
102                          int reason, int source, String provider, Bundle extras,
103                          boolean allowParallelSyncs) {
104         this(new SyncStorageEngine.EndPoint(account, provider, userId), owningUid, owningPackage,
105                 reason, source, extras, allowParallelSyncs);
106     }
107 
SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage, int reason, int source, Bundle extras, boolean allowParallelSyncs)108     private SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage,
109                           int reason, int source, Bundle extras, boolean allowParallelSyncs) {
110         this(info, owningUid, owningPackage, reason, source, extras, allowParallelSyncs, false,
111                 NO_JOB_ID, 0, 0);
112     }
113 
SyncOperation(SyncOperation op, long periodMillis, long flexMillis)114     public SyncOperation(SyncOperation op, long periodMillis, long flexMillis) {
115         this(op.target, op.owningUid, op.owningPackage, op.reason, op.syncSource,
116                 new Bundle(op.extras), op.allowParallelSyncs, op.isPeriodic, op.sourcePeriodicId,
117                 periodMillis, flexMillis);
118     }
119 
SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage, int reason, int source, Bundle extras, boolean allowParallelSyncs, boolean isPeriodic, int sourcePeriodicId, long periodMillis, long flexMillis)120     public SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage,
121                          int reason, int source, Bundle extras, boolean allowParallelSyncs,
122                          boolean isPeriodic, int sourcePeriodicId, long periodMillis,
123                          long flexMillis) {
124         this.target = info;
125         this.owningUid = owningUid;
126         this.owningPackage = owningPackage;
127         this.reason = reason;
128         this.syncSource = source;
129         this.extras = new Bundle(extras);
130         this.allowParallelSyncs = allowParallelSyncs;
131         this.isPeriodic = isPeriodic;
132         this.sourcePeriodicId = sourcePeriodicId;
133         this.periodMillis = periodMillis;
134         this.flexMillis = flexMillis;
135         this.jobId = NO_JOB_ID;
136         this.key = toKey();
137     }
138 
139     /* Get a one off sync operation instance from a periodic sync. */
createOneTimeSyncOperation()140     public SyncOperation createOneTimeSyncOperation() {
141         if (!isPeriodic) {
142             return null;
143         }
144         SyncOperation op = new SyncOperation(target, owningUid, owningPackage, reason, syncSource,
145                 new Bundle(extras), allowParallelSyncs, false, jobId /* sourcePeriodicId */,
146                 periodMillis, flexMillis);
147         return op;
148     }
149 
SyncOperation(SyncOperation other)150     public SyncOperation(SyncOperation other) {
151         target = other.target;
152         owningUid = other.owningUid;
153         owningPackage = other.owningPackage;
154         reason = other.reason;
155         syncSource = other.syncSource;
156         allowParallelSyncs = other.allowParallelSyncs;
157         extras = new Bundle(other.extras);
158         wakeLockName = other.wakeLockName();
159         isPeriodic = other.isPeriodic;
160         sourcePeriodicId = other.sourcePeriodicId;
161         periodMillis = other.periodMillis;
162         flexMillis = other.flexMillis;
163         this.key = other.key;
164     }
165 
166     /**
167      * All fields are stored in a corresponding key in the persistable bundle.
168      *
169      * {@link #extras} is a Bundle and can contain parcelable objects. But only the type Account
170      * is allowed {@link ContentResolver#validateSyncExtrasBundle(Bundle)} that can't be stored in
171      * a PersistableBundle. For every value of type Account with key 'key', we store a
172      * PersistableBundle containing account information at key 'ACCOUNT:key'. The Account object
173      * can be reconstructed using this.
174      *
175      * We put a flag with key 'SyncManagerJob', to identify while reconstructing a sync operation
176      * from a bundle whether the bundle actually contains information about a sync.
177      * @return A persistable bundle containing all information to re-construct the sync operation.
178      */
toJobInfoExtras()179     PersistableBundle toJobInfoExtras() {
180         // This will be passed as extras bundle to a JobScheduler job.
181         PersistableBundle jobInfoExtras = new PersistableBundle();
182 
183         PersistableBundle syncExtrasBundle = new PersistableBundle();
184         for (String key: extras.keySet()) {
185             Object value = extras.get(key);
186             if (value instanceof Account) {
187                 Account account = (Account) value;
188                 PersistableBundle accountBundle = new PersistableBundle();
189                 accountBundle.putString("accountName", account.name);
190                 accountBundle.putString("accountType", account.type);
191                 // This is stored in jobInfoExtras so that we don't override a user specified
192                 // sync extra with the same key.
193                 jobInfoExtras.putPersistableBundle("ACCOUNT:" + key, accountBundle);
194             } else if (value instanceof Long) {
195                 syncExtrasBundle.putLong(key, (Long) value);
196             } else if (value instanceof Integer) {
197                 syncExtrasBundle.putInt(key, (Integer) value);
198             } else if (value instanceof Boolean) {
199                 syncExtrasBundle.putBoolean(key, (Boolean) value);
200             } else if (value instanceof Float) {
201                 syncExtrasBundle.putDouble(key, (double) (float) value);
202             } else if (value instanceof Double) {
203                 syncExtrasBundle.putDouble(key, (Double) value);
204             } else if (value instanceof String) {
205                 syncExtrasBundle.putString(key, (String) value);
206             } else if (value == null) {
207                 syncExtrasBundle.putString(key, null);
208             } else {
209                 Slog.e(TAG, "Unknown extra type.");
210             }
211         }
212         jobInfoExtras.putPersistableBundle("syncExtras", syncExtrasBundle);
213 
214         jobInfoExtras.putBoolean("SyncManagerJob", true);
215 
216         jobInfoExtras.putString("provider", target.provider);
217         jobInfoExtras.putString("accountName", target.account.name);
218         jobInfoExtras.putString("accountType", target.account.type);
219         jobInfoExtras.putInt("userId", target.userId);
220         jobInfoExtras.putInt("owningUid", owningUid);
221         jobInfoExtras.putString("owningPackage", owningPackage);
222         jobInfoExtras.putInt("reason", reason);
223         jobInfoExtras.putInt("source", syncSource);
224         jobInfoExtras.putBoolean("allowParallelSyncs", allowParallelSyncs);
225         jobInfoExtras.putInt("jobId", jobId);
226         jobInfoExtras.putBoolean("isPeriodic", isPeriodic);
227         jobInfoExtras.putInt("sourcePeriodicId", sourcePeriodicId);
228         jobInfoExtras.putLong("periodMillis", periodMillis);
229         jobInfoExtras.putLong("flexMillis", flexMillis);
230         jobInfoExtras.putLong("expectedRuntime", expectedRuntime);
231         jobInfoExtras.putInt("retries", retries);
232         return jobInfoExtras;
233     }
234 
235     /**
236      * Reconstructs a sync operation from an extras Bundle. Returns null if the bundle doesn't
237      * contain a valid sync operation.
238      */
maybeCreateFromJobExtras(PersistableBundle jobExtras)239     static SyncOperation maybeCreateFromJobExtras(PersistableBundle jobExtras) {
240         if (jobExtras == null) {
241             return null;
242         }
243         String accountName, accountType;
244         String provider;
245         int userId, owningUid;
246         String owningPackage;
247         int reason, source;
248         int initiatedBy;
249         Bundle extras;
250         boolean allowParallelSyncs, isPeriodic;
251         long periodMillis, flexMillis;
252 
253         if (!jobExtras.getBoolean("SyncManagerJob", false)) {
254             return null;
255         }
256 
257         accountName = jobExtras.getString("accountName");
258         accountType = jobExtras.getString("accountType");
259         provider = jobExtras.getString("provider");
260         userId = jobExtras.getInt("userId", Integer.MAX_VALUE);
261         owningUid = jobExtras.getInt("owningUid");
262         owningPackage = jobExtras.getString("owningPackage");
263         reason = jobExtras.getInt("reason", Integer.MAX_VALUE);
264         source = jobExtras.getInt("source", Integer.MAX_VALUE);
265         allowParallelSyncs = jobExtras.getBoolean("allowParallelSyncs", false);
266         isPeriodic = jobExtras.getBoolean("isPeriodic", false);
267         initiatedBy = jobExtras.getInt("sourcePeriodicId", NO_JOB_ID);
268         periodMillis = jobExtras.getLong("periodMillis");
269         flexMillis = jobExtras.getLong("flexMillis");
270         extras = new Bundle();
271 
272         PersistableBundle syncExtras = jobExtras.getPersistableBundle("syncExtras");
273         if (syncExtras != null) {
274             extras.putAll(syncExtras);
275         }
276 
277         for (String key: jobExtras.keySet()) {
278             if (key!= null && key.startsWith("ACCOUNT:")) {
279                 String newKey = key.substring(8); // Strip off the 'ACCOUNT:' prefix.
280                 PersistableBundle accountsBundle = jobExtras.getPersistableBundle(key);
281                 Account account = new Account(accountsBundle.getString("accountName"),
282                         accountsBundle.getString("accountType"));
283                 extras.putParcelable(newKey, account);
284             }
285         }
286 
287         Account account = new Account(accountName, accountType);
288         SyncStorageEngine.EndPoint target =
289                 new SyncStorageEngine.EndPoint(account, provider, userId);
290         SyncOperation op = new SyncOperation(target, owningUid, owningPackage, reason, source,
291                 extras, allowParallelSyncs, isPeriodic, initiatedBy, periodMillis, flexMillis);
292         op.jobId = jobExtras.getInt("jobId");
293         op.expectedRuntime = jobExtras.getLong("expectedRuntime");
294         op.retries = jobExtras.getInt("retries");
295         return op;
296     }
297 
298     /**
299      * Determine whether if this sync operation is running, the provided operation would conflict
300      * with it.
301      * Parallel syncs allow multiple accounts to be synced at the same time.
302      */
isConflict(SyncOperation toRun)303     boolean isConflict(SyncOperation toRun) {
304         final SyncStorageEngine.EndPoint other = toRun.target;
305         return target.account.type.equals(other.account.type)
306                 && target.provider.equals(other.provider)
307                 && target.userId == other.userId
308                 && (!allowParallelSyncs
309                 || target.account.name.equals(other.account.name));
310     }
311 
isReasonPeriodic()312     boolean isReasonPeriodic() {
313         return reason == REASON_PERIODIC;
314     }
315 
matchesPeriodicOperation(SyncOperation other)316     boolean matchesPeriodicOperation(SyncOperation other) {
317         return target.matchesSpec(other.target)
318                 && SyncManager.syncExtrasEquals(extras, other.extras, true)
319                 && periodMillis == other.periodMillis && flexMillis == other.flexMillis;
320     }
321 
isDerivedFromFailedPeriodicSync()322     boolean isDerivedFromFailedPeriodicSync() {
323         return sourcePeriodicId != NO_JOB_ID;
324     }
325 
findPriority()326     int findPriority() {
327         if (isInitialization()) {
328             return JobInfo.PRIORITY_SYNC_INITIALIZATION;
329         } else if (isExpedited()) {
330             return JobInfo.PRIORITY_SYNC_EXPEDITED;
331         }
332         return JobInfo.PRIORITY_DEFAULT;
333     }
334 
toKey()335     private String toKey() {
336         StringBuilder sb = new StringBuilder();
337         sb.append("provider: ").append(target.provider);
338         sb.append(" account {name=" + target.account.name
339                 + ", user="
340                 + target.userId
341                 + ", type="
342                 + target.account.type
343                 + "}");
344         sb.append(" isPeriodic: ").append(isPeriodic);
345         sb.append(" period: ").append(periodMillis);
346         sb.append(" flex: ").append(flexMillis);
347         sb.append(" extras: ");
348         extrasToStringBuilder(extras, sb);
349         return sb.toString();
350     }
351 
352     @Override
toString()353     public String toString() {
354         return dump(null, true);
355     }
356 
dump(PackageManager pm, boolean shorter)357     String dump(PackageManager pm, boolean shorter) {
358         StringBuilder sb = new StringBuilder();
359         sb.append("JobId=").append(jobId)
360                 .append(" ")
361                 .append(target.account.name)
362                 .append("/")
363                 .append(target.account.type)
364                 .append(" u")
365                 .append(target.userId)
366                 .append(" [")
367                 .append(target.provider)
368                 .append("] ");
369         sb.append(SyncStorageEngine.SOURCES[syncSource]);
370         if (expectedRuntime != 0) {
371             sb.append(" ExpectedIn=");
372             SyncManager.formatDurationHMS(sb,
373                     (expectedRuntime - SystemClock.elapsedRealtime()));
374         }
375         if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) {
376             sb.append(" EXPEDITED");
377         }
378         sb.append(" Reason=");
379         sb.append(reasonToString(pm, reason));
380         if (isPeriodic) {
381             sb.append(" (period=");
382             SyncManager.formatDurationHMS(sb, periodMillis);
383             sb.append(" flex=");
384             SyncManager.formatDurationHMS(sb, flexMillis);
385             sb.append(")");
386         }
387         if (!shorter) {
388             sb.append(" Owner={");
389             UserHandle.formatUid(sb, owningUid);
390             sb.append(" ");
391             sb.append(owningPackage);
392             sb.append("}");
393             if (!extras.keySet().isEmpty()) {
394                 sb.append(" ");
395                 extrasToStringBuilder(extras, sb);
396             }
397         }
398         return sb.toString();
399     }
400 
reasonToString(PackageManager pm, int reason)401     static String reasonToString(PackageManager pm, int reason) {
402         if (reason >= 0) {
403             if (pm != null) {
404                 final String[] packages = pm.getPackagesForUid(reason);
405                 if (packages != null && packages.length == 1) {
406                     return packages[0];
407                 }
408                 final String name = pm.getNameForUid(reason);
409                 if (name != null) {
410                     return name;
411                 }
412                 return String.valueOf(reason);
413             } else {
414                 return String.valueOf(reason);
415             }
416         } else {
417             final int index = -reason - 1;
418             if (index >= REASON_NAMES.length) {
419                 return String.valueOf(reason);
420             } else {
421                 return REASON_NAMES[index];
422             }
423         }
424     }
425 
isInitialization()426     boolean isInitialization() {
427         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
428     }
429 
isExpedited()430     boolean isExpedited() {
431         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
432     }
433 
ignoreBackoff()434     boolean ignoreBackoff() {
435         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
436     }
437 
isNotAllowedOnMetered()438     boolean isNotAllowedOnMetered() {
439         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, false);
440     }
441 
isManual()442     boolean isManual() {
443         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
444     }
445 
isIgnoreSettings()446     boolean isIgnoreSettings() {
447         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
448     }
449 
extrasToStringBuilder(Bundle bundle, StringBuilder sb)450     static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
451         if (bundle == null) {
452             sb.append("null");
453             return;
454         }
455         sb.append("[");
456         for (String key : bundle.keySet()) {
457             sb.append(key).append("=").append(bundle.get(key)).append(" ");
458         }
459         sb.append("]");
460     }
461 
extrasToString(Bundle bundle)462     static String extrasToString(Bundle bundle) {
463         final StringBuilder sb = new StringBuilder();
464         extrasToStringBuilder(bundle, sb);
465         return sb.toString();
466     }
467 
wakeLockName()468     String wakeLockName() {
469         if (wakeLockName != null) {
470             return wakeLockName;
471         }
472         return (wakeLockName = target.provider
473                 + "/" + target.account.type
474                 + "/" + target.account.name);
475     }
476 
477     // TODO: Test this to make sure that casting to object doesn't lose the type info for EventLog.
toEventLog(int event)478     public Object[] toEventLog(int event) {
479         Object[] logArray = new Object[4];
480         logArray[1] = event;
481         logArray[2] = syncSource;
482         logArray[0] = target.provider;
483         logArray[3] = target.account.name.hashCode();
484         return logArray;
485     }
486 }
487