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);
+ }
+}