1 /**
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.service.voice;
18 
19 import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
20 import static android.Manifest.permission.RECORD_AUDIO;
21 import static android.service.voice.SoundTriggerFailure.ERROR_CODE_UNKNOWN;
22 import static android.service.voice.VoiceInteractionService.MULTIPLE_ACTIVE_HOTWORD_DETECTORS;
23 
24 import android.annotation.ElapsedRealtimeLong;
25 import android.annotation.FlaggedApi;
26 import android.annotation.IntDef;
27 import android.annotation.NonNull;
28 import android.annotation.Nullable;
29 import android.annotation.RequiresPermission;
30 import android.annotation.SuppressLint;
31 import android.annotation.SystemApi;
32 import android.annotation.TestApi;
33 import android.app.ActivityThread;
34 import android.app.compat.CompatChanges;
35 import android.compat.annotation.ChangeId;
36 import android.compat.annotation.EnabledSince;
37 import android.compat.annotation.UnsupportedAppUsage;
38 import android.content.Context;
39 import android.content.Intent;
40 import android.hardware.soundtrigger.KeyphraseEnrollmentInfo;
41 import android.hardware.soundtrigger.KeyphraseMetadata;
42 import android.hardware.soundtrigger.SoundTrigger;
43 import android.hardware.soundtrigger.SoundTrigger.ConfidenceLevel;
44 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
45 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
46 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
47 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
48 import android.media.AudioFormat;
49 import android.media.permission.Identity;
50 import android.os.AsyncTask;
51 import android.os.Binder;
52 import android.os.Build;
53 import android.os.Handler;
54 import android.os.HandlerExecutor;
55 import android.os.IBinder;
56 import android.os.Looper;
57 import android.os.Message;
58 import android.os.ParcelFileDescriptor;
59 import android.os.PersistableBundle;
60 import android.os.RemoteException;
61 import android.os.SharedMemory;
62 import android.os.SystemClock;
63 import android.text.TextUtils;
64 import android.util.Log;
65 import android.util.Slog;
66 
67 import com.android.internal.annotations.GuardedBy;
68 import com.android.internal.app.IHotwordRecognitionStatusCallback;
69 import com.android.internal.app.IVoiceInteractionManagerService;
70 import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
71 import com.android.internal.infra.AndroidFuture;
72 
73 import java.io.PrintWriter;
74 import java.lang.annotation.Retention;
75 import java.lang.annotation.RetentionPolicy;
76 import java.util.Arrays;
77 import java.util.Collections;
78 import java.util.HashSet;
79 import java.util.List;
80 import java.util.Locale;
81 import java.util.Objects;
82 import java.util.Set;
83 import java.util.concurrent.Executor;
84 
85 /**
86  * A class that lets a VoiceInteractionService implementation interact with
87  * always-on keyphrase detection APIs.
88  *
89  * @hide
90  * TODO(b/168605867): Once Metalava supports expressing a removed public, but current system API,
91  *                    mark and track it as such.
92  */
93 @SystemApi
94 public class AlwaysOnHotwordDetector extends AbstractDetector {
95     //---- States of Keyphrase availability. Return codes for onAvailabilityChanged() ----//
96     /**
97      * Indicates that this hotword detector is no longer valid for any recognition
98      * and should not be used anymore.
99      */
100     private static final int STATE_INVALID = -3;
101 
102     /**
103      * Indicates that recognition for the given keyphrase is not available on the system
104      * because of the hardware configuration.
105      * No further interaction should be performed with the detector that returns this availability.
106      */
107     public static final int STATE_HARDWARE_UNAVAILABLE = -2;
108 
109     /**
110      * Indicates that recognition for the given keyphrase is not supported.
111      * No further interaction should be performed with the detector that returns this availability.
112      *
113      * @deprecated This is no longer a valid state. Enrollment can occur outside of
114      * {@link KeyphraseEnrollmentInfo} through another privileged application. We can no longer
115      * determine ahead of time if the keyphrase and locale are unsupported by the system.
116      */
117     @Deprecated
118     public static final int STATE_KEYPHRASE_UNSUPPORTED = -1;
119 
120     /**
121      * Indicates that the given keyphrase is not enrolled.
122      * The caller may choose to begin an enrollment flow for the keyphrase.
123      */
124     public static final int STATE_KEYPHRASE_UNENROLLED = 1;
125 
126     /**
127      * Indicates that the given keyphrase is currently enrolled and it's possible to start
128      * recognition for it.
129      */
130     public static final int STATE_KEYPHRASE_ENROLLED = 2;
131 
132     /**
133      * Indicates that the availability state of the active keyphrase can't be known due to an error.
134      *
135      * <p>NOTE: No further interaction should be performed with the detector that returns this
136      * state, it would be better to create {@link AlwaysOnHotwordDetector} again.
137      */
138     public static final int STATE_ERROR = 3;
139 
140     /**
141      * Indicates that the detector isn't ready currently.
142      */
143     private static final int STATE_NOT_READY = 0;
144 
145     //-- Flags for startRecognition    ----//
146     /** @hide */
147     @Retention(RetentionPolicy.SOURCE)
148     @IntDef(flag = true, prefix = { "RECOGNITION_FLAG_" }, value = {
149             RECOGNITION_FLAG_NONE,
150             RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO,
151             RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS,
152             RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION,
153             RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION,
154             RECOGNITION_FLAG_RUN_IN_BATTERY_SAVER,
155     })
156     public @interface RecognitionFlags {}
157 
158     /**
159      * Empty flag for {@link #startRecognition(int)}.
160      *
161      * @hide
162      */
163     public static final int RECOGNITION_FLAG_NONE = 0;
164 
165     /**
166      * Recognition flag for {@link #startRecognition(int)} that indicates
167      * whether the trigger audio for hotword needs to be captured.
168      */
169     public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 0x1;
170 
171     /**
172      * Recognition flag for {@link #startRecognition(int)} that indicates
173      * whether the recognition should keep going on even after the keyphrase triggers.
174      * If this flag is specified, it's possible to get multiple triggers after a
175      * call to {@link #startRecognition(int)} if the user speaks the keyphrase multiple times.
176      * When this isn't specified, the default behavior is to stop recognition once the
177      * keyphrase is spoken, till the caller starts recognition again.
178      */
179     public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 0x2;
180 
181     /**
182      * Audio capabilities flag for {@link #startRecognition(int)} that indicates
183      * if the underlying recognition should use AEC.
184      * This capability may or may not be supported by the system, and support can be queried
185      * by calling {@link #getSupportedAudioCapabilities()}. The corresponding capabilities field for
186      * this flag is {@link #AUDIO_CAPABILITY_ECHO_CANCELLATION}. If this flag is passed without the
187      * audio capability supported, there will be no audio effect applied.
188      */
189     public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 0x4;
190 
191     /**
192      * Audio capabilities flag for {@link #startRecognition(int)} that indicates
193      * if the underlying recognition should use noise suppression.
194      * This capability may or may not be supported by the system, and support can be queried
195      * by calling {@link #getSupportedAudioCapabilities()}. The corresponding capabilities field for
196      * this flag is {@link #AUDIO_CAPABILITY_NOISE_SUPPRESSION}. If this flag is passed without the
197      * audio capability supported, there will be no audio effect applied.
198      */
199     public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 0x8;
200 
201     /**
202      * Recognition flag for {@link #startRecognition(int)} that indicates whether the recognition
203      * should continue after battery saver mode is enabled.
204      * When this flag is specified, the caller will be checked for
205      * {@link android.Manifest.permission#SOUND_TRIGGER_RUN_IN_BATTERY_SAVER} permission granted.
206      */
207     public static final int RECOGNITION_FLAG_RUN_IN_BATTERY_SAVER = 0x10;
208 
209     //---- Recognition mode flags. Return codes for getSupportedRecognitionModes() ----//
210     // Must be kept in sync with the related attribute defined as searchKeyphraseRecognitionFlags.
211 
212     /** @hide */
213     @Retention(RetentionPolicy.SOURCE)
214     @IntDef(flag = true, prefix = { "RECOGNITION_MODE_" }, value = {
215             RECOGNITION_MODE_VOICE_TRIGGER,
216             RECOGNITION_MODE_USER_IDENTIFICATION,
217     })
218     public @interface RecognitionModes {}
219 
220     /**
221      * Simple recognition of the key phrase.
222      * Returned by {@link #getSupportedRecognitionModes()}
223      */
224     public static final int RECOGNITION_MODE_VOICE_TRIGGER
225             = SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER;
226 
227     /**
228      * User identification performed with the keyphrase recognition.
229      * Returned by {@link #getSupportedRecognitionModes()}
230      */
231     public static final int RECOGNITION_MODE_USER_IDENTIFICATION
232             = SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
233 
234     //-- Audio capabilities. Values in returned bit field for getSupportedAudioCapabilities() --//
235 
236     /** @hide */
237     @Retention(RetentionPolicy.SOURCE)
238     @IntDef(flag = true, prefix = { "AUDIO_CAPABILITY_" }, value = {
239             AUDIO_CAPABILITY_ECHO_CANCELLATION,
240             AUDIO_CAPABILITY_NOISE_SUPPRESSION,
241     })
242     public @interface AudioCapabilities {}
243 
244     /**
245      * If set the underlying module supports AEC.
246      * Returned by {@link #getSupportedAudioCapabilities()}
247      */
248     public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION =
249             SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION;
250 
251     /**
252      * If set, the underlying module supports noise suppression.
253      * Returned by {@link #getSupportedAudioCapabilities()}
254      */
255     public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION =
256             SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION;
257 
258     /** @hide */
259     @Retention(RetentionPolicy.SOURCE)
260     @IntDef(flag = true, prefix = { "MODEL_PARAM_" }, value = {
261             MODEL_PARAM_THRESHOLD_FACTOR,
262     })
263     public @interface ModelParams {}
264 
265     /**
266      * Gates returning {@code IllegalStateException} in {@link #initialize(
267      * PersistableBundle, SharedMemory, SoundTrigger.ModuleProperties)} when no DSP module
268      * is available. If the change is not enabled, the existing behavior of not throwing an
269      * exception and delivering {@link STATE_HARDWARE_UNAVAILABLE} is retained.
270      */
271     @ChangeId
272     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
273     static final long THROW_ON_INITIALIZE_IF_NO_DSP = 269165460L;
274 
275     /**
276      * Gates returning {@link Callback#onFailure} and {@link Callback#onUnknownFailure}
277      * when asynchronous exceptions are propagated to the client. If the change is not enabled,
278      * the existing behavior of delivering {@link #STATE_ERROR} is retained.
279      */
280     @ChangeId
281     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
282     static final long SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS = 280471513L;
283 
284     /**
285      * Controls the sensitivity threshold adjustment factor for a given model.
286      * Negative value corresponds to less sensitive model (high threshold) and
287      * a positive value corresponds to a more sensitive model (low threshold).
288      * Default value is 0.
289      */
290     public static final int MODEL_PARAM_THRESHOLD_FACTOR =
291             android.hardware.soundtrigger.ModelParams.THRESHOLD_FACTOR;
292 
293     static final String TAG = "AlwaysOnHotwordDetector";
294     static final boolean DBG = false;
295 
296     private static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR;
297     private static final int STATUS_OK = SoundTrigger.STATUS_OK;
298 
299     private static final int MSG_AVAILABILITY_CHANGED = 1;
300     private static final int MSG_HOTWORD_DETECTED = 2;
301     private static final int MSG_DETECTION_ERROR = 3;
302     private static final int MSG_DETECTION_PAUSE = 4;
303     private static final int MSG_DETECTION_RESUME = 5;
304     private static final int MSG_HOTWORD_REJECTED = 6;
305     private static final int MSG_HOTWORD_STATUS_REPORTED = 7;
306     private static final int MSG_PROCESS_RESTARTED = 8;
307     private static final int MSG_DETECTION_HOTWORD_DETECTION_SERVICE_FAILURE = 9;
308     private static final int MSG_DETECTION_SOUND_TRIGGER_FAILURE = 10;
309     private static final int MSG_DETECTION_UNKNOWN_FAILURE = 11;
310 
311     private final String mText;
312     private final Locale mLocale;
313     /**
314      * The metadata of the Keyphrase, derived from the enrollment application.
315      * This may be null if this keyphrase isn't supported by the enrollment application.
316      */
317     @GuardedBy("mLock")
318     @Nullable
319     private KeyphraseMetadata mKeyphraseMetadata;
320     private final KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo;
321     private final IVoiceInteractionManagerService mModelManagementService;
322     private IVoiceInteractionSoundTriggerSession mSoundTriggerSession;
323     private final SoundTriggerListener mInternalCallback;
324     private final Callback mExternalCallback;
325     private final Executor mExternalExecutor;
326     private final Handler mHandler;
327     private final IBinder mBinder = new Binder();
328     private final boolean mSupportSandboxedDetectionService;
329     private final String mAttributionTag;
330 
331     @GuardedBy("mLock")
332     private boolean mIsAvailabilityOverriddenByTestApi = false;
333     @GuardedBy("mLock")
334     private int mAvailability = STATE_NOT_READY;
335 
336     /**
337      *  A ModelParamRange is a representation of supported parameter range for a
338      *  given loaded model.
339      */
340     public static final class ModelParamRange {
341         private final SoundTrigger.ModelParamRange mModelParamRange;
342 
343         /** @hide */
ModelParamRange(SoundTrigger.ModelParamRange modelParamRange)344         ModelParamRange(SoundTrigger.ModelParamRange modelParamRange) {
345             mModelParamRange = modelParamRange;
346         }
347 
348         /**
349          * Get the beginning of the param range
350          *
351          * @return The inclusive start of the supported range.
352          */
getStart()353         public int getStart() {
354             return mModelParamRange.getStart();
355         }
356 
357         /**
358          * Get the end of the param range
359          *
360          * @return The inclusive end of the supported range.
361          */
getEnd()362         public int getEnd() {
363             return mModelParamRange.getEnd();
364         }
365 
366         @Override
367         @NonNull
toString()368         public String toString() {
369             return mModelParamRange.toString();
370         }
371 
372         @Override
equals(@ullable Object obj)373         public boolean equals(@Nullable Object obj) {
374             return mModelParamRange.equals(obj);
375         }
376 
377         @Override
hashCode()378         public int hashCode() {
379             return mModelParamRange.hashCode();
380         }
381     }
382 
383     /**
384      * Additional payload for {@link Callback#onDetected}.
385      */
386     public static class EventPayload {
387 
388         /**
389          * Flags for describing the data format provided in the event payload.
390          *
391          * @hide
392          */
393         @Retention(RetentionPolicy.SOURCE)
394         @IntDef(prefix = {"DATA_FORMAT_"}, value = {
395                 DATA_FORMAT_RAW,
396                 DATA_FORMAT_TRIGGER_AUDIO,
397         })
398         public @interface DataFormat {
399         }
400 
401         /**
402          * Data format is not strictly defined by the framework, and the
403          * {@link android.hardware.soundtrigger.SoundTriggerModule} voice engine may populate this
404          * field in any format.
405          */
406         public static final int DATA_FORMAT_RAW = 0;
407 
408         /**
409          * Data format is defined as trigger audio.
410          *
411          * <p>When this format is used, {@link #getCaptureAudioFormat()} can be used to understand
412          * further the audio format for reading the data.
413          *
414          * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
415          */
416         public static final int DATA_FORMAT_TRIGGER_AUDIO = 1;
417 
418         @DataFormat
419         private final int mDataFormat;
420         // Indicates if {@code captureSession} can be used to continue capturing more audio
421         // from the DSP hardware.
422         private final boolean mCaptureAvailable;
423         // The session to use when attempting to capture more audio from the DSP hardware.
424         private final int mCaptureSession;
425         private final AudioFormat mAudioFormat;
426         // Raw data associated with the event.
427         // This is the audio that triggered the keyphrase if {@code isTriggerAudio} is true.
428         private final byte[] mData;
429         private final HotwordDetectedResult mHotwordDetectedResult;
430         private final ParcelFileDescriptor mAudioStream;
431         private final List<KeyphraseRecognitionExtra> mKephraseExtras;
432 
433         @ElapsedRealtimeLong
434         private final long mHalEventReceivedMillis;
435 
436         private final boolean mIsRecognitionStopped;
437 
EventPayload( boolean captureAvailable, @Nullable AudioFormat audioFormat, int captureSession, @DataFormat int dataFormat, @Nullable byte[] data, @Nullable HotwordDetectedResult hotwordDetectedResult, @Nullable ParcelFileDescriptor audioStream, @NonNull List<KeyphraseRecognitionExtra> keyphraseExtras, @ElapsedRealtimeLong long halEventReceivedMillis, boolean isRecognitionStopped)438         private EventPayload(
439                 boolean captureAvailable,
440                 @Nullable AudioFormat audioFormat,
441                 int captureSession,
442                 @DataFormat int dataFormat,
443                 @Nullable byte[] data,
444                 @Nullable HotwordDetectedResult hotwordDetectedResult,
445                 @Nullable ParcelFileDescriptor audioStream,
446                 @NonNull List<KeyphraseRecognitionExtra> keyphraseExtras,
447                 @ElapsedRealtimeLong long halEventReceivedMillis,
448                 boolean isRecognitionStopped) {
449             mCaptureAvailable = captureAvailable;
450             mCaptureSession = captureSession;
451             mAudioFormat = audioFormat;
452             mDataFormat = dataFormat;
453             mData = data;
454             mHotwordDetectedResult = hotwordDetectedResult;
455             mAudioStream = audioStream;
456             mKephraseExtras = keyphraseExtras;
457             mHalEventReceivedMillis = halEventReceivedMillis;
458             mIsRecognitionStopped = isRecognitionStopped;
459         }
460 
461         /**
462          * Gets the format of the audio obtained using {@link #getTriggerAudio()}.
463          * May be null if there's no audio present.
464          */
465         @Nullable
getCaptureAudioFormat()466         public AudioFormat getCaptureAudioFormat() {
467             return mAudioFormat;
468         }
469 
470         /**
471          * Gets the raw audio that triggered the keyphrase.
472          * This may be null if the trigger audio isn't available.
473          * If non-null, the format of the audio can be obtained by calling
474          * {@link #getCaptureAudioFormat()}.
475          *
476          * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
477          * @deprecated Use {@link #getData()} instead.
478          */
479         @Deprecated
480         @Nullable
getTriggerAudio()481         public byte[] getTriggerAudio() {
482             if (mDataFormat == DATA_FORMAT_TRIGGER_AUDIO) {
483                 return mData;
484             } else {
485                 return null;
486             }
487         }
488 
489         /**
490          * Conveys the format of the additional data that is triggered with the keyphrase event.
491          *
492          * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
493          * @see DataFormat
494          */
495         @DataFormat
getDataFormat()496         public int getDataFormat() {
497             return mDataFormat;
498         }
499 
500         /**
501          * Gets additional raw data that is triggered with the keyphrase event.
502          *
503          * <p>A {@link android.hardware.soundtrigger.SoundTriggerModule} may populate this
504          * field with opaque data for use by system applications who know about voice
505          * engine internals. Data may be null if the field is not populated by the
506          * {@link android.hardware.soundtrigger.SoundTriggerModule}.
507          *
508          * <p>If {@link #getDataFormat()} is {@link #DATA_FORMAT_TRIGGER_AUDIO}, then the
509          * entirety of this buffer is expected to be of the format from
510          * {@link #getCaptureAudioFormat()}.
511          *
512          * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
513          */
514         @Nullable
getData()515         public byte[] getData() {
516             return mData;
517         }
518 
519         /**
520          * Gets the session ID to start a capture from the DSP.
521          * This may be null if streaming capture isn't possible.
522          * If non-null, the format of the audio that can be captured can be
523          * obtained using {@link #getCaptureAudioFormat()}.
524          *
525          * TODO: Candidate for Public API when the API to start capture with a session ID
526          * is made public.
527          *
528          * TODO: Add this to {@link #getCaptureAudioFormat()}:
529          * "Gets the format of the audio obtained using {@link #getTriggerAudio()}
530          * or {@link #getCaptureSession()}. May be null if no audio can be obtained
531          * for either the trigger or a streaming session."
532          *
533          * TODO: Should this return a known invalid value instead?
534          *
535          * @hide
536          */
537         @Nullable
538         @UnsupportedAppUsage
getCaptureSession()539         public Integer getCaptureSession() {
540             if (mCaptureAvailable) {
541                 return mCaptureSession;
542             } else {
543                 return null;
544             }
545         }
546 
547         /**
548          * Returns {@link HotwordDetectedResult} associated with the hotword event, passed from
549          * {@link HotwordDetectionService}.
550          */
551         @Nullable
getHotwordDetectedResult()552         public HotwordDetectedResult getHotwordDetectedResult() {
553             return mHotwordDetectedResult;
554         }
555 
556         /**
557          * Returns a stream with bytes corresponding to the open audio stream with hotword data.
558          *
559          * <p>This data represents an audio stream in the format returned by
560          * {@link #getCaptureAudioFormat}.
561          *
562          * <p>Clients are expected to start consuming the stream within 1 second of receiving the
563          * event.
564          *
565          * <p>When this method returns a non-null, clients must close this stream when it's no
566          * longer needed. Failing to do so will result in microphone being open for longer periods
567          * of time, and app being attributed for microphone usage.
568          */
569         @Nullable
getAudioStream()570         public ParcelFileDescriptor getAudioStream() {
571             return mAudioStream;
572         }
573 
574         /**
575          * Returns the keyphrases recognized by the voice engine with additional confidence
576          * information
577          *
578          * @return List of keyphrase extras describing additional data for each keyphrase the voice
579          * engine triggered on for this event. The ordering of the list is preserved based on what
580          * the ordering provided by {@link android.hardware.soundtrigger.SoundTriggerModule}.
581          */
582         @NonNull
getKeyphraseRecognitionExtras()583         public List<KeyphraseRecognitionExtra> getKeyphraseRecognitionExtras() {
584             return mKephraseExtras;
585         }
586 
587         /**
588          * Timestamp of when the trigger event from SoundTriggerHal was received by the framework.
589          *
590          * Clock monotonic including suspend time or its equivalent on the system,
591          * in the same units and timebase as {@link SystemClock#elapsedRealtime()}.
592          *
593          * @return Elapsed realtime in milliseconds when the event was received from the HAL.
594          *      Returns -1 if the event was not generated from the HAL.
595          */
596         @ElapsedRealtimeLong
getHalEventReceivedMillis()597         public long getHalEventReceivedMillis() {
598             return mHalEventReceivedMillis;
599         }
600 
601         /** Returns whether the system has stopped hotword recognition because of this detection. */
602         @FlaggedApi(android.app.wearable.Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API)
isRecognitionStopped()603         public boolean isRecognitionStopped() {
604             return mIsRecognitionStopped;
605         }
606 
607         /**
608          * Builder class for {@link EventPayload} objects
609          *
610          * @hide
611          */
612         @TestApi
613         public static final class Builder {
614             private boolean mCaptureAvailable = false;
615             private int mCaptureSession = -1;
616             private AudioFormat mAudioFormat = null;
617             @DataFormat
618             private int mDataFormat = DATA_FORMAT_RAW;
619             private byte[] mData = null;
620             private HotwordDetectedResult mHotwordDetectedResult = null;
621             private ParcelFileDescriptor mAudioStream = null;
622             private List<KeyphraseRecognitionExtra> mKeyphraseExtras = Collections.emptyList();
623             @ElapsedRealtimeLong
624             private long mHalEventReceivedMillis = -1;
625             // default to true to keep prior behavior
626             private boolean mIsRecognitionStopped = true;
627 
Builder()628             public Builder() {}
629 
Builder(SoundTrigger.KeyphraseRecognitionEvent keyphraseRecognitionEvent)630             Builder(SoundTrigger.KeyphraseRecognitionEvent keyphraseRecognitionEvent) {
631                 setCaptureAvailable(keyphraseRecognitionEvent.isCaptureAvailable());
632                 setCaptureSession(keyphraseRecognitionEvent.getCaptureSession());
633                 if (keyphraseRecognitionEvent.getCaptureFormat() != null) {
634                     setCaptureAudioFormat(keyphraseRecognitionEvent.getCaptureFormat());
635                 }
636                 setDataFormat((keyphraseRecognitionEvent.triggerInData) ? DATA_FORMAT_TRIGGER_AUDIO
637                         : DATA_FORMAT_RAW);
638                 if (keyphraseRecognitionEvent.getData() != null) {
639                     setData(keyphraseRecognitionEvent.getData());
640                 }
641                 if (keyphraseRecognitionEvent.keyphraseExtras != null) {
642                     setKeyphraseRecognitionExtras(
643                             Arrays.asList(keyphraseRecognitionEvent.keyphraseExtras));
644                 }
645                 setHalEventReceivedMillis(keyphraseRecognitionEvent.getHalEventReceivedMillis());
646             }
647 
648             /**
649              * Indicates if {@code captureSession} can be used to continue capturing more audio from
650              * the DSP hardware.
651              */
652             @SuppressLint("MissingGetterMatchingBuilder")
653             @NonNull
setCaptureAvailable(boolean captureAvailable)654             public Builder setCaptureAvailable(boolean captureAvailable) {
655                 mCaptureAvailable = captureAvailable;
656                 return this;
657             }
658 
659             /**
660              * Sets the session ID to start a capture from the DSP.
661              */
662             @SuppressLint("MissingGetterMatchingBuilder")
663             @NonNull
setCaptureSession(int captureSession)664             public Builder setCaptureSession(int captureSession) {
665                 mCaptureSession = captureSession;
666                 return this;
667             }
668 
669             /**
670              * Sets the format of the audio obtained using {@link #getTriggerAudio()}.
671              */
672             @NonNull
setCaptureAudioFormat(@onNull AudioFormat audioFormat)673             public Builder setCaptureAudioFormat(@NonNull AudioFormat audioFormat) {
674                 mAudioFormat = audioFormat;
675                 return this;
676             }
677 
678             /**
679              * Conveys the format of the additional data that is triggered with the keyphrase event.
680              *
681              * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
682              * @see DataFormat
683              */
684             @NonNull
setDataFormat(@ataFormat int dataFormat)685             public Builder setDataFormat(@DataFormat int dataFormat) {
686                 mDataFormat = dataFormat;
687                 return this;
688             }
689 
690             /**
691              * Sets additional raw data that is triggered with the keyphrase event.
692              *
693              * <p>A {@link android.hardware.soundtrigger.SoundTriggerModule} may populate this
694              * field with opaque data for use by system applications who know about voice
695              * engine internals. Data may be null if the field is not populated by the
696              * {@link android.hardware.soundtrigger.SoundTriggerModule}.
697              *
698              * <p>If {@link #getDataFormat()} is {@link #DATA_FORMAT_TRIGGER_AUDIO}, then the
699              * entirety of this
700              * buffer is expected to be of the format from {@link #getCaptureAudioFormat()}.
701              *
702              * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
703              */
704             @NonNull
setData(@onNull byte[] data)705             public Builder setData(@NonNull byte[] data) {
706                 mData = data;
707                 return this;
708             }
709 
710             /**
711              * Sets {@link HotwordDetectedResult} associated with the hotword event, passed from
712              * {@link HotwordDetectionService}.
713              */
714             @NonNull
setHotwordDetectedResult( @onNull HotwordDetectedResult hotwordDetectedResult)715             public Builder setHotwordDetectedResult(
716                     @NonNull HotwordDetectedResult hotwordDetectedResult) {
717                 mHotwordDetectedResult = hotwordDetectedResult;
718                 return this;
719             }
720 
721             /**
722              * Sets a stream with bytes corresponding to the open audio stream with hotword data.
723              *
724              * <p>This data represents an audio stream in the format returned by
725              * {@link #getCaptureAudioFormat}.
726              *
727              * <p>Clients are expected to start consuming the stream within 1 second of receiving
728              * the
729              * event.
730              */
731             @NonNull
setAudioStream(@onNull ParcelFileDescriptor audioStream)732             public Builder setAudioStream(@NonNull ParcelFileDescriptor audioStream) {
733                 mAudioStream = audioStream;
734                 return this;
735             }
736 
737             /**
738              * Sets the keyphrases recognized by the voice engine with additional confidence
739              * information
740              */
741             @NonNull
setKeyphraseRecognitionExtras( @onNull List<KeyphraseRecognitionExtra> keyphraseRecognitionExtras)742             public Builder setKeyphraseRecognitionExtras(
743                     @NonNull List<KeyphraseRecognitionExtra> keyphraseRecognitionExtras) {
744                 mKeyphraseExtras = keyphraseRecognitionExtras;
745                 return this;
746             }
747 
748             /**
749              * Timestamp of when the trigger event from SoundTriggerHal was received by the
750              * framework.
751              *
752              * Clock monotonic including suspend time or its equivalent on the system,
753              * in the same units and timebase as {@link SystemClock#elapsedRealtime()}.
754              */
755             @NonNull
setHalEventReceivedMillis( @lapsedRealtimeLong long halEventReceivedMillis)756             public Builder setHalEventReceivedMillis(
757                     @ElapsedRealtimeLong long halEventReceivedMillis) {
758                 mHalEventReceivedMillis = halEventReceivedMillis;
759                 return this;
760             }
761 
762             /**
763              * Sets whether the system has stopped hotword recognition because of this detection.
764              */
765             @FlaggedApi(android.app.wearable.Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API)
766             @NonNull
setIsRecognitionStopped(boolean isRecognitionStopped)767             public Builder setIsRecognitionStopped(boolean isRecognitionStopped) {
768                 mIsRecognitionStopped = isRecognitionStopped;
769                 return this;
770             }
771 
772             /**
773              * Builds an {@link EventPayload} instance
774              */
775             @NonNull
build()776             public EventPayload build() {
777                 return new EventPayload(
778                         mCaptureAvailable,
779                         mAudioFormat,
780                         mCaptureSession,
781                         mDataFormat,
782                         mData,
783                         mHotwordDetectedResult,
784                         mAudioStream,
785                         mKeyphraseExtras,
786                         mHalEventReceivedMillis,
787                         mIsRecognitionStopped);
788             }
789         }
790     }
791 
792     /**
793      * Callbacks for always-on hotword detection.
794      */
795     public abstract static class Callback implements HotwordDetector.Callback {
796 
797         /**
798          * Updates the availability state of the active keyphrase and locale on every keyphrase
799          * sound model change.
800          *
801          * <p>This API is called whenever there's a possibility that the keyphrase associated
802          * with this detector has been updated. It is not guaranteed that there is in fact any
803          * change, as it may be called for other reasons.</p>
804          *
805          * <p>This API is also guaranteed to be called right after an AlwaysOnHotwordDetector
806          * instance is created to updated the current availability state.</p>
807          *
808          * <p>Availability implies the current enrollment state of the given keyphrase. If the
809          * hardware on this system is not capable of listening for the given keyphrase,
810          * {@link AlwaysOnHotwordDetector#STATE_HARDWARE_UNAVAILABLE} will be returned.
811          *
812          * @see AlwaysOnHotwordDetector#STATE_HARDWARE_UNAVAILABLE
813          * @see AlwaysOnHotwordDetector#STATE_KEYPHRASE_UNENROLLED
814          * @see AlwaysOnHotwordDetector#STATE_KEYPHRASE_ENROLLED
815          * @see AlwaysOnHotwordDetector#STATE_ERROR
816          */
onAvailabilityChanged(int status)817         public abstract void onAvailabilityChanged(int status);
818 
819         /**
820          * Called when the keyphrase is spoken.
821          *
822          * <p>If {@code eventPayload.isRecognitionStopped()} returns true, this implicitly stops
823          * listening for the keyphrase once it's detected. Clients should start a recognition again
824          * once they are done handling this detection.
825          *
826          * @param eventPayload Payload data for the detection event. This may contain the trigger
827          *     audio, if requested when calling {@link
828          *     AlwaysOnHotwordDetector#startRecognition(int)} or if the audio comes from the {@link
829          *     android.service.wearable.WearableSensingService}.
830          */
onDetected(@onNull EventPayload eventPayload)831         public abstract void onDetected(@NonNull EventPayload eventPayload);
832 
833         /**
834          * {@inheritDoc}
835          *
836          * @deprecated On {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above,
837          * implement {@link HotwordDetector.Callback#onFailure(HotwordDetectionServiceFailure)},
838          * {@link AlwaysOnHotwordDetector.Callback#onFailure(SoundTriggerFailure)},
839          * {@link HotwordDetector.Callback#onUnknownFailure(String)} instead.
840          */
841         @Deprecated
842         @Override
onError()843         public abstract void onError();
844 
845         /**
846          * Called when the detection fails due to an error occurs in the
847          * {@link com.android.server.soundtrigger.SoundTriggerService} and
848          * {@link com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareService},
849          * {@link SoundTriggerFailure} will be reported to the detector.
850          *
851          * @param soundTriggerFailure It provides the error code, error message and suggested
852          *                            action.
853          */
onFailure(@onNull SoundTriggerFailure soundTriggerFailure)854         public void onFailure(@NonNull SoundTriggerFailure soundTriggerFailure) {
855             onError();
856         }
857 
858         /** {@inheritDoc} */
onRecognitionPaused()859         public abstract void onRecognitionPaused();
860 
861         /** {@inheritDoc} */
onRecognitionResumed()862         public abstract void onRecognitionResumed();
863 
864         /** {@inheritDoc} */
onRejected(@onNull HotwordRejectedResult result)865         public void onRejected(@NonNull HotwordRejectedResult result) {
866         }
867 
868         /** {@inheritDoc} */
onHotwordDetectionServiceInitialized(int status)869         public void onHotwordDetectionServiceInitialized(int status) {
870         }
871 
872         /** {@inheritDoc} */
onHotwordDetectionServiceRestarted()873         public void onHotwordDetectionServiceRestarted() {
874         }
875     }
876 
877     /**
878      * @param text The keyphrase text to get the detector for.
879      * @param locale The java locale for the detector.
880      * @param callback A non-null Callback for receiving the recognition events.
881      * @param modelManagementService A service that allows management of sound models.
882      * @param targetSdkVersion The target SDK version.
883      * @param SupportSandboxedDetectionService {@code true} if HotwordDetectionService should be
884      * triggered, otherwise {@code false}.
885      * @param attributionTag an optional attribution tag passed form the
886      * {@link VoiceInteractionService} context via the
887      * {@link createAlwaysOnHotwordDetectorInternal(String, Locale, boolean, PersistableBundle,
888      * SharedMemory, ModuleProperties, Executor, Callback)}.
889      *
890      * @hide
891      */
AlwaysOnHotwordDetector(String text, Locale locale, Executor executor, Callback callback, KeyphraseEnrollmentInfo keyphraseEnrollmentInfo, IVoiceInteractionManagerService modelManagementService, int targetSdkVersion, boolean supportSandboxedDetectionService, @Nullable String attributionTag)892     public AlwaysOnHotwordDetector(String text, Locale locale, Executor executor, Callback callback,
893             KeyphraseEnrollmentInfo keyphraseEnrollmentInfo,
894             IVoiceInteractionManagerService modelManagementService, int targetSdkVersion,
895             boolean supportSandboxedDetectionService, @Nullable String attributionTag) {
896         super(modelManagementService, executor, callback);
897 
898         mHandler = new MyHandler(Looper.getMainLooper());
899         mText = text;
900         mLocale = locale;
901         mKeyphraseEnrollmentInfo = keyphraseEnrollmentInfo;
902         mExternalCallback = callback;
903         mExternalExecutor = executor != null ? executor : new HandlerExecutor(
904                 new Handler(Looper.myLooper()));
905         mInternalCallback = new SoundTriggerListener(mHandler);
906         mModelManagementService = modelManagementService;
907         mSupportSandboxedDetectionService = supportSandboxedDetectionService;
908         mAttributionTag = attributionTag;
909     }
910 
911     // Do nothing. This method should not be abstract.
912     // TODO (b/269355519) un-subclass AOHD.
913     @Override
initialize(@ullable PersistableBundle options, @Nullable SharedMemory sharedMemory)914     void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {}
915 
initialize(@ullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @Nullable SoundTrigger.ModuleProperties moduleProperties)916     void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory,
917             @Nullable SoundTrigger.ModuleProperties moduleProperties) {
918         if (mSupportSandboxedDetectionService) {
919             initAndVerifyDetector(options, sharedMemory, mInternalCallback,
920                     DETECTOR_TYPE_TRUSTED_HOTWORD_DSP, mAttributionTag);
921         }
922         try {
923             Identity identity = new Identity();
924             identity.packageName = ActivityThread.currentOpPackageName();
925             if (IS_IDENTITY_WITH_ATTRIBUTION_TAG) {
926                 identity.attributionTag = mAttributionTag;
927             }
928             if (moduleProperties == null) {
929                 moduleProperties = mModelManagementService
930                         .listModuleProperties(identity)
931                         .stream()
932                         .filter(prop -> !prop.getSupportedModelArch()
933                                 .equals(SoundTrigger.FAKE_HAL_ARCH))
934                         .findFirst()
935                         .orElse(null);
936                 if (CompatChanges.isChangeEnabled(THROW_ON_INITIALIZE_IF_NO_DSP) &&
937                         moduleProperties == null) {
938                     throw new IllegalStateException("No DSP module available to attach to");
939                 }
940             }
941             mSoundTriggerSession =
942                     mModelManagementService.createSoundTriggerSessionAsOriginator(
943                             identity, mBinder, moduleProperties);
944         } catch (RemoteException e) {
945             throw e.rethrowAsRuntimeException();
946         }
947         new RefreshAvailabilityTask().execute();
948     }
949 
950     /**
951      * {@inheritDoc}
952      *
953      * @throws IllegalStateException if this AlwaysOnHotwordDetector wasn't specified to use a
954      * {@link HotwordDetectionService} when it was created. In addition, if this
955      * AlwaysOnHotwordDetector is in an invalid or error state.
956      */
957     @Override
updateState(@ullable PersistableBundle options, @Nullable SharedMemory sharedMemory)958     public final void updateState(@Nullable PersistableBundle options,
959             @Nullable SharedMemory sharedMemory) {
960         synchronized (mLock) {
961             if (!mSupportSandboxedDetectionService) {
962                 throw new IllegalStateException(
963                         "updateState called, but it doesn't support hotword detection service");
964             }
965             if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
966                 throw new IllegalStateException(
967                         "updateState called on an invalid detector or error state");
968             }
969         }
970 
971         super.updateState(options, sharedMemory);
972     }
973 
974     /**
975      * Test API for manipulating the voice engine and sound model availability.
976      *
977      * After overriding the availability status, the client's
978      * {@link Callback#onAvailabilityChanged(int)} will be called to reflect the updated state.
979      *
980      * When this override is set, all system updates to availability will be ignored.
981      * @hide
982      */
983     @TestApi
overrideAvailability(int availability)984     public void overrideAvailability(int availability) {
985         synchronized (mLock) {
986             mAvailability = availability;
987             mIsAvailabilityOverriddenByTestApi = true;
988             // ENROLLED state requires there to be metadata about the sound model so a fake one
989             // is created.
990             if (mKeyphraseMetadata == null && mAvailability == STATE_KEYPHRASE_ENROLLED) {
991                 Set<Locale> fakeSupportedLocales = new HashSet<>();
992                 fakeSupportedLocales.add(mLocale);
993                 mKeyphraseMetadata = new KeyphraseMetadata(1, mText, fakeSupportedLocales,
994                         AlwaysOnHotwordDetector.RECOGNITION_MODE_VOICE_TRIGGER);
995             }
996             notifyStateChangedLocked();
997         }
998     }
999 
1000     /**
1001      * Test API for clearing an availability override set by {@link #overrideAvailability(int)}
1002      *
1003      * This method will restore the availability to the current system state.
1004      * @hide
1005      */
1006     @TestApi
resetAvailability()1007     public void resetAvailability() {
1008         synchronized (mLock) {
1009             mIsAvailabilityOverriddenByTestApi = false;
1010         }
1011         // Execute a refresh availability task - which should then notify of a change.
1012         new RefreshAvailabilityTask().execute();
1013     }
1014 
1015     /**
1016      * Test API to simulate to trigger hardware recognition event for test.
1017      *
1018      * @hide
1019      */
1020     @TestApi
1021     @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
triggerHardwareRecognitionEventForTest(int status, int soundModelHandle, @ElapsedRealtimeLong long halEventReceivedMillis, boolean captureAvailable, int captureSession, int captureDelayMs, int capturePreambleMs, boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data, @NonNull List<KeyphraseRecognitionExtra> keyphraseRecognitionExtras)1022     public void triggerHardwareRecognitionEventForTest(int status, int soundModelHandle,
1023             @ElapsedRealtimeLong long halEventReceivedMillis, boolean captureAvailable,
1024             int captureSession, int captureDelayMs, int capturePreambleMs, boolean triggerInData,
1025             @NonNull AudioFormat captureFormat, @Nullable byte[] data,
1026             @NonNull List<KeyphraseRecognitionExtra> keyphraseRecognitionExtras) {
1027         Log.d(TAG, "triggerHardwareRecognitionEventForTest()");
1028         synchronized (mLock) {
1029             if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
1030                 throw new IllegalStateException("triggerHardwareRecognitionEventForTest called on"
1031                         + " an invalid detector or error state");
1032             }
1033             try {
1034                 mModelManagementService.triggerHardwareRecognitionEventForTest(
1035                         new KeyphraseRecognitionEvent(status, soundModelHandle, captureAvailable,
1036                                 captureSession, captureDelayMs, capturePreambleMs, triggerInData,
1037                                 captureFormat, data, keyphraseRecognitionExtras.toArray(
1038                                 new KeyphraseRecognitionExtra[0]), halEventReceivedMillis,
1039                                 new Binder()),
1040                         mInternalCallback);
1041             } catch (RemoteException e) {
1042                 throw e.rethrowFromSystemServer();
1043             }
1044         }
1045     }
1046 
1047     /**
1048      * Gets the recognition modes supported by the associated keyphrase.
1049      *
1050      * @see #RECOGNITION_MODE_USER_IDENTIFICATION
1051      * @see #RECOGNITION_MODE_VOICE_TRIGGER
1052      *
1053      * @throws UnsupportedOperationException if the keyphrase itself isn't supported.
1054      *         Callers should only call this method after a supported state callback on
1055      *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
1056      * @throws IllegalStateException if the detector is in an invalid or error state.
1057      *         This may happen if another detector has been instantiated or the
1058      *         {@link VoiceInteractionService} hosting this detector has been shut down.
1059      */
getSupportedRecognitionModes()1060     public @RecognitionModes int getSupportedRecognitionModes() {
1061         if (DBG) Slog.d(TAG, "getSupportedRecognitionModes()");
1062         synchronized (mLock) {
1063             return getSupportedRecognitionModesLocked();
1064         }
1065     }
1066 
1067     @GuardedBy("mLock")
getSupportedRecognitionModesLocked()1068     private int getSupportedRecognitionModesLocked() {
1069         if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
1070             throw new IllegalStateException(
1071                     "getSupportedRecognitionModes called on an invalid detector or error state");
1072         }
1073 
1074         // This method only makes sense if we can actually support a recognition.
1075         if (mAvailability != STATE_KEYPHRASE_ENROLLED || mKeyphraseMetadata == null) {
1076             throw new UnsupportedOperationException(
1077                     "Getting supported recognition modes for the keyphrase is not supported");
1078         }
1079 
1080         return mKeyphraseMetadata.getRecognitionModeFlags();
1081     }
1082 
1083     /**
1084      * Get the audio capabilities supported by the platform which can be enabled when
1085      * starting a recognition.
1086      * Caller must be the active voice interaction service via
1087      * Settings.Secure.VOICE_INTERACTION_SERVICE.
1088      *
1089      * @see #AUDIO_CAPABILITY_ECHO_CANCELLATION
1090      * @see #AUDIO_CAPABILITY_NOISE_SUPPRESSION
1091      *
1092      * @return Bit field encoding of the AudioCapabilities supported.
1093      */
1094     @AudioCapabilities
getSupportedAudioCapabilities()1095     public int getSupportedAudioCapabilities() {
1096         if (DBG) Slog.d(TAG, "getSupportedAudioCapabilities()");
1097         synchronized (mLock) {
1098             return getSupportedAudioCapabilitiesLocked();
1099         }
1100     }
1101 
1102     @GuardedBy("mLock")
getSupportedAudioCapabilitiesLocked()1103     private int getSupportedAudioCapabilitiesLocked() {
1104         try {
1105             ModuleProperties properties =
1106                     mSoundTriggerSession.getDspModuleProperties();
1107             if (properties != null) {
1108                 return properties.getAudioCapabilities();
1109             }
1110 
1111             return 0;
1112         } catch (RemoteException e) {
1113             throw e.rethrowFromSystemServer();
1114         }
1115     }
1116 
1117     /**
1118      * Starts recognition for the associated keyphrase.
1119      * Caller must be the active voice interaction service via
1120      * Settings.Secure.VOICE_INTERACTION_SERVICE.
1121      *
1122      * @see #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
1123      * @see #RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS
1124      *
1125      * @param recognitionFlags The flags to control the recognition properties.
1126      * @param data Additional pass-through data to the system voice engine along with the
1127      *             startRecognition request. This data is intended to provide additional parameters
1128      *             when starting the opaque sound model.
1129      * @return Indicates whether the call succeeded or not.
1130      * @throws UnsupportedOperationException if the recognition isn't supported.
1131      *         Callers should only call this method after a supported state callback on
1132      *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
1133      * @throws IllegalStateException if the detector is in an invalid or error state.
1134      *         This may happen if another detector has been instantiated or the
1135      *         {@link VoiceInteractionService} hosting this detector has been shut down.
1136      */
1137     @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
startRecognition(@ecognitionFlags int recognitionFlags, @NonNull byte[] data)1138     public boolean startRecognition(@RecognitionFlags int recognitionFlags, @NonNull byte[] data) {
1139         synchronized (mLock) {
1140             return startRecognitionLocked(recognitionFlags, data)
1141                     == STATUS_OK;
1142         }
1143     }
1144 
1145     /**
1146      * Starts recognition for the associated keyphrase.
1147      * Caller must be the active voice interaction service via
1148      * Settings.Secure.VOICE_INTERACTION_SERVICE.
1149      *
1150      * @see #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
1151      * @see #RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS
1152      *
1153      * @param recognitionFlags The flags to control the recognition properties.
1154      * @return Indicates whether the call succeeded or not.
1155      * @throws UnsupportedOperationException if the recognition isn't supported.
1156      *         Callers should only call this method after a supported state callback on
1157      *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
1158      * @throws IllegalStateException if the detector is in an invalid or error state.
1159      *         This may happen if another detector has been instantiated or the
1160      *         {@link VoiceInteractionService} hosting this detector has been shut down.
1161      */
1162     @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
startRecognition(@ecognitionFlags int recognitionFlags)1163     public boolean startRecognition(@RecognitionFlags int recognitionFlags) {
1164         if (DBG) Slog.d(TAG, "startRecognition(" + recognitionFlags + ")");
1165         synchronized (mLock) {
1166             return startRecognitionLocked(recognitionFlags, null /* data */) == STATUS_OK;
1167         }
1168     }
1169 
1170     /**
1171      * Starts recognition for the associated keyphrase.
1172      *
1173      * @see #startRecognition(int)
1174      */
1175     @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
1176     @Override
startRecognition()1177     public boolean startRecognition() {
1178         return startRecognition(0);
1179     }
1180 
1181     /**
1182      * Stops recognition for the associated keyphrase.
1183      * Caller must be the active voice interaction service via
1184      * Settings.Secure.VOICE_INTERACTION_SERVICE.
1185      *
1186      * @return Indicates whether the call succeeded or not.
1187      * @throws UnsupportedOperationException if the recognition isn't supported.
1188      *         Callers should only call this method after a supported state callback on
1189      *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
1190      * @throws IllegalStateException if the detector is in an invalid or error state.
1191      *         This may happen if another detector has been instantiated or the
1192      *         {@link VoiceInteractionService} hosting this detector has been shut down.
1193      */
1194     // TODO: Remove this RequiresPermission since it isn't actually enforced. Also fix the javadoc
1195     // about permissions enforcement (when it throws vs when it just returns false) for other
1196     // methods in this class.
1197     @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
1198     @Override
stopRecognition()1199     public boolean stopRecognition() {
1200         if (DBG) Slog.d(TAG, "stopRecognition()");
1201         synchronized (mLock) {
1202             if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
1203                 throw new IllegalStateException(
1204                         "stopRecognition called on an invalid detector or error state");
1205             }
1206 
1207             // Check if we can start/stop a recognition.
1208             if (mAvailability != STATE_KEYPHRASE_ENROLLED) {
1209                 throw new UnsupportedOperationException(
1210                         "Recognition for the given keyphrase is not supported");
1211             }
1212 
1213             return stopRecognitionLocked() == STATUS_OK;
1214         }
1215     }
1216 
1217     /**
1218      * Set a model specific {@link ModelParams} with the given value. This
1219      * parameter will keep its value for the duration the model is loaded regardless of starting and
1220      * stopping recognition. Once the model is unloaded, the value will be lost.
1221      * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before calling this
1222      * method.
1223      * Caller must be the active voice interaction service via
1224      * Settings.Secure.VOICE_INTERACTION_SERVICE.
1225      *
1226      * @param modelParam   {@link ModelParams}
1227      * @param value        Value to set
1228      * @return - {@link SoundTrigger#STATUS_OK} in case of success
1229      *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
1230      *         - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
1231      *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
1232      *           if API is not supported by HAL
1233      * @throws IllegalStateException if the detector is in an invalid or error state.
1234      *         This may happen if another detector has been instantiated or the
1235      *         {@link VoiceInteractionService} hosting this detector has been shut down.
1236      */
1237     @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
setParameter(@odelParams int modelParam, int value)1238     public int setParameter(@ModelParams int modelParam, int value) {
1239         if (DBG) {
1240             Slog.d(TAG, "setParameter(" + modelParam + ", " + value + ")");
1241         }
1242 
1243         synchronized (mLock) {
1244             if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
1245                 throw new IllegalStateException(
1246                         "setParameter called on an invalid detector or error state");
1247             }
1248 
1249             return setParameterLocked(modelParam, value);
1250         }
1251     }
1252 
1253     /**
1254      * Get a model specific {@link ModelParams}. This parameter will keep its value
1255      * for the duration the model is loaded regardless of starting and stopping recognition.
1256      * Once the model is unloaded, the value will be lost. If the value is not set, a default
1257      * value is returned. See {@link ModelParams} for parameter default values.
1258      * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before
1259      * calling this method.
1260      * Caller must be the active voice interaction service via
1261      * Settings.Secure.VOICE_INTERACTION_SERVICE.
1262      *
1263      * @param modelParam   {@link ModelParams}
1264      * @return value of parameter
1265      * @throws IllegalStateException if the detector is in an invalid or error state.
1266      *         This may happen if another detector has been instantiated or the
1267      *         {@link VoiceInteractionService} hosting this detector has been shut down.
1268      */
1269     @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
getParameter(@odelParams int modelParam)1270     public int getParameter(@ModelParams int modelParam) {
1271         if (DBG) {
1272             Slog.d(TAG, "getParameter(" + modelParam + ")");
1273         }
1274 
1275         synchronized (mLock) {
1276             if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
1277                 throw new IllegalStateException(
1278                         "getParameter called on an invalid detector or error state");
1279             }
1280 
1281             return getParameterLocked(modelParam);
1282         }
1283     }
1284 
1285     /**
1286      * Determine if parameter control is supported for the given model handle.
1287      * This method should be checked prior to calling {@link AlwaysOnHotwordDetector#setParameter}
1288      * or {@link AlwaysOnHotwordDetector#getParameter}.
1289      * Caller must be the active voice interaction service via
1290      * Settings.Secure.VOICE_INTERACTION_SERVICE.
1291      *
1292      * @param modelParam {@link ModelParams}
1293      * @return supported range of parameter, null if not supported
1294      * @throws IllegalStateException if the detector is in an invalid or error state.
1295      *         This may happen if another detector has been instantiated or the
1296      *         {@link VoiceInteractionService} hosting this detector has been shut down.
1297      */
1298     @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
1299     @Nullable
queryParameter(@odelParams int modelParam)1300     public ModelParamRange queryParameter(@ModelParams int modelParam) {
1301         if (DBG) {
1302             Slog.d(TAG, "queryParameter(" + modelParam + ")");
1303         }
1304 
1305         synchronized (mLock) {
1306             if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
1307                 throw new IllegalStateException(
1308                         "queryParameter called on an invalid detector or error state");
1309             }
1310 
1311             return queryParameterLocked(modelParam);
1312         }
1313     }
1314 
1315     /**
1316      * Creates an intent to start the enrollment for the associated keyphrase.
1317      * This intent must be invoked using {@link Context#startForegroundService(Intent)}.
1318      * Starting re-enrollment is only valid if the keyphrase is un-enrolled,
1319      * i.e. {@link #STATE_KEYPHRASE_UNENROLLED},
1320      * otherwise {@link #createReEnrollIntent()} should be preferred.
1321      *
1322      * @return An {@link Intent} to start enrollment for the given keyphrase.
1323      * @throws UnsupportedOperationException if managing they keyphrase isn't supported.
1324      *         Callers should only call this method after a supported state callback on
1325      *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
1326      * @throws IllegalStateException if the detector is in an invalid state.
1327      *         This may happen if another detector has been instantiated or the
1328      *         {@link VoiceInteractionService} hosting this detector has been shut down.
1329      */
1330     @Nullable
createEnrollIntent()1331     public Intent createEnrollIntent() {
1332         if (DBG) Slog.d(TAG, "createEnrollIntent");
1333         synchronized (mLock) {
1334             return getManageIntentLocked(KeyphraseEnrollmentInfo.MANAGE_ACTION_ENROLL);
1335         }
1336     }
1337 
1338     /**
1339      * Creates an intent to start the un-enrollment for the associated keyphrase.
1340      * This intent must be invoked using {@link Context#startForegroundService(Intent)}.
1341      * Starting re-enrollment is only valid if the keyphrase is already enrolled,
1342      * i.e. {@link #STATE_KEYPHRASE_ENROLLED}, otherwise invoking this may result in an error.
1343      *
1344      * @return An {@link Intent} to start un-enrollment for the given keyphrase.
1345      * @throws UnsupportedOperationException if managing they keyphrase isn't supported.
1346      *         Callers should only call this method after a supported state callback on
1347      *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
1348      * @throws IllegalStateException if the detector is in an invalid state.
1349      *         This may happen if another detector has been instantiated or the
1350      *         {@link VoiceInteractionService} hosting this detector has been shut down.
1351      */
1352     @Nullable
createUnEnrollIntent()1353     public Intent createUnEnrollIntent() {
1354         if (DBG) Slog.d(TAG, "createUnEnrollIntent");
1355         synchronized (mLock) {
1356             return getManageIntentLocked(KeyphraseEnrollmentInfo.MANAGE_ACTION_UN_ENROLL);
1357         }
1358     }
1359 
1360     /**
1361      * Creates an intent to start the re-enrollment for the associated keyphrase.
1362      * This intent must be invoked using {@link Context#startForegroundService(Intent)}.
1363      * Starting re-enrollment is only valid if the keyphrase is already enrolled,
1364      * i.e. {@link #STATE_KEYPHRASE_ENROLLED}, otherwise invoking this may result in an error.
1365      *
1366      * @return An {@link Intent} to start re-enrollment for the given keyphrase.
1367      * @throws UnsupportedOperationException if managing they keyphrase isn't supported.
1368      *         Callers should only call this method after a supported state callback on
1369      *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
1370      * @throws IllegalStateException if the detector is in an invalid or error state.
1371      *         This may happen if another detector has been instantiated or the
1372      *         {@link VoiceInteractionService} hosting this detector has been shut down.
1373      */
1374     @Nullable
createReEnrollIntent()1375     public Intent createReEnrollIntent() {
1376         if (DBG) Slog.d(TAG, "createReEnrollIntent");
1377         synchronized (mLock) {
1378             return getManageIntentLocked(KeyphraseEnrollmentInfo.MANAGE_ACTION_RE_ENROLL);
1379         }
1380     }
1381 
1382     @GuardedBy("mLock")
getManageIntentLocked(@eyphraseEnrollmentInfo.ManageActions int action)1383     private Intent getManageIntentLocked(@KeyphraseEnrollmentInfo.ManageActions int action) {
1384         if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
1385             throw new IllegalStateException(
1386                     "getManageIntent called on an invalid detector or error state");
1387         }
1388 
1389         // This method only makes sense if we can actually support a recognition.
1390         if (mAvailability != STATE_KEYPHRASE_ENROLLED
1391                 && mAvailability != STATE_KEYPHRASE_UNENROLLED) {
1392             throw new UnsupportedOperationException(
1393                     "Managing the given keyphrase is not supported");
1394         }
1395 
1396         return mKeyphraseEnrollmentInfo.getManageKeyphraseIntent(action, mText, mLocale);
1397     }
1398 
1399     /** {@inheritDoc} */
1400     @Override
destroy()1401     public void destroy() {
1402         synchronized (mLock) {
1403             detachSessionLocked();
1404 
1405             mAvailability = STATE_INVALID;
1406             mIsAvailabilityOverriddenByTestApi = false;
1407             notifyStateChangedLocked();
1408         }
1409         super.destroy();
1410     }
1411 
detachSessionLocked()1412     private void detachSessionLocked() {
1413         try {
1414             if (DBG) Slog.d(TAG, "detachSessionLocked() " + mSoundTriggerSession);
1415             if (mSoundTriggerSession != null) {
1416                 mSoundTriggerSession.detach();
1417             }
1418         } catch (RemoteException e) {
1419             e.rethrowFromSystemServer();
1420         }
1421     }
1422 
1423     /**
1424      * @hide
1425      */
1426     @Override
isUsingSandboxedDetectionService()1427     public boolean isUsingSandboxedDetectionService() {
1428         return mSupportSandboxedDetectionService;
1429     }
1430 
1431     /**
1432      * Reloads the sound models from the service.
1433      *
1434      * @hide
1435      */
1436     // TODO(b/281608561): remove the enrollment flow from AlwaysOnHotwordDetector
onSoundModelsChanged()1437     void onSoundModelsChanged() {
1438         synchronized (mLock) {
1439             if (mAvailability == STATE_INVALID
1440                     || mAvailability == STATE_HARDWARE_UNAVAILABLE
1441                     || mAvailability == STATE_ERROR) {
1442                 Slog.w(TAG, "Received onSoundModelsChanged for an unsupported keyphrase/config"
1443                         + " or in the error state");
1444                 return;
1445             }
1446 
1447             // Because this method reflects an update from the system service models, we should not
1448             // update the client of an availability change when the availability has been overridden
1449             // via a test API.
1450             if (mIsAvailabilityOverriddenByTestApi) {
1451                 Slog.w(TAG, "Suppressing system availability update. "
1452                         + "Availability is overridden by test API.");
1453                 return;
1454             }
1455 
1456             // Stop the recognition before proceeding if we are in the enrolled state.
1457             // The framework makes the guarantee that an actively used model is present in the
1458             // system server's enrollment database. For this reason we much stop an actively running
1459             // model when the underlying sound model in enrollment database no longer match.
1460             if (mAvailability == STATE_KEYPHRASE_ENROLLED) {
1461                 // A SoundTriggerFailure will be sent to the client if the model state was
1462                 // changed. This is an overloading of the onFailure usage because we are sending a
1463                 // callback even in the successful stop case. If stopRecognition is successful,
1464                 // suggested next action RESTART_RECOGNITION will be sent.
1465                 // TODO(b/281608561): This code path will be removed with other enrollment flows in
1466                 //  this class.
1467                 try {
1468                     int result = stopRecognitionLocked();
1469                     if (result == STATUS_OK) {
1470                         sendSoundTriggerFailure(new SoundTriggerFailure(ERROR_CODE_UNKNOWN,
1471                                 "stopped recognition because of enrollment update",
1472                                 FailureSuggestedAction.RESTART_RECOGNITION));
1473                     }
1474                     // only log to logcat here because many failures can be false positives such as
1475                     // calling stopRecognition where there is no started session.
1476                     Log.w(TAG, "Failed to stop recognition after enrollment update: code="
1477                             + result);
1478                 } catch (Exception e) {
1479                     Slog.w(TAG, "Failed to stop recognition after enrollment update", e);
1480                     if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) {
1481                         sendSoundTriggerFailure(new SoundTriggerFailure(ERROR_CODE_UNKNOWN,
1482                                 "Failed to stop recognition after enrollment update: "
1483                                         + Log.getStackTraceString(e),
1484                                 FailureSuggestedAction.RECREATE_DETECTOR));
1485                     } else {
1486                         updateAndNotifyStateChangedLocked(STATE_ERROR);
1487                     }
1488                     return;
1489                 }
1490             }
1491 
1492             // Execute a refresh availability task - which should then notify of a change.
1493             new RefreshAvailabilityTask().execute();
1494         }
1495     }
1496 
1497     @GuardedBy("mLock")
startRecognitionLocked(int recognitionFlags, @Nullable byte[] data)1498     private int startRecognitionLocked(int recognitionFlags,
1499             @Nullable byte[] data) {
1500         if (DBG) {
1501             Slog.d(TAG, "startRecognition("
1502                     + recognitionFlags
1503                     + ", " + Arrays.toString(data) + ")");
1504         }
1505         if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
1506             throw new IllegalStateException(
1507                     "startRecognition called on an invalid detector or error state");
1508         }
1509 
1510         // Check if we can start/stop a recognition.
1511         if (mAvailability != STATE_KEYPHRASE_ENROLLED) {
1512             throw new UnsupportedOperationException(
1513                     "Recognition for the given keyphrase is not supported");
1514         }
1515 
1516         KeyphraseRecognitionExtra[] recognitionExtra = new KeyphraseRecognitionExtra[1];
1517         // TODO: Do we need to do something about the confidence level here?
1518         recognitionExtra[0] = new KeyphraseRecognitionExtra(mKeyphraseMetadata.getId(),
1519                 mKeyphraseMetadata.getRecognitionModeFlags(), 0, new ConfidenceLevel[0]);
1520         boolean captureTriggerAudio =
1521                 (recognitionFlags&RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0;
1522         boolean allowMultipleTriggers =
1523                 (recognitionFlags&RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0;
1524         boolean runInBatterySaver = (recognitionFlags&RECOGNITION_FLAG_RUN_IN_BATTERY_SAVER) != 0;
1525 
1526         int audioCapabilities = 0;
1527         if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION) != 0) {
1528             audioCapabilities |= AUDIO_CAPABILITY_ECHO_CANCELLATION;
1529         }
1530         if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION) != 0) {
1531             audioCapabilities |= AUDIO_CAPABILITY_NOISE_SUPPRESSION;
1532         }
1533 
1534         int code;
1535         try {
1536             code = mSoundTriggerSession.startRecognition(
1537                     mKeyphraseMetadata.getId(), mLocale.toLanguageTag(), mInternalCallback,
1538                     new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers,
1539                             recognitionExtra, data, audioCapabilities),
1540                     runInBatterySaver);
1541         } catch (RemoteException e) {
1542             throw e.rethrowFromSystemServer();
1543         }
1544 
1545         if (code != STATUS_OK) {
1546             Slog.w(TAG, "startRecognition() failed with error code " + code);
1547         }
1548         return code;
1549     }
1550 
1551     @GuardedBy("mLock")
stopRecognitionLocked()1552     private int stopRecognitionLocked() {
1553         int code;
1554         try {
1555             code = mSoundTriggerSession.stopRecognition(mKeyphraseMetadata.getId(),
1556                     mInternalCallback);
1557         } catch (RemoteException e) {
1558             throw e.rethrowFromSystemServer();
1559         }
1560 
1561         if (code != STATUS_OK) {
1562             Slog.w(TAG, "stopRecognition() failed with error code " + code);
1563         }
1564         return code;
1565     }
1566 
1567     @GuardedBy("mLock")
setParameterLocked(@odelParams int modelParam, int value)1568     private int setParameterLocked(@ModelParams int modelParam, int value) {
1569         try {
1570             int code = mSoundTriggerSession.setParameter(mKeyphraseMetadata.getId(), modelParam,
1571                     value);
1572 
1573             if (code != STATUS_OK) {
1574                 Slog.w(TAG, "setParameter failed with error code " + code);
1575             }
1576 
1577             return code;
1578         } catch (RemoteException e) {
1579             throw e.rethrowFromSystemServer();
1580         }
1581     }
1582 
1583     @GuardedBy("mLock")
getParameterLocked(@odelParams int modelParam)1584     private int getParameterLocked(@ModelParams int modelParam) {
1585         try {
1586             return mSoundTriggerSession.getParameter(mKeyphraseMetadata.getId(), modelParam);
1587         } catch (RemoteException e) {
1588             throw e.rethrowFromSystemServer();
1589         }
1590     }
1591 
1592     @GuardedBy("mLock")
1593     @Nullable
queryParameterLocked(@odelParams int modelParam)1594     private ModelParamRange queryParameterLocked(@ModelParams int modelParam) {
1595         try {
1596             SoundTrigger.ModelParamRange modelParamRange =
1597                     mSoundTriggerSession.queryParameter(mKeyphraseMetadata.getId(), modelParam);
1598 
1599             if (modelParamRange == null) {
1600                 return null;
1601             }
1602 
1603             return new ModelParamRange(modelParamRange);
1604         } catch (RemoteException e) {
1605             throw e.rethrowFromSystemServer();
1606         }
1607     }
1608 
1609     @GuardedBy("mLock")
updateAndNotifyStateChangedLocked(int availability)1610     private void updateAndNotifyStateChangedLocked(int availability) {
1611         updateAvailabilityLocked(availability);
1612         notifyStateChangedLocked();
1613     }
1614 
1615     @GuardedBy("mLock")
updateAvailabilityLocked(int availability)1616     private void updateAvailabilityLocked(int availability) {
1617         if (DBG) {
1618             Slog.d(TAG, "Hotword availability changed from " + mAvailability
1619                     + " -> " + availability);
1620         }
1621         if (!mIsAvailabilityOverriddenByTestApi) {
1622             mAvailability = availability;
1623         }
1624     }
1625 
1626     @GuardedBy("mLock")
notifyStateChangedLocked()1627     private void notifyStateChangedLocked() {
1628         Message message = Message.obtain(mHandler, MSG_AVAILABILITY_CHANGED);
1629         message.arg1 = mAvailability;
1630         message.sendToTarget();
1631     }
1632 
1633     @GuardedBy("mLock")
sendUnknownFailure(String failureMessage)1634     private void sendUnknownFailure(String failureMessage) {
1635         // update but do not call onAvailabilityChanged callback for STATE_ERROR
1636         updateAvailabilityLocked(STATE_ERROR);
1637         Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE, failureMessage).sendToTarget();
1638     }
1639 
sendSoundTriggerFailure(@onNull SoundTriggerFailure soundTriggerFailure)1640     private void sendSoundTriggerFailure(@NonNull SoundTriggerFailure soundTriggerFailure) {
1641         Message.obtain(mHandler, MSG_DETECTION_SOUND_TRIGGER_FAILURE, soundTriggerFailure)
1642                 .sendToTarget();
1643     }
1644 
1645     /** @hide */
1646     static final class SoundTriggerListener extends IHotwordRecognitionStatusCallback.Stub {
1647         private final Handler mHandler;
1648 
SoundTriggerListener(Handler handler)1649         public SoundTriggerListener(Handler handler) {
1650             mHandler = handler;
1651         }
1652 
1653         @Override
onKeyphraseDetected( KeyphraseRecognitionEvent event, HotwordDetectedResult result)1654         public void onKeyphraseDetected(
1655                 KeyphraseRecognitionEvent event, HotwordDetectedResult result) {
1656             if (DBG) {
1657                 Slog.d(TAG, "onDetected(" + event + ")");
1658             } else {
1659                 Slog.i(TAG, "onDetected");
1660             }
1661             Message.obtain(mHandler, MSG_HOTWORD_DETECTED,
1662                     new EventPayload.Builder(event)
1663                             .setHotwordDetectedResult(result)
1664                             .build())
1665                     .sendToTarget();
1666         }
1667 
1668         @Override
onKeyphraseDetectedFromExternalSource(HotwordDetectedResult result)1669         public void onKeyphraseDetectedFromExternalSource(HotwordDetectedResult result) {
1670             Slog.i(TAG, "onKeyphraseDetectedFromExternalSource");
1671             EventPayload.Builder eventPayloadBuilder = new EventPayload.Builder();
1672             if (android.app.wearable.Flags.enableHotwordWearableSensingApi()) {
1673                 eventPayloadBuilder.setIsRecognitionStopped(false);
1674             }
1675             Message.obtain(
1676                             mHandler,
1677                             MSG_HOTWORD_DETECTED,
1678                             eventPayloadBuilder.setHotwordDetectedResult(result).build())
1679                     .sendToTarget();
1680         }
1681 
1682         @Override
onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event)1683         public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
1684             Slog.w(TAG, "Generic sound trigger event detected at AOHD: " + event);
1685         }
1686 
1687         @Override
onRejected(@onNull HotwordRejectedResult result)1688         public void onRejected(@NonNull HotwordRejectedResult result) {
1689             if (DBG) {
1690                 Slog.d(TAG, "onRejected(" + result + ")");
1691             } else {
1692                 Slog.i(TAG, "onRejected");
1693             }
1694             Message.obtain(mHandler, MSG_HOTWORD_REJECTED, result).sendToTarget();
1695         }
1696 
1697         @Override
onHotwordDetectionServiceFailure( HotwordDetectionServiceFailure hotwordDetectionServiceFailure)1698         public void onHotwordDetectionServiceFailure(
1699                 HotwordDetectionServiceFailure hotwordDetectionServiceFailure) {
1700             Slog.v(TAG, "onHotwordDetectionServiceFailure: " + hotwordDetectionServiceFailure);
1701             if (hotwordDetectionServiceFailure != null) {
1702                 Message.obtain(mHandler, MSG_DETECTION_HOTWORD_DETECTION_SERVICE_FAILURE,
1703                         hotwordDetectionServiceFailure).sendToTarget();
1704             } else {
1705                 Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE,
1706                         "Error data is null").sendToTarget();
1707             }
1708         }
1709 
1710         @Override
onVisualQueryDetectionServiceFailure( VisualQueryDetectionServiceFailure visualQueryDetectionServiceFailure)1711         public void onVisualQueryDetectionServiceFailure(
1712                 VisualQueryDetectionServiceFailure visualQueryDetectionServiceFailure)
1713                 throws RemoteException {
1714             // It should never be called here.
1715             Slog.w(TAG,
1716                     "onVisualQueryDetectionServiceFailure: " + visualQueryDetectionServiceFailure);
1717         }
1718 
1719         @Override
onSoundTriggerFailure(SoundTriggerFailure soundTriggerFailure)1720         public void onSoundTriggerFailure(SoundTriggerFailure soundTriggerFailure) {
1721             Message.obtain(mHandler, MSG_DETECTION_SOUND_TRIGGER_FAILURE,
1722                     Objects.requireNonNull(soundTriggerFailure)).sendToTarget();
1723         }
1724 
1725         @Override
onUnknownFailure(String errorMessage)1726         public void onUnknownFailure(String errorMessage) throws RemoteException {
1727             Slog.v(TAG, "onUnknownFailure: " + errorMessage);
1728             Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE,
1729                     !TextUtils.isEmpty(errorMessage) ? errorMessage
1730                             : "Error data is null").sendToTarget();
1731         }
1732 
1733         @Override
onRecognitionPaused()1734         public void onRecognitionPaused() {
1735             Slog.i(TAG, "onRecognitionPaused");
1736             mHandler.sendEmptyMessage(MSG_DETECTION_PAUSE);
1737         }
1738 
1739         @Override
onRecognitionResumed()1740         public void onRecognitionResumed() {
1741             Slog.i(TAG, "onRecognitionResumed");
1742             mHandler.sendEmptyMessage(MSG_DETECTION_RESUME);
1743         }
1744 
1745         @Override
onStatusReported(int status)1746         public void onStatusReported(int status) {
1747             if (DBG) {
1748                 Slog.d(TAG, "onStatusReported(" + status + ")");
1749             } else {
1750                 Slog.i(TAG, "onStatusReported");
1751             }
1752             Message message = Message.obtain(mHandler, MSG_HOTWORD_STATUS_REPORTED);
1753             message.arg1 = status;
1754             message.sendToTarget();
1755         }
1756 
1757         @Override
onProcessRestarted()1758         public void onProcessRestarted() {
1759             Slog.i(TAG, "onProcessRestarted");
1760             mHandler.sendEmptyMessage(MSG_PROCESS_RESTARTED);
1761         }
1762 
1763         @Override
onOpenFile(String filename, AndroidFuture future)1764         public void onOpenFile(String filename, AndroidFuture future) throws RemoteException {
1765             throw new UnsupportedOperationException("Hotword cannot access files from the disk.");
1766         }
1767     }
1768 
onDetectorRemoteException()1769     void onDetectorRemoteException() {
1770         Message.obtain(mHandler, MSG_DETECTION_HOTWORD_DETECTION_SERVICE_FAILURE,
1771                 new HotwordDetectionServiceFailure(
1772                         HotwordDetectionServiceFailure.ERROR_CODE_REMOTE_EXCEPTION,
1773                         "Detector remote exception occurs")).sendToTarget();
1774     }
1775 
1776     class MyHandler extends Handler {
MyHandler(@onNull Looper looper)1777         MyHandler(@NonNull Looper looper) {
1778             super(looper);
1779         }
1780 
1781         @Override
handleMessage(Message msg)1782         public void handleMessage(Message msg) {
1783             synchronized (mLock) {
1784                 if (mAvailability == STATE_INVALID) {
1785                     Slog.w(TAG, "Received message: " + msg.what + " for an invalid detector");
1786                     return;
1787                 }
1788             }
1789             final Message message = Message.obtain(msg);
1790             Binder.withCleanCallingIdentity(() -> mExternalExecutor.execute(() -> {
1791                 Slog.i(TAG, "handle message " + message.what);
1792                 switch (message.what) {
1793                     case MSG_AVAILABILITY_CHANGED:
1794                         mExternalCallback.onAvailabilityChanged(message.arg1);
1795                         break;
1796                     case MSG_HOTWORD_DETECTED:
1797                         mExternalCallback.onDetected((EventPayload) message.obj);
1798                         break;
1799                     case MSG_DETECTION_ERROR:
1800                         // TODO(b/271534248): After reverting the workaround, this logic is still
1801                         // necessary.
1802                         mExternalCallback.onError();
1803                         break;
1804                     case MSG_DETECTION_PAUSE:
1805                         mExternalCallback.onRecognitionPaused();
1806                         break;
1807                     case MSG_DETECTION_RESUME:
1808                         mExternalCallback.onRecognitionResumed();
1809                         break;
1810                     case MSG_HOTWORD_REJECTED:
1811                         mExternalCallback.onRejected((HotwordRejectedResult) message.obj);
1812                         break;
1813                     case MSG_HOTWORD_STATUS_REPORTED:
1814                         mExternalCallback.onHotwordDetectionServiceInitialized(message.arg1);
1815                         break;
1816                     case MSG_PROCESS_RESTARTED:
1817                         mExternalCallback.onHotwordDetectionServiceRestarted();
1818                         break;
1819                     case MSG_DETECTION_HOTWORD_DETECTION_SERVICE_FAILURE:
1820                         mExternalCallback.onFailure((HotwordDetectionServiceFailure) message.obj);
1821                         break;
1822                     case MSG_DETECTION_SOUND_TRIGGER_FAILURE:
1823                         mExternalCallback.onFailure((SoundTriggerFailure) message.obj);
1824                         break;
1825                     case MSG_DETECTION_UNKNOWN_FAILURE:
1826                         mExternalCallback.onUnknownFailure((String) message.obj);
1827                         break;
1828                     default:
1829                         super.handleMessage(message);
1830                 }
1831                 message.recycle();
1832             }));
1833         }
1834     }
1835 
1836     // TODO(b/267681692): remove the AsyncTask usage
1837     class RefreshAvailabilityTask extends AsyncTask<Void, Void, Void> {
1838 
1839         @Override
doInBackground(Void... params)1840         public Void doInBackground(Void... params) {
1841             try {
1842                 int availability = internalGetInitialAvailability();
1843 
1844                 synchronized (mLock) {
1845                     if (availability == STATE_NOT_READY) {
1846                         internalUpdateEnrolledKeyphraseMetadata();
1847                         if (mKeyphraseMetadata != null) {
1848                             availability = STATE_KEYPHRASE_ENROLLED;
1849                         } else {
1850                             availability = STATE_KEYPHRASE_UNENROLLED;
1851                         }
1852                     }
1853                     updateAndNotifyStateChangedLocked(availability);
1854                 }
1855             } catch (Exception e) {
1856                 // Any exception here not caught will crash the process because AsyncTask does not
1857                 // bubble up the exceptions to the client app, so we must propagate it to the app.
1858                 Slog.w(TAG, "Failed to refresh availability", e);
1859                 synchronized (mLock) {
1860                     if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) {
1861                         sendUnknownFailure(
1862                                 "Failed to refresh availability: " + Log.getStackTraceString(e));
1863                     } else {
1864                         updateAndNotifyStateChangedLocked(STATE_ERROR);
1865                     }
1866                 }
1867             }
1868 
1869             return null;
1870         }
1871 
1872         /**
1873          * @return The initial availability without checking the enrollment status.
1874          */
internalGetInitialAvailability()1875         private int internalGetInitialAvailability() {
1876             synchronized (mLock) {
1877                 // This detector has already been invalidated.
1878                 if (mAvailability == STATE_INVALID) {
1879                     return STATE_INVALID;
1880                 }
1881             }
1882 
1883             if (!CompatChanges.isChangeEnabled(THROW_ON_INITIALIZE_IF_NO_DSP)) {
1884                 ModuleProperties dspModuleProperties;
1885                 try {
1886                     dspModuleProperties =
1887                             mSoundTriggerSession.getDspModuleProperties();
1888                 } catch (RemoteException e) {
1889                     throw e.rethrowFromSystemServer();
1890                 }
1891 
1892                 // No DSP available
1893                 if (dspModuleProperties == null) {
1894                     return STATE_HARDWARE_UNAVAILABLE;
1895                 }
1896             }
1897 
1898             return STATE_NOT_READY;
1899         }
1900 
internalUpdateEnrolledKeyphraseMetadata()1901         private void internalUpdateEnrolledKeyphraseMetadata() {
1902             try {
1903                 mKeyphraseMetadata = mModelManagementService.getEnrolledKeyphraseMetadata(
1904                         mText, mLocale.toLanguageTag());
1905             } catch (RemoteException e) {
1906                 throw e.rethrowFromSystemServer();
1907             }
1908         }
1909     }
1910 
1911     @Override
equals(Object obj)1912     public boolean equals(Object obj) {
1913         if (CompatChanges.isChangeEnabled(MULTIPLE_ACTIVE_HOTWORD_DETECTORS)) {
1914             if (!(obj instanceof AlwaysOnHotwordDetector)) {
1915                 return false;
1916             }
1917             AlwaysOnHotwordDetector other = (AlwaysOnHotwordDetector) obj;
1918             return TextUtils.equals(mText, other.mText) && mLocale.equals(other.mLocale);
1919         }
1920 
1921         return super.equals(obj);
1922     }
1923 
1924     @Override
hashCode()1925     public int hashCode() {
1926         return Objects.hash(mText, mLocale);
1927     }
1928 
1929     /** @hide */
1930     @Override
dump(String prefix, PrintWriter pw)1931     public void dump(String prefix, PrintWriter pw) {
1932         synchronized (mLock) {
1933             pw.print(prefix); pw.print("Text="); pw.println(mText);
1934             pw.print(prefix); pw.print("Locale="); pw.println(mLocale);
1935             pw.print(prefix); pw.print("Availability="); pw.println(mAvailability);
1936             pw.print(prefix); pw.print("KeyphraseMetadata="); pw.println(mKeyphraseMetadata);
1937             pw.print(prefix); pw.print("EnrollmentInfo="); pw.println(mKeyphraseEnrollmentInfo);
1938         }
1939     }
1940 }
1941