| /* |
| * Copyright 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.location; |
| |
| import android.hardware.contexthub.V1_0.IContexthub; |
| import android.hardware.contexthub.V1_0.Result; |
| import android.hardware.contexthub.V1_0.TransactionResult; |
| import android.hardware.location.ContextHubTransaction; |
| import android.hardware.location.IContextHubTransactionCallback; |
| import android.hardware.location.NanoAppBinary; |
| import android.hardware.location.NanoAppState; |
| import android.os.RemoteException; |
| import android.util.Log; |
| |
| import java.util.ArrayDeque; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.concurrent.ScheduledThreadPoolExecutor; |
| import java.util.concurrent.ScheduledFuture; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| /** |
| * Manages transactions at the Context Hub Service. |
| * |
| * This class maintains a queue of transaction requests made to the ContextHubService by clients, |
| * and executes them through the Context Hub. At any point in time, either the transaction queue is |
| * empty, or there is a pending transaction that is waiting for an asynchronous response from the |
| * hub. This class also handles synchronous errors and timeouts of each transaction. |
| * |
| * @hide |
| */ |
| /* package */ class ContextHubTransactionManager { |
| private static final String TAG = "ContextHubTransactionManager"; |
| |
| /* |
| * Maximum number of transaction requests that can be pending at a time |
| */ |
| private static final int MAX_PENDING_REQUESTS = 10000; |
| |
| /* |
| * The proxy to talk to the Context Hub |
| */ |
| private final IContexthub mContextHubProxy; |
| |
| /* |
| * The manager for all clients for the service. |
| */ |
| private final ContextHubClientManager mClientManager; |
| |
| /* |
| * The nanoapp state manager for the service |
| */ |
| private final NanoAppStateManager mNanoAppStateManager; |
| |
| /* |
| * A queue containing the current transactions |
| */ |
| private final ArrayDeque<ContextHubServiceTransaction> mTransactionQueue = new ArrayDeque<>(); |
| |
| /* |
| * The next available transaction ID |
| */ |
| private final AtomicInteger mNextAvailableId = new AtomicInteger(); |
| |
| /* |
| * An executor and the future object for scheduling timeout timers |
| */ |
| private final ScheduledThreadPoolExecutor mTimeoutExecutor = new ScheduledThreadPoolExecutor(1); |
| private ScheduledFuture<?> mTimeoutFuture = null; |
| |
| /* package */ ContextHubTransactionManager( |
| IContexthub contextHubProxy, ContextHubClientManager clientManager, |
| NanoAppStateManager nanoAppStateManager) { |
| mContextHubProxy = contextHubProxy; |
| mClientManager = clientManager; |
| mNanoAppStateManager = nanoAppStateManager; |
| } |
| |
| /** |
| * Creates a transaction for loading a nanoapp. |
| * |
| * @param contextHubId the ID of the hub to load the nanoapp to |
| * @param nanoAppBinary the binary of the nanoapp to load |
| * @param onCompleteCallback the client on complete callback |
| * @return the generated transaction |
| */ |
| /* package */ ContextHubServiceTransaction createLoadTransaction( |
| int contextHubId, NanoAppBinary nanoAppBinary, |
| IContextHubTransactionCallback onCompleteCallback) { |
| return new ContextHubServiceTransaction( |
| mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_LOAD_NANOAPP) { |
| @Override |
| /* package */ int onTransact() { |
| android.hardware.contexthub.V1_0.NanoAppBinary hidlNanoAppBinary = |
| ContextHubServiceUtil.createHidlNanoAppBinary(nanoAppBinary); |
| try { |
| return mContextHubProxy.loadNanoApp( |
| contextHubId, hidlNanoAppBinary, this.getTransactionId()); |
| } catch (RemoteException e) { |
| Log.e(TAG, "RemoteException while trying to load nanoapp with ID 0x" + |
| Long.toHexString(nanoAppBinary.getNanoAppId()), e); |
| return Result.UNKNOWN_FAILURE; |
| } |
| } |
| |
| @Override |
| /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) { |
| if (result == ContextHubTransaction.RESULT_SUCCESS) { |
| // NOTE: The legacy JNI code used to do a query right after a load success |
| // to synchronize the service cache. Instead store the binary that was |
| // requested to load to update the cache later without doing a query. |
| mNanoAppStateManager.addNanoAppInstance( |
| contextHubId, nanoAppBinary.getNanoAppId(), |
| nanoAppBinary.getNanoAppVersion()); |
| } |
| try { |
| onCompleteCallback.onTransactionComplete(result); |
| if (result == ContextHubTransaction.RESULT_SUCCESS) { |
| mClientManager.onNanoAppLoaded(contextHubId, nanoAppBinary.getNanoAppId()); |
| } |
| } catch (RemoteException e) { |
| Log.e(TAG, "RemoteException while calling client onTransactionComplete", e); |
| } |
| } |
| }; |
| } |
| |
| /** |
| * Creates a transaction for unloading a nanoapp. |
| * |
| * @param contextHubId the ID of the hub to unload the nanoapp from |
| * @param nanoAppId the ID of the nanoapp to unload |
| * @param onCompleteCallback the client on complete callback |
| * @return the generated transaction |
| */ |
| /* package */ ContextHubServiceTransaction createUnloadTransaction( |
| int contextHubId, long nanoAppId, IContextHubTransactionCallback onCompleteCallback) { |
| return new ContextHubServiceTransaction( |
| mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_UNLOAD_NANOAPP) { |
| @Override |
| /* package */ int onTransact() { |
| try { |
| return mContextHubProxy.unloadNanoApp( |
| contextHubId, nanoAppId, this.getTransactionId()); |
| } catch (RemoteException e) { |
| Log.e(TAG, "RemoteException while trying to unload nanoapp with ID 0x" + |
| Long.toHexString(nanoAppId), e); |
| return Result.UNKNOWN_FAILURE; |
| } |
| } |
| |
| @Override |
| /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) { |
| if (result == ContextHubTransaction.RESULT_SUCCESS) { |
| mNanoAppStateManager.removeNanoAppInstance(contextHubId, nanoAppId); |
| } |
| try { |
| onCompleteCallback.onTransactionComplete(result); |
| if (result == ContextHubTransaction.RESULT_SUCCESS) { |
| mClientManager.onNanoAppUnloaded(contextHubId, nanoAppId); |
| } |
| } catch (RemoteException e) { |
| Log.e(TAG, "RemoteException while calling client onTransactionComplete", e); |
| } |
| } |
| }; |
| } |
| |
| /** |
| * Creates a transaction for enabling a nanoapp. |
| * |
| * @param contextHubId the ID of the hub to enable the nanoapp on |
| * @param nanoAppId the ID of the nanoapp to enable |
| * @param onCompleteCallback the client on complete callback |
| * @return the generated transaction |
| */ |
| /* package */ ContextHubServiceTransaction createEnableTransaction( |
| int contextHubId, long nanoAppId, IContextHubTransactionCallback onCompleteCallback) { |
| return new ContextHubServiceTransaction( |
| mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_ENABLE_NANOAPP) { |
| @Override |
| /* package */ int onTransact() { |
| try { |
| return mContextHubProxy.enableNanoApp( |
| contextHubId, nanoAppId, this.getTransactionId()); |
| } catch (RemoteException e) { |
| Log.e(TAG, "RemoteException while trying to enable nanoapp with ID 0x" + |
| Long.toHexString(nanoAppId), e); |
| return Result.UNKNOWN_FAILURE; |
| } |
| } |
| |
| @Override |
| /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) { |
| try { |
| onCompleteCallback.onTransactionComplete(result); |
| } catch (RemoteException e) { |
| Log.e(TAG, "RemoteException while calling client onTransactionComplete", e); |
| } |
| } |
| }; |
| } |
| |
| /** |
| * Creates a transaction for disabling a nanoapp. |
| * |
| * @param contextHubId the ID of the hub to disable the nanoapp on |
| * @param nanoAppId the ID of the nanoapp to disable |
| * @param onCompleteCallback the client on complete callback |
| * @return the generated transaction |
| */ |
| /* package */ ContextHubServiceTransaction createDisableTransaction( |
| int contextHubId, long nanoAppId, IContextHubTransactionCallback onCompleteCallback) { |
| return new ContextHubServiceTransaction( |
| mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_DISABLE_NANOAPP) { |
| @Override |
| /* package */ int onTransact() { |
| try { |
| return mContextHubProxy.disableNanoApp( |
| contextHubId, nanoAppId, this.getTransactionId()); |
| } catch (RemoteException e) { |
| Log.e(TAG, "RemoteException while trying to disable nanoapp with ID 0x" + |
| Long.toHexString(nanoAppId), e); |
| return Result.UNKNOWN_FAILURE; |
| } |
| } |
| |
| @Override |
| /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) { |
| try { |
| onCompleteCallback.onTransactionComplete(result); |
| } catch (RemoteException e) { |
| Log.e(TAG, "RemoteException while calling client onTransactionComplete", e); |
| } |
| } |
| }; |
| } |
| |
| /** |
| * Creates a transaction for querying for a list of nanoapps. |
| * |
| * @param contextHubId the ID of the hub to query |
| * @param onCompleteCallback the client on complete callback |
| * @return the generated transaction |
| */ |
| /* package */ ContextHubServiceTransaction createQueryTransaction( |
| int contextHubId, IContextHubTransactionCallback onCompleteCallback) { |
| return new ContextHubServiceTransaction( |
| mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_QUERY_NANOAPPS) { |
| @Override |
| /* package */ int onTransact() { |
| try { |
| return mContextHubProxy.queryApps(contextHubId); |
| } catch (RemoteException e) { |
| Log.e(TAG, "RemoteException while trying to query for nanoapps", e); |
| return Result.UNKNOWN_FAILURE; |
| } |
| } |
| |
| @Override |
| /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) { |
| onQueryResponse(result, Collections.emptyList()); |
| } |
| |
| @Override |
| /* package */ void onQueryResponse( |
| @ContextHubTransaction.Result int result, List<NanoAppState> nanoAppStateList) { |
| try { |
| onCompleteCallback.onQueryResponse(result, nanoAppStateList); |
| } catch (RemoteException e) { |
| Log.e(TAG, "RemoteException while calling client onQueryComplete", e); |
| } |
| } |
| }; |
| } |
| |
| /** |
| * Adds a new transaction to the queue. |
| * |
| * If there was no pending transaction at the time, the transaction that was added will be |
| * started in this method. If there were too many transactions in the queue, an exception will |
| * be thrown. |
| * |
| * @param transaction the transaction to add |
| * @throws IllegalStateException if the queue is full |
| */ |
| /* package */ |
| synchronized void addTransaction( |
| ContextHubServiceTransaction transaction) throws IllegalStateException { |
| if (mTransactionQueue.size() == MAX_PENDING_REQUESTS) { |
| throw new IllegalStateException("Transaction queue is full (capacity = " |
| + MAX_PENDING_REQUESTS + ")"); |
| } |
| mTransactionQueue.add(transaction); |
| |
| if (mTransactionQueue.size() == 1) { |
| startNextTransaction(); |
| } |
| } |
| |
| /** |
| * Handles a transaction response from a Context Hub. |
| * |
| * @param transactionId the transaction ID of the response |
| * @param result the result of the transaction as defined by the HAL TransactionResult |
| */ |
| /* package */ |
| synchronized void onTransactionResponse(int transactionId, int result) { |
| ContextHubServiceTransaction transaction = mTransactionQueue.peek(); |
| if (transaction == null) { |
| Log.w(TAG, "Received unexpected transaction response (no transaction pending)"); |
| return; |
| } |
| if (transaction.getTransactionId() != transactionId) { |
| Log.w(TAG, "Received unexpected transaction response (expected ID = " |
| + transaction.getTransactionId() + ", received ID = " + transactionId + ")"); |
| return; |
| } |
| |
| transaction.onTransactionComplete( |
| (result == TransactionResult.SUCCESS) ? |
| ContextHubTransaction.RESULT_SUCCESS : |
| ContextHubTransaction.RESULT_FAILED_AT_HUB); |
| removeTransactionAndStartNext(); |
| } |
| |
| /** |
| * Handles a query response from a Context Hub. |
| * |
| * @param nanoAppStateList the list of nanoapps included in the response |
| */ |
| /* package */ |
| synchronized void onQueryResponse(List<NanoAppState> nanoAppStateList) { |
| ContextHubServiceTransaction transaction = mTransactionQueue.peek(); |
| if (transaction == null) { |
| Log.w(TAG, "Received unexpected query response (no transaction pending)"); |
| return; |
| } |
| if (transaction.getTransactionType() != ContextHubTransaction.TYPE_QUERY_NANOAPPS) { |
| Log.w(TAG, "Received unexpected query response (expected " + transaction + ")"); |
| return; |
| } |
| |
| transaction.onQueryResponse(ContextHubTransaction.RESULT_SUCCESS, nanoAppStateList); |
| removeTransactionAndStartNext(); |
| } |
| |
| /** |
| * Handles a hub reset event by stopping a pending transaction and starting the next. |
| */ |
| /* package */ |
| synchronized void onHubReset() { |
| ContextHubServiceTransaction transaction = mTransactionQueue.peek(); |
| if (transaction == null) { |
| return; |
| } |
| |
| removeTransactionAndStartNext(); |
| } |
| |
| /** |
| * Pops the front transaction from the queue and starts the next pending transaction request. |
| * |
| * Removing elements from the transaction queue must only be done through this method. When a |
| * pending transaction is removed, the timeout timer is cancelled and the transaction is marked |
| * complete. |
| * |
| * It is assumed that the transaction queue is non-empty when this method is invoked, and that |
| * the caller has obtained a lock on this ContextHubTransactionManager object. |
| */ |
| private void removeTransactionAndStartNext() { |
| mTimeoutFuture.cancel(false /* mayInterruptIfRunning */); |
| |
| ContextHubServiceTransaction transaction = mTransactionQueue.remove(); |
| transaction.setComplete(); |
| |
| if (!mTransactionQueue.isEmpty()) { |
| startNextTransaction(); |
| } |
| } |
| |
| /** |
| * Starts the next pending transaction request. |
| * |
| * Starting new transactions must only be done through this method. This method continues to |
| * process the transaction queue as long as there are pending requests, and no transaction is |
| * pending. |
| * |
| * It is assumed that the caller has obtained a lock on this ContextHubTransactionManager |
| * object. |
| */ |
| private void startNextTransaction() { |
| int result = Result.UNKNOWN_FAILURE; |
| while (result != Result.OK && !mTransactionQueue.isEmpty()) { |
| ContextHubServiceTransaction transaction = mTransactionQueue.peek(); |
| result = transaction.onTransact(); |
| |
| if (result == Result.OK) { |
| Runnable onTimeoutFunc = () -> { |
| synchronized (this) { |
| if (!transaction.isComplete()) { |
| Log.d(TAG, transaction + " timed out"); |
| transaction.onTransactionComplete( |
| ContextHubTransaction.RESULT_FAILED_TIMEOUT); |
| |
| removeTransactionAndStartNext(); |
| } |
| } |
| }; |
| |
| long timeoutSeconds = transaction.getTimeout(TimeUnit.SECONDS); |
| mTimeoutFuture = mTimeoutExecutor.schedule(onTimeoutFunc, timeoutSeconds, |
| TimeUnit.SECONDS); |
| } else { |
| transaction.onTransactionComplete( |
| ContextHubServiceUtil.toTransactionResult(result)); |
| mTransactionQueue.remove(); |
| } |
| } |
| } |
| } |