blob: 40687b01ceec8d55263d865220f7b96eff8d2b8d [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
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080019import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
20
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -070021import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
Sandeep Siddhartha05589722014-07-17 16:21:54 -070025import android.hardware.soundtrigger.IRecognitionStatusCallback;
26import android.hardware.soundtrigger.SoundTrigger;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080027import android.hardware.soundtrigger.SoundTrigger.GenericRecognitionEvent;
28import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
Sandeep Siddhartha05589722014-07-17 16:21:54 -070029import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
Sandeep Siddhartha68173372014-07-28 13:25:30 -070030import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
31import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
Sandeep Siddhartha05589722014-07-17 16:21:54 -070032import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
Sandeepd7018202014-07-10 15:15:39 -070033import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -070034import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
Sandeepd7018202014-07-10 15:15:39 -070035import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080036import android.hardware.soundtrigger.SoundTrigger.SoundModel;
Eric Laurentd3b82232014-07-30 08:57:39 -070037import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
Sandeep Siddhartha05589722014-07-17 16:21:54 -070038import android.hardware.soundtrigger.SoundTriggerModule;
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -070039import android.os.PowerManager;
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;
Sandeepd7018202014-07-10 15:15:39 -070044
Sandeep Siddhartha6b8556d2014-08-06 19:47:25 -070045import java.io.FileDescriptor;
46import java.io.PrintWriter;
Sandeepd7018202014-07-10 15:15:39 -070047import java.util.ArrayList;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080048import java.util.HashMap;
49import java.util.UUID;
Sandeepd7018202014-07-10 15:15:39 -070050
51/**
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080052 * Helper for {@link SoundTrigger} APIs. Supports two types of models:
53 * (i) A voice model which is exported via the {@link VoiceInteractionService}. There can only be
54 * a single voice model running on the DSP at any given time.
55 *
56 * (ii) Generic sound-trigger models: Supports multiple of these.
57 *
Sandeepd7018202014-07-10 15:15:39 -070058 * Currently this just acts as an abstraction over all SoundTrigger API calls.
59 *
60 * @hide
61 */
62public class SoundTriggerHelper implements SoundTrigger.StatusListener {
63 static final String TAG = "SoundTriggerHelper";
Sandeep Siddhartha8cf8f712014-09-15 12:51:08 -070064 static final boolean DBG = false;
Sandeepd7018202014-07-10 15:15:39 -070065
66 /**
Sandeep Siddhartha05589722014-07-17 16:21:54 -070067 * Return codes for {@link #startRecognition(int, KeyphraseSoundModel,
68 * IRecognitionStatusCallback, RecognitionConfig)},
69 * {@link #stopRecognition(int, IRecognitionStatusCallback)}
Sandeepd7018202014-07-10 15:15:39 -070070 */
Sandeep Siddhartha05589722014-07-17 16:21:54 -070071 public static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR;
72 public static final int STATUS_OK = SoundTrigger.STATUS_OK;
Sandeepd7018202014-07-10 15:15:39 -070073
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -070074 private static final int INVALID_VALUE = Integer.MIN_VALUE;
Sandeepd7018202014-07-10 15:15:39 -070075
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -070076 /** The {@link ModuleProperties} for the system, or null if none exists. */
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080077 final ModuleProperties mModuleProperties;
Sandeepd7018202014-07-10 15:15:39 -070078
79 /** The properties for the DSP module */
Eric Laurent2475e382014-09-09 15:52:25 -070080 private SoundTriggerModule mModule;
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -070081 private final Object mLock = new Object();
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -070082 private final Context mContext;
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -070083 private final TelephonyManager mTelephonyManager;
84 private final PhoneStateListener mPhoneStateListener;
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -070085 private final PowerManager mPowerManager;
Sandeepd7018202014-07-10 15:15:39 -070086
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080087 // TODO: Since the voice layer currently only handles one recognition
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -070088 // we simplify things by assuming one listener here too.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080089 private IRecognitionStatusCallback mKeyphraseListener;
90
91 // The SoundTriggerManager layer handles multiple generic recognition models. We store the
92 // ModelData here in a hashmap.
93 private final HashMap<UUID, ModelData> mGenericModelDataMap;
94
95 // Note: KeyphraseId is not really used.
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -070096 private int mKeyphraseId = INVALID_VALUE;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080097
98 // Current voice sound model handle. We only allow one voice model to run at any given time.
99 private int mCurrentKeyphraseModelHandle = INVALID_VALUE;
Sandeep Siddhartha45c00b52014-10-16 16:17:11 -0700100 private KeyphraseSoundModel mCurrentSoundModel = null;
Sandeep Siddhartha2c0273e2014-08-01 11:32:03 -0700101 // FIXME: Ideally this should not be stored if allowMultipleTriggers happens at a lower layer.
102 private RecognitionConfig mRecognitionConfig = null;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800103
104 // Whether we are requesting recognition to start.
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700105 private boolean mRequested = false;
106 private boolean mCallActive = false;
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700107 private boolean mIsPowerSaveMode = false;
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700108 // Indicates if the native sound trigger service is disabled or not.
109 // This is an indirect indication of the microphone being open in some other application.
110 private boolean mServiceDisabled = false;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800111
112 // Whether we have ANY recognition (keyphrase or generic) running.
113 private boolean mRecognitionRunning = false;
114
115 // Keeps track of whether the keyphrase recognition is running.
116 private boolean mKeyphraseStarted = false;
Chris Thorntonb7b58122015-10-06 16:48:12 -0700117 private boolean mRecognitionAborted = false;
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700118 private PowerSaveModeListener mPowerSaveModeListener;
Sandeepd7018202014-07-10 15:15:39 -0700119
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700120 SoundTriggerHelper(Context context) {
Sandeepd7018202014-07-10 15:15:39 -0700121 ArrayList <ModuleProperties> modules = new ArrayList<>();
122 int status = SoundTrigger.listModules(modules);
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700123 mContext = context;
124 mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
125 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800126 mGenericModelDataMap = new HashMap<UUID, ModelData>();
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700127 mPhoneStateListener = new MyCallStateListener();
Sandeepd7018202014-07-10 15:15:39 -0700128 if (status != SoundTrigger.STATUS_OK || modules.size() == 0) {
Sandeep Siddharthaa433af62014-07-16 15:36:44 -0700129 Slog.w(TAG, "listModules status=" + status + ", # of modules=" + modules.size());
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800130 mModuleProperties = null;
Sandeepd7018202014-07-10 15:15:39 -0700131 mModule = null;
132 } else {
133 // TODO: Figure out how to determine which module corresponds to the DSP hardware.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800134 mModuleProperties = modules.get(0);
135 }
136 }
137
138 /**
139 * Starts recognition for the given generic sound model ID.
140 *
141 * @param soundModel The sound model to use for recognition.
142 * @param listener The listener for the recognition events related to the given keyphrase.
143 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
144 */
145 int startGenericRecognition(UUID modelId, GenericSoundModel soundModel,
146 IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) {
147 if (soundModel == null || callback == null || recognitionConfig == null) {
148 Slog.w(TAG, "Passed in bad data to startGenericRecognition().");
149 return STATUS_ERROR;
150 }
151
152 synchronized (mLock) {
153
154 if (mModuleProperties == null) {
155 Slog.w(TAG, "Attempting startRecognition without the capability");
156 return STATUS_ERROR;
157 }
158
159 if (mModule == null) {
160 mModule = SoundTrigger.attachModule(mModuleProperties.id, this, null);
161 if (mModule == null) {
162 Slog.w(TAG, "startRecognition cannot attach to sound trigger module");
163 return STATUS_ERROR;
164 }
165 }
166
167 // Initialize power save, call active state monitoring logic.
168 if (!mRecognitionRunning) {
169 initializeTelephonyAndPowerStateListeners();
170 }
171
172 // Fetch a ModelData instance from the hash map. Creates a new one if none
173 // exists.
Ryan Bavetta79655eb2016-03-07 14:34:51 -0800174 ModelData modelData = getOrCreateGenericModelDataLocked(modelId);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800175
176 IRecognitionStatusCallback oldCallback = modelData.getCallback();
177 if (oldCallback != null) {
178 Slog.w(TAG, "Canceling previous recognition for model id: " + modelId);
179 try {
180 oldCallback.onError(STATUS_ERROR);
181 } catch (RemoteException e) {
182 Slog.w(TAG, "RemoteException in onDetectionStopped", e);
183 }
184 modelData.clearCallback();
185 }
186
187 // Load the model if its not loaded.
188 if (!modelData.isModelLoaded()) {
189 // Load the model
190 int[] handle = new int[] { INVALID_VALUE };
191 int status = mModule.loadSoundModel(soundModel, handle);
192 if (status != SoundTrigger.STATUS_OK) {
193 Slog.w(TAG, "loadSoundModel call failed with " + status);
194 return status;
195 }
196 if (handle[0] == INVALID_VALUE) {
197 Slog.w(TAG, "loadSoundModel call returned invalid sound model handle");
198 return STATUS_ERROR;
199 }
200 modelData.setHandle(handle[0]);
Arunesh Mishra933da812016-02-18 15:31:54 -0800201 modelData.setLoaded();
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800202 Slog.d(TAG, "Generic sound model loaded with handle:" + handle[0]);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800203 }
204 modelData.setCallback(callback);
205 modelData.setRecognitionConfig(recognitionConfig);
206
207 // Don't notify for synchronous calls.
208 return startGenericRecognitionLocked(modelData, false);
Sandeepd7018202014-07-10 15:15:39 -0700209 }
210 }
211
212 /**
Sandeep Siddhartha05589722014-07-17 16:21:54 -0700213 * Starts recognition for the given keyphraseId.
Sandeepd7018202014-07-10 15:15:39 -0700214 *
215 * @param keyphraseId The identifier of the keyphrase for which
216 * the recognition is to be started.
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -0700217 * @param soundModel The sound model to use for recognition.
Sandeepd7018202014-07-10 15:15:39 -0700218 * @param listener The listener for the recognition events related to the given keyphrase.
219 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
220 */
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800221 int startKeyphraseRecognition(int keyphraseId,
Sandeep Siddhartha05589722014-07-17 16:21:54 -0700222 KeyphraseSoundModel soundModel,
223 IRecognitionStatusCallback listener,
224 RecognitionConfig recognitionConfig) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700225 if (soundModel == null || listener == null || recognitionConfig == null) {
Sandeepd7018202014-07-10 15:15:39 -0700226 return STATUS_ERROR;
227 }
228
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700229 synchronized (mLock) {
230 if (DBG) {
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800231 Slog.d(TAG, "startKeyphraseRecognition for keyphraseId=" + keyphraseId
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700232 + " soundModel=" + soundModel + ", listener=" + listener.asBinder()
233 + ", recognitionConfig=" + recognitionConfig);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800234 Slog.d(TAG, "moduleProperties=" + mModuleProperties);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700235 Slog.d(TAG, "current listener="
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800236 + (mKeyphraseListener == null ? "null" : mKeyphraseListener.asBinder()));
237 Slog.d(TAG, "current SoundModel handle=" + mCurrentKeyphraseModelHandle);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700238 Slog.d(TAG, "current SoundModel UUID="
Sandeep Siddhartha45c00b52014-10-16 16:17:11 -0700239 + (mCurrentSoundModel == null ? null : mCurrentSoundModel.uuid));
Sandeep Siddhartha299efbe2014-07-30 13:40:56 -0700240 }
Sandeep Siddhartha110f5692014-07-20 12:22:56 -0700241
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800242 if (!mRecognitionRunning) {
243 initializeTelephonyAndPowerStateListeners();
Sandeep Siddhartha05589722014-07-17 16:21:54 -0700244 }
Sandeepd7018202014-07-10 15:15:39 -0700245
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800246 if (mModuleProperties == null) {
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800247 Slog.w(TAG, "Attempting startKeyphraseRecognition without the capability");
Sandeep Siddhartha299efbe2014-07-30 13:40:56 -0700248 return STATUS_ERROR;
249 }
Eric Laurent2475e382014-09-09 15:52:25 -0700250 if (mModule == null) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800251 mModule = SoundTrigger.attachModule(mModuleProperties.id, this, null);
Eric Laurent2475e382014-09-09 15:52:25 -0700252 if (mModule == null) {
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800253 Slog.w(TAG, "startKeyphraseRecognition cannot attach to sound trigger module");
Eric Laurent2475e382014-09-09 15:52:25 -0700254 return STATUS_ERROR;
255 }
256 }
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -0700257
Sandeep Siddhartha8cf8f712014-09-15 12:51:08 -0700258 // Unload the previous model if the current one isn't invalid
Sandeep Siddhartha45c00b52014-10-16 16:17:11 -0700259 // and, it's not the same as the new one.
260 // This helps use cache and reuse the model and just start/stop it when necessary.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800261 if (mCurrentKeyphraseModelHandle != INVALID_VALUE
Sandeep Siddhartha45c00b52014-10-16 16:17:11 -0700262 && !soundModel.equals(mCurrentSoundModel)) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700263 Slog.w(TAG, "Unloading previous sound model");
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800264 int status = mModule.unloadSoundModel(mCurrentKeyphraseModelHandle);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700265 if (status != SoundTrigger.STATUS_OK) {
266 Slog.w(TAG, "unloadSoundModel call failed with " + status);
267 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800268 internalClearKeyphraseSoundModelLocked();
269 mKeyphraseStarted = false;
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700270 }
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -0700271
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700272 // If the previous recognition was by a different listener,
273 // Notify them that it was stopped.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800274 if (mKeyphraseListener != null && mKeyphraseListener.asBinder() != listener.asBinder()) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700275 Slog.w(TAG, "Canceling previous recognition");
276 try {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800277 mKeyphraseListener.onError(STATUS_ERROR);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700278 } catch (RemoteException e) {
Sandeep Siddhartha6df952e2014-08-08 15:28:46 -0700279 Slog.w(TAG, "RemoteException in onDetectionStopped", e);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700280 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800281 mKeyphraseListener = null;
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700282 }
283
284 // Load the sound model if the current one is null.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800285 int soundModelHandle = mCurrentKeyphraseModelHandle;
286 if (mCurrentKeyphraseModelHandle == INVALID_VALUE
Sandeep Siddhartha45c00b52014-10-16 16:17:11 -0700287 || mCurrentSoundModel == null) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700288 int[] handle = new int[] { INVALID_VALUE };
289 int status = mModule.loadSoundModel(soundModel, handle);
290 if (status != SoundTrigger.STATUS_OK) {
291 Slog.w(TAG, "loadSoundModel call failed with " + status);
292 return status;
293 }
294 if (handle[0] == INVALID_VALUE) {
295 Slog.w(TAG, "loadSoundModel call returned invalid sound model handle");
296 return STATUS_ERROR;
297 }
298 soundModelHandle = handle[0];
299 } else {
300 if (DBG) Slog.d(TAG, "Reusing previously loaded sound model");
301 }
302
303 // Start the recognition.
304 mRequested = true;
305 mKeyphraseId = keyphraseId;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800306 mCurrentKeyphraseModelHandle = soundModelHandle;
Sandeep Siddhartha45c00b52014-10-16 16:17:11 -0700307 mCurrentSoundModel = soundModel;
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700308 mRecognitionConfig = recognitionConfig;
309 // Register the new listener. This replaces the old one.
310 // There can only be a maximum of one active listener at any given time.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800311 mKeyphraseListener = listener;
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700312
313 return updateRecognitionLocked(false /* don't notify for synchronous calls */);
314 }
Sandeepd7018202014-07-10 15:15:39 -0700315 }
316
317 /**
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800318 * Stops recognition for the given generic sound model.
319 *
320 * @param modelId The identifier of the generic sound model for which
321 * the recognition is to be stopped.
322 * @param listener The listener for the recognition events related to the given sound model.
323 *
324 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
325 */
326 int stopGenericRecognition(UUID modelId, IRecognitionStatusCallback listener) {
327 if (listener == null) {
328 return STATUS_ERROR;
329 }
330
331 synchronized (mLock) {
332 ModelData modelData = mGenericModelDataMap.get(modelId);
333 if (modelData == null) {
334 Slog.w(TAG, "Attempting stopRecognition on invalid model with id:" + modelId);
335 return STATUS_ERROR;
336 }
337
338 IRecognitionStatusCallback currentCallback = modelData.getCallback();
339 if (DBG) {
340 Slog.d(TAG, "stopRecognition for modelId=" + modelId
341 + ", listener=" + listener.asBinder());
342 Slog.d(TAG, "current callback ="
343 + (currentCallback == null ? "null" : currentCallback.asBinder()));
344 }
345
346 if (mModuleProperties == null || mModule == null) {
347 Slog.w(TAG, "Attempting stopRecognition without the capability");
348 return STATUS_ERROR;
349 }
350
Arunesh Mishra933da812016-02-18 15:31:54 -0800351 if (currentCallback == null || !modelData.isModelStarted()) {
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800352 // startGenericRecognition hasn't been called or it failed.
353 Slog.w(TAG, "Attempting stopGenericRecognition without a successful" +
354 " startGenericRecognition");
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800355 return STATUS_ERROR;
356 }
357 if (currentCallback.asBinder() != listener.asBinder()) {
358 // We don't allow a different listener to stop the recognition than the one
359 // that started it.
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800360 Slog.w(TAG, "Attempting stopGenericRecognition for another recognition");
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800361 return STATUS_ERROR;
362 }
363
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800364 int status = stopGenericRecognitionLocked(modelData,
365 false /* don't notify for synchronous calls */);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800366 if (status != SoundTrigger.STATUS_OK) {
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800367 Slog.w(TAG, "stopGenericRecognition failed: " + status);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800368 return status;
369 }
370
371 // We leave the sound model loaded but not started, this helps us when we start
372 // back.
373 // Also clear the internal state once the recognition has been stopped.
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800374 modelData.setLoaded();
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800375 modelData.clearCallback();
Ryan Bavetta79655eb2016-03-07 14:34:51 -0800376 if (!computeRecognitionRunningLocked()) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800377 internalClearGlobalStateLocked();
378 }
379 return status;
380 }
381 }
382
383 /**
Sandeep Siddhartha05589722014-07-17 16:21:54 -0700384 * Stops recognition for the given {@link Keyphrase} if a recognition is
385 * currently active.
Sandeepd7018202014-07-10 15:15:39 -0700386 *
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -0700387 * @param keyphraseId The identifier of the keyphrase for which
388 * the recognition is to be stopped.
389 * @param listener The listener for the recognition events related to the given keyphrase.
390 *
Sandeepd7018202014-07-10 15:15:39 -0700391 * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
392 */
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800393 int stopKeyphraseRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
Sandeep Siddhartha110f5692014-07-20 12:22:56 -0700394 if (listener == null) {
Sandeep Siddhartha110f5692014-07-20 12:22:56 -0700395 return STATUS_ERROR;
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700396 }
397
398 synchronized (mLock) {
399 if (DBG) {
400 Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId
401 + ", listener=" + listener.asBinder());
402 Slog.d(TAG, "current listener="
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800403 + (mKeyphraseListener == null ? "null" : mKeyphraseListener.asBinder()));
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700404 }
405
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800406 if (mModuleProperties == null || mModule == null) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700407 Slog.w(TAG, "Attempting stopRecognition without the capability");
408 return STATUS_ERROR;
409 }
410
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800411 if (mKeyphraseListener == null) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700412 // startRecognition hasn't been called or it failed.
413 Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition");
414 return STATUS_ERROR;
415 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800416 if (mKeyphraseListener.asBinder() != listener.asBinder()) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700417 // We don't allow a different listener to stop the recognition than the one
418 // that started it.
419 Slog.w(TAG, "Attempting stopRecognition for another recognition");
420 return STATUS_ERROR;
421 }
422
Sandeepd7018202014-07-10 15:15:39 -0700423 // Stop recognition if it's the current one, ignore otherwise.
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700424 mRequested = false;
425 int status = updateRecognitionLocked(false /* don't notify for synchronous calls */);
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -0700426 if (status != SoundTrigger.STATUS_OK) {
Sandeep Siddhartha2c0273e2014-08-01 11:32:03 -0700427 return status;
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -0700428 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700429
Sandeep Siddhartha45c00b52014-10-16 16:17:11 -0700430 // We leave the sound model loaded but not started, this helps us when we start
431 // back.
432 // Also clear the internal state once the recognition has been stopped.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800433 internalClearKeyphraseStateLocked();
434 internalClearGlobalStateLocked();
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700435 return status;
Sandeepd7018202014-07-10 15:15:39 -0700436 }
437 }
438
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700439 /**
440 * Stops all recognitions active currently and clears the internal state.
441 */
442 void stopAllRecognitions() {
443 synchronized (mLock) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800444 if (mModuleProperties == null || mModule == null) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700445 return;
446 }
Sandeep Siddhartha5e33fb02014-08-01 18:09:07 -0700447
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800448 // Stop Keyphrase recognition if one exists.
449 if (mCurrentKeyphraseModelHandle != INVALID_VALUE) {
450
451 mRequested = false;
452 int status = updateRecognitionLocked(
453 false /* don't notify for synchronous calls */);
454 internalClearKeyphraseStateLocked();
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700455 }
Sandeep Siddhartha5e33fb02014-08-01 18:09:07 -0700456
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800457 // Stop all generic recognition models.
458 for (ModelData model : mGenericModelDataMap.values()) {
Arunesh Mishra933da812016-02-18 15:31:54 -0800459 if (model.isModelStarted()) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800460 int status = stopGenericRecognitionLocked(model,
461 false /* do not notify for synchronous calls */);
462 if (status != STATUS_OK) {
463 // What else can we do if there is an error here.
464 Slog.w(TAG, "Error stopping generic model: " + model.getHandle());
465 }
466 model.clearState();
467 model.clearCallback();
468 }
469 }
470 internalClearGlobalStateLocked();
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700471 }
Sandeep Siddhartha5e33fb02014-08-01 18:09:07 -0700472 }
473
Arunesh Mishra55a9b002016-02-01 14:06:37 -0800474 public ModuleProperties getModuleProperties() {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800475 return mModuleProperties;
Arunesh Mishra55a9b002016-02-01 14:06:37 -0800476 }
477
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800478 int unloadKeyphraseSoundModel(int keyphraseId) {
479 if (mModule == null || mCurrentKeyphraseModelHandle == INVALID_VALUE) {
480 return STATUS_ERROR;
481 }
482 if (mKeyphraseId != keyphraseId) {
483 Slog.w(TAG, "Given sound model is not the one loaded.");
484 return STATUS_ERROR;
485 }
486
487 synchronized (mLock) {
488 // Stop recognition if it's the current one.
489 mRequested = false;
490 int status = updateRecognitionLocked(false /* don't notify */);
491 if (status != SoundTrigger.STATUS_OK) {
492 Slog.w(TAG, "Stop recognition failed for keyphrase ID:" + status);
493 }
494
495 status = mModule.unloadSoundModel(mCurrentKeyphraseModelHandle);
496 if (status != SoundTrigger.STATUS_OK) {
497 Slog.w(TAG, "unloadKeyphraseSoundModel call failed with " + status);
498 }
499 internalClearKeyphraseSoundModelLocked();
500 return status;
501 }
502 }
503
504 int unloadGenericSoundModel(UUID modelId) {
505 if (modelId == null || mModule == null) {
506 return STATUS_ERROR;
507 }
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800508 synchronized (mLock) {
Ryan Bavetta79655eb2016-03-07 14:34:51 -0800509 ModelData modelData = mGenericModelDataMap.get(modelId);
510 if (modelData == null) {
511 Slog.w(TAG, "Unload error: Attempting unload invalid generic model with id:" + modelId);
512 return STATUS_ERROR;
513 }
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800514 if (!modelData.isModelLoaded()) {
515 // Nothing to do here.
516 Slog.i(TAG, "Unload: Given generic model is not loaded:" + modelId);
517 return STATUS_OK;
518 }
519 if (modelData.isModelStarted()) {
520 int status = stopGenericRecognitionLocked(modelData,
521 false /* don't notify for synchronous calls */);
522 if (status != SoundTrigger.STATUS_OK) {
523 Slog.w(TAG, "stopGenericRecognition failed: " + status);
524 }
525 }
526
527 int status = mModule.unloadSoundModel(modelData.getHandle());
528 if (status != SoundTrigger.STATUS_OK) {
529 Slog.w(TAG, "unloadGenericSoundModel() call failed with " + status);
530 Slog.w(TAG, "unloadGenericSoundModel() force-marking model as unloaded.");
531 }
532 mGenericModelDataMap.remove(modelId);
Ryan Bavetta79655eb2016-03-07 14:34:51 -0800533 if (DBG) dumpGenericModelStateLocked();
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800534 return status;
535 }
536 }
537
Sandeepd7018202014-07-10 15:15:39 -0700538 //---- SoundTrigger.StatusListener methods
539 @Override
540 public void onRecognition(RecognitionEvent event) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800541 if (event == null) {
542 Slog.w(TAG, "Null recognition event!");
543 return;
544 }
545
546 if (!(event instanceof KeyphraseRecognitionEvent) &&
547 !(event instanceof GenericRecognitionEvent)) {
548 Slog.w(TAG, "Invalid recognition event type (not one of generic or keyphrase) !");
Sandeep Siddhartha8ecaf5f2014-07-10 19:38:18 -0700549 return;
550 }
Sandeepd7018202014-07-10 15:15:39 -0700551
Sandeep Siddhartha68173372014-07-28 13:25:30 -0700552 if (DBG) Slog.d(TAG, "onRecognition: " + event);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700553 synchronized (mLock) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700554 switch (event.status) {
555 // Fire aborts/failures to all listeners since it's not tied to a keyphrase.
556 case SoundTrigger.RECOGNITION_STATUS_ABORT:
557 onRecognitionAbortLocked();
558 break;
559 case SoundTrigger.RECOGNITION_STATUS_FAILURE:
560 onRecognitionFailureLocked();
561 break;
562 case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800563
564 if (isKeyphraseRecognitionEvent(event)) {
565 onKeyphraseRecognitionSuccessLocked((KeyphraseRecognitionEvent) event);
566 } else {
567 onGenericRecognitionSuccessLocked((GenericRecognitionEvent) event);
568 }
569
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700570 break;
571 }
Sandeepd7018202014-07-10 15:15:39 -0700572 }
573 }
574
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800575 private boolean isKeyphraseRecognitionEvent(RecognitionEvent event) {
Arunesh Mishraf47f1732016-02-18 16:16:12 -0800576 return event instanceof KeyphraseRecognitionEvent;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800577 }
578
579 private void onGenericRecognitionSuccessLocked(GenericRecognitionEvent event) {
580 if (event.status != SoundTrigger.RECOGNITION_STATUS_SUCCESS) {
581 return;
582 }
Ryan Bavetta79655eb2016-03-07 14:34:51 -0800583 ModelData model = getModelDataForLocked(event.soundModelHandle);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800584 if (model == null) {
585 Slog.w(TAG, "Generic recognition event: Model does not exist for handle: " +
586 event.soundModelHandle);
587 return;
588 }
589
590 IRecognitionStatusCallback callback = model.getCallback();
591 if (callback == null) {
592 Slog.w(TAG, "Generic recognition event: Null callback for model handle: " +
593 event.soundModelHandle);
594 return;
595 }
596
597 try {
Arunesh Mishraf47f1732016-02-18 16:16:12 -0800598 callback.onGenericSoundTriggerDetected((GenericRecognitionEvent) event);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800599 } catch (RemoteException e) {
Arunesh Mishraf47f1732016-02-18 16:16:12 -0800600 Slog.w(TAG, "RemoteException in onGenericSoundTriggerDetected", e);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800601 }
602
603 model.setStopped();
604 RecognitionConfig config = model.getRecognitionConfig();
605 if (config == null) {
606 Slog.w(TAG, "Generic recognition event: Null RecognitionConfig for model handle: " +
607 event.soundModelHandle);
608 return;
609 }
610
611 // TODO: Remove this block if the lower layer supports multiple triggers.
612 if (config.allowMultipleTriggers) {
613 startGenericRecognitionLocked(model, true /* notify */);
614 }
615 }
616
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700617 @Override
Eric Laurentd3b82232014-07-30 08:57:39 -0700618 public void onSoundModelUpdate(SoundModelEvent event) {
619 if (event == null) {
620 Slog.w(TAG, "Invalid sound model event!");
621 return;
622 }
Eric Laurentd3b82232014-07-30 08:57:39 -0700623 if (DBG) Slog.d(TAG, "onSoundModelUpdate: " + event);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700624 synchronized (mLock) {
625 onSoundModelUpdatedLocked(event);
626 }
Eric Laurentd3b82232014-07-30 08:57:39 -0700627 }
628
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700629 @Override
Eric Laurentd3b82232014-07-30 08:57:39 -0700630 public void onServiceStateChange(int state) {
631 if (DBG) Slog.d(TAG, "onServiceStateChange, state: " + state);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700632 synchronized (mLock) {
633 onServiceStateChangedLocked(SoundTrigger.SERVICE_STATE_DISABLED == state);
634 }
Eric Laurentd3b82232014-07-30 08:57:39 -0700635 }
636
Sandeepd7018202014-07-10 15:15:39 -0700637 @Override
638 public void onServiceDied() {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700639 Slog.e(TAG, "onServiceDied!!");
640 synchronized (mLock) {
641 onServiceDiedLocked();
Sandeep Siddhartha68173372014-07-28 13:25:30 -0700642 }
Sandeepd7018202014-07-10 15:15:39 -0700643 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700644
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700645 private void onCallStateChangedLocked(boolean callActive) {
646 if (mCallActive == callActive) {
647 // We consider multiple call states as being active
648 // so we check if something really changed or not here.
649 return;
650 }
651 mCallActive = callActive;
652 updateRecognitionLocked(true /* notify */);
653 }
654
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700655 private void onPowerSaveModeChangedLocked(boolean isPowerSaveMode) {
656 if (mIsPowerSaveMode == isPowerSaveMode) {
657 return;
658 }
659 mIsPowerSaveMode = isPowerSaveMode;
660 updateRecognitionLocked(true /* notify */);
661 }
662
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700663 private void onSoundModelUpdatedLocked(SoundModelEvent event) {
664 // TODO: Handle sound model update here.
665 }
666
667 private void onServiceStateChangedLocked(boolean disabled) {
668 if (disabled == mServiceDisabled) {
669 return;
670 }
671 mServiceDisabled = disabled;
672 updateRecognitionLocked(true /* notify */);
673 }
674
675 private void onRecognitionAbortLocked() {
676 Slog.w(TAG, "Recognition aborted");
Chris Thorntonb7b58122015-10-06 16:48:12 -0700677 // If abort has been called, the hardware has already stopped recognition, so we shouldn't
678 // call it again when we process the state change.
679 mRecognitionAborted = true;
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700680 }
681
682 private void onRecognitionFailureLocked() {
683 Slog.w(TAG, "Recognition failure");
684 try {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800685 if (mKeyphraseListener != null) {
686 mKeyphraseListener.onError(STATUS_ERROR);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700687 }
688 } catch (RemoteException e) {
689 Slog.w(TAG, "RemoteException in onError", e);
690 } finally {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800691 internalClearKeyphraseStateLocked();
692 internalClearGlobalStateLocked();
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700693 }
694 }
695
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800696 private void onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700697 Slog.i(TAG, "Recognition success");
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800698
699 if (mKeyphraseListener == null) {
700 Slog.w(TAG, "received onRecognition event without any listener for it");
701 return;
702 }
703
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700704 KeyphraseRecognitionExtra[] keyphraseExtras =
705 ((KeyphraseRecognitionEvent) event).keyphraseExtras;
706 if (keyphraseExtras == null || keyphraseExtras.length == 0) {
707 Slog.w(TAG, "Invalid keyphrase recognition event!");
708 return;
709 }
710 // TODO: Handle more than one keyphrase extras.
711 if (mKeyphraseId != keyphraseExtras[0].id) {
712 Slog.w(TAG, "received onRecognition event for a different keyphrase");
713 return;
714 }
715
716 try {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800717 if (mKeyphraseListener != null) {
Arunesh Mishraf47f1732016-02-18 16:16:12 -0800718 mKeyphraseListener.onKeyphraseDetected((KeyphraseRecognitionEvent) event);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700719 }
720 } catch (RemoteException e) {
Arunesh Mishraf47f1732016-02-18 16:16:12 -0800721 Slog.w(TAG, "RemoteException in onKeyphraseDetected", e);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700722 }
723
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800724 mKeyphraseStarted = false;
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700725 mRequested = mRecognitionConfig.allowMultipleTriggers;
726 // TODO: Remove this block if the lower layer supports multiple triggers.
727 if (mRequested) {
728 updateRecognitionLocked(true /* notify */);
729 }
730 }
731
732 private void onServiceDiedLocked() {
733 try {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800734 if (mKeyphraseListener != null) {
735 mKeyphraseListener.onError(SoundTrigger.STATUS_DEAD_OBJECT);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700736 }
737 } catch (RemoteException e) {
738 Slog.w(TAG, "RemoteException in onError", e);
739 } finally {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800740 internalClearKeyphraseSoundModelLocked();
741 internalClearKeyphraseStateLocked();
742 internalClearGenericModelStateLocked();
743 internalClearGlobalStateLocked();
Eric Laurent2475e382014-09-09 15:52:25 -0700744 if (mModule != null) {
745 mModule.detach();
746 mModule = null;
747 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700748 }
749 }
750
751 private int updateRecognitionLocked(boolean notify) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800752 if (mModule == null || mModuleProperties == null
753 || mCurrentKeyphraseModelHandle == INVALID_VALUE || mKeyphraseListener == null) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700754 // Nothing to do here.
755 return STATUS_OK;
756 }
757
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700758 boolean start = mRequested && !mCallActive && !mServiceDisabled && !mIsPowerSaveMode;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800759 if (start == mKeyphraseStarted) {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700760 // No-op.
761 return STATUS_OK;
762 }
763
764 // See if the recognition needs to be started.
765 if (start) {
766 // Start recognition.
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800767 int status = mModule.startRecognition(mCurrentKeyphraseModelHandle,
768 mRecognitionConfig);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700769 if (status != SoundTrigger.STATUS_OK) {
Arunesh Mishraf47f1732016-02-18 16:16:12 -0800770 Slog.w(TAG, "startKeyphraseRecognition failed with " + status);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700771 // Notify of error if needed.
772 if (notify) {
773 try {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800774 mKeyphraseListener.onError(status);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700775 } catch (RemoteException e) {
776 Slog.w(TAG, "RemoteException in onError", e);
777 }
778 }
779 } else {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800780 mKeyphraseStarted = true;
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700781 // Notify of resume if needed.
782 if (notify) {
783 try {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800784 mKeyphraseListener.onRecognitionResumed();
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700785 } catch (RemoteException e) {
786 Slog.w(TAG, "RemoteException in onRecognitionResumed", e);
787 }
788 }
789 }
790 return status;
791 } else {
Chris Thorntonb7b58122015-10-06 16:48:12 -0700792 // Stop recognition (only if we haven't been aborted).
793 int status = STATUS_OK;
794 if (!mRecognitionAborted) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800795 status = mModule.stopRecognition(mCurrentKeyphraseModelHandle);
Chris Thorntonb7b58122015-10-06 16:48:12 -0700796 } else {
797 mRecognitionAborted = false;
798 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700799 if (status != SoundTrigger.STATUS_OK) {
800 Slog.w(TAG, "stopRecognition call failed with " + status);
801 if (notify) {
802 try {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800803 mKeyphraseListener.onError(status);
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700804 } catch (RemoteException e) {
805 Slog.w(TAG, "RemoteException in onError", e);
806 }
807 }
808 } else {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800809 mKeyphraseStarted = false;
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700810 // Notify of pause if needed.
811 if (notify) {
812 try {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800813 mKeyphraseListener.onRecognitionPaused();
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700814 } catch (RemoteException e) {
815 Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
816 }
817 }
818 }
819 return status;
820 }
821 }
822
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800823 // internalClearGlobalStateLocked() gets split into two routines. Cleanup that is
824 // specific to keyphrase sound models named as internalClearKeyphraseStateLocked() and
825 // internalClearGlobalStateLocked() for global state. The global cleanup routine will be used
826 // by the cleanup happening with the generic sound models.
827 private void internalClearGlobalStateLocked() {
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700828 // Unregister from call state changes.
829 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700830
831 // Unregister from power save mode changes.
832 if (mPowerSaveModeListener != null) {
833 mContext.unregisterReceiver(mPowerSaveModeListener);
834 mPowerSaveModeListener = null;
835 }
836 }
837
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800838 private void internalClearKeyphraseStateLocked() {
839 mKeyphraseStarted = false;
840 mRequested = false;
841
842 mKeyphraseId = INVALID_VALUE;
843 mRecognitionConfig = null;
844 mKeyphraseListener = null;
845 }
846
847 private void internalClearGenericModelStateLocked() {
848 for (UUID modelId : mGenericModelDataMap.keySet()) {
849 ModelData modelData = mGenericModelDataMap.get(modelId);
850 modelData.clearState();
851 modelData.clearCallback();
852 }
853 }
854
855 // This routine is a replacement for internalClearSoundModelLocked(). However, we
856 // should see why this should be different from internalClearKeyphraseStateLocked().
857 private void internalClearKeyphraseSoundModelLocked() {
858 mCurrentKeyphraseModelHandle = INVALID_VALUE;
Sandeep Siddhartha45c00b52014-10-16 16:17:11 -0700859 mCurrentSoundModel = null;
860 }
861
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700862 class MyCallStateListener extends PhoneStateListener {
863 @Override
864 public void onCallStateChanged(int state, String arg1) {
865 if (DBG) Slog.d(TAG, "onCallStateChanged: " + state);
866 synchronized (mLock) {
867 onCallStateChangedLocked(TelephonyManager.CALL_STATE_IDLE != state);
868 }
869 }
870 }
871
872 class PowerSaveModeListener extends BroadcastReceiver {
873 @Override
874 public void onReceive(Context context, Intent intent) {
875 if (!PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) {
876 return;
877 }
878 boolean active = mPowerManager.isPowerSaveMode();
879 if (DBG) Slog.d(TAG, "onPowerSaveModeChanged: " + active);
880 synchronized (mLock) {
881 onPowerSaveModeChangedLocked(active);
882 }
883 }
Sandeep Siddharthacb4e81c2014-08-05 15:03:46 -0700884 }
Sandeep Siddhartha6b8556d2014-08-06 19:47:25 -0700885
886 void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
887 synchronized (mLock) {
888 pw.print(" module properties=");
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800889 pw.println(mModuleProperties == null ? "null" : mModuleProperties);
Sandeep Siddhartha6b8556d2014-08-06 19:47:25 -0700890 pw.print(" keyphrase ID="); pw.println(mKeyphraseId);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800891 pw.print(" sound model handle="); pw.println(mCurrentKeyphraseModelHandle);
Sandeep Siddhartha6b8556d2014-08-06 19:47:25 -0700892 pw.print(" sound model UUID=");
Sandeep Siddhartha45c00b52014-10-16 16:17:11 -0700893 pw.println(mCurrentSoundModel == null ? "null" : mCurrentSoundModel.uuid);
Sandeep Siddhartha6b8556d2014-08-06 19:47:25 -0700894 pw.print(" current listener=");
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800895 pw.println(mKeyphraseListener == null ? "null" : mKeyphraseListener.asBinder());
Sandeep Siddhartha6b8556d2014-08-06 19:47:25 -0700896
897 pw.print(" requested="); pw.println(mRequested);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800898 pw.print(" started="); pw.println(mKeyphraseStarted);
Sandeep Siddhartha6b8556d2014-08-06 19:47:25 -0700899 pw.print(" call active="); pw.println(mCallActive);
Sandeep Siddharthaefe0f9c2014-08-22 17:45:04 -0700900 pw.print(" power save mode active="); pw.println(mIsPowerSaveMode);
Sandeep Siddhartha6b8556d2014-08-06 19:47:25 -0700901 pw.print(" service disabled="); pw.println(mServiceDisabled);
902 }
903 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800904
905 private void initializeTelephonyAndPowerStateListeners() {
906 // Get the current call state synchronously for the first recognition.
907 mCallActive = mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE;
908
909 // Register for call state changes when the first call to start recognition occurs.
910 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
911
912 // Register for power saver mode changes when the first call to start recognition
913 // occurs.
914 if (mPowerSaveModeListener == null) {
915 mPowerSaveModeListener = new PowerSaveModeListener();
916 mContext.registerReceiver(mPowerSaveModeListener,
917 new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
918 }
919 mIsPowerSaveMode = mPowerManager.isPowerSaveMode();
920 }
921
Ryan Bavetta79655eb2016-03-07 14:34:51 -0800922 private ModelData getOrCreateGenericModelDataLocked(UUID modelId) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800923 ModelData modelData = mGenericModelDataMap.get(modelId);
924 if (modelData == null) {
925 modelData = new ModelData(modelId);
926 modelData.setTypeGeneric();
927 mGenericModelDataMap.put(modelId, modelData);
928 }
929 return modelData;
930 }
931
932 // Instead of maintaining a second hashmap of modelHandle -> ModelData, we just
933 // iterate through to find the right object (since we don't expect 100s of models
934 // to be stored).
Ryan Bavetta79655eb2016-03-07 14:34:51 -0800935 private ModelData getModelDataForLocked(int modelHandle) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800936 // Fetch ModelData object corresponding to the model handle.
937 for (ModelData model : mGenericModelDataMap.values()) {
938 if (model.getHandle() == modelHandle) {
939 return model;
940 }
941 }
942 return null;
943 }
944
945 // Whether we are allowed to run any recognition at all. The conditions that let us run
946 // a recognition include: no active phone call or not being in a power save mode. Also,
947 // the native service should be enabled.
948 private boolean isRecognitionAllowed() {
949 return !mCallActive && !mServiceDisabled && !mIsPowerSaveMode;
950 }
951
952 private int startGenericRecognitionLocked(ModelData modelData, boolean notify) {
953 IRecognitionStatusCallback callback = modelData.getCallback();
954 int handle = modelData.getHandle();
955 RecognitionConfig config = modelData.getRecognitionConfig();
956 if (callback == null || handle == INVALID_VALUE || config == null) {
957 // Nothing to do here.
958 Slog.w(TAG, "startGenericRecognition: Bad data passed in.");
959 return STATUS_ERROR;
960 }
961
962 if (!isRecognitionAllowed()) {
963 // Nothing to do here.
964 Slog.w(TAG, "startGenericRecognition requested but not allowed.");
965 return STATUS_OK;
966 }
967
968 int status = mModule.startRecognition(handle, config);
969 if (status != SoundTrigger.STATUS_OK) {
Arunesh Mishraf47f1732016-02-18 16:16:12 -0800970 Slog.w(TAG, "startGenericRecognition failed with " + status);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800971 // Notify of error if needed.
972 if (notify) {
973 try {
974 callback.onError(status);
975 } catch (RemoteException e) {
976 Slog.w(TAG, "RemoteException in onError", e);
977 }
978 }
979 } else {
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800980 Slog.i(TAG, "startRecognition successful.");
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800981 modelData.setStarted();
982 // Notify of resume if needed.
983 if (notify) {
984 try {
985 callback.onRecognitionResumed();
986 } catch (RemoteException e) {
987 Slog.w(TAG, "RemoteException in onRecognitionResumed", e);
988 }
989 }
990 }
Ryan Bavetta79655eb2016-03-07 14:34:51 -0800991 if (DBG) dumpGenericModelStateLocked();
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800992 return status;
993 }
994
995 private int stopGenericRecognitionLocked(ModelData modelData, boolean notify) {
996 IRecognitionStatusCallback callback = modelData.getCallback();
997
998 // Stop recognition (only if we haven't been aborted).
999 int status = mModule.stopRecognition(modelData.getHandle());
1000 if (status != SoundTrigger.STATUS_OK) {
1001 Slog.w(TAG, "stopRecognition call failed with " + status);
1002 if (notify) {
1003 try {
1004 callback.onError(status);
1005 } catch (RemoteException e) {
1006 Slog.w(TAG, "RemoteException in onError", e);
1007 }
1008 }
1009 } else {
1010 modelData.setStopped();
1011 // Notify of pause if needed.
1012 if (notify) {
1013 try {
1014 callback.onRecognitionPaused();
1015 } catch (RemoteException e) {
1016 Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
1017 }
1018 }
1019 }
Ryan Bavetta79655eb2016-03-07 14:34:51 -08001020 if (DBG) dumpGenericModelStateLocked();
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001021 return status;
1022 }
1023
Ryan Bavetta79655eb2016-03-07 14:34:51 -08001024 private void dumpGenericModelStateLocked() {
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001025 for (UUID modelId : mGenericModelDataMap.keySet()) {
1026 ModelData modelData = mGenericModelDataMap.get(modelId);
1027 Slog.i(TAG, "Model :" + modelData.toString());
1028 }
1029 }
1030
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001031 // Computes whether we have any recognition running at all (voice or generic). Sets
1032 // the mRecognitionRunning variable with the result.
Ryan Bavetta79655eb2016-03-07 14:34:51 -08001033 private boolean computeRecognitionRunningLocked() {
1034 if (mModuleProperties == null || mModule == null) {
1035 mRecognitionRunning = false;
1036 return mRecognitionRunning;
1037 }
1038 if (mKeyphraseListener != null && mKeyphraseStarted &&
1039 mCurrentKeyphraseModelHandle != INVALID_VALUE && mCurrentSoundModel != null) {
1040 mRecognitionRunning = true;
1041 return mRecognitionRunning;
1042 }
1043 for (UUID modelId : mGenericModelDataMap.keySet()) {
1044 ModelData modelData = mGenericModelDataMap.get(modelId);
1045 if (modelData.isModelStarted()) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001046 mRecognitionRunning = true;
1047 return mRecognitionRunning;
1048 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001049 }
Ryan Bavetta79655eb2016-03-07 14:34:51 -08001050 mRecognitionRunning = false;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001051 return mRecognitionRunning;
1052 }
1053
1054 // This class encapsulates the callbacks, state, handles and any other information that
1055 // represents a model.
1056 private static class ModelData {
1057 // Model not loaded (and hence not started).
1058 static final int MODEL_NOTLOADED = 0;
1059
1060 // Loaded implies model was successfully loaded. Model not started yet.
1061 static final int MODEL_LOADED = 1;
1062
1063 // Started implies model was successfully loaded and start was called.
1064 static final int MODEL_STARTED = 2;
1065
1066 // One of MODEL_NOTLOADED, MODEL_LOADED, MODEL_STARTED (which implies loaded).
1067 private int mModelState;
1068
1069 private UUID mModelId;
1070
1071 // One of SoundModel.TYPE_GENERIC or SoundModel.TYPE_KEYPHRASE. Initially set
1072 // to SoundModel.TYPE_UNKNOWN;
1073 private int mModelType = SoundModel.TYPE_UNKNOWN;
1074 private IRecognitionStatusCallback mCallback = null;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001075 private RecognitionConfig mRecognitionConfig = null;
1076
1077
1078 // Model handle is an integer used by the HAL as an identifier for sound
1079 // models.
1080 private int mModelHandle = INVALID_VALUE;
1081
1082 ModelData(UUID modelId) {
1083 mModelId = modelId;
1084 }
1085
1086 synchronized void setTypeGeneric() {
1087 mModelType = SoundModel.TYPE_GENERIC_SOUND;
1088 }
1089
1090 synchronized void setCallback(IRecognitionStatusCallback callback) {
1091 mCallback = callback;
1092 }
1093
1094 synchronized IRecognitionStatusCallback getCallback() {
1095 return mCallback;
1096 }
1097
1098 synchronized boolean isModelLoaded() {
Arunesh Mishra933da812016-02-18 15:31:54 -08001099 return (mModelState == MODEL_LOADED || mModelState == MODEL_STARTED);
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001100 }
1101
1102 synchronized void setStarted() {
1103 mModelState = MODEL_STARTED;
1104 }
1105
1106 synchronized void setStopped() {
1107 mModelState = MODEL_LOADED;
1108 }
1109
Arunesh Mishra933da812016-02-18 15:31:54 -08001110 synchronized void setLoaded() {
1111 mModelState = MODEL_LOADED;
1112 }
1113
1114 synchronized boolean isModelStarted() {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001115 return mModelState == MODEL_STARTED;
1116 }
1117
1118 synchronized void clearState() {
1119 mModelState = MODEL_NOTLOADED;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001120 mModelHandle = INVALID_VALUE;
1121 }
1122
1123 synchronized void clearCallback() {
1124 mCallback = null;
1125 }
1126
1127 synchronized void setHandle(int handle) {
1128 mModelHandle = handle;
1129 }
1130
1131 synchronized void setRecognitionConfig(RecognitionConfig config) {
1132 mRecognitionConfig = config;
1133 }
1134
1135 synchronized int getHandle() {
1136 return mModelHandle;
1137 }
1138
1139 synchronized RecognitionConfig getRecognitionConfig() {
1140 return mRecognitionConfig;
1141 }
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001142
1143 String stateToString() {
1144 switch(mModelState) {
1145 case MODEL_NOTLOADED: return "NOT_LOADED";
1146 case MODEL_LOADED: return "LOADED";
1147 case MODEL_STARTED: return "STARTED";
1148 }
1149 return "Unknown state";
1150 }
1151
1152 public String toString() {
1153 return "Handle: " + mModelHandle + "ModelState: " + stateToString();
1154 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001155 }
Sandeepd7018202014-07-10 15:15:39 -07001156}