SoundTriggerHelper.java revision 55a9b0089c12dd657a8dc8551c79cf3e0f25d7e4
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.Keyphrase;
26import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
27import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
28import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
29import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
30import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
31import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
32import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
33import android.hardware.soundtrigger.SoundTriggerModule;
34import android.os.PowerManager;
35import android.os.RemoteException;
36import android.telephony.PhoneStateListener;
37import android.telephony.TelephonyManager;
38import android.util.Slog;
39
40import java.io.FileDescriptor;
41import java.io.PrintWriter;
42import java.util.ArrayList;
43
44/**
45 * Helper for {@link SoundTrigger} APIs.
46 * Currently this just acts as an abstraction over all SoundTrigger API calls.
47 *
48 * @hide
49 */
50public class SoundTriggerHelper implements SoundTrigger.StatusListener {
51    static final String TAG = "SoundTriggerHelper";
52    static final boolean DBG = false;
53
54    /**
55     * Return codes for {@link #startRecognition(int, KeyphraseSoundModel,
56     *      IRecognitionStatusCallback, RecognitionConfig)},
57     * {@link #stopRecognition(int, IRecognitionStatusCallback)}
58     */
59    public static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR;
60    public static final int STATUS_OK = SoundTrigger.STATUS_OK;
61
62    private static final int INVALID_VALUE = Integer.MIN_VALUE;
63
64    /** The {@link ModuleProperties} for the system, or null if none exists. */
65    final ModuleProperties moduleProperties;
66
67    /** The properties for the DSP module */
68    private SoundTriggerModule mModule;
69    private final Object mLock = new Object();
70    private final Context mContext;
71    private final TelephonyManager mTelephonyManager;
72    private final PhoneStateListener mPhoneStateListener;
73    private final PowerManager mPowerManager;
74
75    // TODO: Since many layers currently only deal with one recognition
76    // we simplify things by assuming one listener here too.
77    private IRecognitionStatusCallback mActiveListener;
78    private int mKeyphraseId = INVALID_VALUE;
79    private int mCurrentSoundModelHandle = INVALID_VALUE;
80    private KeyphraseSoundModel mCurrentSoundModel = null;
81    // FIXME: Ideally this should not be stored if allowMultipleTriggers happens at a lower layer.
82    private RecognitionConfig mRecognitionConfig = null;
83    private boolean mRequested = false;
84    private boolean mCallActive = false;
85    private boolean mIsPowerSaveMode = false;
86    // Indicates if the native sound trigger service is disabled or not.
87    // This is an indirect indication of the microphone being open in some other application.
88    private boolean mServiceDisabled = false;
89    private boolean mStarted = false;
90    private boolean mRecognitionAborted = false;
91    private PowerSaveModeListener mPowerSaveModeListener;
92
93    SoundTriggerHelper(Context context) {
94        ArrayList <ModuleProperties> modules = new ArrayList<>();
95        int status = SoundTrigger.listModules(modules);
96        mContext = context;
97        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
98        mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
99        mPhoneStateListener = new MyCallStateListener();
100        if (status != SoundTrigger.STATUS_OK || modules.size() == 0) {
101            Slog.w(TAG, "listModules status=" + status + ", # of modules=" + modules.size());
102            moduleProperties = null;
103            mModule = null;
104        } else {
105            // TODO: Figure out how to determine which module corresponds to the DSP hardware.
106            moduleProperties = modules.get(0);
107        }
108    }
109
110    /**
111     * Starts recognition for the given keyphraseId.
112     *
113     * @param keyphraseId The identifier of the keyphrase for which
114     *        the recognition is to be started.
115     * @param soundModel The sound model to use for recognition.
116     * @param listener The listener for the recognition events related to the given keyphrase.
117     * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
118     */
119    int startRecognition(int keyphraseId,
120            KeyphraseSoundModel soundModel,
121            IRecognitionStatusCallback listener,
122            RecognitionConfig recognitionConfig) {
123        if (soundModel == null || listener == null || recognitionConfig == null) {
124            return STATUS_ERROR;
125        }
126
127        synchronized (mLock) {
128            if (DBG) {
129                Slog.d(TAG, "startRecognition for keyphraseId=" + keyphraseId
130                        + " soundModel=" + soundModel + ", listener=" + listener.asBinder()
131                        + ", recognitionConfig=" + recognitionConfig);
132                Slog.d(TAG, "moduleProperties=" + moduleProperties);
133                Slog.d(TAG, "current listener="
134                        + (mActiveListener == null ? "null" : mActiveListener.asBinder()));
135                Slog.d(TAG, "current SoundModel handle=" + mCurrentSoundModelHandle);
136                Slog.d(TAG, "current SoundModel UUID="
137                        + (mCurrentSoundModel == null ? null : mCurrentSoundModel.uuid));
138            }
139
140            if (!mStarted) {
141                // Get the current call state synchronously for the first recognition.
142                mCallActive = mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE;
143                // Register for call state changes when the first call to start recognition occurs.
144                mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
145
146                // Register for power saver mode changes when the first call to start recognition
147                // occurs.
148                if (mPowerSaveModeListener == null) {
149                    mPowerSaveModeListener = new PowerSaveModeListener();
150                    mContext.registerReceiver(mPowerSaveModeListener,
151                            new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
152                }
153                mIsPowerSaveMode = mPowerManager.isPowerSaveMode();
154            }
155
156            if (moduleProperties == null) {
157                Slog.w(TAG, "Attempting startRecognition without the capability");
158                return STATUS_ERROR;
159            }
160            if (mModule == null) {
161                mModule = SoundTrigger.attachModule(moduleProperties.id, this, null);
162                if (mModule == null) {
163                    Slog.w(TAG, "startRecognition cannot attach to sound trigger module");
164                    return STATUS_ERROR;
165                }
166            }
167
168            // Unload the previous model if the current one isn't invalid
169            // and, it's not the same as the new one.
170            // This helps use cache and reuse the model and just start/stop it when necessary.
171            if (mCurrentSoundModelHandle != INVALID_VALUE
172                    && !soundModel.equals(mCurrentSoundModel)) {
173                Slog.w(TAG, "Unloading previous sound model");
174                int status = mModule.unloadSoundModel(mCurrentSoundModelHandle);
175                if (status != SoundTrigger.STATUS_OK) {
176                    Slog.w(TAG, "unloadSoundModel call failed with " + status);
177                }
178                internalClearSoundModelLocked();
179                mStarted = false;
180            }
181
182            // If the previous recognition was by a different listener,
183            // Notify them that it was stopped.
184            if (mActiveListener != null && mActiveListener.asBinder() != listener.asBinder()) {
185                Slog.w(TAG, "Canceling previous recognition");
186                try {
187                    mActiveListener.onError(STATUS_ERROR);
188                } catch (RemoteException e) {
189                    Slog.w(TAG, "RemoteException in onDetectionStopped", e);
190                }
191                mActiveListener = null;
192            }
193
194            // Load the sound model if the current one is null.
195            int soundModelHandle = mCurrentSoundModelHandle;
196            if (mCurrentSoundModelHandle == INVALID_VALUE
197                    || mCurrentSoundModel == null) {
198                int[] handle = new int[] { INVALID_VALUE };
199                int status = mModule.loadSoundModel(soundModel, handle);
200                if (status != SoundTrigger.STATUS_OK) {
201                    Slog.w(TAG, "loadSoundModel call failed with " + status);
202                    return status;
203                }
204                if (handle[0] == INVALID_VALUE) {
205                    Slog.w(TAG, "loadSoundModel call returned invalid sound model handle");
206                    return STATUS_ERROR;
207                }
208                soundModelHandle = handle[0];
209            } else {
210                if (DBG) Slog.d(TAG, "Reusing previously loaded sound model");
211            }
212
213            // Start the recognition.
214            mRequested = true;
215            mKeyphraseId = keyphraseId;
216            mCurrentSoundModelHandle = soundModelHandle;
217            mCurrentSoundModel = soundModel;
218            mRecognitionConfig = recognitionConfig;
219            // Register the new listener. This replaces the old one.
220            // There can only be a maximum of one active listener at any given time.
221            mActiveListener = listener;
222
223            return updateRecognitionLocked(false /* don't notify for synchronous calls */);
224        }
225    }
226
227    /**
228     * Stops recognition for the given {@link Keyphrase} if a recognition is
229     * currently active.
230     *
231     * @param keyphraseId The identifier of the keyphrase for which
232     *        the recognition is to be stopped.
233     * @param listener The listener for the recognition events related to the given keyphrase.
234     *
235     * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
236     */
237    int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
238        if (listener == null) {
239            return STATUS_ERROR;
240        }
241
242        synchronized (mLock) {
243            if (DBG) {
244                Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId
245                        + ", listener=" + listener.asBinder());
246                Slog.d(TAG, "current listener="
247                        + (mActiveListener == null ? "null" : mActiveListener.asBinder()));
248            }
249
250            if (moduleProperties == null || mModule == null) {
251                Slog.w(TAG, "Attempting stopRecognition without the capability");
252                return STATUS_ERROR;
253            }
254
255            if (mActiveListener == null) {
256                // startRecognition hasn't been called or it failed.
257                Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition");
258                return STATUS_ERROR;
259            }
260            if (mActiveListener.asBinder() != listener.asBinder()) {
261                // We don't allow a different listener to stop the recognition than the one
262                // that started it.
263                Slog.w(TAG, "Attempting stopRecognition for another recognition");
264                return STATUS_ERROR;
265            }
266
267            // Stop recognition if it's the current one, ignore otherwise.
268            mRequested = false;
269            int status = updateRecognitionLocked(false /* don't notify for synchronous calls */);
270            if (status != SoundTrigger.STATUS_OK) {
271                return status;
272            }
273
274            // We leave the sound model loaded but not started, this helps us when we start
275            // back.
276            // Also clear the internal state once the recognition has been stopped.
277            internalClearStateLocked();
278            return status;
279        }
280    }
281
282    /**
283     * Stops all recognitions active currently and clears the internal state.
284     */
285    void stopAllRecognitions() {
286        synchronized (mLock) {
287            if (moduleProperties == null || mModule == null) {
288                return;
289            }
290
291            if (mCurrentSoundModelHandle == INVALID_VALUE) {
292                return;
293            }
294
295            mRequested = false;
296            int status = updateRecognitionLocked(false /* don't notify for synchronous calls */);
297            internalClearStateLocked();
298        }
299    }
300
301    public ModuleProperties getModuleProperties() {
302        return moduleProperties;
303    }
304
305    //---- SoundTrigger.StatusListener methods
306    @Override
307    public void onRecognition(RecognitionEvent event) {
308        if (event == null || !(event instanceof KeyphraseRecognitionEvent)) {
309            Slog.w(TAG, "Invalid recognition event!");
310            return;
311        }
312
313        if (DBG) Slog.d(TAG, "onRecognition: " + event);
314        synchronized (mLock) {
315            if (mActiveListener == null) {
316                Slog.w(TAG, "received onRecognition event without any listener for it");
317                return;
318            }
319            switch (event.status) {
320                // Fire aborts/failures to all listeners since it's not tied to a keyphrase.
321                case SoundTrigger.RECOGNITION_STATUS_ABORT:
322                    onRecognitionAbortLocked();
323                    break;
324                case SoundTrigger.RECOGNITION_STATUS_FAILURE:
325                    onRecognitionFailureLocked();
326                    break;
327                case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
328                    onRecognitionSuccessLocked((KeyphraseRecognitionEvent) event);
329                    break;
330            }
331        }
332    }
333
334    @Override
335    public void onSoundModelUpdate(SoundModelEvent event) {
336        if (event == null) {
337            Slog.w(TAG, "Invalid sound model event!");
338            return;
339        }
340        if (DBG) Slog.d(TAG, "onSoundModelUpdate: " + event);
341        synchronized (mLock) {
342            onSoundModelUpdatedLocked(event);
343        }
344    }
345
346    @Override
347    public void onServiceStateChange(int state) {
348        if (DBG) Slog.d(TAG, "onServiceStateChange, state: " + state);
349        synchronized (mLock) {
350            onServiceStateChangedLocked(SoundTrigger.SERVICE_STATE_DISABLED == state);
351        }
352    }
353
354    @Override
355    public void onServiceDied() {
356        Slog.e(TAG, "onServiceDied!!");
357        synchronized (mLock) {
358            onServiceDiedLocked();
359        }
360    }
361
362    private void onCallStateChangedLocked(boolean callActive) {
363        if (mCallActive == callActive) {
364            // We consider multiple call states as being active
365            // so we check if something really changed or not here.
366            return;
367        }
368        mCallActive = callActive;
369        updateRecognitionLocked(true /* notify */);
370    }
371
372    private void onPowerSaveModeChangedLocked(boolean isPowerSaveMode) {
373        if (mIsPowerSaveMode == isPowerSaveMode) {
374            return;
375        }
376        mIsPowerSaveMode = isPowerSaveMode;
377        updateRecognitionLocked(true /* notify */);
378    }
379
380    private void onSoundModelUpdatedLocked(SoundModelEvent event) {
381        // TODO: Handle sound model update here.
382    }
383
384    private void onServiceStateChangedLocked(boolean disabled) {
385        if (disabled == mServiceDisabled) {
386            return;
387        }
388        mServiceDisabled = disabled;
389        updateRecognitionLocked(true /* notify */);
390    }
391
392    private void onRecognitionAbortLocked() {
393        Slog.w(TAG, "Recognition aborted");
394        // If abort has been called, the hardware has already stopped recognition, so we shouldn't
395        // call it again when we process the state change.
396        mRecognitionAborted = true;
397    }
398
399    private void onRecognitionFailureLocked() {
400        Slog.w(TAG, "Recognition failure");
401        try {
402            if (mActiveListener != null) {
403                mActiveListener.onError(STATUS_ERROR);
404            }
405        } catch (RemoteException e) {
406            Slog.w(TAG, "RemoteException in onError", e);
407        } finally {
408            internalClearStateLocked();
409        }
410    }
411
412    private void onRecognitionSuccessLocked(KeyphraseRecognitionEvent event) {
413        Slog.i(TAG, "Recognition success");
414        KeyphraseRecognitionExtra[] keyphraseExtras =
415                ((KeyphraseRecognitionEvent) event).keyphraseExtras;
416        if (keyphraseExtras == null || keyphraseExtras.length == 0) {
417            Slog.w(TAG, "Invalid keyphrase recognition event!");
418            return;
419        }
420        // TODO: Handle more than one keyphrase extras.
421        if (mKeyphraseId != keyphraseExtras[0].id) {
422            Slog.w(TAG, "received onRecognition event for a different keyphrase");
423            return;
424        }
425
426        try {
427            if (mActiveListener != null) {
428                mActiveListener.onDetected((KeyphraseRecognitionEvent) event);
429            }
430        } catch (RemoteException e) {
431            Slog.w(TAG, "RemoteException in onDetected", e);
432        }
433
434        mStarted = false;
435        mRequested = mRecognitionConfig.allowMultipleTriggers;
436        // TODO: Remove this block if the lower layer supports multiple triggers.
437        if (mRequested) {
438            updateRecognitionLocked(true /* notify */);
439        }
440    }
441
442    private void onServiceDiedLocked() {
443        try {
444            if (mActiveListener != null) {
445                mActiveListener.onError(SoundTrigger.STATUS_DEAD_OBJECT);
446            }
447        } catch (RemoteException e) {
448            Slog.w(TAG, "RemoteException in onError", e);
449        } finally {
450            internalClearSoundModelLocked();
451            internalClearStateLocked();
452            if (mModule != null) {
453                mModule.detach();
454                mModule = null;
455            }
456        }
457    }
458
459    private int updateRecognitionLocked(boolean notify) {
460        if (mModule == null || moduleProperties == null
461                || mCurrentSoundModelHandle == INVALID_VALUE || mActiveListener == null) {
462            // Nothing to do here.
463            return STATUS_OK;
464        }
465
466        boolean start = mRequested && !mCallActive && !mServiceDisabled && !mIsPowerSaveMode;
467        if (start == mStarted) {
468            // No-op.
469            return STATUS_OK;
470        }
471
472        // See if the recognition needs to be started.
473        if (start) {
474            // Start recognition.
475            int status = mModule.startRecognition(mCurrentSoundModelHandle, mRecognitionConfig);
476            if (status != SoundTrigger.STATUS_OK) {
477                Slog.w(TAG, "startRecognition failed with " + status);
478                // Notify of error if needed.
479                if (notify) {
480                    try {
481                        mActiveListener.onError(status);
482                    } catch (RemoteException e) {
483                        Slog.w(TAG, "RemoteException in onError", e);
484                    }
485                }
486            } else {
487                mStarted = true;
488                // Notify of resume if needed.
489                if (notify) {
490                    try {
491                        mActiveListener.onRecognitionResumed();
492                    } catch (RemoteException e) {
493                        Slog.w(TAG, "RemoteException in onRecognitionResumed", e);
494                    }
495                }
496            }
497            return status;
498        } else {
499            // Stop recognition (only if we haven't been aborted).
500            int status = STATUS_OK;
501            if (!mRecognitionAborted) {
502                status = mModule.stopRecognition(mCurrentSoundModelHandle);
503            } else {
504                mRecognitionAborted = false;
505            }
506            if (status != SoundTrigger.STATUS_OK) {
507                Slog.w(TAG, "stopRecognition call failed with " + status);
508                if (notify) {
509                    try {
510                        mActiveListener.onError(status);
511                    } catch (RemoteException e) {
512                        Slog.w(TAG, "RemoteException in onError", e);
513                    }
514                }
515            } else {
516                mStarted = false;
517                // Notify of pause if needed.
518                if (notify) {
519                    try {
520                        mActiveListener.onRecognitionPaused();
521                    } catch (RemoteException e) {
522                        Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
523                    }
524                }
525            }
526            return status;
527        }
528    }
529
530    private void internalClearStateLocked() {
531        mStarted = false;
532        mRequested = false;
533
534        mKeyphraseId = INVALID_VALUE;
535        mRecognitionConfig = null;
536        mActiveListener = null;
537
538        // Unregister from call state changes.
539        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
540
541        // Unregister from power save mode changes.
542        if (mPowerSaveModeListener != null) {
543            mContext.unregisterReceiver(mPowerSaveModeListener);
544            mPowerSaveModeListener = null;
545        }
546    }
547
548    private void internalClearSoundModelLocked() {
549        mCurrentSoundModelHandle = INVALID_VALUE;
550        mCurrentSoundModel = null;
551    }
552
553    class MyCallStateListener extends PhoneStateListener {
554        @Override
555        public void onCallStateChanged(int state, String arg1) {
556            if (DBG) Slog.d(TAG, "onCallStateChanged: " + state);
557            synchronized (mLock) {
558                onCallStateChangedLocked(TelephonyManager.CALL_STATE_IDLE != state);
559            }
560        }
561    }
562
563    class PowerSaveModeListener extends BroadcastReceiver {
564        @Override
565        public void onReceive(Context context, Intent intent) {
566            if (!PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) {
567                return;
568            }
569            boolean active = mPowerManager.isPowerSaveMode();
570            if (DBG) Slog.d(TAG, "onPowerSaveModeChanged: " + active);
571            synchronized (mLock) {
572                onPowerSaveModeChangedLocked(active);
573            }
574        }
575    }
576
577    void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
578        synchronized (mLock) {
579            pw.print("  module properties=");
580            pw.println(moduleProperties == null ? "null" : moduleProperties);
581            pw.print("  keyphrase ID="); pw.println(mKeyphraseId);
582            pw.print("  sound model handle="); pw.println(mCurrentSoundModelHandle);
583            pw.print("  sound model UUID=");
584            pw.println(mCurrentSoundModel == null ? "null" : mCurrentSoundModel.uuid);
585            pw.print("  current listener=");
586            pw.println(mActiveListener == null ? "null" : mActiveListener.asBinder());
587
588            pw.print("  requested="); pw.println(mRequested);
589            pw.print("  started="); pw.println(mStarted);
590            pw.print("  call active="); pw.println(mCallActive);
591            pw.print("  power save mode active="); pw.println(mIsPowerSaveMode);
592            pw.print("  service disabled="); pw.println(mServiceDisabled);
593        }
594    }
595}
596