Merge "Add support for onReject and onError for external hotword." into tm-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
90579f32b3
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
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);
|
||||
|
||||
if (mDebugHotwordLogging && result != null) {
|
||||
Slog.i(TAG, "Egressed rejected result: " + result);
|
||||
}
|
||||
// TODO: Propagate the HotwordRejectedResult.
|
||||
}
|
||||
callback.onRejected(result);
|
||||
|
||||
@Override
|
||||
public void onDetected(HotwordDetectedResult triggerResult)
|
||||
throws RemoteException {
|
||||
bestEffortClose(serviceAudioSink);
|
||||
bestEffortClose(serviceAudioSource);
|
||||
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");
|
||||
if (mDebugHotwordLogging) {
|
||||
Slog.i(TAG, "Egressed detected result: " + triggerResult);
|
||||
if (result != null) {
|
||||
Slog.i(TAG, "Egressed 'hotword rejected result' "
|
||||
+ "from hotword trusted process");
|
||||
if (mDebugHotwordLogging) {
|
||||
Slog.i(TAG, "Egressed detected result: " + result);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: Add a delay before closing.
|
||||
bestEffortClose(audioSource);
|
||||
}
|
||||
}));
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 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();
|
||||
|
||||
Reference in New Issue
Block a user