1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.voiceinteraction.cts; 18 19 import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; 20 import static android.Manifest.permission.RECORD_AUDIO; 21 import static android.content.Context.ATTENTION_SERVICE; 22 import static android.service.voice.HotwordDetectedResult.PROXIMITY_FAR; 23 import static android.service.voice.HotwordDetectedResult.PROXIMITY_NEAR; 24 import static android.service.voice.HotwordDetectedResult.PROXIMITY_UNKNOWN; 25 import static android.service.voice.HotwordDetectionService.ENABLE_PROXIMITY_RESULT; 26 import static android.voiceinteraction.cts.testcore.Helper.CTS_SERVICE_PACKAGE; 27 28 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 29 30 import static com.android.compatibility.common.util.ShellUtils.runShellCommand; 31 32 import static com.google.common.truth.Truth.assertThat; 33 34 import static org.junit.Assume.assumeTrue; 35 36 import android.app.Instrumentation; 37 import android.app.UiAutomation; 38 import android.attentionservice.cts.CtsTestAttentionService; 39 import android.content.res.Resources; 40 import android.os.ParcelFileDescriptor; 41 import android.platform.test.annotations.AppModeFull; 42 import android.provider.DeviceConfig; 43 import android.service.voice.AlwaysOnHotwordDetector; 44 import android.service.voice.HotwordDetectedResult; 45 import android.service.voice.HotwordDetectionService; 46 import android.service.voice.HotwordDetector; 47 import android.util.Log; 48 import android.voiceinteraction.cts.services.BaseVoiceInteractionService; 49 import android.voiceinteraction.cts.services.CtsBasicVoiceInteractionService; 50 import android.voiceinteraction.cts.testcore.Helper; 51 import android.voiceinteraction.cts.testcore.VoiceInteractionServiceConnectedRule; 52 53 import androidx.test.ext.junit.runners.AndroidJUnit4; 54 import androidx.test.filters.RequiresDevice; 55 import androidx.test.platform.app.InstrumentationRegistry; 56 57 import com.android.compatibility.common.util.ApiTest; 58 import com.android.compatibility.common.util.DeviceConfigStateChangerRule; 59 import com.android.compatibility.common.util.RequiredServiceRule; 60 61 import org.junit.After; 62 import org.junit.Before; 63 import org.junit.Rule; 64 import org.junit.Test; 65 import org.junit.runner.RunWith; 66 67 import java.util.Objects; 68 69 /** 70 * Tests for using the Attention Service inside VoiceInteractionService using 71 * a basic HotwordDetectionService. 72 */ 73 74 @ApiTest(apis = {"android.service.voice.HotwordDetectedResult#getExtras"}) 75 @RunWith(AndroidJUnit4.class) 76 @AppModeFull(reason = "No real use case for instant mode hotword detection service") 77 public final class HotwordDetectionServiceProximityTest { 78 79 private static final String TAG = "HotwordDetectionServiceProximityTest"; 80 private static final String SERVICE_ENABLED = "service_enabled"; 81 // The VoiceInteractionService used by this test 82 private static final String SERVICE_COMPONENT = 83 "android.voiceinteraction.cts.services.CtsBasicVoiceInteractionService"; 84 85 private static final double PROXIMITY_NEAR_METERS = 2.0; 86 private static final double PROXIMITY_FAR_METERS = 6.0; 87 private static final String FAKE_SERVICE_PACKAGE = 88 HotwordDetectionServiceProximityTest.class.getPackage().getName(); 89 90 @Rule 91 public final RequiredServiceRule ATTENTION_SERVICE_RULE = 92 new RequiredServiceRule(ATTENTION_SERVICE); 93 94 @Rule 95 public final DeviceConfigStateChangerRule mEnableAttentionManagerServiceRule = 96 new DeviceConfigStateChangerRule(sInstrumentation.getTargetContext(), 97 DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE, 98 SERVICE_ENABLED, 99 "true"); 100 101 @Rule 102 public VoiceInteractionServiceConnectedRule mConnectedRule = 103 new VoiceInteractionServiceConnectedRule( 104 getInstrumentation().getTargetContext(), getTestVoiceInteractionService()); 105 106 private CtsBasicVoiceInteractionService mService; 107 108 private static final Instrumentation sInstrumentation = 109 InstrumentationRegistry.getInstrumentation(); 110 111 private final Resources mResources = getInstrumentation().getTargetContext() 112 .getResources(); 113 114 private boolean mIsProximitySupported; 115 116 @Before setup()117 public void setup() { 118 try { 119 mIsProximitySupported = mResources.getBoolean( 120 mResources.getIdentifier("config_enableProximityService", "bool", "android")); 121 } catch (Resources.NotFoundException e) { 122 // this would mean resource is not found in device. skip test 123 } 124 assumeTrue("Proximity Feature not available on this device. Skipping test.", 125 mIsProximitySupported); 126 127 // Set up Attention Service 128 CtsTestAttentionService.reset(); 129 assertThat(setTestableAttentionService(FAKE_SERVICE_PACKAGE)).isTrue(); 130 assertThat(getAttentionServiceComponent()).contains(FAKE_SERVICE_PACKAGE); 131 runShellCommand("cmd attention call checkAttention"); 132 133 // VoiceInteractionServiceConnectedRule handles the service connected, 134 // the test should be able to get service 135 mService = (CtsBasicVoiceInteractionService) BaseVoiceInteractionService.getService(); 136 // Check the test can get the service 137 Objects.requireNonNull(mService); 138 } 139 140 @After tearDown()141 public void tearDown() { 142 runShellCommand("cmd attention clearTestableAttentionService"); 143 mService = null; 144 } 145 146 @Test 147 @RequiresDevice testAttentionService_onDetectFromDsp()148 public void testAttentionService_onDetectFromDsp() throws Throwable { 149 // Create AlwaysOnHotwordDetector and wait the HotwordDetectionService ready 150 AlwaysOnHotwordDetector alwaysOnHotwordDetector = createAlwaysOnHotwordDetector(); 151 152 try { 153 adoptShellPermissionIdentityForHotword(); 154 155 // Trigger recognition for test 156 triggerHardwareRecognitionEventForTest(alwaysOnHotwordDetector); 157 158 // wait onDetected() called and verify the result 159 HotwordDetectedResult hotwordDetectedResult1 = waitHotwordServiceOnDetectedResult(); 160 161 // by default, proximity should not be returned. 162 verifyProximityBundle(hotwordDetectedResult1, null); 163 164 // proximity is unknown 165 CtsTestAttentionService.respondProximity(PROXIMITY_UNKNOWN); 166 167 // Trigger recognition for test 168 triggerHardwareRecognitionEventForTest(alwaysOnHotwordDetector); 169 170 // wait onDetected() called and verify the result 171 HotwordDetectedResult hotwordDetectedResult2 = waitHotwordServiceOnDetectedResult(); 172 173 // when proximity is unknown, proximity should not be returned. 174 verifyProximityBundle(hotwordDetectedResult2, null); 175 176 // proximity is PROXIMITY_NEAR_METERS 177 CtsTestAttentionService.respondProximity(PROXIMITY_NEAR_METERS); 178 179 // Trigger recognition for test 180 triggerHardwareRecognitionEventForTest(alwaysOnHotwordDetector); 181 182 // wait onDetected() called and verify the result 183 HotwordDetectedResult hotwordDetectedResult3 = waitHotwordServiceOnDetectedResult(); 184 185 // when proximity is PROXIMITY_NEAR_METERS, proximity should be PROXIMITY_NEAR. 186 verifyProximityBundle(hotwordDetectedResult3, PROXIMITY_NEAR); 187 188 // proximity is PROXIMITY_FAR_METERS 189 CtsTestAttentionService.respondProximity(PROXIMITY_FAR_METERS); 190 191 // Trigger recognition for test 192 triggerHardwareRecognitionEventForTest(alwaysOnHotwordDetector); 193 194 // wait onDetected() called and verify the result 195 HotwordDetectedResult hotwordDetectedResult4 = waitHotwordServiceOnDetectedResult(); 196 197 // when proximity is PROXIMITY_FAR_METERS, proximity should be PROXIMITY_FAR. 198 verifyProximityBundle(hotwordDetectedResult4, PROXIMITY_FAR); 199 } finally { 200 alwaysOnHotwordDetector.destroy(); 201 // Drop identity adopted. 202 InstrumentationRegistry.getInstrumentation().getUiAutomation() 203 .dropShellPermissionIdentity(); 204 } 205 } 206 207 @Test 208 @RequiresDevice testAttentionService_onDetectFromMic_noUpdates()209 public void testAttentionService_onDetectFromMic_noUpdates() throws Throwable { 210 // Create SoftwareHotwordDetector 211 HotwordDetector softwareHotwordDetector = createSoftwareHotwordDetector(); 212 try { 213 adoptShellPermissionIdentityForHotword(); 214 215 mService.initDetectRejectLatch(); 216 softwareHotwordDetector.startRecognition(); 217 218 // wait onDetected() called and verify the result 219 HotwordDetectedResult hotwordDetectedResult = waitHotwordServiceOnDetectedResult(); 220 221 verifyProximityBundle(hotwordDetectedResult, null); 222 } finally { 223 softwareHotwordDetector.destroy(); 224 // Drop identity adopted. 225 InstrumentationRegistry.getInstrumentation().getUiAutomation() 226 .dropShellPermissionIdentity(); 227 } 228 } 229 230 @Test 231 @RequiresDevice testAttentionService_onDetectFromMic_unknownProximity()232 public void testAttentionService_onDetectFromMic_unknownProximity() throws Throwable { 233 // Create SoftwareHotwordDetector 234 HotwordDetector softwareHotwordDetector = createSoftwareHotwordDetector(); 235 236 CtsTestAttentionService.respondProximity(PROXIMITY_UNKNOWN); 237 238 try { 239 adoptShellPermissionIdentityForHotword(); 240 241 mService.initDetectRejectLatch(); 242 softwareHotwordDetector.startRecognition(); 243 244 // wait onDetected() called and verify the result 245 HotwordDetectedResult hotwordDetectedResult = waitHotwordServiceOnDetectedResult(); 246 247 verifyProximityBundle(hotwordDetectedResult, null); 248 } finally { 249 softwareHotwordDetector.destroy(); 250 // Drop identity adopted. 251 InstrumentationRegistry.getInstrumentation().getUiAutomation() 252 .dropShellPermissionIdentity(); 253 } 254 } 255 256 @Test 257 @RequiresDevice testAttentionService_onDetectFromMic_updatedProximity()258 public void testAttentionService_onDetectFromMic_updatedProximity() throws Throwable { 259 // Create SoftwareHotwordDetector 260 HotwordDetector softwareHotwordDetector = createSoftwareHotwordDetector(); 261 262 CtsTestAttentionService.respondProximity(PROXIMITY_NEAR_METERS); 263 264 try { 265 adoptShellPermissionIdentityForHotword(); 266 267 mService.initDetectRejectLatch(); 268 softwareHotwordDetector.startRecognition(); 269 270 // wait onDetected() called and verify the result 271 HotwordDetectedResult hotwordDetectedResult = waitHotwordServiceOnDetectedResult(); 272 273 verifyProximityBundle(hotwordDetectedResult, PROXIMITY_NEAR); 274 } finally { 275 softwareHotwordDetector.destroy(); 276 // Drop identity adopted. 277 InstrumentationRegistry.getInstrumentation().getUiAutomation() 278 .dropShellPermissionIdentity(); 279 } 280 } 281 282 @Test 283 @RequiresDevice testAttentionService_onDetectFromExternalSource_doesNotReceiveProximity()284 public void testAttentionService_onDetectFromExternalSource_doesNotReceiveProximity() 285 throws Throwable { 286 AlwaysOnHotwordDetector alwaysOnHotwordDetector = createAlwaysOnHotwordDetector(); 287 CtsTestAttentionService.respondProximity(PROXIMITY_FAR); 288 289 try { 290 adoptShellPermissionIdentityForHotword(); 291 292 ParcelFileDescriptor audioStream = Helper.createFakeAudioStream(); 293 mService.initDetectRejectLatch(); 294 alwaysOnHotwordDetector.startRecognition(audioStream, 295 Helper.createFakeAudioFormat(), 296 Helper.createFakePersistableBundleData()); 297 298 // wait onDetected() called and verify the result 299 HotwordDetectedResult hotwordDetectedResult = waitHotwordServiceOnDetectedResult(); 300 301 verifyProximityBundle(hotwordDetectedResult, null); 302 } finally { 303 alwaysOnHotwordDetector.destroy(); 304 // Drop identity adopted. 305 InstrumentationRegistry.getInstrumentation().getUiAutomation() 306 .dropShellPermissionIdentity(); 307 } 308 } 309 getTestVoiceInteractionService()310 public String getTestVoiceInteractionService() { 311 Log.d(TAG, "getTestVoiceInteractionService()"); 312 return CTS_SERVICE_PACKAGE + "/" + SERVICE_COMPONENT; 313 } 314 getAttentionServiceComponent()315 private static String getAttentionServiceComponent() { 316 return runShellCommand("cmd attention getAttentionServiceComponent"); 317 } 318 setTestableAttentionService(String service)319 private static boolean setTestableAttentionService(String service) { 320 return runShellCommand("cmd attention setTestableAttentionService " + service) 321 .equals("true"); 322 } 323 324 // TODO: use a base test case and move common part to base test class triggerHardwareRecognitionEventForTest( AlwaysOnHotwordDetector alwaysOnHotwordDetector)325 private void triggerHardwareRecognitionEventForTest( 326 AlwaysOnHotwordDetector alwaysOnHotwordDetector) { 327 mService.initDetectRejectLatch(); 328 alwaysOnHotwordDetector.triggerHardwareRecognitionEventForTest( 329 /* status= */ 0, /* soundModelHandle= */ 100, 330 /* halEventReceivedMillis= */ 12345, /* captureAvailable= */ true, 331 /* captureSession= */ 101, /* captureDelayMs= */ 1000, 332 /* capturePreambleMs= */ 1001, /* triggerInData= */ true, 333 Helper.createFakeAudioFormat(), new byte[1024], 334 Helper.createFakeKeyphraseRecognitionExtraList()); 335 } 336 waitHotwordServiceOnDetectedResult()337 private HotwordDetectedResult waitHotwordServiceOnDetectedResult() throws Throwable { 338 mService.waitOnDetectOrRejectCalled(); 339 AlwaysOnHotwordDetector.EventPayload detectedResult = 340 mService.getHotwordServiceOnDetectedResult(); 341 return detectedResult.getHotwordDetectedResult(); 342 } 343 adoptShellPermissionIdentityForHotword()344 private void adoptShellPermissionIdentityForHotword() { 345 // Drop any identity adopted earlier. 346 UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 347 uiAutomation.dropShellPermissionIdentity(); 348 // need to retain the identity until the callback is triggered 349 uiAutomation.adoptShellPermissionIdentity(RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD); 350 } 351 352 /** 353 * Create software hotword detector and wait for ready 354 */ createSoftwareHotwordDetector()355 private HotwordDetector createSoftwareHotwordDetector() throws Throwable { 356 // Create SoftwareHotwordDetector 357 mService.createSoftwareHotwordDetector(); 358 359 mService.waitSandboxedDetectionServiceInitializedCalledOrException(); 360 361 // verify callback result 362 assertThat(mService.getSandboxedDetectionServiceInitializedResult()).isEqualTo( 363 HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS); 364 HotwordDetector softwareHotwordDetector = mService.getSoftwareHotwordDetector(); 365 Objects.requireNonNull(softwareHotwordDetector); 366 367 return softwareHotwordDetector; 368 } 369 370 /** 371 * Create AlwaysOnHotwordDetector and wait for ready 372 */ createAlwaysOnHotwordDetector()373 private AlwaysOnHotwordDetector createAlwaysOnHotwordDetector() throws Throwable { 374 // Create AlwaysOnHotwordDetector and wait ready. 375 mService.createAlwaysOnHotwordDetector(); 376 377 mService.waitSandboxedDetectionServiceInitializedCalledOrException(); 378 379 // verify callback result 380 assertThat(mService.getSandboxedDetectionServiceInitializedResult()).isEqualTo( 381 HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS); 382 AlwaysOnHotwordDetector alwaysOnHotwordDetector = mService.getAlwaysOnHotwordDetector(); 383 Objects.requireNonNull(alwaysOnHotwordDetector); 384 385 return alwaysOnHotwordDetector; 386 } 387 388 // simply check that the proximity values are equal. verifyProximityBundle(HotwordDetectedResult hotwordDetectedResult, Integer expected)389 private void verifyProximityBundle(HotwordDetectedResult hotwordDetectedResult, 390 Integer expected) { 391 assertThat(hotwordDetectedResult).isNotNull(); 392 if (expected == null || !ENABLE_PROXIMITY_RESULT) { 393 assertThat(hotwordDetectedResult.getProximity()).isEqualTo(PROXIMITY_UNKNOWN); 394 } else { 395 assertThat(hotwordDetectedResult.getProximity()).isEqualTo(expected); 396 } 397 } 398 } 399