SyncQueue.java revision 307da1a46b4c9b711bafe8fbaaa6b98e8868c18e
1package android.content;
2
3import com.google.android.collect.Maps;
4
5import android.os.Bundle;
6import android.os.SystemClock;
7import android.util.Pair;
8import android.util.Log;
9import android.accounts.Account;
10
11import java.util.HashMap;
12import java.util.ArrayList;
13import java.util.Map;
14import java.util.Iterator;
15
16/**
17 *
18 * @hide
19 */
20public class SyncQueue {
21    private static final String TAG = "SyncManager";
22    private SyncStorageEngine mSyncStorageEngine;
23
24    // A Map of SyncOperations operationKey -> SyncOperation that is designed for
25    // quick lookup of an enqueued SyncOperation.
26    private final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap();
27
28    public SyncQueue(SyncStorageEngine syncStorageEngine) {
29        mSyncStorageEngine = syncStorageEngine;
30        ArrayList<SyncStorageEngine.PendingOperation> ops
31                = mSyncStorageEngine.getPendingOperations();
32        final int N = ops.size();
33        for (int i=0; i<N; i++) {
34            SyncStorageEngine.PendingOperation op = ops.get(i);
35            // -1 is a special value that means expedited
36            final int delay = op.expedited ? -1 : 0;
37            SyncOperation syncOperation = new SyncOperation(
38                    op.account, op.syncSource, op.authority, op.extras, delay);
39            syncOperation.pendingOperation = op;
40            add(syncOperation, op);
41        }
42    }
43
44    public boolean add(SyncOperation operation) {
45        return add(operation, null /* this is not coming from the database */);
46    }
47
48    private boolean add(SyncOperation operation,
49            SyncStorageEngine.PendingOperation pop) {
50        // - if an operation with the same key exists and this one should run earlier,
51        //   update the earliestRunTime of the existing to the new time
52        // - if an operation with the same key exists and if this one should run
53        //   later, ignore it
54        // - if no operation exists then add the new one
55        final String operationKey = operation.key;
56        final SyncOperation existingOperation = mOperationsMap.get(operationKey);
57
58        if (existingOperation != null) {
59            boolean changed = false;
60            if (existingOperation.expedited == operation.expedited) {
61                final long newRunTime =
62                        Math.min(existingOperation.earliestRunTime, operation.earliestRunTime);
63                if (existingOperation.earliestRunTime != newRunTime) {
64                    existingOperation.earliestRunTime = newRunTime;
65                    changed = true;
66                }
67            } else {
68                if (operation.expedited) {
69                    existingOperation.expedited = true;
70                    changed = true;
71                }
72            }
73            return changed;
74        }
75
76        operation.pendingOperation = pop;
77        if (operation.pendingOperation == null) {
78            pop = new SyncStorageEngine.PendingOperation(
79                            operation.account, operation.syncSource,
80                            operation.authority, operation.extras, operation.expedited);
81            pop = mSyncStorageEngine.insertIntoPending(pop);
82            if (pop == null) {
83                throw new IllegalStateException("error adding pending sync operation "
84                        + operation);
85            }
86            operation.pendingOperation = pop;
87        }
88
89        mOperationsMap.put(operationKey, operation);
90        return true;
91    }
92
93    public void remove(SyncOperation operation) {
94        SyncOperation operationToRemove = mOperationsMap.remove(operation.key);
95        if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) {
96            final String errorMessage = "unable to find pending row for " + operationToRemove;
97            Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
98        }
99    }
100
101    /**
102     * Find the operation that should run next. Operations are sorted by their earliestRunTime,
103     * prioritizing expedited operations. The earliestRunTime is adjusted by the sync adapter's
104     * backoff and delayUntil times, if any.
105     * @param now the current {@link android.os.SystemClock#elapsedRealtime()}
106     * @return the operation that should run next and when it should run. The time may be in
107     * the future. It is expressed in milliseconds since boot.
108     */
109    private Pair<SyncOperation, Long> nextOperation(long now) {
110        SyncOperation lowestOp = null;
111        long lowestOpRunTime = 0;
112        for (SyncOperation op : mOperationsMap.values()) {
113            // effectiveRunTime:
114            //   - backoffTime > currentTime : backoffTime
115            //   - backoffTime <= currentTime : op.runTime
116            Pair<Long, Long> backoff = null;
117            long delayUntilTime = 0;
118            final boolean isManualSync =
119                    op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
120            if (!isManualSync) {
121                backoff = mSyncStorageEngine.getBackoff(op.account, op.authority);
122                delayUntilTime = mSyncStorageEngine.getDelayUntilTime(op.account, op.authority);
123            }
124            long backoffTime = Math.max(backoff != null ? backoff.first : 0, delayUntilTime);
125            long opRunTime = backoffTime > now ? backoffTime : op.earliestRunTime;
126            if (lowestOp == null
127                    || (lowestOp.expedited == op.expedited
128                        ? opRunTime < lowestOpRunTime
129                        : op.expedited)) {
130                lowestOp = op;
131                lowestOpRunTime = opRunTime;
132            }
133        }
134        if (lowestOp == null) {
135            return null;
136        }
137        return Pair.create(lowestOp, lowestOpRunTime);
138    }
139
140    /**
141     * Return when the next SyncOperation will be ready to run or null if there are
142     * none.
143     * @param now the current {@link android.os.SystemClock#elapsedRealtime()}, used to
144     * decide if the sync operation is ready to run
145     * @return when the next SyncOperation will be ready to run, expressed in elapsedRealtime()
146     */
147    public Long nextRunTime(long now) {
148        Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation(now);
149        if (nextOpAndRunTime == null) {
150            return null;
151        }
152        return nextOpAndRunTime.second;
153    }
154
155    /**
156     * Find and return the SyncOperation that should be run next and is ready to run.
157     * @param now the current {@link android.os.SystemClock#elapsedRealtime()}, used to
158     * decide if the sync operation is ready to run
159     * @return the SyncOperation that should be run next and is ready to run.
160     */
161    public SyncOperation nextReadyToRun(long now) {
162        Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation(now);
163        if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) {
164            return null;
165        }
166        return nextOpAndRunTime.first;
167    }
168
169    public void clear(Account account, String authority) {
170        Iterator<Map.Entry<String, SyncOperation>> entries = mOperationsMap.entrySet().iterator();
171        while (entries.hasNext()) {
172            Map.Entry<String, SyncOperation> entry = entries.next();
173            SyncOperation syncOperation = entry.getValue();
174            if (account != null && !syncOperation.account.equals(account)) continue;
175            if (authority != null && !syncOperation.authority.equals(authority)) continue;
176            entries.remove();
177            if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) {
178                final String errorMessage = "unable to find pending row for " + syncOperation;
179                Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
180            }
181        }
182    }
183
184    public void dump(StringBuilder sb) {
185        sb.append("SyncQueue: ").append(mOperationsMap.size()).append(" operation(s)\n");
186        for (SyncOperation operation : mOperationsMap.values()) {
187            sb.append(operation).append("\n");
188        }
189    }
190}
191