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