• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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