blob: cced781f8e1b5d558e4a5ad273e3b6547fb2278b [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 */
Arthur Ishiguroe60f91a2017-11-30 09:48:00 -080053 private static final int MAX_PENDING_REQUESTS = 10000;
Arthur Ishiguro7a23a962017-11-01 10:52:28 -070054
55 /*
56 * The proxy to talk to the Context Hub
57 */
58 private final IContexthub mContextHubProxy;
59
60 /*
Arthur Ishiguro0069f122017-11-20 15:07:14 -080061 * The manager for all clients for the service.
62 */
63 private final ContextHubClientManager mClientManager;
64
65 /*
Arthur Ishigurofb9e4c72017-11-21 15:33:21 -080066 * The nanoapp state manager for the service
67 */
68 private final NanoAppStateManager mNanoAppStateManager;
69
70 /*
Arthur Ishiguro7a23a962017-11-01 10:52:28 -070071 * A queue containing the current transactions
72 */
73 private final ArrayDeque<ContextHubServiceTransaction> mTransactionQueue = new ArrayDeque<>();
74
75 /*
76 * The next available transaction ID
77 */
78 private final AtomicInteger mNextAvailableId = new AtomicInteger();
79
80 /*
81 * An executor and the future object for scheduling timeout timers
82 */
83 private final ScheduledThreadPoolExecutor mTimeoutExecutor = new ScheduledThreadPoolExecutor(1);
84 private ScheduledFuture<?> mTimeoutFuture = null;
85
Arthur Ishiguro0069f122017-11-20 15:07:14 -080086 /* package */ ContextHubTransactionManager(
Arthur Ishigurofb9e4c72017-11-21 15:33:21 -080087 IContexthub contextHubProxy, ContextHubClientManager clientManager,
88 NanoAppStateManager nanoAppStateManager) {
Arthur Ishiguro7a23a962017-11-01 10:52:28 -070089 mContextHubProxy = contextHubProxy;
Arthur Ishiguro0069f122017-11-20 15:07:14 -080090 mClientManager = clientManager;
Arthur Ishigurofb9e4c72017-11-21 15:33:21 -080091 mNanoAppStateManager = nanoAppStateManager;
Arthur Ishiguro7a23a962017-11-01 10:52:28 -070092 }
93
94 /**
95 * Creates a transaction for loading a nanoapp.
96 *
97 * @param contextHubId the ID of the hub to load the nanoapp to
98 * @param nanoAppBinary the binary of the nanoapp to load
99 * @param onCompleteCallback the client on complete callback
100 * @return the generated transaction
101 */
102 /* package */ ContextHubServiceTransaction createLoadTransaction(
103 int contextHubId, NanoAppBinary nanoAppBinary,
104 IContextHubTransactionCallback onCompleteCallback) {
105 return new ContextHubServiceTransaction(
106 mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_LOAD_NANOAPP) {
107 @Override
108 /* package */ int onTransact() {
109 android.hardware.contexthub.V1_0.NanoAppBinary hidlNanoAppBinary =
110 ContextHubServiceUtil.createHidlNanoAppBinary(nanoAppBinary);
111 try {
112 return mContextHubProxy.loadNanoApp(
113 contextHubId, hidlNanoAppBinary, this.getTransactionId());
114 } catch (RemoteException e) {
115 Log.e(TAG, "RemoteException while trying to load nanoapp with ID 0x" +
Arthur Ishiguro75a95692017-11-29 09:01:37 -0800116 Long.toHexString(nanoAppBinary.getNanoAppId()), e);
Arthur Ishiguro7a23a962017-11-01 10:52:28 -0700117 return Result.UNKNOWN_FAILURE;
118 }
119 }
120
121 @Override
Arthur Ishiguro57f00902017-11-29 11:54:51 -0800122 /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) {
Arthur Ishiguro6100aa72017-12-20 09:35:00 -0800123 if (result == ContextHubTransaction.RESULT_SUCCESS) {
Arthur Ishigurofb9e4c72017-11-21 15:33:21 -0800124 // NOTE: The legacy JNI code used to do a query right after a load success
125 // to synchronize the service cache. Instead store the binary that was
126 // requested to load to update the cache later without doing a query.
127 mNanoAppStateManager.addNanoAppInstance(
128 contextHubId, nanoAppBinary.getNanoAppId(),
129 nanoAppBinary.getNanoAppVersion());
130 }
Arthur Ishiguro7a23a962017-11-01 10:52:28 -0700131 try {
132 onCompleteCallback.onTransactionComplete(result);
Arthur Ishiguro6100aa72017-12-20 09:35:00 -0800133 if (result == ContextHubTransaction.RESULT_SUCCESS) {
Arthur Ishiguro0069f122017-11-20 15:07:14 -0800134 mClientManager.onNanoAppLoaded(contextHubId, nanoAppBinary.getNanoAppId());
135 }
Arthur Ishiguro7a23a962017-11-01 10:52:28 -0700136 } catch (RemoteException e) {
Arthur Ishiguro75a95692017-11-29 09:01:37 -0800137 Log.e(TAG, "RemoteException while calling client onTransactionComplete", e);
Arthur Ishiguro7a23a962017-11-01 10:52:28 -0700138 }
139 }
140 };
141 }
142
143 /**
144 * Creates a transaction for unloading a nanoapp.
145 *
Arthur Ishiguro0ed545c2017-12-12 15:01:32 -0800146 * @param contextHubId the ID of the hub to unload the nanoapp from
Arthur Ishiguro7a23a962017-11-01 10:52:28 -0700147 * @param nanoAppId the ID of the nanoapp to unload
148 * @param onCompleteCallback the client on complete callback
149 * @return the generated transaction
150 */
151 /* package */ ContextHubServiceTransaction createUnloadTransaction(
152 int contextHubId, long nanoAppId, IContextHubTransactionCallback onCompleteCallback) {
153 return new ContextHubServiceTransaction(
154 mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_UNLOAD_NANOAPP) {
155 @Override
156 /* package */ int onTransact() {
157 try {
158 return mContextHubProxy.unloadNanoApp(
159 contextHubId, nanoAppId, this.getTransactionId());
160 } catch (RemoteException e) {
161 Log.e(TAG, "RemoteException while trying to unload nanoapp with ID 0x" +
Arthur Ishiguro75a95692017-11-29 09:01:37 -0800162 Long.toHexString(nanoAppId), e);
Arthur Ishiguro7a23a962017-11-01 10:52:28 -0700163 return Result.UNKNOWN_FAILURE;
164 }
165 }
166
167 @Override
Arthur Ishiguro57f00902017-11-29 11:54:51 -0800168 /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) {
Arthur Ishiguro6100aa72017-12-20 09:35:00 -0800169 if (result == ContextHubTransaction.RESULT_SUCCESS) {
Arthur Ishigurofb9e4c72017-11-21 15:33:21 -0800170 mNanoAppStateManager.removeNanoAppInstance(contextHubId, nanoAppId);
171 }
Arthur Ishiguro7a23a962017-11-01 10:52:28 -0700172 try {
173 onCompleteCallback.onTransactionComplete(result);
Arthur Ishiguro6100aa72017-12-20 09:35:00 -0800174 if (result == ContextHubTransaction.RESULT_SUCCESS) {
Arthur Ishiguro0069f122017-11-20 15:07:14 -0800175 mClientManager.onNanoAppUnloaded(contextHubId, nanoAppId);
176 }
Arthur Ishiguro7a23a962017-11-01 10:52:28 -0700177 } catch (RemoteException e) {
Arthur Ishiguro75a95692017-11-29 09:01:37 -0800178 Log.e(TAG, "RemoteException while calling client onTransactionComplete", e);
Arthur Ishiguro7a23a962017-11-01 10:52:28 -0700179 }
180 }
181 };
182 }
183
184 /**
Arthur Ishiguro0ed545c2017-12-12 15:01:32 -0800185 * Creates a transaction for enabling a nanoapp.
186 *
187 * @param contextHubId the ID of the hub to enable the nanoapp on
188 * @param nanoAppId the ID of the nanoapp to enable
189 * @param onCompleteCallback the client on complete callback
190 * @return the generated transaction
191 */
192 /* package */ ContextHubServiceTransaction createEnableTransaction(
193 int contextHubId, long nanoAppId, IContextHubTransactionCallback onCompleteCallback) {
194 return new ContextHubServiceTransaction(
195 mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_ENABLE_NANOAPP) {
196 @Override
197 /* package */ int onTransact() {
198 try {
199 return mContextHubProxy.enableNanoApp(
200 contextHubId, nanoAppId, this.getTransactionId());
201 } catch (RemoteException e) {
202 Log.e(TAG, "RemoteException while trying to enable nanoapp with ID 0x" +
203 Long.toHexString(nanoAppId), e);
204 return Result.UNKNOWN_FAILURE;
205 }
206 }
207
208 @Override
209 /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) {
210 try {
211 onCompleteCallback.onTransactionComplete(result);
212 } catch (RemoteException e) {
213 Log.e(TAG, "RemoteException while calling client onTransactionComplete", e);
214 }
215 }
216 };
217 }
218
219 /**
Arthur Ishiguro54e1a892017-12-12 15:09:31 -0800220 * Creates a transaction for disabling a nanoapp.
221 *
222 * @param contextHubId the ID of the hub to disable the nanoapp on
223 * @param nanoAppId the ID of the nanoapp to disable
224 * @param onCompleteCallback the client on complete callback
225 * @return the generated transaction
226 */
227 /* package */ ContextHubServiceTransaction createDisableTransaction(
228 int contextHubId, long nanoAppId, IContextHubTransactionCallback onCompleteCallback) {
229 return new ContextHubServiceTransaction(
230 mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_DISABLE_NANOAPP) {
231 @Override
232 /* package */ int onTransact() {
233 try {
234 return mContextHubProxy.disableNanoApp(
235 contextHubId, nanoAppId, this.getTransactionId());
236 } catch (RemoteException e) {
237 Log.e(TAG, "RemoteException while trying to disable nanoapp with ID 0x" +
238 Long.toHexString(nanoAppId), e);
239 return Result.UNKNOWN_FAILURE;
240 }
241 }
242
243 @Override
244 /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) {
245 try {
246 onCompleteCallback.onTransactionComplete(result);
247 } catch (RemoteException e) {
248 Log.e(TAG, "RemoteException while calling client onTransactionComplete", e);
249 }
250 }
251 };
252 }
253
254 /**
Arthur Ishiguro7a23a962017-11-01 10:52:28 -0700255 * Creates a transaction for querying for a list of nanoapps.
256 *
257 * @param contextHubId the ID of the hub to query
258 * @param onCompleteCallback the client on complete callback
259 * @return the generated transaction
260 */
261 /* package */ ContextHubServiceTransaction createQueryTransaction(
262 int contextHubId, IContextHubTransactionCallback onCompleteCallback) {
263 return new ContextHubServiceTransaction(
264 mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_QUERY_NANOAPPS) {
265 @Override
266 /* package */ int onTransact() {
267 try {
268 return mContextHubProxy.queryApps(contextHubId);
269 } catch (RemoteException e) {
Arthur Ishiguro75a95692017-11-29 09:01:37 -0800270 Log.e(TAG, "RemoteException while trying to query for nanoapps", e);
Arthur Ishiguro7a23a962017-11-01 10:52:28 -0700271 return Result.UNKNOWN_FAILURE;
272 }
273 }
274
275 @Override
Arthur Ishiguro57f00902017-11-29 11:54:51 -0800276 /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) {
Arthur Ishiguro75a95692017-11-29 09:01:37 -0800277 onQueryResponse(result, Collections.emptyList());
Arthur Ishiguro7a23a962017-11-01 10:52:28 -0700278 }
279
280 @Override
Arthur Ishiguro57f00902017-11-29 11:54:51 -0800281 /* package */ void onQueryResponse(
282 @ContextHubTransaction.Result int result, List<NanoAppState> nanoAppStateList) {
Arthur Ishiguro7a23a962017-11-01 10:52:28 -0700283 try {
284 onCompleteCallback.onQueryResponse(result, nanoAppStateList);
285 } catch (RemoteException e) {
Arthur Ishiguro75a95692017-11-29 09:01:37 -0800286 Log.e(TAG, "RemoteException while calling client onQueryComplete", e);
Arthur Ishiguro7a23a962017-11-01 10:52:28 -0700287 }
288 }
289 };
290 }
291
292 /**
293 * Adds a new transaction to the queue.
294 *
295 * If there was no pending transaction at the time, the transaction that was added will be
Arthur Ishiguroe60f91a2017-11-30 09:48:00 -0800296 * started in this method. If there were too many transactions in the queue, an exception will
297 * be thrown.
Arthur Ishiguro7a23a962017-11-01 10:52:28 -0700298 *
299 * @param transaction the transaction to add
300 * @throws IllegalStateException if the queue is full
301 */
302 /* package */
303 synchronized void addTransaction(
304 ContextHubServiceTransaction transaction) throws IllegalStateException {
305 if (mTransactionQueue.size() == MAX_PENDING_REQUESTS) {
Arthur Ishiguroe60f91a2017-11-30 09:48:00 -0800306 throw new IllegalStateException("Transaction queue is full (capacity = "
Arthur Ishiguro7a23a962017-11-01 10:52:28 -0700307 + MAX_PENDING_REQUESTS + ")");
308 }
309 mTransactionQueue.add(transaction);
310
311 if (mTransactionQueue.size() == 1) {
312 startNextTransaction();
313 }
314 }
315
316 /**
317 * Handles a transaction response from a Context Hub.
318 *
319 * @param transactionId the transaction ID of the response
Arthur Ishiguro57f00902017-11-29 11:54:51 -0800320 * @param result the result of the transaction as defined by the HAL TransactionResult
Arthur Ishiguro7a23a962017-11-01 10:52:28 -0700321 */
322 /* package */
323 synchronized void onTransactionResponse(int transactionId, int result) {
324 ContextHubServiceTransaction transaction = mTransactionQueue.peek();
325 if (transaction == null) {
326 Log.w(TAG, "Received unexpected transaction response (no transaction pending)");
327 return;
328 }
329 if (transaction.getTransactionId() != transactionId) {
330 Log.w(TAG, "Received unexpected transaction response (expected ID = "
331 + transaction.getTransactionId() + ", received ID = " + transactionId + ")");
332 return;
333 }
334
Arthur Ishiguro57f00902017-11-29 11:54:51 -0800335 transaction.onTransactionComplete(
336 (result == TransactionResult.SUCCESS) ?
Arthur Ishiguro6100aa72017-12-20 09:35:00 -0800337 ContextHubTransaction.RESULT_SUCCESS :
338 ContextHubTransaction.RESULT_FAILED_AT_HUB);
Arthur Ishiguro7a23a962017-11-01 10:52:28 -0700339 removeTransactionAndStartNext();
340 }
341
342 /**
343 * Handles a query response from a Context Hub.
344 *
345 * @param nanoAppStateList the list of nanoapps included in the response
346 */
347 /* package */
348 synchronized void onQueryResponse(List<NanoAppState> nanoAppStateList) {
349 ContextHubServiceTransaction transaction = mTransactionQueue.peek();
350 if (transaction == null) {
351 Log.w(TAG, "Received unexpected query response (no transaction pending)");
352 return;
353 }
354 if (transaction.getTransactionType() != ContextHubTransaction.TYPE_QUERY_NANOAPPS) {
355 Log.w(TAG, "Received unexpected query response (expected " + transaction + ")");
356 return;
357 }
358
Arthur Ishiguro6100aa72017-12-20 09:35:00 -0800359 transaction.onQueryResponse(ContextHubTransaction.RESULT_SUCCESS, nanoAppStateList);
Arthur Ishiguro7a23a962017-11-01 10:52:28 -0700360 removeTransactionAndStartNext();
361 }
362
363 /**
364 * Handles a hub reset event by stopping a pending transaction and starting the next.
365 */
366 /* package */
367 synchronized void onHubReset() {
368 ContextHubServiceTransaction transaction = mTransactionQueue.peek();
369 if (transaction == null) {
370 return;
371 }
372
373 removeTransactionAndStartNext();
374 }
375
376 /**
377 * Pops the front transaction from the queue and starts the next pending transaction request.
378 *
379 * Removing elements from the transaction queue must only be done through this method. When a
380 * pending transaction is removed, the timeout timer is cancelled and the transaction is marked
381 * complete.
382 *
383 * It is assumed that the transaction queue is non-empty when this method is invoked, and that
384 * the caller has obtained a lock on this ContextHubTransactionManager object.
385 */
386 private void removeTransactionAndStartNext() {
387 mTimeoutFuture.cancel(false /* mayInterruptIfRunning */);
388
389 ContextHubServiceTransaction transaction = mTransactionQueue.remove();
390 transaction.setComplete();
391
392 if (!mTransactionQueue.isEmpty()) {
393 startNextTransaction();
394 }
395 }
396
397 /**
398 * Starts the next pending transaction request.
399 *
400 * Starting new transactions must only be done through this method. This method continues to
401 * process the transaction queue as long as there are pending requests, and no transaction is
402 * pending.
403 *
404 * It is assumed that the caller has obtained a lock on this ContextHubTransactionManager
405 * object.
406 */
407 private void startNextTransaction() {
408 int result = Result.UNKNOWN_FAILURE;
409 while (result != Result.OK && !mTransactionQueue.isEmpty()) {
410 ContextHubServiceTransaction transaction = mTransactionQueue.peek();
411 result = transaction.onTransact();
412
413 if (result == Result.OK) {
414 Runnable onTimeoutFunc = () -> {
415 synchronized (this) {
416 if (!transaction.isComplete()) {
417 Log.d(TAG, transaction + " timed out");
Arthur Ishiguro75a95692017-11-29 09:01:37 -0800418 transaction.onTransactionComplete(
Arthur Ishiguro6100aa72017-12-20 09:35:00 -0800419 ContextHubTransaction.RESULT_FAILED_TIMEOUT);
Arthur Ishiguro7a23a962017-11-01 10:52:28 -0700420
421 removeTransactionAndStartNext();
422 }
423 }
424 };
425
426 long timeoutSeconds = transaction.getTimeout(TimeUnit.SECONDS);
427 mTimeoutFuture = mTimeoutExecutor.schedule(onTimeoutFunc, timeoutSeconds,
428 TimeUnit.SECONDS);
429 } else {
Arthur Ishiguro75a95692017-11-29 09:01:37 -0800430 transaction.onTransactionComplete(
431 ContextHubServiceUtil.toTransactionResult(result));
Arthur Ishiguro7a23a962017-11-01 10:52:28 -0700432 mTransactionQueue.remove();
433 }
434 }
435 }
436}