blob: f809ed4a4a2b412317eb762de40ce171b988182c [file] [log] [blame]
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -08001/*
2 * Copyright (C) 2019 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
17package com.android.server.soundtrigger_middleware;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
22import android.hardware.soundtrigger.V2_2.ISoundTriggerHw;
23import android.media.soundtrigger_middleware.ISoundTriggerCallback;
24import android.media.soundtrigger_middleware.ISoundTriggerModule;
25import android.media.soundtrigger_middleware.ModelParameterRange;
Ytai Ben-Tsvi8ed1a8b2020-06-04 13:11:54 -070026import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
27import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -080028import android.media.soundtrigger_middleware.PhraseSoundModel;
29import android.media.soundtrigger_middleware.RecognitionConfig;
Ytai Ben-Tsvi8ed1a8b2020-06-04 13:11:54 -070030import android.media.soundtrigger_middleware.RecognitionEvent;
Ytai Ben-Tsvi7caef40a2020-06-09 15:50:20 -070031import android.media.soundtrigger_middleware.RecognitionStatus;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -080032import android.media.soundtrigger_middleware.SoundModel;
33import android.media.soundtrigger_middleware.SoundModelType;
34import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
35import android.media.soundtrigger_middleware.Status;
36import android.os.IBinder;
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -080037import android.os.IHwBinder;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -080038import android.os.RemoteException;
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -080039import android.os.ServiceSpecificException;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -080040import android.util.Log;
41
Ytai Ben-Tsvi77c195d2020-05-04 15:26:38 -070042import java.util.ArrayList;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -080043import java.util.HashMap;
44import java.util.HashSet;
Ytai Ben-Tsvi21c886c2020-06-18 11:52:39 -070045import java.util.LinkedList;
Ytai Ben-Tsvi77c195d2020-05-04 15:26:38 -070046import java.util.List;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -080047import java.util.Map;
48import java.util.Set;
49
50/**
51 * This is an implementation of a single module of the ISoundTriggerMiddlewareService interface,
52 * exposing itself through the {@link ISoundTriggerModule} interface, possibly to multiple separate
53 * clients.
54 * <p>
55 * Typical usage is to query the module capabilities using {@link #getProperties()} and then to use
56 * the module through an {@link ISoundTriggerModule} instance, obtained via {@link
57 * #attach(ISoundTriggerCallback)}. Every such interface is its own session and state is not shared
58 * between sessions (i.e. cannot use a handle obtained from one session through another).
59 * <p>
60 * <b>Important conventions:</b>
61 * <ul>
62 * <li>Correct usage is assumed. This implementation does not attempt to gracefully handle
63 * invalid usage, and such usage will result in undefined behavior. If this service is to be
64 * offered to an untrusted client, it must be wrapped with input and state validation.
65 * <li>The underlying driver is assumed to be correct. This implementation does not attempt to
66 * gracefully handle driver malfunction and such behavior will result in undefined behavior. If this
67 * service is to used with an untrusted driver, the driver must be wrapped with validation / error
68 * recovery code.
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -080069 * <li>Recovery from driver death is supported.</li>
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -080070 * <li>RemoteExceptions thrown by the driver are treated as RuntimeExceptions - they are not
71 * considered recoverable faults and should not occur in a properly functioning system.
72 * <li>There is no binder instance associated with this implementation. Do not call asBinder().
73 * <li>The implementation may throw a {@link RecoverableException} to indicate non-fatal,
74 * recoverable faults. The error code would one of the
75 * {@link android.media.soundtrigger_middleware.Status} constants. Any other exception
76 * thrown should be regarded as a bug in the implementation or one of its dependencies
77 * (assuming correct usage).
Ytai Ben-Tsvi7d383d12019-11-25 12:47:40 -080078 * <li>The implementation is designed for testability by featuring dependency injection (the
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -080079 * underlying HAL driver instances are passed to the ctor) and by minimizing dependencies
80 * on Android runtime.
81 * <li>The implementation is thread-safe. This is achieved by a simplistic model, where all entry-
82 * points (both client API and driver callbacks) obtain a lock on the SoundTriggerModule instance
83 * for their entire scope. Any other method can be assumed to be running with the lock already
84 * obtained, so no further locking should be done. While this is not necessarily the most efficient
85 * synchronization strategy, it is very easy to reason about and this code is likely not on any
86 * performance-critical
87 * path.
88 * </ul>
89 *
90 * @hide
91 */
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -080092class SoundTriggerModule implements IHwBinder.DeathRecipient {
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -080093 static private final String TAG = "SoundTriggerModule";
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -080094 @NonNull private HalFactory mHalFactory;
95 @NonNull private ISoundTriggerHw2 mHalService;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -080096 @NonNull private final SoundTriggerMiddlewareImpl.AudioSessionProvider mAudioSessionProvider;
97 private final Set<Session> mActiveSessions = new HashSet<>();
98 private int mNumLoadedModels = 0;
Ytai Ben-Tsvi6df1f3d2020-01-09 15:50:51 -080099 private SoundTriggerModuleProperties mProperties;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800100 private boolean mRecognitionAvailable;
101
102 /**
103 * Ctor.
104 *
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -0800105 * @param halFactory A factory for the underlying HAL driver.
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800106 */
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -0800107 SoundTriggerModule(@NonNull HalFactory halFactory,
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800108 @NonNull SoundTriggerMiddlewareImpl.AudioSessionProvider audioSessionProvider) {
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -0800109 assert halFactory != null;
110 mHalFactory = halFactory;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800111 mAudioSessionProvider = audioSessionProvider;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800112
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -0800113 attachToHal();
114 mProperties = ConversionUtil.hidl2aidlProperties(mHalService.getProperties());
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800115 // We conservatively assume that external capture is active until explicitly told otherwise.
116 mRecognitionAvailable = mProperties.concurrentCapture;
117 }
118
119 /**
120 * Establish a client session with this module.
121 *
122 * This module may be shared by multiple clients, each will get its own session. While resources
123 * are shared between the clients, each session has its own state and data should not be shared
124 * across sessions.
125 *
126 * @param callback The client callback, which will be used for all messages. This is a oneway
127 * callback, so will never block, throw an unchecked exception or return a
128 * value.
129 * @return The interface through which this module can be controlled.
130 */
131 synchronized @NonNull
Ytai Ben-Tsvi6df1f3d2020-01-09 15:50:51 -0800132 ISoundTriggerModule attach(@NonNull ISoundTriggerCallback callback) {
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800133 Session session = new Session(callback);
134 mActiveSessions.add(session);
135 return session;
136 }
137
138 /**
139 * Query the module's properties.
140 *
141 * @return The properties structure.
142 */
143 synchronized @NonNull
144 SoundTriggerModuleProperties getProperties() {
145 return mProperties;
146 }
147
148 /**
149 * Notify the module that external capture has started / finished, using the same input device
150 * used for recognition.
151 * If the underlying driver does not support recognition while capturing, capture will be
152 * aborted, and the recognition callback will receive and abort event. In addition, all active
153 * clients will be notified of the change in state.
154 *
155 * @param active true iff external capture is active.
156 */
Ytai Ben-Tsvi21c886c2020-06-18 11:52:39 -0700157 void setExternalCaptureState(boolean active) {
158 // We should never invoke callbacks while holding the lock, since this may deadlock with
159 // forward calls. Thus, we first gather all the callbacks we need to invoke while holding
160 // the lock, but invoke them after releasing it.
161 List<Runnable> callbacks = new LinkedList<>();
162
163 synchronized (this) {
164 if (mProperties.concurrentCapture) {
165 // If we support concurrent capture, we don't care about any of this.
166 return;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800167 }
Ytai Ben-Tsvi21c886c2020-06-18 11:52:39 -0700168 mRecognitionAvailable = !active;
169 if (!mRecognitionAvailable) {
170 // Our module does not support recognition while a capture is active -
171 // need to abort all active recognitions.
172 for (Session session : mActiveSessions) {
173 session.abortActiveRecognitions(callbacks);
174 }
175 }
176 }
177 for (Runnable callback : callbacks) {
178 callback.run();
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800179 }
180 for (Session session : mActiveSessions) {
181 session.notifyRecognitionAvailability();
182 }
183 }
184
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -0800185 @Override
Ytai Ben-Tsvi77c195d2020-05-04 15:26:38 -0700186 public void serviceDied(long cookie) {
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -0800187 Log.w(TAG, String.format("Underlying HAL driver died."));
Ytai Ben-Tsvi77c195d2020-05-04 15:26:38 -0700188 List<ISoundTriggerCallback> callbacks = new ArrayList<>(mActiveSessions.size());
189 synchronized (this) {
190 for (Session session : mActiveSessions) {
191 callbacks.add(session.moduleDied());
192 }
193 reset();
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -0800194 }
Ytai Ben-Tsvi77c195d2020-05-04 15:26:38 -0700195 // Trigger the callbacks outside of the lock to avoid deadlocks.
196 for (ISoundTriggerCallback callback : callbacks) {
197 try {
198 callback.onModuleDied();
199 } catch (RemoteException e) {
200 throw e.rethrowAsRuntimeException();
201 }
202 }
Ytai Ben-Tsvia23eaa72020-01-21 17:25:52 -0800203 }
204
205 /**
206 * Resets the transient state of this object.
207 */
208 private void reset() {
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -0800209 attachToHal();
Ytai Ben-Tsvia23eaa72020-01-21 17:25:52 -0800210 // We conservatively assume that external capture is active until explicitly told otherwise.
211 mRecognitionAvailable = mProperties.concurrentCapture;
212 mNumLoadedModels = 0;
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -0800213 }
214
215 /**
216 * Attached to the HAL service via factory.
217 */
218 private void attachToHal() {
Ytai Ben-Tsvi56a4ee82020-02-14 16:00:54 -0800219 mHalService = new SoundTriggerHw2Enforcer(new SoundTriggerHw2Compat(mHalFactory.create()));
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -0800220 mHalService.linkToDeath(this, 0);
221 }
222
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800223 /**
224 * Remove session from the list of active sessions.
225 *
226 * @param session The session to remove.
227 */
228 private void removeSession(@NonNull Session session) {
229 mActiveSessions.remove(session);
230 }
231
232 /** State of a single sound model. */
233 private enum ModelState {
234 /** Initial state, until load() is called. */
235 INIT,
236 /** Model is loaded, but recognition is not active. */
237 LOADED,
238 /** Model is loaded and recognition is active. */
239 ACTIVE
240 }
241
242 /**
243 * A single client session with this module.
244 *
245 * This is the main interface used to interact with this module.
246 */
247 private class Session implements ISoundTriggerModule {
248 private ISoundTriggerCallback mCallback;
249 private Map<Integer, Model> mLoadedModels = new HashMap<>();
250
251 /**
252 * Ctor.
253 *
254 * @param callback The client callback interface.
255 */
256 private Session(@NonNull ISoundTriggerCallback callback) {
257 mCallback = callback;
258 notifyRecognitionAvailability();
259 }
260
261 @Override
262 public void detach() {
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800263 synchronized (SoundTriggerModule.this) {
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -0800264 if (mCallback == null) {
265 return;
266 }
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800267 removeSession(this);
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -0800268 mCallback = null;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800269 }
270 }
271
272 @Override
273 public int loadModel(@NonNull SoundModel model) {
Ytai Ben-Tsvi406619f42020-02-13 16:24:56 -0800274 // We must do this outside the lock, to avoid possible deadlocks with the remote process
275 // that provides the audio sessions, which may also be calling into us.
276 SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession =
277 mAudioSessionProvider.acquireSession();
278
279 try {
280 synchronized (SoundTriggerModule.this) {
281 checkValid();
282 if (mNumLoadedModels == mProperties.maxSoundModels) {
283 throw new RecoverableException(Status.RESOURCE_CONTENTION,
284 "Maximum number of models loaded.");
285 }
286 Model loadedModel = new Model();
287 int result = loadedModel.load(model, audioSession);
288 ++mNumLoadedModels;
289 return result;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800290 }
Ytai Ben-Tsvi406619f42020-02-13 16:24:56 -0800291 } catch (Exception e) {
292 // We must do this outside the lock, to avoid possible deadlocks with the remote
293 // process that provides the audio sessions, which may also be calling into us.
294 mAudioSessionProvider.releaseSession(audioSession.mSessionHandle);
295 throw e;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800296 }
297 }
298
299 @Override
300 public int loadPhraseModel(@NonNull PhraseSoundModel model) {
Ytai Ben-Tsvi406619f42020-02-13 16:24:56 -0800301 // We must do this outside the lock, to avoid possible deadlocks with the remote process
302 // that provides the audio sessions, which may also be calling into us.
303 SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession =
304 mAudioSessionProvider.acquireSession();
305
306 try {
307 synchronized (SoundTriggerModule.this) {
308 checkValid();
309 if (mNumLoadedModels == mProperties.maxSoundModels) {
310 throw new RecoverableException(Status.RESOURCE_CONTENTION,
311 "Maximum number of models loaded.");
312 }
313 Model loadedModel = new Model();
314 int result = loadedModel.load(model, audioSession);
315 ++mNumLoadedModels;
316 Log.d(TAG, String.format("loadPhraseModel()->%d", result));
317 return result;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800318 }
Ytai Ben-Tsvi406619f42020-02-13 16:24:56 -0800319 } catch (Exception e) {
320 // We must do this outside the lock, to avoid possible deadlocks with the remote
321 // process that provides the audio sessions, which may also be calling into us.
322 mAudioSessionProvider.releaseSession(audioSession.mSessionHandle);
323 throw e;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800324 }
325 }
326
327 @Override
328 public void unloadModel(int modelHandle) {
Ytai Ben-Tsvi406619f42020-02-13 16:24:56 -0800329 int sessionId;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800330 synchronized (SoundTriggerModule.this) {
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -0800331 checkValid();
Ytai Ben-Tsvi406619f42020-02-13 16:24:56 -0800332 sessionId = mLoadedModels.get(modelHandle).unload();
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800333 --mNumLoadedModels;
334 }
Ytai Ben-Tsvi406619f42020-02-13 16:24:56 -0800335
336 // We must do this outside the lock, to avoid possible deadlocks with the remote process
337 // that provides the audio sessions, which may also be calling into us.
338 mAudioSessionProvider.releaseSession(sessionId);
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800339 }
340
341 @Override
342 public void startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
Ytai Ben-Tsvi21c886c2020-06-18 11:52:39 -0700343 // We should never invoke callbacks while holding the lock, since this may deadlock with
344 // forward calls. Thus, we first gather all the callbacks we need to invoke while holding
345 // the lock, but invoke them after releasing it.
346 List<Runnable> callbacks = new LinkedList<>();
347
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800348 synchronized (SoundTriggerModule.this) {
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -0800349 checkValid();
Ytai Ben-Tsvi21c886c2020-06-18 11:52:39 -0700350 mLoadedModels.get(modelHandle).startRecognition(config, callbacks);
351 }
352
353 for (Runnable callback : callbacks) {
354 callback.run();
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800355 }
356 }
357
358 @Override
359 public void stopRecognition(int modelHandle) {
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800360 synchronized (SoundTriggerModule.this) {
361 mLoadedModels.get(modelHandle).stopRecognition();
362 }
363 }
364
365 @Override
366 public void forceRecognitionEvent(int modelHandle) {
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800367 synchronized (SoundTriggerModule.this) {
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -0800368 checkValid();
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800369 mLoadedModels.get(modelHandle).forceRecognitionEvent();
370 }
371 }
372
373 @Override
Ytai Ben-Tsvi6df1f3d2020-01-09 15:50:51 -0800374 public void setModelParameter(int modelHandle, int modelParam, int value) {
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800375 synchronized (SoundTriggerModule.this) {
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -0800376 checkValid();
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800377 mLoadedModels.get(modelHandle).setParameter(modelParam, value);
378 }
379 }
380
381 @Override
Ytai Ben-Tsvi6df1f3d2020-01-09 15:50:51 -0800382 public int getModelParameter(int modelHandle, int modelParam) {
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800383 synchronized (SoundTriggerModule.this) {
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -0800384 checkValid();
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800385 return mLoadedModels.get(modelHandle).getParameter(modelParam);
386 }
387 }
388
389 @Override
390 @Nullable
391 public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam) {
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800392 synchronized (SoundTriggerModule.this) {
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -0800393 checkValid();
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800394 return mLoadedModels.get(modelHandle).queryModelParameterSupport(modelParam);
395 }
396 }
397
398 /**
399 * Abort all currently active recognitions.
Ytai Ben-Tsvi21c886c2020-06-18 11:52:39 -0700400 * @param callbacks Will be appended with a list of callbacks that need to be invoked
401 * after this method returns, without holding the module lock.
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800402 */
Ytai Ben-Tsvi21c886c2020-06-18 11:52:39 -0700403 private void abortActiveRecognitions(@NonNull List<Runnable> callbacks) {
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800404 for (Model model : mLoadedModels.values()) {
Ytai Ben-Tsvi21c886c2020-06-18 11:52:39 -0700405 model.abortActiveRecognition(callbacks);
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800406 }
407 }
408
409 private void notifyRecognitionAvailability() {
410 try {
411 mCallback.onRecognitionAvailabilityChange(mRecognitionAvailable);
412 } catch (RemoteException e) {
413 // Dead client will be handled by binderDied() - no need to handle here.
414 // In any case, client callbacks are considered best effort.
415 Log.e(TAG, "Client callback execption.", e);
416 }
417 }
418
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -0800419 /**
420 * The underlying module HAL is dead.
Ytai Ben-Tsvi77c195d2020-05-04 15:26:38 -0700421 * @return The client callback that needs to be invoked to notify the client.
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -0800422 */
Ytai Ben-Tsvi77c195d2020-05-04 15:26:38 -0700423 private ISoundTriggerCallback moduleDied() {
424 ISoundTriggerCallback callback = mCallback;
425 removeSession(this);
426 mCallback = null;
427 return callback;
Ytai Ben-Tsvic2327e72020-01-10 10:47:00 -0800428 }
429
430 private void checkValid() {
431 if (mCallback == null) {
432 throw new ServiceSpecificException(Status.DEAD_OBJECT);
433 }
434 }
435
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800436 @Override
437 public @NonNull
438 IBinder asBinder() {
439 throw new UnsupportedOperationException(
440 "This implementation is not intended to be used directly with Binder.");
441 }
442
443 /**
444 * A single sound model in the system.
445 *
446 * All model-based operations are delegated to this class and implemented here.
447 */
448 private class Model implements ISoundTriggerHw2.Callback {
449 public int mHandle;
450 private ModelState mState = ModelState.INIT;
451 private int mModelType = SoundModelType.UNKNOWN;
452 private SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession mSession;
453
454 private @NonNull
455 ModelState getState() {
456 return mState;
457 }
458
459 private void setState(@NonNull ModelState state) {
460 mState = state;
461 SoundTriggerModule.this.notifyAll();
462 }
463
Ytai Ben-Tsvi406619f42020-02-13 16:24:56 -0800464 private int load(@NonNull SoundModel model,
465 SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession) {
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800466 mModelType = model.type;
Ytai Ben-Tsvi406619f42020-02-13 16:24:56 -0800467 mSession = audioSession;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800468 ISoundTriggerHw.SoundModel hidlModel = ConversionUtil.aidl2hidlSoundModel(model);
469
Ytai Ben-Tsvi406619f42020-02-13 16:24:56 -0800470 mHandle = mHalService.loadSoundModel(hidlModel, this, 0);
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800471 setState(ModelState.LOADED);
472 mLoadedModels.put(mHandle, this);
473 return mHandle;
474 }
475
Ytai Ben-Tsvi406619f42020-02-13 16:24:56 -0800476 private int load(@NonNull PhraseSoundModel model,
477 SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession) {
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800478 mModelType = model.common.type;
Ytai Ben-Tsvi406619f42020-02-13 16:24:56 -0800479 mSession = audioSession;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800480 ISoundTriggerHw.PhraseSoundModel hidlModel =
481 ConversionUtil.aidl2hidlPhraseSoundModel(model);
482
Ytai Ben-Tsvi406619f42020-02-13 16:24:56 -0800483 mHandle = mHalService.loadPhraseSoundModel(hidlModel, this, 0);
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800484
485 setState(ModelState.LOADED);
486 mLoadedModels.put(mHandle, this);
487 return mHandle;
488 }
489
Ytai Ben-Tsvi406619f42020-02-13 16:24:56 -0800490 /**
491 * Unloads the model.
492 * @return The audio session handle.
493 */
494 private int unload() {
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800495 mHalService.unloadSoundModel(mHandle);
496 mLoadedModels.remove(mHandle);
Ytai Ben-Tsvi406619f42020-02-13 16:24:56 -0800497 return mSession.mSessionHandle;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800498 }
499
Ytai Ben-Tsvi21c886c2020-06-18 11:52:39 -0700500 private void startRecognition(@NonNull RecognitionConfig config,
501 @NonNull List<Runnable> callbacks) {
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800502 if (!mRecognitionAvailable) {
503 // Recognition is unavailable - send an abort event immediately.
Ytai Ben-Tsvi21c886c2020-06-18 11:52:39 -0700504 callbacks.add(this::notifyAbort);
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800505 return;
506 }
Nicholas Ambur7092a562019-12-16 11:18:55 -0800507 android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig =
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800508 ConversionUtil.aidl2hidlRecognitionConfig(config);
Nicholas Ambur7092a562019-12-16 11:18:55 -0800509 hidlConfig.base.header.captureDevice = mSession.mDeviceHandle;
510 hidlConfig.base.header.captureHandle = mSession.mIoHandle;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800511 mHalService.startRecognition(mHandle, hidlConfig, this, 0);
512 setState(ModelState.ACTIVE);
513 }
514
515 private void stopRecognition() {
516 if (getState() == ModelState.LOADED) {
517 // This call is idempotent in order to avoid races.
518 return;
519 }
520 mHalService.stopRecognition(mHandle);
521 setState(ModelState.LOADED);
522 }
523
524 /** Request a forced recognition event. Will do nothing if recognition is inactive. */
525 private void forceRecognitionEvent() {
526 if (getState() != ModelState.ACTIVE) {
527 // This call is idempotent in order to avoid races.
528 return;
529 }
530 mHalService.getModelState(mHandle);
531 }
532
533
534 private void setParameter(int modelParam, int value) {
535 mHalService.setModelParameter(mHandle,
536 ConversionUtil.aidl2hidlModelParameter(modelParam), value);
537 }
538
539 private int getParameter(int modelParam) {
540 return mHalService.getModelParameter(mHandle,
541 ConversionUtil.aidl2hidlModelParameter(modelParam));
542 }
543
544 @Nullable
545 private ModelParameterRange queryModelParameterSupport(int modelParam) {
546 return ConversionUtil.hidl2aidlModelParameterRange(
547 mHalService.queryParameter(mHandle,
548 ConversionUtil.aidl2hidlModelParameter(modelParam)));
549 }
550
Ytai Ben-Tsvi21c886c2020-06-18 11:52:39 -0700551 /**
552 * Abort the recognition, if active.
553 * @param callbacks Will be appended with a list of callbacks that need to be invoked
554 * after this method returns, without holding the module lock.
555 */
556 private void abortActiveRecognition(List<Runnable> callbacks) {
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800557 // If we're inactive, do nothing.
558 if (getState() != ModelState.ACTIVE) {
559 return;
560 }
561 // Stop recognition.
562 stopRecognition();
563
564 // Notify the client that recognition has been aborted.
Ytai Ben-Tsvi21c886c2020-06-18 11:52:39 -0700565 callbacks.add(this::notifyAbort);
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800566 }
567
568 /** Notify the client that recognition has been aborted. */
569 private void notifyAbort() {
570 try {
571 switch (mModelType) {
572 case SoundModelType.GENERIC: {
573 android.media.soundtrigger_middleware.RecognitionEvent event =
Ytai Ben-Tsvi8ed1a8b2020-06-04 13:11:54 -0700574 newEmptyRecognitionEvent();
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800575 event.status =
576 android.media.soundtrigger_middleware.RecognitionStatus.ABORTED;
Ytai Ben-Tsvi8ed1a8b2020-06-04 13:11:54 -0700577 event.type = SoundModelType.GENERIC;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800578 mCallback.onRecognition(mHandle, event);
579 }
580 break;
581
582 case SoundModelType.KEYPHRASE: {
583 android.media.soundtrigger_middleware.PhraseRecognitionEvent event =
Ytai Ben-Tsvi8ed1a8b2020-06-04 13:11:54 -0700584 newEmptyPhraseRecognitionEvent();
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800585 event.common.status =
586 android.media.soundtrigger_middleware.RecognitionStatus.ABORTED;
Ytai Ben-Tsvi8ed1a8b2020-06-04 13:11:54 -0700587 event.common.type = SoundModelType.KEYPHRASE;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800588 mCallback.onPhraseRecognition(mHandle, event);
589 }
590 break;
591
592 default:
593 Log.e(TAG, "Unknown model type: " + mModelType);
594
595 }
596 } catch (RemoteException e) {
597 // Dead client will be handled by binderDied() - no need to handle here.
598 // In any case, client callbacks are considered best effort.
599 Log.e(TAG, "Client callback execption.", e);
600 }
601 }
602
603 @Override
604 public void recognitionCallback(
605 @NonNull ISoundTriggerHwCallback.RecognitionEvent recognitionEvent,
606 int cookie) {
Ytai Ben-Tsvi21c886c2020-06-18 11:52:39 -0700607 RecognitionEvent aidlEvent =
608 ConversionUtil.hidl2aidlRecognitionEvent(recognitionEvent);
609 aidlEvent.captureSession = mSession.mSessionHandle;
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800610 synchronized (SoundTriggerModule.this) {
Ytai Ben-Tsvi7caef40a2020-06-09 15:50:20 -0700611 if (aidlEvent.status != RecognitionStatus.FORCED) {
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800612 setState(ModelState.LOADED);
613 }
614 }
Ytai Ben-Tsvi21c886c2020-06-18 11:52:39 -0700615 // The callback must be invoked outside of the lock.
616 try {
617 mCallback.onRecognition(mHandle, aidlEvent);
618 } catch (RemoteException e) {
619 // We're not expecting any exceptions here.
620 throw e.rethrowAsRuntimeException();
621 }
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800622 }
623
624 @Override
625 public void phraseRecognitionCallback(
626 @NonNull ISoundTriggerHwCallback.PhraseRecognitionEvent phraseRecognitionEvent,
627 int cookie) {
Ytai Ben-Tsvi21c886c2020-06-18 11:52:39 -0700628 PhraseRecognitionEvent aidlEvent =
629 ConversionUtil.hidl2aidlPhraseRecognitionEvent(phraseRecognitionEvent);
630 aidlEvent.common.captureSession = mSession.mSessionHandle;
631
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800632 synchronized (SoundTriggerModule.this) {
Ytai Ben-Tsvi7caef40a2020-06-09 15:50:20 -0700633 if (aidlEvent.common.status != RecognitionStatus.FORCED) {
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800634 setState(ModelState.LOADED);
635 }
636 }
Ytai Ben-Tsvi21c886c2020-06-18 11:52:39 -0700637
638 // The callback must be invoked outside of the lock.
639 try {
640 mCallback.onPhraseRecognition(mHandle, aidlEvent);
641 } catch (RemoteException e) {
642 // We're not expecting any exceptions here.
643 throw e.rethrowAsRuntimeException();
644 }
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800645 }
646 }
647 }
Ytai Ben-Tsvi8ed1a8b2020-06-04 13:11:54 -0700648
649 /**
650 * Creates a default-initialized recognition event.
651 *
Ytai Ben-Tsvi7caef40a2020-06-09 15:50:20 -0700652 * Non-nullable object fields are default constructed.
653 * Non-nullable array fields are initialized to 0 length.
Ytai Ben-Tsvi8ed1a8b2020-06-04 13:11:54 -0700654 *
655 * @return The event.
656 */
657 private static RecognitionEvent newEmptyRecognitionEvent() {
658 RecognitionEvent result = new RecognitionEvent();
Ytai Ben-Tsvi8ed1a8b2020-06-04 13:11:54 -0700659 result.data = new byte[0];
660 return result;
661 }
662
663 /**
664 * Creates a default-initialized phrase recognition event.
665 *
Ytai Ben-Tsvi7caef40a2020-06-09 15:50:20 -0700666 * Non-nullable object fields are default constructed.
667 * Non-nullable array fields are initialized to 0 length.
Ytai Ben-Tsvi8ed1a8b2020-06-04 13:11:54 -0700668 *
669 * @return The event.
670 */
671 private static PhraseRecognitionEvent newEmptyPhraseRecognitionEvent() {
672 PhraseRecognitionEvent result = new PhraseRecognitionEvent();
673 result.common = newEmptyRecognitionEvent();
674 result.phraseExtras = new PhraseRecognitionExtra[0];
675 return result;
676 }
Ytai Ben-Tsvi93c117c862019-11-25 12:43:28 -0800677}