Adds CHQTS nanoapp info events test executor

Bug: 151159967
Test: Run test and verify pass
Change-Id: Ia7b35a1a8a4a7ca4af5b7930ec70bb992aa9314a
diff --git a/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubGeneralTestExecutor.java b/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubGeneralTestExecutor.java
index 829769a..0cfe924 100644
--- a/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubGeneralTestExecutor.java
+++ b/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubGeneralTestExecutor.java
@@ -82,10 +82,23 @@
         private final NanoAppBinary mNanoAppBinary;
         private final ContextHubTestConstants.TestNames mTestName;
 
+        // Set to false if the nanoapp should not be loaded at init. An example of why this may be
+        // needed are for nanoapps that are loaded in the middle of the test execution, but still
+        // needs to be included in this test executor (e.g. deliver messages from it).
+        private final boolean mLoadAtInit;
+
         public GeneralTestNanoApp(NanoAppBinary nanoAppBinary,
                 ContextHubTestConstants.TestNames testName) {
             mTestName = testName;
             mNanoAppBinary = nanoAppBinary;
+            mLoadAtInit = true;
+        }
+
+        public GeneralTestNanoApp(NanoAppBinary nanoAppBinary,
+                ContextHubTestConstants.TestNames testName, boolean loadAtInit) {
+            mTestName = testName;
+            mNanoAppBinary = nanoAppBinary;
+            mLoadAtInit = loadAtInit;
         }
 
         public NanoAppBinary getNanoAppBinary() {
@@ -95,6 +108,10 @@
         public ContextHubTestConstants.TestNames getTestName() {
             return mTestName;
         }
+
+        public boolean loadAtInit() {
+            return mLoadAtInit;
+        }
     }
 
     /**
@@ -162,8 +179,10 @@
         unloadAllNanoApps();
 
         for (GeneralTestNanoApp test : mGeneralTestNanoAppList) {
-            ChreTestUtil.loadNanoAppAssertSuccess(mContextHubManager, mContextHubInfo,
-                    test.getNanoAppBinary());
+            if (test.loadAtInit()) {
+                ChreTestUtil.loadNanoAppAssertSuccess(mContextHubManager, mContextHubInfo,
+                        test.getNanoAppBinary());
+            }
         }
 
         mErrorString.set(null);
@@ -179,8 +198,10 @@
         mCountDownLatch = new CountDownLatch(1);
 
         for (GeneralTestNanoApp test : mGeneralTestNanoAppList) {
-            sendMessageToNanoAppOrFail(test.getNanoAppBinary().getNanoAppId(),
-                    test.getTestName().asInt(), new byte[0] /* data */);
+            if (test.loadAtInit()) {
+                sendMessageToNanoAppOrFail(test.getNanoAppBinary().getNanoAppId(),
+                        test.getTestName().asInt(), new byte[0] /* data */);
+            }
         }
 
         boolean success = false;
diff --git a/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubNanoAppInfoEventsTestExecutor.java b/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubNanoAppInfoEventsTestExecutor.java
new file mode 100644
index 0000000..6aa1cd6
--- /dev/null
+++ b/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubNanoAppInfoEventsTestExecutor.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2020 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.google.android.chre.test.chqts;
+
+import android.hardware.location.ContextHubInfo;
+import android.hardware.location.ContextHubManager;
+import android.hardware.location.NanoAppBinary;
+
+import com.google.android.utils.chre.ChreTestUtil;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Verify chreConfigureNanoAppInfoEvents.
+ *
+ * Protocol:
+ * There are two nanoapps here, so we'll talk in terms of the observer
+ * and the performer. All data involving the host is sent little endian
+ *
+ * Host to observer: NANOAPP_INFO_EVENTS_OBSERVER, no data
+ * observer to Host: CONTINUE
+ * [Host starts performer]
+ * performer to Host: CONTINUE, 64-bit app ID, 32-bit instance ID
+ * [Host stops performer]
+ * Host to observer: CONTINUE, performer's 32-bit instance ID
+ * observer to host: SUCCESS
+ */
+public class ContextHubNanoAppInfoEventsTestExecutor extends ContextHubGeneralTestExecutor {
+    private final long mObserverNanoAppId;
+    private final NanoAppBinary mPerformerNanoAppBinary;
+
+    public ContextHubNanoAppInfoEventsTestExecutor(ContextHubManager manager, ContextHubInfo info,
+            NanoAppBinary observerBinary, NanoAppBinary performerBinary) {
+        // Note that the performer nanoapp is not loaded/started at init, since it will be loaded
+        // via the message from the observer nanoapp.
+        super(manager, info, new GeneralTestNanoApp(observerBinary,
+                        ContextHubTestConstants.TestNames.NANO_APP_INFO_EVENTS_OBSERVER),
+                new GeneralTestNanoApp(performerBinary,
+                        ContextHubTestConstants.TestNames.NANO_APP_INFO_EVENTS_PERFORMER,
+                        false /* loadAtInit */));
+        mObserverNanoAppId = observerBinary.getNanoAppId();
+        mPerformerNanoAppBinary = performerBinary;
+    }
+
+    @Override
+    protected void handleMessageFromNanoApp(long nanoAppId,
+            ContextHubTestConstants.MessageType type, byte[] data) {
+        if (type != ContextHubTestConstants.MessageType.CONTINUE) {
+            fail("Unexpected message type " + type);
+            return;
+        }
+
+        if (nanoAppId == mObserverNanoAppId) {
+            if (!ChreTestUtil.loadNanoApp(getContextHubManager(), getContextHubInfo(),
+                    mPerformerNanoAppBinary)) {
+                fail("Failed to load performer nanoapp");
+                return;
+            }
+
+            // Send message to have the PERFORMER actually start
+            sendMessageToNanoAppOrFail(mPerformerNanoAppBinary.getNanoAppId(),
+                    ContextHubTestConstants.TestNames.NANO_APP_INFO_EVENTS_PERFORMER.asInt(),
+                    new byte[0] /* data */);
+        } else {
+            ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
+
+            int performerInstanceId = 0;
+
+            try {
+                // Ignore the nanoApp ID
+                buffer.getLong();
+                performerInstanceId = buffer.getInt();
+            } catch (BufferUnderflowException ex) {
+                fail("Not enough data provided in CONTINUE message");
+            }
+
+            assertFalse("Too much data provided in CONTINUE message", buffer.hasRemaining());
+
+            buffer = ByteBuffer.allocate(4)
+                    .order(ByteOrder.LITTLE_ENDIAN)
+                    .putInt(performerInstanceId);
+
+            if (!ChreTestUtil.unloadNanoApp(getContextHubManager(), getContextHubInfo(),
+                    mPerformerNanoAppBinary.getNanoAppId())) {
+                fail("Failed to unload performer nanoapp");
+            }
+
+            sendMessageToNanoAppOrFail(mObserverNanoAppId,
+                    ContextHubTestConstants.MessageType.CONTINUE.asInt(), buffer.array());
+        }
+    }
+}
diff --git a/java/test/utils/src/com/google/android/utils/chre/ChreTestUtil.java b/java/test/utils/src/com/google/android/utils/chre/ChreTestUtil.java
index 6310944..0ebd277 100644
--- a/java/test/utils/src/com/google/android/utils/chre/ChreTestUtil.java
+++ b/java/test/utils/src/com/google/android/utils/chre/ChreTestUtil.java
@@ -108,11 +108,7 @@
     }
 
     /**
-     * Loads a nanoapp and asserts success.
-     *
-     * @param manager       The ContextHubManager to use to load the nanoapp.
-     * @param info          The ContextHubInfo describing the Context Hub to load the nanoapp to.
-     * @param nanoAppBinary The nanoapp binary to load.
+     * Same as loadNanoApp(), but asserts that it succeeds.
      */
     public static void loadNanoAppAssertSuccess(
             ContextHubManager manager, ContextHubInfo info, NanoAppBinary nanoAppBinary) {
@@ -122,13 +118,14 @@
     }
 
     /**
-     * Unloads a nanoapp and asserts success.
+     * Unloads a nanoapp.
      *
      * @param manager   The ContextHubManager to use to unload the nanoapp.
      * @param info      The ContextHubInfo describing the Context Hub to unload the nanoapp from.
-     * @param nanoAppId The nanoapp to unload.
+     * @param nanoAppId The 64-bit ID of the nanoapp to unload.
+     * @return true if the unload succeeded.
      */
-    public static void unloadNanoAppAssertSuccess(
+    public static boolean unloadNanoApp(
             ContextHubManager manager, ContextHubInfo info, long nanoAppId) {
         ContextHubTransaction<Void> txn = manager.unloadNanoApp(info, nanoAppId);
         ContextHubTransaction.Response<Void> resp = null;
@@ -138,8 +135,15 @@
             Assert.fail(e.getMessage());
         }
 
-        if (resp != null && resp.getResult() != ContextHubTransaction.RESULT_SUCCESS) {
-            Assert.fail("Failed to unload nanoapp: result = " + resp.getResult());
+        return resp != null && resp.getResult() == ContextHubTransaction.RESULT_SUCCESS;
+    }
+    /**
+     * Same as unloadNanoApp(), but asserts that it succeeds.
+     */
+    public static void unloadNanoAppAssertSuccess(
+            ContextHubManager manager, ContextHubInfo info, long nanoAppId) {
+        if (!unloadNanoApp(manager, info, nanoAppId)) {
+            Assert.fail("Failed to unload nanoapp");
         }
     }