blob: 2615dddb795ef122a27067d70b4c2218824a55d9 [file] [log] [blame]
/*
* Copyright (C) 2022 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.voiceinteraction.cts;
import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
import static android.Manifest.permission.RECORD_AUDIO;
import static android.content.Context.ATTENTION_SERVICE;
import static android.service.voice.HotwordDetectedResult.PROXIMITY_FAR;
import static android.service.voice.HotwordDetectedResult.PROXIMITY_NEAR;
import static android.service.voice.HotwordDetectedResult.PROXIMITY_UNKNOWN;
import static android.service.voice.HotwordDetectionService.ENABLE_PROXIMITY_RESULT;
import static android.voiceinteraction.cts.testcore.Helper.CTS_SERVICE_PACKAGE;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeTrue;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.attentionservice.cts.CtsTestAttentionService;
import android.content.res.Resources;
import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.AppModeFull;
import android.provider.DeviceConfig;
import android.service.voice.AlwaysOnHotwordDetector;
import android.service.voice.HotwordDetectedResult;
import android.service.voice.HotwordDetectionService;
import android.service.voice.HotwordDetector;
import android.util.Log;
import android.voiceinteraction.cts.services.BaseVoiceInteractionService;
import android.voiceinteraction.cts.services.CtsBasicVoiceInteractionService;
import android.voiceinteraction.cts.testcore.Helper;
import android.voiceinteraction.cts.testcore.VoiceInteractionServiceConnectedRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.RequiresDevice;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.ApiTest;
import com.android.compatibility.common.util.DeviceConfigStateChangerRule;
import com.android.compatibility.common.util.RequiredServiceRule;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Objects;
/**
* Tests for using the Attention Service inside VoiceInteractionService using
* a basic HotwordDetectionService.
*/
@ApiTest(apis = {"android.service.voice.HotwordDetectedResult#getExtras"})
@RunWith(AndroidJUnit4.class)
@AppModeFull(reason = "No real use case for instant mode hotword detection service")
public final class HotwordDetectionServiceProximityTest {
private static final String TAG = "HotwordDetectionServiceProximityTest";
private static final String SERVICE_ENABLED = "service_enabled";
// The VoiceInteractionService used by this test
private static final String SERVICE_COMPONENT =
"android.voiceinteraction.cts.services.CtsBasicVoiceInteractionService";
private static final double PROXIMITY_NEAR_METERS = 2.0;
private static final double PROXIMITY_FAR_METERS = 6.0;
private static final String FAKE_SERVICE_PACKAGE =
HotwordDetectionServiceProximityTest.class.getPackage().getName();
@Rule
public final RequiredServiceRule ATTENTION_SERVICE_RULE =
new RequiredServiceRule(ATTENTION_SERVICE);
@Rule
public final DeviceConfigStateChangerRule mEnableAttentionManagerServiceRule =
new DeviceConfigStateChangerRule(sInstrumentation.getTargetContext(),
DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE,
SERVICE_ENABLED,
"true");
@Rule
public VoiceInteractionServiceConnectedRule mConnectedRule =
new VoiceInteractionServiceConnectedRule(
getInstrumentation().getTargetContext(), getTestVoiceInteractionService());
private CtsBasicVoiceInteractionService mService;
private static final Instrumentation sInstrumentation =
InstrumentationRegistry.getInstrumentation();
private final Resources mResources = getInstrumentation().getTargetContext()
.getResources();
private boolean mIsProximitySupported;
@Before
public void setup() {
try {
mIsProximitySupported = mResources.getBoolean(
mResources.getIdentifier("config_enableProximityService", "bool", "android"));
} catch (Resources.NotFoundException e) {
// this would mean resource is not found in device. skip test
}
assumeTrue("Proximity Feature not available on this device. Skipping test.",
mIsProximitySupported);
// Set up Attention Service
CtsTestAttentionService.reset();
assertThat(setTestableAttentionService(FAKE_SERVICE_PACKAGE)).isTrue();
assertThat(getAttentionServiceComponent()).contains(FAKE_SERVICE_PACKAGE);
runShellCommand("cmd attention call checkAttention");
// VoiceInteractionServiceConnectedRule handles the service connected,
// the test should be able to get service
mService = (CtsBasicVoiceInteractionService) BaseVoiceInteractionService.getService();
// Check the test can get the service
Objects.requireNonNull(mService);
}
@After
public void tearDown() {
runShellCommand("cmd attention clearTestableAttentionService");
mService = null;
}
@Test
@RequiresDevice
public void testAttentionService_onDetectFromDsp() throws Throwable {
// Create AlwaysOnHotwordDetector and wait the HotwordDetectionService ready
AlwaysOnHotwordDetector alwaysOnHotwordDetector = createAlwaysOnHotwordDetector();
try {
adoptShellPermissionIdentityForHotword();
// Trigger recognition for test
triggerHardwareRecognitionEventForTest(alwaysOnHotwordDetector);
// wait onDetected() called and verify the result
HotwordDetectedResult hotwordDetectedResult1 = waitHotwordServiceOnDetectedResult();
// by default, proximity should not be returned.
verifyProximityBundle(hotwordDetectedResult1, null);
// proximity is unknown
CtsTestAttentionService.respondProximity(PROXIMITY_UNKNOWN);
// Trigger recognition for test
triggerHardwareRecognitionEventForTest(alwaysOnHotwordDetector);
// wait onDetected() called and verify the result
HotwordDetectedResult hotwordDetectedResult2 = waitHotwordServiceOnDetectedResult();
// when proximity is unknown, proximity should not be returned.
verifyProximityBundle(hotwordDetectedResult2, null);
// proximity is PROXIMITY_NEAR_METERS
CtsTestAttentionService.respondProximity(PROXIMITY_NEAR_METERS);
// Trigger recognition for test
triggerHardwareRecognitionEventForTest(alwaysOnHotwordDetector);
// wait onDetected() called and verify the result
HotwordDetectedResult hotwordDetectedResult3 = waitHotwordServiceOnDetectedResult();
// when proximity is PROXIMITY_NEAR_METERS, proximity should be PROXIMITY_NEAR.
verifyProximityBundle(hotwordDetectedResult3, PROXIMITY_NEAR);
// proximity is PROXIMITY_FAR_METERS
CtsTestAttentionService.respondProximity(PROXIMITY_FAR_METERS);
// Trigger recognition for test
triggerHardwareRecognitionEventForTest(alwaysOnHotwordDetector);
// wait onDetected() called and verify the result
HotwordDetectedResult hotwordDetectedResult4 = waitHotwordServiceOnDetectedResult();
// when proximity is PROXIMITY_FAR_METERS, proximity should be PROXIMITY_FAR.
verifyProximityBundle(hotwordDetectedResult4, PROXIMITY_FAR);
} finally {
alwaysOnHotwordDetector.destroy();
// Drop identity adopted.
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.dropShellPermissionIdentity();
}
}
@Test
@RequiresDevice
public void testAttentionService_onDetectFromMic_noUpdates() throws Throwable {
// Create SoftwareHotwordDetector
HotwordDetector softwareHotwordDetector = createSoftwareHotwordDetector();
try {
adoptShellPermissionIdentityForHotword();
mService.initDetectRejectLatch();
softwareHotwordDetector.startRecognition();
// wait onDetected() called and verify the result
HotwordDetectedResult hotwordDetectedResult = waitHotwordServiceOnDetectedResult();
verifyProximityBundle(hotwordDetectedResult, null);
} finally {
softwareHotwordDetector.destroy();
// Drop identity adopted.
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.dropShellPermissionIdentity();
}
}
@Test
@RequiresDevice
public void testAttentionService_onDetectFromMic_unknownProximity() throws Throwable {
// Create SoftwareHotwordDetector
HotwordDetector softwareHotwordDetector = createSoftwareHotwordDetector();
CtsTestAttentionService.respondProximity(PROXIMITY_UNKNOWN);
try {
adoptShellPermissionIdentityForHotword();
mService.initDetectRejectLatch();
softwareHotwordDetector.startRecognition();
// wait onDetected() called and verify the result
HotwordDetectedResult hotwordDetectedResult = waitHotwordServiceOnDetectedResult();
verifyProximityBundle(hotwordDetectedResult, null);
} finally {
softwareHotwordDetector.destroy();
// Drop identity adopted.
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.dropShellPermissionIdentity();
}
}
@Test
@RequiresDevice
public void testAttentionService_onDetectFromMic_updatedProximity() throws Throwable {
// Create SoftwareHotwordDetector
HotwordDetector softwareHotwordDetector = createSoftwareHotwordDetector();
CtsTestAttentionService.respondProximity(PROXIMITY_NEAR_METERS);
try {
adoptShellPermissionIdentityForHotword();
mService.initDetectRejectLatch();
softwareHotwordDetector.startRecognition();
// wait onDetected() called and verify the result
HotwordDetectedResult hotwordDetectedResult = waitHotwordServiceOnDetectedResult();
verifyProximityBundle(hotwordDetectedResult, PROXIMITY_NEAR);
} finally {
softwareHotwordDetector.destroy();
// Drop identity adopted.
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.dropShellPermissionIdentity();
}
}
@Test
@RequiresDevice
public void testAttentionService_onDetectFromExternalSource_doesNotReceiveProximity()
throws Throwable {
AlwaysOnHotwordDetector alwaysOnHotwordDetector = createAlwaysOnHotwordDetector();
CtsTestAttentionService.respondProximity(PROXIMITY_FAR);
try {
adoptShellPermissionIdentityForHotword();
ParcelFileDescriptor audioStream = Helper.createFakeAudioStream();
mService.initDetectRejectLatch();
alwaysOnHotwordDetector.startRecognition(audioStream,
Helper.createFakeAudioFormat(),
Helper.createFakePersistableBundleData());
// wait onDetected() called and verify the result
HotwordDetectedResult hotwordDetectedResult = waitHotwordServiceOnDetectedResult();
verifyProximityBundle(hotwordDetectedResult, null);
} finally {
alwaysOnHotwordDetector.destroy();
// Drop identity adopted.
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.dropShellPermissionIdentity();
}
}
public String getTestVoiceInteractionService() {
Log.d(TAG, "getTestVoiceInteractionService()");
return CTS_SERVICE_PACKAGE + "/" + SERVICE_COMPONENT;
}
private static String getAttentionServiceComponent() {
return runShellCommand("cmd attention getAttentionServiceComponent");
}
private static boolean setTestableAttentionService(String service) {
return runShellCommand("cmd attention setTestableAttentionService " + service)
.equals("true");
}
// TODO: use a base test case and move common part to base test class
private void triggerHardwareRecognitionEventForTest(
AlwaysOnHotwordDetector alwaysOnHotwordDetector) {
mService.initDetectRejectLatch();
alwaysOnHotwordDetector.triggerHardwareRecognitionEventForTest(
/* status= */ 0, /* soundModelHandle= */ 100,
/* halEventReceivedMillis= */ 12345, /* captureAvailable= */ true,
/* captureSession= */ 101, /* captureDelayMs= */ 1000,
/* capturePreambleMs= */ 1001, /* triggerInData= */ true,
Helper.createFakeAudioFormat(), new byte[1024],
Helper.createFakeKeyphraseRecognitionExtraList());
}
private HotwordDetectedResult waitHotwordServiceOnDetectedResult() throws Throwable {
mService.waitOnDetectOrRejectCalled();
AlwaysOnHotwordDetector.EventPayload detectedResult =
mService.getHotwordServiceOnDetectedResult();
return detectedResult.getHotwordDetectedResult();
}
private void adoptShellPermissionIdentityForHotword() {
// Drop any identity adopted earlier.
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
uiAutomation.dropShellPermissionIdentity();
// need to retain the identity until the callback is triggered
uiAutomation.adoptShellPermissionIdentity(RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD);
}
/**
* Create software hotword detector and wait for ready
*/
private HotwordDetector createSoftwareHotwordDetector() throws Throwable {
// Create SoftwareHotwordDetector
mService.createSoftwareHotwordDetector();
mService.waitSandboxedDetectionServiceInitializedCalledOrException();
// verify callback result
assertThat(mService.getSandboxedDetectionServiceInitializedResult()).isEqualTo(
HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS);
HotwordDetector softwareHotwordDetector = mService.getSoftwareHotwordDetector();
Objects.requireNonNull(softwareHotwordDetector);
return softwareHotwordDetector;
}
/**
* Create AlwaysOnHotwordDetector and wait for ready
*/
private AlwaysOnHotwordDetector createAlwaysOnHotwordDetector() throws Throwable {
// Create AlwaysOnHotwordDetector and wait ready.
mService.createAlwaysOnHotwordDetector();
mService.waitSandboxedDetectionServiceInitializedCalledOrException();
// verify callback result
assertThat(mService.getSandboxedDetectionServiceInitializedResult()).isEqualTo(
HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS);
AlwaysOnHotwordDetector alwaysOnHotwordDetector = mService.getAlwaysOnHotwordDetector();
Objects.requireNonNull(alwaysOnHotwordDetector);
return alwaysOnHotwordDetector;
}
// simply check that the proximity values are equal.
private void verifyProximityBundle(HotwordDetectedResult hotwordDetectedResult,
Integer expected) {
assertThat(hotwordDetectedResult).isNotNull();
if (expected == null || !ENABLE_PROXIMITY_RESULT) {
assertThat(hotwordDetectedResult.getProximity()).isEqualTo(PROXIMITY_UNKNOWN);
} else {
assertThat(hotwordDetectedResult.getProximity()).isEqualTo(expected);
}
}
}