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

Commit 5ec4c563 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Add support for onReject and onError for external hotword." into tm-dev am: 90579f32

parents 032ed256 90579f32
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -198,5 +198,16 @@ abstract class AbstractHotwordDetector implements HotwordDetector {
                    HotwordDetector.Callback::onError,
                    mCallback));
        }

        @Override
        public void onRejected(@Nullable HotwordRejectedResult result) {
            if (result == null) {
                result = new HotwordRejectedResult.Builder().build();
            }
            mHandler.sendMessage(obtainMessage(
                    HotwordDetector.Callback::onRejected,
                    mCallback,
                    result));
        }
    }
}
+7 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.service.voice;

import android.media.AudioFormat;
import android.service.voice.HotwordDetectedResult;
import android.service.voice.HotwordRejectedResult;

/**
 * Callback for returning the detected result from the HotwordDetectionService.
@@ -38,4 +39,10 @@ oneway interface IMicrophoneHotwordDetectionVoiceInteractionCallback {
     * Called when the detection fails due to an error.
     */
    void onError();

    /**
     * Called when the detected result was not detected.
     */
    void onRejected(
        in HotwordRejectedResult hotwordRejectedResult);
}
+11 −0
Original line number Diff line number Diff line
@@ -164,6 +164,17 @@ class SoftwareHotwordDetector extends AbstractHotwordDetector {
                    HotwordDetector.Callback::onError,
                    mCallback));
        }

        @Override
        public void onRejected(@Nullable HotwordRejectedResult result) {
            if (result == null) {
                result = new HotwordRejectedResult.Builder().build();
            }
            mHandler.sendMessage(obtainMessage(
                    HotwordDetector.Callback::onRejected,
                    mCallback,
                    result));
        }
    }

    private static class InitializationStateListener
+73 −42
Original line number Diff line number Diff line
@@ -119,6 +119,7 @@ final class HotwordDetectionConnection {
    // TODO: These constants need to be refined.
    private static final long VALIDATION_TIMEOUT_MILLIS = 4000;
    private static final long MAX_UPDATE_TIMEOUT_MILLIS = 30000;
    private static final long EXTERNAL_HOTWORD_CLEANUP_MILLIS = 2000;
    private static final Duration MAX_UPDATE_TIMEOUT_DURATION =
            Duration.ofMillis(MAX_UPDATE_TIMEOUT_MILLIS);
    private static final long RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour
@@ -854,6 +855,7 @@ final class HotwordDetectionConnection {
                    int bytesRead = source.read(buffer, 0, 1024);

                    if (bytesRead < 0) {
                        Slog.i(TAG, "Reached end of stream for external hotword");
                        break;
                    }

@@ -864,6 +866,12 @@ final class HotwordDetectionConnection {
                }
            } catch (IOException e) {
                Slog.w(TAG, "Failed supplying audio data to validator", e);

                try {
                    callback.onError();
                } catch (RemoteException ex) {
                    Slog.w(TAG, "Failed to report onError status: " + ex);
                }
            } finally {
                synchronized (mLock) {
                    mCurrentAudioSink = null;
@@ -874,7 +882,8 @@ final class HotwordDetectionConnection {
        // TODO: handle cancellations well
        // TODO: what if we cancelled and started a new one?
        mRemoteHotwordDetectionService.run(
                service -> service.detectFromMicrophoneSource(
                service -> {
                    service.detectFromMicrophoneSource(
                            serviceAudioSource,
                            // TODO: consider making a proxy callback + copy of audio format
                            AUDIO_SOURCE_EXTERNAL,
@@ -884,41 +893,57 @@ final class HotwordDetectionConnection {
                                @Override
                                public void onRejected(HotwordRejectedResult result)
                                        throws RemoteException {
                                bestEffortClose(serviceAudioSink);
                                bestEffortClose(serviceAudioSource);
                                bestEffortClose(audioSource);
                                    mScheduledExecutorService.schedule(
                                            () -> {
                                                bestEffortClose(serviceAudioSink, audioSource);
                                            },
                                            EXTERNAL_HOTWORD_CLEANUP_MILLIS,
                                            TimeUnit.MILLISECONDS);

                                if (mDebugHotwordLogging && result != null) {
                                    Slog.i(TAG, "Egressed rejected result: " + result);
                                    callback.onRejected(result);

                                    if (result != null) {
                                        Slog.i(TAG, "Egressed 'hotword rejected result' "
                                                + "from hotword trusted process");
                                        if (mDebugHotwordLogging) {
                                            Slog.i(TAG, "Egressed detected result: " + result);
                                        }
                                    }
                                // TODO: Propagate the HotwordRejectedResult.
                                }

                                @Override
                                public void onDetected(HotwordDetectedResult triggerResult)
                                        throws RemoteException {
                                bestEffortClose(serviceAudioSink);
                                bestEffortClose(serviceAudioSource);
                                    mScheduledExecutorService.schedule(
                                            () -> {
                                                bestEffortClose(serviceAudioSink, audioSource);
                                            },
                                            EXTERNAL_HOTWORD_CLEANUP_MILLIS,
                                            TimeUnit.MILLISECONDS);

                                    try {
                                        enforcePermissionsForDataDelivery();
                                    } catch (SecurityException e) {
                                    bestEffortClose(audioSource);
                                        callback.onError();
                                        return;
                                    }
                                    callback.onDetected(triggerResult, null /* audioFormat */,
                                            null /* audioStream */);
                                    if (triggerResult != null) {
                                    Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(
                                            triggerResult) + " bits from hotword trusted process");
                                        Slog.i(TAG, "Egressed "
                                                + HotwordDetectedResult.getUsageSize(triggerResult)
                                                + " bits from hotword trusted process");
                                        if (mDebugHotwordLogging) {
                                        Slog.i(TAG, "Egressed detected result: " + triggerResult);
                                            Slog.i(TAG,
                                                    "Egressed detected result: " + triggerResult);
                                        }
                                    }
                                // TODO: Add a delay before closing.
                                bestEffortClose(audioSource);
                                }
                        }));
                            });

                    // A copy of this has been created and passed to the hotword validator
                    bestEffortClose(serviceAudioSource);
                });
    }

    private class ServiceConnectionFactory {
@@ -1118,6 +1143,12 @@ final class HotwordDetectionConnection {
        });
    }

    private static void bestEffortClose(Closeable... closeables) {
        for (Closeable closeable : closeables) {
            bestEffortClose(closeable);
        }
    }

    private static void bestEffortClose(Closeable closeable) {
        try {
            closeable.close();