Merge changes I4395fb12,I89a77a30

* changes:
  Expose headerless binary and flags in NanoAppBinary
  Sets up ContextHub service/manager interface for transactions
diff --git a/Android.mk b/Android.mk
index 1ed8a25..a7cb362 100644
--- a/Android.mk
+++ b/Android.mk
@@ -218,6 +218,7 @@
 	core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl \
 	core/java/android/hardware/location/IContextHubCallback.aidl \
 	core/java/android/hardware/location/IContextHubService.aidl \
+	core/java/android/hardware/location/IContextHubTransactionCallback.aidl \
 	core/java/android/hardware/radio/IRadioService.aidl \
 	core/java/android/hardware/radio/ITuner.aidl \
 	core/java/android/hardware/radio/ITunerCallback.aidl \
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 7cbb436..2411727 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -271,6 +271,59 @@
         throw new UnsupportedOperationException("TODO: Implement this");
     }
 
+    /*
+     * Helper function to generate a stub for a non-query transaction callback.
+     *
+     * @param transaction the transaction to unblock when complete
+     *
+     * @return the callback
+     *
+     * @hide
+     */
+    private IContextHubTransactionCallback createTransactionCallback(
+            ContextHubTransaction<Void> transaction) {
+        return new IContextHubTransactionCallback.Stub() {
+            @Override
+            public void onQueryResponse(int result, List<NanoAppState> nanoappList) {
+                Log.e(TAG, "Received a query callback on a non-query request");
+                transaction.setResponse(new ContextHubTransaction.Response<Void>(
+                        ContextHubTransaction.TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE, null));
+            }
+
+            @Override
+            public void onTransactionComplete(int result) {
+                transaction.setResponse(new ContextHubTransaction.Response<Void>(result, null));
+            }
+        };
+    }
+
+   /*
+    * Helper function to generate a stub for a query transaction callback.
+    *
+    * @param transaction the transaction to unblock when complete
+    *
+    * @return the callback
+    *
+    * @hide
+    */
+    private IContextHubTransactionCallback createQueryCallback(
+            ContextHubTransaction<List<NanoAppState>> transaction) {
+        return new IContextHubTransactionCallback.Stub() {
+            @Override
+            public void onQueryResponse(int result, List<NanoAppState> nanoappList) {
+                transaction.setResponse(new ContextHubTransaction.Response<List<NanoAppState>>(
+                        result, nanoappList));
+            }
+
+            @Override
+            public void onTransactionComplete(int result) {
+                Log.e(TAG, "Received a non-query callback on a query request");
+                transaction.setResponse(new ContextHubTransaction.Response<List<NanoAppState>>(
+                        ContextHubTransaction.TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE, null));
+            }
+        };
+    }
+
     /**
      * Loads a nanoapp at the specified Context Hub.
      *
@@ -411,7 +464,7 @@
      *
      * @param callback the notification callback to register
      * @param hubInfo the hub to attach this client to
-     * @param handler the handler to invoke the callback, if null uses the current thread Looper
+     * @param handler the handler to invoke the callback, if null uses the main thread's Looper
      *
      * @return the registered client object
      *
diff --git a/core/java/android/hardware/location/ContextHubTransaction.java b/core/java/android/hardware/location/ContextHubTransaction.java
index 4877d38..a8569ef 100644
--- a/core/java/android/hardware/location/ContextHubTransaction.java
+++ b/core/java/android/hardware/location/ContextHubTransaction.java
@@ -16,11 +16,16 @@
 package android.hardware.location;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * A class describing a request sent to the Context Hub Service.
@@ -29,17 +34,15 @@
  * 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 callback
- * ({@link #onComplete(ContextHubTransaction.Callback<T>, Handler)}).
- *
- * A transaction can be invalidated if the caller of the transaction is no longer active
- * and the reference to this object is lost, or if timeout period has passed in
- * {@link #waitForResponse(long, TimeUnit)}.
+ * ({@link #setCallbackOnComplete(ContextHubTransaction.Callback, Handler)}).
  *
  * @param <T> the type of the contents in the transaction response
  *
  * @hide
  */
 public class ContextHubTransaction<T> {
+    private static final String TAG = "ContextHubTransaction";
+
     /**
      * Constants describing the type of a transaction through the Context Hub Service.
      */
@@ -68,7 +71,8 @@
             TRANSACTION_FAILED_UNINITIALIZED,
             TRANSACTION_FAILED_PENDING,
             TRANSACTION_FAILED_AT_HUB,
-            TRANSACTION_FAILED_TIMEOUT})
+            TRANSACTION_FAILED_TIMEOUT,
+            TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE})
     public @interface Result {}
     public static final int TRANSACTION_SUCCESS = 0;
     /**
@@ -95,6 +99,10 @@
      * Failure mode when the transaction has timed out.
      */
     public static final int TRANSACTION_FAILED_TIMEOUT = 6;
+    /**
+     * Failure mode when the transaction has failed internally at the service.
+     */
+    public static final int TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE = 7;
 
     /**
      * A class describing the response for a ContextHubTransaction.
@@ -146,11 +154,6 @@
     }
 
     /*
-     * The unique identifier representing the transaction.
-     */
-    private int mTransactionId;
-
-    /*
      * The type of the transaction.
      */
     @Type
@@ -171,8 +174,17 @@
      */
     private ContextHubTransaction.Callback<T> mCallback = null;
 
-    ContextHubTransaction(int id, @Type int type) {
-        mTransactionId = id;
+    /*
+     * 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;
     }
 
@@ -191,17 +203,26 @@
      * for the transaction represented by this object by the Context Hub, or a
      * specified timeout period has elapsed.
      *
-     * If the specified timeout has passed, the transaction represented by this object
-     * is invalidated by the Context Hub Service (resulting in a timeout failure in the
-     * response).
+     * 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) {
-        throw new UnsupportedOperationException("TODO: Implement this");
+    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;
     }
 
     /**
@@ -215,15 +236,100 @@
      * will be immediately posted by the handler. If the transaction has been invalidated,
      * the callback 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 #setCallbackOnCompletecan(ContextHubTransaction.Callback)} can only be
+     * invoked once, or an IllegalStateException will be thrown.
+     *
      * @param callback the callback to be invoked upon completion
      * @param handler the handler to post the callback
+     *
+     * @throws IllegalStateException if this method is called multiple times
+     * @throws NullPointerException if the callback or handler is null
      */
-    public void onComplete(ContextHubTransaction.Callback<T> callback, Handler handler) {
-        throw new UnsupportedOperationException("TODO: Implement this");
+    public void setCallbackOnComplete(
+            @NonNull ContextHubTransaction.Callback<T> callback, @NonNull Handler handler) {
+        synchronized (this) {
+            if (callback == null) {
+                throw new NullPointerException("Callback cannot be null");
+            }
+            if (handler == null) {
+                throw new NullPointerException("Handler cannot be null");
+            }
+            if (mCallback != null) {
+                throw new IllegalStateException(
+                        "Cannot set ContextHubTransaction callback multiple times");
+            }
+
+            mCallback = callback;
+            mHandler = handler;
+
+            if (mDoneSignal.getCount() == 0) {
+                boolean callbackPosted = mHandler.post(() -> {
+                    mCallback.onComplete(this, mResponse);
+                });
+
+                if (!callbackPosted) {
+                    Log.e(TAG, "Failed to post callback to Handler");
+                }
+            }
+        }
     }
 
-    private void setResponse(ContextHubTransaction.Response<T> response) {
-        mResponse = response;
-        throw new UnsupportedOperationException("TODO: Unblock waitForResponse");
+    /**
+     * Sets a callback to be invoked when the transaction completes.
+     *
+     * Equivalent to {@link #setCallbackOnComplete(ContextHubTransaction.Callback, Handler)}
+     * with the handler being that of the main thread's Looper.
+     *
+     * This method or {@link #setCallbackOnComplete(ContextHubTransaction.Callback, Handler)}
+     * can only be invoked once, or an IllegalStateException will be thrown.
+     *
+     * @param callback the callback to be invoked upon completion
+     *
+     * @throws IllegalStateException if this method is called multiple times
+     * @throws NullPointerException if the callback is null
+     */
+    public void setCallbackOnComplete(@NonNull ContextHubTransaction.Callback<T> callback) {
+        setCallbackOnComplete(callback, new Handler(Looper.getMainLooper()));
+    }
+
+    /**
+     * 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
+     */
+    void setResponse(ContextHubTransaction.Response<T> response) {
+        synchronized (this) {
+            if (response == null) {
+                throw new NullPointerException("Response cannot be null");
+            }
+            if (mIsResponseSet) {
+                throw new IllegalStateException(
+                        "Cannot set response of ContextHubTransaction multiple times");
+            }
+
+            mResponse = response;
+            mIsResponseSet = true;
+
+            mDoneSignal.countDown();
+            if (mCallback != null) {
+                boolean callbackPosted = mHandler.post(() -> {
+                    mCallback.onComplete(this, mResponse);
+                });
+
+                if (!callbackPosted) {
+                    Log.e(TAG, "Failed to post callback to Handler");
+                }
+            }
+        }
     }
 }
diff --git a/core/java/android/hardware/location/IContextHubTransactionCallback.aidl b/core/java/android/hardware/location/IContextHubTransactionCallback.aidl
new file mode 100644
index 0000000..5419cd7
--- /dev/null
+++ b/core/java/android/hardware/location/IContextHubTransactionCallback.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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.hardware.location.NanoAppState;
+
+/**
+ * An interface used by the Context Hub Service to invoke callbacks notifying the complete of a
+ * transaction. The callbacks are unique for each type of transaction, and the service is
+ * responsible for invoking the correct callback.
+ *
+ * @hide
+ */
+oneway interface IContextHubTransactionCallback {
+
+    // Callback to be invoked when a query request completes
+    void onQueryResponse(int result, in List<NanoAppState> nanoappList);
+
+    // Callback to be invoked when a non-query request completes
+    void onTransactionComplete(int result);
+}
diff --git a/core/java/android/hardware/location/NanoAppBinary.java b/core/java/android/hardware/location/NanoAppBinary.java
index 5454227..934e9e4 100644
--- a/core/java/android/hardware/location/NanoAppBinary.java
+++ b/core/java/android/hardware/location/NanoAppBinary.java
@@ -22,6 +22,7 @@
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.util.Arrays;
 
 /**
  * @hide
@@ -57,7 +58,7 @@
     private static final int EXPECTED_HEADER_VERSION = 1;
 
     /*
-     * The magic value expected in the header.
+     * The magic value expected in the header as defined in context_hub.h.
      */
     private static final int EXPECTED_MAGIC_VALUE =
             (((int) 'N' <<  0) | ((int) 'A' <<  8) | ((int) 'N' << 16) | ((int) 'O' << 24));
@@ -67,6 +68,17 @@
      */
     private static final ByteOrder HEADER_ORDER = ByteOrder.LITTLE_ENDIAN;
 
+    /*
+     * The size of the header in bytes as defined in context_hub.h.
+     */
+    private static final int HEADER_SIZE_BYTES = 40;
+
+    /*
+     * The bit fields for mFlags as defined in context_hub.h.
+     */
+    private static final int NANOAPP_SIGNED_FLAG_BIT = 0x1;
+    private static final int NANOAPP_ENCRYPTED_FLAG_BIT = 0x2;
+
     public NanoAppBinary(byte[] appBinary) {
         mNanoAppBinary = appBinary;
         parseBinaryHeader();
@@ -111,11 +123,26 @@
     /**
      * @return the app binary byte array
      */
-    public byte[] getNanoAppBinary() {
+    public byte[] getBinary() {
         return mNanoAppBinary;
     }
 
     /**
+     * @return the app binary byte array without the leading header
+     *
+     * @throws IndexOutOfBoundsException if the nanoapp binary size is smaller than the header size
+     * @throws NullPointerException if the nanoapp binary is null
+     */
+    public byte[] getBinaryNoHeader() {
+        if (mNanoAppBinary.length < HEADER_SIZE_BYTES) {
+            throw new IndexOutOfBoundsException("NanoAppBinary binary byte size ("
+                + mNanoAppBinary.length + ") is less than header size (" + HEADER_SIZE_BYTES + ")");
+        }
+
+        return Arrays.copyOfRange(mNanoAppBinary, HEADER_SIZE_BYTES, mNanoAppBinary.length);
+    }
+
+    /**
      * @return {@code true} if the header is valid, {@code false} otherwise
      */
     public boolean hasValidHeader() {
@@ -164,6 +191,31 @@
         return mTargetChreApiMinorVersion;
     }
 
+    /**
+     * Returns the flags for the nanoapp as defined in context_hub.h.
+     *
+     * This method is meant to be used by the Context Hub Service.
+     *
+     * @return the flags for the nanoapp
+     */
+    public int getFlags() {
+        return mFlags;
+    }
+
+    /**
+     * @return {@code true} if the nanoapp binary is signed, {@code false} otherwise
+     */
+    public boolean isSigned() {
+        return (mFlags & NANOAPP_SIGNED_FLAG_BIT) != 0;
+    }
+
+    /**
+     * @return {@code true} if the nanoapp binary is encrypted, {@code false} otherwise
+     */
+    public boolean isEncrypted() {
+        return (mFlags & NANOAPP_ENCRYPTED_FLAG_BIT) != 0;
+    }
+
     private NanoAppBinary(Parcel in) {
         int binaryLength = in.readInt();
         mNanoAppBinary = new byte[binaryLength];