blob: 735b9a1dcf2e57068e9552030c76c38eb8062aa9 [file] [log] [blame]
Sandeepd7018202014-07-10 15:15:39 -07001/**
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
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080017package com.android.server.soundtrigger;
Sandeepd7018202014-07-10 15:15:39 -070018
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -070019import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
Sandeep Siddhartha05589722014-07-17 16:21:54 -070023import android.hardware.soundtrigger.IRecognitionStatusCallback;
24import android.hardware.soundtrigger.SoundTrigger;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080025import android.hardware.soundtrigger.SoundTrigger.GenericRecognitionEvent;
26import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
Sandeep Siddhartha05589722014-07-17 16:21:54 -070027import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
Sandeep Siddhartha68173372014-07-28 13:25:30 -070028import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
29import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
Sandeep Siddhartha05589722014-07-17 16:21:54 -070030import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
Sandeepd7018202014-07-10 15:15:39 -070031import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -070032import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
Sandeepd7018202014-07-10 15:15:39 -070033import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080034import android.hardware.soundtrigger.SoundTrigger.SoundModel;
Eric Laurentd3b82232014-07-30 08:57:39 -070035import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
Sandeep Siddhartha05589722014-07-17 16:21:54 -070036import android.hardware.soundtrigger.SoundTriggerModule;
Hall Liu5fb337f2017-11-22 17:38:15 -080037import android.os.Binder;
Chris Thornton2411a2c2017-03-22 13:52:50 -070038import android.os.DeadObjectException;
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -070039import android.os.PowerManager;
Makoto Onuki2eccd022017-11-01 13:44:23 -070040import android.os.PowerManager.ServiceType;
Sandeep Siddhartha05589722014-07-17 16:21:54 -070041import android.os.RemoteException;
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -070042import android.telephony.PhoneStateListener;
43import android.telephony.TelephonyManager;
Sandeepd7018202014-07-10 15:15:39 -070044import android.util.Slog;
mike dooleyb2ab04a2018-11-07 15:48:54 +010045
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -080046import com.android.internal.logging.MetricsLogger;
Sandeepd7018202014-07-10 15:15:39 -070047
Sandeep Siddhartha6b8556d2014-08-06 19:47:25 -070048import java.io.FileDescriptor;
49import java.io.PrintWriter;
Sandeepd7018202014-07-10 15:15:39 -070050import java.util.ArrayList;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080051import java.util.HashMap;
Chris Thornton2411a2c2017-03-22 13:52:50 -070052import java.util.Iterator;
53import java.util.Map;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080054import java.util.UUID;
Sandeepd7018202014-07-10 15:15:39 -070055
56/**
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080057 * Helper for {@link SoundTrigger} APIs. Supports two types of models:
58 * (i) A voice model which is exported via the {@link VoiceInteractionService}. There can only be
59 * a single voice model running on the DSP at any given time.
60 *
61 * (ii) Generic sound-trigger models: Supports multiple of these.
62 *
Sandeepd7018202014-07-10 15:15:39 -070063 * Currently this just acts as an abstraction over all SoundTrigger API calls.
Sandeepd7018202014-07-10 15:15:39 -070064 * @hide
65 */
66public class SoundTriggerHelper implements SoundTrigger.StatusListener {
67 static final String TAG = "SoundTriggerHelper";
Sandeep Siddhartha8cf8f712014-09-15 12:51:08 -070068 static final boolean DBG = false;
Sandeepd7018202014-07-10 15:15:39 -070069
70 /**
Sandeep Siddhartha05589722014-07-17 16:21:54 -070071 * Return codes for {@link #startRecognition(int, KeyphraseSoundModel,
72 * IRecognitionStatusCallback, RecognitionConfig)},
73 * {@link #stopRecognition(int, IRecognitionStatusCallback)}
Sandeepd7018202014-07-10 15:15:39 -070074 */
Sandeep Siddhartha05589722014-07-17 16:21:54 -070075 public static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR;
76 public static final int STATUS_OK = SoundTrigger.STATUS_OK;
Sandeepd7018202014-07-10 15:15:39 -070077
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -070078 private static final int INVALID_VALUE = Integer.MIN_VALUE;
Sandeepd7018202014-07-10 15:15:39 -070079
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -070080 /** The {@link ModuleProperties} for the system, or null if none exists. */
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080081 final ModuleProperties mModuleProperties;
Sandeepd7018202014-07-10 15:15:39 -070082
83 /** The properties for the DSP module */
Eric Laurent2475e382014-09-09 15:52:25 -070084 private SoundTriggerModule mModule;
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -070085 private final Object mLock = new Object();
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -070086 private final Context mContext;
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -070087 private final TelephonyManager mTelephonyManager;
88 private final PhoneStateListener mPhoneStateListener;
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -070089 private final PowerManager mPowerManager;
Sandeepd7018202014-07-10 15:15:39 -070090
Arunesh Mishrafac25152016-04-10 20:35:16 -070091 // The SoundTriggerManager layer handles multiple recognition models of type generic and
92 // keyphrase. We store the ModelData here in a hashmap.
93 private final HashMap<UUID, ModelData> mModelDataMap;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080094
Arunesh Mishrafac25152016-04-10 20:35:16 -070095 // An index of keyphrase sound models so that we can reach them easily. We support indexing
96 // keyphrase sound models with a keyphrase ID. Sound model with the same keyphrase ID will
97 // replace an existing model, thus there is a 1:1 mapping from keyphrase ID to a voice
98 // sound model.
99 private HashMap<Integer, UUID> mKeyphraseUuidMap;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800100
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700101 private boolean mCallActive = false;
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700102 private boolean mIsPowerSaveMode = false;
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700103 // Indicates if the native sound trigger service is disabled or not.
104 // This is an indirect indication of the microphone being open in some other application.
105 private boolean mServiceDisabled = false;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800106
Eric Laurent80565fb2019-07-18 08:22:38 -0700107 // Whether ANY recognition (keyphrase or generic) has been requested.
108 private boolean mRecognitionRequested = false;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800109
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700110 private PowerSaveModeListener mPowerSaveModeListener;
Sandeepd7018202014-07-10 15:15:39 -0700111
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700112 SoundTriggerHelper(Context context) {
Sandeepd7018202014-07-10 15:15:39 -0700113 ArrayList <ModuleProperties> modules = new ArrayList<>();
114 int status = SoundTrigger.listModules(modules);
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700115 mContext = context;
116 mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
117 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
Arunesh Mishrafac25152016-04-10 20:35:16 -0700118 mModelDataMap = new HashMap<UUID, ModelData>();
119 mKeyphraseUuidMap = new HashMap<Integer, UUID>();
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700120 mPhoneStateListener = new MyCallStateListener();
Sandeepd7018202014-07-10 15:15:39 -0700121 if (status != SoundTrigger.STATUS_OK || modules.size() == 0) {
Sandeep Siddharthaa433af62014-07-16 15:36:44 -0700122 Slog.w(TAG, "listModules status=" + status + ", # of modules=" + modules.size());
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800123 mModuleProperties = null;
Sandeepd7018202014-07-10 15:15:39 -0700124 mModule = null;
125 } else {
126 // TODO: Figure out how to determine which module corresponds to the DSP hardware.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800127 mModuleProperties = modules.get(0);
128 }
129 }
130
131 /**
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800132 * Starts recognition for the given generic sound model ID. This is a wrapper around {@link
133 * startRecognition()}.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800134 *
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800135 * @param modelId UUID of the sound model.
136 * @param soundModel The generic sound model to use for recognition.
137 * @param callback Callack for the recognition events related to the given keyphrase.
138 * @param recognitionConfig Instance of RecognitionConfig containing the parameters for the
139 * recognition.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800140 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
141 */
142 int startGenericRecognition(UUID modelId, GenericSoundModel soundModel,
143 IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800144 MetricsLogger.count(mContext, "sth_start_recognition", 1);
145 if (modelId == null || soundModel == null || callback == null ||
146 recognitionConfig == null) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800147 Slog.w(TAG, "Passed in bad data to startGenericRecognition().");
148 return STATUS_ERROR;
149 }
150
151 synchronized (mLock) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800152 ModelData modelData = getOrCreateGenericModelDataLocked(modelId);
Arunesh Mishrafac25152016-04-10 20:35:16 -0700153 if (modelData == null) {
154 Slog.w(TAG, "Irrecoverable error occurred, check UUID / sound model data.");
155 return STATUS_ERROR;
156 }
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800157 return startRecognition(soundModel, modelData, callback, recognitionConfig,
158 INVALID_VALUE /* keyphraseId */);
159 }
160 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800161
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800162 /**
163 * Starts recognition for the given keyphraseId.
164 *
165 * @param keyphraseId The identifier of the keyphrase for which
166 * the recognition is to be started.
167 * @param soundModel The sound model to use for recognition.
168 * @param callback The callback for the recognition events related to the given keyphrase.
169 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
170 */
171 int startKeyphraseRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
172 IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) {
173 synchronized (mLock) {
174 MetricsLogger.count(mContext, "sth_start_recognition", 1);
175 if (soundModel == null || callback == null || recognitionConfig == null) {
176 return STATUS_ERROR;
177 }
178
179 if (DBG) {
180 Slog.d(TAG, "startKeyphraseRecognition for keyphraseId=" + keyphraseId
181 + " soundModel=" + soundModel + ", callback=" + callback.asBinder()
182 + ", recognitionConfig=" + recognitionConfig);
183 Slog.d(TAG, "moduleProperties=" + mModuleProperties);
Arunesh Mishrafac25152016-04-10 20:35:16 -0700184 dumpModelStateLocked();
185 }
186
187 ModelData model = getKeyphraseModelDataLocked(keyphraseId);
188 if (model != null && !model.isKeyphraseModel()) {
189 Slog.e(TAG, "Generic model with same UUID exists.");
190 return STATUS_ERROR;
191 }
192
193 // Process existing model first.
Chris Thornton41e04a42016-04-18 16:51:08 -0700194 if (model != null && !model.getModelId().equals(soundModel.uuid)) {
Arunesh Mishrafac25152016-04-10 20:35:16 -0700195 // The existing model has a different UUID, should be replaced.
Chris Thornton23a57402017-06-10 17:31:57 -0700196 int status = cleanUpExistingKeyphraseModelLocked(model);
Arunesh Mishrafac25152016-04-10 20:35:16 -0700197 if (status != STATUS_OK) {
198 return status;
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800199 }
Chris Thornton41e04a42016-04-18 16:51:08 -0700200 removeKeyphraseModelLocked(keyphraseId);
Arunesh Mishrafac25152016-04-10 20:35:16 -0700201 model = null;
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800202 }
Arunesh Mishrafac25152016-04-10 20:35:16 -0700203
204 // We need to create a new one: either no previous models existed for given keyphrase id
205 // or the existing model had a different UUID and was cleaned up.
206 if (model == null) {
207 model = createKeyphraseModelDataLocked(soundModel.uuid, keyphraseId);
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800208 }
Arunesh Mishrafac25152016-04-10 20:35:16 -0700209
210 return startRecognition(soundModel, model, callback, recognitionConfig,
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800211 keyphraseId);
212 }
213 }
214
Chris Thornton23a57402017-06-10 17:31:57 -0700215 private int cleanUpExistingKeyphraseModelLocked(ModelData modelData) {
Arunesh Mishrafac25152016-04-10 20:35:16 -0700216 // Stop and clean up a previous ModelData if one exists. This usually is used when the
217 // previous model has a different UUID for the same keyphrase ID.
218 int status = tryStopAndUnloadLocked(modelData, true /* stop */, true /* unload */);
219 if (status != STATUS_OK) {
220 Slog.w(TAG, "Unable to stop or unload previous model: " +
221 modelData.toString());
222 }
223 return status;
224 }
225
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800226 /**
227 * Starts recognition for the given sound model. A single routine for both keyphrase and
228 * generic sound models.
229 *
230 * @param soundModel The sound model to use for recognition.
231 * @param modelData Instance of {@link #ModelData} for the given model.
232 * @param callback Callback for the recognition events related to the given keyphrase.
233 * @param recognitionConfig Instance of {@link RecognitionConfig} containing the parameters
234 * @param keyphraseId Keyphrase ID for keyphrase models only. Pass in INVALID_VALUE for other
235 * models.
236 * for the recognition.
237 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
238 */
239 int startRecognition(SoundModel soundModel, ModelData modelData,
240 IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig,
241 int keyphraseId) {
242 synchronized (mLock) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800243 if (mModuleProperties == null) {
244 Slog.w(TAG, "Attempting startRecognition without the capability");
245 return STATUS_ERROR;
246 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800247 if (mModule == null) {
248 mModule = SoundTrigger.attachModule(mModuleProperties.id, this, null);
249 if (mModule == null) {
250 Slog.w(TAG, "startRecognition cannot attach to sound trigger module");
251 return STATUS_ERROR;
252 }
253 }
254
Arunesh Mishrafac25152016-04-10 20:35:16 -0700255 // If the existing SoundModel is different (for the same UUID for Generic and same
256 // keyphrase ID for voice), ensure that it is unloaded and stopped before proceeding.
257 // This works for both keyphrase and generic models. This logic also ensures that a
258 // previously loaded (or started) model is appropriately stopped. Since this is a
259 // generalization of the previous logic with a single keyphrase model, we should have
260 // no regression with the previous version of this code as was given in the
261 // startKeyphrase() routine.
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800262 if (modelData.getSoundModel() != null) {
Arunesh Mishrafac25152016-04-10 20:35:16 -0700263 boolean stopModel = false; // Stop the model after checking that it is started.
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800264 boolean unloadModel = false;
265 if (modelData.getSoundModel().equals(soundModel) && modelData.isModelStarted()) {
266 // The model has not changed, but the previous model is "started".
267 // Stop the previously running model.
268 stopModel = true;
269 unloadModel = false; // No need to unload if the model hasn't changed.
270 } else if (!modelData.getSoundModel().equals(soundModel)) {
271 // We have a different model for this UUID. Stop and unload if needed. This
272 // helps maintain the singleton restriction for keyphrase sound models.
273 stopModel = modelData.isModelStarted();
274 unloadModel = modelData.isModelLoaded();
275 }
276 if (stopModel || unloadModel) {
277 int status = tryStopAndUnloadLocked(modelData, stopModel, unloadModel);
278 if (status != STATUS_OK) {
279 Slog.w(TAG, "Unable to stop or unload previous model: " +
280 modelData.toString());
281 return status;
282 }
283 }
284 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800285
286 IRecognitionStatusCallback oldCallback = modelData.getCallback();
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800287 if (oldCallback != null && oldCallback.asBinder() != callback.asBinder()) {
288 Slog.w(TAG, "Canceling previous recognition for model id: " +
289 modelData.getModelId());
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800290 try {
291 oldCallback.onError(STATUS_ERROR);
292 } catch (RemoteException e) {
293 Slog.w(TAG, "RemoteException in onDetectionStopped", e);
294 }
295 modelData.clearCallback();
296 }
297
Arunesh Mishrafac25152016-04-10 20:35:16 -0700298 // Load the model if it is not loaded.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800299 if (!modelData.isModelLoaded()) {
Chris Thorntona533b992017-03-24 17:30:29 -0700300 // Before we try and load this model, we should first make sure that any other
301 // models that don't have an active recognition/dead callback are unloaded. Since
302 // there is a finite limit on the number of models that the hardware may be able to
303 // have loaded, we want to make sure there's room for our model.
304 stopAndUnloadDeadModelsLocked();
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800305 int[] handle = new int[] { INVALID_VALUE };
306 int status = mModule.loadSoundModel(soundModel, handle);
307 if (status != SoundTrigger.STATUS_OK) {
308 Slog.w(TAG, "loadSoundModel call failed with " + status);
309 return status;
310 }
311 if (handle[0] == INVALID_VALUE) {
312 Slog.w(TAG, "loadSoundModel call returned invalid sound model handle");
313 return STATUS_ERROR;
314 }
315 modelData.setHandle(handle[0]);
Arunesh Mishra933da812016-02-18 15:31:54 -0800316 modelData.setLoaded();
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800317 Slog.d(TAG, "Sound model loaded with handle:" + handle[0]);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800318 }
319 modelData.setCallback(callback);
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800320 modelData.setRequested(true);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800321 modelData.setRecognitionConfig(recognitionConfig);
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800322 modelData.setSoundModel(soundModel);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800323
Eric Laurent80565fb2019-07-18 08:22:38 -0700324 int status = startRecognitionLocked(modelData,
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800325 false /* Don't notify for synchronous calls */);
Eric Laurent80565fb2019-07-18 08:22:38 -0700326
327 // Initialize power save, call active state monitoring logic.
Eric Laurent2fedc552019-09-11 18:10:15 -0700328 if (status == STATUS_OK) {
Eric Laurent80565fb2019-07-18 08:22:38 -0700329 initializeTelephonyAndPowerStateListeners();
Eric Laurent80565fb2019-07-18 08:22:38 -0700330 }
331
332 return status;
Sandeepd7018202014-07-10 15:15:39 -0700333 }
334 }
335
336 /**
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800337 * Stops recognition for the given generic sound model. This is a wrapper for {@link
338 * #stopRecognition}.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800339 *
340 * @param modelId The identifier of the generic sound model for which
341 * the recognition is to be stopped.
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800342 * @param callback The callback for the recognition events related to the given sound model.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800343 *
344 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
345 */
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800346 int stopGenericRecognition(UUID modelId, IRecognitionStatusCallback callback) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800347 synchronized (mLock) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800348 MetricsLogger.count(mContext, "sth_stop_recognition", 1);
349 if (callback == null || modelId == null) {
350 Slog.e(TAG, "Null callbackreceived for stopGenericRecognition() for modelid:" +
351 modelId);
352 return STATUS_ERROR;
353 }
354
Arunesh Mishrafac25152016-04-10 20:35:16 -0700355 ModelData modelData = mModelDataMap.get(modelId);
356 if (modelData == null || !modelData.isGenericModel()) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800357 Slog.w(TAG, "Attempting stopRecognition on invalid model with id:" + modelId);
358 return STATUS_ERROR;
359 }
360
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800361 int status = stopRecognition(modelData, callback);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800362 if (status != SoundTrigger.STATUS_OK) {
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800363 Slog.w(TAG, "stopGenericRecognition failed: " + status);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800364 }
365 return status;
366 }
367 }
368
369 /**
Sandeep Siddhartha05589722014-07-17 16:21:54 -0700370 * Stops recognition for the given {@link Keyphrase} if a recognition is
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800371 * currently active. This is a wrapper for {@link #stopRecognition()}.
Sandeepd7018202014-07-10 15:15:39 -0700372 *
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -0700373 * @param keyphraseId The identifier of the keyphrase for which
374 * the recognition is to be stopped.
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800375 * @param callback The callback for the recognition events related to the given keyphrase.
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -0700376 *
Sandeepd7018202014-07-10 15:15:39 -0700377 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
378 */
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800379 int stopKeyphraseRecognition(int keyphraseId, IRecognitionStatusCallback callback) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700380 synchronized (mLock) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800381 MetricsLogger.count(mContext, "sth_stop_recognition", 1);
382 if (callback == null) {
383 Slog.e(TAG, "Null callback received for stopKeyphraseRecognition() for keyphraseId:" +
384 keyphraseId);
385 return STATUS_ERROR;
386 }
387
Arunesh Mishrafac25152016-04-10 20:35:16 -0700388 ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
389 if (modelData == null || !modelData.isKeyphraseModel()) {
Chris Thornton2411a2c2017-03-22 13:52:50 -0700390 Slog.e(TAG, "No model exists for given keyphrase Id " + keyphraseId);
Arunesh Mishrafac25152016-04-10 20:35:16 -0700391 return STATUS_ERROR;
392 }
393
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700394 if (DBG) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800395 Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId + ", callback =" +
396 callback.asBinder());
Arunesh Mishrafac25152016-04-10 20:35:16 -0700397 Slog.d(TAG, "current callback=" + (modelData == null ? "null" :
398 modelData.getCallback().asBinder()));
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700399 }
Arunesh Mishrafac25152016-04-10 20:35:16 -0700400 int status = stopRecognition(modelData, callback);
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -0700401 if (status != SoundTrigger.STATUS_OK) {
Sandeep Siddhartha2c0273e2014-08-01 11:32:03 -0700402 return status;
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -0700403 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700404
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700405 return status;
Sandeepd7018202014-07-10 15:15:39 -0700406 }
407 }
408
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700409 /**
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800410 * Stops recognition for the given ModelData instance.
411 *
412 * @param modelData Instance of {@link #ModelData} sound model.
413 * @param callback The callback for the recognition events related to the given keyphrase.
414 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
415 */
416 private int stopRecognition(ModelData modelData, IRecognitionStatusCallback callback) {
417 synchronized (mLock) {
418 if (callback == null) {
419 return STATUS_ERROR;
420 }
421 if (mModuleProperties == null || mModule == null) {
422 Slog.w(TAG, "Attempting stopRecognition without the capability");
423 return STATUS_ERROR;
424 }
425
426 IRecognitionStatusCallback currentCallback = modelData.getCallback();
Chris Thornton37f97762016-04-29 16:23:45 -0700427 if (modelData == null || currentCallback == null ||
428 (!modelData.isRequested() && !modelData.isModelStarted())) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800429 // startGenericRecognition hasn't been called or it failed.
Chris Thornton37f97762016-04-29 16:23:45 -0700430 Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition");
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800431 return STATUS_ERROR;
432 }
433
434 if (currentCallback.asBinder() != callback.asBinder()) {
435 // We don't allow a different listener to stop the recognition than the one
436 // that started it.
437 Slog.w(TAG, "Attempting stopRecognition for another recognition");
438 return STATUS_ERROR;
439 }
440
441 // Request stop recognition via the update() method.
442 modelData.setRequested(false);
443 int status = updateRecognitionLocked(modelData, isRecognitionAllowed(),
444 false /* don't notify for synchronous calls */);
445 if (status != SoundTrigger.STATUS_OK) {
446 return status;
447 }
448
449 // We leave the sound model loaded but not started, this helps us when we start back.
450 // Also clear the internal state once the recognition has been stopped.
451 modelData.setLoaded();
452 modelData.clearCallback();
453 modelData.setRecognitionConfig(null);
454
Eric Laurent80565fb2019-07-18 08:22:38 -0700455 if (!computeRecognitionRequestedLocked()) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800456 internalClearGlobalStateLocked();
457 }
458
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800459 return status;
460 }
461 }
462
463 // Stop a previously started model if it was started. Optionally, unload if the previous model
464 // is stale and is about to be replaced.
465 // Needs to be called with the mLock held.
466 private int tryStopAndUnloadLocked(ModelData modelData, boolean stopModel,
467 boolean unloadModel) {
468 int status = STATUS_OK;
469 if (modelData.isModelNotLoaded()) {
470 return status;
471 }
472 if (stopModel && modelData.isModelStarted()) {
473 status = stopRecognitionLocked(modelData,
474 false /* don't notify for synchronous calls */);
475 if (status != SoundTrigger.STATUS_OK) {
476 Slog.w(TAG, "stopRecognition failed: " + status);
477 return status;
478 }
479 }
480
481 if (unloadModel && modelData.isModelLoaded()) {
482 Slog.d(TAG, "Unloading previously loaded stale model.");
Eric Laurent2fedc552019-09-11 18:10:15 -0700483 if (mModule == null) {
484 return STATUS_ERROR;
485 }
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800486 status = mModule.unloadSoundModel(modelData.getHandle());
487 MetricsLogger.count(mContext, "sth_unloading_stale_model", 1);
488 if (status != SoundTrigger.STATUS_OK) {
489 Slog.w(TAG, "unloadSoundModel call failed with " + status);
490 } else {
491 // Clear the ModelData state if successful.
492 modelData.clearState();
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800493 }
494 }
495 return status;
496 }
497
Arunesh Mishra55a9b002016-02-01 14:06:37 -0800498 public ModuleProperties getModuleProperties() {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800499 return mModuleProperties;
Arunesh Mishra55a9b002016-02-01 14:06:37 -0800500 }
501
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800502 int unloadKeyphraseSoundModel(int keyphraseId) {
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800503 synchronized (mLock) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800504 MetricsLogger.count(mContext, "sth_unload_keyphrase_sound_model", 1);
Arunesh Mishrafac25152016-04-10 20:35:16 -0700505 ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
506 if (mModule == null || modelData == null || modelData.getHandle() == INVALID_VALUE ||
507 !modelData.isKeyphraseModel()) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800508 return STATUS_ERROR;
509 }
510
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800511 // Stop recognition if it's the current one.
Arunesh Mishrafac25152016-04-10 20:35:16 -0700512 modelData.setRequested(false);
513 int status = updateRecognitionLocked(modelData, isRecognitionAllowed(),
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800514 false /* don't notify */);
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800515 if (status != SoundTrigger.STATUS_OK) {
516 Slog.w(TAG, "Stop recognition failed for keyphrase ID:" + status);
517 }
518
Arunesh Mishrafac25152016-04-10 20:35:16 -0700519 status = mModule.unloadSoundModel(modelData.getHandle());
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800520 if (status != SoundTrigger.STATUS_OK) {
521 Slog.w(TAG, "unloadKeyphraseSoundModel call failed with " + status);
522 }
Arunesh Mishrafac25152016-04-10 20:35:16 -0700523
524 // Remove it from existence.
525 removeKeyphraseModelLocked(keyphraseId);
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800526 return status;
527 }
528 }
529
530 int unloadGenericSoundModel(UUID modelId) {
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800531 synchronized (mLock) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800532 MetricsLogger.count(mContext, "sth_unload_generic_sound_model", 1);
533 if (modelId == null || mModule == null) {
534 return STATUS_ERROR;
535 }
Arunesh Mishrafac25152016-04-10 20:35:16 -0700536 ModelData modelData = mModelDataMap.get(modelId);
537 if (modelData == null || !modelData.isGenericModel()) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800538 Slog.w(TAG, "Unload error: Attempting unload invalid generic model with id:" +
539 modelId);
Ryan Bavetta79655eb2016-03-07 14:34:51 -0800540 return STATUS_ERROR;
541 }
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800542 if (!modelData.isModelLoaded()) {
543 // Nothing to do here.
544 Slog.i(TAG, "Unload: Given generic model is not loaded:" + modelId);
545 return STATUS_OK;
546 }
547 if (modelData.isModelStarted()) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800548 int status = stopRecognitionLocked(modelData,
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800549 false /* don't notify for synchronous calls */);
550 if (status != SoundTrigger.STATUS_OK) {
551 Slog.w(TAG, "stopGenericRecognition failed: " + status);
552 }
553 }
554
Eric Laurent2fedc552019-09-11 18:10:15 -0700555 if (mModule == null) {
556 return STATUS_ERROR;
557 }
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800558 int status = mModule.unloadSoundModel(modelData.getHandle());
559 if (status != SoundTrigger.STATUS_OK) {
560 Slog.w(TAG, "unloadGenericSoundModel() call failed with " + status);
561 Slog.w(TAG, "unloadGenericSoundModel() force-marking model as unloaded.");
562 }
Arunesh Mishrafac25152016-04-10 20:35:16 -0700563
564 // Remove it from existence.
565 mModelDataMap.remove(modelId);
566 if (DBG) dumpModelStateLocked();
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800567 return status;
568 }
569 }
570
Chris Thorntonba08b792017-06-08 22:34:37 -0700571 boolean isRecognitionRequested(UUID modelId) {
572 synchronized (mLock) {
573 ModelData modelData = mModelDataMap.get(modelId);
574 return modelData != null && modelData.isRequested();
575 }
576 }
577
mike dooleyb2ab04a2018-11-07 15:48:54 +0100578 int getGenericModelState(UUID modelId) {
Michael Dooley291751e2018-10-16 19:53:29 +0000579 synchronized (mLock) {
580 MetricsLogger.count(mContext, "sth_get_generic_model_state", 1);
581 if (modelId == null || mModule == null) {
mike dooleyb2ab04a2018-11-07 15:48:54 +0100582 return STATUS_ERROR;
Michael Dooley291751e2018-10-16 19:53:29 +0000583 }
584 ModelData modelData = mModelDataMap.get(modelId);
585 if (modelData == null || !modelData.isGenericModel()) {
586 Slog.w(TAG, "GetGenericModelState error: Invalid generic model id:" +
587 modelId);
mike dooleyb2ab04a2018-11-07 15:48:54 +0100588 return STATUS_ERROR;
Michael Dooley291751e2018-10-16 19:53:29 +0000589 }
590 if (!modelData.isModelLoaded()) {
591 Slog.i(TAG, "GetGenericModelState: Given generic model is not loaded:" + modelId);
mike dooleyb2ab04a2018-11-07 15:48:54 +0100592 return STATUS_ERROR;
Michael Dooley291751e2018-10-16 19:53:29 +0000593 }
594 if (!modelData.isModelStarted()) {
595 Slog.i(TAG, "GetGenericModelState: Given generic model is not started:" + modelId);
mike dooleyb2ab04a2018-11-07 15:48:54 +0100596 return STATUS_ERROR;
Michael Dooley291751e2018-10-16 19:53:29 +0000597 }
598
mike dooleyb2ab04a2018-11-07 15:48:54 +0100599 return mModule.getModelState(modelData.getHandle());
Michael Dooley291751e2018-10-16 19:53:29 +0000600 }
601 }
602
mike dooleyb2ab04a2018-11-07 15:48:54 +0100603 int getKeyphraseModelState(UUID modelId) {
Michael Dooley291751e2018-10-16 19:53:29 +0000604 Slog.w(TAG, "GetKeyphraseModelState error: Not implemented");
mike dooleyb2ab04a2018-11-07 15:48:54 +0100605 return STATUS_ERROR;
Michael Dooley291751e2018-10-16 19:53:29 +0000606 }
607
Sandeepd7018202014-07-10 15:15:39 -0700608 //---- SoundTrigger.StatusListener methods
609 @Override
610 public void onRecognition(RecognitionEvent event) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800611 if (event == null) {
612 Slog.w(TAG, "Null recognition event!");
613 return;
614 }
615
616 if (!(event instanceof KeyphraseRecognitionEvent) &&
617 !(event instanceof GenericRecognitionEvent)) {
Chris Thornton37f97762016-04-29 16:23:45 -0700618 Slog.w(TAG, "Invalid recognition event type (not one of generic or keyphrase)!");
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -0700619 return;
620 }
Sandeepd7018202014-07-10 15:15:39 -0700621
Sandeep Siddhartha68173372014-07-28 13:25:30 -0700622 if (DBG) Slog.d(TAG, "onRecognition: " + event);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700623 synchronized (mLock) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700624 switch (event.status) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700625 case SoundTrigger.RECOGNITION_STATUS_ABORT:
Chris Thornton37f97762016-04-29 16:23:45 -0700626 onRecognitionAbortLocked(event);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700627 break;
628 case SoundTrigger.RECOGNITION_STATUS_FAILURE:
Chris Thornton37f97762016-04-29 16:23:45 -0700629 // Fire failures to all listeners since it's not tied to a keyphrase.
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700630 onRecognitionFailureLocked();
631 break;
632 case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
mike dooley23c62562018-12-20 16:05:24 +0100633 case SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE:
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800634 if (isKeyphraseRecognitionEvent(event)) {
635 onKeyphraseRecognitionSuccessLocked((KeyphraseRecognitionEvent) event);
636 } else {
637 onGenericRecognitionSuccessLocked((GenericRecognitionEvent) event);
638 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700639 break;
640 }
Sandeepd7018202014-07-10 15:15:39 -0700641 }
642 }
643
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800644 private boolean isKeyphraseRecognitionEvent(RecognitionEvent event) {
Arunesh Mishraf47f1732016-02-18 16:16:12 -0800645 return event instanceof KeyphraseRecognitionEvent;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800646 }
647
648 private void onGenericRecognitionSuccessLocked(GenericRecognitionEvent event) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800649 MetricsLogger.count(mContext, "sth_generic_recognition_event", 1);
mike dooley23c62562018-12-20 16:05:24 +0100650 if (event.status != SoundTrigger.RECOGNITION_STATUS_SUCCESS
651 && event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800652 return;
653 }
Ryan Bavetta79655eb2016-03-07 14:34:51 -0800654 ModelData model = getModelDataForLocked(event.soundModelHandle);
Arunesh Mishrafac25152016-04-10 20:35:16 -0700655 if (model == null || !model.isGenericModel()) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800656 Slog.w(TAG, "Generic recognition event: Model does not exist for handle: " +
657 event.soundModelHandle);
658 return;
659 }
660
661 IRecognitionStatusCallback callback = model.getCallback();
662 if (callback == null) {
663 Slog.w(TAG, "Generic recognition event: Null callback for model handle: " +
664 event.soundModelHandle);
665 return;
666 }
667
mike dooleyda480922019-01-04 09:39:47 +0100668 model.setStopped();
669
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800670 try {
Arunesh Mishraf47f1732016-02-18 16:16:12 -0800671 callback.onGenericSoundTriggerDetected((GenericRecognitionEvent) event);
Chris Thornton2411a2c2017-03-22 13:52:50 -0700672 } catch (DeadObjectException e) {
Chris Thornton23a57402017-06-10 17:31:57 -0700673 forceStopAndUnloadModelLocked(model, e);
Chris Thornton2411a2c2017-03-22 13:52:50 -0700674 return;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800675 } catch (RemoteException e) {
Arunesh Mishraf47f1732016-02-18 16:16:12 -0800676 Slog.w(TAG, "RemoteException in onGenericSoundTriggerDetected", e);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800677 }
678
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800679 RecognitionConfig config = model.getRecognitionConfig();
680 if (config == null) {
681 Slog.w(TAG, "Generic recognition event: Null RecognitionConfig for model handle: " +
682 event.soundModelHandle);
683 return;
684 }
685
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800686 model.setRequested(config.allowMultipleTriggers);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800687 // TODO: Remove this block if the lower layer supports multiple triggers.
Chris Thornton37f97762016-04-29 16:23:45 -0700688 if (model.isRequested()) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800689 updateRecognitionLocked(model, isRecognitionAllowed() /* isAllowed */,
690 true /* notify */);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800691 }
692 }
693
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700694 @Override
Eric Laurentd3b82232014-07-30 08:57:39 -0700695 public void onSoundModelUpdate(SoundModelEvent event) {
696 if (event == null) {
697 Slog.w(TAG, "Invalid sound model event!");
698 return;
699 }
Eric Laurentd3b82232014-07-30 08:57:39 -0700700 if (DBG) Slog.d(TAG, "onSoundModelUpdate: " + event);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700701 synchronized (mLock) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800702 MetricsLogger.count(mContext, "sth_sound_model_updated", 1);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700703 onSoundModelUpdatedLocked(event);
704 }
Eric Laurentd3b82232014-07-30 08:57:39 -0700705 }
706
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700707 @Override
Eric Laurentd3b82232014-07-30 08:57:39 -0700708 public void onServiceStateChange(int state) {
709 if (DBG) Slog.d(TAG, "onServiceStateChange, state: " + state);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700710 synchronized (mLock) {
711 onServiceStateChangedLocked(SoundTrigger.SERVICE_STATE_DISABLED == state);
712 }
Eric Laurentd3b82232014-07-30 08:57:39 -0700713 }
714
Sandeepd7018202014-07-10 15:15:39 -0700715 @Override
716 public void onServiceDied() {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700717 Slog.e(TAG, "onServiceDied!!");
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800718 MetricsLogger.count(mContext, "sth_service_died", 1);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700719 synchronized (mLock) {
720 onServiceDiedLocked();
Sandeep Siddhartha68173372014-07-28 13:25:30 -0700721 }
Sandeepd7018202014-07-10 15:15:39 -0700722 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700723
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700724 private void onCallStateChangedLocked(boolean callActive) {
725 if (mCallActive == callActive) {
726 // We consider multiple call states as being active
727 // so we check if something really changed or not here.
728 return;
729 }
730 mCallActive = callActive;
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800731 updateAllRecognitionsLocked(true /* notify */);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700732 }
733
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700734 private void onPowerSaveModeChangedLocked(boolean isPowerSaveMode) {
735 if (mIsPowerSaveMode == isPowerSaveMode) {
736 return;
737 }
738 mIsPowerSaveMode = isPowerSaveMode;
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800739 updateAllRecognitionsLocked(true /* notify */);
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700740 }
741
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700742 private void onSoundModelUpdatedLocked(SoundModelEvent event) {
743 // TODO: Handle sound model update here.
744 }
745
746 private void onServiceStateChangedLocked(boolean disabled) {
747 if (disabled == mServiceDisabled) {
748 return;
749 }
750 mServiceDisabled = disabled;
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800751 updateAllRecognitionsLocked(true /* notify */);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700752 }
753
Chris Thornton37f97762016-04-29 16:23:45 -0700754 private void onRecognitionAbortLocked(RecognitionEvent event) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700755 Slog.w(TAG, "Recognition aborted");
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800756 MetricsLogger.count(mContext, "sth_recognition_aborted", 1);
Chris Thornton37f97762016-04-29 16:23:45 -0700757 ModelData modelData = getModelDataForLocked(event.soundModelHandle);
Chris Thorntond0a83b82016-07-19 18:56:15 -0700758 if (modelData != null && modelData.isModelStarted()) {
Chris Thornton37f97762016-04-29 16:23:45 -0700759 modelData.setStopped();
Chris Thorntond0a83b82016-07-19 18:56:15 -0700760 try {
761 modelData.getCallback().onRecognitionPaused();
Chris Thornton2411a2c2017-03-22 13:52:50 -0700762 } catch (DeadObjectException e) {
Chris Thornton23a57402017-06-10 17:31:57 -0700763 forceStopAndUnloadModelLocked(modelData, e);
Chris Thorntond0a83b82016-07-19 18:56:15 -0700764 } catch (RemoteException e) {
765 Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
766 }
Chris Thornton37f97762016-04-29 16:23:45 -0700767 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700768 }
769
770 private void onRecognitionFailureLocked() {
771 Slog.w(TAG, "Recognition failure");
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800772 MetricsLogger.count(mContext, "sth_recognition_failure_event", 1);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700773 try {
Chris Thornton23a57402017-06-10 17:31:57 -0700774 sendErrorCallbacksToAllLocked(STATUS_ERROR);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700775 } finally {
Arunesh Mishrafac25152016-04-10 20:35:16 -0700776 internalClearModelStateLocked();
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800777 internalClearGlobalStateLocked();
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700778 }
779 }
780
Arunesh Mishrafac25152016-04-10 20:35:16 -0700781 private int getKeyphraseIdFromEvent(KeyphraseRecognitionEvent event) {
782 if (event == null) {
783 Slog.w(TAG, "Null RecognitionEvent received.");
784 return INVALID_VALUE;
785 }
786 KeyphraseRecognitionExtra[] keyphraseExtras =
787 ((KeyphraseRecognitionEvent) event).keyphraseExtras;
788 if (keyphraseExtras == null || keyphraseExtras.length == 0) {
789 Slog.w(TAG, "Invalid keyphrase recognition event!");
790 return INVALID_VALUE;
791 }
792 // TODO: Handle more than one keyphrase extras.
793 return keyphraseExtras[0].id;
794 }
795
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800796 private void onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700797 Slog.i(TAG, "Recognition success");
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800798 MetricsLogger.count(mContext, "sth_keyphrase_recognition_event", 1);
Arunesh Mishrafac25152016-04-10 20:35:16 -0700799 int keyphraseId = getKeyphraseIdFromEvent(event);
800 ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800801
Arunesh Mishrafac25152016-04-10 20:35:16 -0700802 if (modelData == null || !modelData.isKeyphraseModel()) {
803 Slog.e(TAG, "Keyphase model data does not exist for ID:" + keyphraseId);
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800804 return;
805 }
806
Arunesh Mishrafac25152016-04-10 20:35:16 -0700807 if (modelData.getCallback() == null) {
808 Slog.w(TAG, "Received onRecognition event without callback for keyphrase model.");
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700809 return;
810 }
mike dooley23c62562018-12-20 16:05:24 +0100811
mike dooleyda480922019-01-04 09:39:47 +0100812 modelData.setStopped();
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700813
814 try {
Arunesh Mishrafac25152016-04-10 20:35:16 -0700815 modelData.getCallback().onKeyphraseDetected((KeyphraseRecognitionEvent) event);
Chris Thornton2411a2c2017-03-22 13:52:50 -0700816 } catch (DeadObjectException e) {
Chris Thornton23a57402017-06-10 17:31:57 -0700817 forceStopAndUnloadModelLocked(modelData, e);
Chris Thornton2411a2c2017-03-22 13:52:50 -0700818 return;
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700819 } catch (RemoteException e) {
Arunesh Mishraf47f1732016-02-18 16:16:12 -0800820 Slog.w(TAG, "RemoteException in onKeyphraseDetected", e);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700821 }
822
Arunesh Mishrafac25152016-04-10 20:35:16 -0700823 RecognitionConfig config = modelData.getRecognitionConfig();
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800824 if (config != null) {
825 // Whether we should continue by starting this again.
Arunesh Mishrafac25152016-04-10 20:35:16 -0700826 modelData.setRequested(config.allowMultipleTriggers);
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800827 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700828 // TODO: Remove this block if the lower layer supports multiple triggers.
Chris Thornton37f97762016-04-29 16:23:45 -0700829 if (modelData.isRequested()) {
Arunesh Mishrafac25152016-04-10 20:35:16 -0700830 updateRecognitionLocked(modelData, isRecognitionAllowed(), true /* notify */);
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800831 }
832 }
833
834 private void updateAllRecognitionsLocked(boolean notify) {
835 boolean isAllowed = isRecognitionAllowed();
Chris Thornton23a57402017-06-10 17:31:57 -0700836 // updateRecognitionLocked can possibly update the list of models
837 ArrayList<ModelData> modelDatas = new ArrayList<ModelData>(mModelDataMap.values());
838 for (ModelData modelData : modelDatas) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800839 updateRecognitionLocked(modelData, isAllowed, notify);
840 }
841 }
842
843 private int updateRecognitionLocked(ModelData model, boolean isAllowed,
844 boolean notify) {
Chris Thornton37f97762016-04-29 16:23:45 -0700845 boolean start = model.isRequested() && isAllowed;
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800846 if (start == model.isModelStarted()) {
847 // No-op.
848 return STATUS_OK;
849 }
850 if (start) {
851 return startRecognitionLocked(model, notify);
852 } else {
853 return stopRecognitionLocked(model, notify);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700854 }
855 }
856
857 private void onServiceDiedLocked() {
858 try {
Chris Thornton2411a2c2017-03-22 13:52:50 -0700859 MetricsLogger.count(mContext, "sth_service_died", 1);
Chris Thornton23a57402017-06-10 17:31:57 -0700860 sendErrorCallbacksToAllLocked(SoundTrigger.STATUS_DEAD_OBJECT);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700861 } finally {
Arunesh Mishrafac25152016-04-10 20:35:16 -0700862 internalClearModelStateLocked();
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800863 internalClearGlobalStateLocked();
Eric Laurent2475e382014-09-09 15:52:25 -0700864 if (mModule != null) {
865 mModule.detach();
866 mModule = null;
867 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700868 }
869 }
870
Arunesh Mishrafac25152016-04-10 20:35:16 -0700871 // internalClearGlobalStateLocked() cleans up the telephony and power save listeners.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800872 private void internalClearGlobalStateLocked() {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700873 // Unregister from call state changes.
mike dooleycabbb112018-04-17 17:19:04 +0200874 long token = Binder.clearCallingIdentity();
875 try {
876 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
877 } finally {
878 Binder.restoreCallingIdentity(token);
879 }
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700880
881 // Unregister from power save mode changes.
882 if (mPowerSaveModeListener != null) {
883 mContext.unregisterReceiver(mPowerSaveModeListener);
884 mPowerSaveModeListener = null;
885 }
Eric Laurent2fedc552019-09-11 18:10:15 -0700886 mRecognitionRequested = false;
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700887 }
888
Arunesh Mishrafac25152016-04-10 20:35:16 -0700889 // Clears state for all models (generic and keyphrase).
890 private void internalClearModelStateLocked() {
891 for (ModelData modelData : mModelDataMap.values()) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800892 modelData.clearState();
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800893 }
894 }
895
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700896 class MyCallStateListener extends PhoneStateListener {
897 @Override
898 public void onCallStateChanged(int state, String arg1) {
899 if (DBG) Slog.d(TAG, "onCallStateChanged: " + state);
900 synchronized (mLock) {
ragof96269a2019-03-26 10:34:04 -0700901 onCallStateChangedLocked(TelephonyManager.CALL_STATE_OFFHOOK == state);
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700902 }
903 }
904 }
905
906 class PowerSaveModeListener extends BroadcastReceiver {
907 @Override
908 public void onReceive(Context context, Intent intent) {
909 if (!PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) {
910 return;
911 }
jackqdyulei455e90a2017-02-09 15:29:16 -0800912 boolean active = mPowerManager.getPowerSaveState(ServiceType.SOUND)
913 .batterySaverEnabled;
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700914 if (DBG) Slog.d(TAG, "onPowerSaveModeChanged: " + active);
915 synchronized (mLock) {
916 onPowerSaveModeChangedLocked(active);
917 }
918 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700919 }
Sandeep Siddhartha6b8556d2014-08-06 19:47:25 -0700920
921 void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
922 synchronized (mLock) {
923 pw.print(" module properties=");
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800924 pw.println(mModuleProperties == null ? "null" : mModuleProperties);
Sandeep Siddhartha6b8556d2014-08-06 19:47:25 -0700925
Sandeep Siddhartha6b8556d2014-08-06 19:47:25 -0700926 pw.print(" call active="); pw.println(mCallActive);
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700927 pw.print(" power save mode active="); pw.println(mIsPowerSaveMode);
Sandeep Siddhartha6b8556d2014-08-06 19:47:25 -0700928 pw.print(" service disabled="); pw.println(mServiceDisabled);
929 }
930 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800931
932 private void initializeTelephonyAndPowerStateListeners() {
Eric Laurent2fedc552019-09-11 18:10:15 -0700933 if (mRecognitionRequested) {
934 return;
935 }
Hall Liu5fb337f2017-11-22 17:38:15 -0800936 long token = Binder.clearCallingIdentity();
937 try {
938 // Get the current call state synchronously for the first recognition.
ragof96269a2019-03-26 10:34:04 -0700939 mCallActive = mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800940
Hall Liu5fb337f2017-11-22 17:38:15 -0800941 // Register for call state changes when the first call to start recognition occurs.
942 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800943
Hall Liu5fb337f2017-11-22 17:38:15 -0800944 // Register for power saver mode changes when the first call to start recognition
945 // occurs.
946 if (mPowerSaveModeListener == null) {
947 mPowerSaveModeListener = new PowerSaveModeListener();
948 mContext.registerReceiver(mPowerSaveModeListener,
949 new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
950 }
951 mIsPowerSaveMode = mPowerManager.getPowerSaveState(ServiceType.SOUND)
952 .batterySaverEnabled;
Eric Laurent2fedc552019-09-11 18:10:15 -0700953
954 mRecognitionRequested = true;
Hall Liu5fb337f2017-11-22 17:38:15 -0800955 } finally {
956 Binder.restoreCallingIdentity(token);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800957 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800958 }
959
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800960 // Sends an error callback to all models with a valid registered callback.
Chris Thornton23a57402017-06-10 17:31:57 -0700961 private void sendErrorCallbacksToAllLocked(int errorCode) {
Arunesh Mishrafac25152016-04-10 20:35:16 -0700962 for (ModelData modelData : mModelDataMap.values()) {
963 IRecognitionStatusCallback callback = modelData.getCallback();
964 if (callback != null) {
Chris Thornton2411a2c2017-03-22 13:52:50 -0700965 try {
966 callback.onError(errorCode);
967 } catch (RemoteException e) {
Chris Thornton23a57402017-06-10 17:31:57 -0700968 Slog.w(TAG, "RemoteException sendErrorCallbacksToAllLocked for model handle " +
Chris Thornton2411a2c2017-03-22 13:52:50 -0700969 modelData.getHandle(), e);
970 }
971 }
972 }
973 }
974
Chris Thorntona533b992017-03-24 17:30:29 -0700975 /**
976 * Stops and unloads a sound model, and removes any reference to the model if successful.
977 *
978 * @param modelData The model data to remove.
979 * @param exception Optional exception to print in logcat. May be null.
980 */
Chris Thornton23a57402017-06-10 17:31:57 -0700981 private void forceStopAndUnloadModelLocked(ModelData modelData, Exception exception) {
982 forceStopAndUnloadModelLocked(modelData, exception, null /* modelDataIterator */);
Chris Thorntona533b992017-03-24 17:30:29 -0700983 }
984
985 /**
986 * Stops and unloads a sound model, and removes any reference to the model if successful.
987 *
988 * @param modelData The model data to remove.
989 * @param exception Optional exception to print in logcat. May be null.
990 * @param modelDataIterator If this function is to be used while iterating over the
991 * mModelDataMap, you can provide the iterator for the current model data to be used to
992 * remove the modelData from the map. This avoids generating a
993 * ConcurrentModificationException, since this function will try and remove the model
994 * data from the mModelDataMap when it can successfully unload the model.
995 */
Chris Thornton23a57402017-06-10 17:31:57 -0700996 private void forceStopAndUnloadModelLocked(ModelData modelData, Exception exception,
Chris Thorntona533b992017-03-24 17:30:29 -0700997 Iterator modelDataIterator) {
Chris Thornton2411a2c2017-03-22 13:52:50 -0700998 if (exception != null) {
999 Slog.e(TAG, "forceStopAndUnloadModel", exception);
1000 }
Eric Laurent2fedc552019-09-11 18:10:15 -07001001 if (mModule == null) {
1002 return;
1003 }
Chris Thornton2411a2c2017-03-22 13:52:50 -07001004 if (modelData.isModelStarted()) {
1005 Slog.d(TAG, "Stopping previously started dangling model " + modelData.getHandle());
1006 if (mModule.stopRecognition(modelData.getHandle()) != STATUS_OK) {
1007 modelData.setStopped();
1008 modelData.setRequested(false);
1009 } else {
1010 Slog.e(TAG, "Failed to stop model " + modelData.getHandle());
1011 }
1012 }
1013 if (modelData.isModelLoaded()) {
1014 Slog.d(TAG, "Unloading previously loaded dangling model " + modelData.getHandle());
1015 if (mModule.unloadSoundModel(modelData.getHandle()) == STATUS_OK) {
1016 // Remove the model data from existence.
Chris Thorntona533b992017-03-24 17:30:29 -07001017 if (modelDataIterator != null) {
1018 modelDataIterator.remove();
1019 } else {
1020 mModelDataMap.remove(modelData.getModelId());
1021 }
Chris Thornton2411a2c2017-03-22 13:52:50 -07001022 Iterator it = mKeyphraseUuidMap.entrySet().iterator();
1023 while (it.hasNext()) {
1024 Map.Entry pair = (Map.Entry) it.next();
1025 if (pair.getValue().equals(modelData.getModelId())) {
1026 it.remove();
1027 }
1028 }
1029 modelData.clearState();
1030 } else {
1031 Slog.e(TAG, "Failed to unload model " + modelData.getHandle());
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001032 }
1033 }
1034 }
1035
Chris Thorntona533b992017-03-24 17:30:29 -07001036 private void stopAndUnloadDeadModelsLocked() {
1037 Iterator it = mModelDataMap.entrySet().iterator();
1038 while (it.hasNext()) {
1039 ModelData modelData = (ModelData) ((Map.Entry) it.next()).getValue();
1040 if (!modelData.isModelLoaded()) {
1041 continue;
1042 }
1043 if (modelData.getCallback() == null
1044 || (modelData.getCallback().asBinder() != null
1045 && !modelData.getCallback().asBinder().pingBinder())) {
1046 // No one is listening on this model, so we might as well evict it.
1047 Slog.w(TAG, "Removing model " + modelData.getHandle() + " that has no clients");
Chris Thornton23a57402017-06-10 17:31:57 -07001048 forceStopAndUnloadModelLocked(modelData, null /* exception */, it);
Chris Thorntona533b992017-03-24 17:30:29 -07001049 }
1050 }
1051 }
1052
Ryan Bavetta79655eb2016-03-07 14:34:51 -08001053 private ModelData getOrCreateGenericModelDataLocked(UUID modelId) {
Arunesh Mishrafac25152016-04-10 20:35:16 -07001054 ModelData modelData = mModelDataMap.get(modelId);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001055 if (modelData == null) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001056 modelData = ModelData.createGenericModelData(modelId);
Arunesh Mishrafac25152016-04-10 20:35:16 -07001057 mModelDataMap.put(modelId, modelData);
1058 } else if (!modelData.isGenericModel()) {
1059 Slog.e(TAG, "UUID already used for non-generic model.");
1060 return null;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001061 }
1062 return modelData;
1063 }
1064
Arunesh Mishrafac25152016-04-10 20:35:16 -07001065 private void removeKeyphraseModelLocked(int keyphraseId) {
1066 UUID uuid = mKeyphraseUuidMap.get(keyphraseId);
1067 if (uuid == null) {
1068 return;
1069 }
1070 mModelDataMap.remove(uuid);
1071 mKeyphraseUuidMap.remove(keyphraseId);
1072 }
1073
1074 private ModelData getKeyphraseModelDataLocked(int keyphraseId) {
1075 UUID uuid = mKeyphraseUuidMap.get(keyphraseId);
1076 if (uuid == null) {
1077 return null;
1078 }
1079 return mModelDataMap.get(uuid);
1080 }
1081
1082 // Use this to create a new ModelData entry for a keyphrase Id. It will overwrite existing
1083 // mapping if one exists.
1084 private ModelData createKeyphraseModelDataLocked(UUID modelId, int keyphraseId) {
1085 mKeyphraseUuidMap.remove(keyphraseId);
1086 mModelDataMap.remove(modelId);
1087 mKeyphraseUuidMap.put(keyphraseId, modelId);
1088 ModelData modelData = ModelData.createKeyphraseModelData(modelId);
1089 mModelDataMap.put(modelId, modelData);
1090 return modelData;
1091 }
1092
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001093 // Instead of maintaining a second hashmap of modelHandle -> ModelData, we just
1094 // iterate through to find the right object (since we don't expect 100s of models
1095 // to be stored).
Ryan Bavetta79655eb2016-03-07 14:34:51 -08001096 private ModelData getModelDataForLocked(int modelHandle) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001097 // Fetch ModelData object corresponding to the model handle.
Arunesh Mishrafac25152016-04-10 20:35:16 -07001098 for (ModelData model : mModelDataMap.values()) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001099 if (model.getHandle() == modelHandle) {
1100 return model;
1101 }
1102 }
1103 return null;
1104 }
1105
1106 // Whether we are allowed to run any recognition at all. The conditions that let us run
1107 // a recognition include: no active phone call or not being in a power save mode. Also,
1108 // the native service should be enabled.
1109 private boolean isRecognitionAllowed() {
Eric Laurent2fedc552019-09-11 18:10:15 -07001110 // if mRecognitionRequested is false, call and power state listeners are not registered so
1111 // we read current state directly from services
1112 if (!mRecognitionRequested) {
1113 mCallActive = mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK;
1114 mIsPowerSaveMode =
1115 mPowerManager.getPowerSaveState(ServiceType.SOUND).batterySaverEnabled;
1116 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001117 return !mCallActive && !mServiceDisabled && !mIsPowerSaveMode;
1118 }
1119
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001120 // A single routine that implements the start recognition logic for both generic and keyphrase
1121 // models.
1122 private int startRecognitionLocked(ModelData modelData, boolean notify) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001123 IRecognitionStatusCallback callback = modelData.getCallback();
1124 int handle = modelData.getHandle();
1125 RecognitionConfig config = modelData.getRecognitionConfig();
1126 if (callback == null || handle == INVALID_VALUE || config == null) {
1127 // Nothing to do here.
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001128 Slog.w(TAG, "startRecognition: Bad data passed in.");
1129 MetricsLogger.count(mContext, "sth_start_recognition_error", 1);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001130 return STATUS_ERROR;
1131 }
1132
1133 if (!isRecognitionAllowed()) {
1134 // Nothing to do here.
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001135 Slog.w(TAG, "startRecognition requested but not allowed.");
1136 MetricsLogger.count(mContext, "sth_start_recognition_not_allowed", 1);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001137 return STATUS_OK;
1138 }
1139
Eric Laurent2fedc552019-09-11 18:10:15 -07001140 if (mModule == null) {
1141 return STATUS_ERROR;
1142 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001143 int status = mModule.startRecognition(handle, config);
1144 if (status != SoundTrigger.STATUS_OK) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001145 Slog.w(TAG, "startRecognition failed with " + status);
1146 MetricsLogger.count(mContext, "sth_start_recognition_error", 1);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001147 // Notify of error if needed.
1148 if (notify) {
1149 try {
1150 callback.onError(status);
Chris Thornton2411a2c2017-03-22 13:52:50 -07001151 } catch (DeadObjectException e) {
Chris Thornton23a57402017-06-10 17:31:57 -07001152 forceStopAndUnloadModelLocked(modelData, e);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001153 } catch (RemoteException e) {
1154 Slog.w(TAG, "RemoteException in onError", e);
1155 }
1156 }
1157 } else {
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001158 Slog.i(TAG, "startRecognition successful.");
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001159 MetricsLogger.count(mContext, "sth_start_recognition_success", 1);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001160 modelData.setStarted();
1161 // Notify of resume if needed.
1162 if (notify) {
1163 try {
1164 callback.onRecognitionResumed();
Chris Thornton2411a2c2017-03-22 13:52:50 -07001165 } catch (DeadObjectException e) {
Chris Thornton23a57402017-06-10 17:31:57 -07001166 forceStopAndUnloadModelLocked(modelData, e);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001167 } catch (RemoteException e) {
1168 Slog.w(TAG, "RemoteException in onRecognitionResumed", e);
1169 }
1170 }
1171 }
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001172 if (DBG) {
1173 Slog.d(TAG, "Model being started :" + modelData.toString());
1174 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001175 return status;
1176 }
1177
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001178 private int stopRecognitionLocked(ModelData modelData, boolean notify) {
Eric Laurent2fedc552019-09-11 18:10:15 -07001179 if (mModule == null) {
1180 return STATUS_ERROR;
1181 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001182
Eric Laurent2fedc552019-09-11 18:10:15 -07001183 IRecognitionStatusCallback callback = modelData.getCallback();
Chris Thornton37f97762016-04-29 16:23:45 -07001184 // Stop recognition.
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001185 int status = STATUS_OK;
1186
Chris Thornton37f97762016-04-29 16:23:45 -07001187 status = mModule.stopRecognition(modelData.getHandle());
1188
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001189 if (status != SoundTrigger.STATUS_OK) {
1190 Slog.w(TAG, "stopRecognition call failed with " + status);
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001191 MetricsLogger.count(mContext, "sth_stop_recognition_error", 1);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001192 if (notify) {
1193 try {
1194 callback.onError(status);
Chris Thornton2411a2c2017-03-22 13:52:50 -07001195 } catch (DeadObjectException e) {
Chris Thornton23a57402017-06-10 17:31:57 -07001196 forceStopAndUnloadModelLocked(modelData, e);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001197 } catch (RemoteException e) {
1198 Slog.w(TAG, "RemoteException in onError", e);
1199 }
1200 }
1201 } else {
1202 modelData.setStopped();
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001203 MetricsLogger.count(mContext, "sth_stop_recognition_success", 1);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001204 // Notify of pause if needed.
1205 if (notify) {
1206 try {
1207 callback.onRecognitionPaused();
Chris Thornton2411a2c2017-03-22 13:52:50 -07001208 } catch (DeadObjectException e) {
Chris Thornton23a57402017-06-10 17:31:57 -07001209 forceStopAndUnloadModelLocked(modelData, e);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001210 } catch (RemoteException e) {
1211 Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
1212 }
1213 }
1214 }
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001215 if (DBG) {
1216 Slog.d(TAG, "Model being stopped :" + modelData.toString());
1217 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001218 return status;
1219 }
1220
Arunesh Mishrafac25152016-04-10 20:35:16 -07001221 private void dumpModelStateLocked() {
1222 for (UUID modelId : mModelDataMap.keySet()) {
1223 ModelData modelData = mModelDataMap.get(modelId);
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001224 Slog.i(TAG, "Model :" + modelData.toString());
1225 }
1226 }
1227
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001228 // Computes whether we have any recognition running at all (voice or generic). Sets
Eric Laurent80565fb2019-07-18 08:22:38 -07001229 // the mRecognitionRequested variable with the result.
1230 private boolean computeRecognitionRequestedLocked() {
Ryan Bavetta79655eb2016-03-07 14:34:51 -08001231 if (mModuleProperties == null || mModule == null) {
Eric Laurent80565fb2019-07-18 08:22:38 -07001232 mRecognitionRequested = false;
1233 return mRecognitionRequested;
Ryan Bavetta79655eb2016-03-07 14:34:51 -08001234 }
Arunesh Mishrafac25152016-04-10 20:35:16 -07001235 for (ModelData modelData : mModelDataMap.values()) {
Eric Laurent80565fb2019-07-18 08:22:38 -07001236 if (modelData.isRequested()) {
1237 mRecognitionRequested = true;
1238 return mRecognitionRequested;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001239 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001240 }
Eric Laurent80565fb2019-07-18 08:22:38 -07001241 mRecognitionRequested = false;
1242 return mRecognitionRequested;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001243 }
1244
1245 // This class encapsulates the callbacks, state, handles and any other information that
1246 // represents a model.
1247 private static class ModelData {
1248 // Model not loaded (and hence not started).
1249 static final int MODEL_NOTLOADED = 0;
1250
1251 // Loaded implies model was successfully loaded. Model not started yet.
1252 static final int MODEL_LOADED = 1;
1253
1254 // Started implies model was successfully loaded and start was called.
1255 static final int MODEL_STARTED = 2;
1256
1257 // One of MODEL_NOTLOADED, MODEL_LOADED, MODEL_STARTED (which implies loaded).
1258 private int mModelState;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001259 private UUID mModelId;
1260
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001261 // mRequested captures the explicit intent that a start was requested for this model. We
1262 // continue to capture and retain this state even after the model gets started, so that we
1263 // know when a model gets stopped due to "other" reasons, that we should start it again.
1264 // This was the intended behavior of the "mRequested" variable in the previous version of
1265 // this code that we are replicating here.
1266 //
1267 // The "other" reasons include power save, abort being called from the lower layer (due
1268 // to concurrent capture not being supported) and phone call state. Once we recover from
1269 // these transient disruptions, we would start such models again where mRequested == true.
1270 // Thus, mRequested gets reset only when there is an explicit intent to stop the model
1271 // coming from the SoundTriggerService layer that uses this class (and thus eventually
1272 // from the app that manages this model).
1273 private boolean mRequested = false;
1274
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001275 // One of SoundModel.TYPE_GENERIC or SoundModel.TYPE_KEYPHRASE. Initially set
1276 // to SoundModel.TYPE_UNKNOWN;
1277 private int mModelType = SoundModel.TYPE_UNKNOWN;
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001278
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001279 private IRecognitionStatusCallback mCallback = null;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001280 private RecognitionConfig mRecognitionConfig = null;
1281
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001282 // Model handle is an integer used by the HAL as an identifier for sound
1283 // models.
1284 private int mModelHandle = INVALID_VALUE;
1285
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001286 // The SoundModel instance, one of KeyphraseSoundModel or GenericSoundModel.
1287 private SoundModel mSoundModel = null;
1288
1289 private ModelData(UUID modelId, int modelType) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001290 mModelId = modelId;
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001291 // Private constructor, since we require modelType to be one of TYPE_GENERIC,
1292 // TYPE_KEYPHRASE or TYPE_UNKNOWN.
1293 mModelType = modelType;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001294 }
1295
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001296 static ModelData createKeyphraseModelData(UUID modelId) {
1297 return new ModelData(modelId, SoundModel.TYPE_KEYPHRASE);
1298 }
1299
1300 static ModelData createGenericModelData(UUID modelId) {
1301 return new ModelData(modelId, SoundModel.TYPE_GENERIC_SOUND);
1302 }
1303
1304 // Note that most of the functionality in this Java class will not work for
1305 // SoundModel.TYPE_UNKNOWN nevertheless we have it since lower layers support it.
1306 static ModelData createModelDataOfUnknownType(UUID modelId) {
1307 return new ModelData(modelId, SoundModel.TYPE_UNKNOWN);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001308 }
1309
1310 synchronized void setCallback(IRecognitionStatusCallback callback) {
1311 mCallback = callback;
1312 }
1313
1314 synchronized IRecognitionStatusCallback getCallback() {
1315 return mCallback;
1316 }
1317
1318 synchronized boolean isModelLoaded() {
Arunesh Mishra933da812016-02-18 15:31:54 -08001319 return (mModelState == MODEL_LOADED || mModelState == MODEL_STARTED);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001320 }
1321
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001322 synchronized boolean isModelNotLoaded() {
1323 return mModelState == MODEL_NOTLOADED;
1324 }
1325
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001326 synchronized void setStarted() {
1327 mModelState = MODEL_STARTED;
1328 }
1329
1330 synchronized void setStopped() {
1331 mModelState = MODEL_LOADED;
1332 }
1333
Arunesh Mishra933da812016-02-18 15:31:54 -08001334 synchronized void setLoaded() {
1335 mModelState = MODEL_LOADED;
1336 }
1337
1338 synchronized boolean isModelStarted() {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001339 return mModelState == MODEL_STARTED;
1340 }
1341
1342 synchronized void clearState() {
1343 mModelState = MODEL_NOTLOADED;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001344 mModelHandle = INVALID_VALUE;
Chris Thornton41e04a42016-04-18 16:51:08 -07001345 mRecognitionConfig = null;
1346 mRequested = false;
1347 mCallback = null;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001348 }
1349
1350 synchronized void clearCallback() {
1351 mCallback = null;
1352 }
1353
1354 synchronized void setHandle(int handle) {
1355 mModelHandle = handle;
1356 }
1357
1358 synchronized void setRecognitionConfig(RecognitionConfig config) {
1359 mRecognitionConfig = config;
1360 }
1361
1362 synchronized int getHandle() {
1363 return mModelHandle;
1364 }
1365
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001366 synchronized UUID getModelId() {
1367 return mModelId;
1368 }
1369
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001370 synchronized RecognitionConfig getRecognitionConfig() {
1371 return mRecognitionConfig;
1372 }
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001373
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001374 // Whether a start recognition was requested.
Chris Thornton37f97762016-04-29 16:23:45 -07001375 synchronized boolean isRequested() {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001376 return mRequested;
1377 }
1378
1379 synchronized void setRequested(boolean requested) {
1380 mRequested = requested;
1381 }
1382
1383 synchronized void setSoundModel(SoundModel soundModel) {
1384 mSoundModel = soundModel;
1385 }
1386
1387 synchronized SoundModel getSoundModel() {
1388 return mSoundModel;
1389 }
1390
1391 synchronized int getModelType() {
1392 return mModelType;
1393 }
1394
1395 synchronized boolean isKeyphraseModel() {
1396 return mModelType == SoundModel.TYPE_KEYPHRASE;
1397 }
1398
Arunesh Mishrafac25152016-04-10 20:35:16 -07001399 synchronized boolean isGenericModel() {
1400 return mModelType == SoundModel.TYPE_GENERIC_SOUND;
1401 }
1402
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001403 synchronized String stateToString() {
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001404 switch(mModelState) {
1405 case MODEL_NOTLOADED: return "NOT_LOADED";
1406 case MODEL_LOADED: return "LOADED";
1407 case MODEL_STARTED: return "STARTED";
1408 }
1409 return "Unknown state";
1410 }
1411
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001412 synchronized String requestedToString() {
1413 return "Requested: " + (mRequested ? "Yes" : "No");
1414 }
1415
1416 synchronized String callbackToString() {
1417 return "Callback: " + (mCallback != null ? mCallback.asBinder() : "null");
1418 }
1419
1420 synchronized String uuidToString() {
1421 return "UUID: " + mModelId;
1422 }
1423
1424 synchronized public String toString() {
1425 return "Handle: " + mModelHandle + "\n" +
1426 "ModelState: " + stateToString() + "\n" +
1427 requestedToString() + "\n" +
1428 callbackToString() + "\n" +
Arunesh Mishrafac25152016-04-10 20:35:16 -07001429 uuidToString() + "\n" + modelTypeToString();
1430 }
1431
1432 synchronized String modelTypeToString() {
1433 String type = null;
1434 switch (mModelType) {
1435 case SoundModel.TYPE_GENERIC_SOUND: type = "Generic"; break;
1436 case SoundModel.TYPE_UNKNOWN: type = "Unknown"; break;
1437 case SoundModel.TYPE_KEYPHRASE: type = "Keyphrase"; break;
1438 }
1439 return "Model type: " + type + "\n";
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001440 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001441 }
Sandeepd7018202014-07-10 15:15:39 -07001442}