blob: 99337565e12858d13060aae6e6817dbd0cb4f643 [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.
328 if (status == STATUS_OK && !mRecognitionRequested) {
329 initializeTelephonyAndPowerStateListeners();
330 mRecognitionRequested = true;
331 }
332
333 return status;
Sandeepd7018202014-07-10 15:15:39 -0700334 }
335 }
336
337 /**
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800338 * Stops recognition for the given generic sound model. This is a wrapper for {@link
339 * #stopRecognition}.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800340 *
341 * @param modelId The identifier of the generic sound model for which
342 * the recognition is to be stopped.
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800343 * @param callback The callback for the recognition events related to the given sound model.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800344 *
345 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
346 */
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800347 int stopGenericRecognition(UUID modelId, IRecognitionStatusCallback callback) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800348 synchronized (mLock) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800349 MetricsLogger.count(mContext, "sth_stop_recognition", 1);
350 if (callback == null || modelId == null) {
351 Slog.e(TAG, "Null callbackreceived for stopGenericRecognition() for modelid:" +
352 modelId);
353 return STATUS_ERROR;
354 }
355
Arunesh Mishrafac25152016-04-10 20:35:16 -0700356 ModelData modelData = mModelDataMap.get(modelId);
357 if (modelData == null || !modelData.isGenericModel()) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800358 Slog.w(TAG, "Attempting stopRecognition on invalid model with id:" + modelId);
359 return STATUS_ERROR;
360 }
361
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800362 int status = stopRecognition(modelData, callback);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800363 if (status != SoundTrigger.STATUS_OK) {
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800364 Slog.w(TAG, "stopGenericRecognition failed: " + status);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800365 }
366 return status;
367 }
368 }
369
370 /**
Sandeep Siddhartha05589722014-07-17 16:21:54 -0700371 * Stops recognition for the given {@link Keyphrase} if a recognition is
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800372 * currently active. This is a wrapper for {@link #stopRecognition()}.
Sandeepd7018202014-07-10 15:15:39 -0700373 *
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -0700374 * @param keyphraseId The identifier of the keyphrase for which
375 * the recognition is to be stopped.
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800376 * @param callback The callback for the recognition events related to the given keyphrase.
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -0700377 *
Sandeepd7018202014-07-10 15:15:39 -0700378 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
379 */
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800380 int stopKeyphraseRecognition(int keyphraseId, IRecognitionStatusCallback callback) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700381 synchronized (mLock) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800382 MetricsLogger.count(mContext, "sth_stop_recognition", 1);
383 if (callback == null) {
384 Slog.e(TAG, "Null callback received for stopKeyphraseRecognition() for keyphraseId:" +
385 keyphraseId);
386 return STATUS_ERROR;
387 }
388
Arunesh Mishrafac25152016-04-10 20:35:16 -0700389 ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
390 if (modelData == null || !modelData.isKeyphraseModel()) {
Chris Thornton2411a2c2017-03-22 13:52:50 -0700391 Slog.e(TAG, "No model exists for given keyphrase Id " + keyphraseId);
Arunesh Mishrafac25152016-04-10 20:35:16 -0700392 return STATUS_ERROR;
393 }
394
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700395 if (DBG) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800396 Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId + ", callback =" +
397 callback.asBinder());
Arunesh Mishrafac25152016-04-10 20:35:16 -0700398 Slog.d(TAG, "current callback=" + (modelData == null ? "null" :
399 modelData.getCallback().asBinder()));
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700400 }
Arunesh Mishrafac25152016-04-10 20:35:16 -0700401 int status = stopRecognition(modelData, callback);
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -0700402 if (status != SoundTrigger.STATUS_OK) {
Sandeep Siddhartha2c0273e2014-08-01 11:32:03 -0700403 return status;
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -0700404 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700405
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700406 return status;
Sandeepd7018202014-07-10 15:15:39 -0700407 }
408 }
409
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700410 /**
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800411 * Stops recognition for the given ModelData instance.
412 *
413 * @param modelData Instance of {@link #ModelData} sound model.
414 * @param callback The callback for the recognition events related to the given keyphrase.
415 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
416 */
417 private int stopRecognition(ModelData modelData, IRecognitionStatusCallback callback) {
418 synchronized (mLock) {
419 if (callback == null) {
420 return STATUS_ERROR;
421 }
422 if (mModuleProperties == null || mModule == null) {
423 Slog.w(TAG, "Attempting stopRecognition without the capability");
424 return STATUS_ERROR;
425 }
426
427 IRecognitionStatusCallback currentCallback = modelData.getCallback();
Chris Thornton37f97762016-04-29 16:23:45 -0700428 if (modelData == null || currentCallback == null ||
429 (!modelData.isRequested() && !modelData.isModelStarted())) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800430 // startGenericRecognition hasn't been called or it failed.
Chris Thornton37f97762016-04-29 16:23:45 -0700431 Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition");
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800432 return STATUS_ERROR;
433 }
434
435 if (currentCallback.asBinder() != callback.asBinder()) {
436 // We don't allow a different listener to stop the recognition than the one
437 // that started it.
438 Slog.w(TAG, "Attempting stopRecognition for another recognition");
439 return STATUS_ERROR;
440 }
441
442 // Request stop recognition via the update() method.
443 modelData.setRequested(false);
444 int status = updateRecognitionLocked(modelData, isRecognitionAllowed(),
445 false /* don't notify for synchronous calls */);
446 if (status != SoundTrigger.STATUS_OK) {
447 return status;
448 }
449
450 // We leave the sound model loaded but not started, this helps us when we start back.
451 // Also clear the internal state once the recognition has been stopped.
452 modelData.setLoaded();
453 modelData.clearCallback();
454 modelData.setRecognitionConfig(null);
455
Eric Laurent80565fb2019-07-18 08:22:38 -0700456 if (!computeRecognitionRequestedLocked()) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800457 internalClearGlobalStateLocked();
458 }
459
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800460 return status;
461 }
462 }
463
464 // Stop a previously started model if it was started. Optionally, unload if the previous model
465 // is stale and is about to be replaced.
466 // Needs to be called with the mLock held.
467 private int tryStopAndUnloadLocked(ModelData modelData, boolean stopModel,
468 boolean unloadModel) {
469 int status = STATUS_OK;
470 if (modelData.isModelNotLoaded()) {
471 return status;
472 }
473 if (stopModel && modelData.isModelStarted()) {
474 status = stopRecognitionLocked(modelData,
475 false /* don't notify for synchronous calls */);
476 if (status != SoundTrigger.STATUS_OK) {
477 Slog.w(TAG, "stopRecognition failed: " + status);
478 return status;
479 }
480 }
481
482 if (unloadModel && modelData.isModelLoaded()) {
483 Slog.d(TAG, "Unloading previously loaded stale model.");
484 status = mModule.unloadSoundModel(modelData.getHandle());
485 MetricsLogger.count(mContext, "sth_unloading_stale_model", 1);
486 if (status != SoundTrigger.STATUS_OK) {
487 Slog.w(TAG, "unloadSoundModel call failed with " + status);
488 } else {
489 // Clear the ModelData state if successful.
490 modelData.clearState();
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800491 }
492 }
493 return status;
494 }
495
Arunesh Mishra55a9b002016-02-01 14:06:37 -0800496 public ModuleProperties getModuleProperties() {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800497 return mModuleProperties;
Arunesh Mishra55a9b002016-02-01 14:06:37 -0800498 }
499
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800500 int unloadKeyphraseSoundModel(int keyphraseId) {
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800501 synchronized (mLock) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800502 MetricsLogger.count(mContext, "sth_unload_keyphrase_sound_model", 1);
Arunesh Mishrafac25152016-04-10 20:35:16 -0700503 ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
504 if (mModule == null || modelData == null || modelData.getHandle() == INVALID_VALUE ||
505 !modelData.isKeyphraseModel()) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800506 return STATUS_ERROR;
507 }
508
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800509 // Stop recognition if it's the current one.
Arunesh Mishrafac25152016-04-10 20:35:16 -0700510 modelData.setRequested(false);
511 int status = updateRecognitionLocked(modelData, isRecognitionAllowed(),
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800512 false /* don't notify */);
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800513 if (status != SoundTrigger.STATUS_OK) {
514 Slog.w(TAG, "Stop recognition failed for keyphrase ID:" + status);
515 }
516
Arunesh Mishrafac25152016-04-10 20:35:16 -0700517 status = mModule.unloadSoundModel(modelData.getHandle());
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800518 if (status != SoundTrigger.STATUS_OK) {
519 Slog.w(TAG, "unloadKeyphraseSoundModel call failed with " + status);
520 }
Arunesh Mishrafac25152016-04-10 20:35:16 -0700521
522 // Remove it from existence.
523 removeKeyphraseModelLocked(keyphraseId);
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800524 return status;
525 }
526 }
527
528 int unloadGenericSoundModel(UUID modelId) {
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800529 synchronized (mLock) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800530 MetricsLogger.count(mContext, "sth_unload_generic_sound_model", 1);
531 if (modelId == null || mModule == null) {
532 return STATUS_ERROR;
533 }
Arunesh Mishrafac25152016-04-10 20:35:16 -0700534 ModelData modelData = mModelDataMap.get(modelId);
535 if (modelData == null || !modelData.isGenericModel()) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800536 Slog.w(TAG, "Unload error: Attempting unload invalid generic model with id:" +
537 modelId);
Ryan Bavetta79655eb2016-03-07 14:34:51 -0800538 return STATUS_ERROR;
539 }
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800540 if (!modelData.isModelLoaded()) {
541 // Nothing to do here.
542 Slog.i(TAG, "Unload: Given generic model is not loaded:" + modelId);
543 return STATUS_OK;
544 }
545 if (modelData.isModelStarted()) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800546 int status = stopRecognitionLocked(modelData,
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800547 false /* don't notify for synchronous calls */);
548 if (status != SoundTrigger.STATUS_OK) {
549 Slog.w(TAG, "stopGenericRecognition failed: " + status);
550 }
551 }
552
553 int status = mModule.unloadSoundModel(modelData.getHandle());
554 if (status != SoundTrigger.STATUS_OK) {
555 Slog.w(TAG, "unloadGenericSoundModel() call failed with " + status);
556 Slog.w(TAG, "unloadGenericSoundModel() force-marking model as unloaded.");
557 }
Arunesh Mishrafac25152016-04-10 20:35:16 -0700558
559 // Remove it from existence.
560 mModelDataMap.remove(modelId);
561 if (DBG) dumpModelStateLocked();
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800562 return status;
563 }
564 }
565
Chris Thorntonba08b792017-06-08 22:34:37 -0700566 boolean isRecognitionRequested(UUID modelId) {
567 synchronized (mLock) {
568 ModelData modelData = mModelDataMap.get(modelId);
569 return modelData != null && modelData.isRequested();
570 }
571 }
572
mike dooleyb2ab04a2018-11-07 15:48:54 +0100573 int getGenericModelState(UUID modelId) {
Michael Dooley291751e2018-10-16 19:53:29 +0000574 synchronized (mLock) {
575 MetricsLogger.count(mContext, "sth_get_generic_model_state", 1);
576 if (modelId == null || mModule == null) {
mike dooleyb2ab04a2018-11-07 15:48:54 +0100577 return STATUS_ERROR;
Michael Dooley291751e2018-10-16 19:53:29 +0000578 }
579 ModelData modelData = mModelDataMap.get(modelId);
580 if (modelData == null || !modelData.isGenericModel()) {
581 Slog.w(TAG, "GetGenericModelState error: Invalid generic model id:" +
582 modelId);
mike dooleyb2ab04a2018-11-07 15:48:54 +0100583 return STATUS_ERROR;
Michael Dooley291751e2018-10-16 19:53:29 +0000584 }
585 if (!modelData.isModelLoaded()) {
586 Slog.i(TAG, "GetGenericModelState: Given generic model is not loaded:" + modelId);
mike dooleyb2ab04a2018-11-07 15:48:54 +0100587 return STATUS_ERROR;
Michael Dooley291751e2018-10-16 19:53:29 +0000588 }
589 if (!modelData.isModelStarted()) {
590 Slog.i(TAG, "GetGenericModelState: Given generic model is not started:" + modelId);
mike dooleyb2ab04a2018-11-07 15:48:54 +0100591 return STATUS_ERROR;
Michael Dooley291751e2018-10-16 19:53:29 +0000592 }
593
mike dooleyb2ab04a2018-11-07 15:48:54 +0100594 return mModule.getModelState(modelData.getHandle());
Michael Dooley291751e2018-10-16 19:53:29 +0000595 }
596 }
597
mike dooleyb2ab04a2018-11-07 15:48:54 +0100598 int getKeyphraseModelState(UUID modelId) {
Michael Dooley291751e2018-10-16 19:53:29 +0000599 Slog.w(TAG, "GetKeyphraseModelState error: Not implemented");
mike dooleyb2ab04a2018-11-07 15:48:54 +0100600 return STATUS_ERROR;
Michael Dooley291751e2018-10-16 19:53:29 +0000601 }
602
Sandeepd7018202014-07-10 15:15:39 -0700603 //---- SoundTrigger.StatusListener methods
604 @Override
605 public void onRecognition(RecognitionEvent event) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800606 if (event == null) {
607 Slog.w(TAG, "Null recognition event!");
608 return;
609 }
610
611 if (!(event instanceof KeyphraseRecognitionEvent) &&
612 !(event instanceof GenericRecognitionEvent)) {
Chris Thornton37f97762016-04-29 16:23:45 -0700613 Slog.w(TAG, "Invalid recognition event type (not one of generic or keyphrase)!");
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -0700614 return;
615 }
Sandeepd7018202014-07-10 15:15:39 -0700616
Sandeep Siddhartha68173372014-07-28 13:25:30 -0700617 if (DBG) Slog.d(TAG, "onRecognition: " + event);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700618 synchronized (mLock) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700619 switch (event.status) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700620 case SoundTrigger.RECOGNITION_STATUS_ABORT:
Chris Thornton37f97762016-04-29 16:23:45 -0700621 onRecognitionAbortLocked(event);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700622 break;
623 case SoundTrigger.RECOGNITION_STATUS_FAILURE:
Chris Thornton37f97762016-04-29 16:23:45 -0700624 // Fire failures to all listeners since it's not tied to a keyphrase.
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700625 onRecognitionFailureLocked();
626 break;
627 case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
mike dooley23c62562018-12-20 16:05:24 +0100628 case SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE:
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800629 if (isKeyphraseRecognitionEvent(event)) {
630 onKeyphraseRecognitionSuccessLocked((KeyphraseRecognitionEvent) event);
631 } else {
632 onGenericRecognitionSuccessLocked((GenericRecognitionEvent) event);
633 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700634 break;
635 }
Sandeepd7018202014-07-10 15:15:39 -0700636 }
637 }
638
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800639 private boolean isKeyphraseRecognitionEvent(RecognitionEvent event) {
Arunesh Mishraf47f1732016-02-18 16:16:12 -0800640 return event instanceof KeyphraseRecognitionEvent;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800641 }
642
643 private void onGenericRecognitionSuccessLocked(GenericRecognitionEvent event) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800644 MetricsLogger.count(mContext, "sth_generic_recognition_event", 1);
mike dooley23c62562018-12-20 16:05:24 +0100645 if (event.status != SoundTrigger.RECOGNITION_STATUS_SUCCESS
646 && event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800647 return;
648 }
Ryan Bavetta79655eb2016-03-07 14:34:51 -0800649 ModelData model = getModelDataForLocked(event.soundModelHandle);
Arunesh Mishrafac25152016-04-10 20:35:16 -0700650 if (model == null || !model.isGenericModel()) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800651 Slog.w(TAG, "Generic recognition event: Model does not exist for handle: " +
652 event.soundModelHandle);
653 return;
654 }
655
656 IRecognitionStatusCallback callback = model.getCallback();
657 if (callback == null) {
658 Slog.w(TAG, "Generic recognition event: Null callback for model handle: " +
659 event.soundModelHandle);
660 return;
661 }
662
mike dooleyda480922019-01-04 09:39:47 +0100663 model.setStopped();
664
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800665 try {
Arunesh Mishraf47f1732016-02-18 16:16:12 -0800666 callback.onGenericSoundTriggerDetected((GenericRecognitionEvent) event);
Chris Thornton2411a2c2017-03-22 13:52:50 -0700667 } catch (DeadObjectException e) {
Chris Thornton23a57402017-06-10 17:31:57 -0700668 forceStopAndUnloadModelLocked(model, e);
Chris Thornton2411a2c2017-03-22 13:52:50 -0700669 return;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800670 } catch (RemoteException e) {
Arunesh Mishraf47f1732016-02-18 16:16:12 -0800671 Slog.w(TAG, "RemoteException in onGenericSoundTriggerDetected", e);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800672 }
673
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800674 RecognitionConfig config = model.getRecognitionConfig();
675 if (config == null) {
676 Slog.w(TAG, "Generic recognition event: Null RecognitionConfig for model handle: " +
677 event.soundModelHandle);
678 return;
679 }
680
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800681 model.setRequested(config.allowMultipleTriggers);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800682 // TODO: Remove this block if the lower layer supports multiple triggers.
Chris Thornton37f97762016-04-29 16:23:45 -0700683 if (model.isRequested()) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800684 updateRecognitionLocked(model, isRecognitionAllowed() /* isAllowed */,
685 true /* notify */);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800686 }
687 }
688
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700689 @Override
Eric Laurentd3b82232014-07-30 08:57:39 -0700690 public void onSoundModelUpdate(SoundModelEvent event) {
691 if (event == null) {
692 Slog.w(TAG, "Invalid sound model event!");
693 return;
694 }
Eric Laurentd3b82232014-07-30 08:57:39 -0700695 if (DBG) Slog.d(TAG, "onSoundModelUpdate: " + event);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700696 synchronized (mLock) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800697 MetricsLogger.count(mContext, "sth_sound_model_updated", 1);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700698 onSoundModelUpdatedLocked(event);
699 }
Eric Laurentd3b82232014-07-30 08:57:39 -0700700 }
701
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700702 @Override
Eric Laurentd3b82232014-07-30 08:57:39 -0700703 public void onServiceStateChange(int state) {
704 if (DBG) Slog.d(TAG, "onServiceStateChange, state: " + state);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700705 synchronized (mLock) {
706 onServiceStateChangedLocked(SoundTrigger.SERVICE_STATE_DISABLED == state);
707 }
Eric Laurentd3b82232014-07-30 08:57:39 -0700708 }
709
Sandeepd7018202014-07-10 15:15:39 -0700710 @Override
711 public void onServiceDied() {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700712 Slog.e(TAG, "onServiceDied!!");
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800713 MetricsLogger.count(mContext, "sth_service_died", 1);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700714 synchronized (mLock) {
715 onServiceDiedLocked();
Sandeep Siddhartha68173372014-07-28 13:25:30 -0700716 }
Sandeepd7018202014-07-10 15:15:39 -0700717 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700718
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700719 private void onCallStateChangedLocked(boolean callActive) {
720 if (mCallActive == callActive) {
721 // We consider multiple call states as being active
722 // so we check if something really changed or not here.
723 return;
724 }
725 mCallActive = callActive;
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800726 updateAllRecognitionsLocked(true /* notify */);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700727 }
728
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700729 private void onPowerSaveModeChangedLocked(boolean isPowerSaveMode) {
730 if (mIsPowerSaveMode == isPowerSaveMode) {
731 return;
732 }
733 mIsPowerSaveMode = isPowerSaveMode;
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800734 updateAllRecognitionsLocked(true /* notify */);
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700735 }
736
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700737 private void onSoundModelUpdatedLocked(SoundModelEvent event) {
738 // TODO: Handle sound model update here.
739 }
740
741 private void onServiceStateChangedLocked(boolean disabled) {
742 if (disabled == mServiceDisabled) {
743 return;
744 }
745 mServiceDisabled = disabled;
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800746 updateAllRecognitionsLocked(true /* notify */);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700747 }
748
Chris Thornton37f97762016-04-29 16:23:45 -0700749 private void onRecognitionAbortLocked(RecognitionEvent event) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700750 Slog.w(TAG, "Recognition aborted");
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800751 MetricsLogger.count(mContext, "sth_recognition_aborted", 1);
Chris Thornton37f97762016-04-29 16:23:45 -0700752 ModelData modelData = getModelDataForLocked(event.soundModelHandle);
Chris Thorntond0a83b82016-07-19 18:56:15 -0700753 if (modelData != null && modelData.isModelStarted()) {
Chris Thornton37f97762016-04-29 16:23:45 -0700754 modelData.setStopped();
Chris Thorntond0a83b82016-07-19 18:56:15 -0700755 try {
756 modelData.getCallback().onRecognitionPaused();
Chris Thornton2411a2c2017-03-22 13:52:50 -0700757 } catch (DeadObjectException e) {
Chris Thornton23a57402017-06-10 17:31:57 -0700758 forceStopAndUnloadModelLocked(modelData, e);
Chris Thorntond0a83b82016-07-19 18:56:15 -0700759 } catch (RemoteException e) {
760 Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
761 }
Chris Thornton37f97762016-04-29 16:23:45 -0700762 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700763 }
764
765 private void onRecognitionFailureLocked() {
766 Slog.w(TAG, "Recognition failure");
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800767 MetricsLogger.count(mContext, "sth_recognition_failure_event", 1);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700768 try {
Chris Thornton23a57402017-06-10 17:31:57 -0700769 sendErrorCallbacksToAllLocked(STATUS_ERROR);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700770 } finally {
Arunesh Mishrafac25152016-04-10 20:35:16 -0700771 internalClearModelStateLocked();
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800772 internalClearGlobalStateLocked();
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700773 }
774 }
775
Arunesh Mishrafac25152016-04-10 20:35:16 -0700776 private int getKeyphraseIdFromEvent(KeyphraseRecognitionEvent event) {
777 if (event == null) {
778 Slog.w(TAG, "Null RecognitionEvent received.");
779 return INVALID_VALUE;
780 }
781 KeyphraseRecognitionExtra[] keyphraseExtras =
782 ((KeyphraseRecognitionEvent) event).keyphraseExtras;
783 if (keyphraseExtras == null || keyphraseExtras.length == 0) {
784 Slog.w(TAG, "Invalid keyphrase recognition event!");
785 return INVALID_VALUE;
786 }
787 // TODO: Handle more than one keyphrase extras.
788 return keyphraseExtras[0].id;
789 }
790
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800791 private void onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700792 Slog.i(TAG, "Recognition success");
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800793 MetricsLogger.count(mContext, "sth_keyphrase_recognition_event", 1);
Arunesh Mishrafac25152016-04-10 20:35:16 -0700794 int keyphraseId = getKeyphraseIdFromEvent(event);
795 ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800796
Arunesh Mishrafac25152016-04-10 20:35:16 -0700797 if (modelData == null || !modelData.isKeyphraseModel()) {
798 Slog.e(TAG, "Keyphase model data does not exist for ID:" + keyphraseId);
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800799 return;
800 }
801
Arunesh Mishrafac25152016-04-10 20:35:16 -0700802 if (modelData.getCallback() == null) {
803 Slog.w(TAG, "Received onRecognition event without callback for keyphrase model.");
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700804 return;
805 }
mike dooley23c62562018-12-20 16:05:24 +0100806
mike dooleyda480922019-01-04 09:39:47 +0100807 modelData.setStopped();
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700808
809 try {
Arunesh Mishrafac25152016-04-10 20:35:16 -0700810 modelData.getCallback().onKeyphraseDetected((KeyphraseRecognitionEvent) event);
Chris Thornton2411a2c2017-03-22 13:52:50 -0700811 } catch (DeadObjectException e) {
Chris Thornton23a57402017-06-10 17:31:57 -0700812 forceStopAndUnloadModelLocked(modelData, e);
Chris Thornton2411a2c2017-03-22 13:52:50 -0700813 return;
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700814 } catch (RemoteException e) {
Arunesh Mishraf47f1732016-02-18 16:16:12 -0800815 Slog.w(TAG, "RemoteException in onKeyphraseDetected", e);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700816 }
817
Arunesh Mishrafac25152016-04-10 20:35:16 -0700818 RecognitionConfig config = modelData.getRecognitionConfig();
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800819 if (config != null) {
820 // Whether we should continue by starting this again.
Arunesh Mishrafac25152016-04-10 20:35:16 -0700821 modelData.setRequested(config.allowMultipleTriggers);
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800822 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700823 // TODO: Remove this block if the lower layer supports multiple triggers.
Chris Thornton37f97762016-04-29 16:23:45 -0700824 if (modelData.isRequested()) {
Arunesh Mishrafac25152016-04-10 20:35:16 -0700825 updateRecognitionLocked(modelData, isRecognitionAllowed(), true /* notify */);
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800826 }
827 }
828
829 private void updateAllRecognitionsLocked(boolean notify) {
830 boolean isAllowed = isRecognitionAllowed();
Chris Thornton23a57402017-06-10 17:31:57 -0700831 // updateRecognitionLocked can possibly update the list of models
832 ArrayList<ModelData> modelDatas = new ArrayList<ModelData>(mModelDataMap.values());
833 for (ModelData modelData : modelDatas) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800834 updateRecognitionLocked(modelData, isAllowed, notify);
835 }
836 }
837
838 private int updateRecognitionLocked(ModelData model, boolean isAllowed,
839 boolean notify) {
Chris Thornton37f97762016-04-29 16:23:45 -0700840 boolean start = model.isRequested() && isAllowed;
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800841 if (start == model.isModelStarted()) {
842 // No-op.
843 return STATUS_OK;
844 }
845 if (start) {
846 return startRecognitionLocked(model, notify);
847 } else {
848 return stopRecognitionLocked(model, notify);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700849 }
850 }
851
852 private void onServiceDiedLocked() {
853 try {
Chris Thornton2411a2c2017-03-22 13:52:50 -0700854 MetricsLogger.count(mContext, "sth_service_died", 1);
Chris Thornton23a57402017-06-10 17:31:57 -0700855 sendErrorCallbacksToAllLocked(SoundTrigger.STATUS_DEAD_OBJECT);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700856 } finally {
Arunesh Mishrafac25152016-04-10 20:35:16 -0700857 internalClearModelStateLocked();
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800858 internalClearGlobalStateLocked();
Eric Laurent2475e382014-09-09 15:52:25 -0700859 if (mModule != null) {
860 mModule.detach();
861 mModule = null;
862 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700863 }
864 }
865
Arunesh Mishrafac25152016-04-10 20:35:16 -0700866 // internalClearGlobalStateLocked() cleans up the telephony and power save listeners.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800867 private void internalClearGlobalStateLocked() {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700868 // Unregister from call state changes.
mike dooleycabbb112018-04-17 17:19:04 +0200869 long token = Binder.clearCallingIdentity();
870 try {
871 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
872 } finally {
873 Binder.restoreCallingIdentity(token);
874 }
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700875
876 // Unregister from power save mode changes.
877 if (mPowerSaveModeListener != null) {
878 mContext.unregisterReceiver(mPowerSaveModeListener);
879 mPowerSaveModeListener = null;
880 }
881 }
882
Arunesh Mishrafac25152016-04-10 20:35:16 -0700883 // Clears state for all models (generic and keyphrase).
884 private void internalClearModelStateLocked() {
885 for (ModelData modelData : mModelDataMap.values()) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800886 modelData.clearState();
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800887 }
888 }
889
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700890 class MyCallStateListener extends PhoneStateListener {
891 @Override
892 public void onCallStateChanged(int state, String arg1) {
893 if (DBG) Slog.d(TAG, "onCallStateChanged: " + state);
894 synchronized (mLock) {
ragof96269a2019-03-26 10:34:04 -0700895 onCallStateChangedLocked(TelephonyManager.CALL_STATE_OFFHOOK == state);
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700896 }
897 }
898 }
899
900 class PowerSaveModeListener extends BroadcastReceiver {
901 @Override
902 public void onReceive(Context context, Intent intent) {
903 if (!PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) {
904 return;
905 }
jackqdyulei455e90a2017-02-09 15:29:16 -0800906 boolean active = mPowerManager.getPowerSaveState(ServiceType.SOUND)
907 .batterySaverEnabled;
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700908 if (DBG) Slog.d(TAG, "onPowerSaveModeChanged: " + active);
909 synchronized (mLock) {
910 onPowerSaveModeChangedLocked(active);
911 }
912 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700913 }
Sandeep Siddhartha6b8556d2014-08-06 19:47:25 -0700914
915 void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
916 synchronized (mLock) {
917 pw.print(" module properties=");
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800918 pw.println(mModuleProperties == null ? "null" : mModuleProperties);
Sandeep Siddhartha6b8556d2014-08-06 19:47:25 -0700919
Sandeep Siddhartha6b8556d2014-08-06 19:47:25 -0700920 pw.print(" call active="); pw.println(mCallActive);
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700921 pw.print(" power save mode active="); pw.println(mIsPowerSaveMode);
Sandeep Siddhartha6b8556d2014-08-06 19:47:25 -0700922 pw.print(" service disabled="); pw.println(mServiceDisabled);
923 }
924 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800925
926 private void initializeTelephonyAndPowerStateListeners() {
Hall Liu5fb337f2017-11-22 17:38:15 -0800927 long token = Binder.clearCallingIdentity();
928 try {
929 // Get the current call state synchronously for the first recognition.
ragof96269a2019-03-26 10:34:04 -0700930 mCallActive = mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800931
Hall Liu5fb337f2017-11-22 17:38:15 -0800932 // Register for call state changes when the first call to start recognition occurs.
933 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800934
Hall Liu5fb337f2017-11-22 17:38:15 -0800935 // Register for power saver mode changes when the first call to start recognition
936 // occurs.
937 if (mPowerSaveModeListener == null) {
938 mPowerSaveModeListener = new PowerSaveModeListener();
939 mContext.registerReceiver(mPowerSaveModeListener,
940 new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
941 }
942 mIsPowerSaveMode = mPowerManager.getPowerSaveState(ServiceType.SOUND)
943 .batterySaverEnabled;
944 } finally {
945 Binder.restoreCallingIdentity(token);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800946 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800947 }
948
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800949 // Sends an error callback to all models with a valid registered callback.
Chris Thornton23a57402017-06-10 17:31:57 -0700950 private void sendErrorCallbacksToAllLocked(int errorCode) {
Arunesh Mishrafac25152016-04-10 20:35:16 -0700951 for (ModelData modelData : mModelDataMap.values()) {
952 IRecognitionStatusCallback callback = modelData.getCallback();
953 if (callback != null) {
Chris Thornton2411a2c2017-03-22 13:52:50 -0700954 try {
955 callback.onError(errorCode);
956 } catch (RemoteException e) {
Chris Thornton23a57402017-06-10 17:31:57 -0700957 Slog.w(TAG, "RemoteException sendErrorCallbacksToAllLocked for model handle " +
Chris Thornton2411a2c2017-03-22 13:52:50 -0700958 modelData.getHandle(), e);
959 }
960 }
961 }
962 }
963
Chris Thorntona533b992017-03-24 17:30:29 -0700964 /**
965 * Stops and unloads a sound model, and removes any reference to the model if successful.
966 *
967 * @param modelData The model data to remove.
968 * @param exception Optional exception to print in logcat. May be null.
969 */
Chris Thornton23a57402017-06-10 17:31:57 -0700970 private void forceStopAndUnloadModelLocked(ModelData modelData, Exception exception) {
971 forceStopAndUnloadModelLocked(modelData, exception, null /* modelDataIterator */);
Chris Thorntona533b992017-03-24 17:30:29 -0700972 }
973
974 /**
975 * Stops and unloads a sound model, and removes any reference to the model if successful.
976 *
977 * @param modelData The model data to remove.
978 * @param exception Optional exception to print in logcat. May be null.
979 * @param modelDataIterator If this function is to be used while iterating over the
980 * mModelDataMap, you can provide the iterator for the current model data to be used to
981 * remove the modelData from the map. This avoids generating a
982 * ConcurrentModificationException, since this function will try and remove the model
983 * data from the mModelDataMap when it can successfully unload the model.
984 */
Chris Thornton23a57402017-06-10 17:31:57 -0700985 private void forceStopAndUnloadModelLocked(ModelData modelData, Exception exception,
Chris Thorntona533b992017-03-24 17:30:29 -0700986 Iterator modelDataIterator) {
Chris Thornton2411a2c2017-03-22 13:52:50 -0700987 if (exception != null) {
988 Slog.e(TAG, "forceStopAndUnloadModel", exception);
989 }
990 if (modelData.isModelStarted()) {
991 Slog.d(TAG, "Stopping previously started dangling model " + modelData.getHandle());
992 if (mModule.stopRecognition(modelData.getHandle()) != STATUS_OK) {
993 modelData.setStopped();
994 modelData.setRequested(false);
995 } else {
996 Slog.e(TAG, "Failed to stop model " + modelData.getHandle());
997 }
998 }
999 if (modelData.isModelLoaded()) {
1000 Slog.d(TAG, "Unloading previously loaded dangling model " + modelData.getHandle());
1001 if (mModule.unloadSoundModel(modelData.getHandle()) == STATUS_OK) {
1002 // Remove the model data from existence.
Chris Thorntona533b992017-03-24 17:30:29 -07001003 if (modelDataIterator != null) {
1004 modelDataIterator.remove();
1005 } else {
1006 mModelDataMap.remove(modelData.getModelId());
1007 }
Chris Thornton2411a2c2017-03-22 13:52:50 -07001008 Iterator it = mKeyphraseUuidMap.entrySet().iterator();
1009 while (it.hasNext()) {
1010 Map.Entry pair = (Map.Entry) it.next();
1011 if (pair.getValue().equals(modelData.getModelId())) {
1012 it.remove();
1013 }
1014 }
1015 modelData.clearState();
1016 } else {
1017 Slog.e(TAG, "Failed to unload model " + modelData.getHandle());
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001018 }
1019 }
1020 }
1021
Chris Thorntona533b992017-03-24 17:30:29 -07001022 private void stopAndUnloadDeadModelsLocked() {
1023 Iterator it = mModelDataMap.entrySet().iterator();
1024 while (it.hasNext()) {
1025 ModelData modelData = (ModelData) ((Map.Entry) it.next()).getValue();
1026 if (!modelData.isModelLoaded()) {
1027 continue;
1028 }
1029 if (modelData.getCallback() == null
1030 || (modelData.getCallback().asBinder() != null
1031 && !modelData.getCallback().asBinder().pingBinder())) {
1032 // No one is listening on this model, so we might as well evict it.
1033 Slog.w(TAG, "Removing model " + modelData.getHandle() + " that has no clients");
Chris Thornton23a57402017-06-10 17:31:57 -07001034 forceStopAndUnloadModelLocked(modelData, null /* exception */, it);
Chris Thorntona533b992017-03-24 17:30:29 -07001035 }
1036 }
1037 }
1038
Ryan Bavetta79655eb2016-03-07 14:34:51 -08001039 private ModelData getOrCreateGenericModelDataLocked(UUID modelId) {
Arunesh Mishrafac25152016-04-10 20:35:16 -07001040 ModelData modelData = mModelDataMap.get(modelId);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001041 if (modelData == null) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001042 modelData = ModelData.createGenericModelData(modelId);
Arunesh Mishrafac25152016-04-10 20:35:16 -07001043 mModelDataMap.put(modelId, modelData);
1044 } else if (!modelData.isGenericModel()) {
1045 Slog.e(TAG, "UUID already used for non-generic model.");
1046 return null;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001047 }
1048 return modelData;
1049 }
1050
Arunesh Mishrafac25152016-04-10 20:35:16 -07001051 private void removeKeyphraseModelLocked(int keyphraseId) {
1052 UUID uuid = mKeyphraseUuidMap.get(keyphraseId);
1053 if (uuid == null) {
1054 return;
1055 }
1056 mModelDataMap.remove(uuid);
1057 mKeyphraseUuidMap.remove(keyphraseId);
1058 }
1059
1060 private ModelData getKeyphraseModelDataLocked(int keyphraseId) {
1061 UUID uuid = mKeyphraseUuidMap.get(keyphraseId);
1062 if (uuid == null) {
1063 return null;
1064 }
1065 return mModelDataMap.get(uuid);
1066 }
1067
1068 // Use this to create a new ModelData entry for a keyphrase Id. It will overwrite existing
1069 // mapping if one exists.
1070 private ModelData createKeyphraseModelDataLocked(UUID modelId, int keyphraseId) {
1071 mKeyphraseUuidMap.remove(keyphraseId);
1072 mModelDataMap.remove(modelId);
1073 mKeyphraseUuidMap.put(keyphraseId, modelId);
1074 ModelData modelData = ModelData.createKeyphraseModelData(modelId);
1075 mModelDataMap.put(modelId, modelData);
1076 return modelData;
1077 }
1078
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001079 // Instead of maintaining a second hashmap of modelHandle -> ModelData, we just
1080 // iterate through to find the right object (since we don't expect 100s of models
1081 // to be stored).
Ryan Bavetta79655eb2016-03-07 14:34:51 -08001082 private ModelData getModelDataForLocked(int modelHandle) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001083 // Fetch ModelData object corresponding to the model handle.
Arunesh Mishrafac25152016-04-10 20:35:16 -07001084 for (ModelData model : mModelDataMap.values()) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001085 if (model.getHandle() == modelHandle) {
1086 return model;
1087 }
1088 }
1089 return null;
1090 }
1091
1092 // Whether we are allowed to run any recognition at all. The conditions that let us run
1093 // a recognition include: no active phone call or not being in a power save mode. Also,
1094 // the native service should be enabled.
1095 private boolean isRecognitionAllowed() {
1096 return !mCallActive && !mServiceDisabled && !mIsPowerSaveMode;
1097 }
1098
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001099 // A single routine that implements the start recognition logic for both generic and keyphrase
1100 // models.
1101 private int startRecognitionLocked(ModelData modelData, boolean notify) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001102 IRecognitionStatusCallback callback = modelData.getCallback();
1103 int handle = modelData.getHandle();
1104 RecognitionConfig config = modelData.getRecognitionConfig();
1105 if (callback == null || handle == INVALID_VALUE || config == null) {
1106 // Nothing to do here.
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001107 Slog.w(TAG, "startRecognition: Bad data passed in.");
1108 MetricsLogger.count(mContext, "sth_start_recognition_error", 1);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001109 return STATUS_ERROR;
1110 }
1111
1112 if (!isRecognitionAllowed()) {
1113 // Nothing to do here.
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001114 Slog.w(TAG, "startRecognition requested but not allowed.");
1115 MetricsLogger.count(mContext, "sth_start_recognition_not_allowed", 1);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001116 return STATUS_OK;
1117 }
1118
1119 int status = mModule.startRecognition(handle, config);
1120 if (status != SoundTrigger.STATUS_OK) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001121 Slog.w(TAG, "startRecognition failed with " + status);
1122 MetricsLogger.count(mContext, "sth_start_recognition_error", 1);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001123 // Notify of error if needed.
1124 if (notify) {
1125 try {
1126 callback.onError(status);
Chris Thornton2411a2c2017-03-22 13:52:50 -07001127 } catch (DeadObjectException e) {
Chris Thornton23a57402017-06-10 17:31:57 -07001128 forceStopAndUnloadModelLocked(modelData, e);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001129 } catch (RemoteException e) {
1130 Slog.w(TAG, "RemoteException in onError", e);
1131 }
1132 }
1133 } else {
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001134 Slog.i(TAG, "startRecognition successful.");
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001135 MetricsLogger.count(mContext, "sth_start_recognition_success", 1);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001136 modelData.setStarted();
1137 // Notify of resume if needed.
1138 if (notify) {
1139 try {
1140 callback.onRecognitionResumed();
Chris Thornton2411a2c2017-03-22 13:52:50 -07001141 } catch (DeadObjectException e) {
Chris Thornton23a57402017-06-10 17:31:57 -07001142 forceStopAndUnloadModelLocked(modelData, e);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001143 } catch (RemoteException e) {
1144 Slog.w(TAG, "RemoteException in onRecognitionResumed", e);
1145 }
1146 }
1147 }
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001148 if (DBG) {
1149 Slog.d(TAG, "Model being started :" + modelData.toString());
1150 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001151 return status;
1152 }
1153
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001154 private int stopRecognitionLocked(ModelData modelData, boolean notify) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001155 IRecognitionStatusCallback callback = modelData.getCallback();
1156
Chris Thornton37f97762016-04-29 16:23:45 -07001157 // Stop recognition.
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001158 int status = STATUS_OK;
1159
Chris Thornton37f97762016-04-29 16:23:45 -07001160 status = mModule.stopRecognition(modelData.getHandle());
1161
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001162 if (status != SoundTrigger.STATUS_OK) {
1163 Slog.w(TAG, "stopRecognition call failed with " + status);
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001164 MetricsLogger.count(mContext, "sth_stop_recognition_error", 1);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001165 if (notify) {
1166 try {
1167 callback.onError(status);
Chris Thornton2411a2c2017-03-22 13:52:50 -07001168 } catch (DeadObjectException e) {
Chris Thornton23a57402017-06-10 17:31:57 -07001169 forceStopAndUnloadModelLocked(modelData, e);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001170 } catch (RemoteException e) {
1171 Slog.w(TAG, "RemoteException in onError", e);
1172 }
1173 }
1174 } else {
1175 modelData.setStopped();
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001176 MetricsLogger.count(mContext, "sth_stop_recognition_success", 1);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001177 // Notify of pause if needed.
1178 if (notify) {
1179 try {
1180 callback.onRecognitionPaused();
Chris Thornton2411a2c2017-03-22 13:52:50 -07001181 } catch (DeadObjectException e) {
Chris Thornton23a57402017-06-10 17:31:57 -07001182 forceStopAndUnloadModelLocked(modelData, e);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001183 } catch (RemoteException e) {
1184 Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
1185 }
1186 }
1187 }
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001188 if (DBG) {
1189 Slog.d(TAG, "Model being stopped :" + modelData.toString());
1190 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001191 return status;
1192 }
1193
Arunesh Mishrafac25152016-04-10 20:35:16 -07001194 private void dumpModelStateLocked() {
1195 for (UUID modelId : mModelDataMap.keySet()) {
1196 ModelData modelData = mModelDataMap.get(modelId);
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001197 Slog.i(TAG, "Model :" + modelData.toString());
1198 }
1199 }
1200
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001201 // Computes whether we have any recognition running at all (voice or generic). Sets
Eric Laurent80565fb2019-07-18 08:22:38 -07001202 // the mRecognitionRequested variable with the result.
1203 private boolean computeRecognitionRequestedLocked() {
Ryan Bavetta79655eb2016-03-07 14:34:51 -08001204 if (mModuleProperties == null || mModule == null) {
Eric Laurent80565fb2019-07-18 08:22:38 -07001205 mRecognitionRequested = false;
1206 return mRecognitionRequested;
Ryan Bavetta79655eb2016-03-07 14:34:51 -08001207 }
Arunesh Mishrafac25152016-04-10 20:35:16 -07001208 for (ModelData modelData : mModelDataMap.values()) {
Eric Laurent80565fb2019-07-18 08:22:38 -07001209 if (modelData.isRequested()) {
1210 mRecognitionRequested = true;
1211 return mRecognitionRequested;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001212 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001213 }
Eric Laurent80565fb2019-07-18 08:22:38 -07001214 mRecognitionRequested = false;
1215 return mRecognitionRequested;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001216 }
1217
1218 // This class encapsulates the callbacks, state, handles and any other information that
1219 // represents a model.
1220 private static class ModelData {
1221 // Model not loaded (and hence not started).
1222 static final int MODEL_NOTLOADED = 0;
1223
1224 // Loaded implies model was successfully loaded. Model not started yet.
1225 static final int MODEL_LOADED = 1;
1226
1227 // Started implies model was successfully loaded and start was called.
1228 static final int MODEL_STARTED = 2;
1229
1230 // One of MODEL_NOTLOADED, MODEL_LOADED, MODEL_STARTED (which implies loaded).
1231 private int mModelState;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001232 private UUID mModelId;
1233
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001234 // mRequested captures the explicit intent that a start was requested for this model. We
1235 // continue to capture and retain this state even after the model gets started, so that we
1236 // know when a model gets stopped due to "other" reasons, that we should start it again.
1237 // This was the intended behavior of the "mRequested" variable in the previous version of
1238 // this code that we are replicating here.
1239 //
1240 // The "other" reasons include power save, abort being called from the lower layer (due
1241 // to concurrent capture not being supported) and phone call state. Once we recover from
1242 // these transient disruptions, we would start such models again where mRequested == true.
1243 // Thus, mRequested gets reset only when there is an explicit intent to stop the model
1244 // coming from the SoundTriggerService layer that uses this class (and thus eventually
1245 // from the app that manages this model).
1246 private boolean mRequested = false;
1247
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001248 // One of SoundModel.TYPE_GENERIC or SoundModel.TYPE_KEYPHRASE. Initially set
1249 // to SoundModel.TYPE_UNKNOWN;
1250 private int mModelType = SoundModel.TYPE_UNKNOWN;
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001251
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001252 private IRecognitionStatusCallback mCallback = null;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001253 private RecognitionConfig mRecognitionConfig = null;
1254
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001255 // Model handle is an integer used by the HAL as an identifier for sound
1256 // models.
1257 private int mModelHandle = INVALID_VALUE;
1258
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001259 // The SoundModel instance, one of KeyphraseSoundModel or GenericSoundModel.
1260 private SoundModel mSoundModel = null;
1261
1262 private ModelData(UUID modelId, int modelType) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001263 mModelId = modelId;
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001264 // Private constructor, since we require modelType to be one of TYPE_GENERIC,
1265 // TYPE_KEYPHRASE or TYPE_UNKNOWN.
1266 mModelType = modelType;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001267 }
1268
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001269 static ModelData createKeyphraseModelData(UUID modelId) {
1270 return new ModelData(modelId, SoundModel.TYPE_KEYPHRASE);
1271 }
1272
1273 static ModelData createGenericModelData(UUID modelId) {
1274 return new ModelData(modelId, SoundModel.TYPE_GENERIC_SOUND);
1275 }
1276
1277 // Note that most of the functionality in this Java class will not work for
1278 // SoundModel.TYPE_UNKNOWN nevertheless we have it since lower layers support it.
1279 static ModelData createModelDataOfUnknownType(UUID modelId) {
1280 return new ModelData(modelId, SoundModel.TYPE_UNKNOWN);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001281 }
1282
1283 synchronized void setCallback(IRecognitionStatusCallback callback) {
1284 mCallback = callback;
1285 }
1286
1287 synchronized IRecognitionStatusCallback getCallback() {
1288 return mCallback;
1289 }
1290
1291 synchronized boolean isModelLoaded() {
Arunesh Mishra933da812016-02-18 15:31:54 -08001292 return (mModelState == MODEL_LOADED || mModelState == MODEL_STARTED);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001293 }
1294
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001295 synchronized boolean isModelNotLoaded() {
1296 return mModelState == MODEL_NOTLOADED;
1297 }
1298
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001299 synchronized void setStarted() {
1300 mModelState = MODEL_STARTED;
1301 }
1302
1303 synchronized void setStopped() {
1304 mModelState = MODEL_LOADED;
1305 }
1306
Arunesh Mishra933da812016-02-18 15:31:54 -08001307 synchronized void setLoaded() {
1308 mModelState = MODEL_LOADED;
1309 }
1310
1311 synchronized boolean isModelStarted() {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001312 return mModelState == MODEL_STARTED;
1313 }
1314
1315 synchronized void clearState() {
1316 mModelState = MODEL_NOTLOADED;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001317 mModelHandle = INVALID_VALUE;
Chris Thornton41e04a42016-04-18 16:51:08 -07001318 mRecognitionConfig = null;
1319 mRequested = false;
1320 mCallback = null;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001321 }
1322
1323 synchronized void clearCallback() {
1324 mCallback = null;
1325 }
1326
1327 synchronized void setHandle(int handle) {
1328 mModelHandle = handle;
1329 }
1330
1331 synchronized void setRecognitionConfig(RecognitionConfig config) {
1332 mRecognitionConfig = config;
1333 }
1334
1335 synchronized int getHandle() {
1336 return mModelHandle;
1337 }
1338
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001339 synchronized UUID getModelId() {
1340 return mModelId;
1341 }
1342
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001343 synchronized RecognitionConfig getRecognitionConfig() {
1344 return mRecognitionConfig;
1345 }
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001346
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001347 // Whether a start recognition was requested.
Chris Thornton37f97762016-04-29 16:23:45 -07001348 synchronized boolean isRequested() {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001349 return mRequested;
1350 }
1351
1352 synchronized void setRequested(boolean requested) {
1353 mRequested = requested;
1354 }
1355
1356 synchronized void setSoundModel(SoundModel soundModel) {
1357 mSoundModel = soundModel;
1358 }
1359
1360 synchronized SoundModel getSoundModel() {
1361 return mSoundModel;
1362 }
1363
1364 synchronized int getModelType() {
1365 return mModelType;
1366 }
1367
1368 synchronized boolean isKeyphraseModel() {
1369 return mModelType == SoundModel.TYPE_KEYPHRASE;
1370 }
1371
Arunesh Mishrafac25152016-04-10 20:35:16 -07001372 synchronized boolean isGenericModel() {
1373 return mModelType == SoundModel.TYPE_GENERIC_SOUND;
1374 }
1375
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001376 synchronized String stateToString() {
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001377 switch(mModelState) {
1378 case MODEL_NOTLOADED: return "NOT_LOADED";
1379 case MODEL_LOADED: return "LOADED";
1380 case MODEL_STARTED: return "STARTED";
1381 }
1382 return "Unknown state";
1383 }
1384
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001385 synchronized String requestedToString() {
1386 return "Requested: " + (mRequested ? "Yes" : "No");
1387 }
1388
1389 synchronized String callbackToString() {
1390 return "Callback: " + (mCallback != null ? mCallback.asBinder() : "null");
1391 }
1392
1393 synchronized String uuidToString() {
1394 return "UUID: " + mModelId;
1395 }
1396
1397 synchronized public String toString() {
1398 return "Handle: " + mModelHandle + "\n" +
1399 "ModelState: " + stateToString() + "\n" +
1400 requestedToString() + "\n" +
1401 callbackToString() + "\n" +
Arunesh Mishrafac25152016-04-10 20:35:16 -07001402 uuidToString() + "\n" + modelTypeToString();
1403 }
1404
1405 synchronized String modelTypeToString() {
1406 String type = null;
1407 switch (mModelType) {
1408 case SoundModel.TYPE_GENERIC_SOUND: type = "Generic"; break;
1409 case SoundModel.TYPE_UNKNOWN: type = "Unknown"; break;
1410 case SoundModel.TYPE_KEYPHRASE: type = "Keyphrase"; break;
1411 }
1412 return "Model type: " + type + "\n";
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001413 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001414 }
Sandeepd7018202014-07-10 15:15:39 -07001415}