蓝牙通讯录主要包含联系人和通话记录
一、BluetoothPbapClient功能介绍
1、主要实现电话簿下载
2、电话号码簿访问协议(Phonebook Access Profile)
二、BluetoothPbapClient的使用
public void getProfileProxy() {
boolean isPbapService = mAdapter.getProfileProxy(mContext, new ProxyServiceListener(), BluetoothProfile.PBAP_CLIENT);
Log.i(TAG, "getProfileProxy"+isPbapService);
}
private final class ProxyServiceListener implements BluetoothProfile.ServiceListener{
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
Log.d(TAG,"Bluetooth service connected profile == " + profile);
if (profile == BluetoothProfile.PBAP_CLIENT) {
mPbapClient = (BluetoothPbapClient) proxy;
isPbapProfileReady = true;
}
}
@Override
public void onServiceDisconnected(int profile) {
Log.d(TAG, "BluetoothPbapClient Profile Proxy Disconnected");
if (profile == BluetoothProfile.PBAP_CLIENT) {
isPbapProfileReady = false;
mPbapClient = null;
}
}
}
2、BluetoothPbapClient连接
// 连接
public boolean connect(BluetoothDevice device) {
if (null != mPbapClient) {
return mPbapClient.connect(device);
}
Log.i(TAG, "mPbapClient == null");
return false;
}
//断连
public boolean disconnect(BluetoothDevice device) {
if (null != mPbapClient) {
return mPbapClient.disconnect(device);
}
Log.i(TAG, "mPbapClient == null");
return false;
}
//判断连接状态
public int getConnectionState() {
if (null != mPbapClient) {
List<BluetoothDevice> deviceList = mPbapClient.getConnectedDevices();
if (deviceList.isEmpty()) {
return BluetoothProfile.STATE_DISCONNECTED;
} else {
return mPbapClient.getConnectionState(deviceList.remove(0));
}
}
return BluetoothProfile.STATE_DISCONNECTED;
}
三、源码分析
代码路径:
android/packages/apps/Bluetooth/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java
android/packages/apps/Bluetooth/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
android/packages/apps/Bluetooth/src/com/android/bluetooth/pbapclient/PbapClientService.java
1、在PbapClientStateMachine.java文件中内部类Connecting类中,当我们连接蓝牙后就会执行Connecting类中enter()方法,并且也会注册该广播android.bluetooth.device.action.SDP_RECORD
class Connecting extends State {
private SDPBroadcastReceiver mSdpReceiver;
@Override
public void enter() {
if (DBG) {
Log.d(TAG, "Enter Connecting: " + getCurrentMessage().what);
}
onConnectionStateChanged(mCurrentDevice, mMostRecentState,
BluetoothProfile.STATE_CONNECTING);
mSdpReceiver = new SDPBroadcastReceiver();
mSdpReceiver.register();
mCurrentDevice.sdpSearch(BluetoothUuid.PBAP_PSE);
mMostRecentState = BluetoothProfile.STATE_CONNECTING;
// Create a separate handler instance and thread for performing
// connect/download/disconnect operations as they may be time consuming and error prone.
mHandlerThread =
new HandlerThread("PBAP PCE handler", Process.THREAD_PRIORITY_BACKGROUND);
mHandlerThread.start();
mConnectionHandler =
new PbapClientConnectionHandler.Builder().setLooper(mHandlerThread.getLooper())
.setContext(mService)
.setClientSM(PbapClientStateMachine.this)
.setRemoteDevice(mCurrentDevice)
.build();
sendMessageDelayed(MSG_CONNECT_TIMEOUT, CONNECT_TIMEOUT);
}
@Override
public boolean processMessage(Message message) {
if (DBG) {
Log.d(TAG, "Processing MSG " + message.what + " from " + this.getName());
}
switch (message.what) {
case MSG_DISCONNECT:
if (message.obj instanceof BluetoothDevice && message.obj.equals(
mCurrentDevice)) {
removeMessages(MSG_CONNECT_TIMEOUT);
transitionTo(mDisconnecting);
}
break;
case MSG_CONNECTION_COMPLETE:
removeMessages(MSG_CONNECT_TIMEOUT);
transitionTo(mConnected);
break;
case MSG_CONNECTION_FAILED:
case MSG_CONNECT_TIMEOUT:
removeMessages(MSG_CONNECT_TIMEOUT);
transitionTo(mDisconnecting);
break;
case MSG_SDP_COMPLETE:
mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_CONNECT,
message.obj).sendToTarget();
break;
default:
Log.w(TAG, "Received unexpected message while Connecting");
return NOT_HANDLED;
}
return HANDLED;
}
@Override
public void exit() {
mSdpReceiver.unregister();
mSdpReceiver = null;
}
private class SDPBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (DBG) {
Log.v(TAG, "onReceive" + action);
}
if (action.equals(BluetoothDevice.ACTION_SDP_RECORD)) {
BluetoothDevice device =
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (!device.equals(getDevice())) {
Log.w(TAG, "SDP Record fetched for different device - Ignore");
return;
}
ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID);
if (DBG) {
Log.v(TAG, "Received UUID: " + uuid.toString());
}
if (DBG) {
Log.v(TAG, "expected UUID: " + BluetoothUuid.PBAP_PSE.toString());
}
if (uuid.equals(BluetoothUuid.PBAP_PSE)) {
sendMessage(MSG_SDP_COMPLETE,
intent.getParcelableExtra(BluetoothDevice.EXTRA_SDP_RECORD));
}
}
}
public void register() {
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_SDP_RECORD);
mService.registerReceiver(this, filter);
}
public void unregister() {
mService.unregisterReceiver(this);
}
}
}
2、蓝牙连接后会先通过socket去连接UUID
private boolean connectSocket() {
try {
/* Use BluetoothSocket to connect */
if (mPseRec == null) {
// BackWardCompatability: Fall back to create RFCOMM through UUID.
Log.v(TAG, "connectSocket: UUID: " + BluetoothUuid.PBAP_PSE.getUuid());
mSocket =
mDevice.createRfcommSocketToServiceRecord(BluetoothUuid.PBAP_PSE.getUuid());
} else if (mPseRec.getL2capPsm() != L2CAP_INVALID_PSM) {
Log.v(TAG, "connectSocket: PSM: " + mPseRec.getL2capPsm());
mSocket = mDevice.createL2capSocket(mPseRec.getL2capPsm());
} else {
Log.v(TAG, "connectSocket: channel: " + mPseRec.getRfcommChannelNumber());
mSocket = mDevice.createRfcommSocket(mPseRec.getRfcommChannelNumber());
}
if (mSocket != null) {
mSocket.connect();
return true;
} else {
Log.w(TAG, "Could not create socket");
}
} catch (IOException e) {
Log.e(TAG, "Error while connecting socket", e);
}
return false;
}
3、蓝牙socket连接成功后就会去Obex Client Session
case MSG_CONNECT:
mPseRec = (SdpPseRecord) msg.obj;
/* To establish a connection, first open a socket and then create an OBEX session */
if (connectSocket()) {
if (DBG) {
Log.d(TAG, "Socket connected");
}
} else {
Log.w(TAG, "Socket CONNECT Failure ");
mPbapClientStateMachine.obtainMessage(
PbapClientStateMachine.MSG_CONNECTION_FAILED).sendToTarget();
return;
}
if (connectObexSession()) {
mPbapClientStateMachine.obtainMessage(
PbapClientStateMachine.MSG_CONNECTION_COMPLETE).sendToTarget();
} else {
mPbapClientStateMachine.obtainMessage(
PbapClientStateMachine.MSG_CONNECTION_FAILED).sendToTarget();
}
break;
4、Obex Client Session连接
private boolean connectObexSession() {
boolean connectionSuccessful = false;
try {
if (DBG) {
Log.v(TAG, "Start Obex Client Session");
}
BluetoothObexTransport transport = new BluetoothObexTransport(mSocket);
mObexSession = new ClientSession(transport);
mObexSession.setAuthenticator(mAuth);
HeaderSet connectionRequest = new HeaderSet();
connectionRequest.setHeader(HeaderSet.TARGET, PBAP_TARGET);
if (mPseRec != null) {
if (DBG) {
Log.d(TAG, "Remote PbapSupportedFeatures " + mPseRec.getSupportedFeatures());
}
ObexAppParameters oap = new ObexAppParameters();
if (mPseRec.getProfileVersion() >= PBAP_V1_2) {
oap.add(BluetoothPbapRequest.OAP_TAGID_PBAP_SUPPORTED_FEATURES,
PBAP_SUPPORTED_FEATURE);
}
oap.addToHeaderSet(connectionRequest);
}
HeaderSet connectionResponse = mObexSession.connect(connectionRequest);
connectionSuccessful =
(connectionResponse.getResponseCode() == ResponseCodes.OBEX_HTTP_OK);
if (DBG) {
Log.d(TAG, "Success = " + Boolean.toString(connectionSuccessful));
}
} catch (IOException e) {
Log.w(TAG, "CONNECT Failure " + e.toString());
closeSocket();
}
return connectionSuccessful;
}
5、Obex Client Session连接成功后,就可以去下载手机端的联系人和通话记录
case MSG_DOWNLOAD:
try {
mAccountCreated = addAccount(mAccount);
if (!mAccountCreated) {
Log.e(TAG, "Account creation failed.");
return;
}
// Start at contact 1 to exclued Owner Card PBAP 1.1 sec 3.1.5.2
BluetoothPbapRequestPullPhoneBook request =
new BluetoothPbapRequestPullPhoneBook(PB_PATH, mAccount,
PBAP_REQUESTED_FIELDS, VCARD_TYPE_30, 0, 1);
request.execute(mObexSession);
PhonebookPullRequest processor =
new PhonebookPullRequest(mPbapClientStateMachine.getContext(),
mAccount);
processor.setResults(request.getList());
processor.onPullComplete();
HashMap<String, Integer> callCounter = new HashMap<>();
downloadCallLog(MCH_PATH, callCounter);
downloadCallLog(ICH_PATH, callCounter);
downloadCallLog(OCH_PATH, callCounter);
} catch (IOException e) {
Log.w(TAG, "DOWNLOAD_CONTACTS Failure" + e.toString());
}
break;
总结:
1、蓝牙连接后会先通过socket去连接UUID
2、socket连接上后会连接OBEX协议
3、OBEX协议连接上之后就可以去下载联系人和通话记录
连接上蓝牙后下载通讯录的日志
08-10 22:00:57.986 2629 4617 D PbapClientStateMachine: Enter Connecting: -2
08-10 22:00:57.987 2629 4617 D PbapClientStateMachine: Connection state 54:92:09:12:BF:E9: 0->1
08-10 22:01:00.372 2629 2629 V PbapClientStateMachine: onReceiveandroid.bluetooth.device.action.SDP_RECORD
08-10 22:01:00.372 2629 2629 V PbapClientStateMachine: Received UUID: 0000112f-0000-1000-8000-00805f9b34fb
08-10 22:01:00.373 2629 2629 V PbapClientStateMachine: expected UUID: 0000112f-0000-1000-8000-00805f9b34fb
08-10 22:01:00.373 2629 4617 D PbapClientStateMachine: Processing MSG 9 from Connecting
08-10 22:01:00.373 2629 4618 D PbapClientConnectionHandler: Handling Message = 1
08-10 22:01:00.374 2629 4618 V PbapClientConnectionHandler: connectSocket: channel: 19
08-10 22:01:00.674 2629 4618 D PbapClientConnectionHandler: Socket connected
08-10 22:01:00.674 2629 4618 V PbapClientConnectionHandler: Start Obex Client Session
08-10 22:01:00.677 2629 4618 D PbapClientConnectionHandler: Remote PbapSupportedFeatures 3
08-10 22:01:00.742 2629 4618 D PbapClientConnectionHandler: Success = true
08-10 22:01:00.742 2629 4617 D PbapClientStateMachine: Processing MSG 5 from Connecting
08-10 22:01:00.744 2629 4617 D PbapClientStateMachine: Enter Connected: 5
08-10 22:01:00.745 2629 4617 D PbapClientStateMachine: Connection state 54:92:09:12:BF:E9: 1->2
08-10 22:01:00.751 2629 4618 D PbapClientConnectionHandler: Handling Message = 3
08-10 22:01:00.798 2629 4618 D PbapClientConnectionHandler: Added account Account {name=54:92:09:12:BF:E9, type=com.android.bluetooth.pbapsink}
断开蓝牙后日志
08-10 22:42:09.088 2629 3393 D PbapClientStateMachine: Disconnect Request 54:92:09:12:BF:E9
08-10 22:42:09.088 2629 4617 D PbapClientStateMachine: Processing MSG 2 from Connected
08-10 22:42:09.088 2629 4617 D PbapClientStateMachine: Enter Disconnecting: 2
08-10 22:42:09.088 2629 4617 D PbapClientStateMachine: Connection state 54:92:09:12:BF:E9: 2->3
08-10 22:42:09.104 2629 4618 D PbapClientConnectionHandler: Handling Message = 2
08-10 22:42:09.104 2629 4618 D PbapClientConnectionHandler: Starting Disconnect
08-10 22:42:09.104 2629 4618 D PbapClientConnectionHandler: obexSessionDisconnectjavax.obex.ClientSession@c0dce50
08-10 22:42:09.679 2629 4618 D PbapClientConnectionHandler: Closing Socket
08-10 22:42:09.679 2629 4618 D PbapClientConnectionHandler: Closing socketandroid.bluetooth.BluetoothSocket@89d6849
08-10 22:42:09.680 2629 4618 D PbapClientConnectionHandler: Completing Disconnect
08-10 22:42:09.768 2629 4618 D PbapClientConnectionHandler: Removed account Account {name=54:92:09:12:BF:E9, type=com.android.bluetooth.pbapsink}
08-10 22:42:10.096 2629 4617 D PbapClientStateMachine: Processing MSG 7 from Disconnecting
08-10 22:42:10.097 2629 4617 D PbapClientStateMachine: Enter Disconnected: 7
08-10 22:42:10.097 2629 4617 D PbapClientStateMachine: Connection state 54:92:09:12:BF:E9: 3->0
遇到问题点:
1、当我们使用BluetoothPbapClient来访问连接状态时候,mPbapClient.connect一直返回是false,返回false的原因是因为:getPriority(device) <= BluetoothProfile.PRIORITY_OFF,该行代码返回false,返回false就导致不能同步手机端的联系人数据。
数据存放的路径:/data/user/0/com.android.providers.contacts/databases
//客户端
public boolean connect(BluetoothDevice device) {
if (null != mPbapClient) {
return mPbapClient.connect(device);
}
Log.i(TAG, "mPbapClient == null");
return false;
}
//服务端
public boolean connect(BluetoothDevice device) {
if (device == null) {
throw new IllegalArgumentException("Null device");
}
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
Log.d(TAG, "Received request to ConnectPBAPPhonebook " + device.getAddress());
Log.d(TAG, "Received request to ConnectPBAPPhonebook getPriority(device) " + getPriority(device));
Log.d(TAG, "Received request to ConnectPBAPPhonebook BluetoothProfile.PRIORITY_OFF " + BluetoothProfile.PRIORITY_OFF);
if (getPriority(device) <= BluetoothProfile.PRIORITY_OFF) {
// return false;
}
synchronized (mPbapClientStateMachineMap) {
PbapClientStateMachine pbapClientStateMachine = mPbapClientStateMachineMap.get(device);
if (pbapClientStateMachine == null
&& mPbapClientStateMachineMap.size() < MAXIMUM_DEVICES) {
pbapClientStateMachine = new PbapClientStateMachine(this, device);
pbapClientStateMachine.start();
mPbapClientStateMachineMap.put(device, pbapClientStateMachine);
return true;
} else {
Log.w(TAG, "Received connect request while already connecting/connected.");
return false;
}
}
}
//把该行代码注释掉就可以解决connect返回false的问题,这样就可以正常同步手机端的联系人
if (getPriority(device) <= BluetoothProfile.PRIORITY_OFF) {
// return false;
}