[Error API] add CTS tests for Error cases

Currently I add this one error case.
1.HotwordDetectionServiceFailure.ERROR_CODE_BINDING_DIED

Bug: 261549853
Test: atest CtsVoiceInteractionTestCases
Change-Id: If3078654adaff7076195f05b6fdfd4220e512ee6
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
index 7f9b4ed..b12b698 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
@@ -45,7 +45,9 @@
 import android.os.SystemClock;
 import android.platform.test.annotations.AppModeFull;
 import android.service.voice.AlwaysOnHotwordDetector;
+import android.service.voice.DetectorFailure;
 import android.service.voice.HotwordDetectionService;
+import android.service.voice.HotwordDetectionServiceFailure;
 import android.service.voice.HotwordDetector;
 import android.service.voice.HotwordDetector.IllegalDetectorStateException;
 import android.service.voice.HotwordRejectedResult;
@@ -224,7 +226,8 @@
         assumeTrue("Not support multiple hotword detectors", enableMultipleHotwordDetectors);
 
         // Create first AlwaysOnHotwordDetector, it's fine.
-        AlwaysOnHotwordDetector alwaysOnHotwordDetector = createAlwaysOnHotwordDetector();
+        AlwaysOnHotwordDetector alwaysOnHotwordDetector =
+                createAlwaysOnHotwordDetector(/* useOnFailure= */ false);
 
         // Create second AlwaysOnHotwordDetector, it will get the IllegalStateException due to
         // the previous AlwaysOnHotwordDetector is not destroy.
@@ -279,7 +282,10 @@
     @Test
     public void testHotwordDetectionService_processDied_triggerOnError() throws Throwable {
         // Create first AlwaysOnHotwordDetector
-        AlwaysOnHotwordDetector alwaysOnHotwordDetector = createAlwaysOnHotwordDetector();
+        AlwaysOnHotwordDetector alwaysOnHotwordDetector =
+                createAlwaysOnHotwordDetector(/* useOnFailure= */ false);
+
+        mService.initOnErrorLatch();
 
         // Use AlwaysOnHotwordDetector to test process died of HotwordDetectionService
         runWithShellPermissionIdentity(() -> {
@@ -291,6 +297,45 @@
                     Helper.createFakeSharedMemoryData());
         }, MANAGE_HOTWORD_DETECTION);
 
+        mService.waitOnErrorCalled();
+
+        // ActivityManager will schedule a timer to restart the HotwordDetectionService due to
+        // we crash the service in this test case. It may impact the other test cases when
+        // ActivityManager restarts the HotwordDetectionService again. Add the sleep time to wait
+        // ActivityManager to restart the HotwordDetectionService, so that the service can be
+        // destroyed after finishing this test case.
+        Thread.sleep(5000);
+
+        alwaysOnHotwordDetector.destroy();
+    }
+
+    @Test
+    public void testHotwordDetectionService_processDied_triggerOnFailure() throws Throwable {
+        // Create alwaysOnHotwordDetector with onFailure callback
+        AlwaysOnHotwordDetector alwaysOnHotwordDetector =
+                createAlwaysOnHotwordDetector(/* useOnFailure= */ true);
+
+        mService.initOnFailureLatch();
+
+        // Use AlwaysOnHotwordDetector to test process died of HotwordDetectionService
+        runWithShellPermissionIdentity(() -> {
+            PersistableBundle persistableBundle = new PersistableBundle();
+            persistableBundle.putInt(Helper.KEY_TEST_SCENARIO,
+                    Helper.EXTRA_HOTWORD_DETECTION_SERVICE_ON_UPDATE_STATE_CRASH);
+            alwaysOnHotwordDetector.updateState(
+                    persistableBundle,
+                    Helper.createFakeSharedMemoryData());
+        }, MANAGE_HOTWORD_DETECTION);
+
+        mService.waitOnFailureCalled();
+
+        DetectorFailure detectorFailure = mService.getDetectorFailure();
+
+        assertThat(detectorFailure).isNotNull();
+        assertThat(detectorFailure).isInstanceOf(HotwordDetectionServiceFailure.class);
+        assertThat(((HotwordDetectionServiceFailure) detectorFailure).getErrorCode()).isEqualTo(
+                HotwordDetectionServiceFailure.ERROR_CODE_BINDING_DIED);
+
         // ActivityManager will schedule a timer to restart the HotwordDetectionService due to
         // we crash the service in this test case. It may impact the other test cases when
         // ActivityManager restarts the HotwordDetectionService again. Add the sleep time to wait
@@ -312,7 +357,8 @@
 
         // Create AlwaysOnHotwordDetector
         startWatchingNoted();
-        AlwaysOnHotwordDetector alwaysOnHotwordDetector = createAlwaysOnHotwordDetector();
+        AlwaysOnHotwordDetector alwaysOnHotwordDetector =
+                createAlwaysOnHotwordDetector(/* useOnFailure= */ false);
         try {
             adoptShellPermissionIdentityForHotword();
 
@@ -335,7 +381,8 @@
     public void testHotwordDetectionService_onDetectFromDsp_success() throws Throwable {
         startWatchingNoted();
         // Create AlwaysOnHotwordDetector
-        AlwaysOnHotwordDetector alwaysOnHotwordDetector = createAlwaysOnHotwordDetector();
+        AlwaysOnHotwordDetector alwaysOnHotwordDetector =
+                createAlwaysOnHotwordDetector(/* useOnFailure= */ false);
         try {
             adoptShellPermissionIdentityForHotword();
 
@@ -358,7 +405,8 @@
     public void testHotwordDetectionService_onDetectFromDsp_rejection() throws Throwable {
         startWatchingNoted();
         // Create AlwaysOnHotwordDetector
-        AlwaysOnHotwordDetector alwaysOnHotwordDetector = createAlwaysOnHotwordDetector();
+        AlwaysOnHotwordDetector alwaysOnHotwordDetector =
+                createAlwaysOnHotwordDetector(/* useOnFailure= */ false);
         try {
             mService.initDetectRejectLatch();
             runWithShellPermissionIdentity(() -> {
@@ -392,7 +440,8 @@
     public void testHotwordDetectionService_onDetectFromDsp_timeout() throws Throwable {
         startWatchingNoted();
         // Create AlwaysOnHotwordDetector
-        AlwaysOnHotwordDetector alwaysOnHotwordDetector = createAlwaysOnHotwordDetector();
+        AlwaysOnHotwordDetector alwaysOnHotwordDetector =
+                createAlwaysOnHotwordDetector(/* useOnFailure= */ false);
         // Update HotwordDetectionService options to delay detection, to cause a timeout
         runWithShellPermissionIdentity(() -> {
             PersistableBundle options = Helper.createFakePersistableBundleData();
@@ -432,7 +481,8 @@
     public void testHotwordDetectionService_destroyDspDetector_activeDetectorRemoved()
             throws Throwable {
         // Create AlwaysOnHotwordDetector
-        AlwaysOnHotwordDetector alwaysOnHotwordDetector = createAlwaysOnHotwordDetector();
+        AlwaysOnHotwordDetector alwaysOnHotwordDetector =
+                createAlwaysOnHotwordDetector(/* useOnFailure= */ false);
         // destroy detector
         alwaysOnHotwordDetector.destroy();
         try {
@@ -459,7 +509,8 @@
     public void testHotwordDetectionService_onDetectFromExternalSource_success() throws Throwable {
         startWatchingNoted();
         // Create AlwaysOnHotwordDetector
-        AlwaysOnHotwordDetector alwaysOnHotwordDetector = createAlwaysOnHotwordDetector();
+        AlwaysOnHotwordDetector alwaysOnHotwordDetector =
+                createAlwaysOnHotwordDetector(/* useOnFailure= */ false);
         try {
             adoptShellPermissionIdentityForHotword();
 
@@ -617,7 +668,8 @@
                 Helper.isEnableMultipleDetectors());
 
         // Create AlwaysOnHotwordDetector
-        AlwaysOnHotwordDetector alwaysOnHotwordDetector = createAlwaysOnHotwordDetector();
+        AlwaysOnHotwordDetector alwaysOnHotwordDetector =
+                createAlwaysOnHotwordDetector(/* useOnFailure= */ false);
 
         // Create SoftwareHotwordDetector
         HotwordDetector softwareHotwordDetector = createSoftwareHotwordDetector();
@@ -644,7 +696,8 @@
     @Test
     public void testHotwordDetectionService_onHotwordDetectionServiceRestarted() throws Throwable {
         // Create AlwaysOnHotwordDetector
-        createAlwaysOnHotwordDetector();
+        AlwaysOnHotwordDetector alwaysOnHotwordDetector =
+                createAlwaysOnHotwordDetector(/* useOnFailure= */ false);
 
         mService.initOnHotwordDetectionServiceRestartedLatch();
         // force re-start by shell command
@@ -652,6 +705,9 @@
 
         // wait onHotwordDetectionServiceRestarted() called
         mService.waitOnHotwordDetectionServiceRestartedCalled();
+
+        // Destroy the always on detector
+        alwaysOnHotwordDetector.destroy();
     }
 
     private void verifySoftwareDetectorDetectSuccess(HotwordDetector softwareHotwordDetector)
@@ -687,9 +743,15 @@
     /**
      * Create AlwaysOnHotwordDetector and wait for ready
      */
-    private AlwaysOnHotwordDetector createAlwaysOnHotwordDetector() throws Throwable {
+    private AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(boolean useOnFailure)
+            throws Throwable {
         // Create AlwaysOnHotwordDetector and wait ready.
-        mService.createAlwaysOnHotwordDetector();
+        if (useOnFailure) {
+            mService.createAlwaysOnHotwordDetectorWithOnFailureCallback(/* useExecutor= */
+                    false, /* runOnMainThread= */ false);
+        } else {
+            mService.createAlwaysOnHotwordDetector();
+        }
 
         mService.waitSandboxedDetectionServiceInitializedCalledOrException();
 
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/services/CtsBasicVoiceInteractionService.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/services/CtsBasicVoiceInteractionService.java
index 7e67c58..72973fa 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/services/CtsBasicVoiceInteractionService.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/services/CtsBasicVoiceInteractionService.java
@@ -56,6 +56,8 @@
     private CountDownLatch mOnDetectRejectLatch;
     // The CountDownLatch waits for a service onError called
     private CountDownLatch mOnErrorLatch;
+    // The CountDownLatch waits for a service onFailure called
+    private CountDownLatch mOnFailureLatch;
     // The CountDownLatch waits for vqds
     private CountDownLatch mOnQueryFinishRejectLatch;
     // The CountDownLatch waits for a service onHotwordDetectionServiceRestarted called
@@ -65,7 +67,7 @@
     private HotwordRejectedResult mRejectedResult;
     private ArrayList<String> mStreamedQueries = new ArrayList<>();
     private String mCurrentQuery = "";
-
+    private DetectorFailure mDetectorFailure = null;
     public CtsBasicVoiceInteractionService() {
         HandlerThread handlerThread = new HandlerThread("CtsBasicVoiceInteractionService");
         handlerThread.start();
@@ -135,14 +137,18 @@
     }
 
     /**
-     * Create AlwaysOnHotwordDetector.
+     * Create an AlwaysOnHotwordDetector, but it will not implement the onFailure method of
+     * AlwaysOnHotwordDetector.Callback. It will implement the onFailure method by using
+     * createAlwaysOnHotwordDetectorWithOnFailureCallback method.
      */
     public void createAlwaysOnHotwordDetector() {
         createAlwaysOnHotwordDetector(/* useExecutor= */ false, /* runOnMainThread= */ false);
     }
 
     /**
-     * Create AlwaysOnHotwordDetector.
+     * Create an AlwaysOnHotwordDetector, but it will not implement the onFailure method of
+     * AlwaysOnHotwordDetector.Callback. It will implement the onFailure method by using
+     * createAlwaysOnHotwordDetectorWithOnFailureCallback method.
      */
     public void createAlwaysOnHotwordDetector(boolean useExecutor, boolean runOnMainThread) {
         Log.i(TAG, "createAlwaysOnHotwordDetector!!!!");
@@ -246,6 +252,96 @@
                 BIND_HOTWORD_DETECTION_SERVICE));
     }
 
+    /**
+     * Create an AlwaysOnHotwordDetector with onFailure callback. The onFailure provides the error
+     * code, error message and suggested action the assistant application should take.
+     */
+    public void createAlwaysOnHotwordDetectorWithOnFailureCallback(boolean useExecutor,
+            boolean runOnMainThread) {
+        Log.i(TAG, "createAlwaysOnHotwordDetectorWithOnFailureCallback");
+        mServiceTriggerLatch = new CountDownLatch(1);
+
+        final AlwaysOnHotwordDetector.Callback callback = new AlwaysOnHotwordDetector.Callback() {
+            @Override
+            public void onAvailabilityChanged(int status) {
+                Log.i(TAG, "onAvailabilityChanged(" + status + ")");
+                mAvailabilityStatus = status;
+                setIsDetectorCallbackRunningOnMainThread(isRunningOnMainThread());
+                if (mAvailabilityChangeLatch != null) {
+                    mAvailabilityChangeLatch.countDown();
+                }
+            }
+
+            @Override
+            public void onDetected(AlwaysOnHotwordDetector.EventPayload eventPayload) {
+                Log.i(TAG, "onDetected");
+                mDetectedResult = eventPayload;
+                setIsDetectorCallbackRunningOnMainThread(isRunningOnMainThread());
+                if (mOnDetectRejectLatch != null) {
+                    mOnDetectRejectLatch.countDown();
+                }
+            }
+
+            @Override
+            public void onRejected(@NonNull HotwordRejectedResult result) {
+                Log.i(TAG, "onRejected");
+                mRejectedResult = result;
+                setIsDetectorCallbackRunningOnMainThread(isRunningOnMainThread());
+                if (mOnDetectRejectLatch != null) {
+                    mOnDetectRejectLatch.countDown();
+                }
+            }
+
+            @Override
+            public void onError() {
+                Log.i(TAG, "onError");
+            }
+
+            @Override
+            public void onFailure(DetectorFailure detectorFailure) {
+                Log.i(TAG, "onFailure detectorFailure=" + detectorFailure);
+                mDetectorFailure = detectorFailure;
+                setIsDetectorCallbackRunningOnMainThread(isRunningOnMainThread());
+                if (mOnFailureLatch != null) {
+                    mOnFailureLatch.countDown();
+                }
+            }
+
+            @Override
+            public void onRecognitionPaused() {
+                Log.i(TAG, "onRecognitionPaused");
+            }
+
+            @Override
+            public void onRecognitionResumed() {
+                Log.i(TAG, "onRecognitionResumed");
+            }
+
+            @Override
+            public void onHotwordDetectionServiceInitialized(int status) {
+                Log.i(TAG, "onHotwordDetectionServiceInitialized status = " + status);
+                if (status != HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS) {
+                    return;
+                }
+                mInitializedStatus = status;
+                setIsDetectorCallbackRunningOnMainThread(isRunningOnMainThread());
+                if (mServiceTriggerLatch != null) {
+                    mServiceTriggerLatch.countDown();
+                }
+            }
+
+            @Override
+            public void onHotwordDetectionServiceRestarted() {
+                Log.i(TAG, "onHotwordDetectionServiceRestarted");
+            }
+        };
+
+        final Handler handler = runOnMainThread ? new Handler(Looper.getMainLooper()) : mHandler;
+        handler.post(() -> runWithShellPermissionIdentity(() -> {
+            mAlwaysOnHotwordDetector = callCreateAlwaysOnHotwordDetector(callback, useExecutor);
+        }, MANAGE_HOTWORD_DETECTION));
+    }
+
     public void createSoftwareHotwordDetector() {
         createSoftwareHotwordDetector(/* useExecutor= */ false, /* runOnMainThread= */ false);
     }
@@ -429,6 +525,13 @@
     }
 
     /**
+     * Create a CountDownLatch that is used to wait for onFailure()
+     */
+    public void initOnFailureLatch() {
+        mOnFailureLatch = new CountDownLatch(1);
+    }
+
+    /**
      * Returns the onDetected() result.
      */
     public AlwaysOnHotwordDetector.EventPayload getHotwordServiceOnDetectedResult() {
@@ -463,6 +566,13 @@
     }
 
     /**
+     * Returns the OnFailure() result.
+     */
+    public DetectorFailure getDetectorFailure() {
+        return mDetectorFailure;
+    }
+
+    /**
      * Wait for onAvailabilityChanged() callback called.
      */
     public void waitAvailabilityChangedCalled() throws InterruptedException {
@@ -520,4 +630,17 @@
         }
         mOnErrorLatch = null;
     }
+
+    /**
+     * Wait for onFailure() callback called.
+     */
+    public void waitOnFailureCalled() throws InterruptedException {
+        Log.d(TAG, "waitOnFailureCalled(), latch=" + mOnFailureLatch);
+        if (mOnFailureLatch == null
+                || !mOnFailureLatch.await(WAIT_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS)) {
+            mOnFailureLatch = null;
+            throw new AssertionError("OnFailure() fail.");
+        }
+        mOnFailureLatch = null;
+    }
 }