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