1 /* 2 * Copyright (C) 2021 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.MANAGE_HOTWORD_DETECTION; 21 import static android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE; 22 import static android.Manifest.permission.RECORD_AUDIO; 23 import static android.content.pm.PackageManager.FEATURE_MICROPHONE; 24 import static android.voiceinteraction.common.Utils.AUDIO_EGRESS_DETECTED_RESULT; 25 import static android.voiceinteraction.common.Utils.AUDIO_EGRESS_DETECTED_RESULT_WRONG_COPY_BUFFER_SIZE; 26 import static android.voiceinteraction.common.Utils.EXTRA_HOTWORD_DETECTION_SERVICE_CAN_READ_AUDIO_DATA_IS_NOT_ZERO; 27 import static android.voiceinteraction.cts.testcore.Helper.CTS_SERVICE_PACKAGE; 28 import static android.voiceinteraction.cts.testcore.Helper.MANAGE_VOICE_KEYPHRASES; 29 import static android.voiceinteraction.cts.testcore.Helper.WAIT_TIMEOUT_IN_MS; 30 import static android.voiceinteraction.cts.testcore.Helper.createKeyphraseRecognitionExtraList; 31 import static android.voiceinteraction.cts.testcore.Helper.waitForFutureDoneAndAssertSuccessful; 32 33 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 34 35 import static com.android.compatibility.common.util.ShellUtils.runShellCommand; 36 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; 37 38 import static com.google.common.truth.Truth.assertThat; 39 40 import static org.junit.Assert.assertThrows; 41 import static org.junit.Assume.assumeFalse; 42 import static org.junit.Assume.assumeTrue; 43 44 import static java.util.concurrent.TimeUnit.SECONDS; 45 46 import android.app.AppOpsManager; 47 import android.app.Instrumentation; 48 import android.app.UiAutomation; 49 import android.app.wearable.WearableSensingManager; 50 import android.content.ComponentName; 51 import android.content.pm.PackageManager; 52 import android.hardware.soundtrigger.SoundTrigger; 53 import android.media.AudioAttributes; 54 import android.media.AudioFormat; 55 import android.media.AudioRecord; 56 import android.media.MediaRecorder; 57 import android.media.soundtrigger.SoundTriggerInstrumentation.RecognitionSession; 58 import android.os.ParcelFileDescriptor; 59 import android.os.PersistableBundle; 60 import android.os.Process; 61 import android.os.SystemClock; 62 import android.os.UserHandle; 63 import android.platform.test.annotations.AppModeFull; 64 import android.platform.test.annotations.RequiresFlagsEnabled; 65 import android.platform.test.flag.junit.CheckFlagsRule; 66 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 67 import android.provider.DeviceConfig; 68 import android.service.voice.AlwaysOnHotwordDetector; 69 import android.service.voice.HotwordDetectionService; 70 import android.service.voice.HotwordDetectionServiceFailure; 71 import android.service.voice.HotwordDetector; 72 import android.service.voice.HotwordRejectedResult; 73 import android.service.voice.SandboxedDetectionInitializer; 74 import android.soundtrigger.cts.instrumentation.SoundTriggerInstrumentationObserver; 75 import android.util.Log; 76 import android.voiceinteraction.common.Utils; 77 import android.voiceinteraction.cts.services.BaseVoiceInteractionService; 78 import android.voiceinteraction.cts.services.CtsBasicVoiceInteractionService; 79 import android.voiceinteraction.cts.testcore.Helper; 80 import android.voiceinteraction.cts.testcore.VoiceInteractionServiceConnectedRule; 81 import android.voiceinteraction.service.MainWearableSensingService; 82 83 import androidx.test.ext.junit.runners.AndroidJUnit4; 84 import androidx.test.filters.RequiresDevice; 85 import androidx.test.platform.app.InstrumentationRegistry; 86 87 import com.android.compatibility.common.util.CddTest; 88 import com.android.compatibility.common.util.DisableAnimationRule; 89 import com.android.compatibility.common.util.RequiredFeatureRule; 90 import com.android.compatibility.common.util.SystemUtil; 91 92 import org.junit.After; 93 import org.junit.AfterClass; 94 import org.junit.Before; 95 import org.junit.BeforeClass; 96 import org.junit.Ignore; 97 import org.junit.Rule; 98 import org.junit.Test; 99 import org.junit.runner.RunWith; 100 101 import java.io.IOException; 102 import java.io.OutputStream; 103 import java.util.Objects; 104 import java.util.Random; 105 import java.util.UUID; 106 import java.util.concurrent.CountDownLatch; 107 import java.util.concurrent.Executor; 108 import java.util.concurrent.Executors; 109 import java.util.concurrent.TimeUnit; 110 import java.util.concurrent.atomic.AtomicInteger; 111 112 /** 113 * Tests for {@link HotwordDetectionService}. 114 */ 115 @RunWith(AndroidJUnit4.class) 116 @AppModeFull(reason = "No real use case for instant mode hotword detection service") 117 public class HotwordDetectionServiceBasicTest { 118 119 private static final String TAG = "HotwordDetectionServiceTest"; 120 // The VoiceInteractionService used by this test 121 private static final String SERVICE_COMPONENT = 122 "android.voiceinteraction.cts.services.CtsBasicVoiceInteractionService"; 123 private static final int USER_ID = UserHandle.myUserId(); 124 private static final String MAIN_WEARABLE_SENSING_SERVICE_NAME = 125 "android.voiceinteraction.cts/android.voiceinteraction.service." 126 + "MainWearableSensingService"; 127 private static final ComponentName VIS_COMPONENT_NAME = 128 new ComponentName("android.voiceinteraction.cts", SERVICE_COMPONENT); 129 private static final int TEMPORARY_SERVICE_DURATION_MS = 10000; 130 private static final String KEY_WEARABLE_SENSING_SERVICE_ENABLED = "service_enabled"; 131 132 private final CountDownLatch mLatch = new CountDownLatch(1); 133 134 private String mOpNoted = ""; 135 136 private final AppOpsManager mAppOpsManager = sInstrumentation.getContext() 137 .getSystemService(AppOpsManager.class); 138 139 private final AppOpsManager.OnOpNotedListener mOnOpNotedListener = 140 (op, uid, pkgName, attributionTag, flags, result) -> { 141 Log.d(TAG, "Get OnOpNotedListener callback op = " + op + ", uid = " + uid); 142 // We adopt ShellPermissionIdentity for RECORD_AUDIO to pass the permission check, 143 // so the uid should be the shell uid. 144 if (Process.SHELL_UID == uid && op.equals(AppOpsManager.OPSTR_RECORD_AUDIO)) { 145 if (mLatch != null) { 146 mLatch.countDown(); 147 } 148 } 149 mOpNoted = op; 150 }; 151 152 private CtsBasicVoiceInteractionService mService; 153 154 private static String sWasIndicatorEnabled; 155 private static String sDefaultScreenOffTimeoutValue; 156 private static String sDefaultHotwordDetectionServiceRestartPeriodValue; 157 private static final Instrumentation sInstrumentation = 158 InstrumentationRegistry.getInstrumentation(); 159 private static final PackageManager sPkgMgr = sInstrumentation.getContext().getPackageManager(); 160 private static final WearableSensingManager sWearableSensingManager = 161 sInstrumentation.getContext().getSystemService(WearableSensingManager.class); 162 163 @Rule 164 public VoiceInteractionServiceConnectedRule mConnectedRule = 165 new VoiceInteractionServiceConnectedRule( 166 getInstrumentation().getTargetContext(), getTestVoiceInteractionService()); 167 168 @Rule 169 public DisableAnimationRule mDisableAnimationRule = new DisableAnimationRule(); 170 171 @Rule 172 public RequiredFeatureRule REQUIRES_MIC_RULE = new RequiredFeatureRule(FEATURE_MICROPHONE); 173 174 @Rule 175 public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); 176 177 private SoundTrigger.Keyphrase[] mKeyphraseArray; 178 private final SoundTriggerInstrumentationObserver mInstrumentationObserver = 179 new SoundTriggerInstrumentationObserver(); 180 private final Executor mExecutor = Executors.newSingleThreadExecutor(); 181 private String mOriginalWearableSensingServiceEnabledConfig; 182 183 @BeforeClass enableIndicators()184 public static void enableIndicators() { 185 sWasIndicatorEnabled = Helper.getIndicatorEnabledState(); 186 Helper.setIndicatorEnabledState(Boolean.toString(true)); 187 } 188 189 @AfterClass resetIndicators()190 public static void resetIndicators() { 191 Helper.setIndicatorEnabledState(sWasIndicatorEnabled); 192 } 193 194 @BeforeClass extendScreenOffTimeout()195 public static void extendScreenOffTimeout() throws Exception { 196 // Change screen off timeout to 20 minutes. 197 sDefaultScreenOffTimeoutValue = SystemUtil.runShellCommand( 198 "settings get system screen_off_timeout"); 199 SystemUtil.runShellCommand("settings put system screen_off_timeout 1200000"); 200 } 201 202 @AfterClass restoreScreenOffTimeout()203 public static void restoreScreenOffTimeout() { 204 SystemUtil.runShellCommand( 205 "settings put system screen_off_timeout " + sDefaultScreenOffTimeoutValue); 206 } 207 208 @BeforeClass getHotwordDetectionServiceRestartPeriodValue()209 public static void getHotwordDetectionServiceRestartPeriodValue() { 210 sDefaultHotwordDetectionServiceRestartPeriodValue = 211 Helper.getHotwordDetectionServiceRestartPeriod(); 212 } 213 214 @AfterClass resetHotwordDetectionServiceRestartPeriodValue()215 public static void resetHotwordDetectionServiceRestartPeriodValue() { 216 Helper.setHotwordDetectionServiceRestartPeriod( 217 sDefaultHotwordDetectionServiceRestartPeriodValue); 218 } 219 220 @Before setup()221 public void setup() { 222 // VoiceInteractionServiceConnectedRule handles the service connected, 223 // the test should be able to get service 224 mService = (CtsBasicVoiceInteractionService) BaseVoiceInteractionService.getService(); 225 // Check the test can get the service 226 Objects.requireNonNull(mService); 227 228 mKeyphraseArray = Helper.createKeyphraseArray(mService); 229 230 // Hook up SoundTriggerInjection to inject/observe STHAL operations. 231 // Requires MANAGE_SOUND_TRIGGER 232 runWithShellPermissionIdentity(() -> 233 mInstrumentationObserver.attachInstrumentation()); 234 235 // Wait the original HotwordDetectionService finish clean up to avoid flaky 236 // This also waits for mic indicator disappear 237 SystemClock.sleep(5_000); 238 } 239 240 @After tearDown()241 public void tearDown() { 242 // Clean up any unexpected HAL state 243 try { 244 mInstrumentationObserver.close(); 245 } catch (Exception e) { 246 throw new RuntimeException(e); 247 } 248 249 mService.resetState(); 250 mService = null; 251 clearTestableWearableSensingService(); 252 } 253 getTestVoiceInteractionService()254 public String getTestVoiceInteractionService() { 255 Log.d(TAG, "getTestVoiceInteractionService()"); 256 return CTS_SERVICE_PACKAGE + "/" + SERVICE_COMPONENT; 257 } 258 259 @Test testHotwordDetectionService_getMaxCustomInitializationStatus()260 public void testHotwordDetectionService_getMaxCustomInitializationStatus() 261 throws Throwable { 262 // TODO: not use Deprecated method 263 assertThat(HotwordDetectionService.getMaxCustomInitializationStatus()).isEqualTo(2); 264 } 265 266 @Test testHotwordDetectionService_createDspDetector_sendOverMaxResult_getException()267 public void testHotwordDetectionService_createDspDetector_sendOverMaxResult_getException() 268 throws Throwable { 269 PersistableBundle persistableBundle = new PersistableBundle(); 270 persistableBundle.putInt(Utils.KEY_TEST_SCENARIO, 271 Utils.EXTRA_HOTWORD_DETECTION_SERVICE_SEND_OVER_MAX_INIT_STATUS); 272 273 try { 274 // Create AlwaysOnHotwordDetector and wait result 275 mService.createAlwaysOnHotwordDetector(/* useExecutor= */ false, /* runOnMainThread= */ 276 false, persistableBundle); 277 278 // Wait the result and verify expected result 279 mService.waitSandboxedDetectionServiceInitializedCalledOrException(); 280 281 // When the HotwordDetectionService sends the initialization status that overs the 282 // getMaxCustomInitializationStatus, the HotwordDetectionService will get the 283 // IllegalArgumentException. In order to test this case, we send the max custom 284 // initialization status when the HotwordDetectionService gets the 285 // IllegalArgumentException. 286 assertThat(mService.getSandboxedDetectionServiceInitializedResult()).isEqualTo( 287 SandboxedDetectionInitializer.getMaxCustomInitializationStatus()); 288 } finally { 289 AlwaysOnHotwordDetector alwaysOnHotwordDetector = mService.getAlwaysOnHotwordDetector(); 290 if (alwaysOnHotwordDetector != null) { 291 alwaysOnHotwordDetector.destroy(); 292 } 293 } 294 } 295 296 @Test testHotwordDetectionService_createSoftwareDetector_sendOverMaxResult_getException()297 public void testHotwordDetectionService_createSoftwareDetector_sendOverMaxResult_getException() 298 throws Throwable { 299 PersistableBundle persistableBundle = new PersistableBundle(); 300 persistableBundle.putInt(Utils.KEY_TEST_SCENARIO, 301 Utils.EXTRA_HOTWORD_DETECTION_SERVICE_SEND_OVER_MAX_INIT_STATUS); 302 303 try { 304 // Create SoftwareHotwordDetector and wait result 305 mService.createSoftwareHotwordDetector(/* useExecutor= */ false, /* runOnMainThread= */ 306 false, persistableBundle); 307 308 // Wait the result and verify expected result 309 mService.waitSandboxedDetectionServiceInitializedCalledOrException(); 310 311 // When the HotwordDetectionService sends the initialization status that overs the 312 // getMaxCustomInitializationStatus, the HotwordDetectionService will get the 313 // IllegalArgumentException. In order to test this case, we send the max custom 314 // initialization status when the HotwordDetectionService gets the 315 // IllegalArgumentException. 316 assertThat(mService.getSandboxedDetectionServiceInitializedResult()).isEqualTo( 317 SandboxedDetectionInitializer.getMaxCustomInitializationStatus()); 318 } finally { 319 HotwordDetector softwareHotwordDetector = mService.getSoftwareHotwordDetector(); 320 if (softwareHotwordDetector != null) { 321 softwareHotwordDetector.destroy(); 322 } 323 } 324 } 325 326 @Test testHotwordDetectionService_createDspDetector_customResult_getCustomStatus()327 public void testHotwordDetectionService_createDspDetector_customResult_getCustomStatus() 328 throws Throwable { 329 final int customStatus = SandboxedDetectionInitializer.INITIALIZATION_STATUS_SUCCESS + 1; 330 PersistableBundle persistableBundle = new PersistableBundle(); 331 persistableBundle.putInt(Utils.KEY_TEST_SCENARIO, 332 Utils.EXTRA_HOTWORD_DETECTION_SERVICE_SEND_CUSTOM_INIT_STATUS); 333 persistableBundle.putInt(Utils.KEY_INITIALIZATION_STATUS, customStatus); 334 335 try { 336 // Create AlwaysOnHotwordDetector and wait result 337 mService.createAlwaysOnHotwordDetector(/* useExecutor= */ false, /* runOnMainThread= */ 338 false, persistableBundle); 339 340 // Wait the result and verify expected result 341 mService.waitSandboxedDetectionServiceInitializedCalledOrException(); 342 343 // verify callback result 344 assertThat(mService.getSandboxedDetectionServiceInitializedResult()).isEqualTo( 345 customStatus); 346 } finally { 347 AlwaysOnHotwordDetector alwaysOnHotwordDetector = mService.getAlwaysOnHotwordDetector(); 348 if (alwaysOnHotwordDetector != null) { 349 alwaysOnHotwordDetector.destroy(); 350 } 351 } 352 } 353 354 @Test testHotwordDetectionService_createSoftwareDetector_customResult_getCustomStatus()355 public void testHotwordDetectionService_createSoftwareDetector_customResult_getCustomStatus() 356 throws Throwable { 357 final int customStatus = SandboxedDetectionInitializer.INITIALIZATION_STATUS_SUCCESS + 1; 358 PersistableBundle persistableBundle = new PersistableBundle(); 359 persistableBundle.putInt(Utils.KEY_TEST_SCENARIO, 360 Utils.EXTRA_HOTWORD_DETECTION_SERVICE_SEND_CUSTOM_INIT_STATUS); 361 persistableBundle.putInt(Utils.KEY_INITIALIZATION_STATUS, customStatus); 362 363 try { 364 // Create SoftwareHotwordDetector and wait result 365 mService.createSoftwareHotwordDetector(/* useExecutor= */ false, /* runOnMainThread= */ 366 false, persistableBundle); 367 368 // Wait the result and verify expected result 369 mService.waitSandboxedDetectionServiceInitializedCalledOrException(); 370 371 // verify callback result 372 assertThat(mService.getSandboxedDetectionServiceInitializedResult()).isEqualTo( 373 customStatus); 374 } finally { 375 HotwordDetector softwareHotwordDetector = mService.getSoftwareHotwordDetector(); 376 if (softwareHotwordDetector != null) { 377 softwareHotwordDetector.destroy(); 378 } 379 } 380 } 381 382 @Test testHotwordDetectionService_validHotwordDetectionComponentName_triggerSuccess()383 public void testHotwordDetectionService_validHotwordDetectionComponentName_triggerSuccess() 384 throws Throwable { 385 // Create alwaysOnHotwordDetector and wait result 386 mService.createAlwaysOnHotwordDetector(); 387 388 mService.waitSandboxedDetectionServiceInitializedCalledOrException(); 389 390 // verify callback result 391 // TODO: not use Deprecated variable 392 assertThat(mService.getSandboxedDetectionServiceInitializedResult()).isEqualTo( 393 HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS); 394 395 // The AlwaysOnHotwordDetector should be created correctly 396 AlwaysOnHotwordDetector alwaysOnHotwordDetector = mService.getAlwaysOnHotwordDetector(); 397 Objects.requireNonNull(alwaysOnHotwordDetector); 398 399 alwaysOnHotwordDetector.destroy(); 400 } 401 402 @Test 403 @CddTest(requirements = {"9.8/H-1-9"}) testVoiceInteractionService_withoutManageHotwordDetectionPermission_triggerFailure()404 public void testVoiceInteractionService_withoutManageHotwordDetectionPermission_triggerFailure() 405 throws Throwable { 406 // Create alwaysOnHotwordDetector and wait result 407 mService.createAlwaysOnHotwordDetectorWithoutManageHotwordDetectionPermission(); 408 409 // Wait the result and verify expected result 410 mService.waitSandboxedDetectionServiceInitializedCalledOrException(); 411 412 // Verify SecurityException throws 413 assertThat(mService.isCreateDetectorSecurityExceptionThrow()).isTrue(); 414 } 415 416 @Test testVoiceInteractionService_holdBindHotwordDetectionPermission_triggerFailure()417 public void testVoiceInteractionService_holdBindHotwordDetectionPermission_triggerFailure() 418 throws Throwable { 419 // Create alwaysOnHotwordDetector and wait result 420 mService.createAlwaysOnHotwordDetectorHoldBindHotwordDetectionPermission(); 421 422 // Wait the result and verify expected result 423 mService.waitSandboxedDetectionServiceInitializedCalledOrException(); 424 425 // Verify SecurityException throws 426 assertThat(mService.isCreateDetectorSecurityExceptionThrow()).isTrue(); 427 } 428 429 @Test 430 @CddTest(requirements = {"9.8/H-1-9"}) testVoiceInteractionService_createSoftwareWithoutPermission_triggerFailure()431 public void testVoiceInteractionService_createSoftwareWithoutPermission_triggerFailure() 432 throws Throwable { 433 // Create SoftwareHotwordDetector and wait result 434 mService.createSoftwareHotwordDetectorWithoutManageHotwordDetectionPermission(); 435 436 // Wait the result and verify expected result 437 mService.waitSandboxedDetectionServiceInitializedCalledOrException(); 438 439 // Verify SecurityException throws 440 assertThat(mService.isCreateDetectorSecurityExceptionThrow()).isTrue(); 441 } 442 443 @Test testVoiceInteractionService_createSoftwareBindHotwordDetectionPermission_Failure()444 public void testVoiceInteractionService_createSoftwareBindHotwordDetectionPermission_Failure() 445 throws Throwable { 446 // Create SoftwareHotwordDetector and wait result 447 mService.createSoftwareHotwordDetectorHoldBindHotwordDetectionPermission(); 448 449 // Wait the result and verify expected result 450 mService.waitSandboxedDetectionServiceInitializedCalledOrException(); 451 452 // Verify SecurityException throws 453 assertThat(mService.isCreateDetectorSecurityExceptionThrow()).isTrue(); 454 } 455 456 @Test testVoiceInteractionService_disallowCreateAlwaysOnHotwordDetectorTwice()457 public void testVoiceInteractionService_disallowCreateAlwaysOnHotwordDetectorTwice() 458 throws Throwable { 459 final boolean enableMultipleHotwordDetectors = Helper.isEnableMultipleDetectors(); 460 assumeTrue("Not support multiple hotword detectors", enableMultipleHotwordDetectors); 461 462 // Create first AlwaysOnHotwordDetector, it's fine. 463 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 464 createAlwaysOnHotwordDetector(/* useOnFailure= */ false); 465 466 // Create second AlwaysOnHotwordDetector, it will get the IllegalStateException due to 467 // the previous AlwaysOnHotwordDetector is not destroy. 468 mService.createAlwaysOnHotwordDetector(); 469 mService.waitSandboxedDetectionServiceInitializedCalledOrException(); 470 471 // Verify IllegalStateException throws 472 assertThat(mService.isCreateDetectorIllegalStateExceptionThrow()).isTrue(); 473 474 alwaysOnHotwordDetector.destroy(); 475 } 476 477 @Test testVoiceInteractionService_disallowCreateSoftwareHotwordDetectorTwice()478 public void testVoiceInteractionService_disallowCreateSoftwareHotwordDetectorTwice() 479 throws Throwable { 480 final boolean enableMultipleHotwordDetectors = Helper.isEnableMultipleDetectors(); 481 assumeTrue("Not support multiple hotword detectors", enableMultipleHotwordDetectors); 482 483 // Create first SoftwareHotwordDetector and wait the HotwordDetectionService ready 484 HotwordDetector softwareHotwordDetector = createSoftwareHotwordDetector(/* useOnFailure= */ 485 false); 486 487 // Create second SoftwareHotwordDetector, it will get the IllegalStateException due to 488 // the previous SoftwareHotwordDetector is not destroy. 489 mService.createSoftwareHotwordDetector(); 490 mService.waitSandboxedDetectionServiceInitializedCalledOrException(); 491 492 // Verify IllegalStateException throws 493 assertThat(mService.isCreateDetectorIllegalStateExceptionThrow()).isTrue(); 494 495 softwareHotwordDetector.destroy(); 496 } 497 498 @Test testHotwordDetectionService_processDied_triggerOnError()499 public void testHotwordDetectionService_processDied_triggerOnError() throws Throwable { 500 // Create first AlwaysOnHotwordDetector 501 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 502 createAlwaysOnHotwordDetector(/* useOnFailure= */ false); 503 504 mService.initOnErrorLatch(); 505 506 // Use AlwaysOnHotwordDetector to test process died of HotwordDetectionService 507 runWithShellPermissionIdentity(() -> { 508 PersistableBundle persistableBundle = new PersistableBundle(); 509 persistableBundle.putInt(Helper.KEY_TEST_SCENARIO, 510 Helper.EXTRA_HOTWORD_DETECTION_SERVICE_ON_UPDATE_STATE_CRASH); 511 alwaysOnHotwordDetector.updateState( 512 persistableBundle, 513 Helper.createFakeSharedMemoryData()); 514 }, MANAGE_HOTWORD_DETECTION); 515 516 mService.waitOnErrorCalled(); 517 518 // ActivityManager will schedule a timer to restart the HotwordDetectionService due to 519 // we crash the service in this test case. It may impact the other test cases when 520 // ActivityManager restarts the HotwordDetectionService again. Add the sleep time to wait 521 // ActivityManager to restart the HotwordDetectionService, so that the service can be 522 // destroyed after finishing this test case. 523 Thread.sleep(5000); 524 525 alwaysOnHotwordDetector.destroy(); 526 } 527 528 @Test testHotwordDetectionService_processDied_triggerOnFailure()529 public void testHotwordDetectionService_processDied_triggerOnFailure() throws Throwable { 530 // Create alwaysOnHotwordDetector with onFailure callback 531 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 532 createAlwaysOnHotwordDetector(/* useOnFailure= */ true); 533 534 try { 535 mService.initOnFailureLatch(); 536 537 // Use AlwaysOnHotwordDetector to test process died of HotwordDetectionService 538 runWithShellPermissionIdentity(() -> { 539 PersistableBundle persistableBundle = new PersistableBundle(); 540 persistableBundle.putInt(Helper.KEY_TEST_SCENARIO, 541 Helper.EXTRA_HOTWORD_DETECTION_SERVICE_ON_UPDATE_STATE_CRASH); 542 alwaysOnHotwordDetector.updateState( 543 persistableBundle, 544 Helper.createFakeSharedMemoryData()); 545 }, MANAGE_HOTWORD_DETECTION); 546 547 mService.waitOnFailureCalled(); 548 549 verifyHotwordDetectionServiceFailure(mService.getHotwordDetectionServiceFailure(), 550 HotwordDetectionServiceFailure.ERROR_CODE_BINDING_DIED); 551 552 // ActivityManager will schedule a timer to restart the HotwordDetectionService due to 553 // we crash the service in this test case. It may impact the other test cases when 554 // ActivityManager restarts the HotwordDetectionService again. Add the sleep time to 555 // wait 556 // ActivityManager to restart the HotwordDetectionService, so that the service can be 557 // destroyed after finishing this test case. 558 Thread.sleep(5000); 559 } finally { 560 // destroy detector 561 alwaysOnHotwordDetector.destroy(); 562 } 563 } 564 565 @Test testHotwordDetectionService_softwareDetector_processDied_triggerOnFailure()566 public void testHotwordDetectionService_softwareDetector_processDied_triggerOnFailure() 567 throws Throwable { 568 // Create SoftwareHotwordDetector 569 HotwordDetector softwareHotwordDetector = 570 createSoftwareHotwordDetector(/* useOnFailure= */ true); 571 try { 572 // Use SoftwareHotwordDetector to test process died of HotwordDetectionService 573 mService.initOnFailureLatch(); 574 runWithShellPermissionIdentity(() -> { 575 PersistableBundle persistableBundle = new PersistableBundle(); 576 persistableBundle.putInt(Helper.KEY_TEST_SCENARIO, 577 Helper.EXTRA_HOTWORD_DETECTION_SERVICE_ON_UPDATE_STATE_CRASH); 578 softwareHotwordDetector.updateState( 579 persistableBundle, 580 Helper.createFakeSharedMemoryData()); 581 }, MANAGE_HOTWORD_DETECTION); 582 // wait OnFailure() called and verify the result 583 mService.waitOnFailureCalled(); 584 verifyHotwordDetectionServiceFailure(mService.getHotwordDetectionServiceFailure(), 585 HotwordDetectionServiceFailure.ERROR_CODE_BINDING_DIED); 586 587 // ActivityManager will schedule a timer to restart the HotwordDetectionService due to 588 // we crash the service in this test case. It may impact the other test cases when 589 // ActivityManager restarts the HotwordDetectionService again. Add the sleep time to 590 // wait ActivityManager to restart the HotwordDetectionService, so that the service 591 // can be destroyed after finishing this test case. 592 Thread.sleep(5000); 593 } finally { 594 // destroy detector 595 softwareHotwordDetector.destroy(); 596 } 597 } 598 599 @Test testHotwordDetectionService_onDetectFromDspTimeout_triggerOnFailure()600 public void testHotwordDetectionService_onDetectFromDspTimeout_triggerOnFailure() 601 throws Throwable { 602 // Create alwaysOnHotwordDetector with onFailure callback 603 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 604 createAlwaysOnHotwordDetector(/* useOnFailure= */ true); 605 606 try { 607 // Update HotwordDetectionService options to delay detection, to cause a timeout 608 runWithShellPermissionIdentity(() -> { 609 PersistableBundle options = Helper.createFakePersistableBundleData(); 610 options.putInt(Utils.KEY_DETECTION_DELAY_MS, 5000); 611 alwaysOnHotwordDetector.updateState(options, 612 Helper.createFakeSharedMemoryData()); 613 }); 614 615 adoptShellPermissionIdentityForHotword(); 616 617 mService.initOnFailureLatch(); 618 619 alwaysOnHotwordDetector.triggerHardwareRecognitionEventForTest( 620 /* status= */ 0, /* soundModelHandle= */ 100, 621 /* halEventReceivedMillis */ 12345, /* captureAvailable= */ true, 622 /* captureSession= */ 101, /* captureDelayMs= */ 1000, 623 /* capturePreambleMs= */ 1001, /* triggerInData= */ true, 624 Helper.createFakeAudioFormat(), new byte[1024], 625 Helper.createFakeKeyphraseRecognitionExtraList()); 626 627 // wait onFailure() called and verify the result 628 mService.waitOnFailureCalled(); 629 630 verifyHotwordDetectionServiceFailure(mService.getHotwordDetectionServiceFailure(), 631 HotwordDetectionServiceFailure.ERROR_CODE_DETECT_TIMEOUT); 632 } finally { 633 // destroy detector 634 alwaysOnHotwordDetector.destroy(); 635 636 // Drop identity adopted. 637 InstrumentationRegistry.getInstrumentation().getUiAutomation() 638 .dropShellPermissionIdentity(); 639 } 640 } 641 642 @Test testHotwordDetectionService_onDetectFromDspSecurityException_onFailure()643 public void testHotwordDetectionService_onDetectFromDspSecurityException_onFailure() 644 throws Throwable { 645 // Create alwaysOnHotwordDetector with onFailure callback 646 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 647 createAlwaysOnHotwordDetector(/* useOnFailure= */ true); 648 649 try { 650 mService.initOnFailureLatch(); 651 652 runWithShellPermissionIdentity(() -> { 653 alwaysOnHotwordDetector.triggerHardwareRecognitionEventForTest( 654 /* status= */ 0, /* soundModelHandle= */ 100, 655 /* halEventReceivedMillis */ 12345, /* captureAvailable= */ true, 656 /* captureSession= */ 101, /* captureDelayMs= */ 1000, 657 /* capturePreambleMs= */ 1001, /* triggerInData= */ true, 658 Helper.createFakeAudioFormat(), new byte[1024], 659 Helper.createFakeKeyphraseRecognitionExtraList()); 660 }); 661 662 mService.waitOnFailureCalled(); 663 664 verifyHotwordDetectionServiceFailure(mService.getHotwordDetectionServiceFailure(), 665 HotwordDetectionServiceFailure.ERROR_CODE_ON_DETECTED_SECURITY_EXCEPTION); 666 } finally { 667 // destroy detector 668 alwaysOnHotwordDetector.destroy(); 669 } 670 } 671 672 @Test testHotwordDetectionService_onDetectFromExternalSourceSecurityException_onFailure()673 public void testHotwordDetectionService_onDetectFromExternalSourceSecurityException_onFailure() 674 throws Throwable { 675 // Create alwaysOnHotwordDetector with onFailure callback 676 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 677 createAlwaysOnHotwordDetector(/* useOnFailure= */ true); 678 679 try { 680 mService.initOnFailureLatch(); 681 682 runWithShellPermissionIdentity(() -> { 683 alwaysOnHotwordDetector.startRecognition(Helper.createFakeAudioStream(), 684 Helper.createFakeAudioFormat(), Helper.createFakePersistableBundleData()); 685 }); 686 687 mService.waitOnFailureCalled(); 688 689 verifyHotwordDetectionServiceFailure(mService.getHotwordDetectionServiceFailure(), 690 HotwordDetectionServiceFailure.ERROR_CODE_ON_DETECTED_SECURITY_EXCEPTION); 691 } finally { 692 // destroy detector 693 alwaysOnHotwordDetector.destroy(); 694 } 695 } 696 697 @Test testHotwordDetectionService_software_externalSourceSecurityException_onFailure()698 public void testHotwordDetectionService_software_externalSourceSecurityException_onFailure() 699 throws Throwable { 700 // Create SoftwareHotwordDetector with onFailure callback 701 HotwordDetector softwareHotwordDetector = createSoftwareHotwordDetector(/* useOnFailure= */ 702 true); 703 704 try { 705 mService.initOnFailureLatch(); 706 707 runWithShellPermissionIdentity(() -> { 708 softwareHotwordDetector.startRecognition(Helper.createFakeAudioStream(), 709 Helper.createFakeAudioFormat(), Helper.createFakePersistableBundleData()); 710 }); 711 712 mService.waitOnFailureCalled(); 713 714 verifyHotwordDetectionServiceFailure(mService.getHotwordDetectionServiceFailure(), 715 HotwordDetectionServiceFailure.ERROR_CODE_ON_DETECTED_SECURITY_EXCEPTION); 716 } finally { 717 // Destroy detector 718 softwareHotwordDetector.destroy(); 719 } 720 } 721 722 @Test testHotwordDetectionService_onDetectFromMicSecurityException_onFailure()723 public void testHotwordDetectionService_onDetectFromMicSecurityException_onFailure() 724 throws Throwable { 725 // Create SoftwareHotwordDetector with onFailure callback 726 HotwordDetector softwareHotwordDetector = createSoftwareHotwordDetector(/* useOnFailure= */ 727 true); 728 729 try { 730 mService.initOnFailureLatch(); 731 732 runWithShellPermissionIdentity(() -> { 733 softwareHotwordDetector.startRecognition(); 734 }); 735 736 // wait onFailure() called and verify the result 737 mService.waitOnFailureCalled(); 738 739 verifyHotwordDetectionServiceFailure(mService.getHotwordDetectionServiceFailure(), 740 HotwordDetectionServiceFailure.ERROR_CODE_ON_DETECTED_SECURITY_EXCEPTION); 741 } finally { 742 // destroy detector 743 softwareHotwordDetector.destroy(); 744 } 745 } 746 747 @Test 748 @Ignore("b/272527340") testHotwordDetectionService_onDetectFromExternalSourceAudioBroken_onFailure()749 public void testHotwordDetectionService_onDetectFromExternalSourceAudioBroken_onFailure() 750 throws Throwable { 751 // Create alwaysOnHotwordDetector with onFailure callback 752 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 753 createAlwaysOnHotwordDetector(/* useOnFailure= */ true); 754 755 try { 756 adoptShellPermissionIdentityForHotword(); 757 758 // Create the ParcelFileDescriptor to read/write audio stream 759 final ParcelFileDescriptor[] parcelFileDescriptors = ParcelFileDescriptor.createPipe(); 760 761 // After the client calls the startRecognition method, the system side will start to 762 // read the audio stream. When no data is read, the system side will normally end the 763 // process. If the client closes the audio stream when the system is still reading the 764 // audio stream, the system will get the IOException and use the onFailure callback to 765 // inform the client. 766 // In order to simulate the IOException case, it would be better to write 5 * 10 * 1024 767 // bytes data first before calling startRecognition to avoid the timing issue that no 768 // data is read from the system and make sure to close the audio stream during system 769 // is still reading the audio stream. 770 final CountDownLatch writeAudioStreamLatch = new CountDownLatch(5); 771 772 Executors.newCachedThreadPool().execute(() -> { 773 try (OutputStream fos = new ParcelFileDescriptor.AutoCloseOutputStream( 774 parcelFileDescriptors[1])) { 775 byte[] largeData = new byte[10 * 1024]; 776 int count = 1000; 777 while (count-- > 0) { 778 Random random = new Random(); 779 random.nextBytes(largeData); 780 fos.write(largeData, 0, 10 * 1024); 781 writeAudioStreamLatch.countDown(); 782 } 783 } catch (IOException e) { 784 Log.w(TAG, "Failed to pipe audio data : ", e); 785 } 786 }); 787 788 writeAudioStreamLatch.await(WAIT_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS); 789 790 mService.initOnFailureLatch(); 791 792 alwaysOnHotwordDetector.startRecognition(parcelFileDescriptors[0], 793 Helper.createFakeAudioFormat(), Helper.createFakePersistableBundleData()); 794 795 // Close the parcelFileDescriptors to cause the IOException when reading audio 796 // stream in the system side. 797 parcelFileDescriptors[0].close(); 798 parcelFileDescriptors[1].close(); 799 800 // wait onFailure() called and verify the result 801 mService.waitOnFailureCalled(); 802 803 verifyHotwordDetectionServiceFailure(mService.getHotwordDetectionServiceFailure(), 804 HotwordDetectionServiceFailure.ERROR_CODE_COPY_AUDIO_DATA_FAILURE); 805 } finally { 806 // destroy detector 807 alwaysOnHotwordDetector.destroy(); 808 809 // Drop identity adopted. 810 InstrumentationRegistry.getInstrumentation().getUiAutomation() 811 .dropShellPermissionIdentity(); 812 } 813 } 814 815 @Test 816 @RequiresDevice testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess()817 public void testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess() 818 throws Throwable { 819 // Create SoftwareHotwordDetector 820 HotwordDetector softwareHotwordDetector = createSoftwareHotwordDetector(/* useOnFailure= */ 821 false); 822 // destroy software hotword detector 823 softwareHotwordDetector.destroy(); 824 825 // Create AlwaysOnHotwordDetector 826 startWatchingNoted(); 827 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 828 createAlwaysOnHotwordDetectorWithSoundTriggerInjection(); 829 try { 830 runWithShellPermissionIdentity(() -> { 831 // Update state with test scenario HotwordDetectionService can read audio and 832 // check the data is not zero 833 PersistableBundle persistableBundle = new PersistableBundle(); 834 persistableBundle.putInt(Helper.KEY_TEST_SCENARIO, 835 EXTRA_HOTWORD_DETECTION_SERVICE_CAN_READ_AUDIO_DATA_IS_NOT_ZERO); 836 alwaysOnHotwordDetector.updateState( 837 persistableBundle, 838 Helper.createFakeSharedMemoryData()); 839 }, MANAGE_HOTWORD_DETECTION); 840 841 adoptShellPermissionIdentityForHotword(); 842 843 verifyOnDetectFromDspWithSoundTriggerInjectionSuccess(alwaysOnHotwordDetector); 844 845 // Verify RECORD_AUDIO noted 846 verifyRecordAudioNote(/* shouldNote= */ true); 847 } finally { 848 // destroy detector 849 alwaysOnHotwordDetector.destroy(); 850 // Drop identity adopted. 851 InstrumentationRegistry.getInstrumentation().getUiAutomation() 852 .dropShellPermissionIdentity(); 853 stopWatchingNoted(); 854 } 855 } 856 857 @Test 858 @CddTest(requirements = {"9.8/H-1-15"}) testHotwordDetectionServiceDspWithAudioEgress()859 public void testHotwordDetectionServiceDspWithAudioEgress() throws Throwable { 860 // Create AlwaysOnHotwordDetector 861 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 862 createAlwaysOnHotwordDetectorWithSoundTriggerInjection(); 863 864 // Update HotwordDetectionService options to enable Audio egress 865 runWithShellPermissionIdentity(() -> { 866 PersistableBundle persistableBundle = new PersistableBundle(); 867 persistableBundle.putInt(Helper.KEY_TEST_SCENARIO, 868 Utils.EXTRA_HOTWORD_DETECTION_SERVICE_ENABLE_AUDIO_EGRESS); 869 alwaysOnHotwordDetector.updateState( 870 persistableBundle, 871 Helper.createFakeSharedMemoryData()); 872 }, MANAGE_HOTWORD_DETECTION); 873 874 try { 875 adoptShellPermissionIdentityForHotword(); 876 877 mService.initDetectRejectLatch(); 878 879 // start recognition and trigger recognition event via recognition session 880 alwaysOnHotwordDetector.startRecognition(0, new byte[]{1, 2, 3, 4, 5}); 881 RecognitionSession recognitionSession = waitForFutureDoneAndAssertSuccessful( 882 mInstrumentationObserver.getOnRecognitionStartedFuture()); 883 assertThat(recognitionSession).isNotNull(); 884 885 recognitionSession.triggerRecognitionEvent(new byte[1024], 886 createKeyphraseRecognitionExtraList()); 887 888 // wait onDetected() called and verify the result 889 mService.waitOnDetectOrRejectCalled(); 890 AlwaysOnHotwordDetector.EventPayload detectResult = 891 mService.getHotwordServiceOnDetectedResult(); 892 893 Helper.verifyAudioEgressDetectedResult(detectResult, AUDIO_EGRESS_DETECTED_RESULT); 894 895 } finally { 896 // destroy detector 897 alwaysOnHotwordDetector.destroy(); 898 // Drop identity adopted. 899 InstrumentationRegistry.getInstrumentation().getUiAutomation() 900 .dropShellPermissionIdentity(); 901 disableTestModel(); 902 } 903 } 904 905 @Test 906 @CddTest(requirements = {"9.8/H-1-15"}) testHotwordDetectionService_softwareDetectorWithAudioEgress()907 public void testHotwordDetectionService_softwareDetectorWithAudioEgress() throws Throwable { 908 // Create SoftwareHotwordDetector 909 HotwordDetector softwareHotwordDetector = createSoftwareHotwordDetector(/* useOnFailure= */ 910 false); 911 912 // Update HotwordDetectionService options to enable Audio egress 913 runWithShellPermissionIdentity(() -> { 914 PersistableBundle persistableBundle = new PersistableBundle(); 915 persistableBundle.putInt(Helper.KEY_TEST_SCENARIO, 916 Utils.EXTRA_HOTWORD_DETECTION_SERVICE_ENABLE_AUDIO_EGRESS); 917 softwareHotwordDetector.updateState( 918 persistableBundle, 919 Helper.createFakeSharedMemoryData()); 920 }, MANAGE_HOTWORD_DETECTION); 921 922 try { 923 adoptShellPermissionIdentityForHotword(); 924 925 mService.initDetectRejectLatch(); 926 softwareHotwordDetector.startRecognition(); 927 928 // wait onDetected() called and verify the result 929 mService.waitOnDetectOrRejectCalled(); 930 AlwaysOnHotwordDetector.EventPayload detectResult = 931 mService.getHotwordServiceOnDetectedResult(); 932 933 Helper.verifyDetectedResult(detectResult, AUDIO_EGRESS_DETECTED_RESULT); 934 } finally { 935 softwareHotwordDetector.destroy(); 936 // Drop identity adopted. 937 InstrumentationRegistry.getInstrumentation().getUiAutomation() 938 .dropShellPermissionIdentity(); 939 } 940 } 941 942 @Test 943 @CddTest(requirements = {"9.8/H-1-15"}) testHotwordDetectionService_onDetectFromExternalSourceWithAudioEgress()944 public void testHotwordDetectionService_onDetectFromExternalSourceWithAudioEgress() 945 throws Throwable { 946 // Create AlwaysOnHotwordDetector 947 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 948 createAlwaysOnHotwordDetector(/* useOnFailure= */ false); 949 950 // Update HotwordDetectionService options to enable Audio egress 951 runWithShellPermissionIdentity(() -> { 952 PersistableBundle persistableBundle = new PersistableBundle(); 953 persistableBundle.putInt(Helper.KEY_TEST_SCENARIO, 954 Utils.EXTRA_HOTWORD_DETECTION_SERVICE_ENABLE_AUDIO_EGRESS); 955 alwaysOnHotwordDetector.updateState( 956 persistableBundle, 957 Helper.createFakeSharedMemoryData()); 958 }, MANAGE_HOTWORD_DETECTION); 959 960 try { 961 adoptShellPermissionIdentityForHotword(); 962 963 ParcelFileDescriptor audioStream = Helper.createFakeAudioStream(); 964 mService.initDetectRejectLatch(); 965 alwaysOnHotwordDetector.startRecognition(audioStream, 966 Helper.createFakeAudioFormat(), 967 Helper.createFakePersistableBundleData()); 968 969 // wait onDetected() called and verify the result 970 mService.waitOnDetectOrRejectCalled(); 971 AlwaysOnHotwordDetector.EventPayload detectResult = 972 mService.getHotwordServiceOnDetectedResult(); 973 974 Helper.verifyDetectedResult(detectResult, AUDIO_EGRESS_DETECTED_RESULT); 975 } finally { 976 // destroy detector 977 alwaysOnHotwordDetector.destroy(); 978 // Drop identity adopted. 979 InstrumentationRegistry.getInstrumentation().getUiAutomation() 980 .dropShellPermissionIdentity(); 981 } 982 } 983 984 @Test 985 @RequiresFlagsEnabled(android.app.wearable.Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API) testHotwordDetectionService_onDetectFromWearableWithAudioEgress()986 public void testHotwordDetectionService_onDetectFromWearableWithAudioEgress() throws Throwable { 987 assumeFalse(isWatch()); // WearableSensingManagerService is not supported on WearOS 988 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 989 createAlwaysOnHotwordDetectorWithSoundTriggerInjection(); 990 try { 991 setupForWearableTests(alwaysOnHotwordDetector); 992 CountDownLatch statusLatch = new CountDownLatch(1); 993 sWearableSensingManager.startHotwordRecognition( 994 VIS_COMPONENT_NAME, 995 mExecutor, 996 (status) -> { 997 statusLatch.countDown(); 998 }); 999 assertThat(statusLatch.await(3, SECONDS)).isTrue(); 1000 mService.initDetectRejectLatch(); 1001 1002 sendAudioStreamFromWearable(); 1003 1004 // wait for onDetected() to be called and verify the result 1005 mService.waitOnDetectOrRejectCalled(); 1006 AlwaysOnHotwordDetector.EventPayload detectResult = 1007 mService.getHotwordServiceOnDetectedResult(); 1008 1009 Helper.verifyDetectedResult(detectResult, AUDIO_EGRESS_DETECTED_RESULT); 1010 // Wait for the async call into WearableSensingService. 1011 SystemClock.sleep(2000); 1012 verifyWearableSensingServiceHotwordValidatedCalled(); 1013 } finally { 1014 cleanupForWearableTests(alwaysOnHotwordDetector); 1015 } 1016 } 1017 1018 @Test 1019 @RequiresFlagsEnabled(android.app.wearable.Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API) testHotwordDetectionService_onDetectFromWearable_doesNotNoteRecordAudioOp()1020 public void testHotwordDetectionService_onDetectFromWearable_doesNotNoteRecordAudioOp() 1021 throws Throwable { 1022 assumeFalse(isWatch()); 1023 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 1024 createAlwaysOnHotwordDetectorWithSoundTriggerInjection(); 1025 try { 1026 setupForWearableTests(alwaysOnHotwordDetector); 1027 CountDownLatch statusLatch = new CountDownLatch(1); 1028 sWearableSensingManager.startHotwordRecognition( 1029 VIS_COMPONENT_NAME, 1030 mExecutor, 1031 (status) -> { 1032 statusLatch.countDown(); 1033 }); 1034 assertThat(statusLatch.await(3, SECONDS)).isTrue(); 1035 mService.initDetectRejectLatch(); 1036 1037 sendAudioStreamFromWearable(); 1038 1039 // wait for onDetected() to be called 1040 mService.waitOnDetectOrRejectCalled(); 1041 AlwaysOnHotwordDetector.EventPayload detectResult = 1042 mService.getHotwordServiceOnDetectedResult(); 1043 // make sure this is onDetected and not onRejected 1044 assertThat(detectResult).isNotNull(); 1045 1046 verifyRecordAudioNote(/* shouldNote= */ false); 1047 } finally { 1048 cleanupForWearableTests(alwaysOnHotwordDetector); 1049 } 1050 } 1051 1052 @Test 1053 @RequiresFlagsEnabled(android.app.wearable.Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API) testHotwordDetectionService_onRejectWearableHotword_notifiesWearable()1054 public void testHotwordDetectionService_onRejectWearableHotword_notifiesWearable() 1055 throws Throwable { 1056 assumeFalse(isWatch()); // WearableSensingManagerService is not supported on WearOS 1057 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 1058 createAlwaysOnHotwordDetectorWithSoundTriggerInjection(); 1059 try { 1060 setupForWearableTests(alwaysOnHotwordDetector); 1061 CountDownLatch statusLatch = new CountDownLatch(1); 1062 sWearableSensingManager.startHotwordRecognition( 1063 VIS_COMPONENT_NAME, 1064 mExecutor, 1065 (status) -> { 1066 statusLatch.countDown(); 1067 }); 1068 assertThat(statusLatch.await(3, SECONDS)).isTrue(); 1069 1070 sendNonHotwordAudioStreamFromWearable(); 1071 1072 // Wait for the async call into WearableSensingService. 1073 SystemClock.sleep(2000); 1074 verifyWearableSensingServiceAudioStopCalled(); 1075 } finally { 1076 cleanupForWearableTests(alwaysOnHotwordDetector); 1077 } 1078 } 1079 1080 @Test 1081 @RequiresFlagsEnabled(android.app.wearable.Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API) testHotwordDetectionService_receivesOptionsFromWearable()1082 public void testHotwordDetectionService_receivesOptionsFromWearable() throws Throwable { 1083 assumeFalse(isWatch()); // WearableSensingManagerService is not supported on WearOS 1084 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 1085 createAlwaysOnHotwordDetectorWithSoundTriggerInjection(); 1086 try { 1087 setupForWearableTests(alwaysOnHotwordDetector); 1088 CountDownLatch statusLatch = new CountDownLatch(1); 1089 sWearableSensingManager.startHotwordRecognition( 1090 VIS_COMPONENT_NAME, 1091 mExecutor, 1092 (status) -> { 1093 statusLatch.countDown(); 1094 }); 1095 assertThat(statusLatch.await(3, SECONDS)).isTrue(); 1096 mService.initDetectRejectLatch(); 1097 1098 // The HotwordDetectionService should reject the non-hotword audio stream, but if it 1099 // receives the expected options, it will call onDetect instead. This is an indirect 1100 // way to verify that options are received because it is difficult to send a message 1101 // from HotwordDetectionService back to this test. 1102 sendNonHotwordAudioStreamWithAcceptDetectionOptionsFromWearable(); 1103 1104 // verify that onDetect is called 1105 mService.waitOnDetectOrRejectCalled(); 1106 AlwaysOnHotwordDetector.EventPayload detectResult = 1107 mService.getHotwordServiceOnDetectedResult(); 1108 assertThat(detectResult).isNotNull(); 1109 } finally { 1110 cleanupForWearableTests(alwaysOnHotwordDetector); 1111 } 1112 } 1113 1114 @Test 1115 @RequiresFlagsEnabled(android.app.wearable.Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API) testHotwordDetectionService_wearableHotwordWithWrongVisComponent_notifiesWearable()1116 public void testHotwordDetectionService_wearableHotwordWithWrongVisComponent_notifiesWearable() 1117 throws Throwable { 1118 assumeFalse(isWatch()); // WearableSensingManagerService is not supported on WearOS 1119 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 1120 createAlwaysOnHotwordDetectorWithSoundTriggerInjection(); 1121 try { 1122 setupForWearableTests(alwaysOnHotwordDetector); 1123 CountDownLatch statusLatch = new CountDownLatch(1); 1124 sWearableSensingManager.startHotwordRecognition( 1125 new ComponentName("my.package", "my.package.MyClass"), 1126 mExecutor, 1127 (status) -> { 1128 statusLatch.countDown(); 1129 }); 1130 assertThat(statusLatch.await(3, SECONDS)).isTrue(); 1131 mService.initDetectRejectLatch(); 1132 1133 sendAudioStreamFromWearable(); 1134 1135 // Wait for the async call into WearableSensingService. 1136 SystemClock.sleep(2000); 1137 verifyWearableSensingServiceAudioStopCalled(); 1138 // The audio stream is not sent to HDS, so neither onDetect nor onReject should be 1139 // called 1140 assertThrows(AssertionError.class, () -> mService.waitOnDetectOrRejectCalled()); 1141 } finally { 1142 cleanupForWearableTests(alwaysOnHotwordDetector); 1143 } 1144 } 1145 1146 @Test 1147 @RequiresFlagsEnabled(android.app.wearable.Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API) testHotwordDetectionService_closePipeInWearableHotwordResult_notifiesWearable()1148 public void testHotwordDetectionService_closePipeInWearableHotwordResult_notifiesWearable() 1149 throws Throwable { 1150 assumeFalse(isWatch()); // WearableSensingManagerService is not supported on WearOS 1151 // Create AlwaysOnHotwordDetector 1152 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 1153 createAlwaysOnHotwordDetectorWithSoundTriggerInjection(); 1154 try { 1155 // Do not close the audio stream in HotwordDetectionService immediately after 1156 // read. Wait until this test requests it. 1157 setupForWearableTests(alwaysOnHotwordDetector, /* closeStreamAfterRead= */ false); 1158 CountDownLatch statusLatch = new CountDownLatch(1); 1159 sWearableSensingManager.startHotwordRecognition( 1160 VIS_COMPONENT_NAME, 1161 mExecutor, 1162 (status) -> { 1163 statusLatch.countDown(); 1164 }); 1165 assertThat(statusLatch.await(3, SECONDS)).isTrue(); 1166 mService.initDetectRejectLatch(); 1167 1168 sendAudioStreamFromWearable(); 1169 // wait for onDetected() called and close the PFD in the result 1170 mService.waitOnDetectOrRejectCalled(); 1171 AlwaysOnHotwordDetector.EventPayload detectResult = 1172 mService.getHotwordServiceOnDetectedResult(); 1173 detectResult 1174 .getHotwordDetectedResult() 1175 .getAudioStreams() 1176 .get(0) 1177 .getAudioStreamParcelFileDescriptor() 1178 .close(); 1179 /* 1180 * Check if the output PFD sent by HotwordDetectionService is broken (it should). 1181 * If yes, HotwordDetectionService will close the audio stream it received. 1182 * This is indirect because there is no simple way for HotwordDetectionService to 1183 * propagate a test failure signal back to the test driver. 1184 */ 1185 runWithShellPermissionIdentity( 1186 () -> { 1187 PersistableBundle persistableBundle = new PersistableBundle(); 1188 persistableBundle.putBoolean( 1189 Utils.KEY_CLOSE_INPUT_AUDIO_STREAM_IF_OUTPUT_PIPE_BROKEN, true); 1190 alwaysOnHotwordDetector.updateState( 1191 persistableBundle, Helper.createFakeSharedMemoryData()); 1192 }, 1193 MANAGE_HOTWORD_DETECTION); 1194 adoptShellPermissionIdentityForHotwordAndWearableSensing(); 1195 // Wait for all the PFDs involved with the call above to be cleaned up 1196 SystemClock.sleep(4000); 1197 1198 /* 1199 * Write more data from wearable onto the same stream. This should trigger a pipe 1200 * broken exception in the system_server thread that copies from the output of 1201 * WearableSensingService to the input of HotwordDetectionService, which triggers 1202 * the callback to stop the wearable stream. 1203 */ 1204 sendMoreAudioDataFromWearable(); 1205 // Wait for the async call into WearableSensingService. 1206 SystemClock.sleep(2000); 1207 verifyWearableSensingServiceAudioStopCalled(); 1208 } finally { 1209 cleanupForWearableTests(alwaysOnHotwordDetector); 1210 } 1211 } 1212 1213 @Test testHotwordDetectionServiceDspWithAudioEgressWrongCopyBufferSize()1214 public void testHotwordDetectionServiceDspWithAudioEgressWrongCopyBufferSize() 1215 throws Throwable { 1216 // Create AlwaysOnHotwordDetector 1217 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 1218 createAlwaysOnHotwordDetectorWithSoundTriggerInjection(); 1219 1220 // Update HotwordDetectionService options to enable Audio egress 1221 runWithShellPermissionIdentity(() -> { 1222 PersistableBundle persistableBundle = new PersistableBundle(); 1223 persistableBundle.putInt(Helper.KEY_TEST_SCENARIO, 1224 Utils.EXTRA_HOTWORD_DETECTION_SERVICE_ENABLE_AUDIO_EGRESS); 1225 persistableBundle.putBoolean(Utils.KEY_AUDIO_EGRESS_USE_ILLEGAL_COPY_BUFFER_SIZE, true); 1226 alwaysOnHotwordDetector.updateState( 1227 persistableBundle, 1228 Helper.createFakeSharedMemoryData()); 1229 }, MANAGE_HOTWORD_DETECTION); 1230 1231 try { 1232 adoptShellPermissionIdentityForHotword(); 1233 1234 mService.initDetectRejectLatch(); 1235 1236 // start recognition and trigger recognition event via recognition session 1237 alwaysOnHotwordDetector.startRecognition(0, new byte[]{1, 2, 3, 4, 5}); 1238 RecognitionSession recognitionSession = waitForFutureDoneAndAssertSuccessful( 1239 mInstrumentationObserver.getOnRecognitionStartedFuture()); 1240 assertThat(recognitionSession).isNotNull(); 1241 1242 recognitionSession.triggerRecognitionEvent(new byte[1024], 1243 createKeyphraseRecognitionExtraList()); 1244 1245 // wait onDetected() called and verify the result 1246 mService.waitOnDetectOrRejectCalled(); 1247 AlwaysOnHotwordDetector.EventPayload detectResult = 1248 mService.getHotwordServiceOnDetectedResult(); 1249 1250 Helper.verifyAudioEgressDetectedResult(detectResult, 1251 AUDIO_EGRESS_DETECTED_RESULT_WRONG_COPY_BUFFER_SIZE); 1252 1253 } finally { 1254 // destroy detector 1255 alwaysOnHotwordDetector.destroy(); 1256 // Drop identity adopted. 1257 InstrumentationRegistry.getInstrumentation().getUiAutomation() 1258 .dropShellPermissionIdentity(); 1259 disableTestModel(); 1260 } 1261 } 1262 1263 @Test testHotwordDetectionService_softwareDetectorWithAudioEgressWrongCopyBufferSize()1264 public void testHotwordDetectionService_softwareDetectorWithAudioEgressWrongCopyBufferSize() 1265 throws Throwable { 1266 // Create SoftwareHotwordDetector 1267 HotwordDetector softwareHotwordDetector = createSoftwareHotwordDetector(/* useOnFailure= */ 1268 false); 1269 1270 // Update HotwordDetectionService options to enable Audio egress 1271 runWithShellPermissionIdentity(() -> { 1272 PersistableBundle persistableBundle = new PersistableBundle(); 1273 persistableBundle.putInt(Helper.KEY_TEST_SCENARIO, 1274 Utils.EXTRA_HOTWORD_DETECTION_SERVICE_ENABLE_AUDIO_EGRESS); 1275 persistableBundle.putBoolean(Utils.KEY_AUDIO_EGRESS_USE_ILLEGAL_COPY_BUFFER_SIZE, true); 1276 softwareHotwordDetector.updateState( 1277 persistableBundle, 1278 Helper.createFakeSharedMemoryData()); 1279 }, MANAGE_HOTWORD_DETECTION); 1280 1281 try { 1282 adoptShellPermissionIdentityForHotword(); 1283 1284 mService.initDetectRejectLatch(); 1285 softwareHotwordDetector.startRecognition(); 1286 1287 // wait onDetected() called and verify the result 1288 mService.waitOnDetectOrRejectCalled(); 1289 AlwaysOnHotwordDetector.EventPayload detectResult = 1290 mService.getHotwordServiceOnDetectedResult(); 1291 1292 Helper.verifyAudioEgressDetectedResult(detectResult, 1293 AUDIO_EGRESS_DETECTED_RESULT_WRONG_COPY_BUFFER_SIZE); 1294 } finally { 1295 softwareHotwordDetector.destroy(); 1296 // Drop identity adopted. 1297 InstrumentationRegistry.getInstrumentation().getUiAutomation() 1298 .dropShellPermissionIdentity(); 1299 } 1300 } 1301 1302 @Test testHotwordDetectionService_externalSourceWithAudioEgressWrongCopyBufferSize()1303 public void testHotwordDetectionService_externalSourceWithAudioEgressWrongCopyBufferSize() 1304 throws Throwable { 1305 // Create AlwaysOnHotwordDetector 1306 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 1307 createAlwaysOnHotwordDetector(/* useOnFailure= */ false); 1308 1309 // Update HotwordDetectionService options to enable Audio egress 1310 runWithShellPermissionIdentity(() -> { 1311 PersistableBundle persistableBundle = new PersistableBundle(); 1312 persistableBundle.putInt(Helper.KEY_TEST_SCENARIO, 1313 Utils.EXTRA_HOTWORD_DETECTION_SERVICE_ENABLE_AUDIO_EGRESS); 1314 persistableBundle.putBoolean(Utils.KEY_AUDIO_EGRESS_USE_ILLEGAL_COPY_BUFFER_SIZE, true); 1315 alwaysOnHotwordDetector.updateState( 1316 persistableBundle, 1317 Helper.createFakeSharedMemoryData()); 1318 }, MANAGE_HOTWORD_DETECTION); 1319 1320 try { 1321 adoptShellPermissionIdentityForHotword(); 1322 1323 ParcelFileDescriptor audioStream = Helper.createFakeAudioStream(); 1324 mService.initDetectRejectLatch(); 1325 alwaysOnHotwordDetector.startRecognition(audioStream, 1326 Helper.createFakeAudioFormat(), 1327 Helper.createFakePersistableBundleData()); 1328 1329 // wait onDetected() called and verify the result 1330 mService.waitOnDetectOrRejectCalled(); 1331 AlwaysOnHotwordDetector.EventPayload detectResult = 1332 mService.getHotwordServiceOnDetectedResult(); 1333 1334 Helper.verifyAudioEgressDetectedResult(detectResult, 1335 AUDIO_EGRESS_DETECTED_RESULT_WRONG_COPY_BUFFER_SIZE); 1336 } finally { 1337 // destroy detector 1338 alwaysOnHotwordDetector.destroy(); 1339 // Drop identity adopted. 1340 InstrumentationRegistry.getInstrumentation().getUiAutomation() 1341 .dropShellPermissionIdentity(); 1342 } 1343 } 1344 1345 @Test 1346 @CddTest(requirement = "9.8/H-1-2,H-1-8,H-1-14") testHotwordDetectionService_onDetectFromDsp_success()1347 public void testHotwordDetectionService_onDetectFromDsp_success() throws Throwable { 1348 startWatchingNoted(); 1349 // Create AlwaysOnHotwordDetector 1350 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 1351 createAlwaysOnHotwordDetectorWithSoundTriggerInjection(); 1352 try { 1353 adoptShellPermissionIdentityForHotword(); 1354 1355 verifyOnDetectFromDspWithSoundTriggerInjectionSuccess(alwaysOnHotwordDetector); 1356 1357 // Verify RECORD_AUDIO noted 1358 verifyRecordAudioNote(/* shouldNote= */ true); 1359 } finally { 1360 // destroy detector 1361 alwaysOnHotwordDetector.destroy(); 1362 // Drop identity adopted. 1363 InstrumentationRegistry.getInstrumentation().getUiAutomation() 1364 .dropShellPermissionIdentity(); 1365 stopWatchingNoted(); 1366 } 1367 } 1368 1369 @Test testHotwordDetectionService_onDetectFromDsp_rejection()1370 public void testHotwordDetectionService_onDetectFromDsp_rejection() throws Throwable { 1371 startWatchingNoted(); 1372 // Create AlwaysOnHotwordDetector 1373 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 1374 createAlwaysOnHotwordDetector(/* useOnFailure= */ false); 1375 try { 1376 mService.initDetectRejectLatch(); 1377 runWithShellPermissionIdentity(() -> { 1378 // pass null data parameter 1379 alwaysOnHotwordDetector.triggerHardwareRecognitionEventForTest( 1380 /* status= */ 0, /* soundModelHandle= */ 100, 1381 /* halEventReceivedMillis */ 12345, /* captureAvailable= */ true, 1382 /* captureSession= */ 101, /* captureDelayMs= */ 1000, 1383 /* capturePreambleMs= */ 1001, /* triggerInData= */ true, 1384 Helper.createFakeAudioFormat(), null, 1385 Helper.createFakeKeyphraseRecognitionExtraList()); 1386 }); 1387 // wait onRejected() called and verify the result 1388 mService.waitOnDetectOrRejectCalled(); 1389 HotwordRejectedResult rejectedResult = 1390 mService.getHotwordServiceOnRejectedResult(); 1391 1392 assertThat(rejectedResult).isEqualTo(Helper.REJECTED_RESULT); 1393 1394 // Verify RECORD_AUDIO does not note 1395 verifyRecordAudioNote(/* shouldNote= */ false); 1396 } finally { 1397 // destroy detector 1398 alwaysOnHotwordDetector.destroy(); 1399 stopWatchingNoted(); 1400 } 1401 } 1402 1403 @Test 1404 @CddTest(requirement = "9.8/H-1-3") testHotwordDetectionService_onDetectFromDsp_timeout()1405 public void testHotwordDetectionService_onDetectFromDsp_timeout() throws Throwable { 1406 startWatchingNoted(); 1407 // Create AlwaysOnHotwordDetector 1408 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 1409 createAlwaysOnHotwordDetector(/* useOnFailure= */ false); 1410 // Update HotwordDetectionService options to delay detection, to cause a timeout 1411 runWithShellPermissionIdentity(() -> { 1412 PersistableBundle options = Helper.createFakePersistableBundleData(); 1413 options.putInt(Utils.KEY_DETECTION_DELAY_MS, 5000); 1414 alwaysOnHotwordDetector.updateState(options, 1415 Helper.createFakeSharedMemoryData()); 1416 }); 1417 try { 1418 adoptShellPermissionIdentityForHotword(); 1419 1420 mService.initOnErrorLatch(); 1421 alwaysOnHotwordDetector.triggerHardwareRecognitionEventForTest( 1422 /* status= */ 0, /* soundModelHandle= */ 100, 1423 /* halEventReceivedMillis */ 12345, /* captureAvailable= */ true, 1424 /* captureSession= */ 101, /* captureDelayMs= */ 1000, 1425 /* capturePreambleMs= */ 1001, /* triggerInData= */ true, 1426 Helper.createFakeAudioFormat(), new byte[1024], 1427 Helper.createFakeKeyphraseRecognitionExtraList()); 1428 1429 // wait onError() called and verify the result 1430 mService.waitOnErrorCalled(); 1431 1432 // Verify RECORD_AUDIO does not note 1433 verifyRecordAudioNote(/* shouldNote= */ false); 1434 } finally { 1435 // destroy detector 1436 alwaysOnHotwordDetector.destroy(); 1437 // Drop identity adopted. 1438 InstrumentationRegistry.getInstrumentation().getUiAutomation() 1439 .dropShellPermissionIdentity(); 1440 stopWatchingNoted(); 1441 } 1442 } 1443 1444 @Test testHotwordDetectionService_destroyDspDetector_activeDetectorRemoved()1445 public void testHotwordDetectionService_destroyDspDetector_activeDetectorRemoved() 1446 throws Throwable { 1447 // Create AlwaysOnHotwordDetector 1448 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 1449 createAlwaysOnHotwordDetector(/* useOnFailure= */ false); 1450 // destroy detector 1451 alwaysOnHotwordDetector.destroy(); 1452 try { 1453 adoptShellPermissionIdentityForHotword(); 1454 1455 assertThrows(IllegalStateException.class, () -> { 1456 // Can no longer use the detector because it is in an invalid state 1457 alwaysOnHotwordDetector.triggerHardwareRecognitionEventForTest( 1458 /* status= */ 0, /* soundModelHandle= */ 100, 1459 /* halEventReceivedMillis */ 12345, /* captureAvailable= */ true, 1460 /* captureSession= */ 101, /* captureDelayMs= */ 1000, 1461 /* capturePreambleMs= */ 1001, /* triggerInData= */ true, 1462 Helper.createFakeAudioFormat(), new byte[1024], 1463 Helper.createFakeKeyphraseRecognitionExtraList()); 1464 }); 1465 } finally { 1466 // Drop identity adopted. 1467 InstrumentationRegistry.getInstrumentation().getUiAutomation() 1468 .dropShellPermissionIdentity(); 1469 } 1470 } 1471 1472 @Test 1473 @CddTest(requirement = "9.8/H-1-2,H-1-8,H-1-14") testHotwordDetectionService_onDetectFromExternalSource_success()1474 public void testHotwordDetectionService_onDetectFromExternalSource_success() throws Throwable { 1475 startWatchingNoted(); 1476 // Create AlwaysOnHotwordDetector 1477 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 1478 createAlwaysOnHotwordDetector(/* useOnFailure= */ false); 1479 try { 1480 adoptShellPermissionIdentityForHotword(); 1481 1482 ParcelFileDescriptor audioStream = Helper.createFakeAudioStream(); 1483 mService.initDetectRejectLatch(); 1484 alwaysOnHotwordDetector.startRecognition(audioStream, 1485 Helper.createFakeAudioFormat(), 1486 Helper.createFakePersistableBundleData()); 1487 1488 // wait onDetected() called and verify the result 1489 mService.waitOnDetectOrRejectCalled(); 1490 AlwaysOnHotwordDetector.EventPayload detectResult = 1491 mService.getHotwordServiceOnDetectedResult(); 1492 1493 Helper.verifyDetectedResult(detectResult, Helper.DETECTED_RESULT); 1494 1495 // Verify RECORD_AUDIO noted 1496 verifyRecordAudioNote(/* shouldNote= */ true); 1497 } finally { 1498 // destroy detector 1499 alwaysOnHotwordDetector.destroy(); 1500 // Drop identity adopted. 1501 InstrumentationRegistry.getInstrumentation().getUiAutomation() 1502 .dropShellPermissionIdentity(); 1503 stopWatchingNoted(); 1504 } 1505 } 1506 1507 @Test testHotwordDetectionService_dspDetector_onDetectFromExternalSource_rejected()1508 public void testHotwordDetectionService_dspDetector_onDetectFromExternalSource_rejected() 1509 throws Throwable { 1510 // Create AlwaysOnHotwordDetector 1511 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 1512 createAlwaysOnHotwordDetector(/* useOnFailure= */ false); 1513 try { 1514 adoptShellPermissionIdentityForHotword(); 1515 1516 PersistableBundle options = Helper.createFakePersistableBundleData(); 1517 options.putBoolean(Utils.KEY_DETECTION_REJECTED, true); 1518 1519 mService.initDetectRejectLatch(); 1520 alwaysOnHotwordDetector.startRecognition(Helper.createFakeAudioStream(), 1521 Helper.createFakeAudioFormat(), options); 1522 1523 // Wait onRejected() called and verify the result 1524 mService.waitOnDetectOrRejectCalled(); 1525 HotwordRejectedResult rejectedResult = 1526 mService.getHotwordServiceOnRejectedResult(); 1527 1528 assertThat(rejectedResult).isEqualTo(Helper.REJECTED_RESULT); 1529 } finally { 1530 // Destroy detector 1531 alwaysOnHotwordDetector.destroy(); 1532 // Drop identity adopted. 1533 InstrumentationRegistry.getInstrumentation().getUiAutomation() 1534 .dropShellPermissionIdentity(); 1535 } 1536 } 1537 1538 @Test testHotwordDetectionService_softwareDetector_onDetectFromExternalSource_rejected()1539 public void testHotwordDetectionService_softwareDetector_onDetectFromExternalSource_rejected() 1540 throws Throwable { 1541 // Create SoftwareHotwordDetector 1542 HotwordDetector softwareHotwordDetector = createSoftwareHotwordDetector(/* useOnFailure= */ 1543 false); 1544 try { 1545 adoptShellPermissionIdentityForHotword(); 1546 1547 PersistableBundle options = Helper.createFakePersistableBundleData(); 1548 options.putBoolean(Utils.KEY_DETECTION_REJECTED, true); 1549 1550 mService.initDetectRejectLatch(); 1551 softwareHotwordDetector.startRecognition(Helper.createFakeAudioStream(), 1552 Helper.createFakeAudioFormat(), options); 1553 1554 // Wait onRejected() called and verify the result 1555 mService.waitOnDetectOrRejectCalled(); 1556 HotwordRejectedResult rejectedResult = 1557 mService.getHotwordServiceOnRejectedResult(); 1558 1559 assertThat(rejectedResult).isEqualTo(Helper.REJECTED_RESULT); 1560 } finally { 1561 // Destroy detector 1562 softwareHotwordDetector.destroy(); 1563 // Drop identity adopted. 1564 InstrumentationRegistry.getInstrumentation().getUiAutomation() 1565 .dropShellPermissionIdentity(); 1566 } 1567 } 1568 1569 @Test 1570 @CddTest(requirement = "9.8/H-1-2,H-1-8,H-1-14") testHotwordDetectionService_onDetectFromMic_success()1571 public void testHotwordDetectionService_onDetectFromMic_success() throws Throwable { 1572 startWatchingNoted(); 1573 // Create SoftwareHotwordDetector 1574 HotwordDetector softwareHotwordDetector = createSoftwareHotwordDetector(/* useOnFailure= */ 1575 false); 1576 try { 1577 adoptShellPermissionIdentityForHotword(); 1578 1579 mService.initDetectRejectLatch(); 1580 softwareHotwordDetector.startRecognition(); 1581 1582 // wait onDetected() called and verify the result 1583 mService.waitOnDetectOrRejectCalled(); 1584 AlwaysOnHotwordDetector.EventPayload detectResult = 1585 mService.getHotwordServiceOnDetectedResult(); 1586 1587 Helper.verifyDetectedResult(detectResult, Helper.DETECTED_RESULT); 1588 1589 // Verify RECORD_AUDIO noted 1590 verifyRecordAudioNote(/* shouldNote= */ true); 1591 } finally { 1592 softwareHotwordDetector.destroy(); 1593 // Drop identity adopted. 1594 InstrumentationRegistry.getInstrumentation().getUiAutomation() 1595 .dropShellPermissionIdentity(); 1596 stopWatchingNoted(); 1597 } 1598 } 1599 1600 @Test testHotwordDetectionService_destroySoftwareDetector_activeDetectorRemoved()1601 public void testHotwordDetectionService_destroySoftwareDetector_activeDetectorRemoved() 1602 throws Throwable { 1603 // Create SoftwareHotwordDetector 1604 HotwordDetector softwareHotwordDetector = createSoftwareHotwordDetector(/* useOnFailure= */ 1605 false); 1606 1607 // Destroy SoftwareHotwordDetector 1608 softwareHotwordDetector.destroy(); 1609 1610 try { 1611 adoptShellPermissionIdentityForHotword(); 1612 // Can no longer use the detector because it is in an invalid state 1613 assertThrows(IllegalStateException.class, softwareHotwordDetector::startRecognition); 1614 } finally { 1615 // Drop identity adopted. 1616 InstrumentationRegistry.getInstrumentation().getUiAutomation() 1617 .dropShellPermissionIdentity(); 1618 } 1619 } 1620 1621 @Test testHotwordDetectionService_onStopDetection()1622 public void testHotwordDetectionService_onStopDetection() throws Throwable { 1623 // Create SoftwareHotwordDetector 1624 HotwordDetector softwareHotwordDetector = createSoftwareHotwordDetector(/* useOnFailure= */ 1625 false); 1626 try { 1627 adoptShellPermissionIdentityForHotword(); 1628 1629 // The HotwordDetectionService can't report any result after recognition is stopped. So 1630 // restart it after stopping; then the service can report a special result. 1631 softwareHotwordDetector.startRecognition(); 1632 softwareHotwordDetector.stopRecognition(); 1633 mService.initDetectRejectLatch(); 1634 softwareHotwordDetector.startRecognition(); 1635 1636 // wait onDetected() called and verify the result 1637 mService.waitOnDetectOrRejectCalled(); 1638 AlwaysOnHotwordDetector.EventPayload detectResult = 1639 mService.getHotwordServiceOnDetectedResult(); 1640 Helper.verifyDetectedResult(detectResult, Helper.DETECTED_RESULT_AFTER_STOP_DETECTION); 1641 } finally { 1642 softwareHotwordDetector.destroy(); 1643 // Drop identity adopted. 1644 InstrumentationRegistry.getInstrumentation().getUiAutomation() 1645 .dropShellPermissionIdentity(); 1646 } 1647 } 1648 1649 @Test 1650 @RequiresDevice testHotwordDetectionService_concurrentCapture()1651 public void testHotwordDetectionService_concurrentCapture() throws Throwable { 1652 // Create SoftwareHotwordDetector 1653 HotwordDetector softwareHotwordDetector = createSoftwareHotwordDetector(/* useOnFailure= */ 1654 false); 1655 1656 try { 1657 SystemUtil.runWithShellPermissionIdentity(() -> { 1658 AudioRecord record = 1659 new AudioRecord.Builder() 1660 .setAudioAttributes( 1661 new AudioAttributes.Builder() 1662 .setInternalCapturePreset( 1663 MediaRecorder.AudioSource.MIC).build()) 1664 .setAudioFormat( 1665 new AudioFormat.Builder() 1666 .setChannelMask(AudioFormat.CHANNEL_IN_MONO) 1667 .setEncoding(AudioFormat.ENCODING_PCM_16BIT) 1668 .build()) 1669 .setBufferSizeInBytes(10240) // something large enough to not fail 1670 .build(); 1671 assertThat(record.getState()).isEqualTo(AudioRecord.STATE_INITIALIZED); 1672 1673 try { 1674 record.startRecording(); 1675 1676 // Update state with test scenario HotwordDetectionService can read audio 1677 // and check the data is not zero 1678 PersistableBundle persistableBundle = new PersistableBundle(); 1679 persistableBundle.putInt(Helper.KEY_TEST_SCENARIO, 1680 EXTRA_HOTWORD_DETECTION_SERVICE_CAN_READ_AUDIO_DATA_IS_NOT_ZERO); 1681 softwareHotwordDetector.updateState( 1682 persistableBundle, 1683 Helper.createFakeSharedMemoryData()); 1684 1685 mService.initDetectRejectLatch(); 1686 softwareHotwordDetector.startRecognition(); 1687 mService.waitOnDetectOrRejectCalled(); 1688 AlwaysOnHotwordDetector.EventPayload detectResult = 1689 mService.getHotwordServiceOnDetectedResult(); 1690 Helper.verifyDetectedResult(detectResult, Helper.DETECTED_RESULT); 1691 // TODO: Test that it still works after restarting the process or killing audio 1692 // server. 1693 } finally { 1694 record.release(); 1695 } 1696 }); 1697 } finally { 1698 softwareHotwordDetector.destroy(); 1699 } 1700 } 1701 1702 @Test 1703 @RequiresDevice testMultipleDetectors_onDetectFromDspAndMic_success()1704 public void testMultipleDetectors_onDetectFromDspAndMic_success() throws Throwable { 1705 assumeTrue("Not support multiple hotword detectors", 1706 Helper.isEnableMultipleDetectors()); 1707 1708 // Create AlwaysOnHotwordDetector 1709 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 1710 createAlwaysOnHotwordDetectorWithSoundTriggerInjection(); 1711 1712 // Create SoftwareHotwordDetector 1713 HotwordDetector softwareHotwordDetector = createSoftwareHotwordDetector(/* useOnFailure= */ 1714 false); 1715 1716 try { 1717 runWithShellPermissionIdentity(() -> { 1718 // Update state with test scenario HotwordDetectionService can read audio and 1719 // check the data is not zero 1720 PersistableBundle persistableBundle = new PersistableBundle(); 1721 persistableBundle.putInt(Helper.KEY_TEST_SCENARIO, 1722 EXTRA_HOTWORD_DETECTION_SERVICE_CAN_READ_AUDIO_DATA_IS_NOT_ZERO); 1723 alwaysOnHotwordDetector.updateState( 1724 persistableBundle, 1725 Helper.createFakeSharedMemoryData()); 1726 }, MANAGE_HOTWORD_DETECTION); 1727 1728 adoptShellPermissionIdentityForHotword(); 1729 // Test AlwaysOnHotwordDetector to be able to detect well 1730 verifyOnDetectFromDspWithSoundTriggerInjectionSuccess( 1731 alwaysOnHotwordDetector, /* shouldDisableTestModel= */ false); 1732 1733 // Test SoftwareHotwordDetector to be able to detect well 1734 verifySoftwareDetectorDetectSuccess(softwareHotwordDetector); 1735 } finally { 1736 // Drop identity adopted. 1737 InstrumentationRegistry.getInstrumentation().getUiAutomation() 1738 .dropShellPermissionIdentity(); 1739 // Destroy the always on detector 1740 alwaysOnHotwordDetector.destroy(); 1741 1742 // Destroy the software detector 1743 softwareHotwordDetector.destroy(); 1744 disableTestModel(); 1745 } 1746 } 1747 1748 @Test testHotwordDetectionService_onHotwordDetectionServiceRestarted()1749 public void testHotwordDetectionService_onHotwordDetectionServiceRestarted() throws Throwable { 1750 // Create AlwaysOnHotwordDetector 1751 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 1752 createAlwaysOnHotwordDetector(/* useOnFailure= */ false); 1753 1754 mService.initOnHotwordDetectionServiceRestartedLatch(); 1755 // force re-start by shell command 1756 runShellCommand("cmd voiceinteraction restart-detection"); 1757 1758 // wait onHotwordDetectionServiceRestarted() called 1759 mService.waitOnHotwordDetectionServiceRestartedCalled(); 1760 1761 // Destroy the always on detector 1762 alwaysOnHotwordDetector.destroy(); 1763 } 1764 1765 @Test testHotwordDetectionService_dspDetector_serviceScheduleRestarted()1766 public void testHotwordDetectionService_dspDetector_serviceScheduleRestarted() 1767 throws Throwable { 1768 // Change the period of restarting hotword detection 1769 final String restartPeriod = Helper.getHotwordDetectionServiceRestartPeriod(); 1770 Helper.setHotwordDetectionServiceRestartPeriod("8"); 1771 1772 try { 1773 mService.initOnHotwordDetectionServiceRestartedLatch(); 1774 1775 // Create AlwaysOnHotwordDetector and wait result 1776 mService.createAlwaysOnHotwordDetector(/* useExecutor= */ false, /* runOnMainThread= */ 1777 false); 1778 1779 // Wait the result and verify expected result 1780 mService.waitSandboxedDetectionServiceInitializedCalledOrException(); 1781 1782 // Verify callback result 1783 assertThat(mService.getSandboxedDetectionServiceInitializedResult()).isEqualTo( 1784 HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS); 1785 1786 // Wait onHotwordDetectionServiceRestarted() called 1787 mService.waitOnHotwordDetectionServiceRestartedCalled(); 1788 } finally { 1789 Helper.setHotwordDetectionServiceRestartPeriod(restartPeriod); 1790 AlwaysOnHotwordDetector alwaysOnHotwordDetector = mService.getAlwaysOnHotwordDetector(); 1791 if (alwaysOnHotwordDetector != null) { 1792 alwaysOnHotwordDetector.destroy(); 1793 } 1794 } 1795 } 1796 1797 @Test testHotwordDetectionService_softwareDetector_serviceScheduleRestarted()1798 public void testHotwordDetectionService_softwareDetector_serviceScheduleRestarted() 1799 throws Throwable { 1800 // Change the period of restarting hotword detection 1801 final String restartPeriod = Helper.getHotwordDetectionServiceRestartPeriod(); 1802 Helper.setHotwordDetectionServiceRestartPeriod("8"); 1803 1804 try { 1805 mService.initOnHotwordDetectionServiceRestartedLatch(); 1806 1807 // Create AlwaysOnHotwordDetector and wait result 1808 mService.createSoftwareHotwordDetector(/* useExecutor= */ false, /* runOnMainThread= */ 1809 false); 1810 1811 // Wait the result and verify expected result 1812 mService.waitSandboxedDetectionServiceInitializedCalledOrException(); 1813 1814 // Verify callback result 1815 assertThat(mService.getSandboxedDetectionServiceInitializedResult()).isEqualTo( 1816 HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS); 1817 1818 // Wait onHotwordDetectionServiceRestarted() called 1819 mService.waitOnHotwordDetectionServiceRestartedCalled(); 1820 } finally { 1821 Helper.setHotwordDetectionServiceRestartPeriod(restartPeriod); 1822 HotwordDetector softwareHotwordDetector = mService.getSoftwareHotwordDetector(); 1823 if (softwareHotwordDetector != null) { 1824 softwareHotwordDetector.destroy(); 1825 } 1826 } 1827 } 1828 1829 @Test testHotwordDetectionService_onDetectedTwice_clientOnlyOneOnDetected()1830 public void testHotwordDetectionService_onDetectedTwice_clientOnlyOneOnDetected() 1831 throws Throwable { 1832 // Create SoftwareHotwordDetector 1833 HotwordDetector softwareHotwordDetector = 1834 createSoftwareHotwordDetector(/*useOnFailure=*/ false); 1835 try { 1836 runWithShellPermissionIdentity(() -> { 1837 // Update state with test scenario unexpected onDetect callback 1838 // HDS will call back onDetected() twice 1839 PersistableBundle persistableBundle = new PersistableBundle(); 1840 persistableBundle.putInt(Helper.KEY_TEST_SCENARIO, 1841 Utils.EXTRA_HOTWORD_DETECTION_SERVICE_ON_UPDATE_STATE_UNEXPECTED_CALLBACK); 1842 softwareHotwordDetector.updateState( 1843 persistableBundle, 1844 Helper.createFakeSharedMemoryData()); 1845 }, MANAGE_HOTWORD_DETECTION); 1846 1847 adoptShellPermissionIdentityForHotword(); 1848 1849 mService.initDetectRejectLatch(); 1850 softwareHotwordDetector.startRecognition(); 1851 1852 // wait onDetected() called and only once (even HDS callback onDetected() many times, 1853 // only one onDetected() on VIS will be called) 1854 mService.waitOnDetectOrRejectCalled(); 1855 // Wait for a while to make sure no 2nd onDetected() will be called 1856 Thread.sleep(500); 1857 assertThat(mService.getSoftwareOnDetectedCount()).isEqualTo(1); 1858 } finally { 1859 softwareHotwordDetector.destroy(); 1860 InstrumentationRegistry.getInstrumentation().getUiAutomation() 1861 .dropShellPermissionIdentity(); 1862 } 1863 } 1864 1865 @Test testHotwordDetectionService_dspDetector_onDetectedTwice_clientOnlyOneOnDetected()1866 public void testHotwordDetectionService_dspDetector_onDetectedTwice_clientOnlyOneOnDetected() 1867 throws Throwable { 1868 startWatchingNoted(); 1869 // Create AlwaysOnHotwordDetector 1870 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 1871 createAlwaysOnHotwordDetectorWithSoundTriggerInjection(); 1872 try { 1873 runWithShellPermissionIdentity(() -> { 1874 // Update state with test scenario unexpected onDetect callback 1875 // HDS will call back onDetected() twice 1876 PersistableBundle persistableBundle = new PersistableBundle(); 1877 persistableBundle.putInt(Helper.KEY_TEST_SCENARIO, 1878 Utils.EXTRA_HOTWORD_DETECTION_SERVICE_ON_UPDATE_STATE_UNEXPECTED_CALLBACK); 1879 alwaysOnHotwordDetector.updateState( 1880 persistableBundle, 1881 Helper.createFakeSharedMemoryData()); 1882 }, MANAGE_HOTWORD_DETECTION); 1883 1884 adoptShellPermissionIdentityForHotword(); 1885 1886 verifyOnDetectFromDspWithSoundTriggerInjectionSuccess(alwaysOnHotwordDetector); 1887 1888 // Verify RECORD_AUDIO noted 1889 verifyRecordAudioNote(/* shouldNote= */ true); 1890 1891 // Wait for a while to make sure no 2nd onDetected() will be called 1892 Thread.sleep(500); 1893 assertThat(mService.getDspOnDetectedCount()).isEqualTo(1); 1894 } finally { 1895 // Destroy detector 1896 alwaysOnHotwordDetector.destroy(); 1897 // Drop identity adopted. 1898 InstrumentationRegistry.getInstrumentation().getUiAutomation() 1899 .dropShellPermissionIdentity(); 1900 stopWatchingNoted(); 1901 } 1902 } 1903 1904 @Test testHotwordDetectionService_dspDetector_onRejectedTwice_clientOnlyOneOnRejected()1905 public void testHotwordDetectionService_dspDetector_onRejectedTwice_clientOnlyOneOnRejected() 1906 throws Throwable { 1907 // Create AlwaysOnHotwordDetector 1908 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 1909 createAlwaysOnHotwordDetector(/* useOnFailure= */ false); 1910 try { 1911 runWithShellPermissionIdentity(() -> { 1912 // Update state with test scenario unexpected onRejected callback 1913 // HDS will call back onRejected() twice 1914 PersistableBundle persistableBundle = new PersistableBundle(); 1915 persistableBundle.putInt(Helper.KEY_TEST_SCENARIO, 1916 Utils.EXTRA_HOTWORD_DETECTION_SERVICE_ON_UPDATE_STATE_UNEXPECTED_CALLBACK); 1917 alwaysOnHotwordDetector.updateState( 1918 persistableBundle, 1919 Helper.createFakeSharedMemoryData()); 1920 }, MANAGE_HOTWORD_DETECTION); 1921 1922 mService.initDetectRejectLatch(); 1923 runWithShellPermissionIdentity(() -> { 1924 // Pass null data parameter 1925 alwaysOnHotwordDetector.triggerHardwareRecognitionEventForTest( 1926 /* status= */ 0, /* soundModelHandle= */ 100, 1927 /* halEventReceivedMillis */ 12345, /* captureAvailable= */ true, 1928 /* captureSession= */ 101, /* captureDelayMs= */ 1000, 1929 /* capturePreambleMs= */ 1001, /* triggerInData= */ true, 1930 Helper.createFakeAudioFormat(), null, 1931 Helper.createFakeKeyphraseRecognitionExtraList()); 1932 }); 1933 // Wait onRejected() called and verify the result 1934 mService.waitOnDetectOrRejectCalled(); 1935 HotwordRejectedResult rejectedResult = 1936 mService.getHotwordServiceOnRejectedResult(); 1937 1938 assertThat(rejectedResult).isEqualTo(Helper.REJECTED_RESULT); 1939 1940 // Wait for a while to make sure no 2nd onDetected() will be called 1941 Thread.sleep(500); 1942 assertThat(mService.getDspOnRejectedCount()).isEqualTo(1); 1943 } finally { 1944 // Destroy detector 1945 alwaysOnHotwordDetector.destroy(); 1946 } 1947 } 1948 1949 @Test testHotwordDetectionService_dspDetector_duringOnDetect_serviceRestart()1950 public void testHotwordDetectionService_dspDetector_duringOnDetect_serviceRestart() 1951 throws Throwable { 1952 // Create AlwaysOnHotwordDetector 1953 AlwaysOnHotwordDetector alwaysOnHotwordDetector = 1954 createAlwaysOnHotwordDetectorWithSoundTriggerInjection(); 1955 try { 1956 runWithShellPermissionIdentity(() -> { 1957 // Inform the HotwordDetectionService to ignore the onDetect 1958 PersistableBundle persistableBundle = new PersistableBundle(); 1959 persistableBundle.putInt(Helper.KEY_TEST_SCENARIO, 1960 Utils.EXTRA_HOTWORD_DETECTION_SERVICE_NO_NEED_ACTION_DURING_DETECTION); 1961 alwaysOnHotwordDetector.updateState( 1962 persistableBundle, 1963 Helper.createFakeSharedMemoryData()); 1964 }, MANAGE_HOTWORD_DETECTION); 1965 1966 adoptShellPermissionIdentityForHotword(); 1967 1968 alwaysOnHotwordDetector.startRecognition(0, new byte[]{1, 2, 3, 4, 5}); 1969 RecognitionSession recognitionSession = waitForFutureDoneAndAssertSuccessful( 1970 mInstrumentationObserver.getOnRecognitionStartedFuture()); 1971 assertThat(recognitionSession).isNotNull(); 1972 1973 // Verify we received model load, recognition start 1974 recognitionSession.triggerRecognitionEvent(new byte[1024], 1975 createKeyphraseRecognitionExtraList()); 1976 1977 // Wait for a while to make sure onDetect() will be called in the 1978 // MainHotwordDetectionService 1979 Thread.sleep(500); 1980 1981 mService.initDetectRejectLatch(); 1982 1983 // Force re-start by shell command 1984 runShellCommand("cmd voiceinteraction restart-detection"); 1985 1986 // Wait onRejected() called and verify the result 1987 mService.waitOnDetectOrRejectCalled(); 1988 HotwordRejectedResult rejectedResult = 1989 mService.getHotwordServiceOnRejectedResult(); 1990 1991 assertThat(rejectedResult).isNotNull(); 1992 } finally { 1993 // Destroy detector 1994 alwaysOnHotwordDetector.destroy(); 1995 // Drop identity adopted. 1996 InstrumentationRegistry.getInstrumentation().getUiAutomation() 1997 .dropShellPermissionIdentity(); 1998 disableTestModel(); 1999 } 2000 } 2001 2002 /** 2003 * Before using this method, please call {@link #adoptShellPermissionIdentityForHotword()} to 2004 * grant permissions. And please use 2005 * {@link #createAlwaysOnHotwordDetectorWithSoundTriggerInjection()} to create 2006 * AlwaysOnHotwordDetector. This method will disable test model after verifying the result. 2007 * If you want to disable the test model manually, please use 2008 * {@link #verifyOnDetectFromDspWithSoundTriggerInjectionSuccess( 2009 * AlwaysOnHotwordDetector, boolean)} 2010 */ verifyOnDetectFromDspWithSoundTriggerInjectionSuccess( AlwaysOnHotwordDetector alwaysOnHotwordDetector)2011 private void verifyOnDetectFromDspWithSoundTriggerInjectionSuccess( 2012 AlwaysOnHotwordDetector alwaysOnHotwordDetector) throws Throwable { 2013 verifyOnDetectFromDspWithSoundTriggerInjectionSuccess( 2014 alwaysOnHotwordDetector, /* shouldDisableTestModel= */ true); 2015 } 2016 2017 /** 2018 * Before using this method, please call {@link #adoptShellPermissionIdentityForHotword()} to 2019 * grant permissions. And please use 2020 * {@link #createAlwaysOnHotwordDetectorWithSoundTriggerInjection()} to create 2021 * AlwaysOnHotwordDetector. If {@code shouldDisableTestModel} is true, it will disable the test 2022 * model. Otherwise, it doesn't disable the test model. Please call {@link #disableTestModel} 2023 * manually. 2024 */ verifyOnDetectFromDspWithSoundTriggerInjectionSuccess( AlwaysOnHotwordDetector alwaysOnHotwordDetector, boolean shouldDisableTestModel)2025 private void verifyOnDetectFromDspWithSoundTriggerInjectionSuccess( 2026 AlwaysOnHotwordDetector alwaysOnHotwordDetector, 2027 boolean shouldDisableTestModel) throws Throwable { 2028 try { 2029 alwaysOnHotwordDetector.startRecognition(0, new byte[]{1, 2, 3, 4, 5}); 2030 RecognitionSession recognitionSession = waitForFutureDoneAndAssertSuccessful( 2031 mInstrumentationObserver.getOnRecognitionStartedFuture()); 2032 assertThat(recognitionSession).isNotNull(); 2033 2034 mService.initDetectRejectLatch(); 2035 recognitionSession.triggerRecognitionEvent(new byte[1024], 2036 createKeyphraseRecognitionExtraList()); 2037 2038 // wait onDetected() called and verify the result 2039 mService.waitOnDetectOrRejectCalled(); 2040 AlwaysOnHotwordDetector.EventPayload detectResult = 2041 mService.getHotwordServiceOnDetectedResult(); 2042 2043 Helper.verifyDetectedResult(detectResult, Helper.DETECTED_RESULT); 2044 } finally { 2045 if (shouldDisableTestModel) { 2046 disableTestModel(); 2047 } 2048 } 2049 } 2050 disableTestModel()2051 private void disableTestModel() { 2052 runWithShellPermissionIdentity(() -> mService.disableOverrideRegisterModel(), 2053 MANAGE_VOICE_KEYPHRASES); 2054 } 2055 verifySoftwareDetectorDetectSuccess(HotwordDetector softwareHotwordDetector)2056 private void verifySoftwareDetectorDetectSuccess(HotwordDetector softwareHotwordDetector) 2057 throws Exception { 2058 mService.initDetectRejectLatch(); 2059 softwareHotwordDetector.startRecognition(); 2060 2061 // wait onDetected() called and verify the result 2062 mService.waitOnDetectOrRejectCalled(); 2063 AlwaysOnHotwordDetector.EventPayload detectResult = 2064 mService.getHotwordServiceOnDetectedResult(); 2065 Helper.verifyDetectedResult(detectResult, Helper.DETECTED_RESULT); 2066 } 2067 verifyHotwordDetectionServiceFailure( HotwordDetectionServiceFailure hotwordDetectionServiceFailure, int errorCode)2068 private void verifyHotwordDetectionServiceFailure( 2069 HotwordDetectionServiceFailure hotwordDetectionServiceFailure, int errorCode) 2070 throws Throwable { 2071 assertThat(hotwordDetectionServiceFailure).isNotNull(); 2072 assertThat(hotwordDetectionServiceFailure.getErrorCode()).isEqualTo(errorCode); 2073 } 2074 2075 /** 2076 * Create software hotword detector and wait for ready 2077 */ createSoftwareHotwordDetector(boolean useOnFailure)2078 private HotwordDetector createSoftwareHotwordDetector(boolean useOnFailure) throws Throwable { 2079 // Create SoftwareHotwordDetector 2080 if (useOnFailure) { 2081 mService.createSoftwareHotwordDetectorWithOnFailureCallback(/* useExecutor= */ 2082 false, /* runOnMainThread= */ false); 2083 } else { 2084 mService.createSoftwareHotwordDetector(); 2085 } 2086 2087 mService.waitSandboxedDetectionServiceInitializedCalledOrException(); 2088 2089 // verify callback result 2090 // TODO: not use Deprecated variable 2091 assertThat(mService.getSandboxedDetectionServiceInitializedResult()).isEqualTo( 2092 HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS); 2093 HotwordDetector softwareHotwordDetector = mService.getSoftwareHotwordDetector(); 2094 Objects.requireNonNull(softwareHotwordDetector); 2095 2096 return softwareHotwordDetector; 2097 } 2098 2099 /** 2100 * Create AlwaysOnHotwordDetector and wait for ready 2101 */ createAlwaysOnHotwordDetector(boolean useOnFailure)2102 private AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(boolean useOnFailure) 2103 throws Throwable { 2104 // Create AlwaysOnHotwordDetector and wait ready. 2105 if (useOnFailure) { 2106 mService.createAlwaysOnHotwordDetectorWithOnFailureCallback(/* useExecutor= */ 2107 false, /* runOnMainThread= */ false); 2108 } else { 2109 mService.createAlwaysOnHotwordDetector(); 2110 } 2111 2112 mService.waitSandboxedDetectionServiceInitializedCalledOrException(); 2113 2114 // verify callback result 2115 // TODO: not use Deprecated variable 2116 assertThat(mService.getSandboxedDetectionServiceInitializedResult()).isEqualTo( 2117 HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS); 2118 AlwaysOnHotwordDetector alwaysOnHotwordDetector = mService.getAlwaysOnHotwordDetector(); 2119 Objects.requireNonNull(alwaysOnHotwordDetector); 2120 2121 return alwaysOnHotwordDetector; 2122 } 2123 2124 /** 2125 * Create AlwaysOnHotwordDetector with SoundTrigger injection. Please call 2126 * {@link #verifyOnDetectFromDspWithSoundTriggerInjectionSuccess} or {@link #disableTestModel()} 2127 * before finishing the test. 2128 */ createAlwaysOnHotwordDetectorWithSoundTriggerInjection()2129 private AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorWithSoundTriggerInjection() 2130 throws Throwable { 2131 final UUID uuid = new UUID(5, 7); 2132 final UUID vendorUuid = new UUID(7, 5); 2133 2134 // Start override enrolled model with a custom model for test purposes. 2135 runWithShellPermissionIdentity(() -> mService.enableOverrideRegisterModel( 2136 new SoundTrigger.KeyphraseSoundModel(uuid, vendorUuid, /* data= */ null, 2137 mKeyphraseArray)), MANAGE_VOICE_KEYPHRASES); 2138 2139 // Call initAvailabilityChangeLatch for onAvailabilityChanged() callback called 2140 // following AlwaysOnHotwordDetector creation. 2141 mService.initAvailabilityChangeLatch(); 2142 2143 // Create AlwaysOnHotwordDetector and wait ready. 2144 mService.createAlwaysOnHotwordDetector(); 2145 2146 mService.waitSandboxedDetectionServiceInitializedCalledOrException(); 2147 2148 // verify initialization callback result 2149 // TODO: not use Deprecated variable 2150 assertThat(mService.getSandboxedDetectionServiceInitializedResult()).isEqualTo( 2151 HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS); 2152 AlwaysOnHotwordDetector alwaysOnHotwordDetector = mService.getAlwaysOnHotwordDetector(); 2153 Objects.requireNonNull(alwaysOnHotwordDetector); 2154 2155 // verify have entered the ENROLLED state 2156 mService.waitAvailabilityChangedCalled(); 2157 assertThat(mService.getHotwordDetectionServiceAvailabilityResult()).isEqualTo( 2158 AlwaysOnHotwordDetector.STATE_KEYPHRASE_ENROLLED); 2159 2160 return alwaysOnHotwordDetector; 2161 } 2162 adoptShellPermissionIdentityForHotword()2163 private void adoptShellPermissionIdentityForHotword() { 2164 // Drop any identity adopted earlier. 2165 UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 2166 uiAutomation.dropShellPermissionIdentity(); 2167 // need to retain the identity until the callback is triggered 2168 uiAutomation.adoptShellPermissionIdentity(RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD); 2169 } 2170 adoptShellPermissionIdentityForHotwordAndWearableSensing()2171 private void adoptShellPermissionIdentityForHotwordAndWearableSensing() { 2172 // Drop any identity adopted earlier. 2173 UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 2174 uiAutomation.dropShellPermissionIdentity(); 2175 // need to retain the identity until the callback is triggered 2176 uiAutomation.adoptShellPermissionIdentity( 2177 RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD, MANAGE_WEARABLE_SENSING_SERVICE); 2178 } 2179 startWatchingNoted()2180 private void startWatchingNoted() { 2181 runWithShellPermissionIdentity(() -> { 2182 if (mAppOpsManager != null) { 2183 mAppOpsManager.startWatchingNoted(new String[]{ 2184 AppOpsManager.OPSTR_RECORD_AUDIO}, 2185 mOnOpNotedListener); 2186 } 2187 }); 2188 } 2189 stopWatchingNoted()2190 private void stopWatchingNoted() { 2191 runWithShellPermissionIdentity(() -> { 2192 if (mAppOpsManager != null) { 2193 mAppOpsManager.stopWatchingNoted(mOnOpNotedListener); 2194 } 2195 }); 2196 } 2197 verifyRecordAudioNote(boolean shouldNote)2198 private void verifyRecordAudioNote(boolean shouldNote) throws Exception { 2199 if (sPkgMgr.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { 2200 // TODO: test TV indicator 2201 } else if (sPkgMgr.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { 2202 // TODO: test Auto indicator 2203 } else if (sPkgMgr.hasSystemFeature(PackageManager.FEATURE_WATCH)) { 2204 // The privacy chips/indicators are not implemented on Wear 2205 } else { 2206 boolean isNoted = mLatch.await(Helper.CLEAR_CHIP_MS, TimeUnit.MILLISECONDS); 2207 assertThat(isNoted).isEqualTo(shouldNote); 2208 if (isNoted) { 2209 assertThat(mOpNoted).isEqualTo(AppOpsManager.OPSTR_RECORD_AUDIO); 2210 } 2211 } 2212 } 2213 setupForWearableTests(AlwaysOnHotwordDetector alwaysOnHotwordDetector)2214 private void setupForWearableTests(AlwaysOnHotwordDetector alwaysOnHotwordDetector) 2215 throws Exception { 2216 setupForWearableTests(alwaysOnHotwordDetector, true); 2217 } 2218 setupForWearableTests( AlwaysOnHotwordDetector alwaysOnHotwordDetector, boolean closeStreamAfterRead)2219 private void setupForWearableTests( 2220 AlwaysOnHotwordDetector alwaysOnHotwordDetector, boolean closeStreamAfterRead) 2221 throws Exception { 2222 mOriginalWearableSensingServiceEnabledConfig = 2223 getWearableSensingServiceEnabledDeviceConfig(); 2224 setWearableSensingServiceEnabledDeviceConfig("true"); 2225 // Update HotwordDetectionService options to enable Audio egress 2226 runWithShellPermissionIdentity( 2227 () -> { 2228 PersistableBundle persistableBundle = new PersistableBundle(); 2229 persistableBundle.putInt( 2230 Helper.KEY_TEST_SCENARIO, 2231 Utils.EXTRA_HOTWORD_DETECTION_SERVICE_ENABLE_AUDIO_EGRESS); 2232 persistableBundle.putBoolean( 2233 Utils.KEY_AUDIO_EGRESS_CLOSE_AUDIO_STREAM_AFTER_READ, 2234 closeStreamAfterRead); 2235 alwaysOnHotwordDetector.updateState( 2236 persistableBundle, Helper.createFakeSharedMemoryData()); 2237 }, 2238 MANAGE_HOTWORD_DETECTION); 2239 adoptShellPermissionIdentityForHotwordAndWearableSensing(); 2240 setTestableWearableSensingService(); 2241 resetWearableSensingServiceStates(); 2242 alwaysOnHotwordDetector.startRecognition(0, new byte[] {1, 2, 3, 4, 5}); 2243 RecognitionSession recognitionSession = 2244 waitForFutureDoneAndAssertSuccessful( 2245 mInstrumentationObserver.getOnRecognitionStartedFuture()); 2246 assertThat(recognitionSession).isNotNull(); 2247 } 2248 cleanupForWearableTests(AlwaysOnHotwordDetector alwaysOnHotwordDetector)2249 private void cleanupForWearableTests(AlwaysOnHotwordDetector alwaysOnHotwordDetector) 2250 throws Exception { 2251 if (mOriginalWearableSensingServiceEnabledConfig != null) { 2252 setWearableSensingServiceEnabledDeviceConfig( 2253 mOriginalWearableSensingServiceEnabledConfig); 2254 mOriginalWearableSensingServiceEnabledConfig = null; 2255 } 2256 // destroy detector 2257 alwaysOnHotwordDetector.destroy(); 2258 // Drop identity adopted. 2259 InstrumentationRegistry.getInstrumentation() 2260 .getUiAutomation() 2261 .dropShellPermissionIdentity(); 2262 clearTestableWearableSensingService(); 2263 } 2264 getWearableSensingServiceEnabledDeviceConfig()2265 private static String getWearableSensingServiceEnabledDeviceConfig() { 2266 return runWithShellPermissionIdentity( 2267 () -> { 2268 return DeviceConfig.getProperty( 2269 DeviceConfig.NAMESPACE_WEARABLE_SENSING, 2270 KEY_WEARABLE_SENSING_SERVICE_ENABLED); 2271 }); 2272 } 2273 setWearableSensingServiceEnabledDeviceConfig(String newValue)2274 private static void setWearableSensingServiceEnabledDeviceConfig(String newValue) { 2275 runWithShellPermissionIdentity( 2276 () -> { 2277 DeviceConfig.setProperty( 2278 DeviceConfig.NAMESPACE_WEARABLE_SENSING, 2279 KEY_WEARABLE_SENSING_SERVICE_ENABLED, 2280 newValue, 2281 /* makeDefault= */ false); 2282 }); 2283 } 2284 2285 /** Temporarily sets the WearableSensingService to the test implementation. */ setTestableWearableSensingService()2286 private void setTestableWearableSensingService() { 2287 runShellCommand( 2288 "cmd wearable_sensing set-temporary-service %d %s %d", 2289 USER_ID, MAIN_WEARABLE_SENSING_SERVICE_NAME, TEMPORARY_SERVICE_DURATION_MS); 2290 } 2291 2292 /** Resets the WearableSensingService implementation. */ clearTestableWearableSensingService()2293 private void clearTestableWearableSensingService() { 2294 runShellCommand("cmd wearable_sensing set-temporary-service %d", USER_ID); 2295 } 2296 resetWearableSensingServiceStates()2297 private void resetWearableSensingServiceStates() throws Exception { 2298 provideDataToWearableSensingServiceAndWait(MainWearableSensingService.ACTION_RESET); 2299 } 2300 sendAudioStreamFromWearable()2301 private void sendAudioStreamFromWearable() throws Exception { 2302 provideDataToWearableSensingServiceAndWait(MainWearableSensingService.ACTION_SEND_AUDIO); 2303 } 2304 sendNonHotwordAudioStreamFromWearable()2305 private void sendNonHotwordAudioStreamFromWearable() throws Exception { 2306 provideDataToWearableSensingServiceAndWait( 2307 MainWearableSensingService.ACTION_SEND_NON_HOTWORD_AUDIO); 2308 } 2309 sendNonHotwordAudioStreamWithAcceptDetectionOptionsFromWearable()2310 private void sendNonHotwordAudioStreamWithAcceptDetectionOptionsFromWearable() 2311 throws Exception { 2312 provideDataToWearableSensingServiceAndWait( 2313 MainWearableSensingService 2314 .ACTION_SEND_NON_HOTWORD_AUDIO_WITH_ACCEPT_DETECTION_OPTIONS); 2315 } 2316 sendMoreAudioDataFromWearable()2317 private void sendMoreAudioDataFromWearable() throws Exception { 2318 provideDataToWearableSensingServiceAndWait( 2319 MainWearableSensingService.ACTION_SEND_MORE_AUDIO_DATA); 2320 } 2321 verifyWearableSensingServiceHotwordValidatedCalled()2322 private void verifyWearableSensingServiceHotwordValidatedCalled() throws Exception { 2323 assertThat( 2324 provideDataToWearableSensingServiceAndWait( 2325 MainWearableSensingService.ACTION_VERIFY_HOTWORD_VALIDATED_CALLED)) 2326 .isEqualTo(WearableSensingManager.STATUS_SUCCESS); 2327 } 2328 verifyWearableSensingServiceAudioStopCalled()2329 private void verifyWearableSensingServiceAudioStopCalled() throws Exception { 2330 assertThat( 2331 provideDataToWearableSensingServiceAndWait( 2332 MainWearableSensingService.ACTION_VERIFY_AUDIO_STOP_CALLED)) 2333 .isEqualTo(WearableSensingManager.STATUS_SUCCESS); 2334 } 2335 provideDataToWearableSensingServiceAndWait(String value)2336 private int provideDataToWearableSensingServiceAndWait(String value) throws Exception { 2337 CountDownLatch latch = new CountDownLatch(1); 2338 AtomicInteger statusRef = new AtomicInteger(); 2339 PersistableBundle data = new PersistableBundle(); 2340 data.putString(MainWearableSensingService.BUNDLE_ACTION_KEY, value); 2341 sWearableSensingManager.provideData( 2342 data, 2343 null, 2344 mExecutor, 2345 (status) -> { 2346 statusRef.set(status); 2347 latch.countDown(); 2348 }); 2349 assertThat(latch.await(3, SECONDS)).isTrue(); 2350 return statusRef.get(); 2351 } 2352 isWatch()2353 private boolean isWatch() { 2354 return sPkgMgr.hasSystemFeature(PackageManager.FEATURE_WATCH); 2355 } 2356 } 2357