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