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