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