blob: 898b76c9d8208babf7d711354896e57669022777 [file] [log] [blame]
Arthur Ishiguro7a23a962017-11-01 10:52:28 -07001/*
2 * Copyright 2017 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 com.android.server.location;
18
19import android.hardware.contexthub.V1_0.IContexthub;
20import android.hardware.contexthub.V1_0.Result;
21import android.hardware.contexthub.V1_0.TransactionResult;
22import android.hardware.location.ContextHubTransaction;
23import android.hardware.location.IContextHubTransactionCallback;
24import android.hardware.location.NanoAppBinary;
25import android.hardware.location.NanoAppState;
26import android.os.RemoteException;
27import android.util.Log;
28
29import java.util.ArrayDeque;
30import java.util.Collections;
31import java.util.List;
32import java.util.concurrent.ScheduledThreadPoolExecutor;
33import java.util.concurrent.ScheduledFuture;
34import java.util.concurrent.TimeUnit;
35import java.util.concurrent.atomic.AtomicInteger;
36
37/**
38 * Manages transactions at the Context Hub Service.
39 *
40 * This class maintains a queue of transaction requests made to the ContextHubService by clients,
41 * and executes them through the Context Hub. At any point in time, either the transaction queue is
42 * empty, or there is a pending transaction that is waiting for an asynchronous response from the
43 * hub. This class also handles synchronous errors and timeouts of each transaction.
44 *
45 * @hide
46 */
47/* package */ class ContextHubTransactionManager {
48 private static final String TAG = "ContextHubTransactionManager";
49
50 /*
51 * Maximum number of transaction requests that can be pending at a time
52 */
53 private static final int MAX_PENDING_REQUESTS = 10;
54
55 /*
56 * The proxy to talk to the Context Hub
57 */
58 private final IContexthub mContextHubProxy;
59
60 /*
61 * A queue containing the current transactions
62 */
63 private final ArrayDeque<ContextHubServiceTransaction> mTransactionQueue = new ArrayDeque<>();
64
65 /*
66 * The next available transaction ID
67 */
68 private final AtomicInteger mNextAvailableId = new AtomicInteger();
69
70 /*
71 * An executor and the future object for scheduling timeout timers
72 */
73 private final ScheduledThreadPoolExecutor mTimeoutExecutor = new ScheduledThreadPoolExecutor(1);
74 private ScheduledFuture<?> mTimeoutFuture = null;
75
76 /* package */ ContextHubTransactionManager(IContexthub contextHubProxy) {
77 mContextHubProxy = contextHubProxy;
78 }
79
80 /**
81 * Creates a transaction for loading a nanoapp.
82 *
83 * @param contextHubId the ID of the hub to load the nanoapp to
84 * @param nanoAppBinary the binary of the nanoapp to load
85 * @param onCompleteCallback the client on complete callback
86 * @return the generated transaction
87 */
88 /* package */ ContextHubServiceTransaction createLoadTransaction(
89 int contextHubId, NanoAppBinary nanoAppBinary,
90 IContextHubTransactionCallback onCompleteCallback) {
91 return new ContextHubServiceTransaction(
92 mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_LOAD_NANOAPP) {
93 @Override
94 /* package */ int onTransact() {
95 android.hardware.contexthub.V1_0.NanoAppBinary hidlNanoAppBinary =
96 ContextHubServiceUtil.createHidlNanoAppBinary(nanoAppBinary);
97 try {
98 return mContextHubProxy.loadNanoApp(
99 contextHubId, hidlNanoAppBinary, this.getTransactionId());
100 } catch (RemoteException e) {
101 Log.e(TAG, "RemoteException while trying to load nanoapp with ID 0x" +
102 Long.toHexString(nanoAppBinary.getNanoAppId()));
103 return Result.UNKNOWN_FAILURE;
104 }
105 }
106
107 @Override
108 /* package */ void onTimeout() {
109 onTransactionComplete(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT);
110 }
111
112 @Override
113 /* package */ void onTransactionComplete(int result) {
114 try {
115 onCompleteCallback.onTransactionComplete(result);
116 } catch (RemoteException e) {
117 Log.e(TAG, "RemoteException while calling client onTransactionComplete");
118 }
119 }
120 };
121 }
122
123 /**
124 * Creates a transaction for unloading a nanoapp.
125 *
126 * @param contextHubId the ID of the hub to load the nanoapp to
127 * @param nanoAppId the ID of the nanoapp to unload
128 * @param onCompleteCallback the client on complete callback
129 * @return the generated transaction
130 */
131 /* package */ ContextHubServiceTransaction createUnloadTransaction(
132 int contextHubId, long nanoAppId, IContextHubTransactionCallback onCompleteCallback) {
133 return new ContextHubServiceTransaction(
134 mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_UNLOAD_NANOAPP) {
135 @Override
136 /* package */ int onTransact() {
137 try {
138 return mContextHubProxy.unloadNanoApp(
139 contextHubId, nanoAppId, this.getTransactionId());
140 } catch (RemoteException e) {
141 Log.e(TAG, "RemoteException while trying to unload nanoapp with ID 0x" +
142 Long.toHexString(nanoAppId));
143 return Result.UNKNOWN_FAILURE;
144 }
145 }
146
147 @Override
148 /* package */ void onTimeout() {
149 onTransactionComplete(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT);
150 }
151
152 @Override
153 /* package */ void onTransactionComplete(int result) {
154 try {
155 onCompleteCallback.onTransactionComplete(result);
156 } catch (RemoteException e) {
157 Log.e(TAG, "RemoteException while calling client onTransactionComplete");
158 }
159 }
160 };
161 }
162
163 /**
164 * Creates a transaction for querying for a list of nanoapps.
165 *
166 * @param contextHubId the ID of the hub to query
167 * @param onCompleteCallback the client on complete callback
168 * @return the generated transaction
169 */
170 /* package */ ContextHubServiceTransaction createQueryTransaction(
171 int contextHubId, IContextHubTransactionCallback onCompleteCallback) {
172 return new ContextHubServiceTransaction(
173 mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_QUERY_NANOAPPS) {
174 @Override
175 /* package */ int onTransact() {
176 try {
177 return mContextHubProxy.queryApps(contextHubId);
178 } catch (RemoteException e) {
179 Log.e(TAG, "RemoteException while trying to query for nanoapps");
180 return Result.UNKNOWN_FAILURE;
181 }
182 }
183
184 @Override
185 /* package */ void onTimeout() {
186 onQueryResponse(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT,
187 Collections.emptyList());
188 }
189
190 @Override
191 /* package */ void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
192 try {
193 onCompleteCallback.onQueryResponse(result, nanoAppStateList);
194 } catch (RemoteException e) {
195 Log.e(TAG, "RemoteException while calling client onQueryComplete");
196 }
197 }
198 };
199 }
200
201 /**
202 * Adds a new transaction to the queue.
203 *
204 * If there was no pending transaction at the time, the transaction that was added will be
205 * started in this method.
206 *
207 * @param transaction the transaction to add
208 * @throws IllegalStateException if the queue is full
209 */
210 /* package */
211 synchronized void addTransaction(
212 ContextHubServiceTransaction transaction) throws IllegalStateException {
213 if (mTransactionQueue.size() == MAX_PENDING_REQUESTS) {
214 throw new IllegalStateException("Transaction transaction queue is full (capacity = "
215 + MAX_PENDING_REQUESTS + ")");
216 }
217 mTransactionQueue.add(transaction);
218
219 if (mTransactionQueue.size() == 1) {
220 startNextTransaction();
221 }
222 }
223
224 /**
225 * Handles a transaction response from a Context Hub.
226 *
227 * @param transactionId the transaction ID of the response
228 * @param result the result of the transaction
229 */
230 /* package */
231 synchronized void onTransactionResponse(int transactionId, int result) {
232 ContextHubServiceTransaction transaction = mTransactionQueue.peek();
233 if (transaction == null) {
234 Log.w(TAG, "Received unexpected transaction response (no transaction pending)");
235 return;
236 }
237 if (transaction.getTransactionId() != transactionId) {
238 Log.w(TAG, "Received unexpected transaction response (expected ID = "
239 + transaction.getTransactionId() + ", received ID = " + transactionId + ")");
240 return;
241 }
242
243 transaction.onTransactionComplete(result);
244 removeTransactionAndStartNext();
245 }
246
247 /**
248 * Handles a query response from a Context Hub.
249 *
250 * @param nanoAppStateList the list of nanoapps included in the response
251 */
252 /* package */
253 synchronized void onQueryResponse(List<NanoAppState> nanoAppStateList) {
254 ContextHubServiceTransaction transaction = mTransactionQueue.peek();
255 if (transaction == null) {
256 Log.w(TAG, "Received unexpected query response (no transaction pending)");
257 return;
258 }
259 if (transaction.getTransactionType() != ContextHubTransaction.TYPE_QUERY_NANOAPPS) {
260 Log.w(TAG, "Received unexpected query response (expected " + transaction + ")");
261 return;
262 }
263
264 transaction.onQueryResponse(TransactionResult.SUCCESS, nanoAppStateList);
265 removeTransactionAndStartNext();
266 }
267
268 /**
269 * Handles a hub reset event by stopping a pending transaction and starting the next.
270 */
271 /* package */
272 synchronized void onHubReset() {
273 ContextHubServiceTransaction transaction = mTransactionQueue.peek();
274 if (transaction == null) {
275 return;
276 }
277
278 removeTransactionAndStartNext();
279 }
280
281 /**
282 * Pops the front transaction from the queue and starts the next pending transaction request.
283 *
284 * Removing elements from the transaction queue must only be done through this method. When a
285 * pending transaction is removed, the timeout timer is cancelled and the transaction is marked
286 * complete.
287 *
288 * It is assumed that the transaction queue is non-empty when this method is invoked, and that
289 * the caller has obtained a lock on this ContextHubTransactionManager object.
290 */
291 private void removeTransactionAndStartNext() {
292 mTimeoutFuture.cancel(false /* mayInterruptIfRunning */);
293
294 ContextHubServiceTransaction transaction = mTransactionQueue.remove();
295 transaction.setComplete();
296
297 if (!mTransactionQueue.isEmpty()) {
298 startNextTransaction();
299 }
300 }
301
302 /**
303 * Starts the next pending transaction request.
304 *
305 * Starting new transactions must only be done through this method. This method continues to
306 * process the transaction queue as long as there are pending requests, and no transaction is
307 * pending.
308 *
309 * It is assumed that the caller has obtained a lock on this ContextHubTransactionManager
310 * object.
311 */
312 private void startNextTransaction() {
313 int result = Result.UNKNOWN_FAILURE;
314 while (result != Result.OK && !mTransactionQueue.isEmpty()) {
315 ContextHubServiceTransaction transaction = mTransactionQueue.peek();
316 result = transaction.onTransact();
317
318 if (result == Result.OK) {
319 Runnable onTimeoutFunc = () -> {
320 synchronized (this) {
321 if (!transaction.isComplete()) {
322 Log.d(TAG, transaction + " timed out");
323 transaction.onTimeout();
324
325 removeTransactionAndStartNext();
326 }
327 }
328 };
329
330 long timeoutSeconds = transaction.getTimeout(TimeUnit.SECONDS);
331 mTimeoutFuture = mTimeoutExecutor.schedule(onTimeoutFunc, timeoutSeconds,
332 TimeUnit.SECONDS);
333 } else {
334 mTransactionQueue.remove();
335 }
336 }
337 }
338}