blob: 47d9d5686140a5df0cfea9bb4739b9b13beee690 [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 /*
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 Ishiguro7a23a962017-11-01 10:52:28 -070066 * A queue containing the current transactions
67 */
68 private final ArrayDeque<ContextHubServiceTransaction> mTransactionQueue = new ArrayDeque<>();
69
70 /*
71 * The next available transaction ID
72 */
73 private final AtomicInteger mNextAvailableId = new AtomicInteger();
74
75 /*
76 * An executor and the future object for scheduling timeout timers
77 */
78 private final ScheduledThreadPoolExecutor mTimeoutExecutor = new ScheduledThreadPoolExecutor(1);
79 private ScheduledFuture<?> mTimeoutFuture = null;
80
Arthur Ishiguro0069f122017-11-20 15:07:14 -080081 /* package */ ContextHubTransactionManager(
82 IContexthub contextHubProxy, ContextHubClientManager clientManager) {
Arthur Ishiguro7a23a962017-11-01 10:52:28 -070083 mContextHubProxy = contextHubProxy;
Arthur Ishiguro0069f122017-11-20 15:07:14 -080084 mClientManager = clientManager;
Arthur Ishiguro7a23a962017-11-01 10:52:28 -070085 }
86
87 /**
88 * Creates a transaction for loading a nanoapp.
89 *
90 * @param contextHubId the ID of the hub to load the nanoapp to
91 * @param nanoAppBinary the binary of the nanoapp to load
92 * @param onCompleteCallback the client on complete callback
93 * @return the generated transaction
94 */
95 /* package */ ContextHubServiceTransaction createLoadTransaction(
96 int contextHubId, NanoAppBinary nanoAppBinary,
97 IContextHubTransactionCallback onCompleteCallback) {
98 return new ContextHubServiceTransaction(
99 mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_LOAD_NANOAPP) {
100 @Override
101 /* package */ int onTransact() {
102 android.hardware.contexthub.V1_0.NanoAppBinary hidlNanoAppBinary =
103 ContextHubServiceUtil.createHidlNanoAppBinary(nanoAppBinary);
104 try {
105 return mContextHubProxy.loadNanoApp(
106 contextHubId, hidlNanoAppBinary, this.getTransactionId());
107 } catch (RemoteException e) {
108 Log.e(TAG, "RemoteException while trying to load nanoapp with ID 0x" +
109 Long.toHexString(nanoAppBinary.getNanoAppId()));
110 return Result.UNKNOWN_FAILURE;
111 }
112 }
113
114 @Override
115 /* package */ void onTimeout() {
116 onTransactionComplete(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT);
117 }
118
119 @Override
120 /* package */ void onTransactionComplete(int result) {
121 try {
122 onCompleteCallback.onTransactionComplete(result);
Arthur Ishiguro0069f122017-11-20 15:07:14 -0800123 if (result == Result.OK) {
124 mClientManager.onNanoAppLoaded(contextHubId, nanoAppBinary.getNanoAppId());
125 }
Arthur Ishiguro7a23a962017-11-01 10:52:28 -0700126 } catch (RemoteException e) {
127 Log.e(TAG, "RemoteException while calling client onTransactionComplete");
128 }
129 }
130 };
131 }
132
133 /**
134 * Creates a transaction for unloading a nanoapp.
135 *
136 * @param contextHubId the ID of the hub to load the nanoapp to
137 * @param nanoAppId the ID of the nanoapp to unload
138 * @param onCompleteCallback the client on complete callback
139 * @return the generated transaction
140 */
141 /* package */ ContextHubServiceTransaction createUnloadTransaction(
142 int contextHubId, long nanoAppId, IContextHubTransactionCallback onCompleteCallback) {
143 return new ContextHubServiceTransaction(
144 mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_UNLOAD_NANOAPP) {
145 @Override
146 /* package */ int onTransact() {
147 try {
148 return mContextHubProxy.unloadNanoApp(
149 contextHubId, nanoAppId, this.getTransactionId());
150 } catch (RemoteException e) {
151 Log.e(TAG, "RemoteException while trying to unload nanoapp with ID 0x" +
152 Long.toHexString(nanoAppId));
153 return Result.UNKNOWN_FAILURE;
154 }
155 }
156
157 @Override
158 /* package */ void onTimeout() {
159 onTransactionComplete(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT);
160 }
161
162 @Override
163 /* package */ void onTransactionComplete(int result) {
164 try {
165 onCompleteCallback.onTransactionComplete(result);
Arthur Ishiguro0069f122017-11-20 15:07:14 -0800166 if (result == Result.OK) {
167 mClientManager.onNanoAppUnloaded(contextHubId, nanoAppId);
168 }
Arthur Ishiguro7a23a962017-11-01 10:52:28 -0700169 } catch (RemoteException e) {
170 Log.e(TAG, "RemoteException while calling client onTransactionComplete");
171 }
172 }
173 };
174 }
175
176 /**
177 * Creates a transaction for querying for a list of nanoapps.
178 *
179 * @param contextHubId the ID of the hub to query
180 * @param onCompleteCallback the client on complete callback
181 * @return the generated transaction
182 */
183 /* package */ ContextHubServiceTransaction createQueryTransaction(
184 int contextHubId, IContextHubTransactionCallback onCompleteCallback) {
185 return new ContextHubServiceTransaction(
186 mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_QUERY_NANOAPPS) {
187 @Override
188 /* package */ int onTransact() {
189 try {
190 return mContextHubProxy.queryApps(contextHubId);
191 } catch (RemoteException e) {
192 Log.e(TAG, "RemoteException while trying to query for nanoapps");
193 return Result.UNKNOWN_FAILURE;
194 }
195 }
196
197 @Override
198 /* package */ void onTimeout() {
199 onQueryResponse(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT,
200 Collections.emptyList());
201 }
202
203 @Override
204 /* package */ void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
205 try {
206 onCompleteCallback.onQueryResponse(result, nanoAppStateList);
207 } catch (RemoteException e) {
208 Log.e(TAG, "RemoteException while calling client onQueryComplete");
209 }
210 }
211 };
212 }
213
214 /**
215 * Adds a new transaction to the queue.
216 *
217 * If there was no pending transaction at the time, the transaction that was added will be
218 * started in this method.
219 *
220 * @param transaction the transaction to add
221 * @throws IllegalStateException if the queue is full
222 */
223 /* package */
224 synchronized void addTransaction(
225 ContextHubServiceTransaction transaction) throws IllegalStateException {
226 if (mTransactionQueue.size() == MAX_PENDING_REQUESTS) {
227 throw new IllegalStateException("Transaction transaction queue is full (capacity = "
228 + MAX_PENDING_REQUESTS + ")");
229 }
230 mTransactionQueue.add(transaction);
231
232 if (mTransactionQueue.size() == 1) {
233 startNextTransaction();
234 }
235 }
236
237 /**
238 * Handles a transaction response from a Context Hub.
239 *
240 * @param transactionId the transaction ID of the response
241 * @param result the result of the transaction
242 */
243 /* package */
244 synchronized void onTransactionResponse(int transactionId, int result) {
245 ContextHubServiceTransaction transaction = mTransactionQueue.peek();
246 if (transaction == null) {
247 Log.w(TAG, "Received unexpected transaction response (no transaction pending)");
248 return;
249 }
250 if (transaction.getTransactionId() != transactionId) {
251 Log.w(TAG, "Received unexpected transaction response (expected ID = "
252 + transaction.getTransactionId() + ", received ID = " + transactionId + ")");
253 return;
254 }
255
256 transaction.onTransactionComplete(result);
257 removeTransactionAndStartNext();
258 }
259
260 /**
261 * Handles a query response from a Context Hub.
262 *
263 * @param nanoAppStateList the list of nanoapps included in the response
264 */
265 /* package */
266 synchronized void onQueryResponse(List<NanoAppState> nanoAppStateList) {
267 ContextHubServiceTransaction transaction = mTransactionQueue.peek();
268 if (transaction == null) {
269 Log.w(TAG, "Received unexpected query response (no transaction pending)");
270 return;
271 }
272 if (transaction.getTransactionType() != ContextHubTransaction.TYPE_QUERY_NANOAPPS) {
273 Log.w(TAG, "Received unexpected query response (expected " + transaction + ")");
274 return;
275 }
276
277 transaction.onQueryResponse(TransactionResult.SUCCESS, nanoAppStateList);
278 removeTransactionAndStartNext();
279 }
280
281 /**
282 * Handles a hub reset event by stopping a pending transaction and starting the next.
283 */
284 /* package */
285 synchronized void onHubReset() {
286 ContextHubServiceTransaction transaction = mTransactionQueue.peek();
287 if (transaction == null) {
288 return;
289 }
290
291 removeTransactionAndStartNext();
292 }
293
294 /**
295 * Pops the front transaction from the queue and starts the next pending transaction request.
296 *
297 * Removing elements from the transaction queue must only be done through this method. When a
298 * pending transaction is removed, the timeout timer is cancelled and the transaction is marked
299 * complete.
300 *
301 * It is assumed that the transaction queue is non-empty when this method is invoked, and that
302 * the caller has obtained a lock on this ContextHubTransactionManager object.
303 */
304 private void removeTransactionAndStartNext() {
305 mTimeoutFuture.cancel(false /* mayInterruptIfRunning */);
306
307 ContextHubServiceTransaction transaction = mTransactionQueue.remove();
308 transaction.setComplete();
309
310 if (!mTransactionQueue.isEmpty()) {
311 startNextTransaction();
312 }
313 }
314
315 /**
316 * Starts the next pending transaction request.
317 *
318 * Starting new transactions must only be done through this method. This method continues to
319 * process the transaction queue as long as there are pending requests, and no transaction is
320 * pending.
321 *
322 * It is assumed that the caller has obtained a lock on this ContextHubTransactionManager
323 * object.
324 */
325 private void startNextTransaction() {
326 int result = Result.UNKNOWN_FAILURE;
327 while (result != Result.OK && !mTransactionQueue.isEmpty()) {
328 ContextHubServiceTransaction transaction = mTransactionQueue.peek();
329 result = transaction.onTransact();
330
331 if (result == Result.OK) {
332 Runnable onTimeoutFunc = () -> {
333 synchronized (this) {
334 if (!transaction.isComplete()) {
335 Log.d(TAG, transaction + " timed out");
336 transaction.onTimeout();
337
338 removeTransactionAndStartNext();
339 }
340 }
341 };
342
343 long timeoutSeconds = transaction.getTimeout(TimeUnit.SECONDS);
344 mTimeoutFuture = mTimeoutExecutor.schedule(onTimeoutFunc, timeoutSeconds,
345 TimeUnit.SECONDS);
346 } else {
347 mTransactionQueue.remove();
348 }
349 }
350 }
351}