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