blob: 25c54b3e51eb8f3795964657207f0cde187f0f43 [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;
Chris Thornton2411a2c2017-03-22 13:52:50 -070037import android.os.DeadObjectException;
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -070038import android.os.PowerManager;
Makoto Onuki2eccd022017-11-01 13:44:23 -070039import android.os.PowerManager.ServiceType;
Sandeep Siddhartha05589722014-07-17 16:21:54 -070040import android.os.RemoteException;
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -070041import android.telephony.PhoneStateListener;
42import android.telephony.TelephonyManager;
Sandeepd7018202014-07-10 15:15:39 -070043import android.util.Slog;
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -080044import com.android.internal.logging.MetricsLogger;
Sandeepd7018202014-07-10 15:15:39 -070045
Sandeep Siddhartha6b8556d2014-08-06 19:47:25 -070046import java.io.FileDescriptor;
47import java.io.PrintWriter;
Sandeepd7018202014-07-10 15:15:39 -070048import java.util.ArrayList;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080049import java.util.HashMap;
Chris Thornton2411a2c2017-03-22 13:52:50 -070050import java.util.Iterator;
51import java.util.Map;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080052import java.util.UUID;
Sandeepd7018202014-07-10 15:15:39 -070053
54/**
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080055 * Helper for {@link SoundTrigger} APIs. Supports two types of models:
56 * (i) A voice model which is exported via the {@link VoiceInteractionService}. There can only be
57 * a single voice model running on the DSP at any given time.
58 *
59 * (ii) Generic sound-trigger models: Supports multiple of these.
60 *
Sandeepd7018202014-07-10 15:15:39 -070061 * Currently this just acts as an abstraction over all SoundTrigger API calls.
Sandeepd7018202014-07-10 15:15:39 -070062 * @hide
63 */
64public class SoundTriggerHelper implements SoundTrigger.StatusListener {
65 static final String TAG = "SoundTriggerHelper";
Sandeep Siddhartha8cf8f712014-09-15 12:51:08 -070066 static final boolean DBG = false;
Sandeepd7018202014-07-10 15:15:39 -070067
68 /**
Sandeep Siddhartha05589722014-07-17 16:21:54 -070069 * Return codes for {@link #startRecognition(int, KeyphraseSoundModel,
70 * IRecognitionStatusCallback, RecognitionConfig)},
71 * {@link #stopRecognition(int, IRecognitionStatusCallback)}
Sandeepd7018202014-07-10 15:15:39 -070072 */
Sandeep Siddhartha05589722014-07-17 16:21:54 -070073 public static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR;
74 public static final int STATUS_OK = SoundTrigger.STATUS_OK;
Sandeepd7018202014-07-10 15:15:39 -070075
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -070076 private static final int INVALID_VALUE = Integer.MIN_VALUE;
Sandeepd7018202014-07-10 15:15:39 -070077
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -070078 /** The {@link ModuleProperties} for the system, or null if none exists. */
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080079 final ModuleProperties mModuleProperties;
Sandeepd7018202014-07-10 15:15:39 -070080
81 /** The properties for the DSP module */
Eric Laurent2475e382014-09-09 15:52:25 -070082 private SoundTriggerModule mModule;
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -070083 private final Object mLock = new Object();
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -070084 private final Context mContext;
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -070085 private final TelephonyManager mTelephonyManager;
86 private final PhoneStateListener mPhoneStateListener;
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -070087 private final PowerManager mPowerManager;
Sandeepd7018202014-07-10 15:15:39 -070088
Arunesh Mishrafac25152016-04-10 20:35:16 -070089 // The SoundTriggerManager layer handles multiple recognition models of type generic and
90 // keyphrase. We store the ModelData here in a hashmap.
91 private final HashMap<UUID, ModelData> mModelDataMap;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080092
Arunesh Mishrafac25152016-04-10 20:35:16 -070093 // An index of keyphrase sound models so that we can reach them easily. We support indexing
94 // keyphrase sound models with a keyphrase ID. Sound model with the same keyphrase ID will
95 // replace an existing model, thus there is a 1:1 mapping from keyphrase ID to a voice
96 // sound model.
97 private HashMap<Integer, UUID> mKeyphraseUuidMap;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080098
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -070099 private boolean mCallActive = false;
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700100 private boolean mIsPowerSaveMode = false;
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700101 // Indicates if the native sound trigger service is disabled or not.
102 // This is an indirect indication of the microphone being open in some other application.
103 private boolean mServiceDisabled = false;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800104
105 // Whether we have ANY recognition (keyphrase or generic) running.
106 private boolean mRecognitionRunning = false;
107
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700108 private PowerSaveModeListener mPowerSaveModeListener;
Sandeepd7018202014-07-10 15:15:39 -0700109
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700110 SoundTriggerHelper(Context context) {
Sandeepd7018202014-07-10 15:15:39 -0700111 ArrayList <ModuleProperties> modules = new ArrayList<>();
112 int status = SoundTrigger.listModules(modules);
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700113 mContext = context;
114 mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
115 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
Arunesh Mishrafac25152016-04-10 20:35:16 -0700116 mModelDataMap = new HashMap<UUID, ModelData>();
117 mKeyphraseUuidMap = new HashMap<Integer, UUID>();
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700118 mPhoneStateListener = new MyCallStateListener();
Sandeepd7018202014-07-10 15:15:39 -0700119 if (status != SoundTrigger.STATUS_OK || modules.size() == 0) {
Sandeep Siddharthaa433af62014-07-16 15:36:44 -0700120 Slog.w(TAG, "listModules status=" + status + ", # of modules=" + modules.size());
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800121 mModuleProperties = null;
Sandeepd7018202014-07-10 15:15:39 -0700122 mModule = null;
123 } else {
124 // TODO: Figure out how to determine which module corresponds to the DSP hardware.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800125 mModuleProperties = modules.get(0);
126 }
127 }
128
129 /**
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800130 * Starts recognition for the given generic sound model ID. This is a wrapper around {@link
131 * startRecognition()}.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800132 *
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800133 * @param modelId UUID of the sound model.
134 * @param soundModel The generic sound model to use for recognition.
135 * @param callback Callack for the recognition events related to the given keyphrase.
136 * @param recognitionConfig Instance of RecognitionConfig containing the parameters for the
137 * recognition.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800138 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
139 */
140 int startGenericRecognition(UUID modelId, GenericSoundModel soundModel,
141 IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800142 MetricsLogger.count(mContext, "sth_start_recognition", 1);
143 if (modelId == null || soundModel == null || callback == null ||
144 recognitionConfig == null) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800145 Slog.w(TAG, "Passed in bad data to startGenericRecognition().");
146 return STATUS_ERROR;
147 }
148
149 synchronized (mLock) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800150 ModelData modelData = getOrCreateGenericModelDataLocked(modelId);
Arunesh Mishrafac25152016-04-10 20:35:16 -0700151 if (modelData == null) {
152 Slog.w(TAG, "Irrecoverable error occurred, check UUID / sound model data.");
153 return STATUS_ERROR;
154 }
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800155 return startRecognition(soundModel, modelData, callback, recognitionConfig,
156 INVALID_VALUE /* keyphraseId */);
157 }
158 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800159
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800160 /**
161 * Starts recognition for the given keyphraseId.
162 *
163 * @param keyphraseId The identifier of the keyphrase for which
164 * the recognition is to be started.
165 * @param soundModel The sound model to use for recognition.
166 * @param callback The callback for the recognition events related to the given keyphrase.
167 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
168 */
169 int startKeyphraseRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
170 IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) {
171 synchronized (mLock) {
172 MetricsLogger.count(mContext, "sth_start_recognition", 1);
173 if (soundModel == null || callback == null || recognitionConfig == null) {
174 return STATUS_ERROR;
175 }
176
177 if (DBG) {
178 Slog.d(TAG, "startKeyphraseRecognition for keyphraseId=" + keyphraseId
179 + " soundModel=" + soundModel + ", callback=" + callback.asBinder()
180 + ", recognitionConfig=" + recognitionConfig);
181 Slog.d(TAG, "moduleProperties=" + mModuleProperties);
Arunesh Mishrafac25152016-04-10 20:35:16 -0700182 dumpModelStateLocked();
183 }
184
185 ModelData model = getKeyphraseModelDataLocked(keyphraseId);
186 if (model != null && !model.isKeyphraseModel()) {
187 Slog.e(TAG, "Generic model with same UUID exists.");
188 return STATUS_ERROR;
189 }
190
191 // Process existing model first.
Chris Thornton41e04a42016-04-18 16:51:08 -0700192 if (model != null && !model.getModelId().equals(soundModel.uuid)) {
Arunesh Mishrafac25152016-04-10 20:35:16 -0700193 // The existing model has a different UUID, should be replaced.
Chris Thornton23a57402017-06-10 17:31:57 -0700194 int status = cleanUpExistingKeyphraseModelLocked(model);
Arunesh Mishrafac25152016-04-10 20:35:16 -0700195 if (status != STATUS_OK) {
196 return status;
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800197 }
Chris Thornton41e04a42016-04-18 16:51:08 -0700198 removeKeyphraseModelLocked(keyphraseId);
Arunesh Mishrafac25152016-04-10 20:35:16 -0700199 model = null;
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800200 }
Arunesh Mishrafac25152016-04-10 20:35:16 -0700201
202 // We need to create a new one: either no previous models existed for given keyphrase id
203 // or the existing model had a different UUID and was cleaned up.
204 if (model == null) {
205 model = createKeyphraseModelDataLocked(soundModel.uuid, keyphraseId);
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800206 }
Arunesh Mishrafac25152016-04-10 20:35:16 -0700207
208 return startRecognition(soundModel, model, callback, recognitionConfig,
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800209 keyphraseId);
210 }
211 }
212
Chris Thornton23a57402017-06-10 17:31:57 -0700213 private int cleanUpExistingKeyphraseModelLocked(ModelData modelData) {
Arunesh Mishrafac25152016-04-10 20:35:16 -0700214 // Stop and clean up a previous ModelData if one exists. This usually is used when the
215 // previous model has a different UUID for the same keyphrase ID.
216 int status = tryStopAndUnloadLocked(modelData, true /* stop */, true /* unload */);
217 if (status != STATUS_OK) {
218 Slog.w(TAG, "Unable to stop or unload previous model: " +
219 modelData.toString());
220 }
221 return status;
222 }
223
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800224 /**
225 * Starts recognition for the given sound model. A single routine for both keyphrase and
226 * generic sound models.
227 *
228 * @param soundModel The sound model to use for recognition.
229 * @param modelData Instance of {@link #ModelData} for the given model.
230 * @param callback Callback for the recognition events related to the given keyphrase.
231 * @param recognitionConfig Instance of {@link RecognitionConfig} containing the parameters
232 * @param keyphraseId Keyphrase ID for keyphrase models only. Pass in INVALID_VALUE for other
233 * models.
234 * for the recognition.
235 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
236 */
237 int startRecognition(SoundModel soundModel, ModelData modelData,
238 IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig,
239 int keyphraseId) {
240 synchronized (mLock) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800241 if (mModuleProperties == null) {
242 Slog.w(TAG, "Attempting startRecognition without the capability");
243 return STATUS_ERROR;
244 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800245 if (mModule == null) {
246 mModule = SoundTrigger.attachModule(mModuleProperties.id, this, null);
247 if (mModule == null) {
248 Slog.w(TAG, "startRecognition cannot attach to sound trigger module");
249 return STATUS_ERROR;
250 }
251 }
252
253 // Initialize power save, call active state monitoring logic.
254 if (!mRecognitionRunning) {
255 initializeTelephonyAndPowerStateListeners();
256 }
257
Arunesh Mishrafac25152016-04-10 20:35:16 -0700258 // If the existing SoundModel is different (for the same UUID for Generic and same
259 // keyphrase ID for voice), ensure that it is unloaded and stopped before proceeding.
260 // This works for both keyphrase and generic models. This logic also ensures that a
261 // previously loaded (or started) model is appropriately stopped. Since this is a
262 // generalization of the previous logic with a single keyphrase model, we should have
263 // no regression with the previous version of this code as was given in the
264 // startKeyphrase() routine.
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800265 if (modelData.getSoundModel() != null) {
Arunesh Mishrafac25152016-04-10 20:35:16 -0700266 boolean stopModel = false; // Stop the model after checking that it is started.
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800267 boolean unloadModel = false;
268 if (modelData.getSoundModel().equals(soundModel) && modelData.isModelStarted()) {
269 // The model has not changed, but the previous model is "started".
270 // Stop the previously running model.
271 stopModel = true;
272 unloadModel = false; // No need to unload if the model hasn't changed.
273 } else if (!modelData.getSoundModel().equals(soundModel)) {
274 // We have a different model for this UUID. Stop and unload if needed. This
275 // helps maintain the singleton restriction for keyphrase sound models.
276 stopModel = modelData.isModelStarted();
277 unloadModel = modelData.isModelLoaded();
278 }
279 if (stopModel || unloadModel) {
280 int status = tryStopAndUnloadLocked(modelData, stopModel, unloadModel);
281 if (status != STATUS_OK) {
282 Slog.w(TAG, "Unable to stop or unload previous model: " +
283 modelData.toString());
284 return status;
285 }
286 }
287 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800288
289 IRecognitionStatusCallback oldCallback = modelData.getCallback();
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800290 if (oldCallback != null && oldCallback.asBinder() != callback.asBinder()) {
291 Slog.w(TAG, "Canceling previous recognition for model id: " +
292 modelData.getModelId());
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800293 try {
294 oldCallback.onError(STATUS_ERROR);
295 } catch (RemoteException e) {
296 Slog.w(TAG, "RemoteException in onDetectionStopped", e);
297 }
298 modelData.clearCallback();
299 }
300
Arunesh Mishrafac25152016-04-10 20:35:16 -0700301 // Load the model if it is not loaded.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800302 if (!modelData.isModelLoaded()) {
Chris Thorntona533b992017-03-24 17:30:29 -0700303 // Before we try and load this model, we should first make sure that any other
304 // models that don't have an active recognition/dead callback are unloaded. Since
305 // there is a finite limit on the number of models that the hardware may be able to
306 // have loaded, we want to make sure there's room for our model.
307 stopAndUnloadDeadModelsLocked();
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800308 int[] handle = new int[] { INVALID_VALUE };
309 int status = mModule.loadSoundModel(soundModel, handle);
310 if (status != SoundTrigger.STATUS_OK) {
311 Slog.w(TAG, "loadSoundModel call failed with " + status);
312 return status;
313 }
314 if (handle[0] == INVALID_VALUE) {
315 Slog.w(TAG, "loadSoundModel call returned invalid sound model handle");
316 return STATUS_ERROR;
317 }
318 modelData.setHandle(handle[0]);
Arunesh Mishra933da812016-02-18 15:31:54 -0800319 modelData.setLoaded();
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800320 Slog.d(TAG, "Sound model loaded with handle:" + handle[0]);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800321 }
322 modelData.setCallback(callback);
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800323 modelData.setRequested(true);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800324 modelData.setRecognitionConfig(recognitionConfig);
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800325 modelData.setSoundModel(soundModel);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800326
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800327 return startRecognitionLocked(modelData,
328 false /* Don't notify for synchronous calls */);
Sandeepd7018202014-07-10 15:15:39 -0700329 }
330 }
331
332 /**
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800333 * Stops recognition for the given generic sound model. This is a wrapper for {@link
334 * #stopRecognition}.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800335 *
336 * @param modelId The identifier of the generic sound model for which
337 * the recognition is to be stopped.
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800338 * @param callback The callback for the recognition events related to the given sound model.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800339 *
340 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
341 */
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800342 int stopGenericRecognition(UUID modelId, IRecognitionStatusCallback callback) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800343 synchronized (mLock) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800344 MetricsLogger.count(mContext, "sth_stop_recognition", 1);
345 if (callback == null || modelId == null) {
346 Slog.e(TAG, "Null callbackreceived for stopGenericRecognition() for modelid:" +
347 modelId);
348 return STATUS_ERROR;
349 }
350
Arunesh Mishrafac25152016-04-10 20:35:16 -0700351 ModelData modelData = mModelDataMap.get(modelId);
352 if (modelData == null || !modelData.isGenericModel()) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800353 Slog.w(TAG, "Attempting stopRecognition on invalid model with id:" + modelId);
354 return STATUS_ERROR;
355 }
356
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800357 int status = stopRecognition(modelData, callback);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800358 if (status != SoundTrigger.STATUS_OK) {
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800359 Slog.w(TAG, "stopGenericRecognition failed: " + status);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800360 }
361 return status;
362 }
363 }
364
365 /**
Sandeep Siddhartha05589722014-07-17 16:21:54 -0700366 * Stops recognition for the given {@link Keyphrase} if a recognition is
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800367 * currently active. This is a wrapper for {@link #stopRecognition()}.
Sandeepd7018202014-07-10 15:15:39 -0700368 *
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -0700369 * @param keyphraseId The identifier of the keyphrase for which
370 * the recognition is to be stopped.
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800371 * @param callback The callback for the recognition events related to the given keyphrase.
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -0700372 *
Sandeepd7018202014-07-10 15:15:39 -0700373 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
374 */
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800375 int stopKeyphraseRecognition(int keyphraseId, IRecognitionStatusCallback callback) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700376 synchronized (mLock) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800377 MetricsLogger.count(mContext, "sth_stop_recognition", 1);
378 if (callback == null) {
379 Slog.e(TAG, "Null callback received for stopKeyphraseRecognition() for keyphraseId:" +
380 keyphraseId);
381 return STATUS_ERROR;
382 }
383
Arunesh Mishrafac25152016-04-10 20:35:16 -0700384 ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
385 if (modelData == null || !modelData.isKeyphraseModel()) {
Chris Thornton2411a2c2017-03-22 13:52:50 -0700386 Slog.e(TAG, "No model exists for given keyphrase Id " + keyphraseId);
Arunesh Mishrafac25152016-04-10 20:35:16 -0700387 return STATUS_ERROR;
388 }
389
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700390 if (DBG) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800391 Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId + ", callback =" +
392 callback.asBinder());
Arunesh Mishrafac25152016-04-10 20:35:16 -0700393 Slog.d(TAG, "current callback=" + (modelData == null ? "null" :
394 modelData.getCallback().asBinder()));
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700395 }
Arunesh Mishrafac25152016-04-10 20:35:16 -0700396 int status = stopRecognition(modelData, callback);
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -0700397 if (status != SoundTrigger.STATUS_OK) {
Sandeep Siddhartha2c0273e2014-08-01 11:32:03 -0700398 return status;
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -0700399 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700400
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700401 return status;
Sandeepd7018202014-07-10 15:15:39 -0700402 }
403 }
404
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700405 /**
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800406 * Stops recognition for the given ModelData instance.
407 *
408 * @param modelData Instance of {@link #ModelData} sound model.
409 * @param callback The callback for the recognition events related to the given keyphrase.
410 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
411 */
412 private int stopRecognition(ModelData modelData, IRecognitionStatusCallback callback) {
413 synchronized (mLock) {
414 if (callback == null) {
415 return STATUS_ERROR;
416 }
417 if (mModuleProperties == null || mModule == null) {
418 Slog.w(TAG, "Attempting stopRecognition without the capability");
419 return STATUS_ERROR;
420 }
421
422 IRecognitionStatusCallback currentCallback = modelData.getCallback();
Chris Thornton37f97762016-04-29 16:23:45 -0700423 if (modelData == null || currentCallback == null ||
424 (!modelData.isRequested() && !modelData.isModelStarted())) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800425 // startGenericRecognition hasn't been called or it failed.
Chris Thornton37f97762016-04-29 16:23:45 -0700426 Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition");
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800427 return STATUS_ERROR;
428 }
429
430 if (currentCallback.asBinder() != callback.asBinder()) {
431 // We don't allow a different listener to stop the recognition than the one
432 // that started it.
433 Slog.w(TAG, "Attempting stopRecognition for another recognition");
434 return STATUS_ERROR;
435 }
436
437 // Request stop recognition via the update() method.
438 modelData.setRequested(false);
439 int status = updateRecognitionLocked(modelData, isRecognitionAllowed(),
440 false /* don't notify for synchronous calls */);
441 if (status != SoundTrigger.STATUS_OK) {
442 return status;
443 }
444
445 // We leave the sound model loaded but not started, this helps us when we start back.
446 // Also clear the internal state once the recognition has been stopped.
447 modelData.setLoaded();
448 modelData.clearCallback();
449 modelData.setRecognitionConfig(null);
450
451 if (!computeRecognitionRunningLocked()) {
452 internalClearGlobalStateLocked();
453 }
454
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800455 return status;
456 }
457 }
458
459 // Stop a previously started model if it was started. Optionally, unload if the previous model
460 // is stale and is about to be replaced.
461 // Needs to be called with the mLock held.
462 private int tryStopAndUnloadLocked(ModelData modelData, boolean stopModel,
463 boolean unloadModel) {
464 int status = STATUS_OK;
465 if (modelData.isModelNotLoaded()) {
466 return status;
467 }
468 if (stopModel && modelData.isModelStarted()) {
469 status = stopRecognitionLocked(modelData,
470 false /* don't notify for synchronous calls */);
471 if (status != SoundTrigger.STATUS_OK) {
472 Slog.w(TAG, "stopRecognition failed: " + status);
473 return status;
474 }
475 }
476
477 if (unloadModel && modelData.isModelLoaded()) {
478 Slog.d(TAG, "Unloading previously loaded stale model.");
479 status = mModule.unloadSoundModel(modelData.getHandle());
480 MetricsLogger.count(mContext, "sth_unloading_stale_model", 1);
481 if (status != SoundTrigger.STATUS_OK) {
482 Slog.w(TAG, "unloadSoundModel call failed with " + status);
483 } else {
484 // Clear the ModelData state if successful.
485 modelData.clearState();
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800486 }
487 }
488 return status;
489 }
490
Arunesh Mishra55a9b002016-02-01 14:06:37 -0800491 public ModuleProperties getModuleProperties() {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800492 return mModuleProperties;
Arunesh Mishra55a9b002016-02-01 14:06:37 -0800493 }
494
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800495 int unloadKeyphraseSoundModel(int keyphraseId) {
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800496 synchronized (mLock) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800497 MetricsLogger.count(mContext, "sth_unload_keyphrase_sound_model", 1);
Arunesh Mishrafac25152016-04-10 20:35:16 -0700498 ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
499 if (mModule == null || modelData == null || modelData.getHandle() == INVALID_VALUE ||
500 !modelData.isKeyphraseModel()) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800501 return STATUS_ERROR;
502 }
503
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800504 // Stop recognition if it's the current one.
Arunesh Mishrafac25152016-04-10 20:35:16 -0700505 modelData.setRequested(false);
506 int status = updateRecognitionLocked(modelData, isRecognitionAllowed(),
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800507 false /* don't notify */);
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800508 if (status != SoundTrigger.STATUS_OK) {
509 Slog.w(TAG, "Stop recognition failed for keyphrase ID:" + status);
510 }
511
Arunesh Mishrafac25152016-04-10 20:35:16 -0700512 status = mModule.unloadSoundModel(modelData.getHandle());
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800513 if (status != SoundTrigger.STATUS_OK) {
514 Slog.w(TAG, "unloadKeyphraseSoundModel call failed with " + status);
515 }
Arunesh Mishrafac25152016-04-10 20:35:16 -0700516
517 // Remove it from existence.
518 removeKeyphraseModelLocked(keyphraseId);
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800519 return status;
520 }
521 }
522
523 int unloadGenericSoundModel(UUID modelId) {
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800524 synchronized (mLock) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800525 MetricsLogger.count(mContext, "sth_unload_generic_sound_model", 1);
526 if (modelId == null || mModule == null) {
527 return STATUS_ERROR;
528 }
Arunesh Mishrafac25152016-04-10 20:35:16 -0700529 ModelData modelData = mModelDataMap.get(modelId);
530 if (modelData == null || !modelData.isGenericModel()) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800531 Slog.w(TAG, "Unload error: Attempting unload invalid generic model with id:" +
532 modelId);
Ryan Bavetta79655eb2016-03-07 14:34:51 -0800533 return STATUS_ERROR;
534 }
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800535 if (!modelData.isModelLoaded()) {
536 // Nothing to do here.
537 Slog.i(TAG, "Unload: Given generic model is not loaded:" + modelId);
538 return STATUS_OK;
539 }
540 if (modelData.isModelStarted()) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800541 int status = stopRecognitionLocked(modelData,
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800542 false /* don't notify for synchronous calls */);
543 if (status != SoundTrigger.STATUS_OK) {
544 Slog.w(TAG, "stopGenericRecognition failed: " + status);
545 }
546 }
547
548 int status = mModule.unloadSoundModel(modelData.getHandle());
549 if (status != SoundTrigger.STATUS_OK) {
550 Slog.w(TAG, "unloadGenericSoundModel() call failed with " + status);
551 Slog.w(TAG, "unloadGenericSoundModel() force-marking model as unloaded.");
552 }
Arunesh Mishrafac25152016-04-10 20:35:16 -0700553
554 // Remove it from existence.
555 mModelDataMap.remove(modelId);
556 if (DBG) dumpModelStateLocked();
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800557 return status;
558 }
559 }
560
Chris Thorntonba08b792017-06-08 22:34:37 -0700561 boolean isRecognitionRequested(UUID modelId) {
562 synchronized (mLock) {
563 ModelData modelData = mModelDataMap.get(modelId);
564 return modelData != null && modelData.isRequested();
565 }
566 }
567
Sandeepd7018202014-07-10 15:15:39 -0700568 //---- SoundTrigger.StatusListener methods
569 @Override
570 public void onRecognition(RecognitionEvent event) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800571 if (event == null) {
572 Slog.w(TAG, "Null recognition event!");
573 return;
574 }
575
576 if (!(event instanceof KeyphraseRecognitionEvent) &&
577 !(event instanceof GenericRecognitionEvent)) {
Chris Thornton37f97762016-04-29 16:23:45 -0700578 Slog.w(TAG, "Invalid recognition event type (not one of generic or keyphrase)!");
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -0700579 return;
580 }
Sandeepd7018202014-07-10 15:15:39 -0700581
Sandeep Siddhartha68173372014-07-28 13:25:30 -0700582 if (DBG) Slog.d(TAG, "onRecognition: " + event);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700583 synchronized (mLock) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700584 switch (event.status) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700585 case SoundTrigger.RECOGNITION_STATUS_ABORT:
Chris Thornton37f97762016-04-29 16:23:45 -0700586 onRecognitionAbortLocked(event);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700587 break;
588 case SoundTrigger.RECOGNITION_STATUS_FAILURE:
Chris Thornton37f97762016-04-29 16:23:45 -0700589 // Fire failures to all listeners since it's not tied to a keyphrase.
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700590 onRecognitionFailureLocked();
591 break;
592 case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800593 if (isKeyphraseRecognitionEvent(event)) {
594 onKeyphraseRecognitionSuccessLocked((KeyphraseRecognitionEvent) event);
595 } else {
596 onGenericRecognitionSuccessLocked((GenericRecognitionEvent) event);
597 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700598 break;
599 }
Sandeepd7018202014-07-10 15:15:39 -0700600 }
601 }
602
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800603 private boolean isKeyphraseRecognitionEvent(RecognitionEvent event) {
Arunesh Mishraf47f1732016-02-18 16:16:12 -0800604 return event instanceof KeyphraseRecognitionEvent;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800605 }
606
607 private void onGenericRecognitionSuccessLocked(GenericRecognitionEvent event) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800608 MetricsLogger.count(mContext, "sth_generic_recognition_event", 1);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800609 if (event.status != SoundTrigger.RECOGNITION_STATUS_SUCCESS) {
610 return;
611 }
Ryan Bavetta79655eb2016-03-07 14:34:51 -0800612 ModelData model = getModelDataForLocked(event.soundModelHandle);
Arunesh Mishrafac25152016-04-10 20:35:16 -0700613 if (model == null || !model.isGenericModel()) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800614 Slog.w(TAG, "Generic recognition event: Model does not exist for handle: " +
615 event.soundModelHandle);
616 return;
617 }
618
619 IRecognitionStatusCallback callback = model.getCallback();
620 if (callback == null) {
621 Slog.w(TAG, "Generic recognition event: Null callback for model handle: " +
622 event.soundModelHandle);
623 return;
624 }
625
Chris Thornton2411a2c2017-03-22 13:52:50 -0700626 model.setStopped();
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800627 try {
Arunesh Mishraf47f1732016-02-18 16:16:12 -0800628 callback.onGenericSoundTriggerDetected((GenericRecognitionEvent) event);
Chris Thornton2411a2c2017-03-22 13:52:50 -0700629 } catch (DeadObjectException e) {
Chris Thornton23a57402017-06-10 17:31:57 -0700630 forceStopAndUnloadModelLocked(model, e);
Chris Thornton2411a2c2017-03-22 13:52:50 -0700631 return;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800632 } catch (RemoteException e) {
Arunesh Mishraf47f1732016-02-18 16:16:12 -0800633 Slog.w(TAG, "RemoteException in onGenericSoundTriggerDetected", e);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800634 }
635
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800636 RecognitionConfig config = model.getRecognitionConfig();
637 if (config == null) {
638 Slog.w(TAG, "Generic recognition event: Null RecognitionConfig for model handle: " +
639 event.soundModelHandle);
640 return;
641 }
642
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800643 model.setRequested(config.allowMultipleTriggers);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800644 // TODO: Remove this block if the lower layer supports multiple triggers.
Chris Thornton37f97762016-04-29 16:23:45 -0700645 if (model.isRequested()) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800646 updateRecognitionLocked(model, isRecognitionAllowed() /* isAllowed */,
647 true /* notify */);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800648 }
649 }
650
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700651 @Override
Eric Laurentd3b82232014-07-30 08:57:39 -0700652 public void onSoundModelUpdate(SoundModelEvent event) {
653 if (event == null) {
654 Slog.w(TAG, "Invalid sound model event!");
655 return;
656 }
Eric Laurentd3b82232014-07-30 08:57:39 -0700657 if (DBG) Slog.d(TAG, "onSoundModelUpdate: " + event);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700658 synchronized (mLock) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800659 MetricsLogger.count(mContext, "sth_sound_model_updated", 1);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700660 onSoundModelUpdatedLocked(event);
661 }
Eric Laurentd3b82232014-07-30 08:57:39 -0700662 }
663
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700664 @Override
Eric Laurentd3b82232014-07-30 08:57:39 -0700665 public void onServiceStateChange(int state) {
666 if (DBG) Slog.d(TAG, "onServiceStateChange, state: " + state);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700667 synchronized (mLock) {
668 onServiceStateChangedLocked(SoundTrigger.SERVICE_STATE_DISABLED == state);
669 }
Eric Laurentd3b82232014-07-30 08:57:39 -0700670 }
671
Sandeepd7018202014-07-10 15:15:39 -0700672 @Override
673 public void onServiceDied() {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700674 Slog.e(TAG, "onServiceDied!!");
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800675 MetricsLogger.count(mContext, "sth_service_died", 1);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700676 synchronized (mLock) {
677 onServiceDiedLocked();
Sandeep Siddhartha68173372014-07-28 13:25:30 -0700678 }
Sandeepd7018202014-07-10 15:15:39 -0700679 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700680
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700681 private void onCallStateChangedLocked(boolean callActive) {
682 if (mCallActive == callActive) {
683 // We consider multiple call states as being active
684 // so we check if something really changed or not here.
685 return;
686 }
687 mCallActive = callActive;
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800688 updateAllRecognitionsLocked(true /* notify */);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700689 }
690
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700691 private void onPowerSaveModeChangedLocked(boolean isPowerSaveMode) {
692 if (mIsPowerSaveMode == isPowerSaveMode) {
693 return;
694 }
695 mIsPowerSaveMode = isPowerSaveMode;
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800696 updateAllRecognitionsLocked(true /* notify */);
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700697 }
698
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700699 private void onSoundModelUpdatedLocked(SoundModelEvent event) {
700 // TODO: Handle sound model update here.
701 }
702
703 private void onServiceStateChangedLocked(boolean disabled) {
704 if (disabled == mServiceDisabled) {
705 return;
706 }
707 mServiceDisabled = disabled;
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800708 updateAllRecognitionsLocked(true /* notify */);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700709 }
710
Chris Thornton37f97762016-04-29 16:23:45 -0700711 private void onRecognitionAbortLocked(RecognitionEvent event) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700712 Slog.w(TAG, "Recognition aborted");
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800713 MetricsLogger.count(mContext, "sth_recognition_aborted", 1);
Chris Thornton37f97762016-04-29 16:23:45 -0700714 ModelData modelData = getModelDataForLocked(event.soundModelHandle);
Chris Thorntond0a83b82016-07-19 18:56:15 -0700715 if (modelData != null && modelData.isModelStarted()) {
Chris Thornton37f97762016-04-29 16:23:45 -0700716 modelData.setStopped();
Chris Thorntond0a83b82016-07-19 18:56:15 -0700717 try {
718 modelData.getCallback().onRecognitionPaused();
Chris Thornton2411a2c2017-03-22 13:52:50 -0700719 } catch (DeadObjectException e) {
Chris Thornton23a57402017-06-10 17:31:57 -0700720 forceStopAndUnloadModelLocked(modelData, e);
Chris Thorntond0a83b82016-07-19 18:56:15 -0700721 } catch (RemoteException e) {
722 Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
723 }
Chris Thornton37f97762016-04-29 16:23:45 -0700724 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700725 }
726
727 private void onRecognitionFailureLocked() {
728 Slog.w(TAG, "Recognition failure");
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800729 MetricsLogger.count(mContext, "sth_recognition_failure_event", 1);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700730 try {
Chris Thornton23a57402017-06-10 17:31:57 -0700731 sendErrorCallbacksToAllLocked(STATUS_ERROR);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700732 } finally {
Arunesh Mishrafac25152016-04-10 20:35:16 -0700733 internalClearModelStateLocked();
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800734 internalClearGlobalStateLocked();
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700735 }
736 }
737
Arunesh Mishrafac25152016-04-10 20:35:16 -0700738 private int getKeyphraseIdFromEvent(KeyphraseRecognitionEvent event) {
739 if (event == null) {
740 Slog.w(TAG, "Null RecognitionEvent received.");
741 return INVALID_VALUE;
742 }
743 KeyphraseRecognitionExtra[] keyphraseExtras =
744 ((KeyphraseRecognitionEvent) event).keyphraseExtras;
745 if (keyphraseExtras == null || keyphraseExtras.length == 0) {
746 Slog.w(TAG, "Invalid keyphrase recognition event!");
747 return INVALID_VALUE;
748 }
749 // TODO: Handle more than one keyphrase extras.
750 return keyphraseExtras[0].id;
751 }
752
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800753 private void onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700754 Slog.i(TAG, "Recognition success");
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800755 MetricsLogger.count(mContext, "sth_keyphrase_recognition_event", 1);
Arunesh Mishrafac25152016-04-10 20:35:16 -0700756 int keyphraseId = getKeyphraseIdFromEvent(event);
757 ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800758
Arunesh Mishrafac25152016-04-10 20:35:16 -0700759 if (modelData == null || !modelData.isKeyphraseModel()) {
760 Slog.e(TAG, "Keyphase model data does not exist for ID:" + keyphraseId);
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800761 return;
762 }
763
Arunesh Mishrafac25152016-04-10 20:35:16 -0700764 if (modelData.getCallback() == null) {
765 Slog.w(TAG, "Received onRecognition event without callback for keyphrase model.");
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700766 return;
767 }
Chris Thornton2411a2c2017-03-22 13:52:50 -0700768 modelData.setStopped();
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700769
770 try {
Arunesh Mishrafac25152016-04-10 20:35:16 -0700771 modelData.getCallback().onKeyphraseDetected((KeyphraseRecognitionEvent) event);
Chris Thornton2411a2c2017-03-22 13:52:50 -0700772 } catch (DeadObjectException e) {
Chris Thornton23a57402017-06-10 17:31:57 -0700773 forceStopAndUnloadModelLocked(modelData, e);
Chris Thornton2411a2c2017-03-22 13:52:50 -0700774 return;
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700775 } catch (RemoteException e) {
Arunesh Mishraf47f1732016-02-18 16:16:12 -0800776 Slog.w(TAG, "RemoteException in onKeyphraseDetected", e);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700777 }
778
Arunesh Mishrafac25152016-04-10 20:35:16 -0700779 RecognitionConfig config = modelData.getRecognitionConfig();
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800780 if (config != null) {
781 // Whether we should continue by starting this again.
Arunesh Mishrafac25152016-04-10 20:35:16 -0700782 modelData.setRequested(config.allowMultipleTriggers);
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800783 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700784 // TODO: Remove this block if the lower layer supports multiple triggers.
Chris Thornton37f97762016-04-29 16:23:45 -0700785 if (modelData.isRequested()) {
Arunesh Mishrafac25152016-04-10 20:35:16 -0700786 updateRecognitionLocked(modelData, isRecognitionAllowed(), true /* notify */);
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800787 }
788 }
789
790 private void updateAllRecognitionsLocked(boolean notify) {
791 boolean isAllowed = isRecognitionAllowed();
Chris Thornton23a57402017-06-10 17:31:57 -0700792 // updateRecognitionLocked can possibly update the list of models
793 ArrayList<ModelData> modelDatas = new ArrayList<ModelData>(mModelDataMap.values());
794 for (ModelData modelData : modelDatas) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800795 updateRecognitionLocked(modelData, isAllowed, notify);
796 }
797 }
798
799 private int updateRecognitionLocked(ModelData model, boolean isAllowed,
800 boolean notify) {
Chris Thornton37f97762016-04-29 16:23:45 -0700801 boolean start = model.isRequested() && isAllowed;
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800802 if (start == model.isModelStarted()) {
803 // No-op.
804 return STATUS_OK;
805 }
806 if (start) {
807 return startRecognitionLocked(model, notify);
808 } else {
809 return stopRecognitionLocked(model, notify);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700810 }
811 }
812
813 private void onServiceDiedLocked() {
814 try {
Chris Thornton2411a2c2017-03-22 13:52:50 -0700815 MetricsLogger.count(mContext, "sth_service_died", 1);
Chris Thornton23a57402017-06-10 17:31:57 -0700816 sendErrorCallbacksToAllLocked(SoundTrigger.STATUS_DEAD_OBJECT);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700817 } finally {
Arunesh Mishrafac25152016-04-10 20:35:16 -0700818 internalClearModelStateLocked();
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800819 internalClearGlobalStateLocked();
Eric Laurent2475e382014-09-09 15:52:25 -0700820 if (mModule != null) {
821 mModule.detach();
822 mModule = null;
823 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700824 }
825 }
826
Arunesh Mishrafac25152016-04-10 20:35:16 -0700827 // internalClearGlobalStateLocked() cleans up the telephony and power save listeners.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800828 private void internalClearGlobalStateLocked() {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700829 // Unregister from call state changes.
830 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700831
832 // Unregister from power save mode changes.
833 if (mPowerSaveModeListener != null) {
834 mContext.unregisterReceiver(mPowerSaveModeListener);
835 mPowerSaveModeListener = null;
836 }
837 }
838
Arunesh Mishrafac25152016-04-10 20:35:16 -0700839 // Clears state for all models (generic and keyphrase).
840 private void internalClearModelStateLocked() {
841 for (ModelData modelData : mModelDataMap.values()) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800842 modelData.clearState();
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800843 }
844 }
845
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700846 class MyCallStateListener extends PhoneStateListener {
847 @Override
848 public void onCallStateChanged(int state, String arg1) {
849 if (DBG) Slog.d(TAG, "onCallStateChanged: " + state);
850 synchronized (mLock) {
851 onCallStateChangedLocked(TelephonyManager.CALL_STATE_IDLE != state);
852 }
853 }
854 }
855
856 class PowerSaveModeListener extends BroadcastReceiver {
857 @Override
858 public void onReceive(Context context, Intent intent) {
859 if (!PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) {
860 return;
861 }
jackqdyulei455e90a2017-02-09 15:29:16 -0800862 boolean active = mPowerManager.getPowerSaveState(ServiceType.SOUND)
863 .batterySaverEnabled;
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700864 if (DBG) Slog.d(TAG, "onPowerSaveModeChanged: " + active);
865 synchronized (mLock) {
866 onPowerSaveModeChangedLocked(active);
867 }
868 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700869 }
Sandeep Siddhartha6b8556d2014-08-06 19:47:25 -0700870
871 void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
872 synchronized (mLock) {
873 pw.print(" module properties=");
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800874 pw.println(mModuleProperties == null ? "null" : mModuleProperties);
Sandeep Siddhartha6b8556d2014-08-06 19:47:25 -0700875
Sandeep Siddhartha6b8556d2014-08-06 19:47:25 -0700876 pw.print(" call active="); pw.println(mCallActive);
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700877 pw.print(" power save mode active="); pw.println(mIsPowerSaveMode);
Sandeep Siddhartha6b8556d2014-08-06 19:47:25 -0700878 pw.print(" service disabled="); pw.println(mServiceDisabled);
879 }
880 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800881
882 private void initializeTelephonyAndPowerStateListeners() {
883 // Get the current call state synchronously for the first recognition.
884 mCallActive = mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE;
885
886 // Register for call state changes when the first call to start recognition occurs.
887 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
888
889 // Register for power saver mode changes when the first call to start recognition
890 // occurs.
891 if (mPowerSaveModeListener == null) {
892 mPowerSaveModeListener = new PowerSaveModeListener();
893 mContext.registerReceiver(mPowerSaveModeListener,
894 new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
895 }
jackqdyulei455e90a2017-02-09 15:29:16 -0800896 mIsPowerSaveMode = mPowerManager.getPowerSaveState(ServiceType.SOUND)
897 .batterySaverEnabled;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800898 }
899
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800900 // Sends an error callback to all models with a valid registered callback.
Chris Thornton23a57402017-06-10 17:31:57 -0700901 private void sendErrorCallbacksToAllLocked(int errorCode) {
Arunesh Mishrafac25152016-04-10 20:35:16 -0700902 for (ModelData modelData : mModelDataMap.values()) {
903 IRecognitionStatusCallback callback = modelData.getCallback();
904 if (callback != null) {
Chris Thornton2411a2c2017-03-22 13:52:50 -0700905 try {
906 callback.onError(errorCode);
907 } catch (RemoteException e) {
Chris Thornton23a57402017-06-10 17:31:57 -0700908 Slog.w(TAG, "RemoteException sendErrorCallbacksToAllLocked for model handle " +
Chris Thornton2411a2c2017-03-22 13:52:50 -0700909 modelData.getHandle(), e);
910 }
911 }
912 }
913 }
914
Chris Thorntona533b992017-03-24 17:30:29 -0700915 /**
916 * Stops and unloads a sound model, and removes any reference to the model if successful.
917 *
918 * @param modelData The model data to remove.
919 * @param exception Optional exception to print in logcat. May be null.
920 */
Chris Thornton23a57402017-06-10 17:31:57 -0700921 private void forceStopAndUnloadModelLocked(ModelData modelData, Exception exception) {
922 forceStopAndUnloadModelLocked(modelData, exception, null /* modelDataIterator */);
Chris Thorntona533b992017-03-24 17:30:29 -0700923 }
924
925 /**
926 * Stops and unloads a sound model, and removes any reference to the model if successful.
927 *
928 * @param modelData The model data to remove.
929 * @param exception Optional exception to print in logcat. May be null.
930 * @param modelDataIterator If this function is to be used while iterating over the
931 * mModelDataMap, you can provide the iterator for the current model data to be used to
932 * remove the modelData from the map. This avoids generating a
933 * ConcurrentModificationException, since this function will try and remove the model
934 * data from the mModelDataMap when it can successfully unload the model.
935 */
Chris Thornton23a57402017-06-10 17:31:57 -0700936 private void forceStopAndUnloadModelLocked(ModelData modelData, Exception exception,
Chris Thorntona533b992017-03-24 17:30:29 -0700937 Iterator modelDataIterator) {
Chris Thornton2411a2c2017-03-22 13:52:50 -0700938 if (exception != null) {
939 Slog.e(TAG, "forceStopAndUnloadModel", exception);
940 }
941 if (modelData.isModelStarted()) {
942 Slog.d(TAG, "Stopping previously started dangling model " + modelData.getHandle());
943 if (mModule.stopRecognition(modelData.getHandle()) != STATUS_OK) {
944 modelData.setStopped();
945 modelData.setRequested(false);
946 } else {
947 Slog.e(TAG, "Failed to stop model " + modelData.getHandle());
948 }
949 }
950 if (modelData.isModelLoaded()) {
951 Slog.d(TAG, "Unloading previously loaded dangling model " + modelData.getHandle());
952 if (mModule.unloadSoundModel(modelData.getHandle()) == STATUS_OK) {
953 // Remove the model data from existence.
Chris Thorntona533b992017-03-24 17:30:29 -0700954 if (modelDataIterator != null) {
955 modelDataIterator.remove();
956 } else {
957 mModelDataMap.remove(modelData.getModelId());
958 }
Chris Thornton2411a2c2017-03-22 13:52:50 -0700959 Iterator it = mKeyphraseUuidMap.entrySet().iterator();
960 while (it.hasNext()) {
961 Map.Entry pair = (Map.Entry) it.next();
962 if (pair.getValue().equals(modelData.getModelId())) {
963 it.remove();
964 }
965 }
966 modelData.clearState();
967 } else {
968 Slog.e(TAG, "Failed to unload model " + modelData.getHandle());
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800969 }
970 }
971 }
972
Chris Thorntona533b992017-03-24 17:30:29 -0700973 private void stopAndUnloadDeadModelsLocked() {
974 Iterator it = mModelDataMap.entrySet().iterator();
975 while (it.hasNext()) {
976 ModelData modelData = (ModelData) ((Map.Entry) it.next()).getValue();
977 if (!modelData.isModelLoaded()) {
978 continue;
979 }
980 if (modelData.getCallback() == null
981 || (modelData.getCallback().asBinder() != null
982 && !modelData.getCallback().asBinder().pingBinder())) {
983 // No one is listening on this model, so we might as well evict it.
984 Slog.w(TAG, "Removing model " + modelData.getHandle() + " that has no clients");
Chris Thornton23a57402017-06-10 17:31:57 -0700985 forceStopAndUnloadModelLocked(modelData, null /* exception */, it);
Chris Thorntona533b992017-03-24 17:30:29 -0700986 }
987 }
988 }
989
Ryan Bavetta79655eb2016-03-07 14:34:51 -0800990 private ModelData getOrCreateGenericModelDataLocked(UUID modelId) {
Arunesh Mishrafac25152016-04-10 20:35:16 -0700991 ModelData modelData = mModelDataMap.get(modelId);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800992 if (modelData == null) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -0800993 modelData = ModelData.createGenericModelData(modelId);
Arunesh Mishrafac25152016-04-10 20:35:16 -0700994 mModelDataMap.put(modelId, modelData);
995 } else if (!modelData.isGenericModel()) {
996 Slog.e(TAG, "UUID already used for non-generic model.");
997 return null;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800998 }
999 return modelData;
1000 }
1001
Arunesh Mishrafac25152016-04-10 20:35:16 -07001002 private void removeKeyphraseModelLocked(int keyphraseId) {
1003 UUID uuid = mKeyphraseUuidMap.get(keyphraseId);
1004 if (uuid == null) {
1005 return;
1006 }
1007 mModelDataMap.remove(uuid);
1008 mKeyphraseUuidMap.remove(keyphraseId);
1009 }
1010
1011 private ModelData getKeyphraseModelDataLocked(int keyphraseId) {
1012 UUID uuid = mKeyphraseUuidMap.get(keyphraseId);
1013 if (uuid == null) {
1014 return null;
1015 }
1016 return mModelDataMap.get(uuid);
1017 }
1018
1019 // Use this to create a new ModelData entry for a keyphrase Id. It will overwrite existing
1020 // mapping if one exists.
1021 private ModelData createKeyphraseModelDataLocked(UUID modelId, int keyphraseId) {
1022 mKeyphraseUuidMap.remove(keyphraseId);
1023 mModelDataMap.remove(modelId);
1024 mKeyphraseUuidMap.put(keyphraseId, modelId);
1025 ModelData modelData = ModelData.createKeyphraseModelData(modelId);
1026 mModelDataMap.put(modelId, modelData);
1027 return modelData;
1028 }
1029
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001030 // Instead of maintaining a second hashmap of modelHandle -> ModelData, we just
1031 // iterate through to find the right object (since we don't expect 100s of models
1032 // to be stored).
Ryan Bavetta79655eb2016-03-07 14:34:51 -08001033 private ModelData getModelDataForLocked(int modelHandle) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001034 // Fetch ModelData object corresponding to the model handle.
Arunesh Mishrafac25152016-04-10 20:35:16 -07001035 for (ModelData model : mModelDataMap.values()) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001036 if (model.getHandle() == modelHandle) {
1037 return model;
1038 }
1039 }
1040 return null;
1041 }
1042
1043 // Whether we are allowed to run any recognition at all. The conditions that let us run
1044 // a recognition include: no active phone call or not being in a power save mode. Also,
1045 // the native service should be enabled.
1046 private boolean isRecognitionAllowed() {
1047 return !mCallActive && !mServiceDisabled && !mIsPowerSaveMode;
1048 }
1049
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001050 // A single routine that implements the start recognition logic for both generic and keyphrase
1051 // models.
1052 private int startRecognitionLocked(ModelData modelData, boolean notify) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001053 IRecognitionStatusCallback callback = modelData.getCallback();
1054 int handle = modelData.getHandle();
1055 RecognitionConfig config = modelData.getRecognitionConfig();
1056 if (callback == null || handle == INVALID_VALUE || config == null) {
1057 // Nothing to do here.
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001058 Slog.w(TAG, "startRecognition: Bad data passed in.");
1059 MetricsLogger.count(mContext, "sth_start_recognition_error", 1);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001060 return STATUS_ERROR;
1061 }
1062
1063 if (!isRecognitionAllowed()) {
1064 // Nothing to do here.
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001065 Slog.w(TAG, "startRecognition requested but not allowed.");
1066 MetricsLogger.count(mContext, "sth_start_recognition_not_allowed", 1);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001067 return STATUS_OK;
1068 }
1069
1070 int status = mModule.startRecognition(handle, config);
1071 if (status != SoundTrigger.STATUS_OK) {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001072 Slog.w(TAG, "startRecognition failed with " + status);
1073 MetricsLogger.count(mContext, "sth_start_recognition_error", 1);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001074 // Notify of error if needed.
1075 if (notify) {
1076 try {
1077 callback.onError(status);
Chris Thornton2411a2c2017-03-22 13:52:50 -07001078 } catch (DeadObjectException e) {
Chris Thornton23a57402017-06-10 17:31:57 -07001079 forceStopAndUnloadModelLocked(modelData, e);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001080 } catch (RemoteException e) {
1081 Slog.w(TAG, "RemoteException in onError", e);
1082 }
1083 }
1084 } else {
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001085 Slog.i(TAG, "startRecognition successful.");
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001086 MetricsLogger.count(mContext, "sth_start_recognition_success", 1);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001087 modelData.setStarted();
1088 // Notify of resume if needed.
1089 if (notify) {
1090 try {
1091 callback.onRecognitionResumed();
Chris Thornton2411a2c2017-03-22 13:52:50 -07001092 } catch (DeadObjectException e) {
Chris Thornton23a57402017-06-10 17:31:57 -07001093 forceStopAndUnloadModelLocked(modelData, e);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001094 } catch (RemoteException e) {
1095 Slog.w(TAG, "RemoteException in onRecognitionResumed", e);
1096 }
1097 }
1098 }
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001099 if (DBG) {
1100 Slog.d(TAG, "Model being started :" + modelData.toString());
1101 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001102 return status;
1103 }
1104
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001105 private int stopRecognitionLocked(ModelData modelData, boolean notify) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001106 IRecognitionStatusCallback callback = modelData.getCallback();
1107
Chris Thornton37f97762016-04-29 16:23:45 -07001108 // Stop recognition.
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001109 int status = STATUS_OK;
1110
Chris Thornton37f97762016-04-29 16:23:45 -07001111 status = mModule.stopRecognition(modelData.getHandle());
1112
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001113 if (status != SoundTrigger.STATUS_OK) {
1114 Slog.w(TAG, "stopRecognition call failed with " + status);
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001115 MetricsLogger.count(mContext, "sth_stop_recognition_error", 1);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001116 if (notify) {
1117 try {
1118 callback.onError(status);
Chris Thornton2411a2c2017-03-22 13:52:50 -07001119 } catch (DeadObjectException e) {
Chris Thornton23a57402017-06-10 17:31:57 -07001120 forceStopAndUnloadModelLocked(modelData, e);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001121 } catch (RemoteException e) {
1122 Slog.w(TAG, "RemoteException in onError", e);
1123 }
1124 }
1125 } else {
1126 modelData.setStopped();
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001127 MetricsLogger.count(mContext, "sth_stop_recognition_success", 1);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001128 // Notify of pause if needed.
1129 if (notify) {
1130 try {
1131 callback.onRecognitionPaused();
Chris Thornton2411a2c2017-03-22 13:52:50 -07001132 } catch (DeadObjectException e) {
Chris Thornton23a57402017-06-10 17:31:57 -07001133 forceStopAndUnloadModelLocked(modelData, e);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001134 } catch (RemoteException e) {
1135 Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
1136 }
1137 }
1138 }
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001139 if (DBG) {
1140 Slog.d(TAG, "Model being stopped :" + modelData.toString());
1141 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001142 return status;
1143 }
1144
Arunesh Mishrafac25152016-04-10 20:35:16 -07001145 private void dumpModelStateLocked() {
1146 for (UUID modelId : mModelDataMap.keySet()) {
1147 ModelData modelData = mModelDataMap.get(modelId);
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001148 Slog.i(TAG, "Model :" + modelData.toString());
1149 }
1150 }
1151
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001152 // Computes whether we have any recognition running at all (voice or generic). Sets
1153 // the mRecognitionRunning variable with the result.
Ryan Bavetta79655eb2016-03-07 14:34:51 -08001154 private boolean computeRecognitionRunningLocked() {
1155 if (mModuleProperties == null || mModule == null) {
1156 mRecognitionRunning = false;
1157 return mRecognitionRunning;
1158 }
Arunesh Mishrafac25152016-04-10 20:35:16 -07001159 for (ModelData modelData : mModelDataMap.values()) {
Ryan Bavetta79655eb2016-03-07 14:34:51 -08001160 if (modelData.isModelStarted()) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001161 mRecognitionRunning = true;
1162 return mRecognitionRunning;
1163 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001164 }
Ryan Bavetta79655eb2016-03-07 14:34:51 -08001165 mRecognitionRunning = false;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001166 return mRecognitionRunning;
1167 }
1168
1169 // This class encapsulates the callbacks, state, handles and any other information that
1170 // represents a model.
1171 private static class ModelData {
1172 // Model not loaded (and hence not started).
1173 static final int MODEL_NOTLOADED = 0;
1174
1175 // Loaded implies model was successfully loaded. Model not started yet.
1176 static final int MODEL_LOADED = 1;
1177
1178 // Started implies model was successfully loaded and start was called.
1179 static final int MODEL_STARTED = 2;
1180
1181 // One of MODEL_NOTLOADED, MODEL_LOADED, MODEL_STARTED (which implies loaded).
1182 private int mModelState;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001183 private UUID mModelId;
1184
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001185 // mRequested captures the explicit intent that a start was requested for this model. We
1186 // continue to capture and retain this state even after the model gets started, so that we
1187 // know when a model gets stopped due to "other" reasons, that we should start it again.
1188 // This was the intended behavior of the "mRequested" variable in the previous version of
1189 // this code that we are replicating here.
1190 //
1191 // The "other" reasons include power save, abort being called from the lower layer (due
1192 // to concurrent capture not being supported) and phone call state. Once we recover from
1193 // these transient disruptions, we would start such models again where mRequested == true.
1194 // Thus, mRequested gets reset only when there is an explicit intent to stop the model
1195 // coming from the SoundTriggerService layer that uses this class (and thus eventually
1196 // from the app that manages this model).
1197 private boolean mRequested = false;
1198
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001199 // One of SoundModel.TYPE_GENERIC or SoundModel.TYPE_KEYPHRASE. Initially set
1200 // to SoundModel.TYPE_UNKNOWN;
1201 private int mModelType = SoundModel.TYPE_UNKNOWN;
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001202
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001203 private IRecognitionStatusCallback mCallback = null;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001204 private RecognitionConfig mRecognitionConfig = null;
1205
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001206 // Model handle is an integer used by the HAL as an identifier for sound
1207 // models.
1208 private int mModelHandle = INVALID_VALUE;
1209
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001210 // The SoundModel instance, one of KeyphraseSoundModel or GenericSoundModel.
1211 private SoundModel mSoundModel = null;
1212
1213 private ModelData(UUID modelId, int modelType) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001214 mModelId = modelId;
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001215 // Private constructor, since we require modelType to be one of TYPE_GENERIC,
1216 // TYPE_KEYPHRASE or TYPE_UNKNOWN.
1217 mModelType = modelType;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001218 }
1219
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001220 static ModelData createKeyphraseModelData(UUID modelId) {
1221 return new ModelData(modelId, SoundModel.TYPE_KEYPHRASE);
1222 }
1223
1224 static ModelData createGenericModelData(UUID modelId) {
1225 return new ModelData(modelId, SoundModel.TYPE_GENERIC_SOUND);
1226 }
1227
1228 // Note that most of the functionality in this Java class will not work for
1229 // SoundModel.TYPE_UNKNOWN nevertheless we have it since lower layers support it.
1230 static ModelData createModelDataOfUnknownType(UUID modelId) {
1231 return new ModelData(modelId, SoundModel.TYPE_UNKNOWN);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001232 }
1233
1234 synchronized void setCallback(IRecognitionStatusCallback callback) {
1235 mCallback = callback;
1236 }
1237
1238 synchronized IRecognitionStatusCallback getCallback() {
1239 return mCallback;
1240 }
1241
1242 synchronized boolean isModelLoaded() {
Arunesh Mishra933da812016-02-18 15:31:54 -08001243 return (mModelState == MODEL_LOADED || mModelState == MODEL_STARTED);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001244 }
1245
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001246 synchronized boolean isModelNotLoaded() {
1247 return mModelState == MODEL_NOTLOADED;
1248 }
1249
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001250 synchronized void setStarted() {
1251 mModelState = MODEL_STARTED;
1252 }
1253
1254 synchronized void setStopped() {
1255 mModelState = MODEL_LOADED;
1256 }
1257
Arunesh Mishra933da812016-02-18 15:31:54 -08001258 synchronized void setLoaded() {
1259 mModelState = MODEL_LOADED;
1260 }
1261
1262 synchronized boolean isModelStarted() {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001263 return mModelState == MODEL_STARTED;
1264 }
1265
1266 synchronized void clearState() {
1267 mModelState = MODEL_NOTLOADED;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001268 mModelHandle = INVALID_VALUE;
Chris Thornton41e04a42016-04-18 16:51:08 -07001269 mRecognitionConfig = null;
1270 mRequested = false;
1271 mCallback = null;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001272 }
1273
1274 synchronized void clearCallback() {
1275 mCallback = null;
1276 }
1277
1278 synchronized void setHandle(int handle) {
1279 mModelHandle = handle;
1280 }
1281
1282 synchronized void setRecognitionConfig(RecognitionConfig config) {
1283 mRecognitionConfig = config;
1284 }
1285
1286 synchronized int getHandle() {
1287 return mModelHandle;
1288 }
1289
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001290 synchronized UUID getModelId() {
1291 return mModelId;
1292 }
1293
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001294 synchronized RecognitionConfig getRecognitionConfig() {
1295 return mRecognitionConfig;
1296 }
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001297
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001298 // Whether a start recognition was requested.
Chris Thornton37f97762016-04-29 16:23:45 -07001299 synchronized boolean isRequested() {
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001300 return mRequested;
1301 }
1302
1303 synchronized void setRequested(boolean requested) {
1304 mRequested = requested;
1305 }
1306
1307 synchronized void setSoundModel(SoundModel soundModel) {
1308 mSoundModel = soundModel;
1309 }
1310
1311 synchronized SoundModel getSoundModel() {
1312 return mSoundModel;
1313 }
1314
1315 synchronized int getModelType() {
1316 return mModelType;
1317 }
1318
1319 synchronized boolean isKeyphraseModel() {
1320 return mModelType == SoundModel.TYPE_KEYPHRASE;
1321 }
1322
Arunesh Mishrafac25152016-04-10 20:35:16 -07001323 synchronized boolean isGenericModel() {
1324 return mModelType == SoundModel.TYPE_GENERIC_SOUND;
1325 }
1326
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001327 synchronized String stateToString() {
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001328 switch(mModelState) {
1329 case MODEL_NOTLOADED: return "NOT_LOADED";
1330 case MODEL_LOADED: return "LOADED";
1331 case MODEL_STARTED: return "STARTED";
1332 }
1333 return "Unknown state";
1334 }
1335
Arunesh Mishra78ab7cf2016-03-03 22:52:36 -08001336 synchronized String requestedToString() {
1337 return "Requested: " + (mRequested ? "Yes" : "No");
1338 }
1339
1340 synchronized String callbackToString() {
1341 return "Callback: " + (mCallback != null ? mCallback.asBinder() : "null");
1342 }
1343
1344 synchronized String uuidToString() {
1345 return "UUID: " + mModelId;
1346 }
1347
1348 synchronized public String toString() {
1349 return "Handle: " + mModelHandle + "\n" +
1350 "ModelState: " + stateToString() + "\n" +
1351 requestedToString() + "\n" +
1352 callbackToString() + "\n" +
Arunesh Mishrafac25152016-04-10 20:35:16 -07001353 uuidToString() + "\n" + modelTypeToString();
1354 }
1355
1356 synchronized String modelTypeToString() {
1357 String type = null;
1358 switch (mModelType) {
1359 case SoundModel.TYPE_GENERIC_SOUND: type = "Generic"; break;
1360 case SoundModel.TYPE_UNKNOWN: type = "Unknown"; break;
1361 case SoundModel.TYPE_KEYPHRASE: type = "Keyphrase"; break;
1362 }
1363 return "Model type: " + type + "\n";
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001364 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001365 }
Sandeepd7018202014-07-10 15:15:39 -07001366}