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