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