蓝牙(BlueTooth)是一种无线技术标准,是当今移动终端最流行的三种数据传输方案之一,其余两种是WiFi和NFC(由于红外传输只能是直线传输,故更多地用于遥控器等设备,不适合数据传输)。蓝牙的传输特点是传输距离短(≤10m),速度适中,为24Mbps(比WiFi(802.11ac 1.3Gbps)慢,比NFC(≤400Kbit/s)快),功耗低(最新4.1版本)。
本文将介绍在Android移动设备上蓝牙技术的使用。
调用系统蓝牙技术方案实现设备间的数据传输,涉及到侵犯用户的隐私,需添加如下权限:
//程序中使用了蓝牙技术需要添加的权限
<uses-permission android:name="android.permission.BLUETOOTH" />
//当本程序需要和其他程序绑定时添加的权限
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
创建BlueToothAdapter对象,判断该对象是否为空,以确定当前设备是否支持蓝牙:
//获得BluetoothAdapter对象
BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
//通过判断BluetoothAdapter对象是否为空,来确定设备是否支持蓝牙功能
if (mAdapter != null) {
//设备支持蓝牙功能
} else {
//设备不支持蓝牙功能
}
//断言当前设备具备蓝牙功能
assert(mAdapter != null);
//若BluetoothAdapter.isEnabled()返回true,则当前设备处于开启状态,否则处于关闭状态
if(mAdapter.isEnabled())
{
//当前设备的蓝牙处于开启状态
}
else
{
//当前设备的蓝牙处于关闭状态
}
通过隐式intent开启蓝牙功能。可以在onActivityResult()方法中获取开启成功与否的消息;也可以使用广播接收器(BroadcastReceiver)接收系统发出的关于设备蓝牙状态的广播消息。
//使用隐式intent开启蓝牙
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
//启动蓝牙
startActivityForResult(intent, 0);
//在onActivityResult()方法中得知蓝牙是否开启
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == 0)
{
if(resultCode == RESULT_OK)
{
//打开成功
}
else
{
//打开失败
}
}
}
//通过BroadcastReceiver接收系统发出的关于设备蓝牙状态的广播消息
//在onCreate方法中动态注册BroadcastReceiver
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//表示程序希望接收到蓝牙状态发生改变时,系统发出的广播
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(receiver, filter);
}
//定义BroadcastReceiver截获广播
private BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
//截获的广播附带有BluetoothAdapter.EXTRA_STATE信息,通过该键对应的值可以判断系统发出的是什么广播,从而确定蓝牙处于何种状态
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
switch (state) {
//蓝牙处于关闭状态
case BluetoothAdapter.STATE_OFF:
//"STATE_OFF"状态
break;
//蓝牙处于开启状态
case BluetoothAdapter.STATE_ON:
//"STATE_ON"状态
break;
//蓝牙处于正在打开状态(开启蓝牙一个异步操作)
case BluetoothAdapter.STATE_TURNING_ON:
//"STATE_TURNING_ON"状态
break;
//蓝牙处于正在关闭状态
case BluetoothAdapter.STATE_TURNING_OFF:
//"STATE_TURNING_OFF"状态
break;
default:
break;
}
}
};
蓝牙设备为了安全起见,可设置为不可见,其他设备无法搜索到蓝牙处于不可见状态的设备;反之,若需要被其他设备搜索到,需要将蓝牙设为可见:
//通过隐式intent设置蓝牙的可见性
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
//设置蓝牙的可见性为300秒(5分钟)
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);
当设备变为可被搜索或变为不可被搜索时,系统会发出一条广播,注册一个广播接收器可获得系统发出的广播:
IntentFilter filter = new IntentFilter();
//监听 “设备变为可被搜索时”(也可以监听“设备变为不可见时”),系统发出的广播
filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
//注册该广播
registerReceiver(mReceiver, filter);
在onReceive方法中,接受广播:
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
//接收“当蓝牙可见状态发生改变时”系统发出的广播
if (BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(action)) {
//获取该广播所附带的BluetoothAdapter.EXTRA_SCAN_MODE键对应的值
int scanMode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, 0);
//若值为BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE,表示蓝牙变为可见状态
if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
setProgressBarIndeterminateVisibility(true);
}
//否则蓝牙变为不可见状态
else {
setProgressBarIndeterminateVisibility(false);
}
}
}
开启蓝牙后,可以通过下面方法查找周围已开启蓝牙且蓝牙可见的设备:
BlueToothAdapter.startDiscovery();
当程序开始查找设备、结束查找设备、发现一个可见的设备,系统都会发送一条广播,程序需要通过广播接收器接受响应状态发生时系统发出的广播,当接收到的广播所附带的action为BluetoothAdapter.ACTION_DISCOVERY_STARTED时,表示程序开始查找设备;action为BluetoothAdapter.ACTION_DISCOVERY_FINISHED时,表示程序结束查找,action为BluetoothDevice.ACTION_ FOUND时,intent附带的键BluetoothDevice.EXTRA_DEVICE所对应的值,就是这个搜索到的蓝牙设备:
//查找设备
filter.addAction(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter);
//接收搜索到的设备
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
setProgressBarIndeterminateVisibility(true);
//初始化数据列表
mDeviceList.clear();
mAdapter.notifyDataSetChanged();
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
setProgressBarIndeterminateVisibility(false);
}
//搜索到一个设备
else if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
//将搜索到的设备添加到ListView中
mDeviceList.add(device);
//Adapter通知刷新ListView
mAdapter.notifyDataSetChanged();
}
查找到设备后,可通过createBond()方法对设备进行绑定。createBond()方法在API 19 (Android 4.4) 及以上被引入,低于API 19的版本不支持蓝牙绑定:
mListView.setOnItemClickListener(bindDeviceClick);
private AdapterView.OnItemClickListener bindDeviceClick = new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
BluetoothDevice device = mDeviceList.get(i);
//绑定选中的设备(需API级别≥19),同时需要权限<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
device.createBond();
当绑定或解绑设备时,系统也会发出广播,在onReceive()方法中捕获该广播:
//绑定状态发生改变
if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
//没有绑定的设备
if (remoteDevice == null) {
//未绑定任何设备
return;
}
//绑定设备处于何种状态
int status = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 0);
//已绑定设备
if (status == BluetoothDevice.BOND_BONDED) {
//"Bonded " + remoteDevice.getName())
}
//正在绑定设备
else if (status == BluetoothDevice.BOND_BONDING) {
//"Bonding " + remoteDevice.getName())
}
//结束绑定设备
else if (status == BluetoothDevice.BOND_NONE) {
//"Not bond " + remoteDevice.getName())
}
}
在TCP/IP通信中,可使用Socket交换数据,客户端(Client)和服务器端(Server)的通信流程如上图所示。在客户端,首先建立一个socket对象,然后建立一个connect连接服务,在connect方法中传入需要要换数据的服务器端IP地址和端口号(在蓝牙传输中,这里有所不同),接着利用IO流读写数据,最后关闭连接服务;在服务器端,同样需要建立一个socket连接,并绑定端口号,接着客户端将等待client端的socket的连接请求,此时会阻塞当前线程,所以需要将该操作放到子线程中执行,当有client请求连接时,服务器端accept该请求,并建立一个新的socket对象与之通信,原有socket继续等待,当新建socket与客户端通信结束后,断开连接。(一般情况下由客户端中断连接)
在蓝牙的socket通信中,服务器端通信流程不包含上图所示的bind()和listen()步骤,也就是说,socket创建后直接到达accept,阻塞也是发生在accept;客户端的流程就是上图所示,但参数与TCP/IP通信时不一样。
总结起来,Android蓝牙的server端的建立步骤如下:
client端的建立步骤如下:
当触发监听时,程序进入监听状态,此时设备可看成分服务器端,用于等待其他设备绑定:
//创建一个新线程,用于处理会发生阻塞的监听网络的方法accept
private AcceptThread mAcceptThread;
//获取BlueToothAdapter对象
private BlueToothController mController = new BlueToothController();
//创建Handler对象,用于用于在子线程中通知主线程(UI线程)更新UI
private Handler mUIHandler = new MyHandler();
private class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
//开始监听时,接收由子线程发来的消息
case Constant.MSG_START_LISTENING:
//更新UI
setProgressBarIndeterminateVisibility(true);
break;
//结束监听时,接收由子线程发来的消息
case Constant.MSG_FINISH_LISTENING:
//更新UI
setProgressBarIndeterminateVisibility(false);
break;
//接收到数据后,接收由子线程发来的消息及其附加信息
case Constant.MSG_GOT_DATA:
//在UI线程中显示
showToast("data: "+String.valueOf(msg.obj));
break;
//当发生错误时,接收由子线程发来的消息
case Constant.MSG_ERROR:
//显示错误原因
showToast("error: "+String.valueOf(msg.obj));
break;
//当程序作为客户端连接到服务器后,接收接收由子线程发来的消息
case Constant.MSG_CONNECTED_TO_SERVER:
//通知用户连接成功
showToast("Connected to Server");
break;
//当程序作为服务器端绑定了一个客户端后,接收接收由子线程发来的消息
case Constant.MSG_GOT_A_CLINET:
//通知用户绑定成功
showToast("Got a Client");
break;
}
}
}
//触发监听状态
@Override
onClick(View v)
{
if( mAcceptThread != null) {
mAcceptThread.cancel();
}
//创建一个新线程,传入BlueToothAdapter对象实例和Handler对象实例
mAcceptThread = new AcceptThread(mController.getAdapter(), mUIHandler);
//启动该线程
mAcceptThread.start();
}
子线程AcceptThread如下所示:
public class AcceptThread extends Thread {
private static final String NAME = "BlueToothClass";
//为了保证蓝牙连接,必须使用如下代码的唯一的UUID:
public static final String CONNECTTION_UUID = "00001101-0000-1000-8000-00805F9B34FB";
private static final UUID MY_UUID = UUID.fromString(CONNECTTION_UUID);
private final BluetoothServerSocket mmServerSocket;
private final BluetoothAdapter mBluetoothAdapter;
private final Handler mHandler;
private ConnectedThread mConnectedThread;
public AcceptThread(BluetoothAdapter adapter, Handler handler) {
mBluetoothAdapter = adapter;
mHandler = handler;
// 构造方法中创建一个临时的BluetoothServerSocket对象的引用
BluetoothServerSocket tmp = null;
try {
tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
} catch (IOException e) { }
mmServerSocket = tmp;
}
//子线程中运行的代码
public void run() {
BluetoothSocket socket = null;
// 不断监听来自客户端的连接请求
while (true) {
try {
//通知UI线程:“服务器端开始监听客户端的请求”
mHandler.sendEmptyMessage(Constant.MSG_START_LISTENING);
//accept方法会阻塞线程
socket = mmServerSocket.accept();
} catch (IOException e) {
//若出现异常,退出监听
mHandler.sendMessage(mHandler.obtainMessage(Constant.MSG_ERROR, e));
break;
}
// 连接成功
if (socket != null) {
// 另开启一个子线程,新建一个socket用于传输数据
manageConnectedSocket(socket);
try {
//强制关闭监听
mmServerSocket.close();
mHandler.sendEmptyMessage(Constant.MSG_FINISH_LISTENING);
} catch (IOException e) {
e.printStackTrace();
}
break;
}
}
}
private void manageConnectedSocket(BluetoothSocket socket) {
//只支持同时处理一个连接
if( mConnectedThread != null) {
mConnectedThread.cancel();
}
//通知UI线程连接到一个蓝牙设备(客户端)
mHandler.sendEmptyMessage(Constant.MSG_GOT_A_CLINET);
//另开启的新线程,用于传输数据,传入BluetoothSocket对象引用和Handler对象引用
mConnectedThread = new ConnectedThread(socket, mHandler);
//启动线程
mConnectedThread.start();
}
//主动停止监听
public void cancel() {
try {
mmServerSocket.close();
//通知UI线程:“已停止监听”
mHandler.sendEmptyMessage(Constant.MSG_FINISH_LISTENING);
} catch (IOException e) { }
}
//向客户端写入数据
public void sendData(byte[] data) {
if( mConnectedThread!=null){
mConnectedThread.write(data);
}
}
}
另创建的用于与绑定的设备进行数据通信的线程:
public class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
private final Handler mHandler;
public ConnectedThread(BluetoothSocket socket, Handler handler) {
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
mHandler = handler;
//获得输入输出流
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) { }
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
//在子线程中运行的代码
public void run() {
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()
// 不断循环以读取数据
while (true) {
try {
// 读取数据
bytes = mmInStream.read(buffer);
// 将读取的数据信息发送至UI线程并显示数据
if( bytes >0) {
Message message = mHandler.obtainMessage(Constant.MSG_GOT_DATA, new String(buffer, 0, bytes, "utf-8"));
mHandler.sendMessage(message);
}
Log.d("GOTMSG", "message size" + bytes);
} catch (IOException e) {
mHandler.sendMessage(mHandler.obtainMessage(Constant.MSG_ERROR, e));
break;
}
}
}
//写入数据
public void write(byte[] bytes) {
try {
mmOutStream.write(bytes);
} catch (IOException e) { }
}
//取消连接
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
当设备作为客户端与其他设备(服务端)交换数据时,需保证设备之间已绑定:
//作为客户端连接,设备之间处于绑定状态
mListView.setOnItemClickListener(bindedDeviceClick);
private AdapterView.OnItemClickListener bindedDeviceClick = new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
BluetoothDevice device = mBondedDeviceList.get(i);
if( mConnectThread != null) {
mConnectThread.cancel();
}
//创建子线程
mConnectThread = new ConnectThread(device, mController.getAdapter(), mUIHandler);
mConnectThread.start();
}
};
客户端的线程与服务器端的子线程类似,代码如下:
public class ConnectThread extends Thread {
//UUID必须与之前的UUID一致
private static final UUID MY_UUID = UUID.fromString(Constant.CONNECTTION_UUID);
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
private BluetoothAdapter mBluetoothAdapter;
private final Handler mHandler;
private ConnectedThread mConnectedThread;
public ConnectThread(BluetoothDevice device, BluetoothAdapter adapter, Handler handler) {
// because mmSocket is final
BluetoothSocket tmp = null;
mmDevice = device;
mBluetoothAdapter = adapter;
mHandler = handler;
try {
tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) { }
mmSocket = tmp;
}
public void run() {
// 首先使设备不再查找其他设备,以提高效率
mBluetoothAdapter.cancelDiscovery();
try {
//connect方法会阻塞线程
mmSocket.connect();
} catch (Exception connectException) {
mHandler.sendMessage(mHandler.obtainMessage(Constant.MSG_ERROR, connectException));
// 连接错误,结束线程并抛出异常
try {
mmSocket.close();
} catch (IOException closeException) { }
return;
}
// 与设备交换数据需定义在一个新的线程中(新建一个socket专门用于交互数据)
manageConnectedSocket(mmSocket);
}
private void manageConnectedSocket(BluetoothSocket mmSocket) {
mHandler.sendEmptyMessage(Constant.MSG_CONNECTED_TO_SERVER);
mConnectedThread = new ConnectedThread(mmSocket, mHandler);
mConnectedThread.start();
}
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
public void sendData(byte[] data) {
if( mConnectedThread!=null){
mConnectedThread.write(data);
}
}
}
蓝牙规范(BlueTooth Profile)又叫配置文件,是一种协议。蓝牙规范定义了一种基于蓝牙的应用(如蓝牙耳机、车载蓝牙设备、蓝牙鼠标),每各蓝牙规范主要包括针对开发者的接口、消息的格式和标准,以及如何使用蓝牙协议,使用蓝牙协议通信的蓝牙设备必须使用同一UUID,否则无法配对通信。
常用的蓝牙规范:
A2DP(Advanced Audio Distribution Profile)蓝牙音频传输模型
–应用方式:蓝牙立体声耳机+移动终端
–用途:听音乐等
BlueTooth HeadSet 带打电话功能的蓝牙耳机
–用途:蓝牙耳机+移动终端、车载蓝牙+移动终端
下列代码以HeadSet为例,演示了使用蓝牙耳机与手机连接的蓝牙协议:
public abstract class BluetoothHeadsetUtils {
private Context mContext;
private BluetoothAdapter mBluetoothAdapter;
private BluetoothHeadset mBluetoothHeadset;
private BluetoothDevice mConnectedHeadset;
private AudioManager mAudioManager;
private boolean mIsOnHeadsetSco;
private boolean mIsStarted;
/** * 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 (mBluetoothAdapter.isEnabled() == false){
mIsStarted = false;
return mIsStarted;
}
if (!mIsStarted) {
mIsStarted = true;
mIsStarted = startBluetooth();
}
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;
stopBluetooth();
}
}
/** * * @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 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 startBluetooth() {
// 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. */
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
protected void stopBluetooth() {
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;
}
}
/** * 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) {
stopBluetooth();
}
@SuppressWarnings("synthetic-access")
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
// 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();
}
// 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);
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.
// override this if you want to do other thing when the
// device is connected.
onHeadsetConnected();
} else if (state == BluetoothHeadset.STATE_DISCONNECTED) {
mConnectedHeadset = null;
// override this if you want to do other thing when the
// device is disconnected.
onHeadsetDisconnected();
}
} else // audio
{
state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
// override this if you want to do other thing when headset
// audio is connected.
onScoAudioConnected();
} else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
mIsOnHeadsetSco = false;
// override this if you want to do other thing when headset
// audio is disconnected.
onScoAudioDisconnected();
}
}
}
};
}
public class BluetoothHelper extends BluetoothHeadsetUtils {
private final static String TAG = BluetoothHelper.class.getSimpleName();
Context mContext;
int mCallvol;
// int mMediaVol;
AudioManager mAudioManager;
public BluetoothHelper(Context context) {
super(context);
mContext = context;
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mCallvol = mAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
// mMediaVol = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
}
@Override
public void onHeadsetDisconnected() {
mAudioManager.setBluetoothScoOn(false);
}
@Override
public void onHeadsetConnected() {
mAudioManager.setBluetoothScoOn(true); // 打开SCO
// mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0);
}
@Override
public void onScoAudioDisconnected() {
mAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL, mCallvol, 0);
// mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, mMediaVol, 0);
}
@Override
public void onScoAudioConnected() {
mAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL, mAudioManager.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL), 0);
// mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0);
}
}
有关蓝牙协议的部分,由于Google官方并未提供更多API支持,故了解即可。