From c4d23a482d9e0d65ef146c2d68ff2ab416a5e47d Mon Sep 17 00:00:00 2001 From: Ytai Ben-Tsvi Date: Tue, 18 Aug 2020 14:53:41 -0700 Subject: [PATCH] Demote AlwaysOnHotwordDetector to SystemApi AlwaysOnHotwordDetector gets demoted from public to SystemApi and new permissions are enforced on its methods that imply usage of the microphone via sound trigger. Bug: 163865561 Change-Id: I113d69e569962b20d9d7dd1c6815daa6fb0650c5 --- api/current.txt | 46 ------------------- api/system-current.txt | 46 +++++++++++++++++++ .../voice/AlwaysOnHotwordDetector.java | 18 ++++++++ .../voice/VoiceInteractionService.java | 14 ++++-- non-updatable-api/current.txt | 46 ------------------- non-updatable-api/system-current.txt | 46 +++++++++++++++++++ 6 files changed, 120 insertions(+), 96 deletions(-) diff --git a/api/current.txt b/api/current.txt index 5f32dec22de9c..1ff3845d496c9 100644 --- a/api/current.txt +++ b/api/current.txt @@ -44242,54 +44242,8 @@ package android.service.textservice { package android.service.voice { - public class AlwaysOnHotwordDetector { - method public android.content.Intent createEnrollIntent(); - method public android.content.Intent createReEnrollIntent(); - method public android.content.Intent createUnEnrollIntent(); - method public int getParameter(int); - method public int getSupportedAudioCapabilities(); - method public int getSupportedRecognitionModes(); - method @Nullable public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int); - method public int setParameter(int, int); - method public boolean startRecognition(int); - method public boolean stopRecognition(); - field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1 - field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2 - field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0 - field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2 - field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1 - field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4 - field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8 - field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2 - field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1 - field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe - field public static final int STATE_KEYPHRASE_ENROLLED = 2; // 0x2 - field public static final int STATE_KEYPHRASE_UNENROLLED = 1; // 0x1 - field @Deprecated public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; // 0xffffffff - } - - public abstract static class AlwaysOnHotwordDetector.Callback { - ctor public AlwaysOnHotwordDetector.Callback(); - method public abstract void onAvailabilityChanged(int); - method public abstract void onDetected(@NonNull android.service.voice.AlwaysOnHotwordDetector.EventPayload); - method public abstract void onError(); - method public abstract void onRecognitionPaused(); - method public abstract void onRecognitionResumed(); - } - - public static class AlwaysOnHotwordDetector.EventPayload { - method @Nullable public android.media.AudioFormat getCaptureAudioFormat(); - method @Nullable public byte[] getTriggerAudio(); - } - - public static final class AlwaysOnHotwordDetector.ModelParamRange { - method public int getEnd(); - method public int getStart(); - } - public class VoiceInteractionService extends android.app.Service { ctor public VoiceInteractionService(); - method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback); method public int getDisabledShowContext(); method public static boolean isActiveService(android.content.Context, android.content.ComponentName); method public android.os.IBinder onBind(android.content.Intent); diff --git a/api/system-current.txt b/api/system-current.txt index b908cae38ca1e..77e4d3626376f 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -10249,7 +10249,53 @@ package android.service.trust { package android.service.voice { + public class AlwaysOnHotwordDetector { + method @Nullable public android.content.Intent createEnrollIntent(); + method @Nullable public android.content.Intent createReEnrollIntent(); + method @Nullable public android.content.Intent createUnEnrollIntent(); + method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int getParameter(int); + method public int getSupportedAudioCapabilities(); + method public int getSupportedRecognitionModes(); + method @Nullable @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int); + method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int setParameter(int, int); + method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition(int); + method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean stopRecognition(); + field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1 + field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2 + field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0 + field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2 + field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1 + field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4 + field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8 + field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2 + field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1 + field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe + field public static final int STATE_KEYPHRASE_ENROLLED = 2; // 0x2 + field public static final int STATE_KEYPHRASE_UNENROLLED = 1; // 0x1 + field @Deprecated public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; // 0xffffffff + } + + public abstract static class AlwaysOnHotwordDetector.Callback { + ctor public AlwaysOnHotwordDetector.Callback(); + method public abstract void onAvailabilityChanged(int); + method public abstract void onDetected(@NonNull android.service.voice.AlwaysOnHotwordDetector.EventPayload); + method public abstract void onError(); + method public abstract void onRecognitionPaused(); + method public abstract void onRecognitionResumed(); + } + + public static class AlwaysOnHotwordDetector.EventPayload { + method @Nullable public android.media.AudioFormat getCaptureAudioFormat(); + method @Nullable public byte[] getTriggerAudio(); + } + + public static final class AlwaysOnHotwordDetector.ModelParamRange { + method public int getEnd(); + method public int getStart(); + } + public class VoiceInteractionService extends android.app.Service { + method @NonNull public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback); method @NonNull @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public final android.media.voice.KeyphraseModelManager createKeyphraseModelManager(); } diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index b25053a049b78..8f8e6cc3d84a3 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -16,10 +16,15 @@ package android.service.voice; +import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; +import static android.Manifest.permission.RECORD_AUDIO; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.Intent; @@ -51,7 +56,12 @@ import java.util.Locale; /** * A class that lets a VoiceInteractionService implementation interact with * always-on keyphrase detection APIs. + * + * @hide + * TODO(b/168605867): Once Metalava supports expressing a removed public, but current system API, + * mark and track it as such. */ +@SystemApi public class AlwaysOnHotwordDetector { //---- States of Keyphrase availability. Return codes for onAvailabilityChanged() ----// /** @@ -525,6 +535,7 @@ public class AlwaysOnHotwordDetector { * This may happen if another detector has been instantiated or the * {@link VoiceInteractionService} hosting this detector has been shut down. */ + @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition(@RecognitionFlags int recognitionFlags) { if (DBG) Slog.d(TAG, "startRecognition(" + recognitionFlags + ")"); synchronized (mLock) { @@ -555,6 +566,7 @@ public class AlwaysOnHotwordDetector { * This may happen if another detector has been instantiated or the * {@link VoiceInteractionService} hosting this detector has been shut down. */ + @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) public boolean stopRecognition() { if (DBG) Slog.d(TAG, "stopRecognition()"); synchronized (mLock) { @@ -589,6 +601,7 @@ public class AlwaysOnHotwordDetector { * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or * if API is not supported by HAL */ + @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) public int setParameter(@ModelParams int modelParam, int value) { if (DBG) { Slog.d(TAG, "setParameter(" + modelParam + ", " + value + ")"); @@ -616,6 +629,7 @@ public class AlwaysOnHotwordDetector { * @param modelParam {@link ModelParams} * @return value of parameter */ + @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) public int getParameter(@ModelParams int modelParam) { if (DBG) { Slog.d(TAG, "getParameter(" + modelParam + ")"); @@ -640,6 +654,7 @@ public class AlwaysOnHotwordDetector { * @param modelParam {@link ModelParams} * @return supported range of parameter, null if not supported */ + @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) @Nullable public ModelParamRange queryParameter(@ModelParams int modelParam) { if (DBG) { @@ -670,6 +685,7 @@ public class AlwaysOnHotwordDetector { * This may happen if another detector has been instantiated or the * {@link VoiceInteractionService} hosting this detector has been shut down. */ + @Nullable public Intent createEnrollIntent() { if (DBG) Slog.d(TAG, "createEnrollIntent"); synchronized (mLock) { @@ -691,6 +707,7 @@ public class AlwaysOnHotwordDetector { * This may happen if another detector has been instantiated or the * {@link VoiceInteractionService} hosting this detector has been shut down. */ + @Nullable public Intent createUnEnrollIntent() { if (DBG) Slog.d(TAG, "createUnEnrollIntent"); synchronized (mLock) { @@ -712,6 +729,7 @@ public class AlwaysOnHotwordDetector { * This may happen if another detector has been instantiated or the * {@link VoiceInteractionService} hosting this detector has been shut down. */ + @Nullable public Intent createReEnrollIntent() { if (DBG) Slog.d(TAG, "createReEnrollIntent"); synchronized (mLock) { diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 45d3465fdae8a..fb03ed45113ee 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -20,6 +20,7 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.app.Service; import android.compat.annotation.UnsupportedAppUsage; @@ -237,9 +238,8 @@ public class VoiceInteractionService extends Service { /** * Called during service initialization to tell you when the system is ready * to receive interaction from it. You should generally do initialization here - * rather than in {@link #onCreate}. Methods such as {@link #showSession} and - * {@link #createAlwaysOnHotwordDetector} - * will not be operational until this point. + * rather than in {@link #onCreate}. Methods such as {@link #showSession} will + * not be operational until this point. */ public void onReady() { mSystemService = IVoiceInteractionManagerService.Stub.asInterface( @@ -309,9 +309,15 @@ public class VoiceInteractionService extends Service { * @param locale The locale for which the enrollment needs to be performed. * @param callback The callback to notify of detection events. * @return An always-on hotword detector for the given keyphrase and locale. + * + * @hide */ + @SystemApi + @NonNull public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector( - String keyphrase, Locale locale, AlwaysOnHotwordDetector.Callback callback) { + @SuppressLint("MissingNullability") String keyphrase, // TODO: annotate nullability properly + @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale, + @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { if (mSystemService == null) { throw new IllegalStateException("Not available until onReady() is called"); } diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt index bb40e24a68acc..8f60d5bba998f 100644 --- a/non-updatable-api/current.txt +++ b/non-updatable-api/current.txt @@ -42385,54 +42385,8 @@ package android.service.textservice { package android.service.voice { - public class AlwaysOnHotwordDetector { - method public android.content.Intent createEnrollIntent(); - method public android.content.Intent createReEnrollIntent(); - method public android.content.Intent createUnEnrollIntent(); - method public int getParameter(int); - method public int getSupportedAudioCapabilities(); - method public int getSupportedRecognitionModes(); - method @Nullable public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int); - method public int setParameter(int, int); - method public boolean startRecognition(int); - method public boolean stopRecognition(); - field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1 - field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2 - field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0 - field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2 - field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1 - field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4 - field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8 - field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2 - field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1 - field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe - field public static final int STATE_KEYPHRASE_ENROLLED = 2; // 0x2 - field public static final int STATE_KEYPHRASE_UNENROLLED = 1; // 0x1 - field @Deprecated public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; // 0xffffffff - } - - public abstract static class AlwaysOnHotwordDetector.Callback { - ctor public AlwaysOnHotwordDetector.Callback(); - method public abstract void onAvailabilityChanged(int); - method public abstract void onDetected(@NonNull android.service.voice.AlwaysOnHotwordDetector.EventPayload); - method public abstract void onError(); - method public abstract void onRecognitionPaused(); - method public abstract void onRecognitionResumed(); - } - - public static class AlwaysOnHotwordDetector.EventPayload { - method @Nullable public android.media.AudioFormat getCaptureAudioFormat(); - method @Nullable public byte[] getTriggerAudio(); - } - - public static final class AlwaysOnHotwordDetector.ModelParamRange { - method public int getEnd(); - method public int getStart(); - } - public class VoiceInteractionService extends android.app.Service { ctor public VoiceInteractionService(); - method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback); method public int getDisabledShowContext(); method public static boolean isActiveService(android.content.Context, android.content.ComponentName); method public android.os.IBinder onBind(android.content.Intent); diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt index b807d1be6bf00..147bc256a9767 100644 --- a/non-updatable-api/system-current.txt +++ b/non-updatable-api/system-current.txt @@ -9113,7 +9113,53 @@ package android.service.trust { package android.service.voice { + public class AlwaysOnHotwordDetector { + method @Nullable public android.content.Intent createEnrollIntent(); + method @Nullable public android.content.Intent createReEnrollIntent(); + method @Nullable public android.content.Intent createUnEnrollIntent(); + method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int getParameter(int); + method public int getSupportedAudioCapabilities(); + method public int getSupportedRecognitionModes(); + method @Nullable @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int); + method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int setParameter(int, int); + method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition(int); + method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean stopRecognition(); + field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1 + field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2 + field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0 + field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2 + field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1 + field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4 + field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8 + field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2 + field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1 + field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe + field public static final int STATE_KEYPHRASE_ENROLLED = 2; // 0x2 + field public static final int STATE_KEYPHRASE_UNENROLLED = 1; // 0x1 + field @Deprecated public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; // 0xffffffff + } + + public abstract static class AlwaysOnHotwordDetector.Callback { + ctor public AlwaysOnHotwordDetector.Callback(); + method public abstract void onAvailabilityChanged(int); + method public abstract void onDetected(@NonNull android.service.voice.AlwaysOnHotwordDetector.EventPayload); + method public abstract void onError(); + method public abstract void onRecognitionPaused(); + method public abstract void onRecognitionResumed(); + } + + public static class AlwaysOnHotwordDetector.EventPayload { + method @Nullable public android.media.AudioFormat getCaptureAudioFormat(); + method @Nullable public byte[] getTriggerAudio(); + } + + public static final class AlwaysOnHotwordDetector.ModelParamRange { + method public int getEnd(); + method public int getStart(); + } + public class VoiceInteractionService extends android.app.Service { + method @NonNull public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback); method @NonNull @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public final android.media.voice.KeyphraseModelManager createKeyphraseModelManager(); }