Revert "update HotwordDetector exception throws"

Revert submission 17454767-aohd-exception-update

Reason for revert: http://b/260387702 Incompatible API changes detected after metalava fix

Reverted changes: /q/submissionid:17454767-aohd-exception-update

Test: atest CtsVoiceInteractionHostTestCases
Test: atest CtsVoiceInteractionTestCases
Bug: 260387702
Change-Id: Idf7fce49937c1bf853c127c2587516dd89c0f591
This commit is contained in:
Nicholas Ambur
2023-02-23 21:15:19 +00:00
parent 4e7e638ee6
commit 2ed6c79405
7 changed files with 115 additions and 341 deletions

View File

@@ -12993,20 +12993,20 @@ package android.service.trust {
package android.service.voice {
public class AlwaysOnHotwordDetector implements android.service.voice.HotwordDetector {
method @Nullable public android.content.Intent createEnrollIntent() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
method @Nullable public android.content.Intent createReEnrollIntent() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
method @Nullable public android.content.Intent createUnEnrollIntent() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int getParameter(int) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
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() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
method @Nullable @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int setParameter(int, int) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition(int, @NonNull byte[]) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition(int) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
method public boolean startRecognition(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @Nullable android.os.PersistableBundle) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean stopRecognition() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
method public final void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
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, @NonNull byte[]);
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 startRecognition();
method public boolean startRecognition(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @Nullable android.os.PersistableBundle);
method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean stopRecognition();
method public final void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory);
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
@@ -13185,10 +13185,10 @@ package android.service.voice {
public interface HotwordDetector {
method public default void destroy();
method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
method public boolean startRecognition(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @Nullable android.os.PersistableBundle) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
method public boolean stopRecognition() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
method public void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition();
method public boolean startRecognition(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @Nullable android.os.PersistableBundle);
method public boolean stopRecognition();
method public void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory);
}
public static interface HotwordDetector.Callback {
@@ -13202,9 +13202,6 @@ package android.service.voice {
method public void onRejected(@NonNull android.service.voice.HotwordRejectedResult);
}
public static class HotwordDetector.IllegalDetectorStateException extends android.util.AndroidException {
}
public final class HotwordRejectedResult implements android.os.Parcelable {
method public int describeContents();
method public int getConfidenceLevel();
@@ -13272,9 +13269,9 @@ package android.service.voice {
public class VisualQueryDetector {
method public void destroy();
method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean startRecognition() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean stopRecognition() throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
method public void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory) throws android.service.voice.HotwordDetector.IllegalDetectorStateException;
method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean startRecognition();
method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean stopRecognition();
method public void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory);
}
public static interface VisualQueryDetector.Callback {

View File

@@ -20,7 +20,6 @@ import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
import android.app.compat.CompatChanges;
import android.media.AudioFormat;
import android.media.permission.Identity;
import android.os.Binder;
@@ -100,7 +99,7 @@ abstract class AbstractDetector implements HotwordDetector {
public boolean startRecognition(
@NonNull ParcelFileDescriptor audioStream,
@NonNull AudioFormat audioFormat,
@Nullable PersistableBundle options) throws IllegalDetectorStateException {
@Nullable PersistableBundle options) {
if (DEBUG) {
Slog.i(TAG, "#recognizeHotword");
}
@@ -132,18 +131,13 @@ abstract class AbstractDetector implements HotwordDetector {
* @param sharedMemory The unrestricted data blob to provide to the
* {@link VisualQueryDetectionService} and {@link HotwordDetectionService}. Use this to
* provide the hotword models data or other such data to the trusted process.
* @throws IllegalDetectorStateException Thrown when a caller has a target SDK of
* Android Tiramisu or above and attempts to start a recognition when the detector is
* not able based on the state. Because the caller receives updates via an asynchronous
* callback and the state of the detector can change without caller's knowledge, a
* checked exception is thrown.
* @throws IllegalStateException if this {@link HotwordDetector} wasn't specified to use a
* {@link HotwordDetectionService} or {@link VisualQueryDetectionService} when it was
* created.
*/
@Override
public void updateState(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory) throws IllegalDetectorStateException {
@Nullable SharedMemory sharedMemory) {
if (DEBUG) {
Slog.d(TAG, "updateState()");
}
@@ -199,13 +193,9 @@ abstract class AbstractDetector implements HotwordDetector {
}
}
protected void throwIfDetectorIsNoLongerActive() throws IllegalDetectorStateException {
protected void throwIfDetectorIsNoLongerActive() {
if (!mIsDetectorActive.get()) {
Slog.e(TAG, "attempting to use a destroyed detector which is no longer active");
if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
throw new IllegalDetectorStateException(
"attempting to use a destroyed detector which is no longer active");
}
throw new IllegalStateException(
"attempting to use a destroyed detector which is no longer active");
}

View File

@@ -827,28 +827,19 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
/**
* {@inheritDoc}
*
* @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
* or above and this AlwaysOnHotwordDetector wasn't specified to use a
* {@link HotwordDetectionService} when it was created. In addition, the exception can
* be thrown if this AlwaysOnHotwordDetector is in an invalid or error state.
* @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if
* this AlwaysOnHotwordDetector wasn't specified to use a
* {@link HotwordDetectionService} when it was created. In addition, the exception can
* be thrown if this AlwaysOnHotwordDetector is in an invalid or error state.
* @throws IllegalStateException if this AlwaysOnHotwordDetector wasn't specified to use a
* {@link HotwordDetectionService} when it was created. In addition, if this
* AlwaysOnHotwordDetector is in an invalid or error state.
*/
@Override
public final void updateState(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory) throws IllegalDetectorStateException {
@Nullable SharedMemory sharedMemory) {
synchronized (mLock) {
if (!mSupportSandboxedDetectionService) {
throw new IllegalStateException(
"updateState called, but it doesn't support hotword detection service");
}
if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
throw new IllegalDetectorStateException(
"updateState called on an invalid detector or error state");
}
throw new IllegalStateException(
"updateState called on an invalid detector or error state");
}
@@ -869,17 +860,16 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
@TestApi
public void overrideAvailability(int availability) {
synchronized (mLock) {
mAvailability = availability;
mIsAvailabilityOverriddenByTestApi = true;
// ENROLLED state requires there to be metadata about the sound model so a fake one
// is created.
if (mKeyphraseMetadata == null && availability == STATE_KEYPHRASE_ENROLLED) {
if (mKeyphraseMetadata == null && mAvailability == STATE_KEYPHRASE_ENROLLED) {
Set<Locale> fakeSupportedLocales = new HashSet<>();
fakeSupportedLocales.add(mLocale);
mKeyphraseMetadata = new KeyphraseMetadata(1, mText, fakeSupportedLocales,
AlwaysOnHotwordDetector.RECOGNITION_MODE_VOICE_TRIGGER);
}
mAvailability = availability;
mIsAvailabilityOverriddenByTestApi = true;
notifyStateChangedLocked();
}
}
@@ -935,23 +925,14 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
* @see #RECOGNITION_MODE_USER_IDENTIFICATION
* @see #RECOGNITION_MODE_VOICE_TRIGGER
*
* @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
* or above. Because the caller receives availability updates via an asynchronous
* callback, it may be due to the availability changing while this call is performed.
* - Throws if the detector is in an invalid or error state.
* This may happen if another detector has been instantiated or the
* {@link VoiceInteractionService} hosting this detector has been shut down.
* @throws UnsupportedOperationException Thrown when a caller has a target SDK below API level
* 33 Android if the recognition isn't supported. Callers should only call this method
* after a supported state callback on {@link Callback#onAvailabilityChanged(int)} to
* avoid this exception.
* @throws IllegalStateException Thrown when a caller has a target SDK below Android API level
* 33 if the detector is in an invalid or error state. This may happen if another
* detector has been instantiated or the {@link VoiceInteractionService} hosting this
* detector has been shut down.
* @throws UnsupportedOperationException if the keyphrase itself isn't supported.
* Callers should only call this method after a supported state callback on
* {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
* @throws IllegalStateException if the detector is in an invalid or error state.
* This may happen if another detector has been instantiated or the
* {@link VoiceInteractionService} hosting this detector has been shut down.
*/
public @RecognitionModes
int getSupportedRecognitionModes() throws IllegalDetectorStateException {
public @RecognitionModes int getSupportedRecognitionModes() {
if (DBG) Slog.d(TAG, "getSupportedRecognitionModes()");
synchronized (mLock) {
return getSupportedRecognitionModesLocked();
@@ -959,22 +940,14 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
}
@GuardedBy("mLock")
private int getSupportedRecognitionModesLocked() throws IllegalDetectorStateException {
private int getSupportedRecognitionModesLocked() {
if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
throw new IllegalDetectorStateException("getSupportedRecognitionModes called on an"
+ " invalid detector or error state");
}
throw new IllegalStateException(
"getSupportedRecognitionModes called on an invalid detector or error state");
}
// This method only makes sense if we can actually support a recognition.
if (mAvailability != STATE_KEYPHRASE_ENROLLED || mKeyphraseMetadata == null) {
if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
throw new IllegalDetectorStateException("Getting supported recognition modes for"
+ " the keyphrase is not supported");
}
throw new UnsupportedOperationException(
"Getting supported recognition modes for the keyphrase is not supported");
}
@@ -1029,30 +1002,15 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
* startRecognition request. This data is intended to provide additional parameters
* when starting the opaque sound model.
* @return Indicates whether the call succeeded or not.
* @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
* or above and attempts to start a recognition when the detector is not able based on
* the availability state. This can be thrown even if the state has been checked before
* calling this method because the caller receives availability updates via an
* asynchronous callback, it may be due to the availability changing while this call is
* performed.
* - Throws if the recognition isn't supported.
* Callers should only call this method after a supported state callback on
* {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
* - Also throws if the detector is in an invalid or error state.
* This may happen if another detector has been instantiated or the
* {@link VoiceInteractionService} hosting this detector has been shut down.
* @throws UnsupportedOperationException Thrown when a caller has a target SDK below API level
* 33 Android if the recognition isn't supported. Callers should only call this method
* after a supported state callback on {@link Callback#onAvailabilityChanged(int)} to
* avoid this exception.
* @throws IllegalStateException Thrown when a caller has a target SDK below Android API level
* 33 if the detector is in an invalid or error state. This may happen if another
* detector has been instantiated or the {@link VoiceInteractionService} hosting this
* detector has been shut down.
* @throws UnsupportedOperationException if the recognition isn't supported.
* Callers should only call this method after a supported state callback on
* {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
* @throws IllegalStateException if the detector is in an invalid or error state.
* 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, @NonNull byte[] data)
throws IllegalDetectorStateException {
public boolean startRecognition(@RecognitionFlags int recognitionFlags, @NonNull byte[] data) {
synchronized (mLock) {
return startRecognitionLocked(recognitionFlags, data)
== STATUS_OK;
@@ -1069,30 +1027,15 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
*
* @param recognitionFlags The flags to control the recognition properties.
* @return Indicates whether the call succeeded or not.
* @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
* or above and attempts to start a recognition when the detector is not able based on
* the availability state. This can be thrown even if the state has been checked before
* calling this method because the caller receives availability updates via an
* asynchronous callback, it may be due to the availability changing while this call is
* performed.
* - Throws if the recognition isn't supported.
* Callers should only call this method after a supported state callback on
* {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
* - Also throws if the detector is in an invalid or error state.
* This may happen if another detector has been instantiated or the
* {@link VoiceInteractionService} hosting this detector has been shut down.
* @throws UnsupportedOperationException Thrown when a caller has a target SDK below API level
* 33 if the recognition isn't supported. Callers should only call this method after a
* supported state callback on {@link Callback#onAvailabilityChanged(int)} to avoid this
* exception.
* @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if the
* detector is in an invalid or error state. This may happen if another detector has
* been instantiated or the {@link VoiceInteractionService} hosting this detector has
* been shut down.
* @throws UnsupportedOperationException if the recognition isn't supported.
* Callers should only call this method after a supported state callback on
* {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
* @throws IllegalStateException if the detector is in an invalid or error state.
* 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)
throws IllegalDetectorStateException {
public boolean startRecognition(@RecognitionFlags int recognitionFlags) {
if (DBG) Slog.d(TAG, "startRecognition(" + recognitionFlags + ")");
synchronized (mLock) {
return startRecognitionLocked(recognitionFlags, null /* data */) == STATUS_OK;
@@ -1106,8 +1049,7 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
*/
@RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
@Override
public boolean startRecognition()
throws IllegalDetectorStateException {
public boolean startRecognition() {
return startRecognition(0);
}
@@ -1117,44 +1059,28 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
* Settings.Secure.VOICE_INTERACTION_SERVICE.
*
* @return Indicates whether the call succeeded or not.
* @throws IllegalDetectorStateException Thrown when a caller has a target SDK of
* API level 33 or above and attempts to stop a recognition when the detector is
* not able based on the state. This can be thrown even if the state has been checked
* before calling this method because the caller receives availability updates via an
* asynchronous callback, it may be due to the availability changing while this call is
* performed.
* @throws UnsupportedOperationException Thrown when a caller has a target SDK below API level
* 33 if the recognition isn't supported. Callers should only call this method after a
* supported state callback on {@link Callback#onAvailabilityChanged(int)} to avoid this
* exception.
* @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if the
* detector is in an invalid or error state. This may happen if another detector has
* been instantiated or the {@link VoiceInteractionService} hosting this detector has
* been shut down.
* @throws UnsupportedOperationException if the recognition isn't supported.
* Callers should only call this method after a supported state callback on
* {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
* @throws IllegalStateException if the detector is in an invalid or error state.
* This may happen if another detector has been instantiated or the
* {@link VoiceInteractionService} hosting this detector has been shut down.
*/
// TODO: Remove this RequiresPermission since it isn't actually enforced. Also fix the javadoc
// about permissions enforcement (when it throws vs when it just returns false) for other
// methods in this class.
@RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
@Override
public boolean stopRecognition() throws IllegalDetectorStateException {
public boolean stopRecognition() {
if (DBG) Slog.d(TAG, "stopRecognition()");
synchronized (mLock) {
if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
throw new IllegalDetectorStateException(
"stopRecognition called on an invalid detector or error state");
}
throw new IllegalStateException(
"stopRecognition called on an invalid detector or error state");
}
// Check if we can start/stop a recognition.
if (mAvailability != STATE_KEYPHRASE_ENROLLED) {
if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
throw new IllegalDetectorStateException(
"Recognition for the given keyphrase is not supported");
}
throw new UnsupportedOperationException(
"Recognition for the given keyphrase is not supported");
}
@@ -1179,28 +1105,18 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
* - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
* if API is not supported by HAL
* @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
* if the detector is in an invalid or error state. This may happen if another detector
* has been instantiated or the {@link VoiceInteractionService} hosting this detector
* has been shut down.
* @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if the
* detector is in an invalid or error state. This may happen if another detector has
* been instantiated or the {@link VoiceInteractionService} hosting this detector has
* been shut down.
* @throws IllegalStateException if the detector is in an invalid or error state.
* 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 int setParameter(@ModelParams int modelParam, int value)
throws IllegalDetectorStateException {
public int setParameter(@ModelParams int modelParam, int value) {
if (DBG) {
Slog.d(TAG, "setParameter(" + modelParam + ", " + value + ")");
}
synchronized (mLock) {
if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
throw new IllegalDetectorStateException(
"setParameter called on an invalid detector or error state");
}
throw new IllegalStateException(
"setParameter called on an invalid detector or error state");
}
@@ -1221,27 +1137,18 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
*
* @param modelParam {@link ModelParams}
* @return value of parameter
* @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
* if the detector is in an invalid or error state. This may happen if another detector
* has been instantiated or the {@link VoiceInteractionService} hosting this detector
* has been shut down.
* @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if
* the detector is in an invalid or error state. This may happen if another detector has
* been instantiated or the {@link VoiceInteractionService} hosting this detector has
* been shut down.
* @throws IllegalStateException if the detector is in an invalid or error state.
* 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 int getParameter(@ModelParams int modelParam) throws IllegalDetectorStateException {
public int getParameter(@ModelParams int modelParam) {
if (DBG) {
Slog.d(TAG, "getParameter(" + modelParam + ")");
}
synchronized (mLock) {
if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
throw new IllegalDetectorStateException(
"getParameter called on an invalid detector or error state");
}
throw new IllegalStateException(
"getParameter called on an invalid detector or error state");
}
@@ -1259,29 +1166,19 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
*
* @param modelParam {@link ModelParams}
* @return supported range of parameter, null if not supported
* @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
* if the detector is in an invalid or error state. This may happen if another detector
* has been instantiated or the {@link VoiceInteractionService} hosting this detector
* has been shut down.
* @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if
* the detector is in an invalid or error state. This may happen if another detector has
* been instantiated or the {@link VoiceInteractionService} hosting this detector has
* been shut down.
* @throws IllegalStateException if the detector is in an invalid or error state.
* 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})
@Nullable
public ModelParamRange queryParameter(@ModelParams int modelParam)
throws IllegalDetectorStateException {
public ModelParamRange queryParameter(@ModelParams int modelParam) {
if (DBG) {
Slog.d(TAG, "queryParameter(" + modelParam + ")");
}
synchronized (mLock) {
if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
throw new IllegalDetectorStateException(
"queryParameter called on an invalid detector or error state");
}
throw new IllegalStateException(
"queryParameter called on an invalid detector or error state");
}
@@ -1298,25 +1195,15 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
* otherwise {@link #createReEnrollIntent()} should be preferred.
*
* @return An {@link Intent} to start enrollment for the given keyphrase.
* @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
* or above.
* - Thrown if managing they keyphrase isn't supported. Callers should only call this
* method after a supported state callback on
* {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
* - Thrown if the detector is in an invalid state. This may happen if another detector
* has been instantiated or the {@link VoiceInteractionService} hosting this detector
* has been shut down.
* @throws UnsupportedOperationException Thrown when a caller has a target SDK below API level
* 33 if managing they keyphrase isn't supported. Callers should only call this method
* after a supported state callback on {@link Callback#onAvailabilityChanged(int)} to
* avoid this exception.
* @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if the
* detector is in an invalid state. This may happen if another detector has been
* instantiated or the {@link VoiceInteractionService} hosting this detector has been
* shut down.
* @throws UnsupportedOperationException if managing they keyphrase isn't supported.
* Callers should only call this method after a supported state callback on
* {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
* @throws IllegalStateException if the detector is in an invalid state.
* This may happen if another detector has been instantiated or the
* {@link VoiceInteractionService} hosting this detector has been shut down.
*/
@Nullable
public Intent createEnrollIntent() throws IllegalDetectorStateException {
public Intent createEnrollIntent() {
if (DBG) Slog.d(TAG, "createEnrollIntent");
synchronized (mLock) {
return getManageIntentLocked(KeyphraseEnrollmentInfo.MANAGE_ACTION_ENROLL);
@@ -1330,25 +1217,15 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
* i.e. {@link #STATE_KEYPHRASE_ENROLLED}, otherwise invoking this may result in an error.
*
* @return An {@link Intent} to start un-enrollment for the given keyphrase.
* @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
* or above.
* - Thrown if managing they keyphrase isn't supported. Callers should only call this
* method after a supported state callback on
* {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
* - Thrown if the detector is in an invalid state. This may happen if another detector
* has been instantiated or the {@link VoiceInteractionService} hosting this detector
* has been shut down.
* @throws UnsupportedOperationException Thrown when a caller has a target SDK below API level
* 33 if managing they keyphrase isn't supported. Callers should only call this method
* after a supported state callback on {@link Callback#onAvailabilityChanged(int)} to
* avoid this exception.
* @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if the
* detector is in an invalid state. This may happen if another detector has been
* instantiated or the {@link VoiceInteractionService} hosting this detector has been
* shut down.
* @throws UnsupportedOperationException if managing they keyphrase isn't supported.
* Callers should only call this method after a supported state callback on
* {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
* @throws IllegalStateException if the detector is in an invalid state.
* This may happen if another detector has been instantiated or the
* {@link VoiceInteractionService} hosting this detector has been shut down.
*/
@Nullable
public Intent createUnEnrollIntent() throws IllegalDetectorStateException {
public Intent createUnEnrollIntent() {
if (DBG) Slog.d(TAG, "createUnEnrollIntent");
synchronized (mLock) {
return getManageIntentLocked(KeyphraseEnrollmentInfo.MANAGE_ACTION_UN_ENROLL);
@@ -1362,25 +1239,15 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
* i.e. {@link #STATE_KEYPHRASE_ENROLLED}, otherwise invoking this may result in an error.
*
* @return An {@link Intent} to start re-enrollment for the given keyphrase.
* @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
* or above.
* - Thrown if managing they keyphrase isn't supported. Callers should only call this
* method after a supported state callback on
* {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
* - Thrown if the detector is in an invalid state. This may happen if another detector
* has been instantiated or the {@link VoiceInteractionService} hosting this detector
* has been shut down.
* @throws UnsupportedOperationException Thrown when a caller has a target SDK below API level
* 33 if managing they keyphrase isn't supported. Callers should only call this method
* after a supported state callback on {@link Callback#onAvailabilityChanged(int)} to
* avoid this exception.
* @throws IllegalStateException Thrown when a caller has a target SDK below API level 33 if the
* detector is in an invalid state. This may happen if another detector has been
* instantiated or the {@link VoiceInteractionService} hosting this detector has been
* shut down.
* @throws UnsupportedOperationException if managing they keyphrase isn't supported.
* Callers should only call this method after a supported state callback on
* {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
* @throws IllegalStateException if the detector is in an invalid or error state.
* This may happen if another detector has been instantiated or the
* {@link VoiceInteractionService} hosting this detector has been shut down.
*/
@Nullable
public Intent createReEnrollIntent() throws IllegalDetectorStateException {
public Intent createReEnrollIntent() {
if (DBG) Slog.d(TAG, "createReEnrollIntent");
synchronized (mLock) {
return getManageIntentLocked(KeyphraseEnrollmentInfo.MANAGE_ACTION_RE_ENROLL);
@@ -1388,24 +1255,15 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
}
@GuardedBy("mLock")
private Intent getManageIntentLocked(@KeyphraseEnrollmentInfo.ManageActions int action)
throws IllegalDetectorStateException {
private Intent getManageIntentLocked(@KeyphraseEnrollmentInfo.ManageActions int action) {
if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
throw new IllegalDetectorStateException(
"getManageIntent called on an invalid detector or error state");
}
throw new IllegalStateException(
"getManageIntent called on an invalid detector or error state");
"getManageIntent called on an invalid detector or error state");
}
// This method only makes sense if we can actually support a recognition.
if (mAvailability != STATE_KEYPHRASE_ENROLLED
&& mAvailability != STATE_KEYPHRASE_UNENROLLED) {
if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
throw new IllegalDetectorStateException(
"Managing the given keyphrase is not supported");
}
throw new UnsupportedOperationException(
"Managing the given keyphrase is not supported");
}
@@ -1489,27 +1347,19 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
@GuardedBy("mLock")
private int startRecognitionLocked(int recognitionFlags,
@Nullable byte[] data) throws IllegalDetectorStateException {
@Nullable byte[] data) {
if (DBG) {
Slog.d(TAG, "startRecognition("
+ recognitionFlags
+ ", " + Arrays.toString(data) + ")");
}
if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
throw new IllegalDetectorStateException(
"startRecognition called on an invalid detector or error state");
}
throw new IllegalStateException(
"startRecognition called on an invalid detector or error state");
}
// Check if we can start/stop a recognition.
if (mAvailability != STATE_KEYPHRASE_ENROLLED) {
if (CompatChanges.isChangeEnabled(HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION)) {
throw new IllegalDetectorStateException(
"Recognition for the given keyphrase is not supported");
}
throw new UnsupportedOperationException(
"Recognition for the given keyphrase is not supported");
}

View File

@@ -23,14 +23,10 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.media.AudioFormat;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.SharedMemory;
import android.util.AndroidException;
import java.io.PrintWriter;
@@ -43,23 +39,6 @@ import java.io.PrintWriter;
@SystemApi
public interface HotwordDetector {
/**
* Prior to API level 33, API calls of {@link android.service.voice.HotwordDetector} could
* return both {@link java.lang.IllegalStateException} or
* {@link java.lang.UnsupportedOperationException} depending on the detector's underlying state.
* This lead to confusing behavior as the underlying state of the detector can be modified
* without the knowledge of the caller via system service layer updates.
*
* This change ID, when enabled, changes the API calls to only throw checked exception
* {@link android.service.voice.HotwordDetector.IllegalDetectorStateException} when checking
* against state information modified by both the caller and the system services.
*
* @hide
*/
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
long HOTWORD_DETECTOR_THROW_CHECKED_EXCEPTION = 226355112L;
/**
* Indicates that it is a non-trusted hotword detector.
*
@@ -109,26 +88,16 @@ public interface HotwordDetector {
* Calling this again while recognition is active does nothing.
*
* @return {@code true} if the request to start recognition succeeded
* @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
* or above and attempts to start a recognition when the detector is not able based on
* the state. This can be thrown even if the state has been checked before calling this
* method because the caller receives updates via an asynchronous callback, and the
* state of the detector can change concurrently to the caller calling this method.
*/
@RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
boolean startRecognition() throws IllegalDetectorStateException;
boolean startRecognition();
/**
* Stops sandboxed detection recognition.
*
* @return {@code true} if the request to stop recognition succeeded
* @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
* or above and attempts to stop a recognition when the detector is not able based on
* the state. This can be thrown even if the state has been checked before calling this
* method because the caller receives updates via an asynchronous callback, and the
* state of the detector can change concurrently to the caller calling this method.
*/
boolean stopRecognition() throws IllegalDetectorStateException;
boolean stopRecognition();
/**
* Starts hotword recognition on audio coming from an external connected microphone.
@@ -142,16 +111,11 @@ public interface HotwordDetector {
* PersistableBundle does not allow any remotable objects or other contents that can be
* used to communicate with other processes.
* @return {@code true} if the request to start recognition succeeded
* @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
* or above and attempts to start a recognition when the detector is not able based on
* the state. This can be thrown even if the state has been checked before calling this
* method because the caller receives updates via an asynchronous callback, and the
* state of the detector can change concurrently to the caller calling this method.
*/
boolean startRecognition(
@NonNull ParcelFileDescriptor audioStream,
@NonNull AudioFormat audioFormat,
@Nullable PersistableBundle options) throws IllegalDetectorStateException;
@Nullable PersistableBundle options);
/**
* Set configuration and pass read-only data to sandboxed detection service.
@@ -161,17 +125,10 @@ public interface HotwordDetector {
* communicate with other processes.
* @param sharedMemory The unrestricted data blob to provide to sandboxed detection services.
* Use this to provide model data or other such data to the trusted process.
* @throws IllegalDetectorStateException Thrown when a caller has a target SDK of API level 33
* or above and the detector is not able to perform the operation based on the
* underlying state. This can be thrown even if the state has been checked before
* calling this method because the caller receives updates via an asynchronous callback,
* and the state of the detector can change concurrently to the caller calling this
* method.
* @throws IllegalStateException if this HotwordDetector wasn't specified to use a
* sandboxed detection service when it was created.
*/
void updateState(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory)
throws IllegalDetectorStateException;
void updateState(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory);
/**
* Invalidates this detector so that any future calls to this result
@@ -298,14 +255,4 @@ public interface HotwordDetector {
*/
void onHotwordDetectionServiceRestarted();
}
/**
* {@link HotwordDetector} specific exception thrown when the underlying state of the detector
* is invalid for the given action.
*/
class IllegalDetectorStateException extends AndroidException {
IllegalDetectorStateException(String message) {
super(message);
}
}
}

View File

@@ -86,7 +86,7 @@ class SoftwareHotwordDetector extends AbstractDetector {
@RequiresPermission(RECORD_AUDIO)
@Override
public boolean startRecognition() throws IllegalDetectorStateException {
public boolean startRecognition() {
if (DEBUG) {
Slog.i(TAG, "#startRecognition");
}
@@ -109,7 +109,7 @@ class SoftwareHotwordDetector extends AbstractDetector {
/** TODO: stopRecognition */
@RequiresPermission(RECORD_AUDIO)
@Override
public boolean stopRecognition() throws IllegalDetectorStateException {
public boolean stopRecognition() {
if (DEBUG) {
Slog.i(TAG, "#stopRecognition");
}

View File

@@ -84,8 +84,7 @@ public class VisualQueryDetector {
* @see HotwordDetector#updateState(PersistableBundle, SharedMemory)
*/
public void updateState(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory) throws
HotwordDetector.IllegalDetectorStateException {
@Nullable SharedMemory sharedMemory) {
mInitializationDelegate.updateState(options, sharedMemory);
}
@@ -104,7 +103,7 @@ public class VisualQueryDetector {
* @see HotwordDetector#startRecognition()
*/
@RequiresPermission(allOf = {CAMERA, RECORD_AUDIO})
public boolean startRecognition() throws HotwordDetector.IllegalDetectorStateException {
public boolean startRecognition() {
if (DEBUG) {
Slog.i(TAG, "#startRecognition");
}
@@ -128,7 +127,7 @@ public class VisualQueryDetector {
* @see HotwordDetector#stopRecognition()
*/
@RequiresPermission(allOf = {CAMERA, RECORD_AUDIO})
public boolean stopRecognition() throws HotwordDetector.IllegalDetectorStateException {
public boolean stopRecognition() {
if (DEBUG) {
Slog.i(TAG, "#stopRecognition");
}
@@ -236,13 +235,13 @@ public class VisualQueryDetector {
}
@Override
public boolean stopRecognition() throws IllegalDetectorStateException {
public boolean stopRecognition() {
throwIfDetectorIsNoLongerActive();
return true;
}
@Override
public boolean startRecognition() throws IllegalDetectorStateException {
public boolean startRecognition() {
throwIfDetectorIsNoLongerActive();
return true;
}
@@ -251,7 +250,7 @@ public class VisualQueryDetector {
public final boolean startRecognition(
@NonNull ParcelFileDescriptor audioStream,
@NonNull AudioFormat audioFormat,
@Nullable PersistableBundle options) throws IllegalDetectorStateException {
@Nullable PersistableBundle options) {
//No-op, not supported by VisualQueryDetector as it should be trusted.
return false;
}

View File

@@ -20,7 +20,6 @@ import android.content.Intent;
import android.service.voice.AlwaysOnHotwordDetector;
import android.service.voice.AlwaysOnHotwordDetector.Callback;
import android.service.voice.AlwaysOnHotwordDetector.EventPayload;
import android.service.voice.HotwordDetector;
import android.service.voice.VoiceInteractionService;
import android.util.Log;
@@ -84,24 +83,16 @@ public class MainInteractionService extends VoiceInteractionService {
break;
case AlwaysOnHotwordDetector.STATE_KEYPHRASE_UNENROLLED:
Log.i(TAG, "STATE_KEYPHRASE_UNENROLLED");
try {
Intent enroll = mHotwordDetector.createEnrollIntent();
Log.i(TAG, "Need to enroll with " + enroll);
} catch (HotwordDetector.IllegalDetectorStateException e) {
Log.e(TAG, "createEnrollIntent failed", e);
}
Intent enroll = mHotwordDetector.createEnrollIntent();
Log.i(TAG, "Need to enroll with " + enroll);
break;
case AlwaysOnHotwordDetector.STATE_KEYPHRASE_ENROLLED:
Log.i(TAG, "STATE_KEYPHRASE_ENROLLED - starting recognition");
try {
if (mHotwordDetector.startRecognition(
AlwaysOnHotwordDetector.RECOGNITION_FLAG_NONE)) {
Log.i(TAG, "startRecognition succeeded");
} else {
Log.i(TAG, "startRecognition failed");
}
} catch (HotwordDetector.IllegalDetectorStateException e) {
Log.e(TAG, "startRecognition failed", e);
if (mHotwordDetector.startRecognition(
AlwaysOnHotwordDetector.RECOGNITION_FLAG_NONE)) {
Log.i(TAG, "startRecognition succeeded");
} else {
Log.i(TAG, "startRecognition failed");
}
break;
}