blob: cced781f8e1b5d558e4a5ad273e3b6547fb2278b [file] [log] [blame]
/*
* 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();
}
}
}
}