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 com.android.server.content; 18 19 import android.content.pm.PackageManager; 20 import android.content.SyncAdapterType; 21 import android.content.SyncAdaptersCache; 22 import android.content.pm.RegisteredServicesCache.ServiceInfo; 23 import android.os.Bundle; 24 import android.os.SystemClock; 25 import android.text.format.DateUtils; 26 import android.util.Log; 27 import android.util.Pair; 28 29 import com.google.android.collect.Maps; 30 31 import java.util.ArrayList; 32 import java.util.Collection; 33 import java.util.HashMap; 34 import java.util.Iterator; 35 import java.util.Map; 36 37 /** 38 * Queue of pending sync operations. Not inherently thread safe, external 39 * callers are responsible for locking. 40 * 41 * @hide 42 */ 43 public class SyncQueue { 44 private static final String TAG = "SyncManager"; 45 private final SyncStorageEngine mSyncStorageEngine; 46 private final SyncAdaptersCache mSyncAdapters; 47 private final PackageManager mPackageManager; 48 49 // A Map of SyncOperations operationKey -> SyncOperation that is designed for 50 // quick lookup of an enqueued SyncOperation. 51 private final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap(); 52 SyncQueue(PackageManager packageManager, SyncStorageEngine syncStorageEngine, final SyncAdaptersCache syncAdapters)53 public SyncQueue(PackageManager packageManager, SyncStorageEngine syncStorageEngine, 54 final SyncAdaptersCache syncAdapters) { 55 mPackageManager = packageManager; 56 mSyncStorageEngine = syncStorageEngine; 57 mSyncAdapters = syncAdapters; 58 } 59 addPendingOperations(int userId)60 public void addPendingOperations(int userId) { 61 for (SyncStorageEngine.PendingOperation op : mSyncStorageEngine.getPendingOperations()) { 62 final SyncStorageEngine.EndPoint info = op.target; 63 if (info.userId != userId) continue; 64 65 final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(info); 66 SyncOperation operationToAdd; 67 if (info.target_provider) { 68 final ServiceInfo<SyncAdapterType> syncAdapterInfo = mSyncAdapters.getServiceInfo( 69 SyncAdapterType.newKey(info.provider, info.account.type), info.userId); 70 if (syncAdapterInfo == null) { 71 if (Log.isLoggable(TAG, Log.VERBOSE)) { 72 Log.v(TAG, "Missing sync adapter info for authority " + op.target); 73 } 74 continue; 75 } 76 operationToAdd = new SyncOperation( 77 info.account, info.userId, op.reason, op.syncSource, info.provider, 78 op.extras, 79 op.expedited ? -1 : 0 /* delay */, 80 0 /* flex */, 81 backoff != null ? backoff.first : 0L, 82 mSyncStorageEngine.getDelayUntilTime(info), 83 syncAdapterInfo.type.allowParallelSyncs()); 84 operationToAdd.pendingOperation = op; 85 add(operationToAdd, op); 86 } else if (info.target_service) { 87 try { 88 mPackageManager.getServiceInfo(info.service, 0); 89 } catch (PackageManager.NameNotFoundException e) { 90 if (Log.isLoggable(TAG, Log.VERBOSE)) { 91 Log.w(TAG, "Missing sync service for authority " + op.target); 92 } 93 continue; 94 } 95 operationToAdd = new SyncOperation( 96 info.service, info.userId, op.reason, op.syncSource, 97 op.extras, 98 op.expedited ? -1 : 0 /* delay */, 99 0 /* flex */, 100 backoff != null ? backoff.first : 0, 101 mSyncStorageEngine.getDelayUntilTime(info)); 102 operationToAdd.pendingOperation = op; 103 add(operationToAdd, op); 104 } 105 } 106 } 107 add(SyncOperation operation)108 public boolean add(SyncOperation operation) { 109 return add(operation, null /* this is not coming from the database */); 110 } 111 112 /** 113 * Adds a SyncOperation to the queue and creates a PendingOperation object to track that sync. 114 * If an operation is added that already exists, the existing operation is updated if the newly 115 * added operation occurs before (or the interval overlaps). 116 */ add(SyncOperation operation, SyncStorageEngine.PendingOperation pop)117 private boolean add(SyncOperation operation, 118 SyncStorageEngine.PendingOperation pop) { 119 // If an operation with the same key exists and this one should run sooner/overlaps, 120 // replace the run interval of the existing operation with this new one. 121 // Complications: what if the existing operation is expedited but the new operation has an 122 // earlier run time? Will not be a problem for periodic syncs (no expedited flag), and for 123 // one-off syncs we only change it if the new sync is sooner. 124 final String operationKey = operation.key; 125 final SyncOperation existingOperation = mOperationsMap.get(operationKey); 126 127 if (existingOperation != null) { 128 boolean changed = false; 129 if (operation.compareTo(existingOperation) <= 0 ) { 130 long newRunTime = 131 Math.min(existingOperation.latestRunTime, operation.latestRunTime); 132 // Take smaller runtime. 133 existingOperation.latestRunTime = newRunTime; 134 // Take newer flextime. 135 existingOperation.flexTime = operation.flexTime; 136 changed = true; 137 } 138 return changed; 139 } 140 141 operation.pendingOperation = pop; 142 // Don't update the PendingOp if one already exists. This really is just a placeholder, 143 // no actual scheduling info is placed here. 144 if (operation.pendingOperation == null) { 145 pop = mSyncStorageEngine.insertIntoPending(operation); 146 if (pop == null) { 147 throw new IllegalStateException("error adding pending sync operation " 148 + operation); 149 } 150 operation.pendingOperation = pop; 151 } 152 153 mOperationsMap.put(operationKey, operation); 154 return true; 155 } 156 removeUserLocked(int userId)157 public void removeUserLocked(int userId) { 158 ArrayList<SyncOperation> opsToRemove = new ArrayList<SyncOperation>(); 159 for (SyncOperation op : mOperationsMap.values()) { 160 if (op.target.userId == userId) { 161 opsToRemove.add(op); 162 } 163 } 164 for (SyncOperation op : opsToRemove) { 165 remove(op); 166 } 167 } 168 169 /** 170 * Remove the specified operation if it is in the queue. 171 * @param operation the operation to remove 172 */ remove(SyncOperation operation)173 public void remove(SyncOperation operation) { 174 boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); 175 SyncOperation operationToRemove = mOperationsMap.remove(operation.key); 176 if (isLoggable) { 177 Log.v(TAG, "Attempting to remove: " + operation.key); 178 } 179 if (operationToRemove == null) { 180 if (isLoggable) { 181 Log.v(TAG, "Could not find: " + operation.key); 182 } 183 return; 184 } 185 if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) { 186 final String errorMessage = "unable to find pending row for " + operationToRemove; 187 Log.e(TAG, errorMessage, new IllegalStateException(errorMessage)); 188 } 189 } 190 191 /** Reset backoffs for all operations in the queue. */ clearBackoffs()192 public void clearBackoffs() { 193 for (SyncOperation op : mOperationsMap.values()) { 194 op.backoff = 0L; 195 op.updateEffectiveRunTime(); 196 } 197 } 198 onBackoffChanged(SyncStorageEngine.EndPoint target, long backoff)199 public void onBackoffChanged(SyncStorageEngine.EndPoint target, long backoff) { 200 // For each op that matches the target of the changed op, update its 201 // backoff and effectiveStartTime 202 for (SyncOperation op : mOperationsMap.values()) { 203 if (op.target.matchesSpec(target)) { 204 op.backoff = backoff; 205 op.updateEffectiveRunTime(); 206 } 207 } 208 } 209 onDelayUntilTimeChanged(SyncStorageEngine.EndPoint target, long delayUntil)210 public void onDelayUntilTimeChanged(SyncStorageEngine.EndPoint target, long delayUntil) { 211 // for each op that matches the target info of the provided op, change the delay time. 212 for (SyncOperation op : mOperationsMap.values()) { 213 if (op.target.matchesSpec(target)) { 214 op.delayUntil = delayUntil; 215 op.updateEffectiveRunTime(); 216 } 217 } 218 } 219 220 /** 221 * Remove all of the SyncOperations associated with a given target. 222 * 223 * @param info target object provided here can have null Account/provider. This is the case 224 * where you want to remove all ops associated with a provider (null Account) or all ops 225 * associated with an account (null provider). 226 * @param extras option bundle to include to further specify which operation to remove. If this 227 * bundle contains sync settings flags, they are ignored. 228 */ remove(final SyncStorageEngine.EndPoint info, Bundle extras)229 public void remove(final SyncStorageEngine.EndPoint info, Bundle extras) { 230 Iterator<Map.Entry<String, SyncOperation>> entries = mOperationsMap.entrySet().iterator(); 231 while (entries.hasNext()) { 232 Map.Entry<String, SyncOperation> entry = entries.next(); 233 SyncOperation syncOperation = entry.getValue(); 234 final SyncStorageEngine.EndPoint opInfo = syncOperation.target; 235 if (!opInfo.matchesSpec(info)) { 236 continue; 237 } 238 if (extras != null 239 && !SyncManager.syncExtrasEquals( 240 syncOperation.extras, 241 extras, 242 false /* no config flags*/)) { 243 continue; 244 } 245 entries.remove(); 246 if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) { 247 final String errorMessage = "unable to find pending row for " + syncOperation; 248 Log.e(TAG, errorMessage, new IllegalStateException(errorMessage)); 249 } 250 } 251 } 252 getOperations()253 public Collection<SyncOperation> getOperations() { 254 return mOperationsMap.values(); 255 } 256 dump(StringBuilder sb)257 public void dump(StringBuilder sb) { 258 final long now = SystemClock.elapsedRealtime(); 259 sb.append("SyncQueue: ").append(mOperationsMap.size()).append(" operation(s)\n"); 260 for (SyncOperation operation : mOperationsMap.values()) { 261 sb.append(" "); 262 if (operation.effectiveRunTime <= now) { 263 sb.append("READY"); 264 } else { 265 sb.append(DateUtils.formatElapsedTime((operation.effectiveRunTime - now) / 1000)); 266 } 267 sb.append(" - "); 268 sb.append(operation.dump(mPackageManager, false)).append("\n"); 269 } 270 } 271 } 272