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