SyncOperation.java revision 9158825f9c41869689d6b1786d7c7aa8bdd524ce
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.content.SyncRequest;
24import android.os.Bundle;
25import android.os.SystemClock;
26import android.util.Pair;
27
28/**
29 * Value type that represents a sync operation.
30 * TODO: This is the class to flesh out with all the scheduling data - metered/unmetered,
31 * transfer-size, etc.
32 * {@hide}
33 */
34public class SyncOperation implements Comparable {
35    public static final int REASON_BACKGROUND_DATA_SETTINGS_CHANGED = -1;
36    public static final int REASON_ACCOUNTS_UPDATED = -2;
37    public static final int REASON_SERVICE_CHANGED = -3;
38    public static final int REASON_PERIODIC = -4;
39    public static final int REASON_IS_SYNCABLE = -5;
40    /** Sync started because it has just been set to sync automatically. */
41    public static final int REASON_SYNC_AUTO = -6;
42    /** Sync started because master sync automatically has been set to true. */
43    public static final int REASON_MASTER_SYNC_AUTO = -7;
44    public static final int REASON_USER_START = -8;
45
46    private static String[] REASON_NAMES = new String[] {
47            "DataSettingsChanged",
48            "AccountsUpdated",
49            "ServiceChanged",
50            "Periodic",
51            "IsSyncable",
52            "AutoSync",
53            "MasterSyncAuto",
54            "UserStart",
55    };
56
57    /** Account info to identify a SyncAdapter registered with the system. */
58    public final Account account;
59    /** Authority info to identify a SyncAdapter registered with the system. */
60    public final String authority;
61    /** Service to which this operation will bind to perform the sync. */
62    public final ComponentName service;
63    public final int userId;
64    public final int reason;
65    public int syncSource;
66    public final boolean allowParallelSyncs;
67    public Bundle extras;
68    public final String key;
69    public boolean expedited;
70    public SyncStorageEngine.PendingOperation pendingOperation;
71    /** Elapsed real time in millis at which to run this sync. */
72    public long latestRunTime;
73    /** Set by the SyncManager in order to delay retries. */
74    public Long backoff;
75    /** Specified by the adapter to delay subsequent sync operations. */
76    public long delayUntil;
77    /**
78     * Elapsed real time in millis when this sync will be run.
79     * Depends on max(backoff, latestRunTime, and delayUntil).
80     */
81    public long effectiveRunTime;
82    /** Amount of time before {@link effectiveRunTime} from which this sync can run. */
83    public long flexTime;
84
85    public SyncOperation(Account account, int userId, int reason, int source, String authority,
86            Bundle extras, long runTimeFromNow, long flexTime, long backoff,
87            long delayUntil, boolean allowParallelSyncs) {
88        this.service = null;
89        this.account = account;
90        this.authority = authority;
91        this.userId = userId;
92        this.reason = reason;
93        this.syncSource = source;
94        this.allowParallelSyncs = allowParallelSyncs;
95        this.extras = new Bundle(extras);
96        cleanBundle(this.extras);
97        this.delayUntil = delayUntil;
98        this.backoff = backoff;
99        final long now = SystemClock.elapsedRealtime();
100        // Checks the extras bundle. Must occur after we set the internal bundle.
101        if (runTimeFromNow < 0 || isExpedited()) {
102            this.expedited = true;
103            this.latestRunTime = now;
104            this.flexTime = 0;
105        } else {
106            this.expedited = false;
107            this.latestRunTime = now + runTimeFromNow;
108            this.flexTime = flexTime;
109        }
110        updateEffectiveRunTime();
111        this.key = toKey();
112    }
113
114    /**
115     * Make sure the bundle attached to this SyncOperation doesn't have unnecessary
116     * flags set.
117     * @param bundle to clean.
118     */
119    private void cleanBundle(Bundle bundle) {
120        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_UPLOAD);
121        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_MANUAL);
122        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS);
123        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
124        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY);
125        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS);
126        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_EXPEDITED);
127        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS);
128        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DISALLOW_METERED);
129
130        // Remove Config data.
131        bundle.remove(ContentResolver.SYNC_EXTRAS_EXPECTED_UPLOAD);
132        bundle.remove(ContentResolver.SYNC_EXTRAS_EXPECTED_DOWNLOAD);
133    }
134
135    private void removeFalseExtra(Bundle bundle, String extraName) {
136        if (!bundle.getBoolean(extraName, false)) {
137            bundle.remove(extraName);
138        }
139    }
140
141    /** Only used to immediately reschedule a sync. */
142    SyncOperation(SyncOperation other) {
143        this.service = other.service;
144        this.account = other.account;
145        this.authority = other.authority;
146        this.userId = other.userId;
147        this.reason = other.reason;
148        this.syncSource = other.syncSource;
149        this.extras = new Bundle(other.extras);
150        this.expedited = other.expedited;
151        this.latestRunTime = SystemClock.elapsedRealtime();
152        this.flexTime = 0L;
153        this.backoff = other.backoff;
154        this.allowParallelSyncs = other.allowParallelSyncs;
155        this.updateEffectiveRunTime();
156        this.key = toKey();
157    }
158
159    @Override
160    public String toString() {
161        return dump(null, true);
162    }
163
164    public String dump(PackageManager pm, boolean useOneLine) {
165        StringBuilder sb = new StringBuilder()
166                .append(account.name)
167                .append(" u")
168                .append(userId).append(" (")
169                .append(account.type)
170                .append(")")
171                .append(", ")
172                .append(authority)
173                .append(", ")
174                .append(SyncStorageEngine.SOURCES[syncSource])
175                .append(", latestRunTime ")
176                .append(latestRunTime);
177        if (expedited) {
178            sb.append(", EXPEDITED");
179        }
180        sb.append(", reason: ");
181        sb.append(reasonToString(pm, reason));
182        if (!useOneLine && !extras.keySet().isEmpty()) {
183            sb.append("\n    ");
184            extrasToStringBuilder(extras, sb);
185        }
186        return sb.toString();
187    }
188
189    public static String reasonToString(PackageManager pm, int reason) {
190        if (reason >= 0) {
191            if (pm != null) {
192                final String[] packages = pm.getPackagesForUid(reason);
193                if (packages != null && packages.length == 1) {
194                    return packages[0];
195                }
196                final String name = pm.getNameForUid(reason);
197                if (name != null) {
198                    return name;
199                }
200                return String.valueOf(reason);
201            } else {
202                return String.valueOf(reason);
203            }
204        } else {
205            final int index = -reason - 1;
206            if (index >= REASON_NAMES.length) {
207                return String.valueOf(reason);
208            } else {
209                return REASON_NAMES[index];
210            }
211        }
212    }
213
214    public boolean isMeteredDisallowed() {
215        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, false);
216    }
217
218    public boolean isInitialization() {
219        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
220    }
221
222    public boolean isExpedited() {
223        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false) || expedited;
224    }
225
226    public boolean ignoreBackoff() {
227        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
228    }
229
230    /** Changed in V3. */
231    private String toKey() {
232        StringBuilder sb = new StringBuilder();
233        if (service == null) {
234            sb.append("authority: ").append(authority);
235            sb.append(" account {name=" + account.name + ", user=" + userId + ", type=" + account.type
236                    + "}");
237        } else {
238            sb.append("service {package=" )
239                .append(service.getPackageName())
240                .append(" user=")
241                .append(userId)
242                .append(", class=")
243                .append(service.getClassName())
244                .append("}");
245        }
246        sb.append(" extras: ");
247        extrasToStringBuilder(extras, sb);
248        return sb.toString();
249    }
250
251    public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
252        sb.append("[");
253        for (String key : bundle.keySet()) {
254            sb.append(key).append("=").append(bundle.get(key)).append(" ");
255        }
256        sb.append("]");
257    }
258
259    /**
260     * Update the effective run time of this Operation based on latestRunTime (specified at
261     * creation time of sync), delayUntil (specified by SyncAdapter), or backoff (specified by
262     * SyncManager on soft failures).
263     */
264    public void updateEffectiveRunTime() {
265        // Regardless of whether we're in backoff or honouring a delayUntil, we still incorporate
266        // the flex time provided by the developer.
267        effectiveRunTime = ignoreBackoff() ?
268                latestRunTime :
269                    Math.max(Math.max(latestRunTime, delayUntil), backoff);
270    }
271
272    /**
273     * SyncOperations are sorted based on their earliest effective run time.
274     * This comparator is used to sort the SyncOps at a given time when
275     * deciding which to run, so earliest run time is the best criteria.
276     */
277    @Override
278    public int compareTo(Object o) {
279        SyncOperation other = (SyncOperation) o;
280        if (expedited != other.expedited) {
281            return expedited ? -1 : 1;
282        }
283        long thisIntervalStart = Math.max(effectiveRunTime - flexTime, 0);
284        long otherIntervalStart = Math.max(
285            other.effectiveRunTime - other.flexTime, 0);
286        if (thisIntervalStart < otherIntervalStart) {
287            return -1;
288        } else if (otherIntervalStart < thisIntervalStart) {
289            return 1;
290        } else {
291            return 0;
292        }
293    }
294}
295