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 android.Manifest;
20 import android.annotation.CallbackExecutor;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.RequiresPermission;
24 import android.annotation.SdkConstant;
25 import android.annotation.SuppressLint;
26 import android.annotation.SystemApi;
27 import android.annotation.TestApi;
28 import android.app.ActivityThread;
29 import android.app.Service;
30 import android.app.compat.CompatChanges;
31 import android.compat.annotation.ChangeId;
32 import android.compat.annotation.EnabledSince;
33 import android.compat.annotation.UnsupportedAppUsage;
34 import android.content.ComponentName;
35 import android.content.Context;
36 import android.content.Intent;
37 import android.hardware.soundtrigger.KeyphraseEnrollmentInfo;
38 import android.hardware.soundtrigger.SoundTrigger;
39 import android.media.permission.Identity;
40 import android.media.voice.KeyphraseModelManager;
41 import android.os.Build;
42 import android.os.Bundle;
43 import android.os.Handler;
44 import android.os.IBinder;
45 import android.os.PersistableBundle;
46 import android.os.RemoteException;
47 import android.os.ServiceManager;
48 import android.os.SharedMemory;
49 import android.os.SystemProperties;
50 import android.provider.Settings;
51 import android.util.ArraySet;
52 import android.util.Log;
53 
54 import com.android.internal.annotations.GuardedBy;
55 import com.android.internal.annotations.VisibleForTesting;
56 import com.android.internal.app.IVoiceActionCheckCallback;
57 import com.android.internal.app.IVoiceInteractionManagerService;
58 import com.android.internal.util.function.pooled.PooledLambda;
59 
60 import java.io.FileDescriptor;
61 import java.io.PrintWriter;
62 import java.util.ArrayList;
63 import java.util.Collections;
64 import java.util.List;
65 import java.util.Locale;
66 import java.util.Objects;
67 import java.util.Set;
68 import java.util.concurrent.Executor;
69 
70 /**
71  * Top-level service of the current global voice interactor, which is providing
72  * support for hotwording, the back-end of a {@link android.app.VoiceInteractor}, etc.
73  * The current VoiceInteractionService that has been selected by the user is kept
74  * always running by the system, to allow it to do things like listen for hotwords
75  * in the background to instigate voice interactions.
76  *
77  * <p>Because this service is always running, it should be kept as lightweight as
78  * possible.  Heavy-weight operations (including showing UI) should be implemented
79  * in the associated {@link android.service.voice.VoiceInteractionSessionService} when
80  * an actual voice interaction is taking place, and that service should run in a
81  * separate process from this one.
82  */
83 public class VoiceInteractionService extends Service {
84     static final String TAG = VoiceInteractionService.class.getSimpleName();
85 
86     /**
87      * The {@link Intent} that must be declared as handled by the service.
88      * To be supported, the service must also require the
89      * {@link android.Manifest.permission#BIND_VOICE_INTERACTION} permission so
90      * that other applications can not abuse it.
91      */
92     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
93     public static final String SERVICE_INTERFACE =
94             "android.service.voice.VoiceInteractionService";
95 
96     /**
97      * Name under which a VoiceInteractionService component publishes information about itself.
98      * This meta-data should reference an XML resource containing a
99      * <code>&lt;{@link
100      * android.R.styleable#VoiceInteractionService voice-interaction-service}&gt;</code> tag.
101      */
102     public static final String SERVICE_META_DATA = "android.voice_interaction";
103 
104     /**
105      * For apps targeting Build.VERSION_CODES.UPSIDE_DOWN_CAKE and above, implementors of this
106      * service can create multiple AlwaysOnHotwordDetector instances in parallel. They will
107      * also e ale to create a single SoftwareHotwordDetector in parallel with any other
108      * active AlwaysOnHotwordDetector instances.
109      *
110      * <p>Requirements when this change is enabled:
111      * <ul>
112      *     <li>
113      *         Any number of AlwaysOnHotwordDetector instances can be created in parallel
114      *         as long as they are unique to any other active AlwaysOnHotwordDetector.
115      *     </li>
116      *     <li>
117      *         Only a single instance of SoftwareHotwordDetector can be active at a given
118      *         time. It can be active at the same time as any number of
119      *         AlwaysOnHotwordDetector instances.
120      *     </li>
121      *     <li>
122      *         To release that reference and any resources associated with that reference,
123      *         HotwordDetector#destroy() must be called. An attempt to create an
124      *         HotwordDetector equal to an active HotwordDetector will be rejected
125      *         until HotwordDetector#destroy() is called on the active instance.
126      *     </li>
127      * </ul>
128      *
129      * @hide
130      */
131     @ChangeId
132     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
133     static final long MULTIPLE_ACTIVE_HOTWORD_DETECTORS = 193232191L;
134 
135     private static final boolean SYSPROP_VISUAL_QUERY_SERVICE_ENABLED =
136             SystemProperties.getBoolean("ro.hotword.visual_query_service_enabled", false);
137 
138     IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() {
139         @Override
140         public void ready() {
141             Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
142                     VoiceInteractionService::onReady, VoiceInteractionService.this));
143         }
144 
145         @Override
146         public void shutdown() {
147             Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
148                     VoiceInteractionService::onShutdownInternal, VoiceInteractionService.this));
149         }
150 
151         @Override
152         public void soundModelsChanged() {
153             Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
154                     VoiceInteractionService::onSoundModelsChangedInternal,
155                     VoiceInteractionService.this));
156         }
157 
158         @Override
159         public void launchVoiceAssistFromKeyguard() {
160             Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
161                     VoiceInteractionService::onLaunchVoiceAssistFromKeyguard,
162                     VoiceInteractionService.this));
163         }
164 
165         @Override
166         public void getActiveServiceSupportedActions(List<String> voiceActions,
167                 IVoiceActionCheckCallback callback) {
168             Handler.getMain().executeOrSendMessage(
169                     PooledLambda.obtainMessage(VoiceInteractionService::onHandleVoiceActionCheck,
170                             VoiceInteractionService.this,
171                             voiceActions,
172                             callback));
173         }
174 
175         @Override
176         public void prepareToShowSession(Bundle args, int flags) {
177             Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
178                     VoiceInteractionService::onPrepareToShowSession,
179                     VoiceInteractionService.this, args, flags));
180         }
181 
182         @Override
183         public void showSessionFailed(@NonNull Bundle args) {
184             Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
185                     VoiceInteractionService::onShowSessionFailed,
186                     VoiceInteractionService.this, args));
187         }
188 
189         @Override
190         public void detectorRemoteExceptionOccurred(@NonNull IBinder token, int detectorType) {
191             Log.d(TAG, "detectorRemoteExceptionOccurred");
192             Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
193                     VoiceInteractionService::onDetectorRemoteException,
194                     VoiceInteractionService.this, token, detectorType));
195         }
196     };
197 
198     IVoiceInteractionManagerService mSystemService;
199 
200     private VisualQueryDetector mActiveVisualQueryDetector;
201 
202     private final Object mLock = new Object();
203 
204     private KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo;
205 
206     private final Set<HotwordDetector> mActiveDetectors = new ArraySet<>();
207 
208     // True if any of the createAOHD methods should use the test ST module.
209     @GuardedBy("mLock")
210     private boolean mTestModuleForAlwaysOnHotwordDetectorEnabled = false;
211 
onDetectorRemoteException(@onNull IBinder token, int detectorType)212     private void onDetectorRemoteException(@NonNull IBinder token, int detectorType) {
213         Log.d(TAG, "onDetectorRemoteException for " + HotwordDetector.detectorTypeToString(
214                 detectorType));
215         mActiveDetectors.forEach(detector -> {
216             // TODO: handle normal detector, VQD
217             if (detectorType == HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP
218                     && detector instanceof AlwaysOnHotwordDetector) {
219                 AlwaysOnHotwordDetector alwaysOnDetector = (AlwaysOnHotwordDetector) detector;
220                 if (alwaysOnDetector.isSameToken(token)) {
221                     alwaysOnDetector.onDetectorRemoteException();
222                 }
223             } else if (detectorType == HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE
224                     && detector instanceof SoftwareHotwordDetector) {
225                 SoftwareHotwordDetector softwareDetector = (SoftwareHotwordDetector) detector;
226                 if (softwareDetector.isSameToken(token)) {
227                     softwareDetector.onDetectorRemoteException();
228                 }
229             }
230         });
231     }
232 
233     /**
234      * Called when a user has activated an affordance to launch voice assist from the Keyguard.
235      *
236      * <p>This method will only be called if the VoiceInteractionService has set
237      * {@link android.R.attr#supportsLaunchVoiceAssistFromKeyguard} and the Keyguard is showing.</p>
238      *
239      * <p>A valid implementation must start a new activity that should use {@link
240      * android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED} to display
241      * on top of the lock screen.</p>
242      */
onLaunchVoiceAssistFromKeyguard()243     public void onLaunchVoiceAssistFromKeyguard() {
244     }
245 
246     /**
247      * Notify the interactor when the system prepares to show session. The system is going to
248      * bind the session service.
249      *
250      * @param args  The arguments that were supplied to {@link #showSession(Bundle, int)}.
251      *              It always includes {@link VoiceInteractionSession#KEY_SHOW_SESSION_ID}.
252      * @param flags The show flags originally provided to {@link #showSession(Bundle, int)}.
253      * @see #showSession(Bundle, int)
254      * @see #onShowSessionFailed(Bundle)
255      * @see VoiceInteractionSession#onShow(Bundle, int)
256      * @see VoiceInteractionSession#show(Bundle, int)
257      */
onPrepareToShowSession(@onNull Bundle args, int flags)258     public void onPrepareToShowSession(@NonNull Bundle args, int flags) {
259     }
260 
261     /**
262      * Called when the show session failed. E.g. When the system bound the session service failed.
263      *
264      * @param args Additional info about the show session attempt that failed. For now, includes
265      *             {@link VoiceInteractionSession#KEY_SHOW_SESSION_ID}.
266      * @see #showSession(Bundle, int)
267      * @see #onPrepareToShowSession(Bundle, int)
268      * @see VoiceInteractionSession#onShow(Bundle, int)
269      * @see VoiceInteractionSession#show(Bundle, int)
270      */
onShowSessionFailed(@onNull Bundle args)271     public void onShowSessionFailed(@NonNull Bundle args) {
272     }
273 
274     /**
275      * Check whether the given service component is the currently active
276      * VoiceInteractionService.
277      */
isActiveService(Context context, ComponentName service)278     public static boolean isActiveService(Context context, ComponentName service) {
279         String cur = Settings.Secure.getString(context.getContentResolver(),
280                 Settings.Secure.VOICE_INTERACTION_SERVICE);
281         if (cur == null || cur.isEmpty()) {
282             return false;
283         }
284         ComponentName curComp = ComponentName.unflattenFromString(cur);
285         if (curComp == null) {
286             return false;
287         }
288         return curComp.equals(service);
289     }
290 
291     /**
292      * Set contextual options you would always like to have disabled when a session
293      * is shown.  The flags may be any combination of
294      * {@link VoiceInteractionSession#SHOW_WITH_ASSIST VoiceInteractionSession.SHOW_WITH_ASSIST} and
295      * {@link VoiceInteractionSession#SHOW_WITH_SCREENSHOT
296      * VoiceInteractionSession.SHOW_WITH_SCREENSHOT}.
297      */
setDisabledShowContext(int flags)298     public void setDisabledShowContext(int flags) {
299         try {
300             mSystemService.setDisabledShowContext(flags);
301         } catch (RemoteException e) {
302         }
303     }
304 
305     /**
306      * Return the value set by {@link #setDisabledShowContext}.
307      */
getDisabledShowContext()308     public int getDisabledShowContext() {
309         try {
310             return mSystemService.getDisabledShowContext();
311         } catch (RemoteException e) {
312             return 0;
313         }
314     }
315 
316     /**
317      * Request that the associated {@link android.service.voice.VoiceInteractionSession} be
318      * shown to the user, starting it if necessary.
319      * @param args Arbitrary arguments that will be propagated to the session.
320      * @param flags Indicates additional optional behavior that should be performed.  May
321      * be any combination of
322      * {@link VoiceInteractionSession#SHOW_WITH_ASSIST VoiceInteractionSession.SHOW_WITH_ASSIST} and
323      * {@link VoiceInteractionSession#SHOW_WITH_SCREENSHOT
324      * VoiceInteractionSession.SHOW_WITH_SCREENSHOT}
325      * to request that the system generate and deliver assist data on the current foreground
326      * app as part of showing the session UI.
327      */
showSession(Bundle args, int flags)328     public void showSession(Bundle args, int flags) {
329         if (mSystemService == null) {
330             throw new IllegalStateException("Not available until onReady() is called");
331         }
332         try {
333             mSystemService.showSession(args, flags, getAttributionTag());
334         } catch (RemoteException e) {
335         }
336     }
337 
338     /**
339      * Request to query for what extended voice actions this service supports. This method will
340      * be called when the system checks the supported actions of this
341      * {@link VoiceInteractionService}. Supported actions may be delivered to
342      * {@link VoiceInteractionSession} later to request a session to perform an action.
343      *
344      * <p>Voice actions are defined in support libraries and could vary based on platform context.
345      * For example, car related voice actions will be defined in car support libraries.
346      *
347      * @param voiceActions A set of checked voice actions.
348      * @return Returns a subset of checked voice actions. Additional voice actions in the
349      * returned set will be ignored. Returns empty set if no actions are supported.
350      */
351     @NonNull
onGetSupportedVoiceActions(@onNull Set<String> voiceActions)352     public Set<String> onGetSupportedVoiceActions(@NonNull Set<String> voiceActions) {
353         return Collections.emptySet();
354     }
355 
356     @Override
onBind(Intent intent)357     public IBinder onBind(Intent intent) {
358         if (SERVICE_INTERFACE.equals(intent.getAction())) {
359             return mInterface.asBinder();
360         }
361         return null;
362     }
363 
364     /**
365      * Called during service initialization to tell you when the system is ready
366      * to receive interaction from it. You should generally do initialization here
367      * rather than in {@link #onCreate}. Methods such as {@link #showSession} will
368      * not be operational until this point.
369      */
onReady()370     public void onReady() {
371         mSystemService = IVoiceInteractionManagerService.Stub.asInterface(
372                 ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
373         Objects.requireNonNull(mSystemService);
374         try {
375             mSystemService.asBinder().linkToDeath(mDeathRecipient, 0);
376         } catch (RemoteException e) {
377             Log.wtf(TAG, "unable to link to death with system service");
378         }
379         mKeyphraseEnrollmentInfo = new KeyphraseEnrollmentInfo(getPackageManager());
380     }
381 
382     private IBinder.DeathRecipient mDeathRecipient = () -> {
383         Log.e(TAG, "system service binder died shutting down");
384         Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
385                 VoiceInteractionService::onShutdownInternal, VoiceInteractionService.this));
386     };
387 
onShutdownInternal()388     private void onShutdownInternal() {
389         onShutdown();
390         // Stop any active recognitions when shutting down.
391         // This ensures that if implementations forget to stop any active recognition,
392         // It's still guaranteed to have been stopped.
393         // This helps with cases where the voice interaction implementation is changed
394         // by the user.
395         safelyShutdownAllHotwordDetectors(true);
396     }
397 
398     /**
399      * Called during service de-initialization to tell you when the system is shutting the
400      * service down.
401      * At this point this service may no longer be the active {@link VoiceInteractionService}.
402      */
onShutdown()403     public void onShutdown() {
404     }
405 
onSoundModelsChangedInternal()406     private void onSoundModelsChangedInternal() {
407         synchronized (this) {
408             // TODO: Stop recognition if a sound model that was being recognized gets deleted.
409             mActiveDetectors.forEach(detector -> {
410                 if (detector instanceof AlwaysOnHotwordDetector) {
411                     ((AlwaysOnHotwordDetector) detector).onSoundModelsChanged();
412                 }
413             });
414         }
415     }
416 
onHandleVoiceActionCheck(List<String> voiceActions, IVoiceActionCheckCallback callback)417     private void onHandleVoiceActionCheck(List<String> voiceActions,
418             IVoiceActionCheckCallback callback) {
419         if (callback != null) {
420             try {
421                 Set<String> voiceActionsSet = new ArraySet<>(voiceActions);
422                 Set<String> resultSet = onGetSupportedVoiceActions(voiceActionsSet);
423                 callback.onComplete(new ArrayList<>(resultSet));
424             } catch (RemoteException e) {
425             }
426         }
427     }
428 
429     /**
430      * List available ST modules to attach to for test purposes.
431      * @hide
432      */
433     @TestApi
434     @NonNull
listModuleProperties()435     public final List<SoundTrigger.ModuleProperties> listModuleProperties() {
436         Identity identity = new Identity();
437         identity.packageName = ActivityThread.currentOpPackageName();
438         try {
439             return mSystemService.listModuleProperties(identity);
440         } catch (RemoteException e) {
441             throw e.rethrowFromSystemServer();
442         }
443     }
444 
445     /**
446      * Creates an {@link AlwaysOnHotwordDetector} for the given keyphrase and locale.
447      * This instance must be retained and used by the client.
448      * Calling this a second time invalidates the previously created hotword detector
449      * which can no longer be used to manage recognition.
450      *
451      * <p>Note: If there are any active detectors that are created by using
452      * {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
453      * AlwaysOnHotwordDetector.Callback)} or {@link #createAlwaysOnHotwordDetector(String, Locale,
454      * PersistableBundle, SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)} or
455      * {@link #createHotwordDetector(PersistableBundle, SharedMemory, HotwordDetector.Callback)} or
456      * {@link #createHotwordDetector(PersistableBundle, SharedMemory, Executor,
457      * HotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}.
458      *
459      * <p>Note that the callback will be executed on the current thread. If the current thread
460      * doesn't have a looper, it will throw a {@link RuntimeException}. To specify the execution
461      * thread, use {@link #createAlwaysOnHotwordDetector(String, Locale, Executor,
462      * AlwaysOnHotwordDetector.Callback)}.
463      *
464      * @param keyphrase The keyphrase that's being used, for example "Hello Android".
465      * @param locale The locale for which the enrollment needs to be performed.
466      * @param callback The callback to notify of detection events.
467      * @return An always-on hotword detector for the given keyphrase and locale.
468      *
469      * @throws SecurityException if the caller does not hold required permissions
470      * @throws IllegalStateException if there is no DSP hardware support when a caller has a
471      * target SDK of API level 34 or above.
472      *
473      * @deprecated Use {@link #createAlwaysOnHotwordDetector(String, Locale, Executor,
474      *             AlwaysOnHotwordDetector.Callback)} instead.
475      * @hide
476      */
477     @SystemApi
478     @Deprecated
479     @NonNull
createAlwaysOnHotwordDetector( @uppressLint"MissingNullability") String keyphrase, @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale, @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback)480     public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(
481             @SuppressLint("MissingNullability") String keyphrase,  // TODO: nullability properly
482             @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale,
483             @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) {
484         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
485                 /* supportHotwordDetectionService= */ false, /* options= */ null,
486                 /* sharedMemory= */ null, /* moduleProperties */ null,
487                 /* executor= */ null, callback);
488     }
489 
490     /**
491      * Creates an {@link AlwaysOnHotwordDetector} for the given keyphrase and locale.
492      * This instance must be retained and used by the client.
493      * Calling this a second time invalidates the previously created hotword detector
494      * which can no longer be used to manage recognition.
495      *
496      * <p>Note: If there are any active detectors that are created by using
497      * {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
498      * AlwaysOnHotwordDetector.Callback)} or {@link #createAlwaysOnHotwordDetector(String, Locale,
499      * PersistableBundle, SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)} or
500      * {@link #createHotwordDetector(PersistableBundle, SharedMemory, HotwordDetector.Callback)} or
501      * {@link #createHotwordDetector(PersistableBundle, SharedMemory, Executor,
502      * HotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}.
503      *
504      * @param keyphrase The keyphrase that's being used, for example "Hello Android".
505      * @param locale The locale for which the enrollment needs to be performed.
506      * @param executor The executor on which to run the callback.
507      * @param callback The callback to notify of detection events.
508      * @return An always-on hotword detector for the given keyphrase and locale.
509      *
510      * @throws SecurityException if the caller does not hold required permissions
511      * @throws IllegalStateException if there is no DSP hardware support when a caller has a
512      * target SDK of API level 34 or above.
513      *
514      * @hide
515      */
516     @SystemApi
517     @NonNull
createAlwaysOnHotwordDetector( @onNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback)518     public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(
519             @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale,
520             @NonNull @CallbackExecutor Executor executor,
521             @NonNull AlwaysOnHotwordDetector.Callback callback) {
522         // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
523 
524         Objects.requireNonNull(keyphrase);
525         Objects.requireNonNull(locale);
526         Objects.requireNonNull(executor);
527         Objects.requireNonNull(callback);
528         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
529                 /* supportHotwordDetectionService= */ false, /* options= */ null,
530                 /* sharedMemory= */ null, /* moduleProperties= */ null, executor, callback);
531     }
532 
533     /**
534      * Same as {@link createAlwaysOnHotwordDetector(String, Locale, Executor,
535      * AlwaysOnHotwordDetector.Callback)}, but allow explicit selection of the underlying ST
536      * module to attach to.
537      * Use {@link #listModuleProperties()} to get available modules to attach to.
538      * @hide
539      */
540     @TestApi
541     @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION)
542     @NonNull
createAlwaysOnHotwordDetectorForTest( @onNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale, @NonNull SoundTrigger.ModuleProperties moduleProperties, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback)543     public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(
544             @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale,
545             @NonNull SoundTrigger.ModuleProperties moduleProperties,
546             @NonNull @CallbackExecutor Executor executor,
547             @NonNull AlwaysOnHotwordDetector.Callback callback) {
548 
549         Objects.requireNonNull(keyphrase);
550         Objects.requireNonNull(locale);
551         Objects.requireNonNull(moduleProperties);
552         Objects.requireNonNull(executor);
553         Objects.requireNonNull(callback);
554         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
555                 /* supportHotwordDetectionService= */ false, /* options= */ null,
556                 /* sharedMemory= */ null, moduleProperties, executor, callback);
557     }
558 
559 
560     /**
561      * Create an {@link AlwaysOnHotwordDetector} and trigger a {@link HotwordDetectionService}
562      * service, then it will also pass the read-only data to hotword detection service.
563      *
564      * Like {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)
565      * }. Before calling this function, you should set a valid hotword detection service with
566      * android:hotwordDetectionService in an android.voice_interaction metadata file and set
567      * android:isolatedProcess="true" in the AndroidManifest.xml of hotword detection service.
568      * Otherwise it will throw IllegalStateException. After calling this function, the system will
569      * also trigger a hotword detection service and pass the read-only data back to it.
570      *
571      * <p>Note: The system will trigger hotword detection service after calling this function when
572      * all conditions meet the requirements.
573      *
574      * <p>Note: If there are any active detectors that are created by using
575      * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)} or
576      * {@link #createAlwaysOnHotwordDetector(String, Locale, Executor,
577      * AlwaysOnHotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}.
578      *
579      * <p>Note that the callback will be executed on the current thread. If the current thread
580      * doesn't have a looper, it will throw a {@link RuntimeException}. To specify the execution
581      * thread, use {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle,
582      * SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)}.
583      *
584      * @param keyphrase The keyphrase that's being used, for example "Hello Android".
585      * @param locale The locale for which the enrollment needs to be performed.
586      * @param options Application configuration data provided by the
587      * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or
588      * other contents that can be used to communicate with other processes.
589      * @param sharedMemory The unrestricted data blob provided by the
590      * {@link VoiceInteractionService}. Use this to provide the hotword models data or other
591      * such data to the trusted process.
592      * @param callback The callback to notify of detection events.
593      * @return An always-on hotword detector for the given keyphrase and locale.
594      *
595      * @throws SecurityException if the caller does not hold required permissions
596      * @throws IllegalStateException if the hotword detection service is not set, isolated process
597      * is not set, or there is no DSP hardware support when a caller has a target SDK of API
598      * level 34 or above.
599      *
600      * @deprecated Use {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle,
601      *             SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)} instead.
602      * @hide
603      */
604     @SystemApi
605     @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION)
606     @Deprecated
607     @NonNull
createAlwaysOnHotwordDetector( @uppressLint"MissingNullability") String keyphrase, @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback)608     public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(
609             @SuppressLint("MissingNullability") String keyphrase,  // TODO: nullability properly
610             @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale,
611             @Nullable PersistableBundle options,
612             @Nullable SharedMemory sharedMemory,
613             @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) {
614         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
615                 /* supportHotwordDetectionService= */ true, options, sharedMemory,
616                 /* modulProperties */ null, /* executor= */ null, callback);
617     }
618 
619     /**
620      * Create an {@link AlwaysOnHotwordDetector} and trigger a {@link HotwordDetectionService}
621      * service, then it will also pass the read-only data to hotword detection service.
622      *
623      * Like {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)
624      * }. Before calling this function, you should set a valid hotword detection service with
625      * android:hotwordDetectionService in an android.voice_interaction metadata file and set
626      * android:isolatedProcess="true" in the AndroidManifest.xml of hotword detection service.
627      * Otherwise it will throw IllegalStateException. After calling this function, the system will
628      * also trigger a hotword detection service and pass the read-only data back to it.
629      *
630      * <p>Note: The system will trigger hotword detection service after calling this function when
631      * all conditions meet the requirements.
632      *
633      * <p>Note: If there are any active detectors that are created by using
634      * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)} or
635      * {@link #createAlwaysOnHotwordDetector(String, Locale, Executor,
636      * AlwaysOnHotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}.
637      *
638      * @param keyphrase The keyphrase that's being used, for example "Hello Android".
639      * @param locale The locale for which the enrollment needs to be performed.
640      * @param options Application configuration data provided by the
641      * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or
642      * other contents that can be used to communicate with other processes.
643      * @param sharedMemory The unrestricted data blob provided by the
644      * {@link VoiceInteractionService}. Use this to provide the hotword models data or other
645      * such data to the trusted process.
646      * @param executor The executor on which to run the callback.
647      * @param callback The callback to notify of detection events.
648      * @return An always-on hotword detector for the given keyphrase and locale.
649      *
650      * @throws SecurityException if the caller does not hold required permissions
651      * @throws IllegalStateException if the hotword detection service is not set, isolated process
652      * is not set, or there is no DSP hardware support when a caller has a target SDK of API level
653      * 34 or above.
654      *
655      * @hide
656      */
657     @SystemApi
658     @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION)
659     @NonNull
createAlwaysOnHotwordDetector( @onNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback)660     public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(
661             @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale,
662             @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory,
663             @NonNull @CallbackExecutor Executor executor,
664             @NonNull AlwaysOnHotwordDetector.Callback callback) {
665         // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
666 
667         Objects.requireNonNull(keyphrase);
668         Objects.requireNonNull(locale);
669         Objects.requireNonNull(executor);
670         Objects.requireNonNull(callback);
671         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
672                 /* supportHotwordDetectionService= */ true, options, sharedMemory,
673                 /* moduleProperties= */ null, executor, callback);
674     }
675 
676     /**
677      * Same as {@link createAlwaysOnHotwordDetector(String, Locale,
678      * PersistableBundle, SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)},
679      * but allow explicit selection of the underlying ST module to attach to.
680      * Use {@link #listModuleProperties()} to get available modules to attach to.
681      * @hide
682      */
683     @TestApi
684     @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION)
685     @NonNull
createAlwaysOnHotwordDetectorForTest( @onNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull SoundTrigger.ModuleProperties moduleProperties, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback)686     public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(
687             @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale,
688             @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory,
689             @NonNull SoundTrigger.ModuleProperties moduleProperties,
690             @NonNull @CallbackExecutor Executor executor,
691             @NonNull AlwaysOnHotwordDetector.Callback callback) {
692 
693         Objects.requireNonNull(keyphrase);
694         Objects.requireNonNull(locale);
695         Objects.requireNonNull(moduleProperties);
696         Objects.requireNonNull(executor);
697         Objects.requireNonNull(callback);
698         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
699                 /* supportHotwordDetectionService= */ true, options, sharedMemory,
700                 moduleProperties, executor, callback);
701     }
702 
703 
704 
createAlwaysOnHotwordDetectorInternal( @uppressLint"MissingNullability") String keyphrase, @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale, boolean supportHotwordDetectionService, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @Nullable SoundTrigger.ModuleProperties moduleProperties, @Nullable @CallbackExecutor Executor executor, @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback)705     private AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorInternal(
706             @SuppressLint("MissingNullability") String keyphrase,  // TODO: nullability properly
707             @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale,
708             boolean supportHotwordDetectionService,
709             @Nullable PersistableBundle options,
710             @Nullable SharedMemory sharedMemory,
711             @Nullable SoundTrigger.ModuleProperties moduleProperties,
712             @Nullable @CallbackExecutor Executor executor,
713             @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) {
714 
715         if (mSystemService == null) {
716             throw new IllegalStateException("Not available until onReady() is called");
717         }
718         synchronized (mLock) {
719             if (!CompatChanges.isChangeEnabled(MULTIPLE_ACTIVE_HOTWORD_DETECTORS)) {
720                 // Allow only one concurrent recognition via the APIs.
721                 safelyShutdownAllHotwordDetectors(false);
722             } else {
723                 for (HotwordDetector detector : mActiveDetectors) {
724                     if (detector.isUsingSandboxedDetectionService()
725                             != supportHotwordDetectionService) {
726                         throw new IllegalStateException(
727                                 "It disallows to create trusted and non-trusted detectors "
728                                         + "at the same time.");
729                     } else if (detector instanceof AlwaysOnHotwordDetector) {
730                         throw new IllegalStateException(
731                                 "There is already an active AlwaysOnHotwordDetector. "
732                                         + "It must be destroyed to create a new one.");
733                     }
734                 }
735             }
736 
737             AlwaysOnHotwordDetector dspDetector = new AlwaysOnHotwordDetector(keyphrase, locale,
738                     executor, callback, mKeyphraseEnrollmentInfo, mSystemService,
739                     getApplicationContext().getApplicationInfo().targetSdkVersion,
740                     supportHotwordDetectionService, getAttributionTag());
741             mActiveDetectors.add(dspDetector);
742 
743             try {
744                 dspDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed);
745                 // Check if we are currently overridden, and should use the test module.
746                 if (mTestModuleForAlwaysOnHotwordDetectorEnabled) {
747                     moduleProperties = getTestModuleProperties();
748                 }
749                 // If moduleProperties is null, the default STModule is used.
750                 dspDetector.initialize(options, sharedMemory, moduleProperties);
751             } catch (Exception e) {
752                 mActiveDetectors.remove(dspDetector);
753                 dspDetector.destroy();
754                 throw e;
755             }
756             return dspDetector;
757         }
758     }
759 
760     /**
761      * Creates a {@link HotwordDetector} and initializes the application's
762      * {@link HotwordDetectionService} using {@code options} and {code sharedMemory}.
763      *
764      * <p>To be able to call this, you need to set android:hotwordDetectionService in the
765      * android.voice_interaction metadata file to a valid hotword detection service, and set
766      * android:isolatedProcess="true" in the hotword detection service's declaration. Otherwise,
767      * this throws an {@link IllegalStateException}.
768      *
769      * <p>This instance must be retained and used by the client.
770      * Calling this a second time invalidates the previously created hotword detector
771      * which can no longer be used to manage recognition.
772      *
773      * <p>Using this has a noticeable impact on battery, since the microphone is kept open
774      * for the lifetime of the recognition {@link HotwordDetector#startRecognition() session}. On
775      * devices where hardware filtering is available (such as through a DSP), it's highly
776      * recommended to use {@link #createAlwaysOnHotwordDetector} instead.
777      *
778      * <p>Note: If there are any active detectors that are created by using
779      * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)} or
780      * {@link #createAlwaysOnHotwordDetector(String, Locale, Executor,
781      * AlwaysOnHotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}.
782      *
783      * <p>Note that the callback will be executed on the main thread. To specify the execution
784      * thread, use {@link #createHotwordDetector(PersistableBundle, SharedMemory, Executor,
785      * HotwordDetector.Callback)}.
786      *
787      * @param options Application configuration data to be provided to the
788      * {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or
789      * other contents that can be used to communicate with other processes.
790      * @param sharedMemory The unrestricted data blob to be provided to the
791      * {@link HotwordDetectionService}. Use this to provide hotword models or other such data to the
792      * sandboxed process.
793      * @param callback The callback to notify of detection events.
794      * @return A hotword detector for the given audio format.
795      *
796      * @see #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
797      * AlwaysOnHotwordDetector.Callback)
798      *
799      * @see #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
800      * Executor, AlwaysOnHotwordDetector.Callback)
801      *
802      * @deprecated Use {@link #createHotwordDetector(PersistableBundle, SharedMemory, Executor,
803      *             HotwordDetector.Callback)} instead.
804      * @hide
805      */
806     @SystemApi
807     @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION)
808     @Deprecated
809     @NonNull
createHotwordDetector( @ullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull HotwordDetector.Callback callback)810     public final HotwordDetector createHotwordDetector(
811             @Nullable PersistableBundle options,
812             @Nullable SharedMemory sharedMemory,
813             @NonNull HotwordDetector.Callback callback) {
814         return createHotwordDetectorInternal(options, sharedMemory, /* executor= */ null, callback);
815     }
816 
817     /**
818      * Creates a {@link HotwordDetector} and initializes the application's
819      * {@link HotwordDetectionService} using {@code options} and {code sharedMemory}.
820      *
821      * <p>To be able to call this, you need to set android:hotwordDetectionService in the
822      * android.voice_interaction metadata file to a valid hotword detection service, and set
823      * android:isolatedProcess="true" in the hotword detection service's declaration. Otherwise,
824      * this throws an {@link IllegalStateException}.
825      *
826      * <p>This instance must be retained and used by the client.
827      * Calling this a second time invalidates the previously created hotword detector
828      * which can no longer be used to manage recognition.
829      *
830      * <p>Using this has a noticeable impact on battery, since the microphone is kept open
831      * for the lifetime of the recognition {@link HotwordDetector#startRecognition() session}. On
832      * devices where hardware filtering is available (such as through a DSP), it's highly
833      * recommended to use {@link #createAlwaysOnHotwordDetector} instead.
834      *
835      * <p>Note: If there are any active detectors that are created by using
836      * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)} or
837      * {@link #createAlwaysOnHotwordDetector(String, Locale, Executor,
838      * AlwaysOnHotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}.
839      *
840      * @param options Application configuration data to be provided to the
841      * {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or
842      * other contents that can be used to communicate with other processes.
843      * @param sharedMemory The unrestricted data blob to be provided to the
844      * {@link HotwordDetectionService}. Use this to provide hotword models or other such data to the
845      * sandboxed process.
846      * @param executor The executor on which to run the callback.
847      * @param callback The callback to notify of detection events.
848      * @return A hotword detector for the given audio format.
849      *
850      * @see #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
851      * AlwaysOnHotwordDetector.Callback)
852      *
853      * @see #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
854      * Executor, AlwaysOnHotwordDetector.Callback)
855      *
856      * @hide
857      */
858     @SystemApi
859     @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION)
860     @NonNull
createHotwordDetector( @ullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull @CallbackExecutor Executor executor, @NonNull HotwordDetector.Callback callback)861     public final HotwordDetector createHotwordDetector(
862             @Nullable PersistableBundle options,
863             @Nullable SharedMemory sharedMemory,
864             @NonNull @CallbackExecutor Executor executor,
865             @NonNull HotwordDetector.Callback callback) {
866         // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
867 
868         Objects.requireNonNull(executor);
869         Objects.requireNonNull(callback);
870         return createHotwordDetectorInternal(options, sharedMemory, executor, callback);
871     }
872 
createHotwordDetectorInternal( @ullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @Nullable @CallbackExecutor Executor executor, @NonNull HotwordDetector.Callback callback)873     private HotwordDetector createHotwordDetectorInternal(
874             @Nullable PersistableBundle options,
875             @Nullable SharedMemory sharedMemory,
876             @Nullable @CallbackExecutor Executor executor,
877             @NonNull HotwordDetector.Callback callback) {
878         if (mSystemService == null) {
879             throw new IllegalStateException("Not available until onReady() is called");
880         }
881         synchronized (mLock) {
882             if (!CompatChanges.isChangeEnabled(MULTIPLE_ACTIVE_HOTWORD_DETECTORS)) {
883                 // Allow only one concurrent recognition via the APIs.
884                 safelyShutdownAllHotwordDetectors(false);
885             } else {
886                 for (HotwordDetector detector : mActiveDetectors) {
887                     if (!detector.isUsingSandboxedDetectionService()) {
888                         throw new IllegalStateException(
889                                 "It disallows to create trusted and non-trusted detectors "
890                                         + "at the same time.");
891                     } else if (detector instanceof SoftwareHotwordDetector) {
892                         throw new IllegalStateException(
893                                 "There is already an active SoftwareHotwordDetector. "
894                                         + "It must be destroyed to create a new one.");
895                     }
896                 }
897             }
898 
899             SoftwareHotwordDetector softwareHotwordDetector =
900                     new SoftwareHotwordDetector(mSystemService, /* audioFormat= */ null,
901                             executor, callback, getAttributionTag());
902             mActiveDetectors.add(softwareHotwordDetector);
903 
904             try {
905                 softwareHotwordDetector.registerOnDestroyListener(
906                         this::onHotwordDetectorDestroyed);
907                 softwareHotwordDetector.initialize(options, sharedMemory);
908             } catch (Exception e) {
909                 mActiveDetectors.remove(softwareHotwordDetector);
910                 softwareHotwordDetector.destroy();
911                 throw e;
912             }
913             return softwareHotwordDetector;
914         }
915     }
916 
917     /**
918      * Creates a {@link VisualQueryDetector} and initializes the application's
919      * {@link VisualQueryDetectionService} using {@code options} and {@code sharedMemory}.
920      *
921      * <p>To be able to call this, you need to set android:visualQueryDetectionService in the
922      * android.voice_interaction metadata file to a valid visual query detection service, and set
923      * android:isolatedProcess="true" in the service's declaration. Otherwise, this throws an
924      * {@link IllegalStateException}.
925      *
926      * <p>Using this has a noticeable impact on battery, since the microphone is kept open
927      * for the lifetime of the recognition {@link VisualQueryDetector#startRecognition() session}.
928      *
929      * @param options Application configuration data to be provided to the
930      * {@link VisualQueryDetectionService}. PersistableBundle does not allow any remotable objects
931      * or other contents that can be used to communicate with other processes.
932      * @param sharedMemory The unrestricted data blob to be provided to the
933      * {@link VisualQueryDetectionService}. Use this to provide models or other such data to the
934      * sandboxed process.
935      * @param callback The callback to notify of detection events. Single threaded or sequential
936      *                 executors are recommended for the callback are not guaranteed to be executed
937      *                 in the order of how they were called from the
938      *                 {@link VisualQueryDetectionService}.
939      * @return An instanece of {@link VisualQueryDetector}.
940      * @throws IllegalStateException when there is an existing {@link VisualQueryDetector}, or when
941      * there is a non-trusted hotword detector running.
942      *
943      * @hide
944      */
945     @SystemApi
946     @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION)
947     @NonNull
createVisualQueryDetector( @ullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull @CallbackExecutor Executor executor, @NonNull VisualQueryDetector.Callback callback)948     public final VisualQueryDetector createVisualQueryDetector(
949             @Nullable PersistableBundle options,
950             @Nullable SharedMemory sharedMemory,
951             @NonNull @CallbackExecutor Executor executor,
952             @NonNull VisualQueryDetector.Callback callback) {
953         Objects.requireNonNull(executor);
954         Objects.requireNonNull(callback);
955 
956         if (!SYSPROP_VISUAL_QUERY_SERVICE_ENABLED) {
957             throw new IllegalStateException("VisualQueryDetectionService is not enabled on this "
958                     + "system. Please set ro.hotword.visual_query_service_enabled to true.");
959         }
960         if (mSystemService == null) {
961             throw new IllegalStateException("Not available until onReady() is called");
962         }
963         synchronized (mLock) {
964             if (mActiveVisualQueryDetector != null) {
965                 throw new IllegalStateException(
966                             "There is already an active VisualQueryDetector. "
967                                     + "It must be destroyed to create a new one.");
968             }
969             for (HotwordDetector detector : mActiveDetectors) {
970                 if (!detector.isUsingSandboxedDetectionService()) {
971                     throw new IllegalStateException(
972                             "It disallows to create trusted and non-trusted detectors "
973                                     + "at the same time.");
974                 }
975             }
976 
977             VisualQueryDetector visualQueryDetector =
978                     new VisualQueryDetector(mSystemService, executor, callback, this,
979                             getAttributionTag());
980             HotwordDetector visualQueryDetectorInitializationDelegate =
981                     visualQueryDetector.getInitializationDelegate();
982             mActiveDetectors.add(visualQueryDetectorInitializationDelegate);
983 
984             try {
985                 visualQueryDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed);
986                 visualQueryDetector.initialize(options, sharedMemory);
987             } catch (Exception e) {
988                 mActiveDetectors.remove(visualQueryDetectorInitializationDelegate);
989                 visualQueryDetector.destroy();
990                 throw e;
991             }
992             mActiveVisualQueryDetector = visualQueryDetector;
993             return visualQueryDetector;
994         }
995     }
996 
997     /**
998      * Creates an {@link KeyphraseModelManager} to use for enrolling voice models outside of the
999      * pre-bundled system voice models.
1000      * @hide
1001      */
1002     @SystemApi
1003     @RequiresPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
1004     @NonNull
createKeyphraseModelManager()1005     public final KeyphraseModelManager createKeyphraseModelManager() {
1006         if (mSystemService == null) {
1007             throw new IllegalStateException("Not available until onReady() is called");
1008         }
1009         synchronized (mLock) {
1010             return new KeyphraseModelManager(mSystemService);
1011         }
1012     }
1013 
1014     /**
1015      * @return Details of keyphrases available for enrollment.
1016      * @hide
1017      */
1018     @VisibleForTesting
getKeyphraseEnrollmentInfo()1019     protected final KeyphraseEnrollmentInfo getKeyphraseEnrollmentInfo() {
1020         return mKeyphraseEnrollmentInfo;
1021     }
1022 
1023 
1024     /**
1025      * Configure {@link createAlwaysOnHotwordDetector(String, Locale,
1026      * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)}
1027      * and similar overloads to utilize the test SoundTrigger module instead of the
1028      * actual DSP module.
1029      * @param isEnabled - {@code true} if subsequently created {@link AlwaysOnHotwordDetector}
1030      * objects should attach to a test module. {@code false} if subsequently created
1031      * {@link AlwaysOnHotwordDetector} should attach to the actual DSP module.
1032      * @hide
1033      */
1034     @TestApi
setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean isEnabled)1035     public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean isEnabled) {
1036         synchronized (mLock) {
1037             mTestModuleForAlwaysOnHotwordDetectorEnabled = isEnabled;
1038         }
1039     }
1040 
1041     /**
1042      * Get the {@link SoundTrigger.ModuleProperties} representing the fake
1043      * STHAL to attach to via {@link createAlwaysOnHotwordDetector(String, Locale,
1044      * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} and
1045      * similar overloads for test purposes.
1046      * @return ModuleProperties to use for test purposes.
1047      */
getTestModuleProperties()1048     private final @NonNull SoundTrigger.ModuleProperties getTestModuleProperties() {
1049         var moduleProps = listModuleProperties()
1050                 .stream()
1051                 .filter((SoundTrigger.ModuleProperties prop)
1052                         -> prop.getSupportedModelArch().equals(SoundTrigger.FAKE_HAL_ARCH))
1053                 .findFirst()
1054                 .orElse(null);
1055         if (moduleProps == null) {
1056             throw new IllegalStateException("Fake ST HAL should always be available");
1057         }
1058         return moduleProps;
1059     }
1060 
1061     /**
1062      * Checks if a given keyphrase and locale are supported to create an
1063      * {@link AlwaysOnHotwordDetector}.
1064      *
1065      * @return true if the keyphrase and locale combination is supported, false otherwise.
1066      * @hide
1067      */
1068     @UnsupportedAppUsage
isKeyphraseAndLocaleSupportedForHotword(String keyphrase, Locale locale)1069     public final boolean isKeyphraseAndLocaleSupportedForHotword(String keyphrase, Locale locale) {
1070         if (mKeyphraseEnrollmentInfo == null) {
1071             return false;
1072         }
1073         return mKeyphraseEnrollmentInfo.getKeyphraseMetadata(keyphrase, locale) != null;
1074     }
1075 
safelyShutdownAllHotwordDetectors(boolean shouldShutDownVisualQueryDetector)1076     private void safelyShutdownAllHotwordDetectors(boolean shouldShutDownVisualQueryDetector) {
1077         synchronized (mLock) {
1078             mActiveDetectors.forEach(detector -> {
1079                 try {
1080                     // Skip destroying VisualQueryDetector if HotwordDetectors are created
1081                     if (!(mActiveVisualQueryDetector != null
1082                             && detector == mActiveVisualQueryDetector.getInitializationDelegate())
1083                             || shouldShutDownVisualQueryDetector) {
1084                         detector.destroy();
1085                     }
1086                 } catch (Exception ex) {
1087                     Log.i(TAG, "exception destroying HotwordDetector", ex);
1088                 }
1089             });
1090         }
1091     }
1092 
onHotwordDetectorDestroyed(@onNull HotwordDetector detector)1093     private void onHotwordDetectorDestroyed(@NonNull HotwordDetector detector) {
1094         synchronized (mLock) {
1095             if (mActiveVisualQueryDetector != null
1096                     && detector == mActiveVisualQueryDetector.getInitializationDelegate()) {
1097                 mActiveVisualQueryDetector = null;
1098             }
1099             mActiveDetectors.remove(detector);
1100         }
1101     }
1102 
1103     /**
1104      * Provide hints to be reflected in the system UI.
1105      *
1106      * @param hints Arguments used to show UI.
1107      */
setUiHints(@onNull Bundle hints)1108     public final void setUiHints(@NonNull Bundle hints) {
1109         if (hints == null) {
1110             throw new IllegalArgumentException("Hints must be non-null");
1111         }
1112 
1113         try {
1114             mSystemService.setUiHints(hints);
1115         } catch (RemoteException e) {
1116             throw e.rethrowFromSystemServer();
1117         }
1118     }
1119 
1120     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)1121     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1122         pw.println("VOICE INTERACTION");
1123         synchronized (mLock) {
1124             pw.println("  Sandboxed Detector(s):");
1125             if (mActiveDetectors.size() == 0) {
1126                 pw.println("    No detector.");
1127             } else {
1128                 mActiveDetectors.forEach(detector -> {
1129                     pw.print("  Using sandboxed detection service=");
1130                     pw.println(detector.isUsingSandboxedDetectionService());
1131                     detector.dump("    ", pw);
1132                     pw.println();
1133                 });
1134             }
1135             pw.println("Available Model Enrollment Applications:");
1136             pw.println("  " + mKeyphraseEnrollmentInfo);
1137         }
1138     }
1139 }
1140