Android Speech Recognition Continuous Service

rmooney picture rmooney · Aug 4, 2013 · Viewed 34.1k times · Source

I'm trying to create a service to run continuous speech recognition in Android 4.2. Using the answer from this link ( Android Speech Recognition as a service on Android 4.1 & 4.2 ), I created a service that is run from an Activity. My problem is that I get null exceptions when accessing mTarget.mAudioManager or mTarget.mSpeechRecognizerIntent in the handleMessage method. The target (and mTarget object created from it) is not null, but all the objects inside it are.

What am I doing wrong here?

Relevant Activity Code (static methods called from activity, activityContext is the activity this method is called from):

public static void init(Context context)
{
   voiceCommandService = new VoiceCommandService();
   activityContext = context;
}

public static void startContinuousListening()
{
    Intent service = new Intent(activityContext, VoiceCommandService.class);
    activityContext.startService(service);

    Message msg = new Message();
    msg.what = VoiceCommandService.MSG_RECOGNIZER_START_LISTENING; 

    try
    {
      voiceCommandService.mServerMessenger.send(msg);
    } 
    catch (RemoteException e)
   {
    e.printStackTrace();
   }

}

Service Code:

public class VoiceCommandService extends Service
{
protected AudioManager mAudioManager; 
protected SpeechRecognizer mSpeechRecognizer;
protected Intent mSpeechRecognizerIntent;
protected final Messenger mServerMessenger = new Messenger(new IncomingHandler(this));

protected boolean mIsListening;
protected volatile boolean mIsCountDownOn;

static final int MSG_RECOGNIZER_START_LISTENING = 1;
static final int MSG_RECOGNIZER_CANCEL = 2;

@Override
public void onCreate()
{
    super.onCreate();
    mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 
    mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(this);
    mSpeechRecognizer.setRecognitionListener(new SpeechRecognitionListener());
    mSpeechRecognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
    mSpeechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
                                     RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
    mSpeechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE,
                                     this.getPackageName());
}

protected static class IncomingHandler extends Handler
{
    private WeakReference<VoiceCommandService> mtarget;

    IncomingHandler(VoiceCommandService target)
    {
        mtarget = new WeakReference<VoiceCommandService>(target);
    }


    @Override
    public void handleMessage(Message msg)
    {
        final VoiceCommandService target = mtarget.get();

        switch (msg.what)
        {
            case MSG_RECOGNIZER_START_LISTENING:

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
                {
                    // turn off beep sound  
                    target.mAudioManager.setStreamMute(AudioManager.STREAM_SYSTEM, true);
                }
                 if (!target.mIsListening)
                 {
                     target.mSpeechRecognizer.startListening(target.mSpeechRecognizerIntent);
                     target.mIsListening = true;
                    //Log.d(TAG, "message start listening"); //$NON-NLS-1$
                 }
                 break;

             case MSG_RECOGNIZER_CANCEL:
                  target.mSpeechRecognizer.cancel();
                  target.mIsListening = false;
                  //Log.d(TAG, "message canceled recognizer"); //$NON-NLS-1$
                  break;
         }
   } 
} 

// Count down timer for Jelly Bean work around
protected CountDownTimer mNoSpeechCountDown = new CountDownTimer(5000, 5000)
{

    @Override
    public void onTick(long millisUntilFinished)
    {
        // TODO Auto-generated method stub

    }

    @Override
    public void onFinish()
    {
        mIsCountDownOn = false;
        Message message = Message.obtain(null, MSG_RECOGNIZER_CANCEL);
        try
        {
            mServerMessenger.send(message);
            message = Message.obtain(null, MSG_RECOGNIZER_START_LISTENING);
            mServerMessenger.send(message);
        }
        catch (RemoteException e)
        {

        }
    }
};

@Override
public void onDestroy()
{
    super.onDestroy();

    if (mIsCountDownOn)
    {
        mNoSpeechCountDown.cancel();
    }
    if (mSpeechRecognizer != null)
    {
        mSpeechRecognizer.destroy();
    }
}

protected class SpeechRecognitionListener implements RecognitionListener
{

    private static final String TAG = "SpeechRecognitionListener";

    @Override
    public void onBeginningOfSpeech()
    {
        // speech input will be processed, so there is no need for count down anymore
        if (mIsCountDownOn)
        {
            mIsCountDownOn = false;
            mNoSpeechCountDown.cancel();
        }               
        //Log.d(TAG, "onBeginingOfSpeech"); //$NON-NLS-1$
    }

    @Override
    public void onBufferReceived(byte[] buffer)
    {

    }

    @Override
    public void onEndOfSpeech()
    {
        //Log.d(TAG, "onEndOfSpeech"); //$NON-NLS-1$
     }

    @Override
    public void onError(int error)
    {
        if (mIsCountDownOn)
        {
            mIsCountDownOn = false;
            mNoSpeechCountDown.cancel();
        }
         mIsListening = false;
         Message message = Message.obtain(null, MSG_RECOGNIZER_START_LISTENING);
         try
         {
                mServerMessenger.send(message);
         }
         catch (RemoteException e)
         {

         }
        //Log.d(TAG, "error = " + error); //$NON-NLS-1$
    }

    @Override
    public void onEvent(int eventType, Bundle params)
    {

    }

    @Override
    public void onPartialResults(Bundle partialResults)
    {

    }

    @Override
    public void onReadyForSpeech(Bundle params)
    {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
        {
            mIsCountDownOn = true;
            mNoSpeechCountDown.start();
            mAudioManager.setStreamMute(AudioManager.STREAM_SYSTEM, false);
        }
        Log.d(TAG, "onReadyForSpeech"); //$NON-NLS-1$
    }

    @Override
    public void onResults(Bundle results)
    {
        //Log.d(TAG, "onResults"); //$NON-NLS-1$

    }

    @Override
    public void onRmsChanged(float rmsdB)
    {

    }

}

@Override
public IBinder onBind(Intent arg0) {
    // TODO Auto-generated method stub
    return null;
}
}

Answer

Hoan Nguyen picture Hoan Nguyen · Aug 5, 2013

Class members in MainActivity

private int mBindFlag;
private Messenger mServiceMessenger;

Start service in onCreate()

@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);

    Intent service = new Intent(activityContext, VoiceCommandService.class);
    activityContext.startService(service);
    mBindFlag = Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH ? 0 : Context.BIND_ABOVE_CLIENT;

}

Bind service in onStart()

@Override
protected void onStart()
{
    super.onStart();

    bindService(new Intent(this, VoiceCommandService.class), mServiceConnection, mBindFlag);
}

@Override
protected void onStop()
{
    super.onStop();

    if (mServiceMessenger != null)
    {
        unbindService(mServiceConnection);
        mServiceMessenger = null;
    }
}

mServiceConnection member

private final ServiceConnection mServiceConnection = new ServiceConnection()
{
    @Override
    public void onServiceConnected(ComponentName name, IBinder service)
    {
        if (DEBUG) {Log.d(TAG, "onServiceConnected");} //$NON-NLS-1$

        mServiceMessenger = new Messenger(service);
        Message msg = new Message();
        msg.what = VoiceCommandService.MSG_RECOGNIZER_START_LISTENING; 

        try
        {
            mServiceMessenger.send(msg);
        } 
        catch (RemoteException e)
        {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name)
    {
        if (DEBUG) {Log.d(TAG, "onServiceDisconnected");} //$NON-NLS-1$
        mServiceMessenger = null;
    }

}; // mServiceConnection

In the service

@Override
public IBinder onBind(Intent intent)
{
    Log.d(TAG, "onBind");  //$NON-NLS-1$

    return mServerMessenger.getBinder();
}