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.content.pm.PackageManager;
21 import android.content.ComponentName;
22 import android.content.ContentResolver;
23 import android.os.Bundle;
24 import android.os.SystemClock;
25 import android.util.Log;
26 
27 /**
28  * Value type that represents a sync operation.
29  * TODO: This is the class to flesh out with all the scheduling data - metered/unmetered,
30  * transfer-size, etc.
31  * {@hide}
32  */
33 public class SyncOperation implements Comparable {
34     public static final String TAG = "SyncManager";
35 
36     public static final int REASON_BACKGROUND_DATA_SETTINGS_CHANGED = -1;
37     public static final int REASON_ACCOUNTS_UPDATED = -2;
38     public static final int REASON_SERVICE_CHANGED = -3;
39     public static final int REASON_PERIODIC = -4;
40     /** Sync started because it has just been set to isSyncable. */
41     public static final int REASON_IS_SYNCABLE = -5;
42     /** Sync started because it has just been set to sync automatically. */
43     public static final int REASON_SYNC_AUTO = -6;
44     /** Sync started because master sync automatically has been set to true. */
45     public static final int REASON_MASTER_SYNC_AUTO = -7;
46     public static final int REASON_USER_START = -8;
47 
48     private static String[] REASON_NAMES = new String[] {
49             "DataSettingsChanged",
50             "AccountsUpdated",
51             "ServiceChanged",
52             "Periodic",
53             "IsSyncable",
54             "AutoSync",
55             "MasterSyncAuto",
56             "UserStart",
57     };
58 
59     public static final int SYNC_TARGET_UNKNOWN = 0;
60     public static final int SYNC_TARGET_ADAPTER = 1;
61     public static final int SYNC_TARGET_SERVICE = 2;
62 
63     /** Identifying info for the target for this operation. */
64     public final SyncStorageEngine.EndPoint target;
65     /** Why this sync was kicked off. {@link #REASON_NAMES} */
66     public final int reason;
67     /** Where this sync was initiated. */
68     public final int syncSource;
69     public final boolean allowParallelSyncs;
70     public final String key;
71     /** Internal boolean to avoid reading a bundle everytime we want to compare operations. */
72     private final boolean expedited;
73     public Bundle extras;
74     /** Bare-bones version of this operation that is persisted across reboots. */
75     public SyncStorageEngine.PendingOperation pendingOperation;
76     /** Elapsed real time in millis at which to run this sync. */
77     public long latestRunTime;
78     /** Set by the SyncManager in order to delay retries. */
79     public long backoff;
80     /** Specified by the adapter to delay subsequent sync operations. */
81     public long delayUntil;
82     /**
83      * Elapsed real time in millis when this sync will be run.
84      * Depends on max(backoff, latestRunTime, and delayUntil).
85      */
86     public long effectiveRunTime;
87     /** Amount of time before {@link #effectiveRunTime} from which this sync can run. */
88     public long flexTime;
89 
90     /** Descriptive string key for this operation */
91     public String wakeLockName;
92 
93     /** Whether this sync op was recently skipped due to the app being idle */
94     public boolean appIdle;
95 
SyncOperation(Account account, int userId, int reason, int source, String provider, Bundle extras, long runTimeFromNow, long flexTime, long backoff, long delayUntil, boolean allowParallelSyncs)96     public SyncOperation(Account account, int userId, int reason, int source, String provider,
97             Bundle extras, long runTimeFromNow, long flexTime, long backoff,
98             long delayUntil, boolean allowParallelSyncs) {
99         this(new SyncStorageEngine.EndPoint(account, provider, userId),
100                 reason, source, extras, runTimeFromNow, flexTime, backoff, delayUntil,
101                 allowParallelSyncs);
102     }
103 
SyncOperation(ComponentName service, int userId, int reason, int source, Bundle extras, long runTimeFromNow, long flexTime, long backoff, long delayUntil)104     public SyncOperation(ComponentName service, int userId, int reason, int source,
105             Bundle extras, long runTimeFromNow, long flexTime, long backoff,
106             long delayUntil) {
107         this(new SyncStorageEngine.EndPoint(service, userId), reason, source, extras,
108                 runTimeFromNow, flexTime, backoff, delayUntil, true /* allowParallelSyncs */);
109     }
110 
SyncOperation(SyncStorageEngine.EndPoint info, int reason, int source, Bundle extras, long runTimeFromNow, long flexTime, long backoff, long delayUntil, boolean allowParallelSyncs)111     private SyncOperation(SyncStorageEngine.EndPoint info, int reason, int source, Bundle extras,
112             long runTimeFromNow, long flexTime, long backoff, long delayUntil,
113             boolean allowParallelSyncs) {
114         this.target = info;
115         this.reason = reason;
116         this.syncSource = source;
117         this.extras = new Bundle(extras);
118         cleanBundle(this.extras);
119         this.delayUntil = delayUntil;
120         this.backoff = backoff;
121         this.allowParallelSyncs = allowParallelSyncs;
122         final long now = SystemClock.elapsedRealtime();
123         // Set expedited based on runTimeFromNow. The SyncManager specifies whether the op is
124         // expedited (Not done solely based on bundle).
125         if (runTimeFromNow < 0) {
126             this.expedited = true;
127             // Sanity check: Will always be true.
128             if (!this.extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) {
129                 this.extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
130             }
131             this.latestRunTime = now;
132             this.flexTime = 0;
133         } else {
134             this.expedited = false;
135             this.extras.remove(ContentResolver.SYNC_EXTRAS_EXPEDITED);
136             this.latestRunTime = now + runTimeFromNow;
137             this.flexTime = flexTime;
138         }
139         updateEffectiveRunTime();
140         this.key = toKey(info, this.extras);
141     }
142 
143     /** Used to reschedule a sync at a new point in time. */
SyncOperation(SyncOperation other, long newRunTimeFromNow)144     public SyncOperation(SyncOperation other, long newRunTimeFromNow) {
145         this(other.target, other.reason, other.syncSource, new Bundle(other.extras),
146                 newRunTimeFromNow,
147                 0L /* In back-off so no flex */,
148                 other.backoff,
149                 other.delayUntil,
150                 other.allowParallelSyncs);
151     }
152 
matchesAuthority(SyncOperation other)153     public boolean matchesAuthority(SyncOperation other) {
154         return this.target.matchesSpec(other.target);
155     }
156 
157     /**
158      * Make sure the bundle attached to this SyncOperation doesn't have unnecessary
159      * flags set.
160      * @param bundle to clean.
161      */
cleanBundle(Bundle bundle)162     private void cleanBundle(Bundle bundle) {
163         removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_UPLOAD);
164         removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_MANUAL);
165         removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS);
166         removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
167         removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY);
168         removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS);
169         removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_EXPEDITED);
170         removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS);
171         removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DISALLOW_METERED);
172     }
173 
removeFalseExtra(Bundle bundle, String extraName)174     private void removeFalseExtra(Bundle bundle, String extraName) {
175         if (!bundle.getBoolean(extraName, false)) {
176             bundle.remove(extraName);
177         }
178     }
179 
180     /**
181      * Determine whether if this sync operation is running, the provided operation would conflict
182      * with it.
183      * Parallel syncs allow multiple accounts to be synced at the same time.
184      */
isConflict(SyncOperation toRun)185     public boolean isConflict(SyncOperation toRun) {
186         final SyncStorageEngine.EndPoint other = toRun.target;
187         if (target.target_provider) {
188             return target.account.type.equals(other.account.type)
189                     && target.provider.equals(other.provider)
190                     && target.userId == other.userId
191                     && (!allowParallelSyncs
192                             || target.account.name.equals(other.account.name));
193         } else {
194             // Ops that target a service default to allow parallel syncs, which is handled by the
195             // service returning SYNC_IN_PROGRESS if they don't.
196             return target.service.equals(other.service) && !allowParallelSyncs;
197         }
198     }
199 
200     @Override
toString()201     public String toString() {
202         return dump(null, true);
203     }
204 
dump(PackageManager pm, boolean useOneLine)205     public String dump(PackageManager pm, boolean useOneLine) {
206         StringBuilder sb = new StringBuilder();
207         if (target.target_provider) {
208             sb.append(target.account.name)
209                 .append(" u")
210                 .append(target.userId).append(" (")
211                 .append(target.account.type)
212                 .append(")")
213                 .append(", ")
214                 .append(target.provider)
215                 .append(", ");
216         } else if (target.target_service) {
217             sb.append(target.service.getPackageName())
218                 .append(" u")
219                 .append(target.userId).append(" (")
220                 .append(target.service.getClassName()).append(")")
221                 .append(", ");
222         }
223         sb.append(SyncStorageEngine.SOURCES[syncSource])
224             .append(", currentRunTime ")
225             .append(effectiveRunTime);
226         if (expedited) {
227             sb.append(", EXPEDITED");
228         }
229         sb.append(", reason: ");
230         sb.append(reasonToString(pm, reason));
231         if (!useOneLine && !extras.keySet().isEmpty()) {
232             sb.append("\n    ");
233             extrasToStringBuilder(extras, sb);
234         }
235         return sb.toString();
236     }
237 
reasonToString(PackageManager pm, int reason)238     public static String reasonToString(PackageManager pm, int reason) {
239         if (reason >= 0) {
240             if (pm != null) {
241                 final String[] packages = pm.getPackagesForUid(reason);
242                 if (packages != null && packages.length == 1) {
243                     return packages[0];
244                 }
245                 final String name = pm.getNameForUid(reason);
246                 if (name != null) {
247                     return name;
248                 }
249                 return String.valueOf(reason);
250             } else {
251                 return String.valueOf(reason);
252             }
253         } else {
254             final int index = -reason - 1;
255             if (index >= REASON_NAMES.length) {
256                 return String.valueOf(reason);
257             } else {
258                 return REASON_NAMES[index];
259             }
260         }
261     }
262 
isInitialization()263     public boolean isInitialization() {
264         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
265     }
266 
isExpedited()267     public boolean isExpedited() {
268         return expedited;
269     }
270 
ignoreBackoff()271     public boolean ignoreBackoff() {
272         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
273     }
274 
isNotAllowedOnMetered()275     public boolean isNotAllowedOnMetered() {
276         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, false);
277     }
278 
isManual()279     public boolean isManual() {
280         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
281     }
282 
isIgnoreSettings()283     public boolean isIgnoreSettings() {
284         return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
285     }
286 
287     /** Changed in V3. */
toKey(SyncStorageEngine.EndPoint info, Bundle extras)288     public static String toKey(SyncStorageEngine.EndPoint info, Bundle extras) {
289         StringBuilder sb = new StringBuilder();
290         if (info.target_provider) {
291             sb.append("provider: ").append(info.provider);
292             sb.append(" account {name=" + info.account.name
293                     + ", user="
294                     + info.userId
295                     + ", type="
296                     + info.account.type
297                     + "}");
298         } else if (info.target_service) {
299             sb.append("service {package=" )
300                 .append(info.service.getPackageName())
301                 .append(" user=")
302                 .append(info.userId)
303                 .append(", class=")
304                 .append(info.service.getClassName())
305                 .append("}");
306         } else {
307             Log.v(TAG, "Converting SyncOperaton to key, invalid target: " + info.toString());
308             return "";
309         }
310         sb.append(" extras: ");
311         extrasToStringBuilder(extras, sb);
312         return sb.toString();
313     }
314 
extrasToStringBuilder(Bundle bundle, StringBuilder sb)315     private static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
316         sb.append("[");
317         for (String key : bundle.keySet()) {
318             sb.append(key).append("=").append(bundle.get(key)).append(" ");
319         }
320         sb.append("]");
321     }
322 
wakeLockName()323     public String wakeLockName() {
324         if (wakeLockName != null) {
325             return wakeLockName;
326         }
327         if (target.target_provider) {
328             return (wakeLockName = target.provider
329                     + "/" + target.account.type
330                     + "/" + target.account.name);
331         } else if (target.target_service) {
332             return (wakeLockName = target.service.getPackageName()
333                     + "/" + target.service.getClassName());
334         } else {
335             Log.wtf(TAG, "Invalid target getting wakelock name for operation - " + key);
336             return null;
337         }
338     }
339 
340     /**
341      * Update the effective run time of this Operation based on latestRunTime (specified at
342      * creation time of sync), delayUntil (specified by SyncAdapter), or backoff (specified by
343      * SyncManager on soft failures).
344      */
updateEffectiveRunTime()345     public void updateEffectiveRunTime() {
346         // Regardless of whether we're in backoff or honouring a delayUntil, we still incorporate
347         // the flex time provided by the developer.
348         effectiveRunTime = ignoreBackoff() ?
349                 latestRunTime :
350                     Math.max(Math.max(latestRunTime, delayUntil), backoff);
351     }
352 
353     /**
354      * SyncOperations are sorted based on their earliest effective run time.
355      * This comparator is used to sort the SyncOps at a given time when
356      * deciding which to run, so earliest run time is the best criteria.
357      */
358     @Override
compareTo(Object o)359     public int compareTo(Object o) {
360         SyncOperation other = (SyncOperation) o;
361         if (expedited != other.expedited) {
362             return expedited ? -1 : 1;
363         }
364         long thisIntervalStart = Math.max(effectiveRunTime - flexTime, 0);
365         long otherIntervalStart = Math.max(
366             other.effectiveRunTime - other.flexTime, 0);
367         if (thisIntervalStart < otherIntervalStart) {
368             return -1;
369         } else if (otherIntervalStart < thisIntervalStart) {
370             return 1;
371         } else {
372             return 0;
373         }
374     }
375 
376     // TODO: Test this to make sure that casting to object doesn't lose the type info for EventLog.
toEventLog(int event)377     public Object[] toEventLog(int event) {
378         Object[] logArray = new Object[4];
379         logArray[1] = event;
380         logArray[2] = syncSource;
381         if (target.target_provider) {
382             logArray[0] = target.provider;
383             logArray[3] = target.account.name.hashCode();
384         } else if (target.target_service) {
385             logArray[0] = target.service.getPackageName();
386             logArray[3] = target.service.hashCode();
387         } else {
388             Log.wtf(TAG, "sync op with invalid target: " + key);
389         }
390         return logArray;
391     }
392 }
393