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