Loading core/java/android/service/voice/AlwaysOnHotwordDetector.java +47 −11 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.service.voice; import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; import static android.Manifest.permission.RECORD_AUDIO; import static android.service.voice.SoundTriggerFailure.ERROR_CODE_UNKNOWN; import static android.service.voice.VoiceInteractionService.MULTIPLE_ACTIVE_HOTWORD_DETECTORS; import android.annotation.ElapsedRealtimeLong; Loading Loading @@ -269,6 +270,15 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) static final long THROW_ON_INITIALIZE_IF_NO_DSP = 269165460L; /** * Gates returning {@link Callback#onFailure} and {@link Callback#onUnknownFailure} * when asynchronous exceptions are propagated to the client. If the change is not enabled, * the existing behavior of delivering {@link #STATE_ERROR} is retained. */ @ChangeId @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) static final long SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS = 280471513L; /** * Controls the sensitivity threshold adjustment factor for a given model. * Negative value corresponds to less sensitive model (high threshold) and Loading Loading @@ -1409,12 +1419,16 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { if (mAvailability == STATE_KEYPHRASE_ENROLLED) { try { stopRecognitionLocked(); } catch (SecurityException e) { Slog.w(TAG, "Failed to Stop the recognition", e); if (mTargetSdkVersion <= Build.VERSION_CODES.R) { throw e; } } catch (Exception e) { Slog.w(TAG, "Failed to stop recognition after enrollment update", e); if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) { sendSoundTriggerFailure(new SoundTriggerFailure(ERROR_CODE_UNKNOWN, "Failed to stop recognition after enrollment update: " + Log.getStackTraceString(e), FailureSuggestedAction.RECREATE_DETECTOR)); } else { updateAndNotifyStateChangedLocked(STATE_ERROR); } return; } } Loading Loading @@ -1538,6 +1552,12 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { @GuardedBy("mLock") private void updateAndNotifyStateChangedLocked(int availability) { updateAvailabilityLocked(availability); notifyStateChangedLocked(); } @GuardedBy("mLock") private void updateAvailabilityLocked(int availability) { if (DBG) { Slog.d(TAG, "Hotword availability changed from " + mAvailability + " -> " + availability); Loading @@ -1545,7 +1565,6 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { if (!mIsAvailabilityOverriddenByTestApi) { mAvailability = availability; } notifyStateChangedLocked(); } @GuardedBy("mLock") Loading @@ -1555,6 +1574,18 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { message.sendToTarget(); } @GuardedBy("mLock") private void sendUnknownFailure(String failureMessage) { // update but do not call onAvailabilityChanged callback for STATE_ERROR updateAvailabilityLocked(STATE_ERROR); Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE, failureMessage).sendToTarget(); } private void sendSoundTriggerFailure(@NonNull SoundTriggerFailure soundTriggerFailure) { Message.obtain(mHandler, MSG_DETECTION_SOUND_TRIGGER_FAILURE, soundTriggerFailure) .sendToTarget(); } /** @hide */ static final class SoundTriggerListener extends IHotwordRecognitionStatusCallback.Stub { private final Handler mHandler; Loading Loading @@ -1726,6 +1757,7 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { } } // TODO(b/267681692): remove the AsyncTask usage class RefreshAvailabilityTask extends AsyncTask<Void, Void, Void> { @Override Loading @@ -1744,15 +1776,19 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { } updateAndNotifyStateChangedLocked(availability); } } catch (SecurityException e) { } catch (Exception e) { // Any exception here not caught will crash the process because AsyncTask does not // bubble up the exceptions to the client app, so we must propagate it to the app. Slog.w(TAG, "Failed to refresh availability", e); if (mTargetSdkVersion <= Build.VERSION_CODES.R) { throw e; } synchronized (mLock) { if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) { sendUnknownFailure( "Failed to refresh availability: " + Log.getStackTraceString(e)); } else { updateAndNotifyStateChangedLocked(STATE_ERROR); } } } return null; } Loading core/java/android/service/voice/SoundTriggerFailure.java +26 −5 Original line number Diff line number Diff line Loading @@ -74,14 +74,22 @@ public final class SoundTriggerFailure implements Parcelable { public @interface SoundTriggerErrorCode {} private final int mErrorCode; private final int mSuggestedAction; private final String mErrorMessage; /** * @hide */ @TestApi public SoundTriggerFailure(@SoundTriggerErrorCode int errorCode, @NonNull String errorMessage) { public SoundTriggerFailure(@SoundTriggerErrorCode int errorCode, @NonNull String errorMessage) { this(errorCode, errorMessage, getSuggestedActionBasedOnErrorCode(errorCode)); } /** * @hide */ public SoundTriggerFailure(@SoundTriggerErrorCode int errorCode, @NonNull String errorMessage, @FailureSuggestedAction.FailureSuggestedActionDef int suggestedAction) { if (TextUtils.isEmpty(errorMessage)) { throw new IllegalArgumentException("errorMessage is empty or null."); } Loading @@ -95,7 +103,13 @@ public final class SoundTriggerFailure implements Parcelable { default: throw new IllegalArgumentException("Invalid ErrorCode: " + errorCode); } if (suggestedAction != getSuggestedActionBasedOnErrorCode(errorCode) && errorCode != ERROR_CODE_UNKNOWN) { throw new IllegalArgumentException("Invalid suggested next action: " + "errorCode=" + errorCode + ", suggestedAction=" + suggestedAction); } mErrorMessage = errorMessage; mSuggestedAction = suggestedAction; } /** Loading @@ -119,7 +133,11 @@ public final class SoundTriggerFailure implements Parcelable { */ @FailureSuggestedAction.FailureSuggestedActionDef public int getSuggestedAction() { switch (mErrorCode) { return mSuggestedAction; } private static int getSuggestedActionBasedOnErrorCode(@SoundTriggerErrorCode int errorCode) { switch (errorCode) { case ERROR_CODE_UNKNOWN: case ERROR_CODE_MODULE_DIED: case ERROR_CODE_UNEXPECTED_PREEMPTION: Loading @@ -144,8 +162,11 @@ public final class SoundTriggerFailure implements Parcelable { @Override public String toString() { return "SoundTriggerFailure { errorCode = " + mErrorCode + ", errorMessage = " + mErrorMessage + " }"; return "SoundTriggerFailure {" + " errorCode = " + mErrorCode + ", errorMessage = " + mErrorMessage + ", suggestedNextAction = " + mSuggestedAction + " }"; } public static final @NonNull Parcelable.Creator<SoundTriggerFailure> CREATOR = Loading Loading
core/java/android/service/voice/AlwaysOnHotwordDetector.java +47 −11 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.service.voice; import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; import static android.Manifest.permission.RECORD_AUDIO; import static android.service.voice.SoundTriggerFailure.ERROR_CODE_UNKNOWN; import static android.service.voice.VoiceInteractionService.MULTIPLE_ACTIVE_HOTWORD_DETECTORS; import android.annotation.ElapsedRealtimeLong; Loading Loading @@ -269,6 +270,15 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) static final long THROW_ON_INITIALIZE_IF_NO_DSP = 269165460L; /** * Gates returning {@link Callback#onFailure} and {@link Callback#onUnknownFailure} * when asynchronous exceptions are propagated to the client. If the change is not enabled, * the existing behavior of delivering {@link #STATE_ERROR} is retained. */ @ChangeId @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) static final long SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS = 280471513L; /** * Controls the sensitivity threshold adjustment factor for a given model. * Negative value corresponds to less sensitive model (high threshold) and Loading Loading @@ -1409,12 +1419,16 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { if (mAvailability == STATE_KEYPHRASE_ENROLLED) { try { stopRecognitionLocked(); } catch (SecurityException e) { Slog.w(TAG, "Failed to Stop the recognition", e); if (mTargetSdkVersion <= Build.VERSION_CODES.R) { throw e; } } catch (Exception e) { Slog.w(TAG, "Failed to stop recognition after enrollment update", e); if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) { sendSoundTriggerFailure(new SoundTriggerFailure(ERROR_CODE_UNKNOWN, "Failed to stop recognition after enrollment update: " + Log.getStackTraceString(e), FailureSuggestedAction.RECREATE_DETECTOR)); } else { updateAndNotifyStateChangedLocked(STATE_ERROR); } return; } } Loading Loading @@ -1538,6 +1552,12 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { @GuardedBy("mLock") private void updateAndNotifyStateChangedLocked(int availability) { updateAvailabilityLocked(availability); notifyStateChangedLocked(); } @GuardedBy("mLock") private void updateAvailabilityLocked(int availability) { if (DBG) { Slog.d(TAG, "Hotword availability changed from " + mAvailability + " -> " + availability); Loading @@ -1545,7 +1565,6 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { if (!mIsAvailabilityOverriddenByTestApi) { mAvailability = availability; } notifyStateChangedLocked(); } @GuardedBy("mLock") Loading @@ -1555,6 +1574,18 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { message.sendToTarget(); } @GuardedBy("mLock") private void sendUnknownFailure(String failureMessage) { // update but do not call onAvailabilityChanged callback for STATE_ERROR updateAvailabilityLocked(STATE_ERROR); Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE, failureMessage).sendToTarget(); } private void sendSoundTriggerFailure(@NonNull SoundTriggerFailure soundTriggerFailure) { Message.obtain(mHandler, MSG_DETECTION_SOUND_TRIGGER_FAILURE, soundTriggerFailure) .sendToTarget(); } /** @hide */ static final class SoundTriggerListener extends IHotwordRecognitionStatusCallback.Stub { private final Handler mHandler; Loading Loading @@ -1726,6 +1757,7 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { } } // TODO(b/267681692): remove the AsyncTask usage class RefreshAvailabilityTask extends AsyncTask<Void, Void, Void> { @Override Loading @@ -1744,15 +1776,19 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { } updateAndNotifyStateChangedLocked(availability); } } catch (SecurityException e) { } catch (Exception e) { // Any exception here not caught will crash the process because AsyncTask does not // bubble up the exceptions to the client app, so we must propagate it to the app. Slog.w(TAG, "Failed to refresh availability", e); if (mTargetSdkVersion <= Build.VERSION_CODES.R) { throw e; } synchronized (mLock) { if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) { sendUnknownFailure( "Failed to refresh availability: " + Log.getStackTraceString(e)); } else { updateAndNotifyStateChangedLocked(STATE_ERROR); } } } return null; } Loading
core/java/android/service/voice/SoundTriggerFailure.java +26 −5 Original line number Diff line number Diff line Loading @@ -74,14 +74,22 @@ public final class SoundTriggerFailure implements Parcelable { public @interface SoundTriggerErrorCode {} private final int mErrorCode; private final int mSuggestedAction; private final String mErrorMessage; /** * @hide */ @TestApi public SoundTriggerFailure(@SoundTriggerErrorCode int errorCode, @NonNull String errorMessage) { public SoundTriggerFailure(@SoundTriggerErrorCode int errorCode, @NonNull String errorMessage) { this(errorCode, errorMessage, getSuggestedActionBasedOnErrorCode(errorCode)); } /** * @hide */ public SoundTriggerFailure(@SoundTriggerErrorCode int errorCode, @NonNull String errorMessage, @FailureSuggestedAction.FailureSuggestedActionDef int suggestedAction) { if (TextUtils.isEmpty(errorMessage)) { throw new IllegalArgumentException("errorMessage is empty or null."); } Loading @@ -95,7 +103,13 @@ public final class SoundTriggerFailure implements Parcelable { default: throw new IllegalArgumentException("Invalid ErrorCode: " + errorCode); } if (suggestedAction != getSuggestedActionBasedOnErrorCode(errorCode) && errorCode != ERROR_CODE_UNKNOWN) { throw new IllegalArgumentException("Invalid suggested next action: " + "errorCode=" + errorCode + ", suggestedAction=" + suggestedAction); } mErrorMessage = errorMessage; mSuggestedAction = suggestedAction; } /** Loading @@ -119,7 +133,11 @@ public final class SoundTriggerFailure implements Parcelable { */ @FailureSuggestedAction.FailureSuggestedActionDef public int getSuggestedAction() { switch (mErrorCode) { return mSuggestedAction; } private static int getSuggestedActionBasedOnErrorCode(@SoundTriggerErrorCode int errorCode) { switch (errorCode) { case ERROR_CODE_UNKNOWN: case ERROR_CODE_MODULE_DIED: case ERROR_CODE_UNEXPECTED_PREEMPTION: Loading @@ -144,8 +162,11 @@ public final class SoundTriggerFailure implements Parcelable { @Override public String toString() { return "SoundTriggerFailure { errorCode = " + mErrorCode + ", errorMessage = " + mErrorMessage + " }"; return "SoundTriggerFailure {" + " errorCode = " + mErrorCode + ", errorMessage = " + mErrorMessage + ", suggestedNextAction = " + mSuggestedAction + " }"; } public static final @NonNull Parcelable.Creator<SoundTriggerFailure> CREATOR = Loading