Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1564a67a authored by Mark Punzalan's avatar Mark Punzalan
Browse files

Enforce DSP detection timeout.

Also remove separate detectFromDspSourceForTest() method. It originally
was serving different results for testing, but now it is practically
identical to the non-test method. Removing it allows us to exercise the
same code path in CTS tests as in production.

Bug: 212744476
Test: atest CtsVoiceInteractionTestCases
Test: Verify hotword detection works on Pixel 6 Pro
Change-Id: Ibb0c4f49fbfa64ed6c7362d355466a480c5979e2
parent c58e4395
Loading
Loading
Loading
Loading
+32 −78
Original line number Diff line number Diff line
@@ -136,6 +136,7 @@ final class HotwordDetectionConnection {
    // The error codes are used for onError callback
    private static final int HOTWORD_DETECTION_SERVICE_DIED = -1;
    private static final int CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION = -2;
    private static final int CALLBACK_DETECT_TIMEOUT = -3;

    // Hotword metrics
    private static final int METRICS_INIT_UNKNOWN_TIMEOUT =
@@ -545,66 +546,7 @@ final class HotwordDetectionConnection {
        if (DEBUG) {
            Slog.d(TAG, "triggerHardwareRecognitionEventForTestLocked");
        }
        detectFromDspSourceForTest(event, callback);
    }

    private void detectFromDspSourceForTest(SoundTrigger.KeyphraseRecognitionEvent recognitionEvent,
            IHotwordRecognitionStatusCallback externalCallback) {
        Slog.v(TAG, "detectFromDspSourceForTest");
        IDspHotwordDetectionCallback internalCallback = new IDspHotwordDetectionCallback.Stub() {
            @Override
            public void onDetected(HotwordDetectedResult result) throws RemoteException {
                Slog.v(TAG, "onDetected");
                synchronized (mLock) {
                    if (!mValidatingDspTrigger) {
                        Slog.i(TAG, "Ignored hotword detected since trigger has been handled");
                        return;
                    }
                    mValidatingDspTrigger = false;
                    try {
                        enforcePermissionsForDataDelivery();
                        enforceExtraKeyphraseIdNotLeaked(result, recognitionEvent);
                    } catch (SecurityException e) {
                        externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION);
                        return;
                    }
                    externalCallback.onKeyphraseDetected(recognitionEvent, result);
                    if (result != null) {
                        Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
                                + " bits from hotword trusted process");
                        if (mDebugHotwordLogging) {
                            Slog.i(TAG, "Egressed detected result: " + result);
                        }
                    }
                }
            }

            @Override
            public void onRejected(HotwordRejectedResult result) throws RemoteException {
                Slog.v(TAG, "onRejected");
                synchronized (mLock) {
                    if (mValidatingDspTrigger) {
                        mValidatingDspTrigger = false;
                        externalCallback.onRejected(result);
                        if (mDebugHotwordLogging && result != null) {
                            Slog.i(TAG, "Egressed rejected result: " + result);
                        }
                    } else {
                        Slog.i(TAG, "Ignored hotword rejected since trigger has been handled");
                    }
                }
            }
        };

        synchronized (mLock) {
            mValidatingDspTrigger = true;
            mRemoteHotwordDetectionService.run(
                    service -> service.detectFromDspSource(
                            recognitionEvent,
                            recognitionEvent.getCaptureFormat(),
                            VALIDATION_TIMEOUT_MILLIS,
                            internalCallback));
        }
        detectFromDspSource(event, callback);
    }

    private void detectFromDspSource(SoundTrigger.KeyphraseRecognitionEvent recognitionEvent,
@@ -613,6 +555,7 @@ final class HotwordDetectionConnection {
            Slog.d(TAG, "detectFromDspSource");
        }

        AtomicBoolean timeoutDetected = new AtomicBoolean(false);
        // TODO: consider making this a non-anonymous class.
        IDspHotwordDetectionCallback internalCallback = new IDspHotwordDetectionCallback.Stub() {
            @Override
@@ -621,12 +564,12 @@ final class HotwordDetectionConnection {
                    Slog.d(TAG, "onDetected");
                }
                synchronized (mLock) {
                    // TODO: If the dsp trigger comes in after the timeout, we will log both events.
                    // Because we don't enforce the timeout yet. We should add some synchronizations
                    // within the runnable to prevent the race condition to log both events.
                    if (mCancellationKeyPhraseDetectionFuture != null) {
                        mCancellationKeyPhraseDetectionFuture.cancel(true);
                    }
                    if (timeoutDetected.get()) {
                        return;
                    }
                    HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                            mDetectorType,
                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED);
@@ -668,6 +611,9 @@ final class HotwordDetectionConnection {
                    if (mCancellationKeyPhraseDetectionFuture != null) {
                        mCancellationKeyPhraseDetectionFuture.cancel(true);
                    }
                    if (timeoutDetected.get()) {
                        return;
                    }
                    HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                            mDetectorType,
                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED);
@@ -689,13 +635,21 @@ final class HotwordDetectionConnection {

        synchronized (mLock) {
            mValidatingDspTrigger = true;
            mRemoteHotwordDetectionService.run(
                    service -> {
                        // TODO: avoid allocate every time
            mRemoteHotwordDetectionService.run(service -> {
                mCancellationKeyPhraseDetectionFuture = mScheduledExecutorService.schedule(
                                () -> HotwordMetricsLogger
                                        .writeKeyphraseTriggerEvent(mDetectorType,
                                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT),
                        () -> {
                            // TODO: avoid allocate every time
                            timeoutDetected.set(true);
                            Slog.w(TAG, "Timed out on #detectFromDspSource");
                            HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                                    mDetectorType,
                                    HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT);
                            try {
                                externalCallback.onError(CALLBACK_DETECT_TIMEOUT);
                            } catch (RemoteException e) {
                                Slog.w(TAG, "Failed to report onError status: ", e);
                            }
                        },
                        VALIDATION_TIMEOUT_MILLIS,
                        TimeUnit.MILLISECONDS);
                service.detectFromDspSource(