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