Android 蓝牙开发——MAP协议(八)
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
SDK路径:frameworks/base/core/java/android/bluetooth/
服务路径:packages/apps/Bluetooth/src/com/android/bluetooth/
在使用协议类的时候无法找到该类由于安卓源码中关于蓝牙协议的 Client 部分或相关接口都被 @hide 给隐藏掉了这样 android.jar 满足不了安卓源码 framework 层开发人员的需求可以使用反射机制或者引用 framework.jar 代替 android.jar。
位置:out\target\common\obj\JAVA_LIBRARIES\framework_intermediates\classes.jar
一、SDK接口
BluetoothMapClient.java:蓝牙apk中的MAP协议的代理类应用通过此代理类访问MAP协议的方法。
接口名 | 描述 |
---|---|
connect | 连接指定设备 |
disconnect | 断开指定设备 |
isConnected | 判断指定设备是否连接连接则返回true否则false |
getConnectedDevices | 获取已连接设备列表 |
getDevicesMatchingConnectionStates | 获取指定状态的设备列表 |
getConnectionState | 获得指定设备的状态 |
setPriority | 设置协议的优先级 |
getPriority | 获取协议的优先级 |
sendMessage | 使用指定设备发送消息至指定的联系人 |
getUnreadMessages | 获得未读消息 |
isUploadingSupported | 从SDP记录的MapSupportedFeatures字段返回“上传”特征位值 |
setMessageStatus | 设置MSE上的消息状态 |
重点关注的接口:
/**
* 向指定的电话号码发送SMS消息
*
* @param device 蓝牙设备
* @param contacts 联系人的Uri[]列表
* @param message 要发送的消息
* @param sentIntent 发送消息时发出的意图 SMS消息发送成功将发送{@link #ACTION_MESSAGE_SENT_SUCCESSFULLY} 广播
* @param deliveredIntent 消息传递时发出的意图 SMS消息传递成功将发送{@link #ACTION_MESSAGE_DELIVERED_SUCCESSFULLY} 广播
* @return 如果消息入队则返回 true错误则返回 false
*/
public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
PendingIntent sentIntent, PendingIntent deliveredIntent)
/**
* 获取未读消息. 未读消息将发送 {@link #ACTION_MESSAGE_RECEIVED} 广播。
*
* @param device 蓝牙设备
* @return 如果消息入队则返回 true错误则返回 false
*/
public boolean getUnreadMessages(BluetoothDevice device)
二、APP调用SDK
1、获取 BluetoothMapClient
BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
btAdapter.getProfileProxy(mService, new MapServiceListener(), BluetoothProfile.MAP_CLIENT);
2、MapServiceListener
private BluetoothMapClient mapClient;
class MapServiceListener implements BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
synchronized (this) {
mapClient = (BluetoothMapClient) proxy;
}
}
@Override
public void onServiceDisconnected(int profile) {
synchronized (this) {
mapClient = null;
}
}
}
这里通过回调方法拿到 BluetoothMapClient 类。通过该类可以调用 SDK 中的各种方法。
三、源码解析
1、getProfileProxy()
public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener, int profile) {
if (context == null || listener == null) {
return false;
}
if (profile == BluetoothProfile.HEADSET) {
BluetoothHeadset headset = new BluetoothHeadset(context, listener, this);
return true;
} else if (profile == BluetoothProfile.A2DP) {
BluetoothA2dp a2dp = new BluetoothA2dp(context, listener, this);
return true;
} else if (profile == BluetoothProfile.A2DP_SINK) {
BluetoothA2dpSink a2dpSink = new BluetoothA2dpSink(context, listener, this);
return true;
} else if (profile == BluetoothProfile.AVRCP_CONTROLLER) {
BluetoothAvrcpController avrcp = new BluetoothAvrcpController(context, listener, this);
return true;
} else if (profile == BluetoothProfile.HID_HOST) {
BluetoothHidHost iDev = new BluetoothHidHost(context, listener, this);
return true;
} else if (profile == BluetoothProfile.PAN) {
BluetoothPan pan = new BluetoothPan(context, listener, this);
return true;
} else if (profile == BluetoothProfile.PBAP) {
BluetoothPbap pbap = new BluetoothPbap(context, listener, this);
return true;
} else if (profile == BluetoothProfile.HEALTH) {
Log.e(TAG, "getProfileProxy(): BluetoothHealth is deprecated");
return false;
} else if (profile == BluetoothProfile.MAP) {
BluetoothMap map = new BluetoothMap(context, listener, this);
return true;
} else if (profile == BluetoothProfile.HEADSET_CLIENT) {
BluetoothHeadsetClient headsetClient = new BluetoothHeadsetClient(context, listener, this);
return true;
} else if (profile == BluetoothProfile.SAP) {
BluetoothSap sap = new BluetoothSap(context, listener, this);
return true;
} else if (profile == BluetoothProfile.PBAP_CLIENT) {
BluetoothPbapClient pbapClient = new BluetoothPbapClient(context, listener, this);
return true;
} else if (profile == BluetoothProfile.MAP_CLIENT) {
BluetoothMapClient mapClient = new BluetoothMapClient(context, listener, this);
return true;
} else if (profile == BluetoothProfile.HID_DEVICE) {
BluetoothHidDevice hidDevice = new BluetoothHidDevice(context, listener, this);
return true;
} else if (profile == BluetoothProfile.HEARING_AID) {
if (isHearingAidProfileSupported()) {
BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener, this);
return true;
}
return false;
} else if (profile == BluetoothProfile.LE_AUDIO) {
BluetoothLeAudio leAudio = new BluetoothLeAudio(context, listener, this);
return true;
} else {
return false;
}
}
2、BluetoothMapClient
private final BluetoothProfileConnector<IBluetoothMapClient> mProfileConnector = new BluetoothProfileConnector(this, BluetoothProfile.MAP_CLIENT, "BluetoothMapClient", IBluetoothMapClient.class.getName()) {
@Override
public IBluetoothMapClient getServiceInterface(IBinder service) {
return IBluetoothMapClient.Stub.asInterface(Binder.allowBlocking(service));
}
};
/* package */ BluetoothMapClient(Context context, ServiceListener listener, BluetoothAdapter adapter) {
if (DBG) Log.d(TAG, "Create BluetoothMapClient proxy object");
mAdapter = adapter;
mAttributionSource = adapter.getAttributionSource();
mProfileConnector.connect(context, listener);
}
3、BluetoothProfileConnector.connect()
void connect(Context context, BluetoothProfile.ServiceListener listener) {
mContext = context;
mServiceListener = listener;
// 从BluetoothAdapter类中获取到蓝牙管理服务对象
IBluetoothManager mgr = BluetoothAdapter.getDefaultAdapter().getBluetoothManager();
// Preserve legacy compatibility where apps were depending on
// registerStateChangeCallback() performing a permissions check which
// has been relaxed in modern platform versions
if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.R && context.checkSelfPermission(android.Manifest.permission.BLUETOOTH) != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Need BLUETOOTH permission");
}
if (mgr != null) {
try {
// 注册Map状态回调接口
mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
} catch (RemoteException re) {
logError("Failed to register state change callback. " + re);
}
}
// 绑定蓝牙Map Service
doBind();
}
private boolean doBind() {
synchronized (mConnection) {
if (mService == null) {
logDebug("Binding service...");
mCloseGuard.open("doUnbind");
try {
// 绑定 MapService实现与 Bluetooth 进程通信
Intent intent = new Intent(mServiceName);
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
intent.setComponent(comp);
if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, UserHandle.CURRENT_OR_SELF)) {
logError("Could not bind to Bluetooth Service with " + intent);
return false;
}
} catch (SecurityException se) {
logError("Failed to bind service. " + se);
return false;
}
}
}
return true;
}
这里就绑定了 MapClientService 服务(packages/apps/Bluetooth/src/com/android/bluetooth/mapclient/。接下来分析 如何调用 BluetoothMapClient 提供的 API 方法以 connect() 为例。
4、BluetoothMapClient.connect()
@Override
public boolean connect(BluetoothDevice device, AttributionSource source) {
Attributable.setAttributionSource(device, source);
MapClientService service = getService(source);
if (service == null) {
return false;
}
return service.connect(device);
}
public boolean connect(BluetoothDevice device) {
if (DBG) Log.d(TAG, "connect(" + device + ")" + "for MAPS MCE");
final IBluetoothMapClient service = getService();
if (service != null) {
try {
return service.connect(device, mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, e.toString());
}
} else {
Log.w(TAG, "Proxy not attached to service");
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
}
return false;
}
private IBluetoothMapClient getService() {
return mProfileConnector.getService();
}
这里的 service 就是上面 BluetoothProfileConnector 中绑定的 MapClientService。
MapClientService.connect()
public synchronized boolean connect(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
if (device == null) {
throw new IllegalArgumentException("Null device");
}
if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
Log.w(TAG, "Connection not allowed: <" + device.getAddress() + "> is CONNECTION_POLICY_FORBIDDEN");
return false;
}
MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
if (mapStateMachine == null) {
// 一个映射状态机实例还不存在如果可以的话创建一个新的。
if (mMapInstanceMap.size() < MAXIMUM_CONNECTED_DEVICES) {
addDeviceToMapAndConnect(device);
return true;
} else {
// 允许的连接数已达到最大值
// 看看当前的一些连接是否可以清理腾出空间。
removeUncleanAccounts();
if (mMapInstanceMap.size() < MAXIMUM_CONNECTED_DEVICES) {
addDeviceToMapAndConnect(device);
return true;
} else {
Log.e(TAG, "Maxed out on the number of allowed MAP connections. " + "Connect request rejected on " + device);
return false;
}
}
}
// 状态机已经存在于map中
int state = getConnectionState(device);
if (state == BluetoothProfile.STATE_CONNECTED || state == BluetoothProfile.STATE_CONNECTING) {
Log.w(TAG, "Received connect request while already connecting/connected.");
return true;
}
// 状态机存在但不在连接状态从map上删除添加一个新的
mMapInstanceMap.remove(device);
addDeviceToMapAndConnect(device);
return true;
}
private synchronized void addDeviceToMapAndConnect(BluetoothDevice device) {
// 当创建一个新的状态机时它的状态被设置为连接——这将触发连接。
MceStateMachine mapStateMachine = new MceStateMachine(this, device);
mMapInstanceMap.put(device, mapStateMachine);
}
最后的连接工作在 MceStateMachine 中完成对于各种状态的变化也在 MceStateMachine 里通过各种广播通知出来。
四、获取未读消息
1、MapClientService.getUnreadMessages()
我们直接看 MapClientService 中的 getUnreadMessages() 方法。
public synchronized boolean getUnreadMessages(BluetoothDevice device) {
MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
if (mapStateMachine == null) {
return false;
}
return mapStateMachine.getUnreadMessages();
}
2、MceStateMachine.getUnreadMessages()
synchronized boolean getUnreadMessages() {
if (this.getCurrentState() == mConnected) {
sendMessage(MSG_GET_MESSAGE_LISTING, FOLDER_INBOX);
return true;
}
return false;
}
@Override
public boolean processMessage(Message message) {
switch (message.what) {
case MSG_GET_MESSAGE_LISTING:
// 获取最新50条未读邮件
MessagesFilter filter = new MessagesFilter();
filter.setMessageType(MapUtils.fetchMessageType());
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, -7);
filter.setPeriod(calendar.getTime(), null);
mMasClient.makeRequest(new RequestGetMessagesListing( (String) message.obj, 0, filter, 0, 50, 0));
break;
}
return HANDLED;
}
3、MasClient.makeRequest()
public boolean makeRequest(Request request) {
boolean status = mHandler.sendMessage(mHandler.obtainMessage(REQUEST, request));
if (!status) {
return false;
}
return true;
}
private static class MasClientHandler extends Handler {
@Override
public void handleMessage(Message msg) {
MasClient inst = mInst.get();
switch (msg.what) {
case REQUEST:
if (inst.mConnected) {
inst.executeRequest((Request) msg.obj);
}
break;
}
}
}
private void executeRequest(Request request) {
try {
request.execute(mSession);
mCallback.sendMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED, request);
} catch (IOException e) {
// 断开到清理
disconnect();
}
}