FileDocCategorySizeDatePackage
SoundTriggerHelper.javaAPI DocAndroid 5.1 API23586Thu Mar 12 22:22:42 GMT 2015com.android.server.voiceinteraction

SoundTriggerHelper

public class SoundTriggerHelper extends Object implements SoundTrigger.StatusListener
Helper for {@link SoundTrigger} APIs. Currently this just acts as an abstraction over all SoundTrigger API calls.
hide

Fields Summary
static final String
TAG
static final boolean
DBG
public static final int
STATUS_ERROR
Return codes for {@link #startRecognition(int, KeyphraseSoundModel, IRecognitionStatusCallback, RecognitionConfig)}, {@link #stopRecognition(int, IRecognitionStatusCallback)}
public static final int
STATUS_OK
private static final int
INVALID_VALUE
final android.hardware.soundtrigger.SoundTrigger.ModuleProperties
moduleProperties
The {@link ModuleProperties} for the system, or null if none exists.
private android.hardware.soundtrigger.SoundTriggerModule
mModule
The properties for the DSP module
private final Object
mLock
private final android.content.Context
mContext
private final android.telephony.TelephonyManager
mTelephonyManager
private final android.telephony.PhoneStateListener
mPhoneStateListener
private final android.os.PowerManager
mPowerManager
private android.hardware.soundtrigger.IRecognitionStatusCallback
mActiveListener
private int
mKeyphraseId
private int
mCurrentSoundModelHandle
private android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel
mCurrentSoundModel
private android.hardware.soundtrigger.SoundTrigger.RecognitionConfig
mRecognitionConfig
private boolean
mRequested
private boolean
mCallActive
private boolean
mIsPowerSaveMode
private boolean
mServiceDisabled
private boolean
mStarted
private PowerSaveModeListener
mPowerSaveModeListener
Constructors Summary
SoundTriggerHelper(android.content.Context context)


      
        ArrayList <ModuleProperties> modules = new ArrayList<>();
        int status = SoundTrigger.listModules(modules);
        mContext = context;
        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        mPhoneStateListener = new MyCallStateListener();
        if (status != SoundTrigger.STATUS_OK || modules.size() == 0) {
            Slog.w(TAG, "listModules status=" + status + ", # of modules=" + modules.size());
            moduleProperties = null;
            mModule = null;
        } else {
            // TODO: Figure out how to determine which module corresponds to the DSP hardware.
            moduleProperties = modules.get(0);
        }
    
Methods Summary
voiddump(java.io.FileDescriptor fd, java.io.PrintWriter pw, java.lang.String[] args)

        synchronized (mLock) {
            pw.print("  module properties=");
            pw.println(moduleProperties == null ? "null" : moduleProperties);
            pw.print("  keyphrase ID="); pw.println(mKeyphraseId);
            pw.print("  sound model handle="); pw.println(mCurrentSoundModelHandle);
            pw.print("  sound model UUID=");
            pw.println(mCurrentSoundModel == null ? "null" : mCurrentSoundModel.uuid);
            pw.print("  current listener=");
            pw.println(mActiveListener == null ? "null" : mActiveListener.asBinder());

            pw.print("  requested="); pw.println(mRequested);
            pw.print("  started="); pw.println(mStarted);
            pw.print("  call active="); pw.println(mCallActive);
            pw.print("  power save mode active="); pw.println(mIsPowerSaveMode);
            pw.print("  service disabled="); pw.println(mServiceDisabled);
        }
    
private voidinternalClearSoundModelLocked()

        mCurrentSoundModelHandle = INVALID_VALUE;
        mCurrentSoundModel = null;
    
private voidinternalClearStateLocked()

        mStarted = false;
        mRequested = false;

        mKeyphraseId = INVALID_VALUE;
        mRecognitionConfig = null;
        mActiveListener = null;

        // Unregister from call state changes.
        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);

        // Unregister from power save mode changes.
        if (mPowerSaveModeListener != null) {
            mContext.unregisterReceiver(mPowerSaveModeListener);
            mPowerSaveModeListener = null;
        }
    
private voidonCallStateChangedLocked(boolean callActive)

        if (mCallActive == callActive) {
            // We consider multiple call states as being active
            // so we check if something really changed or not here.
            return;
        }
        mCallActive = callActive;
        updateRecognitionLocked(true /* notify */);
    
private voidonPowerSaveModeChangedLocked(boolean isPowerSaveMode)

        if (mIsPowerSaveMode == isPowerSaveMode) {
            return;
        }
        mIsPowerSaveMode = isPowerSaveMode;
        updateRecognitionLocked(true /* notify */);
    
public voidonRecognition(android.hardware.soundtrigger.SoundTrigger.RecognitionEvent event)

        if (event == null || !(event instanceof KeyphraseRecognitionEvent)) {
            Slog.w(TAG, "Invalid recognition event!");
            return;
        }

        if (DBG) Slog.d(TAG, "onRecognition: " + event);
        synchronized (mLock) {
            if (mActiveListener == null) {
                Slog.w(TAG, "received onRecognition event without any listener for it");
                return;
            }
            switch (event.status) {
                // Fire aborts/failures to all listeners since it's not tied to a keyphrase.
                case SoundTrigger.RECOGNITION_STATUS_ABORT:
                    onRecognitionAbortLocked();
                    break;
                case SoundTrigger.RECOGNITION_STATUS_FAILURE:
                    onRecognitionFailureLocked();
                    break;
                case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
                    onRecognitionSuccessLocked((KeyphraseRecognitionEvent) event);
                    break;
            }
        }
    
private voidonRecognitionAbortLocked()

        Slog.w(TAG, "Recognition aborted");
        // No-op
        // This is handled via service state changes instead.
    
private voidonRecognitionFailureLocked()

        Slog.w(TAG, "Recognition failure");
        try {
            if (mActiveListener != null) {
                mActiveListener.onError(STATUS_ERROR);
            }
        } catch (RemoteException e) {
            Slog.w(TAG, "RemoteException in onError", e);
        } finally {
            internalClearStateLocked();
        }
    
private voidonRecognitionSuccessLocked(android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent event)

        Slog.i(TAG, "Recognition success");
        KeyphraseRecognitionExtra[] keyphraseExtras =
                ((KeyphraseRecognitionEvent) event).keyphraseExtras;
        if (keyphraseExtras == null || keyphraseExtras.length == 0) {
            Slog.w(TAG, "Invalid keyphrase recognition event!");
            return;
        }
        // TODO: Handle more than one keyphrase extras.
        if (mKeyphraseId != keyphraseExtras[0].id) {
            Slog.w(TAG, "received onRecognition event for a different keyphrase");
            return;
        }

        try {
            if (mActiveListener != null) {
                mActiveListener.onDetected((KeyphraseRecognitionEvent) event);
            }
        } catch (RemoteException e) {
            Slog.w(TAG, "RemoteException in onDetected", e);
        }

        mStarted = false;
        mRequested = mRecognitionConfig.allowMultipleTriggers;
        // TODO: Remove this block if the lower layer supports multiple triggers.
        if (mRequested) {
            updateRecognitionLocked(true /* notify */);
        }
    
public voidonServiceDied()

        Slog.e(TAG, "onServiceDied!!");
        synchronized (mLock) {
            onServiceDiedLocked();
        }
    
private voidonServiceDiedLocked()

        try {
            if (mActiveListener != null) {
                mActiveListener.onError(SoundTrigger.STATUS_DEAD_OBJECT);
            }
        } catch (RemoteException e) {
            Slog.w(TAG, "RemoteException in onError", e);
        } finally {
            internalClearSoundModelLocked();
            internalClearStateLocked();
            if (mModule != null) {
                mModule.detach();
                mModule = null;
            }
        }
    
public voidonServiceStateChange(int state)

        if (DBG) Slog.d(TAG, "onServiceStateChange, state: " + state);
        synchronized (mLock) {
            onServiceStateChangedLocked(SoundTrigger.SERVICE_STATE_DISABLED == state);
        }
    
private voidonServiceStateChangedLocked(boolean disabled)

        if (disabled == mServiceDisabled) {
            return;
        }
        mServiceDisabled = disabled;
        updateRecognitionLocked(true /* notify */);
    
public voidonSoundModelUpdate(android.hardware.soundtrigger.SoundTrigger.SoundModelEvent event)

        if (event == null) {
            Slog.w(TAG, "Invalid sound model event!");
            return;
        }
        if (DBG) Slog.d(TAG, "onSoundModelUpdate: " + event);
        synchronized (mLock) {
            onSoundModelUpdatedLocked(event);
        }
    
private voidonSoundModelUpdatedLocked(android.hardware.soundtrigger.SoundTrigger.SoundModelEvent event)

        // TODO: Handle sound model update here.
    
intstartRecognition(int keyphraseId, android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel soundModel, android.hardware.soundtrigger.IRecognitionStatusCallback listener, android.hardware.soundtrigger.SoundTrigger.RecognitionConfig recognitionConfig)
Starts recognition for the given keyphraseId.

param
keyphraseId The identifier of the keyphrase for which the recognition is to be started.
param
soundModel The sound model to use for recognition.
param
listener The listener for the recognition events related to the given keyphrase.
return
One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.

        if (soundModel == null || listener == null || recognitionConfig == null) {
            return STATUS_ERROR;
        }

        synchronized (mLock) {
            if (DBG) {
                Slog.d(TAG, "startRecognition for keyphraseId=" + keyphraseId
                        + " soundModel=" + soundModel + ", listener=" + listener.asBinder()
                        + ", recognitionConfig=" + recognitionConfig);
                Slog.d(TAG, "moduleProperties=" + moduleProperties);
                Slog.d(TAG, "current listener="
                        + (mActiveListener == null ? "null" : mActiveListener.asBinder()));
                Slog.d(TAG, "current SoundModel handle=" + mCurrentSoundModelHandle);
                Slog.d(TAG, "current SoundModel UUID="
                        + (mCurrentSoundModel == null ? null : mCurrentSoundModel.uuid));
            }

            if (!mStarted) {
                // Get the current call state synchronously for the first recognition.
                mCallActive = mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE;
                // Register for call state changes when the first call to start recognition occurs.
                mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);

                // Register for power saver mode changes when the first call to start recognition
                // occurs.
                if (mPowerSaveModeListener == null) {
                    mPowerSaveModeListener = new PowerSaveModeListener();
                    mContext.registerReceiver(mPowerSaveModeListener,
                            new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
                }
                mIsPowerSaveMode = mPowerManager.isPowerSaveMode();
            }

            if (moduleProperties == null) {
                Slog.w(TAG, "Attempting startRecognition without the capability");
                return STATUS_ERROR;
            }
            if (mModule == null) {
                mModule = SoundTrigger.attachModule(moduleProperties.id, this, null);
                if (mModule == null) {
                    Slog.w(TAG, "startRecognition cannot attach to sound trigger module");
                    return STATUS_ERROR;
                }
            }

            // Unload the previous model if the current one isn't invalid
            // and, it's not the same as the new one.
            // This helps use cache and reuse the model and just start/stop it when necessary.
            if (mCurrentSoundModelHandle != INVALID_VALUE
                    && !soundModel.equals(mCurrentSoundModel)) {
                Slog.w(TAG, "Unloading previous sound model");
                int status = mModule.unloadSoundModel(mCurrentSoundModelHandle);
                if (status != SoundTrigger.STATUS_OK) {
                    Slog.w(TAG, "unloadSoundModel call failed with " + status);
                }
                internalClearSoundModelLocked();
                mStarted = false;
            }

            // If the previous recognition was by a different listener,
            // Notify them that it was stopped.
            if (mActiveListener != null && mActiveListener.asBinder() != listener.asBinder()) {
                Slog.w(TAG, "Canceling previous recognition");
                try {
                    mActiveListener.onError(STATUS_ERROR);
                } catch (RemoteException e) {
                    Slog.w(TAG, "RemoteException in onDetectionStopped", e);
                }
                mActiveListener = null;
            }

            // Load the sound model if the current one is null.
            int soundModelHandle = mCurrentSoundModelHandle;
            if (mCurrentSoundModelHandle == INVALID_VALUE
                    || mCurrentSoundModel == null) {
                int[] handle = new int[] { INVALID_VALUE };
                int status = mModule.loadSoundModel(soundModel, handle);
                if (status != SoundTrigger.STATUS_OK) {
                    Slog.w(TAG, "loadSoundModel call failed with " + status);
                    return status;
                }
                if (handle[0] == INVALID_VALUE) {
                    Slog.w(TAG, "loadSoundModel call returned invalid sound model handle");
                    return STATUS_ERROR;
                }
                soundModelHandle = handle[0];
            } else {
                if (DBG) Slog.d(TAG, "Reusing previously loaded sound model");
            }

            // Start the recognition.
            mRequested = true;
            mKeyphraseId = keyphraseId;
            mCurrentSoundModelHandle = soundModelHandle;
            mCurrentSoundModel = soundModel;
            mRecognitionConfig = recognitionConfig;
            // Register the new listener. This replaces the old one.
            // There can only be a maximum of one active listener at any given time.
            mActiveListener = listener;

            return updateRecognitionLocked(false /* don't notify for synchronous calls */);
        }
    
voidstopAllRecognitions()
Stops all recognitions active currently and clears the internal state.

        synchronized (mLock) {
            if (moduleProperties == null || mModule == null) {
                return;
            }

            if (mCurrentSoundModelHandle == INVALID_VALUE) {
                return;
            }

            mRequested = false;
            int status = updateRecognitionLocked(false /* don't notify for synchronous calls */);
            internalClearStateLocked();
        }
    
intstopRecognition(int keyphraseId, android.hardware.soundtrigger.IRecognitionStatusCallback listener)
Stops recognition for the given {@link Keyphrase} if a recognition is currently active.

param
keyphraseId The identifier of the keyphrase for which the recognition is to be stopped.
param
listener The listener for the recognition events related to the given keyphrase.
return
One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.

        if (listener == null) {
            return STATUS_ERROR;
        }

        synchronized (mLock) {
            if (DBG) {
                Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId
                        + ", listener=" + listener.asBinder());
                Slog.d(TAG, "current listener="
                        + (mActiveListener == null ? "null" : mActiveListener.asBinder()));
            }

            if (moduleProperties == null || mModule == null) {
                Slog.w(TAG, "Attempting stopRecognition without the capability");
                return STATUS_ERROR;
            }

            if (mActiveListener == null) {
                // startRecognition hasn't been called or it failed.
                Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition");
                return STATUS_ERROR;
            }
            if (mActiveListener.asBinder() != listener.asBinder()) {
                // We don't allow a different listener to stop the recognition than the one
                // that started it.
                Slog.w(TAG, "Attempting stopRecognition for another recognition");
                return STATUS_ERROR;
            }

            // Stop recognition if it's the current one, ignore otherwise.
            mRequested = false;
            int status = updateRecognitionLocked(false /* don't notify for synchronous calls */);
            if (status != SoundTrigger.STATUS_OK) {
                return status;
            }

            // We leave the sound model loaded but not started, this helps us when we start
            // back.
            // Also clear the internal state once the recognition has been stopped.
            internalClearStateLocked();
            return status;
        }
    
private intupdateRecognitionLocked(boolean notify)

        if (mModule == null || moduleProperties == null
                || mCurrentSoundModelHandle == INVALID_VALUE || mActiveListener == null) {
            // Nothing to do here.
            return STATUS_OK;
        }

        boolean start = mRequested && !mCallActive && !mServiceDisabled && !mIsPowerSaveMode;
        if (start == mStarted) {
            // No-op.
            return STATUS_OK;
        }

        // See if the recognition needs to be started.
        if (start) {
            // Start recognition.
            int status = mModule.startRecognition(mCurrentSoundModelHandle, mRecognitionConfig);
            if (status != SoundTrigger.STATUS_OK) {
                Slog.w(TAG, "startRecognition failed with " + status);
                // Notify of error if needed.
                if (notify) {
                    try {
                        mActiveListener.onError(status);
                    } catch (RemoteException e) {
                        Slog.w(TAG, "RemoteException in onError", e);
                    }
                }
            } else {
                mStarted = true;
                // Notify of resume if needed.
                if (notify) {
                    try {
                        mActiveListener.onRecognitionResumed();
                    } catch (RemoteException e) {
                        Slog.w(TAG, "RemoteException in onRecognitionResumed", e);
                    }
                }
            }
            return status;
        } else {
            // Stop recognition.
            int status = mModule.stopRecognition(mCurrentSoundModelHandle);
            if (status != SoundTrigger.STATUS_OK) {
                Slog.w(TAG, "stopRecognition call failed with " + status);
                if (notify) {
                    try {
                        mActiveListener.onError(status);
                    } catch (RemoteException e) {
                        Slog.w(TAG, "RemoteException in onError", e);
                    }
                }
            } else {
                mStarted = false;
                // Notify of pause if needed.
                if (notify) {
                    try {
                        mActiveListener.onRecognitionPaused();
                    } catch (RemoteException e) {
                        Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
                    }
                }
            }
            return status;
        }