我写了一些代码来检测蓝牙耳机连接并通过耳机启动音频。对于 API 11 及更高版本,可以在连接耳机时调用 startVoiceRecognition。所以几个用例如下:
耳机在应用程序启动之前已打开 应用
程序应在启动时检查耳机是否连接并建立音频连接。用户在应用程序的生命周期内打开耳机应用
程序应注册耳机连接状态的广播,并在接收到连接状态时启动音频连接。
第二个用例有问题。当收到连接状态时,我调用 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>