SyncQueue.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.content.pm.PackageManager;
20import android.content.SyncAdapterType;
21import android.content.SyncAdaptersCache;
22import android.content.pm.RegisteredServicesCache.ServiceInfo;
23import android.os.Bundle;
24import android.os.SystemClock;
25import android.text.format.DateUtils;
26import android.util.Log;
27import android.util.Pair;
28
29import com.google.android.collect.Maps;
30
31import java.util.ArrayList;
32import java.util.Collection;
33import java.util.HashMap;
34import java.util.Iterator;
35import java.util.Map;
36
37/**
38 * Queue of pending sync operations. Not inherently thread safe, external
39 * callers are responsible for locking.
40 *
41 * @hide
42 */
43public class SyncQueue {
44    private static final String TAG = "SyncManager";
45    private final SyncStorageEngine mSyncStorageEngine;
46    private final SyncAdaptersCache mSyncAdapters;
47    private final PackageManager mPackageManager;
48
49    // A Map of SyncOperations operationKey -> SyncOperation that is designed for
50    // quick lookup of an enqueued SyncOperation.
51    private final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap();
52
53    public SyncQueue(PackageManager packageManager, SyncStorageEngine syncStorageEngine,
54            final SyncAdaptersCache syncAdapters) {
55        mPackageManager = packageManager;
56        mSyncStorageEngine = syncStorageEngine;
57        mSyncAdapters = syncAdapters;
58    }
59
60    public void addPendingOperations(int userId) {
61        for (SyncStorageEngine.PendingOperation op : mSyncStorageEngine.getPendingOperations()) {
62            final SyncStorageEngine.EndPoint info = op.target;
63            if (info.userId != userId) continue;
64
65            final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(info);
66            SyncOperation operationToAdd;
67            if (info.target_provider) {
68                final ServiceInfo<SyncAdapterType> syncAdapterInfo = mSyncAdapters.getServiceInfo(
69                        SyncAdapterType.newKey(info.provider, info.account.type), info.userId);
70                if (syncAdapterInfo == null) {
71                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
72                        Log.v(TAG, "Missing sync adapter info for authority " + op.target);
73                    }
74                    continue;
75                }
76                operationToAdd = new SyncOperation(
77                        info.account, info.userId, op.reason, op.syncSource, info.provider,
78                        op.extras,
79                        op.expedited ? -1 : 0 /* delay */,
80                        0 /* flex */,
81                        backoff != null ? backoff.first : 0L,
82                        mSyncStorageEngine.getDelayUntilTime(info),
83                        syncAdapterInfo.type.allowParallelSyncs());
84                operationToAdd.pendingOperation = op;
85                add(operationToAdd, op);
86            } else if (info.target_service) {
87                try {
88                    mPackageManager.getServiceInfo(info.service, 0);
89                } catch (PackageManager.NameNotFoundException e) {
90                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
91                        Log.w(TAG, "Missing sync service for authority " + op.target);
92                    }
93                    continue;
94                }
95                operationToAdd = new SyncOperation(
96                        info.service, info.userId, op.reason, op.syncSource,
97                        op.extras,
98                        op.expedited ? -1 : 0 /* delay */,
99                        0 /* flex */,
100                        backoff != null ? backoff.first : 0,
101                        mSyncStorageEngine.getDelayUntilTime(info));
102                operationToAdd.pendingOperation = op;
103                add(operationToAdd, op);
104            }
105        }
106    }
107
108    public boolean add(SyncOperation operation) {
109        return add(operation, null /* this is not coming from the database */);
110    }
111
112    /**
113     * Adds a SyncOperation to the queue and creates a PendingOperation object to track that sync.
114     * If an operation is added that already exists, the existing operation is updated if the newly
115     * added operation occurs before (or the interval overlaps).
116     */
117    private boolean add(SyncOperation operation,
118            SyncStorageEngine.PendingOperation pop) {
119        // If an operation with the same key exists and this one should run sooner/overlaps,
120        // replace the run interval of the existing operation with this new one.
121        // Complications: what if the existing operation is expedited but the new operation has an
122        // earlier run time? Will not be a problem for periodic syncs (no expedited flag), and for
123        // one-off syncs we only change it if the new sync is sooner.
124        final String operationKey = operation.key;
125        final SyncOperation existingOperation = mOperationsMap.get(operationKey);
126
127        if (existingOperation != null) {
128            boolean changed = false;
129            if (operation.compareTo(existingOperation) <= 0 ) {
130                long newRunTime =
131                        Math.min(existingOperation.latestRunTime, operation.latestRunTime);
132                // Take smaller runtime.
133                existingOperation.latestRunTime = newRunTime;
134                // Take newer flextime.
135                existingOperation.flexTime = operation.flexTime;
136                changed = true;
137            }
138            return changed;
139        }
140
141        operation.pendingOperation = pop;
142        // Don't update the PendingOp if one already exists. This really is just a placeholder,
143        // no actual scheduling info is placed here.
144        if (operation.pendingOperation == null) {
145            pop = mSyncStorageEngine.insertIntoPending(operation);
146            if (pop == null) {
147                throw new IllegalStateException("error adding pending sync operation "
148                        + operation);
149            }
150            operation.pendingOperation = pop;
151        }
152
153        mOperationsMap.put(operationKey, operation);
154        return true;
155    }
156
157    public void removeUserLocked(int userId) {
158        ArrayList<SyncOperation> opsToRemove = new ArrayList<SyncOperation>();
159        for (SyncOperation op : mOperationsMap.values()) {
160            if (op.target.userId == userId) {
161                opsToRemove.add(op);
162            }
163        }
164            for (SyncOperation op : opsToRemove) {
165                remove(op);
166            }
167    }
168
169    /**
170     * Remove the specified operation if it is in the queue.
171     * @param operation the operation to remove
172     */
173    public void remove(SyncOperation operation) {
174        boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
175        SyncOperation operationToRemove = mOperationsMap.remove(operation.key);
176        if (isLoggable) {
177            Log.v(TAG, "Attempting to remove: " + operation.key);
178        }
179        if (operationToRemove == null) {
180            if (isLoggable) {
181                Log.v(TAG, "Could not find: " + operation.key);
182            }
183            return;
184        }
185        if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) {
186            final String errorMessage = "unable to find pending row for " + operationToRemove;
187            Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
188        }
189    }
190
191    /** Reset backoffs for all operations in the queue. */
192    public void clearBackoffs() {
193        for (SyncOperation op : mOperationsMap.values()) {
194            op.backoff = 0L;
195            op.updateEffectiveRunTime();
196        }
197    }
198
199    public void onBackoffChanged(SyncStorageEngine.EndPoint target, long backoff) {
200        // For each op that matches the target of the changed op, update its
201        // backoff and effectiveStartTime
202        for (SyncOperation op : mOperationsMap.values()) {
203            if (op.target.matchesSpec(target)) {
204                op.backoff = backoff;
205                op.updateEffectiveRunTime();
206            }
207        }
208    }
209
210    public void onDelayUntilTimeChanged(SyncStorageEngine.EndPoint target, long delayUntil) {
211        // for each op that matches the target info of the provided op, change the delay time.
212        for (SyncOperation op : mOperationsMap.values()) {
213            if (op.target.matchesSpec(target)) {
214                op.delayUntil = delayUntil;
215                op.updateEffectiveRunTime();
216            }
217        }
218    }
219
220    /**
221     * Remove all of the SyncOperations associated with a given target.
222     *
223     * @param info target object provided here can have null Account/provider. This is the case
224     * where you want to remove all ops associated with a provider (null Account) or all ops
225     * associated with an account (null provider).
226     * @param extras option bundle to include to further specify which operation to remove. If this
227     * bundle contains sync settings flags, they are ignored.
228     */
229    public void remove(final SyncStorageEngine.EndPoint info, Bundle extras) {
230        Iterator<Map.Entry<String, SyncOperation>> entries = mOperationsMap.entrySet().iterator();
231        while (entries.hasNext()) {
232            Map.Entry<String, SyncOperation> entry = entries.next();
233            SyncOperation syncOperation = entry.getValue();
234            final SyncStorageEngine.EndPoint opInfo = syncOperation.target;
235            if (!opInfo.matchesSpec(info)) {
236                continue;
237            }
238            if (extras != null
239                    && !SyncManager.syncExtrasEquals(
240                        syncOperation.extras,
241                        extras,
242                        false /* no config flags*/)) {
243                continue;
244            }
245            entries.remove();
246            if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) {
247                final String errorMessage = "unable to find pending row for " + syncOperation;
248                Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
249            }
250        }
251    }
252
253    public Collection<SyncOperation> getOperations() {
254        return mOperationsMap.values();
255    }
256
257    public void dump(StringBuilder sb) {
258        final long now = SystemClock.elapsedRealtime();
259        sb.append("SyncQueue: ").append(mOperationsMap.size()).append(" operation(s)\n");
260        for (SyncOperation operation : mOperationsMap.values()) {
261            sb.append("  ");
262            if (operation.effectiveRunTime <= now) {
263                sb.append("READY");
264            } else {
265                sb.append(DateUtils.formatElapsedTime((operation.effectiveRunTime - now) / 1000));
266            }
267            sb.append(" - ");
268            sb.append(operation.dump(mPackageManager, false)).append("\n");
269        }
270    }
271}
272