content: /*
   * 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 <T> the type of the contents in the transaction response
   *
   * @hide
   */
  @SystemApi
  public class ContextHubTransaction<T> {
      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 <R> the type of the contents in the response
       */
      public static class Response<R> {
          /*
           * 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 <L> the type of the contents in the transaction response
       */
      @FunctionalInterface
      public interface OnCompleteListener<L> {
          /**
           * 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<L> transaction, ContextHubTransaction.Response<L> response);
      }
  
      /*
       * The type of the transaction.
       */
      @Type
      private int mTransactionType;
  
      /*
       * The response of the transaction.
       */
      private ContextHubTransaction.Response<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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));
              }
          }
      }
  }
  