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 
388 
onShutdownInternal()389     private void onShutdownInternal() {
390         onShutdown();
391         // Stop any active recognitions when shutting down.
392         // This ensures that if implementations forget to stop any active recognition,
393         // It's still guaranteed to have been stopped.
394         // This helps with cases where the voice interaction implementation is changed
395         // by the user.
396         safelyShutdownAllHotwordDetectors(true);
397     }
398 
399     /**
400      * Called during service de-initialization to tell you when the system is shutting the
401      * service down.
402      * At this point this service may no longer be the active {@link VoiceInteractionService}.
403      */
onShutdown()404     public void onShutdown() {
405     }
406 
onSoundModelsChangedInternal()407     private void onSoundModelsChangedInternal() {
408         synchronized (this) {
409             // TODO: Stop recognition if a sound model that was being recognized gets deleted.
410             mActiveDetectors.forEach(detector -> {
411                 if (detector instanceof AlwaysOnHotwordDetector) {
412                     ((AlwaysOnHotwordDetector) detector).onSoundModelsChanged();
413                 }
414             });
415         }
416     }
417 
onHandleVoiceActionCheck(List<String> voiceActions, IVoiceActionCheckCallback callback)418     private void onHandleVoiceActionCheck(List<String> voiceActions,
419             IVoiceActionCheckCallback callback) {
420         if (callback != null) {
421             try {
422                 Set<String> voiceActionsSet = new ArraySet<>(voiceActions);
423                 Set<String> resultSet = onGetSupportedVoiceActions(voiceActionsSet);
424                 callback.onComplete(new ArrayList<>(resultSet));
425             } catch (RemoteException e) {
426             }
427         }
428     }
429 
430     /**
431      * List available ST modules to attach to for test purposes.
432      * @hide
433      */
434     @TestApi
435     @NonNull
listModuleProperties()436     public final List<SoundTrigger.ModuleProperties> listModuleProperties() {
437         Identity identity = new Identity();
438         identity.packageName = ActivityThread.currentOpPackageName();
439         try {
440             return mSystemService.listModuleProperties(identity);
441         } catch (RemoteException e) {
442             throw e.rethrowFromSystemServer();
443         }
444     }
445 
446     /**
447      * Creates an {@link AlwaysOnHotwordDetector} for the given keyphrase and locale.
448      * This instance must be retained and used by the client.
449      * Calling this a second time invalidates the previously created hotword detector
450      * which can no longer be used to manage recognition.
451      *
452      * <p>Note: If there are any active detectors that are created by using
453      * {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
454      * AlwaysOnHotwordDetector.Callback)} or {@link #createAlwaysOnHotwordDetector(String, Locale,
455      * PersistableBundle, SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)} or
456      * {@link #createHotwordDetector(PersistableBundle, SharedMemory, HotwordDetector.Callback)} or
457      * {@link #createHotwordDetector(PersistableBundle, SharedMemory, Executor,
458      * HotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}.
459      *
460      * <p>Note that the callback will be executed on the current thread. If the current thread
461      * doesn't have a looper, it will throw a {@link RuntimeException}. To specify the execution
462      * thread, use {@link #createAlwaysOnHotwordDetector(String, Locale, Executor,
463      * AlwaysOnHotwordDetector.Callback)}.
464      *
465      * @param keyphrase The keyphrase that's being used, for example "Hello Android".
466      * @param locale The locale for which the enrollment needs to be performed.
467      * @param callback The callback to notify of detection events.
468      * @return An always-on hotword detector for the given keyphrase and locale.
469      *
470      * @throws SecurityException if the caller does not hold required permissions
471      * @throws IllegalStateException if there is no DSP hardware support when a caller has a
472      * target SDK of API level 34 or above.
473      *
474      * @deprecated Use {@link #createAlwaysOnHotwordDetector(String, Locale, Executor,
475      *             AlwaysOnHotwordDetector.Callback)} instead.
476      * @hide
477      */
478     @SystemApi
479     @Deprecated
480     @NonNull
createAlwaysOnHotwordDetector( @uppressLintR) String keyphrase, @SuppressLint({R, R}) Locale locale, @SuppressLint(R) AlwaysOnHotwordDetector.Callback callback)481     public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(
482             @SuppressLint("MissingNullability") String keyphrase,  // TODO: nullability properly
483             @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale,
484             @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) {
485         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
486                 /* supportHotwordDetectionService= */ false, /* options= */ null,
487                 /* sharedMemory= */ null, /* moduleProperties */ null,
488                 /* executor= */ null, callback);
489     }
490 
491     /**
492      * Creates an {@link AlwaysOnHotwordDetector} for the given keyphrase and locale.
493      * This instance must be retained and used by the client.
494      * Calling this a second time invalidates the previously created hotword detector
495      * which can no longer be used to manage recognition.
496      *
497      * <p>Note: If there are any active detectors that are created by using
498      * {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
499      * AlwaysOnHotwordDetector.Callback)} or {@link #createAlwaysOnHotwordDetector(String, Locale,
500      * PersistableBundle, SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)} or
501      * {@link #createHotwordDetector(PersistableBundle, SharedMemory, HotwordDetector.Callback)} or
502      * {@link #createHotwordDetector(PersistableBundle, SharedMemory, Executor,
503      * HotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}.
504      *
505      * @param keyphrase The keyphrase that's being used, for example "Hello Android".
506      * @param locale The locale for which the enrollment needs to be performed.
507      * @param executor The executor on which to run the callback.
508      * @param callback The callback to notify of detection events.
509      * @return An always-on hotword detector for the given keyphrase and locale.
510      *
511      * @throws SecurityException if the caller does not hold required permissions
512      * @throws IllegalStateException if there is no DSP hardware support when a caller has a
513      * target SDK of API level 34 or above.
514      *
515      * @hide
516      */
517     @SystemApi
518     @NonNull
createAlwaysOnHotwordDetector( @onNull String keyphrase, @SuppressLint(R) @NonNull Locale locale, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback)519     public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(
520             @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale,
521             @NonNull @CallbackExecutor Executor executor,
522             @NonNull AlwaysOnHotwordDetector.Callback callback) {
523         // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
524 
525         Objects.requireNonNull(keyphrase);
526         Objects.requireNonNull(locale);
527         Objects.requireNonNull(executor);
528         Objects.requireNonNull(callback);
529         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
530                 /* supportHotwordDetectionService= */ false, /* options= */ null,
531                 /* sharedMemory= */ null, /* moduleProperties= */ null, executor, callback);
532     }
533 
534     /**
535      * Same as {@link createAlwaysOnHotwordDetector(String, Locale, Executor,
536      * AlwaysOnHotwordDetector.Callback)}, but allow explicit selection of the underlying ST
537      * module to attach to.
538      * Use {@link #listModuleProperties()} to get available modules to attach to.
539      * @hide
540      */
541     @TestApi
542     @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION)
543     @NonNull
createAlwaysOnHotwordDetectorForTest( @onNull String keyphrase, @SuppressLint(R) @NonNull Locale locale, @NonNull SoundTrigger.ModuleProperties moduleProperties, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback)544     public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(
545             @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale,
546             @NonNull SoundTrigger.ModuleProperties moduleProperties,
547             @NonNull @CallbackExecutor Executor executor,
548             @NonNull AlwaysOnHotwordDetector.Callback callback) {
549 
550         Objects.requireNonNull(keyphrase);
551         Objects.requireNonNull(locale);
552         Objects.requireNonNull(moduleProperties);
553         Objects.requireNonNull(executor);
554         Objects.requireNonNull(callback);
555         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
556                 /* supportHotwordDetectionService= */ false, /* options= */ null,
557                 /* sharedMemory= */ null, moduleProperties, executor, callback);
558     }
559 
560 
561     /**
562      * Create an {@link AlwaysOnHotwordDetector} and trigger a {@link HotwordDetectionService}
563      * service, then it will also pass the read-only data to hotword detection service.
564      *
565      * Like {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)
566      * }. Before calling this function, you should set a valid hotword detection service with
567      * android:hotwordDetectionService in an android.voice_interaction metadata file and set
568      * android:isolatedProcess="true" in the AndroidManifest.xml of hotword detection service.
569      * Otherwise it will throw IllegalStateException. After calling this function, the system will
570      * also trigger a hotword detection service and pass the read-only data back to it.
571      *
572      * <p>Note: The system will trigger hotword detection service after calling this function when
573      * all conditions meet the requirements.
574      *
575      * <p>Note: If there are any active detectors that are created by using
576      * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)} or
577      * {@link #createAlwaysOnHotwordDetector(String, Locale, Executor,
578      * AlwaysOnHotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}.
579      *
580      * <p>Note that the callback will be executed on the current thread. If the current thread
581      * doesn't have a looper, it will throw a {@link RuntimeException}. To specify the execution
582      * thread, use {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle,
583      * SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)}.
584      *
585      * @param keyphrase The keyphrase that's being used, for example "Hello Android".
586      * @param locale The locale for which the enrollment needs to be performed.
587      * @param options Application configuration data provided by the
588      * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or
589      * other contents that can be used to communicate with other processes.
590      * @param sharedMemory The unrestricted data blob provided by the
591      * {@link VoiceInteractionService}. Use this to provide the hotword models data or other
592      * such data to the trusted process.
593      * @param callback The callback to notify of detection events.
594      * @return An always-on hotword detector for the given keyphrase and locale.
595      *
596      * @throws SecurityException if the caller does not hold required permissions
597      * @throws IllegalStateException if the hotword detection service is not set, isolated process
598      * is not set, or there is no DSP hardware support when a caller has a target SDK of API
599      * level 34 or above.
600      *
601      * @deprecated Use {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle,
602      *             SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)} instead.
603      * @hide
604      */
605     @SystemApi
606     @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION)
607     @Deprecated
608     @NonNull
createAlwaysOnHotwordDetector( @uppressLintR) String keyphrase, @SuppressLint({R, R}) Locale locale, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @SuppressLint(R) AlwaysOnHotwordDetector.Callback callback)609     public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(
610             @SuppressLint("MissingNullability") String keyphrase,  // TODO: nullability properly
611             @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale,
612             @Nullable PersistableBundle options,
613             @Nullable SharedMemory sharedMemory,
614             @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) {
615         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
616                 /* supportHotwordDetectionService= */ true, options, sharedMemory,
617                 /* modulProperties */ null, /* executor= */ null, callback);
618     }
619 
620     /**
621      * Create an {@link AlwaysOnHotwordDetector} and trigger a {@link HotwordDetectionService}
622      * service, then it will also pass the read-only data to hotword detection service.
623      *
624      * Like {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)
625      * }. Before calling this function, you should set a valid hotword detection service with
626      * android:hotwordDetectionService in an android.voice_interaction metadata file and set
627      * android:isolatedProcess="true" in the AndroidManifest.xml of hotword detection service.
628      * Otherwise it will throw IllegalStateException. After calling this function, the system will
629      * also trigger a hotword detection service and pass the read-only data back to it.
630      *
631      * <p>Note: The system will trigger hotword detection service after calling this function when
632      * all conditions meet the requirements.
633      *
634      * <p>Note: If there are any active detectors that are created by using
635      * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)} or
636      * {@link #createAlwaysOnHotwordDetector(String, Locale, Executor,
637      * AlwaysOnHotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}.
638      *
639      * @param keyphrase The keyphrase that's being used, for example "Hello Android".
640      * @param locale The locale for which the enrollment needs to be performed.
641      * @param options Application configuration data provided by the
642      * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or
643      * other contents that can be used to communicate with other processes.
644      * @param sharedMemory The unrestricted data blob provided by the
645      * {@link VoiceInteractionService}. Use this to provide the hotword models data or other
646      * such data to the trusted process.
647      * @param executor The executor on which to run the callback.
648      * @param callback The callback to notify of detection events.
649      * @return An always-on hotword detector for the given keyphrase and locale.
650      *
651      * @throws SecurityException if the caller does not hold required permissions
652      * @throws IllegalStateException if the hotword detection service is not set, isolated process
653      * is not set, or there is no DSP hardware support when a caller has a target SDK of API level
654      * 34 or above.
655      *
656      * @hide
657      */
658     @SystemApi
659     @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION)
660     @NonNull
createAlwaysOnHotwordDetector( @onNull String keyphrase, @SuppressLint(R) @NonNull Locale locale, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback)661     public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(
662             @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale,
663             @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory,
664             @NonNull @CallbackExecutor Executor executor,
665             @NonNull AlwaysOnHotwordDetector.Callback callback) {
666         // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
667 
668         Objects.requireNonNull(keyphrase);
669         Objects.requireNonNull(locale);
670         Objects.requireNonNull(executor);
671         Objects.requireNonNull(callback);
672         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
673                 /* supportHotwordDetectionService= */ true, options, sharedMemory,
674                 /* moduleProperties= */ null, executor, callback);
675     }
676 
677     /**
678      * Same as {@link createAlwaysOnHotwordDetector(String, Locale,
679      * PersistableBundle, SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)},
680      * but allow explicit selection of the underlying ST module to attach to.
681      * Use {@link #listModuleProperties()} to get available modules to attach to.
682      * @hide
683      */
684     @TestApi
685     @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION)
686     @NonNull
createAlwaysOnHotwordDetectorForTest( @onNull String keyphrase, @SuppressLint(R) @NonNull Locale locale, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull SoundTrigger.ModuleProperties moduleProperties, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback)687     public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(
688             @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale,
689             @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory,
690             @NonNull SoundTrigger.ModuleProperties moduleProperties,
691             @NonNull @CallbackExecutor Executor executor,
692             @NonNull AlwaysOnHotwordDetector.Callback callback) {
693 
694         Objects.requireNonNull(keyphrase);
695         Objects.requireNonNull(locale);
696         Objects.requireNonNull(moduleProperties);
697         Objects.requireNonNull(executor);
698         Objects.requireNonNull(callback);
699         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
700                 /* supportHotwordDetectionService= */ true, options, sharedMemory,
701                 moduleProperties, executor, callback);
702     }
703 
704 
705 
createAlwaysOnHotwordDetectorInternal( @uppressLintR) String keyphrase, @SuppressLint({R, R}) Locale locale, boolean supportHotwordDetectionService, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @Nullable SoundTrigger.ModuleProperties moduleProperties, @Nullable @CallbackExecutor Executor executor, @SuppressLint(R) AlwaysOnHotwordDetector.Callback callback)706     private AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorInternal(
707             @SuppressLint("MissingNullability") String keyphrase,  // TODO: nullability properly
708             @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale,
709             boolean supportHotwordDetectionService,
710             @Nullable PersistableBundle options,
711             @Nullable SharedMemory sharedMemory,
712             @Nullable SoundTrigger.ModuleProperties moduleProperties,
713             @Nullable @CallbackExecutor Executor executor,
714             @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) {
715 
716         if (mSystemService == null) {
717             throw new IllegalStateException("Not available until onReady() is called");
718         }
719         synchronized (mLock) {
720             if (!CompatChanges.isChangeEnabled(MULTIPLE_ACTIVE_HOTWORD_DETECTORS)) {
721                 // Allow only one concurrent recognition via the APIs.
722                 safelyShutdownAllHotwordDetectors(false);
723             } else {
724                 for (HotwordDetector detector : mActiveDetectors) {
725                     if (detector.isUsingSandboxedDetectionService()
726                             != supportHotwordDetectionService) {
727                         throw new IllegalStateException(
728                                 "It disallows to create trusted and non-trusted detectors "
729                                         + "at the same time.");
730                     } else if (detector instanceof AlwaysOnHotwordDetector) {
731                         throw new IllegalStateException(
732                                 "There is already an active AlwaysOnHotwordDetector. "
733                                         + "It must be destroyed to create a new one.");
734                     }
735                 }
736             }
737 
738             AlwaysOnHotwordDetector dspDetector = new AlwaysOnHotwordDetector(keyphrase, locale,
739                     executor, callback, mKeyphraseEnrollmentInfo, mSystemService,
740                     getApplicationContext().getApplicationInfo().targetSdkVersion,
741                     supportHotwordDetectionService);
742             mActiveDetectors.add(dspDetector);
743 
744             try {
745                 dspDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed);
746                 // Check if we are currently overridden, and should use the test module.
747                 if (mTestModuleForAlwaysOnHotwordDetectorEnabled) {
748                     moduleProperties = getTestModuleProperties();
749                 }
750                 // If moduleProperties is null, the default STModule is used.
751                 dspDetector.initialize(options, sharedMemory, moduleProperties);
752             } catch (Exception e) {
753                 mActiveDetectors.remove(dspDetector);
754                 dspDetector.destroy();
755                 throw e;
756             }
757             return dspDetector;
758         }
759     }
760 
761     /**
762      * Creates a {@link HotwordDetector} and initializes the application's
763      * {@link HotwordDetectionService} using {@code options} and {code sharedMemory}.
764      *
765      * <p>To be able to call this, you need to set android:hotwordDetectionService in the
766      * android.voice_interaction metadata file to a valid hotword detection service, and set
767      * android:isolatedProcess="true" in the hotword detection service's declaration. Otherwise,
768      * this throws an {@link IllegalStateException}.
769      *
770      * <p>This instance must be retained and used by the client.
771      * Calling this a second time invalidates the previously created hotword detector
772      * which can no longer be used to manage recognition.
773      *
774      * <p>Using this has a noticeable impact on battery, since the microphone is kept open
775      * for the lifetime of the recognition {@link HotwordDetector#startRecognition() session}. On
776      * devices where hardware filtering is available (such as through a DSP), it's highly
777      * recommended to use {@link #createAlwaysOnHotwordDetector} instead.
778      *
779      * <p>Note: If there are any active detectors that are created by using
780      * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)} or
781      * {@link #createAlwaysOnHotwordDetector(String, Locale, Executor,
782      * AlwaysOnHotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}.
783      *
784      * <p>Note that the callback will be executed on the main thread. To specify the execution
785      * thread, use {@link #createHotwordDetector(PersistableBundle, SharedMemory, Executor,
786      * HotwordDetector.Callback)}.
787      *
788      * @param options Application configuration data to be provided to the
789      * {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or
790      * other contents that can be used to communicate with other processes.
791      * @param sharedMemory The unrestricted data blob to be provided to the
792      * {@link HotwordDetectionService}. Use this to provide hotword models or other such data to the
793      * sandboxed process.
794      * @param callback The callback to notify of detection events.
795      * @return A hotword detector for the given audio format.
796      *
797      * @see #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
798      * AlwaysOnHotwordDetector.Callback)
799      *
800      * @see #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
801      * Executor, AlwaysOnHotwordDetector.Callback)
802      *
803      * @deprecated Use {@link #createHotwordDetector(PersistableBundle, SharedMemory, Executor,
804      *             HotwordDetector.Callback)} instead.
805      * @hide
806      */
807     @SystemApi
808     @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION)
809     @Deprecated
810     @NonNull
createHotwordDetector( @ullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull HotwordDetector.Callback callback)811     public final HotwordDetector createHotwordDetector(
812             @Nullable PersistableBundle options,
813             @Nullable SharedMemory sharedMemory,
814             @NonNull HotwordDetector.Callback callback) {
815         return createHotwordDetectorInternal(options, sharedMemory, /* executor= */ null, callback);
816     }
817 
818     /**
819      * Creates a {@link HotwordDetector} and initializes the application's
820      * {@link HotwordDetectionService} using {@code options} and {code sharedMemory}.
821      *
822      * <p>To be able to call this, you need to set android:hotwordDetectionService in the
823      * android.voice_interaction metadata file to a valid hotword detection service, and set
824      * android:isolatedProcess="true" in the hotword detection service's declaration. Otherwise,
825      * this throws an {@link IllegalStateException}.
826      *
827      * <p>This instance must be retained and used by the client.
828      * Calling this a second time invalidates the previously created hotword detector
829      * which can no longer be used to manage recognition.
830      *
831      * <p>Using this has a noticeable impact on battery, since the microphone is kept open
832      * for the lifetime of the recognition {@link HotwordDetector#startRecognition() session}. On
833      * devices where hardware filtering is available (such as through a DSP), it's highly
834      * recommended to use {@link #createAlwaysOnHotwordDetector} instead.
835      *
836      * <p>Note: If there are any active detectors that are created by using
837      * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)} or
838      * {@link #createAlwaysOnHotwordDetector(String, Locale, Executor,
839      * AlwaysOnHotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}.
840      *
841      * @param options Application configuration data to be provided to the
842      * {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or
843      * other contents that can be used to communicate with other processes.
844      * @param sharedMemory The unrestricted data blob to be provided to the
845      * {@link HotwordDetectionService}. Use this to provide hotword models or other such data to the
846      * sandboxed process.
847      * @param executor The executor on which to run the callback.
848      * @param callback The callback to notify of detection events.
849      * @return A hotword detector for the given audio format.
850      *
851      * @see #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
852      * AlwaysOnHotwordDetector.Callback)
853      *
854      * @see #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
855      * Executor, AlwaysOnHotwordDetector.Callback)
856      *
857      * @hide
858      */
859     @SystemApi
860     @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION)
861     @NonNull
createHotwordDetector( @ullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull @CallbackExecutor Executor executor, @NonNull HotwordDetector.Callback callback)862     public final HotwordDetector createHotwordDetector(
863             @Nullable PersistableBundle options,
864             @Nullable SharedMemory sharedMemory,
865             @NonNull @CallbackExecutor Executor executor,
866             @NonNull HotwordDetector.Callback callback) {
867         // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
868 
869         Objects.requireNonNull(executor);
870         Objects.requireNonNull(callback);
871         return createHotwordDetectorInternal(options, sharedMemory, executor, callback);
872     }
873 
createHotwordDetectorInternal( @ullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @Nullable @CallbackExecutor Executor executor, @NonNull HotwordDetector.Callback callback)874     private HotwordDetector createHotwordDetectorInternal(
875             @Nullable PersistableBundle options,
876             @Nullable SharedMemory sharedMemory,
877             @Nullable @CallbackExecutor Executor executor,
878             @NonNull HotwordDetector.Callback callback) {
879         if (mSystemService == null) {
880             throw new IllegalStateException("Not available until onReady() is called");
881         }
882         synchronized (mLock) {
883             if (!CompatChanges.isChangeEnabled(MULTIPLE_ACTIVE_HOTWORD_DETECTORS)) {
884                 // Allow only one concurrent recognition via the APIs.
885                 safelyShutdownAllHotwordDetectors(false);
886             } else {
887                 for (HotwordDetector detector : mActiveDetectors) {
888                     if (!detector.isUsingSandboxedDetectionService()) {
889                         throw new IllegalStateException(
890                                 "It disallows to create trusted and non-trusted detectors "
891                                         + "at the same time.");
892                     } else if (detector instanceof SoftwareHotwordDetector) {
893                         throw new IllegalStateException(
894                                 "There is already an active SoftwareHotwordDetector. "
895                                         + "It must be destroyed to create a new one.");
896                     }
897                 }
898             }
899 
900             SoftwareHotwordDetector softwareHotwordDetector =
901                     new SoftwareHotwordDetector(mSystemService, /* audioFormat= */ null,
902                             executor, callback);
903             mActiveDetectors.add(softwareHotwordDetector);
904 
905             try {
906                 softwareHotwordDetector.registerOnDestroyListener(
907                         this::onHotwordDetectorDestroyed);
908                 softwareHotwordDetector.initialize(options, sharedMemory);
909             } catch (Exception e) {
910                 mActiveDetectors.remove(softwareHotwordDetector);
911                 softwareHotwordDetector.destroy();
912                 throw e;
913             }
914             return softwareHotwordDetector;
915         }
916     }
917 
918     /**
919      * Creates a {@link VisualQueryDetector} and initializes the application's
920      * {@link VisualQueryDetectionService} using {@code options} and {@code sharedMemory}.
921      *
922      * <p>To be able to call this, you need to set android:visualQueryDetectionService in the
923      * android.voice_interaction metadata file to a valid visual query detection service, and set
924      * android:isolatedProcess="true" in the service's declaration. Otherwise, this throws an
925      * {@link IllegalStateException}.
926      *
927      * <p>Using this has a noticeable impact on battery, since the microphone is kept open
928      * for the lifetime of the recognition {@link VisualQueryDetector#startRecognition() session}.
929      *
930      * @param options Application configuration data to be provided to the
931      * {@link VisualQueryDetectionService}. PersistableBundle does not allow any remotable objects
932      * or other contents that can be used to communicate with other processes.
933      * @param sharedMemory The unrestricted data blob to be provided to the
934      * {@link VisualQueryDetectionService}. Use this to provide models or other such data to the
935      * sandboxed process.
936      * @param callback The callback to notify of detection events.
937      * @return An instanece of {@link VisualQueryDetector}.
938      * @throws IllegalStateException when there is an existing {@link VisualQueryDetector}, or when
939      * there is a non-trusted hotword detector running.
940      *
941      * @hide
942      */
943     @SystemApi
944     @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION)
945     @NonNull
createVisualQueryDetector( @ullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull @CallbackExecutor Executor executor, @NonNull VisualQueryDetector.Callback callback)946     public final VisualQueryDetector createVisualQueryDetector(
947             @Nullable PersistableBundle options,
948             @Nullable SharedMemory sharedMemory,
949             @NonNull @CallbackExecutor Executor executor,
950             @NonNull VisualQueryDetector.Callback callback) {
951         Objects.requireNonNull(executor);
952         Objects.requireNonNull(callback);
953 
954         if (!SYSPROP_VISUAL_QUERY_SERVICE_ENABLED) {
955             throw new IllegalStateException("VisualQueryDetectionService is not enabled on this "
956                     + "system. Please set ro.hotword.visual_query_service_enabled to true.");
957         }
958         if (mSystemService == null) {
959             throw new IllegalStateException("Not available until onReady() is called");
960         }
961         synchronized (mLock) {
962             if (mActiveVisualQueryDetector != null) {
963                 throw new IllegalStateException(
964                             "There is already an active VisualQueryDetector. "
965                                     + "It must be destroyed to create a new one.");
966             }
967             for (HotwordDetector detector : mActiveDetectors) {
968                 if (!detector.isUsingSandboxedDetectionService()) {
969                     throw new IllegalStateException(
970                             "It disallows to create trusted and non-trusted detectors "
971                                     + "at the same time.");
972                 }
973             }
974 
975             VisualQueryDetector visualQueryDetector =
976                     new VisualQueryDetector(mSystemService, executor, callback, this);
977             HotwordDetector visualQueryDetectorInitializationDelegate =
978                     visualQueryDetector.getInitializationDelegate();
979             mActiveDetectors.add(visualQueryDetectorInitializationDelegate);
980 
981             try {
982                 visualQueryDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed);
983                 visualQueryDetector.initialize(options, sharedMemory);
984             } catch (Exception e) {
985                 mActiveDetectors.remove(visualQueryDetectorInitializationDelegate);
986                 visualQueryDetector.destroy();
987                 throw e;
988             }
989             mActiveVisualQueryDetector = visualQueryDetector;
990             return visualQueryDetector;
991         }
992     }
993 
994     /**
995      * Creates an {@link KeyphraseModelManager} to use for enrolling voice models outside of the
996      * pre-bundled system voice models.
997      * @hide
998      */
999     @SystemApi
1000     @RequiresPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
1001     @NonNull
createKeyphraseModelManager()1002     public final KeyphraseModelManager createKeyphraseModelManager() {
1003         if (mSystemService == null) {
1004             throw new IllegalStateException("Not available until onReady() is called");
1005         }
1006         synchronized (mLock) {
1007             return new KeyphraseModelManager(mSystemService);
1008         }
1009     }
1010 
1011     /**
1012      * @return Details of keyphrases available for enrollment.
1013      * @hide
1014      */
1015     @VisibleForTesting
getKeyphraseEnrollmentInfo()1016     protected final KeyphraseEnrollmentInfo getKeyphraseEnrollmentInfo() {
1017         return mKeyphraseEnrollmentInfo;
1018     }
1019 
1020 
1021     /**
1022      * Configure {@link createAlwaysOnHotwordDetector(String, Locale,
1023      * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)}
1024      * and similar overloads to utilize the test SoundTrigger module instead of the
1025      * actual DSP module.
1026      * @param isEnabled - {@code true} if subsequently created {@link AlwaysOnHotwordDetector}
1027      * objects should attach to a test module. {@code false} if subsequently created
1028      * {@link AlwaysOnHotwordDetector} should attach to the actual DSP module.
1029      * @hide
1030      */
1031     @TestApi
setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean isEnabled)1032     public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean isEnabled) {
1033         synchronized (mLock) {
1034             mTestModuleForAlwaysOnHotwordDetectorEnabled = isEnabled;
1035         }
1036     }
1037 
1038     /**
1039      * Get the {@link SoundTrigger.ModuleProperties} representing the fake
1040      * STHAL to attach to via {@link createAlwaysOnHotwordDetector(String, Locale,
1041      * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} and
1042      * similar overloads for test purposes.
1043      * @return ModuleProperties to use for test purposes.
1044      */
getTestModuleProperties()1045     private final @NonNull SoundTrigger.ModuleProperties getTestModuleProperties() {
1046         var moduleProps = listModuleProperties()
1047                 .stream()
1048                 .filter((SoundTrigger.ModuleProperties prop)
1049                         -> prop.getSupportedModelArch().equals(SoundTrigger.FAKE_HAL_ARCH))
1050                 .findFirst()
1051                 .orElse(null);
1052         if (moduleProps == null) {
1053             throw new IllegalStateException("Fake ST HAL should always be available");
1054         }
1055         return moduleProps;
1056     }
1057 
1058     /**
1059      * Checks if a given keyphrase and locale are supported to create an
1060      * {@link AlwaysOnHotwordDetector}.
1061      *
1062      * @return true if the keyphrase and locale combination is supported, false otherwise.
1063      * @hide
1064      */
1065     @UnsupportedAppUsage
isKeyphraseAndLocaleSupportedForHotword(String keyphrase, Locale locale)1066     public final boolean isKeyphraseAndLocaleSupportedForHotword(String keyphrase, Locale locale) {
1067         if (mKeyphraseEnrollmentInfo == null) {
1068             return false;
1069         }
1070         return mKeyphraseEnrollmentInfo.getKeyphraseMetadata(keyphrase, locale) != null;
1071     }
1072 
safelyShutdownAllHotwordDetectors(boolean shouldShutDownVisualQueryDetector)1073     private void safelyShutdownAllHotwordDetectors(boolean shouldShutDownVisualQueryDetector) {
1074         synchronized (mLock) {
1075             mActiveDetectors.forEach(detector -> {
1076                 try {
1077                     // Skip destroying VisualQueryDetector if HotwordDetectors are created
1078                     if (!(mActiveVisualQueryDetector != null
1079                             && detector == mActiveVisualQueryDetector.getInitializationDelegate())
1080                             || shouldShutDownVisualQueryDetector) {
1081                         detector.destroy();
1082                     }
1083                 } catch (Exception ex) {
1084                     Log.i(TAG, "exception destroying HotwordDetector", ex);
1085                 }
1086             });
1087         }
1088     }
1089 
onHotwordDetectorDestroyed(@onNull HotwordDetector detector)1090     private void onHotwordDetectorDestroyed(@NonNull HotwordDetector detector) {
1091         synchronized (mLock) {
1092             if (mActiveVisualQueryDetector != null
1093                     && detector == mActiveVisualQueryDetector.getInitializationDelegate()) {
1094                 mActiveVisualQueryDetector = null;
1095             }
1096             mActiveDetectors.remove(detector);
1097         }
1098     }
1099 
1100     /**
1101      * Provide hints to be reflected in the system UI.
1102      *
1103      * @param hints Arguments used to show UI.
1104      */
setUiHints(@onNull Bundle hints)1105     public final void setUiHints(@NonNull Bundle hints) {
1106         if (hints == null) {
1107             throw new IllegalArgumentException("Hints must be non-null");
1108         }
1109 
1110         try {
1111             mSystemService.setUiHints(hints);
1112         } catch (RemoteException e) {
1113             throw e.rethrowFromSystemServer();
1114         }
1115     }
1116 
1117     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)1118     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1119         pw.println("VOICE INTERACTION");
1120         synchronized (mLock) {
1121             pw.println("  Sandboxed Detector(s):");
1122             if (mActiveDetectors.size() == 0) {
1123                 pw.println("    No detector.");
1124             } else {
1125                 mActiveDetectors.forEach(detector -> {
1126                     pw.print("  Using sandboxed detection service=");
1127                     pw.println(detector.isUsingSandboxedDetectionService());
1128                     detector.dump("    ", pw);
1129                     pw.println();
1130                 });
1131             }
1132             pw.println("Available Model Enrollment Applications:");
1133             pw.println("  " + mKeyphraseEnrollmentInfo);
1134         }
1135     }
1136 }
1137