Using the Android RecognizerIntent with a bluetooth headset
Asked Answered
F

6

27

I use the following code to launch speech recognition in Android:

PackageManager pm = getPackageManager();
List<ResolveInfo> activities = pm.queryIntentActivities(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0);

if (activities.size() == 0) {
    displayWarning("This device does not support speech recognition");
    return;
}

Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);

startActivityForResult(intent, VOICE_RECOGNITION_REQUEST_CODE);

This works fine. However, it doesn't seem to accept voice input from a bluetooth headset that is paired and connected using the "Phone audio" profile.

I can use an app called SoundAbout to force "Media Audio" to "Bluetooth (mono) (SCO)". With this app set, my voice recognition now works taking my speech input from my headset.

How can I use RecognizerIntent and get speech input from a bluetooth headset?

I see in API level 16 there is a new intent action ACTION_VOICE_SEARCH_HANDS_FREE. This is too new for me to use, but would this solve my problem?

Do I have to muck around in the AudioManager (like I assume SoundAbout is doing) to route the audio input using setBluetoothScoOn() or startBluetoothSco()?

Fieldwork answered 20/2, 2013 at 22:29 Comment(0)
C
45

Manifest permission

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />  

Create an inner class BluetoothHelper extends BluetoothHeadSetUtils in your Activity or Service. Declare a class member mBluetoothHelper and instantiate it in onCreate()

BluetoothHelper mBluetoothHelper;

@Override
public void onCreate()
{ 
     mBluetoothHelper = new BluetoothHelper(this);
}  

@Override
onResume()
{ 
    mBluetoothHelper.start();
}

@Override
onPause()
{
    mBluetoothHelper.stop();
}

// inner class
// BluetoothHeadsetUtils is an abstract class that has
// 4 abstracts methods that need to be implemented.
private class BluetoothHelper extends BluetoothHeadSetUtils
{
    public BluetoothHelper(Context context)
    {
        super(context);
    }

    @Override
    public void onScoAudioDisconnected()
    {
        // Cancel speech recognizer if desired
    }

    @Override
    public void onScoAudioConnected()
    {           
        // Should start speech recognition here if not already started  
    }

    @Override
    public void onHeadsetDisconnected()
    {

    }

    @Override
    public void onHeadsetConnected()
    {

    }
}

To use bluetooth headset with Text To Speech you need to set the AudioManager to STREAM_VOICE_CALL before calling speak. Or use the code below

protected void speak(String text)
{

    HashMap<String, String> myHashRender = new HashMap<String, String>();

    if (mBluetoothHelper.isOnHeadsetSco())
    {
        myHashRender.put(TextToSpeech.Engine.KEY_PARAM_STREAM, 
            String.valueOf(AudioManager.STREAM_VOICE_CALL));
    }
    mTts.speak(text, TextToSpeech.QUEUE_FLUSH, myHashRender);
}

Copy the BluetoothHeadsetUtils class to your project.

import java.util.List;

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Build;
import android.os.CountDownTimer;
import android.util.Log;

/**
 * This is a utility to detect bluetooth headset connection and establish audio connection
 * for android API >= 8. This includes a work around for  API < 11 to detect already connected headset
 * before the application starts. This work around would only fails if Sco audio
 * connection is accepted but the connected device is not a headset.
 * 
 * @author Hoan Nguyen
 *
 */
public abstract class BluetoothHeadsetUtils
{
    private  Context mContext;

    private BluetoothAdapter mBluetoothAdapter;
    private BluetoothHeadset mBluetoothHeadset;
    private BluetoothDevice mConnectedHeadset;

    private AudioManager mAudioManager;

    private boolean mIsCountDownOn;
    private boolean mIsStarting;
    private boolean mIsOnHeadsetSco;
    private boolean mIsStarted;

    private static final String TAG = "BluetoothHeadsetUtils"; //$NON-NLS-1$

    /**
     * Constructor
     * @param context
     */
    public BluetoothHeadsetUtils(Context context)
    {
        mContext = context;
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
    }

    /**
     * Call this to start BluetoothHeadsetUtils functionalities.
     * @return  The return value of startBluetooth() or startBluetooth11()
     */
    public boolean start()
    {
        if (!mIsStarted)
        {
            mIsStarted = true;

            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)
            {
                mIsStarted = startBluetooth();
            }
            else
            {
                mIsStarted = startBluetooth11();
            }
        }

        return mIsStarted;
    }

    /**
     * Should call this on onResume or onDestroy.
     * Unregister broadcast receivers and stop Sco audio connection
     * and cancel count down.
     */
    public void stop()
    {
        if (mIsStarted)
        {
            mIsStarted = false;

            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)
            {
                stopBluetooth();
            }
            else
            {
                stopBluetooth11();
            }
        }
    }

    /**
     * 
     * @return  true if audio is connected through headset.
     */
    public boolean isOnHeadsetSco()
    {
        return mIsOnHeadsetSco;
    }

    public abstract void onHeadsetDisconnected();

    public abstract void onHeadsetConnected();

    public abstract void onScoAudioDisconnected();

    public abstract void onScoAudioConnected();

    /**
     * Register for bluetooth headset connection states and Sco audio states.
     * Try to connect to bluetooth headset audio by calling startBluetoothSco(). 
     * This is a work around for API < 11 to detect if a headset is connected before 
     * the application starts.
     * 
     * The official documentation for startBluetoothSco() states
     * 
     * "This method can be used by applications wanting to send and received audio to/from 
     *  a bluetooth SCO headset while the phone is not in call."
     *  
     * Does this mean that startBluetoothSco() would fail if the connected bluetooth device
     * is not a headset?
     * 
     * Thus if a call to startBluetoothSco() is successful, i.e mBroadcastReceiver will receive
     * an ACTION_SCO_AUDIO_STATE_CHANGED with intent extra SCO_AUDIO_STATE_CONNECTED, then
     * we assume that a headset is connected.
     * 
     * @return  false if device does not support bluetooth or current platform does not supports
     *                use of SCO for off call.
     */
    @SuppressWarnings("deprecation")
    private boolean startBluetooth()
    {
        Log.d(TAG, "startBluetooth"); //$NON-NLS-1$

        // Device support bluetooth
        if (mBluetoothAdapter != null)
        {       
            if (mAudioManager.isBluetoothScoAvailableOffCall())
            {
                mContext.registerReceiver(mBroadcastReceiver, 
                                        new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED));
                mContext.registerReceiver(mBroadcastReceiver, 
                                        new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED));
                mContext.registerReceiver(mBroadcastReceiver, 
                                        new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED));

                // Need to set audio mode to MODE_IN_CALL for call to startBluetoothSco() to succeed.
                mAudioManager.setMode(AudioManager.MODE_IN_CALL);

                mIsCountDownOn = true;
                // mCountDown repeatedly tries to start bluetooth Sco audio connection.
                mCountDown.start();

                // need for audio sco, see mBroadcastReceiver
                mIsStarting = true;

                return true;
            }
        }

        return false;
    }

    /**
     * Register a headset profile listener
     * @return false    if device does not support bluetooth or current platform does not supports
     *                  use of SCO for off call or error in getting profile proxy.
     */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private boolean startBluetooth11()
    {
        Log.d(TAG, "startBluetooth11"); //$NON-NLS-1$

        // Device support bluetooth
        if (mBluetoothAdapter != null)
        {
            if (mAudioManager.isBluetoothScoAvailableOffCall())
            {
                // All the detection and audio connection are done in mHeadsetProfileListener
                if (mBluetoothAdapter.getProfileProxy(mContext, 
                                                    mHeadsetProfileListener, 
                                                    BluetoothProfile.HEADSET))
                {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * API < 11
     * Unregister broadcast receivers and stop Sco audio connection
     * and cancel count down.
     */
    private void stopBluetooth()
    {
        Log.d(TAG, "stopBluetooth"); //$NON-NLS-1$

        if (mIsCountDownOn)
        {
            mIsCountDownOn = false;
            mCountDown.cancel();
        }

        // Need to stop Sco audio connection here when the app
        // change orientation or close with headset still turns on.
        mContext.unregisterReceiver(mBroadcastReceiver);
        mAudioManager.stopBluetoothSco();
        mAudioManager.setMode(AudioManager.MODE_NORMAL);
    }

    /**
     * API >= 11
     * Unregister broadcast receivers and stop Sco audio connection
     * and cancel count down.
     */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    protected void stopBluetooth11()
    {
        Log.d(TAG, "stopBluetooth11"); //$NON-NLS-1$

        if (mIsCountDownOn)
        {
            mIsCountDownOn = false;
            mCountDown11.cancel();
        }

        if (mBluetoothHeadset != null)
        {
            // Need to call stopVoiceRecognition here when the app
            // change orientation or close with headset still turns on.
            mBluetoothHeadset.stopVoiceRecognition(mConnectedHeadset);
            mContext.unregisterReceiver(mHeadsetBroadcastReceiver);
            mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset);
            mBluetoothHeadset = null;
        }
    }

    /**
     * Broadcast receiver for API < 11
     * Handle headset and Sco audio connection states.
     */
    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver()
    {
        @SuppressWarnings({"deprecation", "synthetic-access"})
        @Override
        public void onReceive(Context context, Intent intent)
        {
            String action = intent.getAction();

            if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED))
            {       
                mConnectedHeadset = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                BluetoothClass bluetoothClass = mConnectedHeadset.getBluetoothClass();
                if (bluetoothClass != null)
                {
                    // Check if device is a headset. Besides the 2 below, are there other 
                    // device classes also qualified as headset?
                    int deviceClass = bluetoothClass.getDeviceClass();
                    if (deviceClass == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE 
                        || deviceClass == BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET)
                    {
                        // start bluetooth Sco audio connection.
                        // Calling startBluetoothSco() always returns faIL here, 
                        // that why a count down timer is implemented to call
                        // startBluetoothSco() in the onTick.
                        mAudioManager.setMode(AudioManager.MODE_IN_CALL);
                        mIsCountDownOn = true;
                        mCountDown.start();

                        // override this if you want to do other thing when the device is connected.
                        onHeadsetConnected();
                    }
                }

                Log.d(TAG, mConnectedHeadset.getName() + " connected"); //$NON-NLS-1$
            }
            else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED))
            {
                Log.d(TAG, "Headset disconnected"); //$NON-NLS-1$

                if (mIsCountDownOn)
                {
                    mIsCountDownOn = false;
                    mCountDown.cancel();
                }

                mAudioManager.setMode(AudioManager.MODE_NORMAL);

                // override this if you want to do other thing when the device is disconnected.
                onHeadsetDisconnected();
            }
            else if (action.equals(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED))
            {
                int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, 
                                                AudioManager.SCO_AUDIO_STATE_ERROR);

                if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED)
                {
                    mIsOnHeadsetSco = true;

                    if (mIsStarting)
                    {
                        // When the device is connected before the application starts,
                        // ACTION_ACL_CONNECTED will not be received, so call onHeadsetConnected here
                        mIsStarting = false;
                        onHeadsetConnected();
                    }

                    if (mIsCountDownOn)
                    {
                        mIsCountDownOn = false;
                        mCountDown.cancel();
                    }

                    // override this if you want to do other thing when Sco audio is connected.
                    onScoAudioConnected();

                    Log.d(TAG, "Sco connected"); //$NON-NLS-1$
                }
                else if (state == AudioManager.SCO_AUDIO_STATE_DISCONNECTED)
                {
                    Log.d(TAG, "Sco disconnected"); //$NON-NLS-1$

                    // Always receive SCO_AUDIO_STATE_DISCONNECTED on call to startBluetooth()
                    // which at that stage we do not want to do anything. Thus the if condition.
                    if (!mIsStarting)
                    {
                        mIsOnHeadsetSco = false;

                        // Need to call stopBluetoothSco(), otherwise startBluetoothSco()
                        // will not be successful.
                        mAudioManager.stopBluetoothSco();

                        // override this if you want to do other thing when Sco audio is disconnected.
                        onScoAudioDisconnected();
                    } 
                }
            }
        }
    };

    /**
     * API < 11
     * Try to connect to audio headset in onTick.
     */
    private CountDownTimer mCountDown = new CountDownTimer(10000, 1000)
    {

        @SuppressWarnings("synthetic-access")
        @Override
        public void onTick(long millisUntilFinished)
        {
            // When this call is successful, this count down timer will be canceled.
            mAudioManager.startBluetoothSco();

            Log.d(TAG, "\nonTick start bluetooth Sco"); //$NON-NLS-1$
        }

        @SuppressWarnings("synthetic-access")
        @Override
        public void onFinish()
        {
            // Calls to startBluetoothSco() in onStick are not successful.
            // Should implement something to inform user of this failure
            mIsCountDownOn = false;
            mAudioManager.setMode(AudioManager.MODE_NORMAL);

            Log.d(TAG, "\nonFinish fail to connect to headset audio"); //$NON-NLS-1$
        }
    };

    /**
     * API >= 11
     * Check for already connected headset and if so start audio connection.
     * Register for broadcast of headset and Sco audio connection states.
     */
    private BluetoothProfile.ServiceListener mHeadsetProfileListener = new BluetoothProfile.ServiceListener()
    {

        /**
         * This method is never called, even when we closeProfileProxy on onPause.
         * When or will it ever be called???
         */
        @Override
        public void onServiceDisconnected(int profile)
        {
            Log.d(TAG, "Profile listener onServiceDisconnected"); //$NON-NLS-1$
            stopBluetooth11();
        }

        @SuppressWarnings("synthetic-access")
        @TargetApi(Build.VERSION_CODES.HONEYCOMB)
        @Override
        public void onServiceConnected(int profile, BluetoothProfile proxy)
        {
            Log.d(TAG, "Profile listener onServiceConnected"); //$NON-NLS-1$

            // mBluetoothHeadset is just a headset profile, 
            // it does not represent a headset device.
            mBluetoothHeadset = (BluetoothHeadset) proxy;

            // If a headset is connected before this application starts,
            // ACTION_CONNECTION_STATE_CHANGED will not be broadcast. 
            // So we need to check for already connected headset.
            List<BluetoothDevice> devices = mBluetoothHeadset.getConnectedDevices();
            if (devices.size() > 0)
            {
                // Only one headset can be connected at a time, 
                // so the connected headset is at index 0.
                mConnectedHeadset = devices.get(0);

                onHeadsetConnected();

                // Should not need count down timer, but just in case.
                // See comment below in mHeadsetBroadcastReceiver onReceive()
                mIsCountDownOn = true;
                mCountDown11.start();

                Log.d(TAG, "Start count down"); //$NON-NLS-1$
            }

            // During the active life time of the app, a user may turn on and off the headset.
            // So register for broadcast of connection states.
            mContext.registerReceiver(mHeadsetBroadcastReceiver, 
                            new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED));
            // Calling startVoiceRecognition does not result in immediate audio connection.
            // So register for broadcast of audio connection states. This broadcast will
            // only be sent if startVoiceRecognition returns true.
            mContext.registerReceiver(mHeadsetBroadcastReceiver, 
                            new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED));
        }
    };

    /**
     *  API >= 11
     *  Handle headset and Sco audio connection states.
     */
    private BroadcastReceiver mHeadsetBroadcastReceiver = new BroadcastReceiver()
    {

        @SuppressWarnings("synthetic-access")
        @TargetApi(Build.VERSION_CODES.HONEYCOMB)
        @Override
        public void onReceive(Context context, Intent intent)
        {
            String action = intent.getAction();
            int state;
            if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED))
            {
                state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 
                                            BluetoothHeadset.STATE_DISCONNECTED);
                Log.d(TAG, "\nAction = " + action + "\nState = " + state); //$NON-NLS-1$ //$NON-NLS-2$
                if (state == BluetoothHeadset.STATE_CONNECTED)
                {
                    mConnectedHeadset = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

                    // Calling startVoiceRecognition always returns false here, 
                    // that why a count down timer is implemented to call
                    // startVoiceRecognition in the onTick.
                    mIsCountDownOn = true;
                    mCountDown11.start();

                    // override this if you want to do other thing when the device is connected.
                    onHeadsetConnected();

                    Log.d(TAG, "Start count down"); //$NON-NLS-1$
                }
                else if (state == BluetoothHeadset.STATE_DISCONNECTED)
                {
                    // Calling stopVoiceRecognition always returns false here
                    // as it should since the headset is no longer connected.
                    if (mIsCountDownOn)
                    {
                        mIsCountDownOn = false;
                        mCountDown11.cancel();
                    }
                    mConnectedHeadset = null;

                    // override this if you want to do other thing when the device is disconnected.
                    onHeadsetDisconnected();

                    Log.d(TAG, "Headset disconnected"); //$NON-NLS-1$
                }
            }
            else // audio
            {
                state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
                Log.d(TAG, "\nAction = " + action + "\nState = " + state); //$NON-NLS-1$ //$NON-NLS-2$
                if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED)
                {
                    Log.d(TAG, "\nHeadset audio connected");  //$NON-NLS-1$

                    mIsOnHeadsetSco = true;

                    if (mIsCountDownOn)
                    {
                        mIsCountDownOn = false;
                        mCountDown11.cancel();
                    }

                    // override this if you want to do other thing when headset audio is connected.
                    onScoAudioConnected();
                }
                else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED)
                {
                    mIsOnHeadsetSco = false;

                    // The headset audio is disconnected, but calling
                    // stopVoiceRecognition always returns true here.
                    mBluetoothHeadset.stopVoiceRecognition(mConnectedHeadset);

                    // override this if you want to do other thing when headset audio is disconnected.
                    onScoAudioDisconnected();

                    Log.d(TAG, "Headset audio disconnected"); //$NON-NLS-1$
                }
            }   
        }
    };

    /**
     * API >= 11
     * Try to connect to audio headset in onTick.
     */
    private CountDownTimer mCountDown11 = new CountDownTimer(10000, 1000)
    {
        @TargetApi(Build.VERSION_CODES.HONEYCOMB)
        @SuppressWarnings("synthetic-access")
        @Override
        public void onTick(long millisUntilFinished)
        {
            // First stick calls always returns false. The second stick
            // always returns true if the countDownInterval is set to 1000.
            // It is somewhere in between 500 to a 1000.
            mBluetoothHeadset.startVoiceRecognition(mConnectedHeadset);

            Log.d(TAG, "onTick startVoiceRecognition"); //$NON-NLS-1$
        }

        @SuppressWarnings("synthetic-access")
        @Override
        public void onFinish()
        {
            // Calls to startVoiceRecognition in onStick are not successful.
            // Should implement something to inform user of this failure
            mIsCountDownOn = false;
            Log.d(TAG, "\nonFinish fail to connect to headset audio"); //$NON-NLS-1$
        }
    };

}  

(April 30 2013) Edit to change to @TargetApi(Build.VERSION_CODES.HONEYCOMB) when necessary. If you have problem with java.lang.NoClassDefFoundError for API 8 or 9, just remove all the API >= 11 codes. The startBluetoothSco() works for all API versions.

Canaday answered 21/2, 2013 at 2:29 Comment(24)
Thanks for the detailed answer. This was for a demo and we decided that we didn't need the headset at this point, so I did not get to test this. But, there is so much detail in this answer I'm sure it would have helped.Fieldwork
Make BluetoothHeadsetUtils abstract and fix a slight error in the start() method.Canaday
how to trigger else if (action.equals(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED))Swen
ACTION_SCO_AUDIO_STATE_CHANGED is never executed. What to do that will be executed in old phonesSwen
You need API 8 or better, I do not know if you can work with bluetooth headset for API < 8Canaday
I had to add "import android.annotation.TargetApi;" to get this to compile.Crupper
It doesn't work for me. :( I get in LogCat: State = 12, Headset audio connected. I hear hum in my BT headset but I can't hear tts speaking neither on headset nor phone speaker. Even mic does not work. Does anybody can help me?Yun
I solved my issue: added mAudioManager.setMode(AudioManager.MODE_IN_CALL); in start startBluetooth11 methodYun
I am getting false every time from mBluetoothAdapter.getProfileProxy(mContext, mHeadsetProfileListener, inside method startBluetooth11() BluetoothProfile.HEADSET)Degeneracy
It a long time ago that I post the above answer and haven't touch Bluetooth since but the only way I see that you got false is because the context is invalid or your device does not support BluetoothProfile.HEADSETCanaday
Thank for reply. But I use current activity context and device also support bluetooth headset because i am able to listen songs. But not able to recognize voice.Degeneracy
The above is exactly I use for my code. If mBluetoothAdapter.getProfileProxy returns false all the time then I have no idea. Try it on another device and see if it worksCanaday
@Hoan Nguyen I am using above code BT recogn it is working in Samsung S4 but not in Nexus4 and Note 3. what mistake I am doing any setting required for it? Please help me.Elbertelberta
You have to look at the logcat to see what is wrong. In the code above I provide some log, you probably has to put more in to see where it fails.Canaday
@Hoan Nguyen your code is perfect running but problem is that when I disconnect Bluetooth Headset connection in this case device volume go low. Can you suggest any solution plz. ThanksElbertelberta
Another Problem: #25463248Elbertelberta
@HoanNguyen this code working fine but when i tried same on Nexus i getting error please check with this url #25667794Assess
@HoanNguyen: Thank you so much. I am writing a application to recording audio from handsfree device. It is very useful for me. Could you send your code to my email "[email protected]"Clan
This works really well but I've not been able to get it to work on Nexus 7 (with Lollipop). At least it doesn't crash and burn. Thanks for this solution. It seems to be the best one of the bunch I've tried.Sleep
@HoanNguyen thanks for posting your class. It really simplifies bluetooth management. I'm not sure why android doesn't just work automatically if BT device is connected. It seems silly for us to have to jump through all this as developers.Rivalry
@HoanNguyen Thanks for your answer.This is working fine. But how can I get bluetooth voice input to text in my android app?Grassgreen
MODIFY_PHONE_STATE permission, needed for startVoiceRecognition is restricted to system appsItself
Please describe what is mTts?Elevation
Text to speech class member.Canaday
C
13

I think all you have to do is change the audio settings on your application.

you should put the following code when you want to receive and send the sound from your headset via bluetooth.

 AudioManager audioManager;

audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);

audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.startBluetoothSco();
audioManager.setBluetoothScoOn(true);

Do not forget to restore the normal settings on the phone in the following way when you're not using the bluetooth.

audioManager.setMode(AudioManager.MODE_NORMAL);
audioManager.stopBluetoothSco();
audioManager.setBluetoothScoOn(false);

the permissions that are needed are the following:

<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

it's easy!

Cavendish answered 1/6, 2013 at 13:55 Comment(5)
Excellent - this answer worked for me the best. All I need now is some way of detecting a bluetooth headset is connected.Horst
You can do that with BroadcastReceiver: public class BTReceiver extends BroadcastReceiver { int state = 0; @Override public void onReceive(Context context, Intent intent) { Log.d("Z", "Received: Bluetooth"); try { Bundle extras = intent.getExtras(); if (extras != null) { //Do something } } catch (NullPointerException e) { Log.d("NPE", "NullPointerException " + e.toString()); } }Nihhi
Also you will need add this in your manifest: <receiver android:name="com.android.sam.receiver.BTReceiver" android:enabled="true" android:permission="android.permission.BLUETOOTH" > <intent-filter> <action android:name="android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED" /> </intent-filter> </receiver>Nihhi
And the follow permissions: <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BROADCAST_STICKY" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />Nihhi
Thank you for this post. The setMode was the last hurdle for me. That fixed my issue of either getting voice input or TTS. I couldn't get both before I set the mode. I needed to add this in order to use a 3rd party TTS (Nuance) to work. Google worked fine, but some rugged devices don't install the Google voice files, and our application they have no internet to install them.Severini
F
3

Even though the Bluetooth headset is paired and connected to the Phone audio profile (HF/HS), the actual audio connection (SCO) is established only when a call comes in and accepted.

For your VR application to accept the voice input from a Bluetooth Headset your application will have to establish a SCO to the headset on some VR input trigger, You will need to use the following - isBluetoothScoAvailableOffCall to check if the platform supports this capability, then use the startBluetoothSco and stopBluetoothSco to start the SCO to the headset.

Fragmentation answered 21/2, 2013 at 2:16 Comment(1)
thanks. i've solved my problem with startBluetoothSco method but this works only with VR but not whit TTS.there's another way to enable TTS to speak into headset?Burble
H
2

Add these permissions:

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

Then create a Broadcast Receiver for Bluetooth.

public class BTReceiver extends BroadcastReceiver {
    private static final String TAG = "BTReceiver";
    int state = 0;
    AudioManager audioManager;

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d("Z", "Received: Bluetooth");
        try {
            Bundle extras = intent.getExtras();
            if (extras != null) { //Do something
                audioManager = (AudioManager) context.getSystemService(AUDIO_SERVICE);

                String action = intent.getAction();
                Toast.makeText(context, action, Toast.LENGTH_LONG).show();
                int state;
                if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
                    state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
                            BluetoothHeadset.STATE_DISCONNECTED);
                    Log.d(TAG, "\nAction = " + action + "\nState = " + state); //$NON-NLS-1$ //$NON-NLS-2$
                    if (state == BluetoothHeadset.STATE_CONNECTED) {
                        setModeBluetooth();
                    } else if (state == BluetoothHeadset.STATE_DISCONNECTED) {
                        // Calling stopVoiceRecognition always returns false here
                        // as it should since the headset is no longer connected.
                        setModeNormal();

                        Log.d(TAG, "Headset disconnected"); //$NON-NLS-1$
                    }
                } else // audio
                {
                    state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
                    Log.d(TAG, "\nAction = " + action + "\nState = " + state); //$NON-NLS-1$ //$NON-NLS-2$
                    if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
                        Log.d(TAG, "\nHeadset audio connected");  //$NON-NLS-1$
                        setModeBluetooth();
                    } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
                        setModeNormal();
                        Log.d(TAG, "Headset audio disconnected"); //$NON-NLS-1$
                    }
                }
            }
        } catch (Exception e) {
            Log.d("Exception", "Exception " + e.toString());
        }
    }


    private void setModeBluetooth() {
        try {

            audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
            audioManager.startBluetoothSco();
            audioManager.setBluetoothScoOn(true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void setModeNormal() {
        try {
            audioManager.setMode(AudioManager.MODE_NORMAL);
            audioManager.stopBluetoothSco();
            audioManager.setBluetoothScoOn(false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

Register the receiver in Manifest:

 <receiver
            android:name=".speech.BTReceiver"
            android:enabled="true"
            android:permission="android.permission.BLUETOOTH">
            <intent-filter>
                <action android:name="android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED" />
            </intent-filter>
        </receiver>

In your activity Register and UnRegister this receiver:

First initialize it, in onCreate

 mHeadsetBroadcastReceiver = new BTReceiver();

then

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

    registerReceiver(mHeadsetBroadcastReceiver,
            new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED));
    // Calling startVoiceRecognition does not result in immediate audio connection.
    // So register for broadcast of audio connection states. This broadcast will
    // only be sent if startVoiceRecognition returns true.
    registerReceiver(mHeadsetBroadcastReceiver,
            new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED));
}

@Override
protected void onPause() {
    unregisterReceiver(mHeadsetBroadcastReceiver);

    super.onPause();
}
Hovey answered 14/2, 2020 at 16:36 Comment(2)
This is the "better working" solution for me. I'm using voice recognition through bluetooth earpieces, however if there is no sound (input or output) for a few seconds, microphone stops working. Any idea ?Itself
Linked problem : #72173155Itself
E
1

startBluetoothSco takes a long time to establish, which is problem if you need to use it for voice control.

Is there a quick way to use the bluetooth mic to listen and then turn it off after listening?

If the connection is on the whole time then it is not possible to stream audio via A2DP.

So, ideal world:

Audio out via A2DP. When it starts listening, it uses SCO bluetooth mic. Any response is A2DP again.

In fact, if it is already connected - can you switch on the fly by change the media stream to call stream? If so, is there any noticeable delay?

Eberhardt answered 28/3, 2013 at 9:51 Comment(0)
H
0

Hoan Nguyen has done a great job!
Testing his code I have noticed that in some cases stopBluetoothSco() is not called.

I propose a little change in CountDownTimer:

private CountDownTimer mCountDown11 = new CountDownTimer(10000, 1000)
{
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    @SuppressWarnings("synthetic-access")
    @Override
    public void onTick(long millisUntilFinished)
    {

        mBluetoothHeadset.startVoiceRecognition(mConnectedHeadset);

        Log.d(TAG, "onTick startVoiceRecognition"); //$NON-NLS-1$
    }

    @SuppressWarnings("synthetic-access")
    @Override
    public void onFinish()
    {
        mIsCountDownOn = false;

        /* START EDIT: Unregister broadcast receivers and stop Sco audio connection
           and cancel count down if fails to connect. */

        stopBluetooth11();

        /* END EDIT */

        Log.d(TAG, "\nonFinish fail to connect to headset audio"); //$NON-NLS-1$
    }
};
Homoio answered 16/1, 2014 at 0:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.