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.media.soundtrigger;
18 
19 import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
20 
21 import android.annotation.CallbackExecutor;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.RequiresPermission;
25 import android.annotation.SuppressLint;
26 import android.annotation.SystemApi;
27 import android.annotation.SystemService;
28 import android.annotation.TestApi;
29 import android.app.ActivityThread;
30 import android.compat.annotation.UnsupportedAppUsage;
31 import android.content.ComponentName;
32 import android.content.Context;
33 import android.hardware.soundtrigger.ModelParams;
34 import android.hardware.soundtrigger.SoundTrigger;
35 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
36 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
37 import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
38 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
39 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
40 import android.hardware.soundtrigger.SoundTrigger.SoundModel;
41 import android.media.permission.ClearCallingIdentityContext;
42 import android.media.permission.Identity;
43 import android.media.permission.SafeCloseable;
44 import android.os.Binder;
45 import android.os.Build;
46 import android.os.Bundle;
47 import android.os.Handler;
48 import android.os.IBinder;
49 import android.os.ParcelUuid;
50 import android.os.RemoteException;
51 import android.os.ServiceManager;
52 import android.provider.Settings;
53 import android.util.Slog;
54 
55 import com.android.internal.app.ISoundTriggerService;
56 import com.android.internal.app.ISoundTriggerSession;
57 
58 import java.util.HashMap;
59 import java.util.List;
60 import java.util.Objects;
61 import java.util.UUID;
62 import java.util.concurrent.Executor;
63 
64 /**
65  * This class provides management of non-voice (general sound trigger) based sound recognition
66  * models. Usage of this class is restricted to system or signature applications only. This allows
67  * OEMs to write apps that can manage non-voice based sound trigger models.
68  *
69  * If no ST module is available, {@link getModuleProperties()} will return {@code null}, and all
70  * other methods will throw {@link IllegalStateException}.
71  * @hide
72  */
73 @SystemApi
74 @SystemService(Context.SOUND_TRIGGER_SERVICE)
75 public final class SoundTriggerManager {
76     private static final boolean DBG = false;
77     private static final String TAG = "SoundTriggerManager";
78 
79     private final Context mContext;
80     private final ISoundTriggerService mSoundTriggerService;
81     private final ISoundTriggerSession mSoundTriggerSession;
82     private final IBinder mBinderToken = new Binder();
83 
84     // Stores a mapping from the sound model UUID to the SoundTriggerInstance created by
85     // the createSoundTriggerDetector() call.
86     private final HashMap<UUID, SoundTriggerDetector> mReceiverInstanceMap = new HashMap<>();
87 
88     /**
89      * @hide
90      */
SoundTriggerManager(Context context, ISoundTriggerService soundTriggerService)91     public SoundTriggerManager(Context context, ISoundTriggerService soundTriggerService) {
92         if (DBG) {
93             Slog.i(TAG, "SoundTriggerManager created.");
94         }
95         try {
96             // This assumes that whoever is calling this ctor is the originator of the operations,
97             // as opposed to a service acting on behalf of a separate identity.
98             // Services acting on behalf of some other identity should not be using this class at
99             // all, but rather directly connect to the server and attach with explicit credentials.
100             Identity originatorIdentity = new Identity();
101             originatorIdentity.packageName = ActivityThread.currentOpPackageName();
102 
103             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
104                 ModuleProperties moduleProperties = soundTriggerService
105                         .listModuleProperties(originatorIdentity)
106                         .stream()
107                         .filter(prop -> !prop.getSupportedModelArch()
108                                 .equals(SoundTrigger.FAKE_HAL_ARCH))
109                         .findFirst()
110                         .orElse(null);
111                 if (moduleProperties != null) {
112                     mSoundTriggerSession = soundTriggerService.attachAsOriginator(
113                                                 originatorIdentity,
114                                                 moduleProperties,
115                                                 mBinderToken);
116                 } else {
117                     mSoundTriggerSession = null;
118                 }
119             }
120         } catch (RemoteException e) {
121             throw e.rethrowFromSystemServer();
122         }
123         mContext = context;
124         mSoundTriggerService = soundTriggerService;
125     }
126 
127     /**
128      * Construct a {@link SoundTriggerManager} which connects to a specified module.
129      *
130      * @param moduleProperties - Properties representing the module to attach to
131      * @return - A new {@link SoundTriggerManager} which interfaces with the test module.
132      * @hide
133      */
134     @TestApi
135     @SuppressLint("ManagerLookup")
createManagerForModule( @onNull ModuleProperties moduleProperties)136     public @NonNull SoundTriggerManager createManagerForModule(
137             @NonNull ModuleProperties moduleProperties) {
138         return new SoundTriggerManager(mContext, mSoundTriggerService,
139                 Objects.requireNonNull(moduleProperties));
140     }
141 
142     /**
143      * Construct a {@link SoundTriggerManager} which connects to a ST module
144      * which is available for instrumentation through {@link attachInstrumentation}.
145      *
146      * @return - A new {@link SoundTriggerManager} which interfaces with the test module.
147      * @hide
148      */
149     @TestApi
150     @SuppressLint("ManagerLookup")
createManagerForTestModule()151     public @NonNull SoundTriggerManager createManagerForTestModule() {
152         return new SoundTriggerManager(mContext, mSoundTriggerService, getTestModuleProperties());
153     }
154 
getTestModuleProperties()155     private final @NonNull SoundTrigger.ModuleProperties getTestModuleProperties() {
156         var moduleProps = listModuleProperties()
157                 .stream()
158                 .filter((SoundTrigger.ModuleProperties prop)
159                         -> prop.getSupportedModelArch().equals(SoundTrigger.FAKE_HAL_ARCH))
160                 .findFirst()
161                 .orElse(null);
162         if (moduleProps == null) {
163             throw new AssertionError("Fake ST HAL should always be available");
164         }
165         return moduleProps;
166     }
167 
168     // Helper constructor to create a manager object attached to a specific ST module.
SoundTriggerManager(@onNull Context context, @NonNull ISoundTriggerService soundTriggerService, @NonNull ModuleProperties properties)169     private SoundTriggerManager(@NonNull Context context,
170             @NonNull ISoundTriggerService soundTriggerService,
171             @NonNull ModuleProperties properties) {
172         try {
173             Identity originatorIdentity = new Identity();
174             originatorIdentity.packageName = ActivityThread.currentOpPackageName();
175             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
176                 mSoundTriggerSession = soundTriggerService.attachAsOriginator(
177                                             originatorIdentity,
178                                             Objects.requireNonNull(properties),
179                                             mBinderToken);
180             }
181         } catch (RemoteException e) {
182             throw e.rethrowFromSystemServer();
183         }
184         mContext = Objects.requireNonNull(context);
185         mSoundTriggerService = Objects.requireNonNull(soundTriggerService);
186     }
187 
188     /**
189      * Enumerate the available ST modules. Use {@link createManagerForModule(ModuleProperties)} to
190      * receive a {@link SoundTriggerManager} attached to a specified ST module.
191      * @return - List of available ST modules to attach to.
192      * @hide
193      */
194     @TestApi
listModuleProperties()195     public static @NonNull List<ModuleProperties> listModuleProperties() {
196         try {
197             ISoundTriggerService service = ISoundTriggerService.Stub.asInterface(
198                     ServiceManager.getService(Context.SOUND_TRIGGER_SERVICE));
199             Identity originatorIdentity = new Identity();
200             originatorIdentity.packageName = ActivityThread.currentOpPackageName();
201             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
202                 return service.listModuleProperties(originatorIdentity);
203             }
204         } catch (RemoteException e) {
205             throw e.rethrowFromSystemServer();
206         }
207     }
208 
209     /**
210      * Updates the given sound trigger model.
211      * @deprecated replace with {@link #loadSoundModel}
212      * SoundTriggerService model database will be removed
213      */
214     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
215     @Deprecated
updateModel(Model model)216     public void updateModel(Model model) {
217         if (mSoundTriggerSession == null) {
218             throw new IllegalStateException("No underlying SoundTriggerModule available");
219         }
220         try {
221             mSoundTriggerSession.updateSoundModel(model.getGenericSoundModel());
222         } catch (RemoteException e) {
223             throw e.rethrowFromSystemServer();
224         }
225     }
226 
227     /**
228      * Get {@link SoundTriggerManager.Model} which is registered with the passed UUID
229      *
230      * @param soundModelId UUID associated with a loaded model
231      * @return {@link SoundTriggerManager.Model} associated with UUID soundModelId
232      * @deprecated SoundTriggerService model database will be removed
233      */
234     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
235     @Nullable
236     @Deprecated
getModel(UUID soundModelId)237     public Model getModel(UUID soundModelId) {
238         if (mSoundTriggerSession == null) {
239             throw new IllegalStateException("No underlying SoundTriggerModule available");
240         }
241         try {
242             GenericSoundModel model =
243                     mSoundTriggerSession.getSoundModel(
244                             new ParcelUuid(Objects.requireNonNull(soundModelId)));
245             if (model == null) {
246                 return null;
247             }
248 
249             return new Model(model);
250         } catch (RemoteException e) {
251             throw e.rethrowFromSystemServer();
252         }
253     }
254 
255     /**
256      * Deletes the sound model represented by the provided UUID.
257      * @deprecated replace with {@link #unloadSoundModel}
258      * SoundTriggerService model database will be removed
259      */
260     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
261     @Deprecated
deleteModel(UUID soundModelId)262     public void deleteModel(UUID soundModelId) {
263         if (mSoundTriggerSession == null) {
264             throw new IllegalStateException("No underlying SoundTriggerModule available");
265         }
266 
267         try {
268             mSoundTriggerSession.deleteSoundModel(
269                     new ParcelUuid(Objects.requireNonNull(soundModelId)));
270         } catch (RemoteException e) {
271             throw e.rethrowFromSystemServer();
272         }
273     }
274 
275     /**
276      * Creates an instance of {@link SoundTriggerDetector} which can be used to start/stop
277      * recognition on the model and register for triggers from the model. Note that this call
278      * invalidates any previously returned instances for the same sound model Uuid.
279      *
280      * @param soundModelId UUID of the sound model to create the receiver object for.
281      * @param callback Instance of the {@link SoundTriggerDetector#Callback} object for the
282      * callbacks for the given sound model.
283      * @param handler The Handler to use for the callback operations. A null value will use the
284      * current thread's Looper.
285      * @return Instance of {@link SoundTriggerDetector} or null on error.
286      * @deprecated Use {@link SoundTriggerManager} directly. SoundTriggerDetector does not
287      * ensure callbacks are delivered, and its model state is prone to mismatch.
288      * It will be removed in a subsequent release.
289      */
290     @Nullable
291     @Deprecated
292     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
createSoundTriggerDetector(UUID soundModelId, @NonNull SoundTriggerDetector.Callback callback, @Nullable Handler handler)293     public SoundTriggerDetector createSoundTriggerDetector(UUID soundModelId,
294             @NonNull SoundTriggerDetector.Callback callback, @Nullable Handler handler) {
295         if (mSoundTriggerSession == null) {
296             throw new IllegalStateException("No underlying SoundTriggerModule available");
297         }
298 
299         SoundTriggerDetector oldInstance = mReceiverInstanceMap.get(soundModelId);
300         if (oldInstance != null) {
301             // Shutdown old instance.
302         }
303         try {
304             SoundTriggerDetector newInstance = new SoundTriggerDetector(mSoundTriggerSession,
305                     mSoundTriggerSession.getSoundModel(
306                         new ParcelUuid(Objects.requireNonNull(soundModelId))),
307                     callback, handler);
308             mReceiverInstanceMap.put(soundModelId, newInstance);
309             return newInstance;
310         } catch (RemoteException e) {
311             throw e.rethrowFromSystemServer();
312         }
313    }
314 
315     /**
316      * Class captures the data and fields that represent a non-keyphrase sound model. Use the
317      * factory constructor {@link Model#create()} to create an instance.
318      */
319     // We use encapsulation to expose the SoundTrigger.GenericSoundModel as a SystemApi. This
320     // prevents us from exposing SoundTrigger.GenericSoundModel as an Api.
321     public static class Model {
322 
323         private SoundTrigger.GenericSoundModel mGenericSoundModel;
324 
325         /**
326          * @hide
327          */
Model(SoundTrigger.GenericSoundModel soundTriggerModel)328         Model(SoundTrigger.GenericSoundModel soundTriggerModel) {
329             mGenericSoundModel = soundTriggerModel;
330         }
331 
332         /**
333          * Factory constructor to a voice model to be used with {@link SoundTriggerManager}
334          *
335          * @param modelUuid Unique identifier associated with the model.
336          * @param vendorUuid Unique identifier associated the calling vendor.
337          * @param data Model's data.
338          * @param version Version identifier for the model.
339          * @return Voice model
340          */
341         @NonNull
create(@onNull UUID modelUuid, @NonNull UUID vendorUuid, @Nullable byte[] data, int version)342         public static Model create(@NonNull UUID modelUuid, @NonNull UUID vendorUuid,
343                 @Nullable byte[] data, int version) {
344             Objects.requireNonNull(modelUuid);
345             Objects.requireNonNull(vendorUuid);
346             return new Model(new SoundTrigger.GenericSoundModel(modelUuid, vendorUuid, data,
347                     version));
348         }
349 
350         /**
351          * Factory constructor to a voice model to be used with {@link SoundTriggerManager}
352          *
353          * @param modelUuid Unique identifier associated with the model.
354          * @param vendorUuid Unique identifier associated the calling vendor.
355          * @param data Model's data.
356          * @return Voice model
357          */
358         @NonNull
create(@onNull UUID modelUuid, @NonNull UUID vendorUuid, @Nullable byte[] data)359         public static Model create(@NonNull UUID modelUuid, @NonNull UUID vendorUuid,
360                 @Nullable byte[] data) {
361             return create(modelUuid, vendorUuid, data, -1);
362         }
363 
364         /**
365          * Get the model's unique identifier
366          *
367          * @return UUID associated with the model
368          */
369         @NonNull
getModelUuid()370         public UUID getModelUuid() {
371             return mGenericSoundModel.getUuid();
372         }
373 
374         /**
375          * Get the model's vendor identifier
376          *
377          * @return UUID associated with the vendor of the model
378          */
379         @NonNull
getVendorUuid()380         public UUID getVendorUuid() {
381             return mGenericSoundModel.getVendorUuid();
382         }
383 
384         /**
385          * Get the model's version
386          *
387          * @return Version associated with the model
388          */
getVersion()389         public int getVersion() {
390             return mGenericSoundModel.getVersion();
391         }
392 
393         /**
394          * Get the underlying model data
395          *
396          * @return Backing data of the model
397          */
398         @Nullable
getModelData()399         public byte[] getModelData() {
400             return mGenericSoundModel.getData();
401         }
402 
403         /**
404          * @hide
405          */
getGenericSoundModel()406         SoundTrigger.GenericSoundModel getGenericSoundModel() {
407             return mGenericSoundModel;
408         }
409 
410         /**
411          * Return a {@link SoundTrigger.SoundModel} view of the model for
412          * test purposes.
413          * @hide
414          */
415         @TestApi
getSoundModel()416         public @NonNull SoundTrigger.SoundModel getSoundModel() {
417             return mGenericSoundModel;
418         }
419 
420     }
421 
422 
423     /**
424      * Default message type.
425      * @hide
426      */
427     public static final int FLAG_MESSAGE_TYPE_UNKNOWN = -1;
428     /**
429      * Contents of EXTRA_MESSAGE_TYPE extra for a RecognitionEvent.
430      * @hide
431      */
432     public static final int FLAG_MESSAGE_TYPE_RECOGNITION_EVENT = 0;
433     /**
434      * Contents of EXTRA_MESSAGE_TYPE extra for recognition error events.
435      * @hide
436      */
437     public static final int FLAG_MESSAGE_TYPE_RECOGNITION_ERROR = 1;
438     /**
439      * Contents of EXTRA_MESSAGE_TYPE extra for a recognition paused events.
440      * @hide
441      */
442     public static final int FLAG_MESSAGE_TYPE_RECOGNITION_PAUSED = 2;
443     /**
444      * Contents of EXTRA_MESSAGE_TYPE extra for recognition resumed events.
445      * @hide
446      */
447     public static final int FLAG_MESSAGE_TYPE_RECOGNITION_RESUMED = 3;
448 
449     /**
450      * Extra key in the intent for the type of the message.
451      * @hide
452      */
453     public static final String EXTRA_MESSAGE_TYPE = "android.media.soundtrigger.MESSAGE_TYPE";
454     /**
455      * Extra key in the intent that holds the RecognitionEvent parcelable.
456      * @hide
457      */
458     public static final String EXTRA_RECOGNITION_EVENT = "android.media.soundtrigger.RECOGNITION_EVENT";
459     /**
460      * Extra key in the intent that holds the status in an error message.
461      * @hide
462      */
463     public static final String EXTRA_STATUS = "android.media.soundtrigger.STATUS";
464 
465     /**
466      * Loads a given sound model into the sound trigger. Note the model will be unloaded if there is
467      * an error/the system service is restarted.
468      * @hide
469      */
470     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
471     @UnsupportedAppUsage
472     @TestApi
loadSoundModel(@onNull SoundModel soundModel)473     public int loadSoundModel(@NonNull SoundModel soundModel) {
474         if (mSoundTriggerSession == null) {
475             throw new IllegalStateException("No underlying SoundTriggerModule available");
476         }
477 
478         try {
479             switch (soundModel.getType()) {
480                 case SoundModel.TYPE_GENERIC_SOUND:
481                     return mSoundTriggerSession.loadGenericSoundModel(
482                             (GenericSoundModel) soundModel);
483                 case SoundModel.TYPE_KEYPHRASE:
484                     return mSoundTriggerSession.loadKeyphraseSoundModel(
485                             (KeyphraseSoundModel) soundModel);
486                 default:
487                     Slog.e(TAG, "Unkown model type");
488                     return STATUS_ERROR;
489             }
490         } catch (RemoteException e) {
491             throw e.rethrowFromSystemServer();
492         }
493     }
494 
495     /**
496      * Starts recognition for the given model id. All events from the model will be sent to the
497      * service.
498      *
499      * <p>This only supports generic sound trigger events. For keyphrase events, please use
500      * {@link android.service.voice.VoiceInteractionService}.
501      *
502      * @param soundModelId Id of the sound model
503      * @param params Opaque data sent to each service call of the service as the {@code params}
504      *               argument
505      * @param detectionService The component name of the service that should receive the events.
506      *                         Needs to subclass {@link SoundTriggerDetectionService}
507      * @param config Configures the recognition
508      *
509      * @return {@link SoundTrigger#STATUS_OK} if the recognition could be started, error code
510      *         otherwise
511      *
512      * @hide
513      */
514     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
515     @UnsupportedAppUsage
startRecognition(@onNull UUID soundModelId, @Nullable Bundle params, @NonNull ComponentName detectionService, @NonNull RecognitionConfig config)516     public int startRecognition(@NonNull UUID soundModelId, @Nullable Bundle params,
517         @NonNull ComponentName detectionService, @NonNull RecognitionConfig config) {
518         Objects.requireNonNull(soundModelId);
519         Objects.requireNonNull(detectionService);
520         Objects.requireNonNull(config);
521         if (mSoundTriggerSession == null) {
522             throw new IllegalStateException("No underlying SoundTriggerModule available");
523         }
524         try {
525             return mSoundTriggerSession.startRecognitionForService(new ParcelUuid(soundModelId),
526                 params, detectionService, config);
527         } catch (RemoteException e) {
528             throw e.rethrowFromSystemServer();
529         }
530     }
531 
532     /**
533      * Stops the given model's recognition.
534      * @hide
535      */
536     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
537     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
stopRecognition(UUID soundModelId)538     public int stopRecognition(UUID soundModelId) {
539         if (mSoundTriggerSession == null) {
540             throw new IllegalStateException("No underlying SoundTriggerModule available");
541         }
542         try {
543             return mSoundTriggerSession.stopRecognitionForService(
544                     new ParcelUuid(Objects.requireNonNull(soundModelId)));
545         } catch (RemoteException e) {
546             throw e.rethrowFromSystemServer();
547         }
548     }
549 
550     /**
551      * Removes the given model from memory. Will also stop any pending recognitions.
552      * @hide
553      */
554     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
555     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
unloadSoundModel(UUID soundModelId)556     public int unloadSoundModel(UUID soundModelId) {
557         if (mSoundTriggerSession == null) {
558             throw new IllegalStateException("No underlying SoundTriggerModule available");
559         }
560         try {
561             return mSoundTriggerSession.unloadSoundModel(
562                     new ParcelUuid(Objects.requireNonNull(soundModelId)));
563         } catch (RemoteException e) {
564             throw e.rethrowFromSystemServer();
565         }
566     }
567 
568     /**
569      * Returns true if the given model has had detection started on it.
570      * @hide
571      */
572     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
573     @UnsupportedAppUsage
isRecognitionActive(UUID soundModelId)574     public boolean isRecognitionActive(UUID soundModelId) {
575         if (soundModelId == null || mSoundTriggerSession == null) {
576             return false;
577         }
578         try {
579             return mSoundTriggerSession.isRecognitionActive(
580                     new ParcelUuid(soundModelId));
581         } catch (RemoteException e) {
582             throw e.rethrowFromSystemServer();
583         }
584     }
585 
586     /**
587      * Get the amount of time (in milliseconds) an operation of the
588      * {@link ISoundTriggerDetectionService} is allowed to ask.
589      *
590      * @return The amount of time an sound trigger detection service operation is allowed to last
591      */
getDetectionServiceOperationsTimeout()592     public int getDetectionServiceOperationsTimeout() {
593         try {
594             return Settings.Global.getInt(mContext.getContentResolver(),
595                     Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT);
596         } catch (Settings.SettingNotFoundException e) {
597             return Integer.MAX_VALUE;
598         }
599     }
600 
601     /**
602      * Asynchronously get state of the indicated model.  The model state is returned as
603      * a recognition event in the callback that was registered in the startRecognition
604      * method.
605      * @hide
606      */
607     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
608     @UnsupportedAppUsage
getModelState(UUID soundModelId)609     public int getModelState(UUID soundModelId) {
610         if (mSoundTriggerSession == null) {
611             throw new IllegalStateException("No underlying SoundTriggerModule available");
612         }
613         if (soundModelId == null) {
614             return STATUS_ERROR;
615         }
616         try {
617             return mSoundTriggerSession.getModelState(new ParcelUuid(soundModelId));
618         } catch (RemoteException e) {
619             throw e.rethrowFromSystemServer();
620         }
621     }
622 
623     /**
624      * Get the hardware sound trigger module properties currently loaded.
625      *
626      * @return The properties currently loaded. Returns null if no supported hardware loaded.
627      */
628     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
629     @Nullable
getModuleProperties()630     public ModuleProperties getModuleProperties() {
631         if (mSoundTriggerSession == null) {
632             return null;
633         }
634         try {
635             return mSoundTriggerSession.getModuleProperties();
636         } catch (RemoteException e) {
637             throw e.rethrowFromSystemServer();
638         }
639     }
640 
641     /**
642      * Set a model specific {@link ModelParams} with the given value. This
643      * parameter will keep its value for the duration the model is loaded regardless of starting and
644      * stopping recognition. Once the model is unloaded, the value will be lost.
645      * {@link SoundTriggerManager#queryParameter} should be checked first before calling this
646      * method.
647      *
648      * @param soundModelId UUID of model to apply the parameter value to.
649      * @param modelParam   {@link ModelParams}
650      * @param value        Value to set
651      * @return - {@link SoundTrigger#STATUS_OK} in case of success
652      *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
653      *         - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
654      *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
655      *           if API is not supported by HAL
656      */
657     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
setParameter(@ullable UUID soundModelId, @ModelParams int modelParam, int value)658     public int setParameter(@Nullable UUID soundModelId,
659             @ModelParams int modelParam, int value) {
660         if (mSoundTriggerSession == null) {
661             throw new IllegalStateException("No underlying SoundTriggerModule available");
662         }
663 
664         try {
665             return mSoundTriggerSession.setParameter(
666                     new ParcelUuid(Objects.requireNonNull(soundModelId)), modelParam,
667                     value);
668         } catch (RemoteException e) {
669             throw e.rethrowFromSystemServer();
670         }
671     }
672 
673     /**
674      * Get a model specific {@link ModelParams}. This parameter will keep its value
675      * for the duration the model is loaded regardless of starting and stopping recognition.
676      * Once the model is unloaded, the value will be lost. If the value is not set, a default
677      * value is returned. See {@link ModelParams} for parameter default values.
678      * {@link SoundTriggerManager#queryParameter} should be checked first before
679      * calling this method. Otherwise, an exception can be thrown.
680      *
681      * @param soundModelId UUID of model to get parameter
682      * @param modelParam   {@link ModelParams}
683      * @return value of parameter
684      */
685     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
getParameter(@onNull UUID soundModelId, @ModelParams int modelParam)686     public int getParameter(@NonNull UUID soundModelId,
687             @ModelParams int modelParam) {
688         if (mSoundTriggerSession == null) {
689             throw new IllegalStateException("No underlying SoundTriggerModule available");
690         }
691         try {
692             return mSoundTriggerSession.getParameter(
693                     new ParcelUuid(Objects.requireNonNull(soundModelId)), modelParam);
694         } catch (RemoteException e) {
695             throw e.rethrowFromSystemServer();
696         }
697     }
698 
699     /**
700      * Determine if parameter control is supported for the given model handle.
701      * This method should be checked prior to calling {@link SoundTriggerManager#setParameter} or
702      * {@link SoundTriggerManager#getParameter}.
703      *
704      * @param soundModelId handle of model to get parameter
705      * @param modelParam {@link ModelParams}
706      * @return supported range of parameter, null if not supported
707      */
708     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
709     @Nullable
queryParameter(@ullable UUID soundModelId, @ModelParams int modelParam)710     public ModelParamRange queryParameter(@Nullable UUID soundModelId,
711             @ModelParams int modelParam) {
712         if (mSoundTriggerSession == null) {
713             throw new IllegalStateException("No underlying SoundTriggerModule available");
714         }
715         try {
716             return mSoundTriggerSession.queryParameter(
717                     new ParcelUuid(Objects.requireNonNull(soundModelId)), modelParam);
718         } catch (RemoteException e) {
719             throw e.rethrowFromSystemServer();
720         }
721     }
722 
723     /**
724      * Create a {@link SoundTriggerInstrumentation} for test purposes, which instruments a fake
725      * STHAL. Clients must attach to the appropriate underlying ST module.
726      * @param executor - Executor to dispatch global callbacks on
727      * @param callback - Callback for unsessioned events received by the fake STHAL
728      * @return - A {@link SoundTriggerInstrumentation} for observation/injection.
729      * @hide
730      */
731     @TestApi
732     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
733     @NonNull
attachInstrumentation( @allbackExecutor @onNull Executor executor, @NonNull SoundTriggerInstrumentation.GlobalCallback callback)734     public static SoundTriggerInstrumentation attachInstrumentation(
735             @CallbackExecutor @NonNull Executor executor,
736             @NonNull SoundTriggerInstrumentation.GlobalCallback callback) {
737         ISoundTriggerService service = ISoundTriggerService.Stub.asInterface(
738                     ServiceManager.getService(Context.SOUND_TRIGGER_SERVICE));
739         return new SoundTriggerInstrumentation(service, executor, callback);
740     }
741 
742 }
743