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