7

我写了一些代码来检测蓝牙耳机连接并通过耳机启动音频。对于 API 11 及更高版本,可以在连接耳机时调用 startVoiceRecognition。所以几个用例如下:

  1. 耳机在应用程序启动之前已打开 应用
    程序应在启动时检查耳机是否连接并建立音频连接。

  2. 用户在应用程序的生命周期内打开耳机应用
    程序应注册耳机连接状态的广播,并在接收到连接状态时启动音频连接。

第二个用例有问题。当收到连接状态时,我调用 startVoiceRecognition,但它总是返回 false。所以我必须实现一个计时器,大约一秒钟后,调用将返回 true。我猜操作系统和耳机需要一些时间才能让一切准备就绪。有谁知道如何在不实现计时器的情况下获得耳机音频连接。如果不可能,是否应该由操作系统而不是应用程序来处理这种情况(例如 READY_FOR_AUDIO_CONNECTION 广播)?

以下是 API 11 或更高版本的完整工作代码。

清单权限

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

代码

public class MainActivity extends Activity
{

    protected TextView mInfoTextview;

    protected BluetoothAdapter mBluetoothAdapter;
    protected BluetoothHeadset mBluetoothHeadset;
    protected BluetoothDevice mConnectedHeadset;

    protected AudioManager mAudioManager;


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

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

        setContentView(R.layout.activity_main);
        mInfoTextview = (TextView) findViewById(R.id.main_textview);

        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

        if (mBluetoothAdapter != null)
        {
            mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
            if (mAudioManager.isBluetoothScoAvailableOffCall())
            {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
                {
                    mBluetoothAdapter.getProfileProxy(this, mHeadsetProfileListener, BluetoothProfile.HEADSET);
                }
            }
        }
    }

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

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
        {
            if (mBluetoothHeadset != null)
            {
                // Need to call stopVoiceRecognition here when the app
                // change orientation or close with headset still turns on.
                 mBluetoothHeadset.stopVoiceRecognition(mConnectedHeadset);
                 unregisterReceiver(mHeadsetBroadcastReceiver);
                 mCountDown.cancel();
            }
            mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset);
        }
        Log.d(TAG, "onDestroy"); //$NON-NLS-1$
    }

    protected 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$
            mBluetoothHeadset.stopVoiceRecognition(mConnectedHeadset);
            unregisterReceiver(mHeadsetBroadcastReceiver);
            mBluetoothHeadset = null;
        }

        @Override
        public void onServiceConnected(int profile, BluetoothProfile proxy)
        {
            Log.d(TAG, "Profile listener onServiceConnected"); //$NON-NLS-1$

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

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

                String log;

                // The audio should not yet be connected at this stage.
                // But just to make sure we check.
                if (mBluetoothHeadset.isAudioConnected(mConnectedHeadset))
                {
                    log = "Profile listener audio already connected"; //$NON-NLS-1$     
                }
                else
                {
                    // The if statement is just for debug. So far startVoiceRecognition always 
                    // returns true here. What can we do if it returns false? Perhaps the only
                    // sensible thing is to inform the user.
                    // Well actually, it only returns true if a call to stopVoiceRecognition is
                    // call somewhere after a call to startVoiceRecognition. Otherwise, if 
                    // stopVoiceRecognition is never called, then when the application is restarted
                    // startVoiceRecognition always returns false whenever it is called.
                    if (mBluetoothHeadset.startVoiceRecognition(mConnectedHeadset))
                    {
                        log = "Profile listener startVoiceRecognition returns true"; //$NON-NLS-1$
                    }
                    else
                    {
                        log = "Profile listener startVoiceRecognition returns false"; //$NON-NLS-1$
                    }   
                }

                mInfoTextview.setText("Device name = " + mConnectedHeadset.getName() //$NON-NLS-1$
                                        + "\n\n" + log); //$NON-NLS-1$
                Log.d(TAG, log); 
            }

            // During the active life time of the app, a user may turn on and off the head set.
            // So register for broadcast of connection states.
            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));
        }
    };

    protected BroadcastReceiver mHeadsetBroadcastReceiver = new BroadcastReceiver()
    {

        @Override
        public void onReceive(Context context, Intent intent)
        {
            String action = intent.getAction();
            int state;
            int previousState = intent.getIntExtra(BluetoothHeadset.EXTRA_PREVIOUS_STATE, BluetoothHeadset.STATE_DISCONNECTED);
            String log = ""; //$NON-NLS-1$
            if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED))
            {
                state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_DISCONNECTED);
                if (state == BluetoothHeadset.STATE_CONNECTED)
                {
                    mConnectedHeadset = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

                    mInfoTextview.append("\n\nDevice name = " + mConnectedHeadset.getName()); //$NON-NLS-1$

                    // Audio should not be connected yet but just to make sure.
                    if (mBluetoothHeadset.isAudioConnected(mConnectedHeadset))
                    {
                        log = "Headset connected audio already connected"; //$NON-NLS-1$
                    }
                    else
                    {
                        // Calling startVoiceRecognition always returns false here, 
                        // that why a count down timer is implemented to call
                        // startVoiceRecognition in the onTick and onFinish.
                        if (mBluetoothHeadset.startVoiceRecognition(mConnectedHeadset))
                        {
                            log = "Headset connected startVoiceRecognition returns true"; //$NON-NLS-1$
                        }
                        else
                        {
                            log = "Headset connected startVoiceRecognition returns false"; //$NON-NLS-1$
                            mCountDown.start();
                        }
                    }
                }
                else if (state == BluetoothHeadset.STATE_DISCONNECTED)
                {
                    // Calling stopVoiceRecognition always returns false here
                    // as it should since the headset is no longer connected.
                    mConnectedHeadset = null;
                }
            }
            else // audio
            {
                state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
                if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED)
                {
                    log = "Head set audio connected, cancel countdown timer";  //$NON-NLS-1$
                    mCountDown.cancel();    
                }
                else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED)
                {
                    // The headset audio is disconnected, but calling
                    // stopVoiceRecognition always returns true here.
                    boolean returnValue = mBluetoothHeadset.stopVoiceRecognition(mConnectedHeadset);
                    log = "Audio disconnected stopVoiceRecognition return " + returnValue; //$NON-NLS-1$
                }
            }   

            log += "\nAction = " + action + "\nState = " + state //$NON-NLS-1$ //$NON-NLS-2$
                    + " previous state = " + previousState; //$NON-NLS-1$
            mInfoTextview.append("\n\n" + log); //$NON-NLS-1$
            Log.d(TAG, log);
        }
    };


    protected CountDownTimer mCountDown = new CountDownTimer(10000, 1000)
    {

        @Override
        public void onTick(long millisUntilFinished)
        {
            String log;
            if (mBluetoothHeadset.isAudioConnected(mConnectedHeadset))
            {
                log = "\nonTick audio already connected"; //$NON-NLS-1$
            }
            else
            {
                // 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.
                if (mBluetoothHeadset.startVoiceRecognition(mConnectedHeadset))
                {
                    log = "\nonTick startVoiceRecognition returns true"; //$NON-NLS-1$
                }
                else
                {
                    log = "\nonTick startVoiceRecognition returns false"; //$NON-NLS-1$
                }
            }

            mInfoTextview.append(log);
            Log.d(TAG, log);
        }

        @Override
        public void onFinish()
        {
            String log;
            if (mBluetoothHeadset.isAudioConnected(mConnectedHeadset))
            {
                log = "\nonFinish audio already connected"; //$NON-NLS-1$
            }
            else
            {
                if (mBluetoothHeadset.startVoiceRecognition(mConnectedHeadset))
                {
                    log = "\nonFinish startVoiceRecognition returns true"; //$NON-NLS-1$
                }
                else
                {
                    log = "\nonFinish startVoiceRecognition returns false"; //$NON-NLS-1$
                }
            }
            mInfoTextview.append(log);
            Log.d(TAG, log);
        }
    };
}

布局文件

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <TextView
        android:id="@+id/main_textview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" 
        android:textIsSelectable="false" />

</ScrollView>
4

0 回答 0