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