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
17package com.android.server.soundtrigger;
18
19import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.hardware.soundtrigger.IRecognitionStatusCallback;
24import android.hardware.soundtrigger.SoundTrigger;
25import android.hardware.soundtrigger.SoundTrigger.GenericRecognitionEvent;
26import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
27import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
28import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
29import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
30import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
31import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
32import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
33import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
34import android.hardware.soundtrigger.SoundTrigger.SoundModel;
35import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
36import android.hardware.soundtrigger.SoundTriggerModule;
37import android.os.DeadObjectException;
38import android.os.PowerManager;
39import android.os.RemoteException;
40import android.telephony.PhoneStateListener;
41import android.telephony.TelephonyManager;
42import android.util.Slog;
43import com.android.internal.logging.MetricsLogger;
44import com.android.server.power.BatterySaverPolicy.ServiceType;
45
46import java.io.FileDescriptor;
47import java.io.PrintWriter;
48import java.util.ArrayList;
49import java.util.HashMap;
50import java.util.Iterator;
51import java.util.Map;
52import 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 */
64public 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
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     */
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     */
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
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     */
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     */
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     */
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     */
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.
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
487    public ModuleProperties getModuleProperties() {
488        return mModuleProperties;
489    }
490
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
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
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
592    private boolean isKeyphraseRecognitionEvent(RecognitionEvent event) {
593        return event instanceof KeyphraseRecognitionEvent;
594    }
595
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
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
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
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
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
680    private void onPowerSaveModeChangedLocked(boolean isPowerSaveMode) {
681        if (mIsPowerSaveMode == isPowerSaveMode) {
682            return;
683        }
684        mIsPowerSaveMode = isPowerSaveMode;
685        updateAllRecognitionsLocked(true /* notify */);
686    }
687
688    private void onSoundModelUpdatedLocked(SoundModelEvent event) {
689        // TODO: Handle sound model update here.
690    }
691
692    private void onServiceStateChangedLocked(boolean disabled) {
693        if (disabled == mServiceDisabled) {
694            return;
695        }
696        mServiceDisabled = disabled;
697        updateAllRecognitionsLocked(true /* notify */);
698    }
699
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
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
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
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
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
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
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.
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).
829    private void internalClearModelStateLocked() {
830        for (ModelData modelData : mModelDataMap.values()) {
831            modelData.clearState();
832        }
833    }
834
835    class MyCallStateListener extends PhoneStateListener {
836        @Override
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
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
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
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.
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
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
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
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
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.
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).
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.
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.
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
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
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.
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
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
1166        static ModelData createKeyphraseModelData(UUID modelId) {
1167            return new ModelData(modelId, SoundModel.TYPE_KEYPHRASE);
1168        }
1169
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.
1176        static ModelData createModelDataOfUnknownType(UUID modelId) {
1177            return new ModelData(modelId, SoundModel.TYPE_UNKNOWN);
1178        }
1179
1180        synchronized void setCallback(IRecognitionStatusCallback callback) {
1181            mCallback = callback;
1182        }
1183
1184        synchronized IRecognitionStatusCallback getCallback() {
1185            return mCallback;
1186        }
1187
1188        synchronized boolean isModelLoaded() {
1189            return (mModelState == MODEL_LOADED || mModelState == MODEL_STARTED);
1190        }
1191
1192        synchronized boolean isModelNotLoaded() {
1193            return mModelState == MODEL_NOTLOADED;
1194        }
1195
1196        synchronized void setStarted() {
1197            mModelState = MODEL_STARTED;
1198        }
1199
1200        synchronized void setStopped() {
1201            mModelState = MODEL_LOADED;
1202        }
1203
1204        synchronized void setLoaded() {
1205            mModelState = MODEL_LOADED;
1206        }
1207
1208        synchronized boolean isModelStarted() {
1209            return mModelState == MODEL_STARTED;
1210        }
1211
1212        synchronized void clearState() {
1213            mModelState = MODEL_NOTLOADED;
1214            mModelHandle = INVALID_VALUE;
1215            mRecognitionConfig = null;
1216            mRequested = false;
1217            mCallback = null;
1218        }
1219
1220        synchronized void clearCallback() {
1221            mCallback = null;
1222        }
1223
1224        synchronized void setHandle(int handle) {
1225            mModelHandle = handle;
1226        }
1227
1228        synchronized void setRecognitionConfig(RecognitionConfig config) {
1229            mRecognitionConfig = config;
1230        }
1231
1232        synchronized int getHandle() {
1233            return mModelHandle;
1234        }
1235
1236        synchronized UUID getModelId() {
1237            return mModelId;
1238        }
1239
1240        synchronized RecognitionConfig getRecognitionConfig() {
1241            return mRecognitionConfig;
1242        }
1243
1244        // Whether a start recognition was requested.
1245        synchronized boolean isRequested() {
1246            return mRequested;
1247        }
1248
1249        synchronized void setRequested(boolean requested) {
1250            mRequested = requested;
1251        }
1252
1253        synchronized void setSoundModel(SoundModel soundModel) {
1254            mSoundModel = soundModel;
1255        }
1256
1257        synchronized SoundModel getSoundModel() {
1258            return mSoundModel;
1259        }
1260
1261        synchronized int getModelType() {
1262            return mModelType;
1263        }
1264
1265        synchronized boolean isKeyphraseModel() {
1266            return mModelType == SoundModel.TYPE_KEYPHRASE;
1267        }
1268
1269        synchronized boolean isGenericModel() {
1270            return mModelType == SoundModel.TYPE_GENERIC_SOUND;
1271        }
1272
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
1282        synchronized String requestedToString() {
1283            return "Requested: " + (mRequested ? "Yes" : "No");
1284        }
1285
1286        synchronized String callbackToString() {
1287            return "Callback: " + (mCallback != null ? mCallback.asBinder() : "null");
1288        }
1289
1290        synchronized String uuidToString() {
1291            return "UUID: " + mModelId;
1292        }
1293
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
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