summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author TreeHugger Robot <treehugger-gerrit@google.com> 2022-05-26 17:14:11 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2022-05-26 17:14:11 +0000
commit90579f32b391a2d193870fa255bd2873cda35dac (patch)
tree88e8965b8cccfecf2bc06b6f57ea63045b32736d
parentcb41788177bddacd969d9accd4b6e9b764a427a0 (diff)
parentbe0edce59a33e30716059d5b2e02644b25b2d572 (diff)
Merge "Add support for onReject and onError for external hotword." into tm-dev
-rw-r--r--core/java/android/service/voice/AbstractHotwordDetector.java11
-rw-r--r--core/java/android/service/voice/IMicrophoneHotwordDetectionVoiceInteractionCallback.aidl7
-rw-r--r--core/java/android/service/voice/SoftwareHotwordDetector.java11
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java115
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();