SyncQueue.java revision a706e2fd0059b1bb86c487722dbc9fc0fda9c980
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 android.content;
18
19import android.accounts.Account;
20import android.content.pm.RegisteredServicesCache.ServiceInfo;
21import android.os.SystemClock;
22import android.os.UserHandle;
23import android.text.format.DateUtils;
24import android.util.Log;
25import android.util.Pair;
26
27import com.google.android.collect.Maps;
28
29import java.util.ArrayList;
30import java.util.Collection;
31import java.util.HashMap;
32import java.util.Iterator;
33import java.util.Map;
34
35/**
36 * Queue of pending sync operations. Not inherently thread safe, external
37 * callers are responsible for locking.
38 *
39 * @hide
40 */
41public class SyncQueue {
42    private static final String TAG = "SyncManager";
43
44    private final SyncStorageEngine mSyncStorageEngine;
45    private final SyncAdaptersCache mSyncAdapters;
46
47    // A Map of SyncOperations operationKey -> SyncOperation that is designed for
48    // quick lookup of an enqueued SyncOperation.
49    private final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap();
50
51    public SyncQueue(SyncStorageEngine syncStorageEngine, final SyncAdaptersCache syncAdapters) {
52        mSyncStorageEngine = syncStorageEngine;
53        mSyncAdapters = syncAdapters;
54
55        addPendingOperations(UserHandle.USER_OWNER);
56    }
57
58    public void addPendingOperations(int userId) {
59        for (SyncStorageEngine.PendingOperation op : mSyncStorageEngine.getPendingOperations()) {
60            if (op.userId != userId) continue;
61
62            final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(
63                    op.account, op.userId, op.authority);
64            final ServiceInfo<SyncAdapterType> syncAdapterInfo = mSyncAdapters.getServiceInfo(
65                    SyncAdapterType.newKey(op.authority, op.account.type), op.userId);
66            if (syncAdapterInfo == null) {
67                Log.w(TAG, "Missing sync adapter info for authority " + op.authority + ", userId "
68                        + op.userId);
69                continue;
70            }
71            SyncOperation syncOperation = new SyncOperation(
72                    op.account, op.userId, op.syncSource, op.authority, op.extras, 0 /* delay */,
73                    backoff != null ? backoff.first : 0,
74                    mSyncStorageEngine.getDelayUntilTime(op.account, op.userId, op.authority),
75                    syncAdapterInfo.type.allowParallelSyncs());
76            syncOperation.expedited = op.expedited;
77            syncOperation.pendingOperation = op;
78            add(syncOperation, op);
79        }
80    }
81
82    public boolean add(SyncOperation operation) {
83        return add(operation, null /* this is not coming from the database */);
84    }
85
86    private boolean add(SyncOperation operation,
87            SyncStorageEngine.PendingOperation pop) {
88        // - if an operation with the same key exists and this one should run earlier,
89        //   update the earliestRunTime of the existing to the new time
90        // - if an operation with the same key exists and if this one should run
91        //   later, ignore it
92        // - if no operation exists then add the new one
93        final String operationKey = operation.key;
94        final SyncOperation existingOperation = mOperationsMap.get(operationKey);
95
96        if (existingOperation != null) {
97            boolean changed = false;
98            if (existingOperation.expedited == operation.expedited) {
99                final long newRunTime =
100                        Math.min(existingOperation.earliestRunTime, operation.earliestRunTime);
101                if (existingOperation.earliestRunTime != newRunTime) {
102                    existingOperation.earliestRunTime = newRunTime;
103                    changed = true;
104                }
105            } else {
106                if (operation.expedited) {
107                    existingOperation.expedited = true;
108                    changed = true;
109                }
110            }
111            return changed;
112        }
113
114        operation.pendingOperation = pop;
115        if (operation.pendingOperation == null) {
116            pop = new SyncStorageEngine.PendingOperation(
117                    operation.account, operation.userId, operation.syncSource,
118                    operation.authority, operation.extras, operation.expedited);
119            pop = mSyncStorageEngine.insertIntoPending(pop);
120            if (pop == null) {
121                throw new IllegalStateException("error adding pending sync operation "
122                        + operation);
123            }
124            operation.pendingOperation = pop;
125        }
126
127        mOperationsMap.put(operationKey, operation);
128        return true;
129    }
130
131    public void removeUser(int userId) {
132        ArrayList<SyncOperation> opsToRemove = new ArrayList<SyncOperation>();
133        for (SyncOperation op : mOperationsMap.values()) {
134            if (op.userId == userId) {
135                opsToRemove.add(op);
136            }
137        }
138
139        for (SyncOperation op : opsToRemove) {
140            remove(op);
141        }
142    }
143
144    /**
145     * Remove the specified operation if it is in the queue.
146     * @param operation the operation to remove
147     */
148    public void remove(SyncOperation operation) {
149        SyncOperation operationToRemove = mOperationsMap.remove(operation.key);
150        if (operationToRemove == null) {
151            return;
152        }
153        if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) {
154            final String errorMessage = "unable to find pending row for " + operationToRemove;
155            Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
156        }
157    }
158
159    public void onBackoffChanged(Account account, int userId, String providerName, long backoff) {
160        // for each op that matches the account and provider update its
161        // backoff and effectiveStartTime
162        for (SyncOperation op : mOperationsMap.values()) {
163            if (op.account.equals(account) && op.authority.equals(providerName)
164                    && op.userId == userId) {
165                op.backoff = backoff;
166                op.updateEffectiveRunTime();
167            }
168        }
169    }
170
171    public void onDelayUntilTimeChanged(Account account, String providerName, long delayUntil) {
172        // for each op that matches the account and provider update its
173        // delayUntilTime and effectiveStartTime
174        for (SyncOperation op : mOperationsMap.values()) {
175            if (op.account.equals(account) && op.authority.equals(providerName)) {
176                op.delayUntil = delayUntil;
177                op.updateEffectiveRunTime();
178            }
179        }
180    }
181
182    public void remove(Account account, int userId, String authority) {
183        Iterator<Map.Entry<String, SyncOperation>> entries = mOperationsMap.entrySet().iterator();
184        while (entries.hasNext()) {
185            Map.Entry<String, SyncOperation> entry = entries.next();
186            SyncOperation syncOperation = entry.getValue();
187            if (account != null && !syncOperation.account.equals(account)) {
188                continue;
189            }
190            if (authority != null && !syncOperation.authority.equals(authority)) {
191                continue;
192            }
193            if (userId != syncOperation.userId) {
194                continue;
195            }
196            entries.remove();
197            if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) {
198                final String errorMessage = "unable to find pending row for " + syncOperation;
199                Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
200            }
201        }
202    }
203
204    public Collection<SyncOperation> getOperations() {
205        return mOperationsMap.values();
206    }
207
208    public void dump(StringBuilder sb) {
209        final long now = SystemClock.elapsedRealtime();
210        sb.append("SyncQueue: ").append(mOperationsMap.size()).append(" operation(s)\n");
211        for (SyncOperation operation : mOperationsMap.values()) {
212            sb.append("  ");
213            if (operation.effectiveRunTime <= now) {
214                sb.append("READY");
215            } else {
216                sb.append(DateUtils.formatElapsedTime((operation.effectiveRunTime - now) / 1000));
217            }
218            sb.append(" - ");
219            sb.append(operation.dump(false)).append("\n");
220        }
221    }
222}
223