1 /**
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 
17 package com.android.server.soundtrigger;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.hardware.soundtrigger.IRecognitionStatusCallback;
24 import android.hardware.soundtrigger.SoundTrigger;
25 import android.hardware.soundtrigger.SoundTrigger.GenericRecognitionEvent;
26 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
27 import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
28 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
29 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
30 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
31 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
32 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
33 import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
34 import android.hardware.soundtrigger.SoundTrigger.SoundModel;
35 import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
36 import android.hardware.soundtrigger.SoundTriggerModule;
37 import android.os.Binder;
38 import android.os.DeadObjectException;
39 import android.os.PowerManager;
40 import android.os.PowerManager.ServiceType;
41 import android.os.RemoteException;
42 import android.telephony.PhoneStateListener;
43 import android.telephony.TelephonyManager;
44 import android.util.Slog;
45 
46 import com.android.internal.logging.MetricsLogger;
47 
48 import java.io.FileDescriptor;
49 import java.io.PrintWriter;
50 import java.util.ArrayList;
51 import java.util.HashMap;
52 import java.util.Iterator;
53 import java.util.Map;
54 import java.util.UUID;
55 
56 /**
57  * Helper for {@link SoundTrigger} APIs. Supports two types of models:
58  * (i) A voice model which is exported via the {@link VoiceInteractionService}. There can only be
59  * a single voice model running on the DSP at any given time.
60  *
61  * (ii) Generic sound-trigger models: Supports multiple of these.
62  *
63  * Currently this just acts as an abstraction over all SoundTrigger API calls.
64  * @hide
65  */
66 public class SoundTriggerHelper implements SoundTrigger.StatusListener {
67     static final String TAG = "SoundTriggerHelper";
68     static final boolean DBG = false;
69 
70     /**
71      * Return codes for {@link #startRecognition(int, KeyphraseSoundModel,
72      *      IRecognitionStatusCallback, RecognitionConfig)},
73      * {@link #stopRecognition(int, IRecognitionStatusCallback)}
74      */
75     public static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR;
76     public static final int STATUS_OK = SoundTrigger.STATUS_OK;
77 
78     private static final int INVALID_VALUE = Integer.MIN_VALUE;
79 
80     /** The {@link ModuleProperties} for the system, or null if none exists. */
81     final ModuleProperties mModuleProperties;
82 
83     /** The properties for the DSP module */
84     private SoundTriggerModule mModule;
85     private final Object mLock = new Object();
86     private final Context mContext;
87     private final TelephonyManager mTelephonyManager;
88     private final PhoneStateListener mPhoneStateListener;
89     private final PowerManager mPowerManager;
90 
91     // The SoundTriggerManager layer handles multiple recognition models of type generic and
92     // keyphrase. We store the ModelData here in a hashmap.
93     private final HashMap<UUID, ModelData> mModelDataMap;
94 
95     // An index of keyphrase sound models so that we can reach them easily. We support indexing
96     // keyphrase sound models with a keyphrase ID. Sound model with the same keyphrase ID will
97     // replace an existing model, thus there is a 1:1 mapping from keyphrase ID to a voice
98     // sound model.
99     private HashMap<Integer, UUID> mKeyphraseUuidMap;
100 
101     private boolean mCallActive = false;
102     private boolean mIsPowerSaveMode = false;
103     // Indicates if the native sound trigger service is disabled or not.
104     // This is an indirect indication of the microphone being open in some other application.
105     private boolean mServiceDisabled = false;
106 
107     // Whether we have ANY recognition (keyphrase or generic) running.
108     private boolean mRecognitionRunning = false;
109 
110     private PowerSaveModeListener mPowerSaveModeListener;
111 
SoundTriggerHelper(Context context)112     SoundTriggerHelper(Context context) {
113         ArrayList <ModuleProperties> modules = new ArrayList<>();
114         int status = SoundTrigger.listModules(modules);
115         mContext = context;
116         mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
117         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
118         mModelDataMap = new HashMap<UUID, ModelData>();
119         mKeyphraseUuidMap = new HashMap<Integer, UUID>();
120         mPhoneStateListener = new MyCallStateListener();
121         if (status != SoundTrigger.STATUS_OK || modules.size() == 0) {
122             Slog.w(TAG, "listModules status=" + status + ", # of modules=" + modules.size());
123             mModuleProperties = null;
124             mModule = null;
125         } else {
126             // TODO: Figure out how to determine which module corresponds to the DSP hardware.
127             mModuleProperties = modules.get(0);
128         }
129     }
130 
131     /**
132      * Starts recognition for the given generic sound model ID. This is a wrapper around {@link
133      * startRecognition()}.
134      *
135      * @param modelId UUID of the sound model.
136      * @param soundModel The generic sound model to use for recognition.
137      * @param callback Callack for the recognition events related to the given keyphrase.
138      * @param recognitionConfig Instance of RecognitionConfig containing the parameters for the
139      * recognition.
140      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
141      */
startGenericRecognition(UUID modelId, GenericSoundModel soundModel, IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig)142     int startGenericRecognition(UUID modelId, GenericSoundModel soundModel,
143             IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) {
144         MetricsLogger.count(mContext, "sth_start_recognition", 1);
145         if (modelId == null || soundModel == null || callback == null ||
146                 recognitionConfig == null) {
147             Slog.w(TAG, "Passed in bad data to startGenericRecognition().");
148             return STATUS_ERROR;
149         }
150 
151         synchronized (mLock) {
152             ModelData modelData = getOrCreateGenericModelDataLocked(modelId);
153             if (modelData == null) {
154                 Slog.w(TAG, "Irrecoverable error occurred, check UUID / sound model data.");
155                 return STATUS_ERROR;
156             }
157             return startRecognition(soundModel, modelData, callback, recognitionConfig,
158                     INVALID_VALUE /* keyphraseId */);
159         }
160     }
161 
162     /**
163      * Starts recognition for the given keyphraseId.
164      *
165      * @param keyphraseId The identifier of the keyphrase for which
166      *        the recognition is to be started.
167      * @param soundModel The sound model to use for recognition.
168      * @param callback The callback for the recognition events related to the given keyphrase.
169      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
170      */
startKeyphraseRecognition(int keyphraseId, KeyphraseSoundModel soundModel, IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig)171     int startKeyphraseRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
172             IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) {
173         synchronized (mLock) {
174             MetricsLogger.count(mContext, "sth_start_recognition", 1);
175             if (soundModel == null || callback == null || recognitionConfig == null) {
176                 return STATUS_ERROR;
177             }
178 
179             if (DBG) {
180                 Slog.d(TAG, "startKeyphraseRecognition for keyphraseId=" + keyphraseId
181                         + " soundModel=" + soundModel + ", callback=" + callback.asBinder()
182                         + ", recognitionConfig=" + recognitionConfig);
183                 Slog.d(TAG, "moduleProperties=" + mModuleProperties);
184                 dumpModelStateLocked();
185             }
186 
187             ModelData model = getKeyphraseModelDataLocked(keyphraseId);
188             if (model != null && !model.isKeyphraseModel()) {
189                 Slog.e(TAG, "Generic model with same UUID exists.");
190                 return STATUS_ERROR;
191             }
192 
193             // Process existing model first.
194             if (model != null && !model.getModelId().equals(soundModel.uuid)) {
195                 // The existing model has a different UUID, should be replaced.
196                 int status = cleanUpExistingKeyphraseModelLocked(model);
197                 if (status != STATUS_OK) {
198                     return status;
199                 }
200                 removeKeyphraseModelLocked(keyphraseId);
201                 model = null;
202             }
203 
204             // We need to create a new one: either no previous models existed for given keyphrase id
205             // or the existing model had a different UUID and was cleaned up.
206             if (model == null) {
207                 model = createKeyphraseModelDataLocked(soundModel.uuid, keyphraseId);
208             }
209 
210             return startRecognition(soundModel, model, callback, recognitionConfig,
211                     keyphraseId);
212         }
213     }
214 
cleanUpExistingKeyphraseModelLocked(ModelData modelData)215     private int cleanUpExistingKeyphraseModelLocked(ModelData modelData) {
216         // Stop and clean up a previous ModelData if one exists. This usually is used when the
217         // previous model has a different UUID for the same keyphrase ID.
218         int status = tryStopAndUnloadLocked(modelData, true /* stop */, true /* unload */);
219         if (status != STATUS_OK) {
220             Slog.w(TAG, "Unable to stop or unload previous model: " +
221                     modelData.toString());
222         }
223         return status;
224     }
225 
226     /**
227      * Starts recognition for the given sound model. A single routine for both keyphrase and
228      * generic sound models.
229      *
230      * @param soundModel The sound model to use for recognition.
231      * @param modelData Instance of {@link #ModelData} for the given model.
232      * @param callback Callback for the recognition events related to the given keyphrase.
233      * @param recognitionConfig Instance of {@link RecognitionConfig} containing the parameters
234      * @param keyphraseId Keyphrase ID for keyphrase models only. Pass in INVALID_VALUE for other
235      * models.
236      * for the recognition.
237      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
238      */
startRecognition(SoundModel soundModel, ModelData modelData, IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig, int keyphraseId)239     int startRecognition(SoundModel soundModel, ModelData modelData,
240             IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig,
241             int keyphraseId) {
242         synchronized (mLock) {
243             if (mModuleProperties == null) {
244                 Slog.w(TAG, "Attempting startRecognition without the capability");
245                 return STATUS_ERROR;
246             }
247             if (mModule == null) {
248                 mModule = SoundTrigger.attachModule(mModuleProperties.id, this, null);
249                 if (mModule == null) {
250                     Slog.w(TAG, "startRecognition cannot attach to sound trigger module");
251                     return STATUS_ERROR;
252                 }
253             }
254 
255             // Initialize power save, call active state monitoring logic.
256             if (!mRecognitionRunning) {
257                 initializeTelephonyAndPowerStateListeners();
258             }
259 
260             // If the existing SoundModel is different (for the same UUID for Generic and same
261             // keyphrase ID for voice), ensure that it is unloaded and stopped before proceeding.
262             // This works for both keyphrase and generic models. This logic also ensures that a
263             // previously loaded (or started) model is appropriately stopped. Since this is a
264             // generalization of the previous logic with a single keyphrase model, we should have
265             // no regression with the previous version of this code as was given in the
266             // startKeyphrase() routine.
267             if (modelData.getSoundModel() != null) {
268                 boolean stopModel = false; // Stop the model after checking that it is started.
269                 boolean unloadModel = false;
270                 if (modelData.getSoundModel().equals(soundModel) && modelData.isModelStarted()) {
271                     // The model has not changed, but the previous model is "started".
272                     // Stop the previously running model.
273                     stopModel = true;
274                     unloadModel = false; // No need to unload if the model hasn't changed.
275                 } else if (!modelData.getSoundModel().equals(soundModel)) {
276                     // We have a different model for this UUID. Stop and unload if needed. This
277                     // helps maintain the singleton restriction for keyphrase sound models.
278                     stopModel = modelData.isModelStarted();
279                     unloadModel = modelData.isModelLoaded();
280                 }
281                 if (stopModel || unloadModel) {
282                     int status = tryStopAndUnloadLocked(modelData, stopModel, unloadModel);
283                     if (status != STATUS_OK) {
284                         Slog.w(TAG, "Unable to stop or unload previous model: " +
285                                 modelData.toString());
286                         return status;
287                     }
288                 }
289             }
290 
291             IRecognitionStatusCallback oldCallback = modelData.getCallback();
292             if (oldCallback != null && oldCallback.asBinder() != callback.asBinder()) {
293                 Slog.w(TAG, "Canceling previous recognition for model id: " +
294                         modelData.getModelId());
295                 try {
296                     oldCallback.onError(STATUS_ERROR);
297                 } catch (RemoteException e) {
298                     Slog.w(TAG, "RemoteException in onDetectionStopped", e);
299                 }
300                 modelData.clearCallback();
301             }
302 
303             // Load the model if it is not loaded.
304             if (!modelData.isModelLoaded()) {
305                 // Before we try and load this model, we should first make sure that any other
306                 // models that don't have an active recognition/dead callback are unloaded. Since
307                 // there is a finite limit on the number of models that the hardware may be able to
308                 // have loaded, we want to make sure there's room for our model.
309                 stopAndUnloadDeadModelsLocked();
310                 int[] handle = new int[] { INVALID_VALUE };
311                 int status = mModule.loadSoundModel(soundModel, handle);
312                 if (status != SoundTrigger.STATUS_OK) {
313                     Slog.w(TAG, "loadSoundModel call failed with " + status);
314                     return status;
315                 }
316                 if (handle[0] == INVALID_VALUE) {
317                     Slog.w(TAG, "loadSoundModel call returned invalid sound model handle");
318                     return STATUS_ERROR;
319                 }
320                 modelData.setHandle(handle[0]);
321                 modelData.setLoaded();
322                 Slog.d(TAG, "Sound model loaded with handle:" + handle[0]);
323             }
324             modelData.setCallback(callback);
325             modelData.setRequested(true);
326             modelData.setRecognitionConfig(recognitionConfig);
327             modelData.setSoundModel(soundModel);
328 
329             return startRecognitionLocked(modelData,
330                     false /* Don't notify for synchronous calls */);
331         }
332     }
333 
334     /**
335      * Stops recognition for the given generic sound model. This is a wrapper for {@link
336      * #stopRecognition}.
337      *
338      * @param modelId The identifier of the generic sound model for which
339      *        the recognition is to be stopped.
340      * @param callback The callback for the recognition events related to the given sound model.
341      *
342      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
343      */
stopGenericRecognition(UUID modelId, IRecognitionStatusCallback callback)344     int stopGenericRecognition(UUID modelId, IRecognitionStatusCallback callback) {
345         synchronized (mLock) {
346             MetricsLogger.count(mContext, "sth_stop_recognition", 1);
347             if (callback == null || modelId == null) {
348                 Slog.e(TAG, "Null callbackreceived for stopGenericRecognition() for modelid:" +
349                         modelId);
350                 return STATUS_ERROR;
351             }
352 
353             ModelData modelData = mModelDataMap.get(modelId);
354             if (modelData == null || !modelData.isGenericModel()) {
355                 Slog.w(TAG, "Attempting stopRecognition on invalid model with id:" + modelId);
356                 return STATUS_ERROR;
357             }
358 
359             int status = stopRecognition(modelData, callback);
360             if (status != SoundTrigger.STATUS_OK) {
361                 Slog.w(TAG, "stopGenericRecognition failed: " + status);
362             }
363             return status;
364         }
365     }
366 
367     /**
368      * Stops recognition for the given {@link Keyphrase} if a recognition is
369      * currently active. This is a wrapper for {@link #stopRecognition()}.
370      *
371      * @param keyphraseId The identifier of the keyphrase for which
372      *        the recognition is to be stopped.
373      * @param callback The callback for the recognition events related to the given keyphrase.
374      *
375      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
376      */
stopKeyphraseRecognition(int keyphraseId, IRecognitionStatusCallback callback)377     int stopKeyphraseRecognition(int keyphraseId, IRecognitionStatusCallback callback) {
378         synchronized (mLock) {
379             MetricsLogger.count(mContext, "sth_stop_recognition", 1);
380             if (callback == null) {
381                 Slog.e(TAG, "Null callback received for stopKeyphraseRecognition() for keyphraseId:" +
382                         keyphraseId);
383                 return STATUS_ERROR;
384             }
385 
386             ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
387             if (modelData == null || !modelData.isKeyphraseModel()) {
388                 Slog.e(TAG, "No model exists for given keyphrase Id " + keyphraseId);
389                 return STATUS_ERROR;
390             }
391 
392             if (DBG) {
393                 Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId + ", callback =" +
394                         callback.asBinder());
395                 Slog.d(TAG, "current callback=" + (modelData == null ? "null" :
396                             modelData.getCallback().asBinder()));
397             }
398             int status = stopRecognition(modelData, callback);
399             if (status != SoundTrigger.STATUS_OK) {
400                 return status;
401             }
402 
403             return status;
404         }
405     }
406 
407     /**
408      * Stops recognition for the given ModelData instance.
409      *
410      * @param modelData Instance of {@link #ModelData} sound model.
411      * @param callback The callback for the recognition events related to the given keyphrase.
412      * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
413      */
stopRecognition(ModelData modelData, IRecognitionStatusCallback callback)414     private int stopRecognition(ModelData modelData, IRecognitionStatusCallback callback) {
415         synchronized (mLock) {
416             if (callback == null) {
417                 return STATUS_ERROR;
418             }
419             if (mModuleProperties == null || mModule == null) {
420                 Slog.w(TAG, "Attempting stopRecognition without the capability");
421                 return STATUS_ERROR;
422             }
423 
424             IRecognitionStatusCallback currentCallback = modelData.getCallback();
425             if (modelData == null || currentCallback == null ||
426                     (!modelData.isRequested() && !modelData.isModelStarted())) {
427                 // startGenericRecognition hasn't been called or it failed.
428                 Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition");
429                 return STATUS_ERROR;
430             }
431 
432             if (currentCallback.asBinder() != callback.asBinder()) {
433                 // We don't allow a different listener to stop the recognition than the one
434                 // that started it.
435                 Slog.w(TAG, "Attempting stopRecognition for another recognition");
436                 return STATUS_ERROR;
437             }
438 
439             // Request stop recognition via the update() method.
440             modelData.setRequested(false);
441             int status = updateRecognitionLocked(modelData, isRecognitionAllowed(),
442                     false /* don't notify for synchronous calls */);
443             if (status != SoundTrigger.STATUS_OK) {
444                 return status;
445             }
446 
447             // We leave the sound model loaded but not started, this helps us when we start back.
448             // Also clear the internal state once the recognition has been stopped.
449             modelData.setLoaded();
450             modelData.clearCallback();
451             modelData.setRecognitionConfig(null);
452 
453             if (!computeRecognitionRunningLocked()) {
454                 internalClearGlobalStateLocked();
455             }
456 
457             return status;
458         }
459     }
460 
461     // Stop a previously started model if it was started. Optionally, unload if the previous model
462     // is stale and is about to be replaced.
463     // Needs to be called with the mLock held.
tryStopAndUnloadLocked(ModelData modelData, boolean stopModel, boolean unloadModel)464     private int tryStopAndUnloadLocked(ModelData modelData, boolean stopModel,
465             boolean unloadModel) {
466         int status = STATUS_OK;
467         if (modelData.isModelNotLoaded()) {
468             return status;
469         }
470         if (stopModel && modelData.isModelStarted()) {
471             status = stopRecognitionLocked(modelData,
472                     false /* don't notify for synchronous calls */);
473             if (status != SoundTrigger.STATUS_OK) {
474                 Slog.w(TAG, "stopRecognition failed: " + status);
475                 return status;
476             }
477         }
478 
479         if (unloadModel && modelData.isModelLoaded()) {
480             Slog.d(TAG, "Unloading previously loaded stale model.");
481             status = mModule.unloadSoundModel(modelData.getHandle());
482             MetricsLogger.count(mContext, "sth_unloading_stale_model", 1);
483             if (status != SoundTrigger.STATUS_OK) {
484                 Slog.w(TAG, "unloadSoundModel call failed with " + status);
485             } else {
486                 // Clear the ModelData state if successful.
487                 modelData.clearState();
488             }
489         }
490         return status;
491     }
492 
getModuleProperties()493     public ModuleProperties getModuleProperties() {
494         return mModuleProperties;
495     }
496 
unloadKeyphraseSoundModel(int keyphraseId)497     int unloadKeyphraseSoundModel(int keyphraseId) {
498         synchronized (mLock) {
499             MetricsLogger.count(mContext, "sth_unload_keyphrase_sound_model", 1);
500             ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
501             if (mModule == null || modelData == null || modelData.getHandle() == INVALID_VALUE ||
502                     !modelData.isKeyphraseModel()) {
503                 return STATUS_ERROR;
504             }
505 
506             // Stop recognition if it's the current one.
507             modelData.setRequested(false);
508             int status = updateRecognitionLocked(modelData, isRecognitionAllowed(),
509                     false /* don't notify */);
510             if (status != SoundTrigger.STATUS_OK) {
511                 Slog.w(TAG, "Stop recognition failed for keyphrase ID:" + status);
512             }
513 
514             status = mModule.unloadSoundModel(modelData.getHandle());
515             if (status != SoundTrigger.STATUS_OK) {
516                 Slog.w(TAG, "unloadKeyphraseSoundModel call failed with " + status);
517             }
518 
519             // Remove it from existence.
520             removeKeyphraseModelLocked(keyphraseId);
521             return status;
522         }
523     }
524 
unloadGenericSoundModel(UUID modelId)525     int unloadGenericSoundModel(UUID modelId) {
526         synchronized (mLock) {
527             MetricsLogger.count(mContext, "sth_unload_generic_sound_model", 1);
528             if (modelId == null || mModule == null) {
529                 return STATUS_ERROR;
530             }
531             ModelData modelData = mModelDataMap.get(modelId);
532             if (modelData == null || !modelData.isGenericModel()) {
533                 Slog.w(TAG, "Unload error: Attempting unload invalid generic model with id:" +
534                         modelId);
535                 return STATUS_ERROR;
536             }
537             if (!modelData.isModelLoaded()) {
538                 // Nothing to do here.
539                 Slog.i(TAG, "Unload: Given generic model is not loaded:" + modelId);
540                 return STATUS_OK;
541             }
542             if (modelData.isModelStarted()) {
543                 int status = stopRecognitionLocked(modelData,
544                         false /* don't notify for synchronous calls */);
545                 if (status != SoundTrigger.STATUS_OK) {
546                     Slog.w(TAG, "stopGenericRecognition failed: " + status);
547                 }
548             }
549 
550             int status = mModule.unloadSoundModel(modelData.getHandle());
551             if (status != SoundTrigger.STATUS_OK) {
552                 Slog.w(TAG, "unloadGenericSoundModel() call failed with " + status);
553                 Slog.w(TAG, "unloadGenericSoundModel() force-marking model as unloaded.");
554             }
555 
556             // Remove it from existence.
557             mModelDataMap.remove(modelId);
558             if (DBG) dumpModelStateLocked();
559             return status;
560         }
561     }
562 
isRecognitionRequested(UUID modelId)563     boolean isRecognitionRequested(UUID modelId) {
564         synchronized (mLock) {
565             ModelData modelData = mModelDataMap.get(modelId);
566             return modelData != null && modelData.isRequested();
567         }
568     }
569 
getGenericModelState(UUID modelId)570     int getGenericModelState(UUID modelId) {
571         synchronized (mLock) {
572             MetricsLogger.count(mContext, "sth_get_generic_model_state", 1);
573             if (modelId == null || mModule == null) {
574                 return STATUS_ERROR;
575             }
576             ModelData modelData = mModelDataMap.get(modelId);
577             if (modelData == null || !modelData.isGenericModel()) {
578                 Slog.w(TAG, "GetGenericModelState error: Invalid generic model id:" +
579                         modelId);
580                 return STATUS_ERROR;
581             }
582             if (!modelData.isModelLoaded()) {
583                 Slog.i(TAG, "GetGenericModelState: Given generic model is not loaded:" + modelId);
584                 return STATUS_ERROR;
585             }
586             if (!modelData.isModelStarted()) {
587                 Slog.i(TAG, "GetGenericModelState: Given generic model is not started:" + modelId);
588                 return STATUS_ERROR;
589             }
590 
591             return mModule.getModelState(modelData.getHandle());
592         }
593     }
594 
getKeyphraseModelState(UUID modelId)595     int getKeyphraseModelState(UUID modelId) {
596         Slog.w(TAG, "GetKeyphraseModelState error: Not implemented");
597         return STATUS_ERROR;
598     }
599 
600     //---- SoundTrigger.StatusListener methods
601     @Override
onRecognition(RecognitionEvent event)602     public void onRecognition(RecognitionEvent event) {
603         if (event == null) {
604             Slog.w(TAG, "Null recognition event!");
605             return;
606         }
607 
608         if (!(event instanceof KeyphraseRecognitionEvent) &&
609                 !(event instanceof GenericRecognitionEvent)) {
610             Slog.w(TAG, "Invalid recognition event type (not one of generic or keyphrase)!");
611             return;
612         }
613 
614         if (DBG) Slog.d(TAG, "onRecognition: " + event);
615         synchronized (mLock) {
616             switch (event.status) {
617                 case SoundTrigger.RECOGNITION_STATUS_ABORT:
618                     onRecognitionAbortLocked(event);
619                     break;
620                 case SoundTrigger.RECOGNITION_STATUS_FAILURE:
621                     // Fire failures to all listeners since it's not tied to a keyphrase.
622                     onRecognitionFailureLocked();
623                     break;
624                 case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
625                 case SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE:
626                     if (isKeyphraseRecognitionEvent(event)) {
627                         onKeyphraseRecognitionSuccessLocked((KeyphraseRecognitionEvent) event);
628                     } else {
629                         onGenericRecognitionSuccessLocked((GenericRecognitionEvent) event);
630                     }
631                     break;
632             }
633         }
634     }
635 
isKeyphraseRecognitionEvent(RecognitionEvent event)636     private boolean isKeyphraseRecognitionEvent(RecognitionEvent event) {
637         return event instanceof KeyphraseRecognitionEvent;
638     }
639 
onGenericRecognitionSuccessLocked(GenericRecognitionEvent event)640     private void onGenericRecognitionSuccessLocked(GenericRecognitionEvent event) {
641         MetricsLogger.count(mContext, "sth_generic_recognition_event", 1);
642         if (event.status != SoundTrigger.RECOGNITION_STATUS_SUCCESS
643                 && event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
644             return;
645         }
646         ModelData model = getModelDataForLocked(event.soundModelHandle);
647         if (model == null || !model.isGenericModel()) {
648             Slog.w(TAG, "Generic recognition event: Model does not exist for handle: " +
649                     event.soundModelHandle);
650             return;
651         }
652 
653         IRecognitionStatusCallback callback = model.getCallback();
654         if (callback == null) {
655             Slog.w(TAG, "Generic recognition event: Null callback for model handle: " +
656                     event.soundModelHandle);
657             return;
658         }
659 
660         model.setStopped();
661 
662         try {
663             callback.onGenericSoundTriggerDetected((GenericRecognitionEvent) event);
664         } catch (DeadObjectException e) {
665             forceStopAndUnloadModelLocked(model, e);
666             return;
667         } catch (RemoteException e) {
668             Slog.w(TAG, "RemoteException in onGenericSoundTriggerDetected", e);
669         }
670 
671         RecognitionConfig config = model.getRecognitionConfig();
672         if (config == null) {
673             Slog.w(TAG, "Generic recognition event: Null RecognitionConfig for model handle: " +
674                     event.soundModelHandle);
675             return;
676         }
677 
678         model.setRequested(config.allowMultipleTriggers);
679         // TODO: Remove this block if the lower layer supports multiple triggers.
680         if (model.isRequested()) {
681             updateRecognitionLocked(model, isRecognitionAllowed() /* isAllowed */,
682                     true /* notify */);
683         }
684     }
685 
686     @Override
onSoundModelUpdate(SoundModelEvent event)687     public void onSoundModelUpdate(SoundModelEvent event) {
688         if (event == null) {
689             Slog.w(TAG, "Invalid sound model event!");
690             return;
691         }
692         if (DBG) Slog.d(TAG, "onSoundModelUpdate: " + event);
693         synchronized (mLock) {
694             MetricsLogger.count(mContext, "sth_sound_model_updated", 1);
695             onSoundModelUpdatedLocked(event);
696         }
697     }
698 
699     @Override
onServiceStateChange(int state)700     public void onServiceStateChange(int state) {
701         if (DBG) Slog.d(TAG, "onServiceStateChange, state: " + state);
702         synchronized (mLock) {
703             onServiceStateChangedLocked(SoundTrigger.SERVICE_STATE_DISABLED == state);
704         }
705     }
706 
707     @Override
onServiceDied()708     public void onServiceDied() {
709         Slog.e(TAG, "onServiceDied!!");
710         MetricsLogger.count(mContext, "sth_service_died", 1);
711         synchronized (mLock) {
712             onServiceDiedLocked();
713         }
714     }
715 
onCallStateChangedLocked(boolean callActive)716     private void onCallStateChangedLocked(boolean callActive) {
717         if (mCallActive == callActive) {
718             // We consider multiple call states as being active
719             // so we check if something really changed or not here.
720             return;
721         }
722         mCallActive = callActive;
723         updateAllRecognitionsLocked(true /* notify */);
724     }
725 
onPowerSaveModeChangedLocked(boolean isPowerSaveMode)726     private void onPowerSaveModeChangedLocked(boolean isPowerSaveMode) {
727         if (mIsPowerSaveMode == isPowerSaveMode) {
728             return;
729         }
730         mIsPowerSaveMode = isPowerSaveMode;
731         updateAllRecognitionsLocked(true /* notify */);
732     }
733 
onSoundModelUpdatedLocked(SoundModelEvent event)734     private void onSoundModelUpdatedLocked(SoundModelEvent event) {
735         // TODO: Handle sound model update here.
736     }
737 
onServiceStateChangedLocked(boolean disabled)738     private void onServiceStateChangedLocked(boolean disabled) {
739         if (disabled == mServiceDisabled) {
740             return;
741         }
742         mServiceDisabled = disabled;
743         updateAllRecognitionsLocked(true /* notify */);
744     }
745 
onRecognitionAbortLocked(RecognitionEvent event)746     private void onRecognitionAbortLocked(RecognitionEvent event) {
747         Slog.w(TAG, "Recognition aborted");
748         MetricsLogger.count(mContext, "sth_recognition_aborted", 1);
749         ModelData modelData = getModelDataForLocked(event.soundModelHandle);
750         if (modelData != null && modelData.isModelStarted()) {
751             modelData.setStopped();
752             try {
753                 modelData.getCallback().onRecognitionPaused();
754             } catch (DeadObjectException e) {
755                 forceStopAndUnloadModelLocked(modelData, e);
756             } catch (RemoteException e) {
757                 Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
758             }
759         }
760     }
761 
onRecognitionFailureLocked()762     private void onRecognitionFailureLocked() {
763         Slog.w(TAG, "Recognition failure");
764         MetricsLogger.count(mContext, "sth_recognition_failure_event", 1);
765         try {
766             sendErrorCallbacksToAllLocked(STATUS_ERROR);
767         } finally {
768             internalClearModelStateLocked();
769             internalClearGlobalStateLocked();
770         }
771     }
772 
getKeyphraseIdFromEvent(KeyphraseRecognitionEvent event)773     private int getKeyphraseIdFromEvent(KeyphraseRecognitionEvent event) {
774         if (event == null) {
775             Slog.w(TAG, "Null RecognitionEvent received.");
776             return INVALID_VALUE;
777         }
778         KeyphraseRecognitionExtra[] keyphraseExtras =
779                 ((KeyphraseRecognitionEvent) event).keyphraseExtras;
780         if (keyphraseExtras == null || keyphraseExtras.length == 0) {
781             Slog.w(TAG, "Invalid keyphrase recognition event!");
782             return INVALID_VALUE;
783         }
784         // TODO: Handle more than one keyphrase extras.
785         return keyphraseExtras[0].id;
786     }
787 
onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event)788     private void onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event) {
789         Slog.i(TAG, "Recognition success");
790         MetricsLogger.count(mContext, "sth_keyphrase_recognition_event", 1);
791         int keyphraseId = getKeyphraseIdFromEvent(event);
792         ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
793 
794         if (modelData == null || !modelData.isKeyphraseModel()) {
795             Slog.e(TAG, "Keyphase model data does not exist for ID:" + keyphraseId);
796             return;
797         }
798 
799         if (modelData.getCallback() == null) {
800             Slog.w(TAG, "Received onRecognition event without callback for keyphrase model.");
801             return;
802         }
803 
804         modelData.setStopped();
805 
806         try {
807             modelData.getCallback().onKeyphraseDetected((KeyphraseRecognitionEvent) event);
808         } catch (DeadObjectException e) {
809             forceStopAndUnloadModelLocked(modelData, e);
810             return;
811         } catch (RemoteException e) {
812             Slog.w(TAG, "RemoteException in onKeyphraseDetected", e);
813         }
814 
815         RecognitionConfig config = modelData.getRecognitionConfig();
816         if (config != null) {
817             // Whether we should continue by starting this again.
818             modelData.setRequested(config.allowMultipleTriggers);
819         }
820         // TODO: Remove this block if the lower layer supports multiple triggers.
821         if (modelData.isRequested()) {
822             updateRecognitionLocked(modelData, isRecognitionAllowed(), true /* notify */);
823         }
824     }
825 
updateAllRecognitionsLocked(boolean notify)826     private void updateAllRecognitionsLocked(boolean notify) {
827         boolean isAllowed = isRecognitionAllowed();
828         // updateRecognitionLocked can possibly update the list of models
829         ArrayList<ModelData> modelDatas = new ArrayList<ModelData>(mModelDataMap.values());
830         for (ModelData modelData : modelDatas) {
831             updateRecognitionLocked(modelData, isAllowed, notify);
832         }
833     }
834 
updateRecognitionLocked(ModelData model, boolean isAllowed, boolean notify)835     private int updateRecognitionLocked(ModelData model, boolean isAllowed,
836         boolean notify) {
837         boolean start = model.isRequested() && isAllowed;
838         if (start == model.isModelStarted()) {
839             // No-op.
840             return STATUS_OK;
841         }
842         if (start) {
843             return startRecognitionLocked(model, notify);
844         } else {
845             return stopRecognitionLocked(model, notify);
846         }
847     }
848 
onServiceDiedLocked()849     private void onServiceDiedLocked() {
850         try {
851             MetricsLogger.count(mContext, "sth_service_died", 1);
852             sendErrorCallbacksToAllLocked(SoundTrigger.STATUS_DEAD_OBJECT);
853         } finally {
854             internalClearModelStateLocked();
855             internalClearGlobalStateLocked();
856             if (mModule != null) {
857                 mModule.detach();
858                 mModule = null;
859             }
860         }
861     }
862 
863     // internalClearGlobalStateLocked() cleans up the telephony and power save listeners.
internalClearGlobalStateLocked()864     private void internalClearGlobalStateLocked() {
865         // Unregister from call state changes.
866         long token = Binder.clearCallingIdentity();
867         try {
868             mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
869         } finally {
870             Binder.restoreCallingIdentity(token);
871         }
872 
873         // Unregister from power save mode changes.
874         if (mPowerSaveModeListener != null) {
875             mContext.unregisterReceiver(mPowerSaveModeListener);
876             mPowerSaveModeListener = null;
877         }
878     }
879 
880     // Clears state for all models (generic and keyphrase).
internalClearModelStateLocked()881     private void internalClearModelStateLocked() {
882         for (ModelData modelData : mModelDataMap.values()) {
883             modelData.clearState();
884         }
885     }
886 
887     class MyCallStateListener extends PhoneStateListener {
888         @Override
onCallStateChanged(int state, String arg1)889         public void onCallStateChanged(int state, String arg1) {
890             if (DBG) Slog.d(TAG, "onCallStateChanged: " + state);
891             synchronized (mLock) {
892                 onCallStateChangedLocked(TelephonyManager.CALL_STATE_OFFHOOK == state);
893             }
894         }
895     }
896 
897     class PowerSaveModeListener extends BroadcastReceiver {
898         @Override
onReceive(Context context, Intent intent)899         public void onReceive(Context context, Intent intent) {
900             if (!PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) {
901                 return;
902             }
903             boolean active = mPowerManager.getPowerSaveState(ServiceType.SOUND)
904                     .batterySaverEnabled;
905             if (DBG) Slog.d(TAG, "onPowerSaveModeChanged: " + active);
906             synchronized (mLock) {
907                 onPowerSaveModeChangedLocked(active);
908             }
909         }
910     }
911 
dump(FileDescriptor fd, PrintWriter pw, String[] args)912     void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
913         synchronized (mLock) {
914             pw.print("  module properties=");
915             pw.println(mModuleProperties == null ? "null" : mModuleProperties);
916 
917             pw.print("  call active="); pw.println(mCallActive);
918             pw.print("  power save mode active="); pw.println(mIsPowerSaveMode);
919             pw.print("  service disabled="); pw.println(mServiceDisabled);
920         }
921     }
922 
initializeTelephonyAndPowerStateListeners()923     private void initializeTelephonyAndPowerStateListeners() {
924         long token = Binder.clearCallingIdentity();
925         try {
926             // Get the current call state synchronously for the first recognition.
927             mCallActive = mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK;
928 
929             // Register for call state changes when the first call to start recognition occurs.
930             mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
931 
932             // Register for power saver mode changes when the first call to start recognition
933             // occurs.
934             if (mPowerSaveModeListener == null) {
935                 mPowerSaveModeListener = new PowerSaveModeListener();
936                 mContext.registerReceiver(mPowerSaveModeListener,
937                         new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
938             }
939             mIsPowerSaveMode = mPowerManager.getPowerSaveState(ServiceType.SOUND)
940                     .batterySaverEnabled;
941         } finally {
942             Binder.restoreCallingIdentity(token);
943         }
944     }
945 
946     // Sends an error callback to all models with a valid registered callback.
sendErrorCallbacksToAllLocked(int errorCode)947     private void sendErrorCallbacksToAllLocked(int errorCode) {
948         for (ModelData modelData : mModelDataMap.values()) {
949             IRecognitionStatusCallback callback = modelData.getCallback();
950             if (callback != null) {
951                 try {
952                     callback.onError(errorCode);
953                 } catch (RemoteException e) {
954                     Slog.w(TAG, "RemoteException sendErrorCallbacksToAllLocked for model handle " +
955                             modelData.getHandle(), e);
956                 }
957             }
958         }
959     }
960 
961     /**
962      * Stops and unloads a sound model, and removes any reference to the model if successful.
963      *
964      * @param modelData The model data to remove.
965      * @param exception Optional exception to print in logcat. May be null.
966      */
forceStopAndUnloadModelLocked(ModelData modelData, Exception exception)967     private void forceStopAndUnloadModelLocked(ModelData modelData, Exception exception) {
968       forceStopAndUnloadModelLocked(modelData, exception, null /* modelDataIterator */);
969     }
970 
971     /**
972      * Stops and unloads a sound model, and removes any reference to the model if successful.
973      *
974      * @param modelData The model data to remove.
975      * @param exception Optional exception to print in logcat. May be null.
976      * @param modelDataIterator If this function is to be used while iterating over the
977      *        mModelDataMap, you can provide the iterator for the current model data to be used to
978      *        remove the modelData from the map. This avoids generating a
979      *        ConcurrentModificationException, since this function will try and remove the model
980      *        data from the mModelDataMap when it can successfully unload the model.
981      */
forceStopAndUnloadModelLocked(ModelData modelData, Exception exception, Iterator modelDataIterator)982     private void forceStopAndUnloadModelLocked(ModelData modelData, Exception exception,
983             Iterator modelDataIterator) {
984         if (exception != null) {
985           Slog.e(TAG, "forceStopAndUnloadModel", exception);
986         }
987         if (modelData.isModelStarted()) {
988             Slog.d(TAG, "Stopping previously started dangling model " + modelData.getHandle());
989             if (mModule.stopRecognition(modelData.getHandle()) != STATUS_OK) {
990                 modelData.setStopped();
991                 modelData.setRequested(false);
992             } else {
993                 Slog.e(TAG, "Failed to stop model " + modelData.getHandle());
994             }
995         }
996         if (modelData.isModelLoaded()) {
997             Slog.d(TAG, "Unloading previously loaded dangling model " + modelData.getHandle());
998             if (mModule.unloadSoundModel(modelData.getHandle()) == STATUS_OK) {
999                 // Remove the model data from existence.
1000                 if (modelDataIterator != null) {
1001                     modelDataIterator.remove();
1002                 } else {
1003                     mModelDataMap.remove(modelData.getModelId());
1004                 }
1005                 Iterator it = mKeyphraseUuidMap.entrySet().iterator();
1006                 while (it.hasNext()) {
1007                     Map.Entry pair = (Map.Entry) it.next();
1008                     if (pair.getValue().equals(modelData.getModelId())) {
1009                         it.remove();
1010                     }
1011                 }
1012                 modelData.clearState();
1013             } else {
1014                 Slog.e(TAG, "Failed to unload model " + modelData.getHandle());
1015             }
1016         }
1017     }
1018 
stopAndUnloadDeadModelsLocked()1019     private void stopAndUnloadDeadModelsLocked() {
1020         Iterator it = mModelDataMap.entrySet().iterator();
1021         while (it.hasNext()) {
1022             ModelData modelData = (ModelData) ((Map.Entry) it.next()).getValue();
1023             if (!modelData.isModelLoaded()) {
1024                 continue;
1025             }
1026             if (modelData.getCallback() == null
1027                     || (modelData.getCallback().asBinder() != null
1028                         && !modelData.getCallback().asBinder().pingBinder())) {
1029                 // No one is listening on this model, so we might as well evict it.
1030                 Slog.w(TAG, "Removing model " + modelData.getHandle() + " that has no clients");
1031                 forceStopAndUnloadModelLocked(modelData, null /* exception */, it);
1032             }
1033         }
1034     }
1035 
getOrCreateGenericModelDataLocked(UUID modelId)1036     private ModelData getOrCreateGenericModelDataLocked(UUID modelId) {
1037         ModelData modelData = mModelDataMap.get(modelId);
1038         if (modelData == null) {
1039             modelData = ModelData.createGenericModelData(modelId);
1040             mModelDataMap.put(modelId, modelData);
1041         } else if (!modelData.isGenericModel()) {
1042             Slog.e(TAG, "UUID already used for non-generic model.");
1043             return null;
1044         }
1045         return modelData;
1046     }
1047 
removeKeyphraseModelLocked(int keyphraseId)1048     private void removeKeyphraseModelLocked(int keyphraseId) {
1049         UUID uuid = mKeyphraseUuidMap.get(keyphraseId);
1050         if (uuid == null) {
1051             return;
1052         }
1053         mModelDataMap.remove(uuid);
1054         mKeyphraseUuidMap.remove(keyphraseId);
1055     }
1056 
getKeyphraseModelDataLocked(int keyphraseId)1057     private ModelData getKeyphraseModelDataLocked(int keyphraseId) {
1058         UUID uuid = mKeyphraseUuidMap.get(keyphraseId);
1059         if (uuid == null) {
1060             return null;
1061         }
1062         return mModelDataMap.get(uuid);
1063     }
1064 
1065     // Use this to create a new ModelData entry for a keyphrase Id. It will overwrite existing
1066     // mapping if one exists.
createKeyphraseModelDataLocked(UUID modelId, int keyphraseId)1067     private ModelData createKeyphraseModelDataLocked(UUID modelId, int keyphraseId) {
1068         mKeyphraseUuidMap.remove(keyphraseId);
1069         mModelDataMap.remove(modelId);
1070         mKeyphraseUuidMap.put(keyphraseId, modelId);
1071         ModelData modelData = ModelData.createKeyphraseModelData(modelId);
1072         mModelDataMap.put(modelId, modelData);
1073         return modelData;
1074     }
1075 
1076     // Instead of maintaining a second hashmap of modelHandle -> ModelData, we just
1077     // iterate through to find the right object (since we don't expect 100s of models
1078     // to be stored).
getModelDataForLocked(int modelHandle)1079     private ModelData getModelDataForLocked(int modelHandle) {
1080         // Fetch ModelData object corresponding to the model handle.
1081         for (ModelData model : mModelDataMap.values()) {
1082             if (model.getHandle() == modelHandle) {
1083                 return model;
1084             }
1085         }
1086         return null;
1087     }
1088 
1089     // Whether we are allowed to run any recognition at all. The conditions that let us run
1090     // a recognition include: no active phone call or not being in a power save mode. Also,
1091     // the native service should be enabled.
isRecognitionAllowed()1092     private boolean isRecognitionAllowed() {
1093         return !mCallActive && !mServiceDisabled && !mIsPowerSaveMode;
1094     }
1095 
1096     // A single routine that implements the start recognition logic for both generic and keyphrase
1097     // models.
startRecognitionLocked(ModelData modelData, boolean notify)1098     private int startRecognitionLocked(ModelData modelData, boolean notify) {
1099         IRecognitionStatusCallback callback = modelData.getCallback();
1100         int handle = modelData.getHandle();
1101         RecognitionConfig config = modelData.getRecognitionConfig();
1102         if (callback == null || handle == INVALID_VALUE || config == null) {
1103             // Nothing to do here.
1104             Slog.w(TAG, "startRecognition: Bad data passed in.");
1105             MetricsLogger.count(mContext, "sth_start_recognition_error", 1);
1106             return STATUS_ERROR;
1107         }
1108 
1109         if (!isRecognitionAllowed()) {
1110             // Nothing to do here.
1111             Slog.w(TAG, "startRecognition requested but not allowed.");
1112             MetricsLogger.count(mContext, "sth_start_recognition_not_allowed", 1);
1113             return STATUS_OK;
1114         }
1115 
1116         int status = mModule.startRecognition(handle, config);
1117         if (status != SoundTrigger.STATUS_OK) {
1118             Slog.w(TAG, "startRecognition failed with " + status);
1119             MetricsLogger.count(mContext, "sth_start_recognition_error", 1);
1120             // Notify of error if needed.
1121             if (notify) {
1122                 try {
1123                     callback.onError(status);
1124                 } catch (DeadObjectException e) {
1125                     forceStopAndUnloadModelLocked(modelData, e);
1126                 } catch (RemoteException e) {
1127                     Slog.w(TAG, "RemoteException in onError", e);
1128                 }
1129             }
1130         } else {
1131             Slog.i(TAG, "startRecognition successful.");
1132             MetricsLogger.count(mContext, "sth_start_recognition_success", 1);
1133             modelData.setStarted();
1134             // Notify of resume if needed.
1135             if (notify) {
1136                 try {
1137                     callback.onRecognitionResumed();
1138                 } catch (DeadObjectException e) {
1139                     forceStopAndUnloadModelLocked(modelData, e);
1140                 } catch (RemoteException e) {
1141                     Slog.w(TAG, "RemoteException in onRecognitionResumed", e);
1142                 }
1143             }
1144         }
1145         if (DBG) {
1146             Slog.d(TAG, "Model being started :" + modelData.toString());
1147         }
1148         return status;
1149     }
1150 
stopRecognitionLocked(ModelData modelData, boolean notify)1151     private int stopRecognitionLocked(ModelData modelData, boolean notify) {
1152         IRecognitionStatusCallback callback = modelData.getCallback();
1153 
1154         // Stop recognition.
1155         int status = STATUS_OK;
1156 
1157         status = mModule.stopRecognition(modelData.getHandle());
1158 
1159         if (status != SoundTrigger.STATUS_OK) {
1160             Slog.w(TAG, "stopRecognition call failed with " + status);
1161             MetricsLogger.count(mContext, "sth_stop_recognition_error", 1);
1162             if (notify) {
1163                 try {
1164                     callback.onError(status);
1165                 } catch (DeadObjectException e) {
1166                     forceStopAndUnloadModelLocked(modelData, e);
1167                 } catch (RemoteException e) {
1168                     Slog.w(TAG, "RemoteException in onError", e);
1169                 }
1170             }
1171         } else {
1172             modelData.setStopped();
1173             MetricsLogger.count(mContext, "sth_stop_recognition_success", 1);
1174             // Notify of pause if needed.
1175             if (notify) {
1176                 try {
1177                     callback.onRecognitionPaused();
1178                 } catch (DeadObjectException e) {
1179                     forceStopAndUnloadModelLocked(modelData, e);
1180                 } catch (RemoteException e) {
1181                     Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
1182                 }
1183             }
1184         }
1185         if (DBG) {
1186             Slog.d(TAG, "Model being stopped :" + modelData.toString());
1187         }
1188         return status;
1189     }
1190 
dumpModelStateLocked()1191     private void dumpModelStateLocked() {
1192         for (UUID modelId : mModelDataMap.keySet()) {
1193             ModelData modelData = mModelDataMap.get(modelId);
1194             Slog.i(TAG, "Model :" + modelData.toString());
1195         }
1196     }
1197 
1198     // Computes whether we have any recognition running at all (voice or generic). Sets
1199     // the mRecognitionRunning variable with the result.
computeRecognitionRunningLocked()1200     private boolean computeRecognitionRunningLocked() {
1201         if (mModuleProperties == null || mModule == null) {
1202             mRecognitionRunning = false;
1203             return mRecognitionRunning;
1204         }
1205         for (ModelData modelData : mModelDataMap.values()) {
1206             if (modelData.isModelStarted()) {
1207                 mRecognitionRunning = true;
1208                 return mRecognitionRunning;
1209             }
1210         }
1211         mRecognitionRunning = false;
1212         return mRecognitionRunning;
1213     }
1214 
1215     // This class encapsulates the callbacks, state, handles and any other information that
1216     // represents a model.
1217     private static class ModelData {
1218         // Model not loaded (and hence not started).
1219         static final int MODEL_NOTLOADED = 0;
1220 
1221         // Loaded implies model was successfully loaded. Model not started yet.
1222         static final int MODEL_LOADED = 1;
1223 
1224         // Started implies model was successfully loaded and start was called.
1225         static final int MODEL_STARTED = 2;
1226 
1227         // One of MODEL_NOTLOADED, MODEL_LOADED, MODEL_STARTED (which implies loaded).
1228         private int mModelState;
1229         private UUID mModelId;
1230 
1231         // mRequested captures the explicit intent that a start was requested for this model. We
1232         // continue to capture and retain this state even after the model gets started, so that we
1233         // know when a model gets stopped due to "other" reasons, that we should start it again.
1234         // This was the intended behavior of the "mRequested" variable in the previous version of
1235         // this code that we are replicating here.
1236         //
1237         // The "other" reasons include power save, abort being called from the lower layer (due
1238         // to concurrent capture not being supported) and phone call state. Once we recover from
1239         // these transient disruptions, we would start such models again where mRequested == true.
1240         // Thus, mRequested gets reset only when there is an explicit intent to stop the model
1241         // coming from the SoundTriggerService layer that uses this class (and thus eventually
1242         // from the app that manages this model).
1243         private boolean mRequested = false;
1244 
1245         // One of SoundModel.TYPE_GENERIC or SoundModel.TYPE_KEYPHRASE. Initially set
1246         // to SoundModel.TYPE_UNKNOWN;
1247         private int mModelType = SoundModel.TYPE_UNKNOWN;
1248 
1249         private IRecognitionStatusCallback mCallback = null;
1250         private RecognitionConfig mRecognitionConfig = null;
1251 
1252         // Model handle is an integer used by the HAL as an identifier for sound
1253         // models.
1254         private int mModelHandle = INVALID_VALUE;
1255 
1256         // The SoundModel instance, one of KeyphraseSoundModel or GenericSoundModel.
1257         private SoundModel mSoundModel = null;
1258 
ModelData(UUID modelId, int modelType)1259         private ModelData(UUID modelId, int modelType) {
1260             mModelId = modelId;
1261             // Private constructor, since we require modelType to be one of TYPE_GENERIC,
1262             // TYPE_KEYPHRASE or TYPE_UNKNOWN.
1263             mModelType = modelType;
1264         }
1265 
createKeyphraseModelData(UUID modelId)1266         static ModelData createKeyphraseModelData(UUID modelId) {
1267             return new ModelData(modelId, SoundModel.TYPE_KEYPHRASE);
1268         }
1269 
createGenericModelData(UUID modelId)1270         static ModelData createGenericModelData(UUID modelId) {
1271             return new ModelData(modelId, SoundModel.TYPE_GENERIC_SOUND);
1272         }
1273 
1274         // Note that most of the functionality in this Java class will not work for
1275         // SoundModel.TYPE_UNKNOWN nevertheless we have it since lower layers support it.
createModelDataOfUnknownType(UUID modelId)1276         static ModelData createModelDataOfUnknownType(UUID modelId) {
1277             return new ModelData(modelId, SoundModel.TYPE_UNKNOWN);
1278         }
1279 
setCallback(IRecognitionStatusCallback callback)1280         synchronized void setCallback(IRecognitionStatusCallback callback) {
1281             mCallback = callback;
1282         }
1283 
getCallback()1284         synchronized IRecognitionStatusCallback getCallback() {
1285             return mCallback;
1286         }
1287 
isModelLoaded()1288         synchronized boolean isModelLoaded() {
1289             return (mModelState == MODEL_LOADED || mModelState == MODEL_STARTED);
1290         }
1291 
isModelNotLoaded()1292         synchronized boolean isModelNotLoaded() {
1293             return mModelState == MODEL_NOTLOADED;
1294         }
1295 
setStarted()1296         synchronized void setStarted() {
1297             mModelState = MODEL_STARTED;
1298         }
1299 
setStopped()1300         synchronized void setStopped() {
1301             mModelState = MODEL_LOADED;
1302         }
1303 
setLoaded()1304         synchronized void setLoaded() {
1305             mModelState = MODEL_LOADED;
1306         }
1307 
isModelStarted()1308         synchronized boolean isModelStarted() {
1309             return mModelState == MODEL_STARTED;
1310         }
1311 
clearState()1312         synchronized void clearState() {
1313             mModelState = MODEL_NOTLOADED;
1314             mModelHandle = INVALID_VALUE;
1315             mRecognitionConfig = null;
1316             mRequested = false;
1317             mCallback = null;
1318         }
1319 
clearCallback()1320         synchronized void clearCallback() {
1321             mCallback = null;
1322         }
1323 
setHandle(int handle)1324         synchronized void setHandle(int handle) {
1325             mModelHandle = handle;
1326         }
1327 
setRecognitionConfig(RecognitionConfig config)1328         synchronized void setRecognitionConfig(RecognitionConfig config) {
1329             mRecognitionConfig = config;
1330         }
1331 
getHandle()1332         synchronized int getHandle() {
1333             return mModelHandle;
1334         }
1335 
getModelId()1336         synchronized UUID getModelId() {
1337             return mModelId;
1338         }
1339 
getRecognitionConfig()1340         synchronized RecognitionConfig getRecognitionConfig() {
1341             return mRecognitionConfig;
1342         }
1343 
1344         // Whether a start recognition was requested.
isRequested()1345         synchronized boolean isRequested() {
1346             return mRequested;
1347         }
1348 
setRequested(boolean requested)1349         synchronized void setRequested(boolean requested) {
1350             mRequested = requested;
1351         }
1352 
setSoundModel(SoundModel soundModel)1353         synchronized void setSoundModel(SoundModel soundModel) {
1354             mSoundModel = soundModel;
1355         }
1356 
getSoundModel()1357         synchronized SoundModel getSoundModel() {
1358             return mSoundModel;
1359         }
1360 
getModelType()1361         synchronized int getModelType() {
1362             return mModelType;
1363         }
1364 
isKeyphraseModel()1365         synchronized boolean isKeyphraseModel() {
1366             return mModelType == SoundModel.TYPE_KEYPHRASE;
1367         }
1368 
isGenericModel()1369         synchronized boolean isGenericModel() {
1370             return mModelType == SoundModel.TYPE_GENERIC_SOUND;
1371         }
1372 
stateToString()1373         synchronized String stateToString() {
1374             switch(mModelState) {
1375                 case MODEL_NOTLOADED: return "NOT_LOADED";
1376                 case MODEL_LOADED: return "LOADED";
1377                 case MODEL_STARTED: return "STARTED";
1378             }
1379             return "Unknown state";
1380         }
1381 
requestedToString()1382         synchronized String requestedToString() {
1383             return "Requested: " + (mRequested ? "Yes" : "No");
1384         }
1385 
callbackToString()1386         synchronized String callbackToString() {
1387             return "Callback: " + (mCallback != null ? mCallback.asBinder() : "null");
1388         }
1389 
uuidToString()1390         synchronized String uuidToString() {
1391             return "UUID: " + mModelId;
1392         }
1393 
toString()1394         synchronized public String toString() {
1395             return "Handle: " + mModelHandle + "\n" +
1396                     "ModelState: " + stateToString() + "\n" +
1397                     requestedToString() + "\n" +
1398                     callbackToString() + "\n" +
1399                     uuidToString() + "\n" + modelTypeToString();
1400         }
1401 
modelTypeToString()1402         synchronized String modelTypeToString() {
1403             String type = null;
1404             switch (mModelType) {
1405                 case SoundModel.TYPE_GENERIC_SOUND: type = "Generic"; break;
1406                 case SoundModel.TYPE_UNKNOWN: type = "Unknown"; break;
1407                 case SoundModel.TYPE_KEYPHRASE: type = "Keyphrase"; break;
1408             }
1409             return "Model type: " + type + "\n";
1410         }
1411     }
1412 }
1413