Add CTS tests to cover USB HID compatibility.

Verify the key events generated for HID consumer usage keys of voice
command control as per CDD Section:7.7.2.

Bug: 132078849
Test: atest UsbVoiceCommandTest
Change-Id: I285952f5cbf780c8d1e83010fb1310ef724d76a2
diff --git a/tests/tests/hardware/AndroidManifest.xml b/tests/tests/hardware/AndroidManifest.xml
index 78ff2aa..25f89be 100644
--- a/tests/tests/hardware/AndroidManifest.xml
+++ b/tests/tests/hardware/AndroidManifest.xml
@@ -18,14 +18,16 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.hardware.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-permission android:name="android.permission.REORDER_TASKS" />
+    <uses-permission android:name="android.permission.TRANSMIT_IR" />
+    <uses-permission android:name="android.permission.USE_FINGERPRINT" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.TRANSMIT_IR" />
-    <uses-permission android:name="android.permission.REORDER_TASKS" />
-    <uses-permission android:name="android.permission.USE_FINGERPRINT" />
 
     <application>
         <uses-library android:name="android.test.runner" />
@@ -78,6 +80,18 @@
                     |smallestScreenSize">
         </activity>
 
+        <activity android:name="android.hardware.input.cts.InputAssistantActivity"
+            android:label="InputAssistantActivity"
+            android:exported="true"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation
+                    |screenLayout|fontScale|uiMode|orientation|density|screenSize
+                    |smallestScreenSize">
+            <intent-filter >
+                <action android:name="android.speech.action.VOICE_SEARCH_HANDS_FREE" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
         <activity android:name="android.hardware.cts.FingerprintTestActivity"
             android:label="FingerprintTestActivity">
         </activity>
diff --git a/tests/tests/hardware/res/raw/google_gamepad_assistkey.json b/tests/tests/hardware/res/raw/google_gamepad_assistkey.json
new file mode 100644
index 0000000..902e017
--- /dev/null
+++ b/tests/tests/hardware/res/raw/google_gamepad_assistkey.json
@@ -0,0 +1,11 @@
+[
+  {
+    "name": "Press Voice Assist",
+    "reports": [
+        [0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "events": [
+    ]
+  }
+]
\ No newline at end of file
diff --git a/tests/tests/hardware/res/raw/google_gamepad_keyeventtests.json b/tests/tests/hardware/res/raw/google_gamepad_keyeventtests.json
new file mode 100644
index 0000000..0ca751d
--- /dev/null
+++ b/tests/tests/hardware/res/raw/google_gamepad_keyeventtests.json
@@ -0,0 +1,41 @@
+[
+  {
+    "name": "Press BUTTON Play/Pause",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "MEDIA_PLAY_PAUSE"},
+      {"action": "UP", "keycode": "MEDIA_PLAY_PAUSE"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON VOLUME_UP",
+    "reports": [
+        [0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "VOLUME_UP"},
+      {"action": "UP", "keycode": "VOLUME_UP"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON VOLUME_DOWN",
+    "reports": [
+        [0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "VOLUME_DOWN"},
+      {"action": "UP", "keycode": "VOLUME_DOWN"}
+    ]
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/google_gamepad_usb_register.json b/tests/tests/hardware/res/raw/google_gamepad_usb_register.json
new file mode 100644
index 0000000..b3b0d69
--- /dev/null
+++ b/tests/tests/hardware/res/raw/google_gamepad_usb_register.json
@@ -0,0 +1,22 @@
+{
+  "id": 1,
+  "command": "register",
+  "name": "Generic Gamepad with Voice Command buttons",
+  "vid": 0x18d1,
+  "pid": 0xabcd,
+  "bus": "usb",
+  "descriptor": [
+    0x05, 0x01, 0x09, 0x05, 0xA1, 0x01, 0x05, 0x0C, 0x81, 0x01, 0x09, 0xCD,
+    0x09, 0xE9, 0x09, 0xEA, 0x09, 0xCF, 0x05, 0x09, 0x09, 0x01, 0x09, 0x02,
+    0x09, 0x04, 0x09, 0x05, 0x09, 0x07, 0x09, 0x08, 0x09, 0x0E, 0x09, 0x0F,
+    0x09, 0x0B, 0x09, 0x0C, 0x09, 0x0D, 0x75, 0x01, 0x95, 0x0F, 0x81, 0x02,
+    0x75, 0x01, 0x95, 0x01, 0x81, 0x01, 0x05, 0x01, 0x75, 0x10, 0x95, 0x02,
+    0x16, 0x00, 0x80, 0x26, 0xFF, 0x7F, 0x36, 0x00, 0x80, 0x46, 0xFF, 0x7F,
+    0x09, 0x01, 0xA1, 0x00, 0x09, 0x30, 0x09, 0x31, 0x95, 0x02, 0x81, 0x02,
+    0xC0, 0x09, 0x01, 0xA1, 0x00, 0x09, 0x33, 0x09, 0x34, 0x95, 0x02, 0x81,
+    0x02, 0xC0, 0x75, 0x08, 0x95, 0x02, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x35,
+    0x00, 0x46, 0xFF, 0x00, 0x09, 0x32, 0x09, 0x35, 0x81, 0x02, 0x06, 0x00,
+    0xFF, 0x75, 0x08, 0x95, 0x08, 0x26, 0xFF, 0x00, 0x09, 0x02, 0x91, 0x02,
+    0x75, 0x08, 0x95, 0x30, 0x26, 0xFF, 0x00, 0x09, 0x03, 0xB1, 0x02, 0xC0
+  ]
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/InputAssistantActivity.java b/tests/tests/hardware/src/android/hardware/input/cts/InputAssistantActivity.java
new file mode 100644
index 0000000..59a4bef
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/InputAssistantActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 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 android.hardware.input.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class InputAssistantActivity extends Activity {
+    private static final String TAG = "InputAssistantActivity";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
index 9ee2d2a..67d5694 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
@@ -74,7 +74,7 @@
         new ActivityTestRule<>(InputCtsActivity.class);
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mActivityRule.getActivity().setInputCallback(mInputListener);
         mActivityRule.getActivity().clearUnhandleKeyCode();
@@ -87,7 +87,7 @@
     }
 
     @After
-    public void tearDown() {
+    public void tearDown() throws Exception {
         tearDownDevice();
     }
 
@@ -217,7 +217,7 @@
             // any unexpected event received caused by the HID report injection.
             InputEvent event = waitForEvent();
             if (event != null) {
-                fail("Received unexpected event " + event);
+                fail(mCurrentTestCase + " : Received unexpected event " + event);
             }
             return;
         }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/NintendoSwitchProTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/NintendoSwitchProTest.java
index d235428..28ce6dc 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/NintendoSwitchProTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/NintendoSwitchProTest.java
@@ -34,7 +34,7 @@
     }
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         super.setUp();
         /**
          * During probe, hid-nintendo sends commands to the joystick and waits for some of those
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/UsbVoiceCommandTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/UsbVoiceCommandTest.java
new file mode 100644
index 0000000..3cf4297
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/UsbVoiceCommandTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 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 android.hardware.input.cts.tests;
+
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.UiAutomation;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.hardware.cts.R;
+import android.hardware.input.cts.InputAssistantActivity;
+import android.server.wm.WindowManagerStateHelper;
+import android.speech.RecognizerIntent;
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class UsbVoiceCommandTest extends InputHidTestCase {
+    private static final String TAG = "UsbVoiceCommandTest";
+
+    private final UiDevice mUiDevice =
+            UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+    private final UiAutomation mUiAutomation =
+            InstrumentationRegistry.getInstrumentation().getUiAutomation();
+    private final PackageManager mPackageManager =
+            InstrumentationRegistry.getInstrumentation().getContext().getPackageManager();
+    private final Intent mVoiceIntent;
+    private final List<String> mExcludedPackages = new ArrayList<String>();
+
+    // Simulates the behavior of Google Gamepad with Voice Command buttons.
+    public UsbVoiceCommandTest() {
+        super(R.raw.google_gamepad_usb_register);
+        mVoiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
+        mVoiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, true);
+    }
+
+    private void setPackageState(boolean enabled) throws Exception {
+        runWithShellPermissionIdentity(mUiAutomation, () -> {
+            for (int i = 0; i < mExcludedPackages.size(); i++) {
+                if (enabled) {
+                    mUiDevice.executeShellCommand("pm enable " + mExcludedPackages.get(i));
+                } else {
+                    mUiDevice.executeShellCommand("pm disable " + mExcludedPackages.get(i));
+                }
+            }
+        });
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mUiAutomation.adoptShellPermissionIdentity();
+        List<ResolveInfo> list = mPackageManager.queryIntentActivities(mVoiceIntent,
+                PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
+
+        for (int i = 0; i < list.size(); i++) {
+            ResolveInfo info = list.get(i);
+            if (!info.activityInfo.packageName.equals(
+                    mActivityRule.getActivity().getPackageName())) {
+                mExcludedPackages.add(info.activityInfo.packageName);
+            }
+        }
+        // Set packages state to be disabled.
+        setPackageState(false);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Enable the packages.
+        setPackageState(true);
+        mExcludedPackages.clear();
+        super.tearDown();
+    }
+
+    @Test
+    public void testAllKeys() {
+        testInputEvents(R.raw.google_gamepad_keyeventtests);
+    }
+
+    /**
+     * Assistant keyevent is not sent to apps, verify InputAssistantActivity launched and visible.
+     */
+    @Test
+    public void testVoiceAssistantKey() throws Exception {
+
+        final ResolveInfo resolveInfo = mPackageManager.resolveActivity(mVoiceIntent, 0);
+        /* Verify InputAssistantActivity is the preferred activity by resolver */
+        assertEquals("InputAssistantActivity should be the preferred voice assistant activity",
+                mActivityRule.getActivity().getPackageName(),
+                resolveInfo.activityInfo.packageName);
+        /* Inject assistant key from hid device */
+        testInputEvents(R.raw.google_gamepad_assistkey);
+
+        WindowManagerStateHelper wmStateHelper = new WindowManagerStateHelper();
+
+        /* InputAssistantActivity should be visible */
+        final ComponentName inputAssistant =
+                new ComponentName(mActivityRule.getActivity().getPackageName(),
+                        InputAssistantActivity.class.getName());
+        wmStateHelper.waitForValidState(inputAssistant);
+        wmStateHelper.assertActivityDisplayed(inputAssistant);
+    }
+}