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