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