Merge "Add support for onReject and onError for external hotword." into tm-dev

This commit is contained in:
TreeHugger Robot
2022-05-26 17:14:11 +00:00
committed by Android (Google) Code Review
4 changed files with 101 additions and 41 deletions

View File

@@ -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));
}
} }
} }

View File

@@ -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);
} }

View File

@@ -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

View File

@@ -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,7 +882,8 @@ 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 -> {
service.detectFromMicrophoneSource(
serviceAudioSource, serviceAudioSource,
// TODO: consider making a proxy callback + copy of audio format // TODO: consider making a proxy callback + copy of audio format
AUDIO_SOURCE_EXTERNAL, AUDIO_SOURCE_EXTERNAL,
@@ -884,41 +893,57 @@ final class HotwordDetectionConnection {
@Override @Override
public void onRejected(HotwordRejectedResult result) public void onRejected(HotwordRejectedResult result)
throws RemoteException { throws RemoteException {
bestEffortClose(serviceAudioSink); mScheduledExecutorService.schedule(
bestEffortClose(serviceAudioSource); () -> {
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);
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 @Override
public void onDetected(HotwordDetectedResult triggerResult) public void onDetected(HotwordDetectedResult triggerResult)
throws RemoteException { throws RemoteException {
bestEffortClose(serviceAudioSink); mScheduledExecutorService.schedule(
bestEffortClose(serviceAudioSource); () -> {
bestEffortClose(serviceAudioSink, audioSource);
},
EXTERNAL_HOTWORD_CLEANUP_MILLIS,
TimeUnit.MILLISECONDS);
try { try {
enforcePermissionsForDataDelivery(); enforcePermissionsForDataDelivery();
} catch (SecurityException e) { } catch (SecurityException e) {
bestEffortClose(audioSource);
callback.onError(); callback.onError();
return; return;
} }
callback.onDetected(triggerResult, null /* audioFormat */, callback.onDetected(triggerResult, null /* audioFormat */,
null /* audioStream */); null /* audioStream */);
if (triggerResult != null) { if (triggerResult != null) {
Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize( Slog.i(TAG, "Egressed "
triggerResult) + " bits from hotword trusted process"); + HotwordDetectedResult.getUsageSize(triggerResult)
+ " bits from hotword trusted process");
if (mDebugHotwordLogging) { 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 { 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();