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