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