diff options
| author | 2022-05-26 17:14:11 +0000 | |
|---|---|---|
| committer | 2022-05-26 17:14:11 +0000 | |
| commit | 90579f32b391a2d193870fa255bd2873cda35dac (patch) | |
| tree | 88e8965b8cccfecf2bc06b6f57ea63045b32736d | |
| parent | cb41788177bddacd969d9accd4b6e9b764a427a0 (diff) | |
| parent | be0edce59a33e30716059d5b2e02644b25b2d572 (diff) | |
Merge "Add support for onReject and onError for external hotword." into tm-dev
4 files changed, 102 insertions, 42 deletions
diff --git a/core/java/android/service/voice/AbstractHotwordDetector.java b/core/java/android/service/voice/AbstractHotwordDetector.java index b2bf9bc2ddd4..328750fe7780 100644 --- a/core/java/android/service/voice/AbstractHotwordDetector.java +++ b/core/java/android/service/voice/AbstractHotwordDetector.java @@ -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)); + } } } diff --git a/core/java/android/service/voice/IMicrophoneHotwordDetectionVoiceInteractionCallback.aidl b/core/java/android/service/voice/IMicrophoneHotwordDetectionVoiceInteractionCallback.aidl index e8650894ac14..61ac68be9775 100644 --- a/core/java/android/service/voice/IMicrophoneHotwordDetectionVoiceInteractionCallback.aidl +++ b/core/java/android/service/voice/IMicrophoneHotwordDetectionVoiceInteractionCallback.aidl @@ -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); } diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java index f5a0c66f7b1b..68fa130b0003 100644 --- a/core/java/android/service/voice/SoftwareHotwordDetector.java +++ b/core/java/android/service/voice/SoftwareHotwordDetector.java @@ -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 diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 39923a270c01..42d446d058b4 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -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,51 +882,68 @@ final class HotwordDetectionConnection { // TODO: handle cancellations well // TODO: what if we cancelled and started a new one? mRemoteHotwordDetectionService.run( - service -> service.detectFromMicrophoneSource( - serviceAudioSource, - // TODO: consider making a proxy callback + copy of audio format - AUDIO_SOURCE_EXTERNAL, - audioFormat, - options, - new IDspHotwordDetectionCallback.Stub() { - @Override - public void onRejected(HotwordRejectedResult result) - throws RemoteException { - bestEffortClose(serviceAudioSink); - bestEffortClose(serviceAudioSource); - bestEffortClose(audioSource); - - if (mDebugHotwordLogging && result != null) { - Slog.i(TAG, "Egressed rejected result: " + result); - } - // TODO: Propagate the HotwordRejectedResult. - } - - @Override - public void onDetected(HotwordDetectedResult triggerResult) - throws RemoteException { - bestEffortClose(serviceAudioSink); - bestEffortClose(serviceAudioSource); - try { - enforcePermissionsForDataDelivery(); - } catch (SecurityException e) { - bestEffortClose(audioSource); - callback.onError(); - return; + service -> { + service.detectFromMicrophoneSource( + serviceAudioSource, + // TODO: consider making a proxy callback + copy of audio format + AUDIO_SOURCE_EXTERNAL, + audioFormat, + options, + new IDspHotwordDetectionCallback.Stub() { + @Override + public void onRejected(HotwordRejectedResult result) + throws RemoteException { + mScheduledExecutorService.schedule( + () -> { + bestEffortClose(serviceAudioSink, audioSource); + }, + EXTERNAL_HOTWORD_CLEANUP_MILLIS, + TimeUnit.MILLISECONDS); + + 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); + } + } } - callback.onDetected(triggerResult, null /* audioFormat */, - null /* audioStream */); - if (triggerResult != null) { - Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize( - triggerResult) + " bits from hotword trusted process"); - if (mDebugHotwordLogging) { - Slog.i(TAG, "Egressed detected result: " + triggerResult); + + @Override + public void onDetected(HotwordDetectedResult triggerResult) + throws RemoteException { + mScheduledExecutorService.schedule( + () -> { + bestEffortClose(serviceAudioSink, audioSource); + }, + EXTERNAL_HOTWORD_CLEANUP_MILLIS, + TimeUnit.MILLISECONDS); + + try { + enforcePermissionsForDataDelivery(); + } catch (SecurityException e) { + 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"); + if (mDebugHotwordLogging) { + 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(); |