Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7a23a968 authored by Arthur Ishiguro's avatar Arthur Ishiguro
Browse files

Replace JNI code with Java HIDL at ContextHubService

This CL does the following:
- Use Java HIDL to communicate with the Context Hub
- Reimplement required JNI functionality in Java

Bug: 67734082
Test: Flash device and run CHQTS, verify pass on walleye-userdebug
Change-Id: I8b7563d04e9e3a22295b81af283fd3168e179957
parent 28306600
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -43,7 +43,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
    android.hardware.oemlock-V1.0-java \
    android.hardware.tetheroffload.control-V1.0-java \
    android.hardware.vibrator-V1.0-java \
    android.hardware.configstore-V1.0-java
    android.hardware.configstore-V1.0-java \
    android.hardware.contexthub-V1.0-java

ifneq ($(INCREMENTAL_BUILDS),)
    LOCAL_PROGUARD_ENABLED := disabled
+474 −79

File changed.

Preview size limit exceeded, changes collapsed.

+161 −0
Original line number Diff line number Diff line
/*
 * 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.location.ContextHubTransaction;
import android.hardware.location.NanoAppState;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * An abstract class representing transactions requested to the Context Hub Service.
 *
 * @hide
 */
/* package */ abstract class ContextHubServiceTransaction {
    private final int mTransactionId;
    @ContextHubTransaction.Type
    private final int mTransactionType;

    /*
     * true if the transaction has already completed, false otherwise
     */
    private boolean mIsComplete = false;

    /* package */ ContextHubServiceTransaction(int id, int type) {
        mTransactionId = id;
        mTransactionType = type;
    }

    /**
     * Starts this transaction with a Context Hub.
     *
     * All instances of this class must implement this method by making an asynchronous request to
     * a hub.
     *
     * @return the synchronous error code of the transaction start
     */
    /* package */
    abstract int onTransact();

    /**
     * A function to invoke when a transaction times out.
     *
     * All instances of this class must implement this method by reporting the timeout to the
     * client.
     */
    /* package */
    abstract void onTimeout();

    /**
     * A function to invoke when the transaction completes.
     *
     * Only relevant for load, unload, enable, or disable transactions.
     *
     * @param result the result of the transaction
     */
    /* package */ void onTransactionComplete(int result) {
    }

    /**
     * A function to invoke when a query transaction completes.
     *
     * Only relevant for query transactions.
     *
     * @param result           the result of the query
     * @param nanoAppStateList the list of nanoapps given by the query response
     */
    /* package */ void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
    }

    /**
     * @return the ID of this transaction
     */
    /* package */ int getTransactionId() {
        return mTransactionId;
    }

    /**
     * @return the type of this transaction
     * @see ContextHubTransaction.Type
     */
    @ContextHubTransaction.Type
    /* package */ int getTransactionType() {
        return mTransactionType;
    }

    /**
     * Gets the timeout period as defined in IContexthub.hal
     *
     * @return the timeout of this transaction in the specified time unit
     */
    /* package */ long getTimeout(TimeUnit unit) {
        switch (mTransactionType) {
            case ContextHubTransaction.TYPE_LOAD_NANOAPP:
                return unit.convert(30L, TimeUnit.SECONDS);
            case ContextHubTransaction.TYPE_UNLOAD_NANOAPP:
            case ContextHubTransaction.TYPE_ENABLE_NANOAPP:
            case ContextHubTransaction.TYPE_DISABLE_NANOAPP:
            case ContextHubTransaction.TYPE_QUERY_NANOAPPS:
                // Note: query timeout is not specified at the HAL
            default: /* fall through */
                return unit.convert(5L, TimeUnit.SECONDS);
        }
    }

    /**
     * Marks the transaction as complete.
     *
     * Should only be called as a result of a response from a Context Hub callback
     */
    /* package */ void setComplete() {
        mIsComplete = true;
    }

    /**
     * @return true if the transaction has already completed, false otherwise
     */
    /* package */ boolean isComplete() {
        return mIsComplete;
    }

    /**
     * @return the human-readable string of this transaction's type
     */
    private String getTransactionTypeString() {
        switch (mTransactionType) {
            case ContextHubTransaction.TYPE_LOAD_NANOAPP:
                return "Load";
            case ContextHubTransaction.TYPE_UNLOAD_NANOAPP:
                return "Unload";
            case ContextHubTransaction.TYPE_ENABLE_NANOAPP:
                return "Enable";
            case ContextHubTransaction.TYPE_DISABLE_NANOAPP:
                return "Disable";
            case ContextHubTransaction.TYPE_QUERY_NANOAPPS:
                return "Query";
            default:
                return "Unknown";
        }
    }

    @Override
    public String toString() {
        return getTransactionTypeString() + " transaction (ID = " + mTransactionId + ")";
    }
}
+131 −0
Original line number Diff line number Diff line
/*
 * 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.ContextHub;
import android.hardware.contexthub.V1_0.HubAppInfo;
import android.hardware.location.ContextHubInfo;
import android.hardware.location.NanoAppBinary;
import android.hardware.location.NanoAppState;
import android.util.Log;

import java.util.List;
import java.util.ArrayList;

/**
 * A class encapsulating helper functions used by the ContextHubService class
 */
/* package */ class ContextHubServiceUtil {
    private static final String TAG = "ContextHubServiceUtil";

    /**
     * Creates a ContextHubInfo array from an ArrayList of HIDL ContextHub objects.
     *
     * @param hubList the ContextHub ArrayList
     * @return the ContextHubInfo array
     */
    /* package */
    static ContextHubInfo[] createContextHubInfoArray(List<ContextHub> hubList) {
        ContextHubInfo[] contextHubInfoList = new ContextHubInfo[hubList.size()];
        for (int i = 0; i < hubList.size(); i++) {
            contextHubInfoList[i] = new ContextHubInfo(hubList.get(i));
        }

        return contextHubInfoList;
    }

    /**
     * Copies a primitive byte array to a ArrayList<Byte>.
     *
     * @param inputArray  the primitive byte array
     * @param outputArray the ArrayList<Byte> array to append
     */
    /* package */
    static void copyToByteArrayList(byte[] inputArray, ArrayList<Byte> outputArray) {
        outputArray.clear();
        outputArray.ensureCapacity(inputArray.length);
        for (byte element : inputArray) {
            outputArray.add(element);
        }
    }

    /**
     * Creates a byte array given a ArrayList<Byte> and copies its contents.
     *
     * @param array the ArrayList<Byte> object
     * @return the byte array
     */
    /* package */
    static byte[] createPrimitiveByteArray(ArrayList<Byte> array) {
        byte[] primitiveArray = new byte[array.size()];
        for (int i = 0; i < array.size(); i++) {
            primitiveArray[i] = array.get(i);
        }

        return primitiveArray;
    }

    /**
     * Generates the Context Hub HAL's NanoAppBinary object from the client-facing
     * android.hardware.location.NanoAppBinary object.
     *
     * @param nanoAppBinary the client-facing NanoAppBinary object
     * @return the Context Hub HAL's NanoAppBinary object
     */
    /* package */
    static android.hardware.contexthub.V1_0.NanoAppBinary createHidlNanoAppBinary(
            NanoAppBinary nanoAppBinary) {
        android.hardware.contexthub.V1_0.NanoAppBinary hidlNanoAppBinary =
                new android.hardware.contexthub.V1_0.NanoAppBinary();

        hidlNanoAppBinary.appId = nanoAppBinary.getNanoAppId();
        hidlNanoAppBinary.appVersion = nanoAppBinary.getNanoAppVersion();
        hidlNanoAppBinary.flags = nanoAppBinary.getFlags();
        hidlNanoAppBinary.targetChreApiMajorVersion = nanoAppBinary.getTargetChreApiMajorVersion();
        hidlNanoAppBinary.targetChreApiMinorVersion = nanoAppBinary.getTargetChreApiMinorVersion();

        // Log exceptions while processing the binary, but continue to pass down the binary
        // since the error checking is deferred to the Context Hub.
        try {
            copyToByteArrayList(nanoAppBinary.getBinaryNoHeader(), hidlNanoAppBinary.customBinary);
        } catch (IndexOutOfBoundsException e) {
            Log.w(TAG, e.getMessage());
        } catch (NullPointerException e) {
            Log.w(TAG, "NanoApp binary was null");
        }

        return hidlNanoAppBinary;
    }

    /**
     * Generates a client-facing NanoAppState array from a HAL HubAppInfo array.
     *
     * @param nanoAppInfoList the array of HubAppInfo objects
     * @return the corresponding array of NanoAppState objects
     */
    /* package */
    static List<NanoAppState> createNanoAppStateList(
            List<HubAppInfo> nanoAppInfoList) {
        ArrayList<NanoAppState> nanoAppStateList = new ArrayList<>();
        for (HubAppInfo appInfo : nanoAppInfoList) {
            nanoAppStateList.add(
                    new NanoAppState(appInfo.appId, appInfo.version, appInfo.enabled));
        }

        return nanoAppStateList;
    }
}
+338 −0
Original line number Diff line number Diff line
/*
 * 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 = 10;

    /*
     * The proxy to talk to the Context Hub
     */
    private final IContexthub mContextHubProxy;

    /*
     * 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) {
        mContextHubProxy = contextHubProxy;
    }

    /**
     * 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()));
                    return Result.UNKNOWN_FAILURE;
                }
            }

            @Override
            /* package */ void onTimeout() {
                onTransactionComplete(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT);
            }

            @Override
            /* package */ void onTransactionComplete(int result) {
                try {
                    onCompleteCallback.onTransactionComplete(result);
                } catch (RemoteException e) {
                    Log.e(TAG, "RemoteException while calling client onTransactionComplete");
                }
            }
        };
    }

    /**
     * Creates a transaction for unloading a nanoapp.
     *
     * @param contextHubId       the ID of the hub to load the nanoapp to
     * @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));
                    return Result.UNKNOWN_FAILURE;
                }
            }

            @Override
            /* package */ void onTimeout() {
                onTransactionComplete(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT);
            }

            @Override
            /* package */ void onTransactionComplete(int result) {
                try {
                    onCompleteCallback.onTransactionComplete(result);
                } catch (RemoteException e) {
                    Log.e(TAG, "RemoteException while calling client onTransactionComplete");
                }
            }
        };
    }

    /**
     * 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");
                    return Result.UNKNOWN_FAILURE;
                }
            }

            @Override
            /* package */ void onTimeout() {
                onQueryResponse(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT,
                        Collections.emptyList());
            }

            @Override
            /* package */ void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
                try {
                    onCompleteCallback.onQueryResponse(result, nanoAppStateList);
                } catch (RemoteException e) {
                    Log.e(TAG, "RemoteException while calling client onQueryComplete");
                }
            }
        };
    }

    /**
     * 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.
     *
     * @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 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
     */
    /* 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);
        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(TransactionResult.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.onTimeout();

                            removeTransactionAndStartNext();
                        }
                    }
                };

                long timeoutSeconds = transaction.getTimeout(TimeUnit.SECONDS);
                mTimeoutFuture = mTimeoutExecutor.schedule(onTimeoutFunc, timeoutSeconds,
                        TimeUnit.SECONDS);
            } else {
                mTransactionQueue.remove();
            }
        }
    }
}
Loading