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.hardware.soundtrigger; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.compat.annotation.UnsupportedAppUsage; 22 import android.media.permission.ClearCallingIdentityContext; 23 import android.media.permission.Identity; 24 import android.media.permission.SafeCloseable; 25 import android.media.soundtrigger.PhraseSoundModel; 26 import android.media.soundtrigger.SoundModel; 27 import android.media.soundtrigger_middleware.ISoundTriggerCallback; 28 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService; 29 import android.media.soundtrigger_middleware.ISoundTriggerModule; 30 import android.media.soundtrigger_middleware.PhraseRecognitionEventSys; 31 import android.media.soundtrigger_middleware.RecognitionEventSys; 32 import android.os.Build; 33 import android.os.Handler; 34 import android.os.IBinder; 35 import android.os.Looper; 36 import android.os.Message; 37 import android.os.RemoteException; 38 import android.util.Log; 39 40 import java.io.IOException; 41 42 /** 43 * The SoundTriggerModule provides APIs to control sound models and sound detection 44 * on a given sound trigger hardware module. 45 * 46 * @hide 47 */ 48 public class SoundTriggerModule { 49 private static final String TAG = "SoundTriggerModule"; 50 51 private static final int EVENT_RECOGNITION = 1; 52 private static final int EVENT_SERVICE_DIED = 2; 53 private static final int EVENT_RESOURCES_AVAILABLE = 3; 54 private static final int EVENT_MODEL_UNLOADED = 4; 55 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 56 private int mId; 57 private EventHandlerDelegate mEventHandlerDelegate; 58 private ISoundTriggerModule mService; 59 60 /** 61 * This variant is intended for use when the caller is acting an originator, rather than on 62 * behalf of a different entity, as far as authorization goes. 63 */ SoundTriggerModule(@onNull ISoundTriggerMiddlewareService service, int moduleId, @NonNull SoundTrigger.StatusListener listener, @NonNull Looper looper, @NonNull Identity originatorIdentity)64 public SoundTriggerModule(@NonNull ISoundTriggerMiddlewareService service, 65 int moduleId, @NonNull SoundTrigger.StatusListener listener, @NonNull Looper looper, 66 @NonNull Identity originatorIdentity) { 67 mId = moduleId; 68 mEventHandlerDelegate = new EventHandlerDelegate(listener, looper); 69 try { 70 try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { 71 mService = service.attachAsOriginator(moduleId, originatorIdentity, 72 mEventHandlerDelegate); 73 } 74 mService.asBinder().linkToDeath(mEventHandlerDelegate, 0); 75 } catch (RemoteException e) { 76 throw e.rethrowFromSystemServer(); 77 } 78 } 79 80 /** 81 * This variant is intended for use when the caller is acting as a middleman, i.e. on behalf of 82 * a different entity, as far as authorization goes. 83 */ SoundTriggerModule(@onNull ISoundTriggerMiddlewareService service, int moduleId, @NonNull SoundTrigger.StatusListener listener, @NonNull Looper looper, @NonNull Identity middlemanIdentity, @NonNull Identity originatorIdentity, boolean isTrusted)84 public SoundTriggerModule(@NonNull ISoundTriggerMiddlewareService service, 85 int moduleId, @NonNull SoundTrigger.StatusListener listener, @NonNull Looper looper, 86 @NonNull Identity middlemanIdentity, @NonNull Identity originatorIdentity, 87 boolean isTrusted) { 88 mId = moduleId; 89 mEventHandlerDelegate = new EventHandlerDelegate(listener, looper); 90 91 try { 92 try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { 93 mService = service.attachAsMiddleman(moduleId, middlemanIdentity, 94 originatorIdentity, 95 mEventHandlerDelegate, 96 isTrusted); 97 } 98 mService.asBinder().linkToDeath(mEventHandlerDelegate, 0); 99 } catch (RemoteException e) { 100 throw e.rethrowFromSystemServer(); 101 } 102 } 103 104 @Override finalize()105 protected void finalize() { 106 detach(); 107 } 108 109 /** 110 * Detach from this module. The {@link SoundTrigger.StatusListener} callback will not be called 111 * anymore and associated resources will be released. 112 * All models must have been unloaded prior to detaching. 113 * @deprecated Use {@link android.media.soundtrigger.SoundTriggerManager} instead. 114 */ 115 @Deprecated 116 @UnsupportedAppUsage detach()117 public synchronized void detach() { 118 try { 119 if (mService != null) { 120 mService.asBinder().unlinkToDeath(mEventHandlerDelegate, 0); 121 mService.detach(); 122 mService = null; 123 } 124 } catch (Exception e) { 125 SoundTrigger.handleException(e); 126 } 127 } 128 129 /** 130 * Load a {@link SoundTrigger.SoundModel} to the hardware. A sound model must be loaded in 131 * order to start listening to a key phrase in this model. 132 * @param model The sound model to load. 133 * @param soundModelHandle an array of int where the sound model handle will be returned. 134 * @return - {@link SoundTrigger#STATUS_OK} in case of success 135 * - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error 136 * - {@link SoundTrigger#STATUS_BUSY} in case of transient resource constraints 137 * - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have 138 * system permission 139 * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached 140 * - {@link SoundTrigger#STATUS_BAD_VALUE} if parameters are invalid 141 * - {@link SoundTrigger#STATUS_DEAD_OBJECT} if the binder transaction to the native 142 * service fails 143 * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence 144 * @deprecated Use {@link android.media.soundtrigger.SoundTriggerManager} instead. 145 */ 146 @Deprecated 147 @UnsupportedAppUsage loadSoundModel(@onNull SoundTrigger.SoundModel model, @NonNull int[] soundModelHandle)148 public synchronized int loadSoundModel(@NonNull SoundTrigger.SoundModel model, 149 @NonNull int[] soundModelHandle) { 150 try { 151 if (model instanceof SoundTrigger.GenericSoundModel) { 152 SoundModel aidlModel = ConversionUtil.api2aidlGenericSoundModel( 153 (SoundTrigger.GenericSoundModel) model); 154 try { 155 soundModelHandle[0] = mService.loadModel(aidlModel); 156 } finally { 157 // TODO(b/219825762): We should be able to use the entire object in a 158 // try-with-resources 159 // clause, instead of having to explicitly close internal fields. 160 if (aidlModel.data != null) { 161 try { 162 aidlModel.data.close(); 163 } catch (IOException e) { 164 Log.e(TAG, "Failed to close file", e); 165 } 166 } 167 } 168 return SoundTrigger.STATUS_OK; 169 } 170 if (model instanceof SoundTrigger.KeyphraseSoundModel) { 171 PhraseSoundModel aidlModel = ConversionUtil.api2aidlPhraseSoundModel( 172 (SoundTrigger.KeyphraseSoundModel) model); 173 try { 174 soundModelHandle[0] = mService.loadPhraseModel(aidlModel); 175 } finally { 176 // TODO(b/219825762): We should be able to use the entire object in a 177 // try-with-resources 178 // clause, instead of having to explicitly close internal fields. 179 if (aidlModel.common.data != null) { 180 try { 181 aidlModel.common.data.close(); 182 } catch (IOException e) { 183 Log.e(TAG, "Failed to close file", e); 184 } 185 } 186 } 187 return SoundTrigger.STATUS_OK; 188 } 189 return SoundTrigger.STATUS_BAD_VALUE; 190 } catch (Exception e) { 191 return SoundTrigger.handleException(e); 192 } 193 } 194 195 /** 196 * Unload a {@link SoundTrigger.SoundModel} and abort any pendiong recognition 197 * @param soundModelHandle The sound model handle 198 * @return - {@link SoundTrigger#STATUS_OK} in case of success 199 * - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error 200 * - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have 201 * system permission 202 * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached 203 * - {@link SoundTrigger#STATUS_BAD_VALUE} if the sound model handle is invalid 204 * - {@link SoundTrigger#STATUS_DEAD_OBJECT} if the binder transaction to the native 205 * service fails 206 * @deprecated Use {@link android.media.soundtrigger.SoundTriggerManager} instead. 207 */ 208 @UnsupportedAppUsage 209 @Deprecated unloadSoundModel(int soundModelHandle)210 public synchronized int unloadSoundModel(int soundModelHandle) { 211 try { 212 mService.unloadModel(soundModelHandle); 213 return SoundTrigger.STATUS_OK; 214 } catch (Exception e) { 215 return SoundTrigger.handleException(e); 216 } 217 } 218 219 /** 220 * Start listening to all key phrases in a {@link SoundTrigger.SoundModel}. 221 * Recognition must be restarted after each callback (success or failure) received on 222 * the {@link SoundTrigger.StatusListener}. 223 * @param soundModelHandle The sound model handle to start listening to 224 * @param config contains configuration information for this recognition request: 225 * recognition mode, keyphrases, users, minimum confidence levels... 226 * @return - {@link SoundTrigger#STATUS_OK} in case of success 227 * - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error 228 * - {@link SoundTrigger#STATUS_BUSY} in case of transient resource constraints 229 * - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have 230 * system permission 231 * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached 232 * - {@link SoundTrigger#STATUS_BAD_VALUE} if the sound model handle is invalid 233 * - {@link SoundTrigger#STATUS_DEAD_OBJECT} if the binder transaction to the native 234 * service fails 235 * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence 236 * @deprecated Use {@link android.media.soundtrigger.SoundTriggerManager} instead. 237 */ 238 @UnsupportedAppUsage 239 @Deprecated startRecognition(int soundModelHandle, SoundTrigger.RecognitionConfig config)240 public synchronized int startRecognition(int soundModelHandle, 241 SoundTrigger.RecognitionConfig config) { 242 try { 243 mService.startRecognition(soundModelHandle, 244 ConversionUtil.api2aidlRecognitionConfig(config)); 245 return SoundTrigger.STATUS_OK; 246 } catch (Exception e) { 247 return SoundTrigger.handleException(e); 248 } 249 } 250 251 /** 252 * Same as above, but return a binder token associated with the session. 253 * @hide 254 */ startRecognitionWithToken(int soundModelHandle, SoundTrigger.RecognitionConfig config)255 public synchronized IBinder startRecognitionWithToken(int soundModelHandle, 256 SoundTrigger.RecognitionConfig config) throws RemoteException { 257 return mService.startRecognition(soundModelHandle, 258 ConversionUtil.api2aidlRecognitionConfig(config)); 259 } 260 261 /** 262 * Stop listening to all key phrases in a {@link SoundTrigger.SoundModel} 263 * @param soundModelHandle The sound model handle to stop listening to 264 * @return - {@link SoundTrigger#STATUS_OK} in case of success 265 * - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error 266 * - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have 267 * system permission 268 * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached 269 * - {@link SoundTrigger#STATUS_BAD_VALUE} if the sound model handle is invalid 270 * - {@link SoundTrigger#STATUS_DEAD_OBJECT} if the binder transaction to the native 271 * service fails 272 * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence 273 * @deprecated Use {@link android.media.soundtrigger.SoundTriggerManager} instead. 274 */ 275 @UnsupportedAppUsage 276 @Deprecated stopRecognition(int soundModelHandle)277 public synchronized int stopRecognition(int soundModelHandle) { 278 try { 279 mService.stopRecognition(soundModelHandle); 280 return SoundTrigger.STATUS_OK; 281 } catch (Exception e) { 282 return SoundTrigger.handleException(e); 283 } 284 } 285 286 /** 287 * Get the current state of a {@link SoundTrigger.SoundModel}. 288 * The state will be returned asynchronously as a {@link SoundTrigger.RecognitionEvent} 289 * in the callback registered in the 290 * {@link SoundTrigger#attachModule(int, SoundTrigger.StatusListener, Handler)} method. 291 * @param soundModelHandle The sound model handle indicating which model's state to return 292 * @return - {@link SoundTrigger#STATUS_OK} in case of success 293 * - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error 294 * - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have 295 * system permission 296 * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached 297 * - {@link SoundTrigger#STATUS_BAD_VALUE} if the sound model handle is invalid 298 * - {@link SoundTrigger#STATUS_DEAD_OBJECT} if the binder transaction to the native 299 * service fails 300 * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence 301 */ getModelState(int soundModelHandle)302 public synchronized int getModelState(int soundModelHandle) { 303 try { 304 mService.forceRecognitionEvent(soundModelHandle); 305 return SoundTrigger.STATUS_OK; 306 } catch (Exception e) { 307 return SoundTrigger.handleException(e); 308 } 309 } 310 311 /** 312 * Set a model specific {@link ModelParams} with the given value. This 313 * parameter will keep its value for the duration the model is loaded regardless of starting 314 * and stopping recognition. Once the model is unloaded, the value will be lost. 315 * {@link #queryParameter} should be checked first before calling this method. 316 * 317 * @param soundModelHandle handle of model to apply parameter 318 * @param modelParam {@link ModelParams} 319 * @param value Value to set 320 * @return - {@link SoundTrigger#STATUS_OK} in case of success 321 * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached 322 * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter 323 * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or 324 * if API is not supported by HAL 325 */ setParameter(int soundModelHandle, @ModelParams int modelParam, int value)326 public synchronized int setParameter(int soundModelHandle, @ModelParams int modelParam, 327 int value) { 328 try { 329 mService.setModelParameter(soundModelHandle, 330 ConversionUtil.api2aidlModelParameter(modelParam), value); 331 return SoundTrigger.STATUS_OK; 332 } catch (Exception e) { 333 return SoundTrigger.handleException(e); 334 } 335 } 336 337 /** 338 * Get a model specific {@link ModelParams}. This parameter will keep its value 339 * for the duration the model is loaded regardless of starting and stopping recognition. 340 * Once the model is unloaded, the value will be lost. If the value is not set, a default 341 * value is returned. See {@link ModelParams} for parameter default values. 342 * {@link #queryParameter} should be checked first before 343 * calling this method. Otherwise, an exception can be thrown. 344 * 345 * @param soundModelHandle handle of model to get parameter 346 * @param modelParam {@link ModelParams} 347 * @return value of parameter 348 */ getParameter(int soundModelHandle, @ModelParams int modelParam)349 public synchronized int getParameter(int soundModelHandle, @ModelParams int modelParam) { 350 try { 351 return mService.getModelParameter(soundModelHandle, 352 ConversionUtil.api2aidlModelParameter(modelParam)); 353 } catch (RemoteException e) { 354 throw e.rethrowFromSystemServer(); 355 } 356 } 357 358 /** 359 * Query the parameter support and range for a given {@link ModelParams}. 360 * This method should be check prior to calling {@link #setParameter} or {@link #getParameter}. 361 * 362 * @param soundModelHandle handle of model to get parameter 363 * @param modelParam {@link ModelParams} 364 * @return supported range of parameter, null if not supported 365 */ 366 @Nullable queryParameter(int soundModelHandle, @ModelParams int modelParam)367 public synchronized SoundTrigger.ModelParamRange queryParameter(int soundModelHandle, 368 @ModelParams int modelParam) { 369 try { 370 return ConversionUtil.aidl2apiModelParameterRange(mService.queryModelParameterSupport( 371 soundModelHandle, 372 ConversionUtil.api2aidlModelParameter(modelParam))); 373 } catch (RemoteException e) { 374 throw e.rethrowFromSystemServer(); 375 } 376 } 377 378 private class EventHandlerDelegate extends ISoundTriggerCallback.Stub implements 379 IBinder.DeathRecipient { 380 private final Handler mHandler; 381 EventHandlerDelegate(@onNull final SoundTrigger.StatusListener listener, @NonNull Looper looper)382 EventHandlerDelegate(@NonNull final SoundTrigger.StatusListener listener, 383 @NonNull Looper looper) { 384 385 // construct the event handler with this looper 386 // implement the event handler delegate 387 mHandler = new Handler(looper) { 388 @Override 389 public void handleMessage(Message msg) { 390 switch (msg.what) { 391 case EVENT_RECOGNITION: 392 listener.onRecognition( 393 (SoundTrigger.RecognitionEvent) msg.obj); 394 break; 395 case EVENT_RESOURCES_AVAILABLE: 396 listener.onResourcesAvailable(); 397 break; 398 case EVENT_MODEL_UNLOADED: 399 listener.onModelUnloaded((Integer) msg.obj); 400 break; 401 case EVENT_SERVICE_DIED: 402 listener.onServiceDied(); 403 break; 404 default: 405 Log.e(TAG, "Unknown message: " + msg.toString()); 406 break; 407 } 408 } 409 }; 410 } 411 412 @Override onRecognition(int handle, RecognitionEventSys event, int captureSession)413 public synchronized void onRecognition(int handle, RecognitionEventSys event, 414 int captureSession) 415 throws RemoteException { 416 Message m = mHandler.obtainMessage(EVENT_RECOGNITION, 417 ConversionUtil.aidl2apiRecognitionEvent(handle, captureSession, event)); 418 mHandler.sendMessage(m); 419 } 420 421 @Override onPhraseRecognition(int handle, PhraseRecognitionEventSys event, int captureSession)422 public synchronized void onPhraseRecognition(int handle, PhraseRecognitionEventSys event, 423 int captureSession) 424 throws RemoteException { 425 Message m = mHandler.obtainMessage(EVENT_RECOGNITION, 426 ConversionUtil.aidl2apiPhraseRecognitionEvent(handle, captureSession, event)); 427 mHandler.sendMessage(m); 428 } 429 430 @Override onModelUnloaded(int modelHandle)431 public void onModelUnloaded(int modelHandle) throws RemoteException { 432 Message m = mHandler.obtainMessage(EVENT_MODEL_UNLOADED, modelHandle); 433 mHandler.sendMessage(m); 434 } 435 436 @Override onResourcesAvailable()437 public synchronized void onResourcesAvailable() throws RemoteException { 438 Message m = mHandler.obtainMessage(EVENT_RESOURCES_AVAILABLE); 439 mHandler.sendMessage(m); 440 } 441 442 @Override onModuleDied()443 public synchronized void onModuleDied() { 444 Message m = mHandler.obtainMessage(EVENT_SERVICE_DIED); 445 mHandler.sendMessage(m); 446 } 447 448 @Override binderDied()449 public synchronized void binderDied() { 450 Message m = mHandler.obtainMessage(EVENT_SERVICE_DIED); 451 mHandler.sendMessage(m); 452 } 453 } 454 } 455