• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.location.contexthub;
18 
19 import android.hardware.contexthub.V1_0.IContexthub;
20 import android.hardware.contexthub.V1_0.Result;
21 import android.hardware.contexthub.V1_0.TransactionResult;
22 import android.hardware.location.ContextHubTransaction;
23 import android.hardware.location.IContextHubTransactionCallback;
24 import android.hardware.location.NanoAppBinary;
25 import android.hardware.location.NanoAppState;
26 import android.os.RemoteException;
27 import android.util.Log;
28 
29 import java.text.DateFormat;
30 import java.text.SimpleDateFormat;
31 import java.util.ArrayDeque;
32 import java.util.Collections;
33 import java.util.Date;
34 import java.util.Iterator;
35 import java.util.List;
36 import java.util.concurrent.ScheduledFuture;
37 import java.util.concurrent.ScheduledThreadPoolExecutor;
38 import java.util.concurrent.TimeUnit;
39 import java.util.concurrent.atomic.AtomicInteger;
40 
41 /**
42  * Manages transactions at the Context Hub Service.
43  *
44  * This class maintains a queue of transaction requests made to the ContextHubService by clients,
45  * and executes them through the Context Hub. At any point in time, either the transaction queue is
46  * empty, or there is a pending transaction that is waiting for an asynchronous response from the
47  * hub. This class also handles synchronous errors and timeouts of each transaction.
48  *
49  * @hide
50  */
51 /* package */ class ContextHubTransactionManager {
52     private static final String TAG = "ContextHubTransactionManager";
53 
54     /*
55      * Maximum number of transaction requests that can be pending at a time
56      */
57     private static final int MAX_PENDING_REQUESTS = 10000;
58 
59     /*
60      * The DateFormat for printing TransactionRecord.
61      */
62     private static final DateFormat DATE_FORMAT = new SimpleDateFormat("MM/dd HH:mm:ss.SSS");
63 
64     /*
65      * The proxy to talk to the Context Hub
66      */
67     private final IContexthub mContextHubProxy;
68 
69     /*
70      * The manager for all clients for the service.
71      */
72     private final ContextHubClientManager mClientManager;
73 
74     /*
75      * The nanoapp state manager for the service
76      */
77     private final NanoAppStateManager mNanoAppStateManager;
78 
79     /*
80      * A queue containing the current transactions
81      */
82     private final ArrayDeque<ContextHubServiceTransaction> mTransactionQueue = new ArrayDeque<>();
83 
84     /*
85      * The next available transaction ID
86      */
87     private final AtomicInteger mNextAvailableId = new AtomicInteger();
88 
89     /*
90      * An executor and the future object for scheduling timeout timers
91      */
92     private final ScheduledThreadPoolExecutor mTimeoutExecutor = new ScheduledThreadPoolExecutor(1);
93     private ScheduledFuture<?> mTimeoutFuture = null;
94 
95     /*
96      * The list of previous transaction records.
97      */
98     private static final int NUM_TRANSACTION_RECORDS = 20;
99     private final ConcurrentLinkedEvictingDeque<TransactionRecord> mTransactionRecordDeque =
100             new ConcurrentLinkedEvictingDeque<>(NUM_TRANSACTION_RECORDS);
101 
102     /**
103      * A container class to store a record of transactions.
104      */
105     private class TransactionRecord {
106         private final String mTransaction;
107         private final long mTimestamp;
108 
TransactionRecord(String transaction)109         TransactionRecord(String transaction) {
110             mTransaction = transaction;
111             mTimestamp = System.currentTimeMillis();
112         }
113 
114         // TODO: Add dump to proto here
115 
116         @Override
toString()117         public String toString() {
118             return DATE_FORMAT.format(new Date(mTimestamp)) + " " + mTransaction;
119         }
120     }
121 
ContextHubTransactionManager( IContexthub contextHubProxy, ContextHubClientManager clientManager, NanoAppStateManager nanoAppStateManager)122     /* package */ ContextHubTransactionManager(
123             IContexthub contextHubProxy, ContextHubClientManager clientManager,
124             NanoAppStateManager nanoAppStateManager) {
125         mContextHubProxy = contextHubProxy;
126         mClientManager = clientManager;
127         mNanoAppStateManager = nanoAppStateManager;
128     }
129 
130     /**
131      * Creates a transaction for loading a nanoapp.
132      *
133      * @param contextHubId       the ID of the hub to load the nanoapp to
134      * @param nanoAppBinary      the binary of the nanoapp to load
135      * @param onCompleteCallback the client on complete callback
136      * @return the generated transaction
137      */
createLoadTransaction( int contextHubId, NanoAppBinary nanoAppBinary, IContextHubTransactionCallback onCompleteCallback, String packageName)138     /* package */ ContextHubServiceTransaction createLoadTransaction(
139             int contextHubId, NanoAppBinary nanoAppBinary,
140             IContextHubTransactionCallback onCompleteCallback, String packageName) {
141         return new ContextHubServiceTransaction(
142                 mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_LOAD_NANOAPP,
143                 nanoAppBinary.getNanoAppId(), packageName) {
144             @Override
145                 /* package */ int onTransact() {
146                 android.hardware.contexthub.V1_0.NanoAppBinary hidlNanoAppBinary =
147                         ContextHubServiceUtil.createHidlNanoAppBinary(nanoAppBinary);
148                 try {
149                     return mContextHubProxy.loadNanoApp(
150                             contextHubId, hidlNanoAppBinary, this.getTransactionId());
151                 } catch (RemoteException e) {
152                     Log.e(TAG, "RemoteException while trying to load nanoapp with ID 0x" +
153                             Long.toHexString(nanoAppBinary.getNanoAppId()), e);
154                     return Result.UNKNOWN_FAILURE;
155                 }
156             }
157 
158             @Override
159                 /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) {
160                 if (result == ContextHubTransaction.RESULT_SUCCESS) {
161                     // NOTE: The legacy JNI code used to do a query right after a load success
162                     // to synchronize the service cache. Instead store the binary that was
163                     // requested to load to update the cache later without doing a query.
164                     mNanoAppStateManager.addNanoAppInstance(
165                             contextHubId, nanoAppBinary.getNanoAppId(),
166                             nanoAppBinary.getNanoAppVersion());
167                 }
168                 try {
169                     onCompleteCallback.onTransactionComplete(result);
170                     if (result == ContextHubTransaction.RESULT_SUCCESS) {
171                         mClientManager.onNanoAppLoaded(contextHubId, nanoAppBinary.getNanoAppId());
172                     }
173                 } catch (RemoteException e) {
174                     Log.e(TAG, "RemoteException while calling client onTransactionComplete", e);
175                 }
176             }
177         };
178     }
179 
180     /**
181      * Creates a transaction for unloading a nanoapp.
182      *
183      * @param contextHubId       the ID of the hub to unload the nanoapp from
184      * @param nanoAppId          the ID of the nanoapp to unload
185      * @param onCompleteCallback the client on complete callback
186      * @return the generated transaction
187      */
188     /* package */ ContextHubServiceTransaction createUnloadTransaction(
189             int contextHubId, long nanoAppId, IContextHubTransactionCallback onCompleteCallback,
190             String packageName) {
191         return new ContextHubServiceTransaction(
192                 mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_UNLOAD_NANOAPP,
193                 nanoAppId, packageName) {
194             @Override
195                 /* package */ int onTransact() {
196                 try {
197                     return mContextHubProxy.unloadNanoApp(
198                             contextHubId, nanoAppId, this.getTransactionId());
199                 } catch (RemoteException e) {
200                     Log.e(TAG, "RemoteException while trying to unload nanoapp with ID 0x" +
201                             Long.toHexString(nanoAppId), e);
202                     return Result.UNKNOWN_FAILURE;
203                 }
204             }
205 
206             @Override
207                 /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) {
208                 if (result == ContextHubTransaction.RESULT_SUCCESS) {
209                     mNanoAppStateManager.removeNanoAppInstance(contextHubId, nanoAppId);
210                 }
211                 try {
212                     onCompleteCallback.onTransactionComplete(result);
213                     if (result == ContextHubTransaction.RESULT_SUCCESS) {
214                         mClientManager.onNanoAppUnloaded(contextHubId, nanoAppId);
215                     }
216                 } catch (RemoteException e) {
217                     Log.e(TAG, "RemoteException while calling client onTransactionComplete", e);
218                 }
219             }
220         };
221     }
222 
223     /**
224      * Creates a transaction for enabling a nanoapp.
225      *
226      * @param contextHubId       the ID of the hub to enable the nanoapp on
227      * @param nanoAppId          the ID of the nanoapp to enable
228      * @param onCompleteCallback the client on complete callback
229      * @return the generated transaction
230      */
231     /* package */ ContextHubServiceTransaction createEnableTransaction(
232             int contextHubId, long nanoAppId, IContextHubTransactionCallback onCompleteCallback,
233             String packageName) {
234         return new ContextHubServiceTransaction(
235                 mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_ENABLE_NANOAPP,
236                 packageName) {
237             @Override
238                 /* package */ int onTransact() {
239                 try {
240                     return mContextHubProxy.enableNanoApp(
241                             contextHubId, nanoAppId, this.getTransactionId());
242                 } catch (RemoteException e) {
243                     Log.e(TAG, "RemoteException while trying to enable nanoapp with ID 0x" +
244                             Long.toHexString(nanoAppId), e);
245                     return Result.UNKNOWN_FAILURE;
246                 }
247             }
248 
249             @Override
250                 /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) {
251                 try {
252                     onCompleteCallback.onTransactionComplete(result);
253                 } catch (RemoteException e) {
254                     Log.e(TAG, "RemoteException while calling client onTransactionComplete", e);
255                 }
256             }
257         };
258     }
259 
260     /**
261      * Creates a transaction for disabling a nanoapp.
262      *
263      * @param contextHubId       the ID of the hub to disable the nanoapp on
264      * @param nanoAppId          the ID of the nanoapp to disable
265      * @param onCompleteCallback the client on complete callback
266      * @return the generated transaction
267      */
268     /* package */ ContextHubServiceTransaction createDisableTransaction(
269             int contextHubId, long nanoAppId, IContextHubTransactionCallback onCompleteCallback,
270             String packageName) {
271         return new ContextHubServiceTransaction(
272                 mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_DISABLE_NANOAPP,
273                 packageName) {
274             @Override
275                 /* package */ int onTransact() {
276                 try {
277                     return mContextHubProxy.disableNanoApp(
278                             contextHubId, nanoAppId, this.getTransactionId());
279                 } catch (RemoteException e) {
280                     Log.e(TAG, "RemoteException while trying to disable nanoapp with ID 0x" +
281                             Long.toHexString(nanoAppId), e);
282                     return Result.UNKNOWN_FAILURE;
283                 }
284             }
285 
286             @Override
287                 /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) {
288                 try {
289                     onCompleteCallback.onTransactionComplete(result);
290                 } catch (RemoteException e) {
291                     Log.e(TAG, "RemoteException while calling client onTransactionComplete", e);
292                 }
293             }
294         };
295     }
296 
297     /**
298      * Creates a transaction for querying for a list of nanoapps.
299      *
300      * @param contextHubId       the ID of the hub to query
301      * @param onCompleteCallback the client on complete callback
302      * @return the generated transaction
303      */
304     /* package */ ContextHubServiceTransaction createQueryTransaction(
305             int contextHubId, IContextHubTransactionCallback onCompleteCallback,
306             String packageName) {
307         return new ContextHubServiceTransaction(
308                 mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_QUERY_NANOAPPS,
309                 packageName) {
310             @Override
311                 /* package */ int onTransact() {
312                 try {
313                     return mContextHubProxy.queryApps(contextHubId);
314                 } catch (RemoteException e) {
315                     Log.e(TAG, "RemoteException while trying to query for nanoapps", e);
316                     return Result.UNKNOWN_FAILURE;
317                 }
318             }
319 
320             @Override
321                 /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) {
322                 onQueryResponse(result, Collections.emptyList());
323             }
324 
325             @Override
326                 /* package */ void onQueryResponse(
327                     @ContextHubTransaction.Result int result, List<NanoAppState> nanoAppStateList) {
328                 try {
329                     onCompleteCallback.onQueryResponse(result, nanoAppStateList);
330                 } catch (RemoteException e) {
331                     Log.e(TAG, "RemoteException while calling client onQueryComplete", e);
332                 }
333             }
334         };
335     }
336 
337     /**
338      * Adds a new transaction to the queue.
339      *
340      * If there was no pending transaction at the time, the transaction that was added will be
341      * started in this method. If there were too many transactions in the queue, an exception will
342      * be thrown.
343      *
344      * @param transaction the transaction to add
345      * @throws IllegalStateException if the queue is full
346      */
347     /* package */
348     synchronized void addTransaction(
349             ContextHubServiceTransaction transaction) throws IllegalStateException {
350         if (mTransactionQueue.size() == MAX_PENDING_REQUESTS) {
351             throw new IllegalStateException("Transaction queue is full (capacity = "
352                     + MAX_PENDING_REQUESTS + ")");
353         }
354         mTransactionQueue.add(transaction);
355         mTransactionRecordDeque.add(new TransactionRecord(transaction.toString()));
356 
357         if (mTransactionQueue.size() == 1) {
358             startNextTransaction();
359         }
360     }
361 
362     /**
363      * Handles a transaction response from a Context Hub.
364      *
365      * @param transactionId the transaction ID of the response
366      * @param result        the result of the transaction as defined by the HAL TransactionResult
367      */
368     /* package */
369     synchronized void onTransactionResponse(int transactionId, int result) {
370         ContextHubServiceTransaction transaction = mTransactionQueue.peek();
371         if (transaction == null) {
372             Log.w(TAG, "Received unexpected transaction response (no transaction pending)");
373             return;
374         }
375         if (transaction.getTransactionId() != transactionId) {
376             Log.w(TAG, "Received unexpected transaction response (expected ID = "
377                     + transaction.getTransactionId() + ", received ID = " + transactionId + ")");
378             return;
379         }
380 
381         transaction.onTransactionComplete(
382                 (result == TransactionResult.SUCCESS) ?
383                         ContextHubTransaction.RESULT_SUCCESS :
384                         ContextHubTransaction.RESULT_FAILED_AT_HUB);
385         removeTransactionAndStartNext();
386     }
387 
388     /**
389      * Handles a query response from a Context Hub.
390      *
391      * @param nanoAppStateList the list of nanoapps included in the response
392      */
393     /* package */
394     synchronized void onQueryResponse(List<NanoAppState> nanoAppStateList) {
395         ContextHubServiceTransaction transaction = mTransactionQueue.peek();
396         if (transaction == null) {
397             Log.w(TAG, "Received unexpected query response (no transaction pending)");
398             return;
399         }
400         if (transaction.getTransactionType() != ContextHubTransaction.TYPE_QUERY_NANOAPPS) {
401             Log.w(TAG, "Received unexpected query response (expected " + transaction + ")");
402             return;
403         }
404 
405         transaction.onQueryResponse(ContextHubTransaction.RESULT_SUCCESS, nanoAppStateList);
406         removeTransactionAndStartNext();
407     }
408 
409     /**
410      * Handles a hub reset event by stopping a pending transaction and starting the next.
411      */
412     /* package */
413     synchronized void onHubReset() {
414         ContextHubServiceTransaction transaction = mTransactionQueue.peek();
415         if (transaction == null) {
416             return;
417         }
418 
419         removeTransactionAndStartNext();
420     }
421 
422     /**
423      * Pops the front transaction from the queue and starts the next pending transaction request.
424      *
425      * Removing elements from the transaction queue must only be done through this method. When a
426      * pending transaction is removed, the timeout timer is cancelled and the transaction is marked
427      * complete.
428      *
429      * It is assumed that the transaction queue is non-empty when this method is invoked, and that
430      * the caller has obtained a lock on this ContextHubTransactionManager object.
431      */
432     private void removeTransactionAndStartNext() {
433         mTimeoutFuture.cancel(false /* mayInterruptIfRunning */);
434 
435         ContextHubServiceTransaction transaction = mTransactionQueue.remove();
436         transaction.setComplete();
437 
438         if (!mTransactionQueue.isEmpty()) {
439             startNextTransaction();
440         }
441     }
442 
443     /**
444      * Starts the next pending transaction request.
445      *
446      * Starting new transactions must only be done through this method. This method continues to
447      * process the transaction queue as long as there are pending requests, and no transaction is
448      * pending.
449      *
450      * It is assumed that the caller has obtained a lock on this ContextHubTransactionManager
451      * object.
452      */
453     private void startNextTransaction() {
454         int result = Result.UNKNOWN_FAILURE;
455         while (result != Result.OK && !mTransactionQueue.isEmpty()) {
456             ContextHubServiceTransaction transaction = mTransactionQueue.peek();
457             result = transaction.onTransact();
458 
459             if (result == Result.OK) {
460                 Runnable onTimeoutFunc = () -> {
461                     synchronized (this) {
462                         if (!transaction.isComplete()) {
463                             Log.d(TAG, transaction + " timed out");
464                             transaction.onTransactionComplete(
465                                     ContextHubTransaction.RESULT_FAILED_TIMEOUT);
466 
467                             removeTransactionAndStartNext();
468                         }
469                     }
470                 };
471 
472                 long timeoutSeconds = transaction.getTimeout(TimeUnit.SECONDS);
473                 mTimeoutFuture = mTimeoutExecutor.schedule(onTimeoutFunc, timeoutSeconds,
474                         TimeUnit.SECONDS);
475             } else {
476                 transaction.onTransactionComplete(
477                         ContextHubServiceUtil.toTransactionResult(result));
478                 mTransactionQueue.remove();
479             }
480         }
481     }
482 
483     @Override
484     public String toString() {
485         StringBuilder sb = new StringBuilder(100);
486         TransactionRecord[] arr;
487         synchronized (this) {
488             arr = mTransactionQueue.toArray(new TransactionRecord[0]);
489         }
490         for (int i = 0; i < arr.length; i++) {
491             sb.append(i + ": " + arr[i] + "\n");
492         }
493 
494         sb.append("Transaction History:\n");
495         Iterator<TransactionRecord> iterator = mTransactionRecordDeque.descendingIterator();
496         while (iterator.hasNext()) {
497             sb.append(iterator.next() + "\n");
498         }
499         return sb.toString();
500     }
501 }
502