Loading core/java/android/service/voice/AbstractHotwordDetector.java +11 −0 Original line number Diff line number Diff line Loading @@ -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)); } } } core/java/android/service/voice/IMicrophoneHotwordDetectionVoiceInteractionCallback.aidl +7 −0 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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); } core/java/android/service/voice/SoftwareHotwordDetector.java +11 −0 Original line number Diff line number Diff line Loading @@ -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 Loading services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +73 −42 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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; } Loading @@ -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; Loading @@ -874,7 +882,8 @@ final class HotwordDetectionConnection { // TODO: handle cancellations well // TODO: what if we cancelled and started a new one? mRemoteHotwordDetectionService.run( service -> service.detectFromMicrophoneSource( service -> { service.detectFromMicrophoneSource( serviceAudioSource, // TODO: consider making a proxy callback + copy of audio format AUDIO_SOURCE_EXTERNAL, Loading @@ -884,41 +893,57 @@ final class HotwordDetectionConnection { @Override public void onRejected(HotwordRejectedResult result) throws RemoteException { bestEffortClose(serviceAudioSink); bestEffortClose(serviceAudioSource); bestEffortClose(audioSource); mScheduledExecutorService.schedule( () -> { bestEffortClose(serviceAudioSink, audioSource); }, EXTERNAL_HOTWORD_CLEANUP_MILLIS, TimeUnit.MILLISECONDS); if (mDebugHotwordLogging && result != null) { Slog.i(TAG, "Egressed rejected result: " + result); 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); } } // TODO: Propagate the HotwordRejectedResult. } @Override public void onDetected(HotwordDetectedResult triggerResult) throws RemoteException { bestEffortClose(serviceAudioSink); bestEffortClose(serviceAudioSource); mScheduledExecutorService.schedule( () -> { bestEffortClose(serviceAudioSink, audioSource); }, EXTERNAL_HOTWORD_CLEANUP_MILLIS, TimeUnit.MILLISECONDS); 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"); Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(triggerResult) + " bits from hotword trusted process"); 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 { Loading Loading @@ -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(); Loading Loading
core/java/android/service/voice/AbstractHotwordDetector.java +11 −0 Original line number Diff line number Diff line Loading @@ -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)); } } }
core/java/android/service/voice/IMicrophoneHotwordDetectionVoiceInteractionCallback.aidl +7 −0 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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); }
core/java/android/service/voice/SoftwareHotwordDetector.java +11 −0 Original line number Diff line number Diff line Loading @@ -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 Loading
services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +73 −42 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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; } Loading @@ -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; Loading @@ -874,7 +882,8 @@ final class HotwordDetectionConnection { // TODO: handle cancellations well // TODO: what if we cancelled and started a new one? mRemoteHotwordDetectionService.run( service -> service.detectFromMicrophoneSource( service -> { service.detectFromMicrophoneSource( serviceAudioSource, // TODO: consider making a proxy callback + copy of audio format AUDIO_SOURCE_EXTERNAL, Loading @@ -884,41 +893,57 @@ final class HotwordDetectionConnection { @Override public void onRejected(HotwordRejectedResult result) throws RemoteException { bestEffortClose(serviceAudioSink); bestEffortClose(serviceAudioSource); bestEffortClose(audioSource); mScheduledExecutorService.schedule( () -> { bestEffortClose(serviceAudioSink, audioSource); }, EXTERNAL_HOTWORD_CLEANUP_MILLIS, TimeUnit.MILLISECONDS); if (mDebugHotwordLogging && result != null) { Slog.i(TAG, "Egressed rejected result: " + result); 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); } } // TODO: Propagate the HotwordRejectedResult. } @Override public void onDetected(HotwordDetectedResult triggerResult) throws RemoteException { bestEffortClose(serviceAudioSink); bestEffortClose(serviceAudioSource); mScheduledExecutorService.schedule( () -> { bestEffortClose(serviceAudioSink, audioSource); }, EXTERNAL_HOTWORD_CLEANUP_MILLIS, TimeUnit.MILLISECONDS); 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"); Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(triggerResult) + " bits from hotword trusted process"); 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 { Loading Loading @@ -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(); Loading