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