/* * 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 android.hardware.location; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Handler; import android.os.HandlerExecutor; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** * A class describing a request sent to the Context Hub Service. * * This object is generated as a result of an asynchronous request sent to the Context Hub * through the ContextHubManager APIs. The caller can either retrieve the result * synchronously through a blocking call ({@link #waitForResponse(long, TimeUnit)}) or * asynchronously through a user-defined listener * ({@link #setOnCompleteListener(OnCompleteListener, Executor)} )}). * * @param the type of the contents in the transaction response * * @hide */ @SystemApi public class ContextHubTransaction { private static final String TAG = "ContextHubTransaction"; /** * Constants describing the type of a transaction through the Context Hub Service. * {@hide} */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "TYPE_" }, value = { TYPE_LOAD_NANOAPP, TYPE_UNLOAD_NANOAPP, TYPE_ENABLE_NANOAPP, TYPE_DISABLE_NANOAPP, TYPE_QUERY_NANOAPPS }) public @interface Type { } public static final int TYPE_LOAD_NANOAPP = 0; public static final int TYPE_UNLOAD_NANOAPP = 1; public static final int TYPE_ENABLE_NANOAPP = 2; public static final int TYPE_DISABLE_NANOAPP = 3; public static final int TYPE_QUERY_NANOAPPS = 4; /** * Constants describing the result of a transaction or request through the Context Hub Service. * {@hide} */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "RESULT_" }, value = { RESULT_SUCCESS, RESULT_FAILED_UNKNOWN, RESULT_FAILED_BAD_PARAMS, RESULT_FAILED_UNINITIALIZED, RESULT_FAILED_BUSY, RESULT_FAILED_AT_HUB, RESULT_FAILED_TIMEOUT, RESULT_FAILED_SERVICE_INTERNAL_FAILURE, RESULT_FAILED_HAL_UNAVAILABLE }) public @interface Result {} public static final int RESULT_SUCCESS = 0; /** * Generic failure mode. */ public static final int RESULT_FAILED_UNKNOWN = 1; /** * Failure mode when the request parameters were not valid. */ public static final int RESULT_FAILED_BAD_PARAMS = 2; /** * Failure mode when the Context Hub is not initialized. */ public static final int RESULT_FAILED_UNINITIALIZED = 3; /** * Failure mode when there are too many transactions pending. */ public static final int RESULT_FAILED_BUSY = 4; /** * Failure mode when the request went through, but failed asynchronously at the hub. */ public static final int RESULT_FAILED_AT_HUB = 5; /** * Failure mode when the transaction has timed out. */ public static final int RESULT_FAILED_TIMEOUT = 6; /** * Failure mode when the transaction has failed internally at the service. */ public static final int RESULT_FAILED_SERVICE_INTERNAL_FAILURE = 7; /** * Failure mode when the Context Hub HAL was not available. */ public static final int RESULT_FAILED_HAL_UNAVAILABLE = 8; /** * A class describing the response for a ContextHubTransaction. * * @param the type of the contents in the response */ public static class Response { /* * The result of the transaction. */ @ContextHubTransaction.Result private int mResult; /* * The contents of the response from the Context Hub. */ private R mContents; Response(@ContextHubTransaction.Result int result, R contents) { mResult = result; mContents = contents; } @ContextHubTransaction.Result public int getResult() { return mResult; } public R getContents() { return mContents; } } /** * An interface describing the listener for a transaction completion. * * @param the type of the contents in the transaction response */ @FunctionalInterface public interface OnCompleteListener { /** * The listener function to invoke when the transaction completes. * * @param transaction the transaction that this callback was attached to. * @param response the response of the transaction. */ void onComplete( ContextHubTransaction transaction, ContextHubTransaction.Response response); } /* * The type of the transaction. */ @Type private int mTransactionType; /* * The response of the transaction. */ private ContextHubTransaction.Response mResponse; /* * The executor to invoke the onComplete async callback. */ private Executor mExecutor = null; /* * The listener to be invoked when the transaction completes. */ private ContextHubTransaction.OnCompleteListener mListener = null; /* * Synchronization latch used to block on response. */ private final CountDownLatch mDoneSignal = new CountDownLatch(1); /* * true if the response has been set throught setResponse, false otherwise. */ private boolean mIsResponseSet = false; ContextHubTransaction(@Type int type) { mTransactionType = type; } /** * Converts a transaction type to a human-readable string * * @param type the type of a transaction * @param upperCase {@code true} if upper case the first letter, {@code false} otherwise * @return a string describing the transaction */ public static String typeToString(@Type int type, boolean upperCase) { switch (type) { case ContextHubTransaction.TYPE_LOAD_NANOAPP: return upperCase ? "Load" : "load"; case ContextHubTransaction.TYPE_UNLOAD_NANOAPP: return upperCase ? "Unload" : "unload"; case ContextHubTransaction.TYPE_ENABLE_NANOAPP: return upperCase ? "Enable" : "enable"; case ContextHubTransaction.TYPE_DISABLE_NANOAPP: return upperCase ? "Disable" : "disable"; case ContextHubTransaction.TYPE_QUERY_NANOAPPS: return upperCase ? "Query" : "query"; default: return upperCase ? "Unknown" : "unknown"; } } /** * @return the type of the transaction */ @Type public int getType() { return mTransactionType; } /** * Waits to receive the asynchronous transaction result. * * This function blocks until the Context Hub Service has received a response * for the transaction represented by this object by the Context Hub, or a * specified timeout period has elapsed. * * If the specified timeout has passed, a TimeoutException will be thrown and the caller may * retry the invocation of this method at a later time. * * @param timeout the timeout duration * @param unit the unit of the timeout * * @return the transaction response * * @throws InterruptedException if the current thread is interrupted while waiting for response * @throws TimeoutException if the timeout period has passed */ public ContextHubTransaction.Response waitForResponse( long timeout, TimeUnit unit) throws InterruptedException, TimeoutException { boolean success = mDoneSignal.await(timeout, unit); if (!success) { throw new TimeoutException("Timed out while waiting for transaction"); } return mResponse; } /** * Sets the listener to be invoked invoked when the transaction completes. * * This function provides an asynchronous approach to retrieve the result of the * transaction. When the transaction response has been provided by the Context Hub, * the given listener will be invoked. * * If the transaction has already completed at the time of invocation, the listener * will be immediately invoked. If the transaction has been invalidated, * the listener will never be invoked. * * A transaction can be invalidated if the process owning the transaction is no longer active * and the reference to this object is lost. * * This method or {@link #setOnCompleteListener(ContextHubTransaction.OnCompleteListener)} can * only be invoked once, or an IllegalStateException will be thrown. * * @param listener the listener to be invoked upon completion * @param executor the executor to invoke the callback * * @throws IllegalStateException if this method is called multiple times * @throws NullPointerException if the callback or handler is null */ public void setOnCompleteListener( @NonNull ContextHubTransaction.OnCompleteListener listener, @NonNull @CallbackExecutor Executor executor) { synchronized (this) { Preconditions.checkNotNull(listener, "OnCompleteListener cannot be null"); Preconditions.checkNotNull(executor, "Executor cannot be null"); if (mListener != null) { throw new IllegalStateException( "Cannot set ContextHubTransaction listener multiple times"); } mListener = listener; mExecutor = executor; if (mDoneSignal.getCount() == 0) { mExecutor.execute(() -> mListener.onComplete(this, mResponse)); } } } /** * Sets the listener to be invoked invoked when the transaction completes. * * Equivalent to {@link #setOnCompleteListener(ContextHubTransaction.OnCompleteListener, * Executor)} with the executor using the main thread's Looper. * * This method or {@link #setOnCompleteListener(ContextHubTransaction.OnCompleteListener, * Executor)} can only be invoked once, or an IllegalStateException will be thrown. * * @param listener the listener to be invoked upon completion * * @throws IllegalStateException if this method is called multiple times * @throws NullPointerException if the callback is null */ public void setOnCompleteListener( @NonNull ContextHubTransaction.OnCompleteListener listener) { setOnCompleteListener(listener, new HandlerExecutor(Handler.getMain())); } /** * Sets the response of the transaction. * * This method should only be invoked by ContextHubManager as a result of a callback from * the Context Hub Service indicating the response from a transaction. This method should not be * invoked more than once. * * @param response the response to set * * @throws IllegalStateException if this method is invoked multiple times * @throws NullPointerException if the response is null */ /* package */ void setResponse(ContextHubTransaction.Response response) { synchronized (this) { Preconditions.checkNotNull(response, "Response cannot be null"); if (mIsResponseSet) { throw new IllegalStateException( "Cannot set response of ContextHubTransaction multiple times"); } mResponse = response; mIsResponseSet = true; mDoneSignal.countDown(); if (mListener != null) { mExecutor.execute(() -> mListener.onComplete(this, mResponse)); } } } }