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