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,
|
HotwordDetector.Callback::onError,
|
||||||
mCallback));
|
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.media.AudioFormat;
|
||||||
import android.service.voice.HotwordDetectedResult;
|
import android.service.voice.HotwordDetectedResult;
|
||||||
|
import android.service.voice.HotwordRejectedResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback for returning the detected result from the HotwordDetectionService.
|
* 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.
|
* Called when the detection fails due to an error.
|
||||||
*/
|
*/
|
||||||
void onError();
|
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,
|
HotwordDetector.Callback::onError,
|
||||||
mCallback));
|
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
|
private static class InitializationStateListener
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ final class HotwordDetectionConnection {
|
|||||||
// TODO: These constants need to be refined.
|
// TODO: These constants need to be refined.
|
||||||
private static final long VALIDATION_TIMEOUT_MILLIS = 4000;
|
private static final long VALIDATION_TIMEOUT_MILLIS = 4000;
|
||||||
private static final long MAX_UPDATE_TIMEOUT_MILLIS = 30000;
|
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 =
|
private static final Duration MAX_UPDATE_TIMEOUT_DURATION =
|
||||||
Duration.ofMillis(MAX_UPDATE_TIMEOUT_MILLIS);
|
Duration.ofMillis(MAX_UPDATE_TIMEOUT_MILLIS);
|
||||||
private static final long RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour
|
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);
|
int bytesRead = source.read(buffer, 0, 1024);
|
||||||
|
|
||||||
if (bytesRead < 0) {
|
if (bytesRead < 0) {
|
||||||
|
Slog.i(TAG, "Reached end of stream for external hotword");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -864,6 +866,12 @@ final class HotwordDetectionConnection {
|
|||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Slog.w(TAG, "Failed supplying audio data to validator", 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 {
|
} finally {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
mCurrentAudioSink = null;
|
mCurrentAudioSink = null;
|
||||||
@@ -874,51 +882,68 @@ final class HotwordDetectionConnection {
|
|||||||
// TODO: handle cancellations well
|
// TODO: handle cancellations well
|
||||||
// TODO: what if we cancelled and started a new one?
|
// TODO: what if we cancelled and started a new one?
|
||||||
mRemoteHotwordDetectionService.run(
|
mRemoteHotwordDetectionService.run(
|
||||||
service -> service.detectFromMicrophoneSource(
|
service -> {
|
||||||
serviceAudioSource,
|
service.detectFromMicrophoneSource(
|
||||||
// TODO: consider making a proxy callback + copy of audio format
|
serviceAudioSource,
|
||||||
AUDIO_SOURCE_EXTERNAL,
|
// TODO: consider making a proxy callback + copy of audio format
|
||||||
audioFormat,
|
AUDIO_SOURCE_EXTERNAL,
|
||||||
options,
|
audioFormat,
|
||||||
new IDspHotwordDetectionCallback.Stub() {
|
options,
|
||||||
@Override
|
new IDspHotwordDetectionCallback.Stub() {
|
||||||
public void onRejected(HotwordRejectedResult result)
|
@Override
|
||||||
throws RemoteException {
|
public void onRejected(HotwordRejectedResult result)
|
||||||
bestEffortClose(serviceAudioSink);
|
throws RemoteException {
|
||||||
bestEffortClose(serviceAudioSource);
|
mScheduledExecutorService.schedule(
|
||||||
bestEffortClose(audioSource);
|
() -> {
|
||||||
|
bestEffortClose(serviceAudioSink, audioSource);
|
||||||
|
},
|
||||||
|
EXTERNAL_HOTWORD_CLEANUP_MILLIS,
|
||||||
|
TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
if (mDebugHotwordLogging && result != null) {
|
callback.onRejected(result);
|
||||||
Slog.i(TAG, "Egressed rejected result: " + result);
|
|
||||||
}
|
|
||||||
// TODO: Propagate the HotwordRejectedResult.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
if (result != null) {
|
||||||
public void onDetected(HotwordDetectedResult triggerResult)
|
Slog.i(TAG, "Egressed 'hotword rejected result' "
|
||||||
throws RemoteException {
|
+ "from hotword trusted process");
|
||||||
bestEffortClose(serviceAudioSink);
|
if (mDebugHotwordLogging) {
|
||||||
bestEffortClose(serviceAudioSource);
|
Slog.i(TAG, "Egressed detected result: " + result);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 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 {
|
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) {
|
private static void bestEffortClose(Closeable closeable) {
|
||||||
try {
|
try {
|
||||||
closeable.close();
|
closeable.close();
|
||||||
|
|||||||
Reference in New Issue
Block a user