Android藍牙短信功能開發(fā)
由于我司是做車機中控的愁茁,目前需要在車機上實現(xiàn)與藍牙手機相連,并通過藍牙進行短信發(fā)送和接收的功能,針對這一功能的實現(xiàn)方式做個簡單的記錄良姆。
文檔目錄說明
- 一、藍牙短信協(xié)議規(guī)范
- 二幔戏、協(xié)議SDK文件接口說明
- 三玛追、藍牙MapClient協(xié)議支持
- 四、藍牙短信接收功能開發(fā)
- 五闲延、藍牙短信發(fā)送功能開發(fā)
以下開發(fā)基于Android 9.0 車機版本痊剖,其他版本或存在不同
一、藍牙短信協(xié)議規(guī)范
相關藍牙協(xié)議:MAP(MESSAGE ACCESS PROFILE):藍牙短信訪問協(xié)議規(guī)范垒玲。
借助MAP協(xié)議規(guī)范陆馁,可在車機上通過連接的遠程設備收發(fā)短信。目前不會將短信內(nèi)容存儲在 IVI 本地存儲空間合愈,而是每當連接的遠程設備收到短信時叮贩,IVI 會接收相應短信并對其進行解析,然后在 intent 中廣播消息內(nèi)容佛析,應用便可收到相應內(nèi)容益老。
要連接到移動設備以收發(fā)短信,IVI 必須啟動 MAP 連接寸莫。 MapClientService 中的 MAXIMUM_CONNECTED_DEVICES 指定了 IVI 允許同時連接的 MAP 設備數(shù)量上限捺萌。同時獲得 IVI 和移動設備的授權時每個連接才能傳輸消息。
協(xié)議SDK代碼在源碼內(nèi)的路徑:frameworks/base/core/java/android/bluetooth/BluetoothMapClient.java
協(xié)議服務代碼在源碼內(nèi)的路徑:packages/apps/Bluetooth/src/com/android/bluetooth/mapclient/MapClientService.java
二膘茎、協(xié)議SDK文件接口說明
接口名 | 描述 |
---|---|
connect | 連接指定設備 |
disconnect | 斷開指定設備 |
isConnected | 判斷指定設備是否連接桃纯,連接則返回true酷誓,否則false |
getConnectedDevices | 獲有已連接設備列表 |
getDevicesMatchingConnectionStates | 獲得與指定狀態(tài)匹配的設備 |
getConnectionState | 獲得指定設備的連接狀態(tài) |
setPriority | 設置設備MAP協(xié)議的優(yōu)先級 |
getPriority | 獲得設備MAP協(xié)議的優(yōu)先級 |
sendMessage | 使用指定設備發(fā)送消息至指定的聯(lián)系人 |
getUnreadMessages | 獲得未讀消息 |
其中我們需要重點關注的接口如下:
/**
* 向指定的電話號碼發(fā)送SMS消息
*
* @param device 藍牙設備
* @param contacts 聯(lián)系人的Uri[]列表
* @param message y要發(fā)送的消息
* @param sentIntent 發(fā)送消息時發(fā)出的意圖 SMS消息發(fā)送成功將發(fā)送{@link #ACTION_MESSAGE_SENT_SUCCESSFULLY} 廣播
* @param deliveredIntent 消息傳遞時發(fā)出的意圖 SMS消息傳遞成功將發(fā)送{@link #ACTION_MESSAGE_DELIVERED_SUCCESSFULLY} 廣播
* @return 如果消息入隊則返回 true,錯誤則返回 false
*/
public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
PendingIntent sentIntent, PendingIntent deliveredIntent)
/**
* 獲取未讀消息. 未讀消息將發(fā)送 {@link #ACTION_MESSAGE_RECEIVED} 廣播慈参。
*
* @param device 藍牙設備
* @return 如果消息入隊則返回 true呛牲,錯誤則返回 false
*/
public boolean getUnreadMessages(BluetoothDevice device)
三、藍牙MapClient協(xié)議支持
若需要車機設備與支持MAP的藍牙設備連接后驮配,可進行短信收發(fā)娘扩,那么我們車機系統(tǒng)就需要在藍牙配對時支持MapClient協(xié)議規(guī)范,那么我們需要修改或overlay frameworks/base/core/res/res/values/config.xml內(nèi)的enable_pbap_pce_profile配置值為true(AutoMotive內(nèi)該值默認overlay為true)壮锻,這樣我們在進行藍牙設備連接時才會將MapClientProfile協(xié)議加入到可連接的藍牙協(xié)議規(guī)范列表內(nèi)琐旁,具體的代碼如下:
framework/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
public class LocalBluetoothProfileManager {
....
private MapClientProfile mMapClientProfile;
....
LocalBluetoothProfileManager(Context context,
LocalBluetoothAdapter adapter,
CachedBluetoothDeviceManager deviceManager,
BluetoothEventManager eventManager) {
....
// pbap為電話簿訪問協(xié)議規(guī)范
mUsePbapPce = mContext.getResources().getBoolean(R.bool.enable_pbap_pce_profile);
// MAP Client 通常用于與 PBAP Client 相同的情況
mUseMapClient = mContext.getResources().getBoolean(R.bool.enable_pbap_pce_profile);
....
if (mUseMapClient) {
if(mMapClientProfile==null){
mMapClientProfile = new MapClientProfile(mContext, mLocalAdapter,
mDeviceManager, this);
addProfile(mMapClientProfile, MapClientProfile.NAME,
BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED);
}
} else {
if(mMapProfile==null){
mMapProfile = new MapProfile(mContext, mLocalAdapter, mDeviceManager, this);
addProfile(mMapProfile, MapProfile.NAME,
BluetoothMap.ACTION_CONNECTION_STATE_CHANGED);
}
}
....
}
....
}
之后進行連接就可以在車機的藍牙設備詳情頁面看到對應的協(xié)議開關選項,如一個Text Messages選項:
勾選之后就會進行藍牙短信協(xié)議的連接猜绣。
MapClient連接設備狀態(tài)監(jiān)聽
當MapClient設備連接狀態(tài)變化時灰殴,會發(fā)送BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED廣播,我們需要監(jiān)聽該廣播掰邢,并記錄當前連接的藍牙設備牺陶,以備后續(xù)的未讀短信獲取和短信發(fā)送功能的開發(fā),具體的代碼如下:
// 記錄當前連接的藍牙設備
private BluetoothDevice mBluetoothDevice;
// MapClient設備連接狀態(tài)變化廣播注冊
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED);
BtServiceApplication.getApplication().registerReceiver(mBroadcastReceiver, filter);
// MapClient設備連接狀態(tài)變化廣播監(jiān)聽
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED)) {
if (intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0) == BluetoothProfile.STATE_CONNECTED) {
// 設備連接
mBluetoothDevice = (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
} else if(intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0) == BluetoothProfile.STATE_DISCONNECTED){
// 設備斷開
BluetoothDevice bluetoothDevice = (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if(bluetoothDevice!=null && mBluetoothDevice.getAddress().equals(bluetoothDevice.getAddress())){
mBluetoothDevice = null;
}
}
}
}
};
四辣之、藍牙短信接收功能開發(fā)
根據(jù)官方協(xié)議來看掰伸,目前可支持的藍牙短信接收功能有如下兩個:
- 獲取所有未讀短信;
- 實時短信接收怀估;
針對已讀短信僅通過現(xiàn)有的協(xié)議還無法實現(xiàn)狮鸭,后續(xù)有機會再繼續(xù)研究。
根據(jù)協(xié)議規(guī)范說明可知多搀,藍牙短信收實際上是通過廣播的形式進行接收的歧蕉,所以我們只需要在代碼監(jiān)聽對應的廣播信息,解析內(nèi)部的短信內(nèi)容即可康铭,實現(xiàn)代碼如下:
// 短信接收廣播注冊
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothMapClient.ACTION_MESSAGE_RECEIVED);
BtServiceApplication.getApplication().registerReceiver(mBroadcastReceiver, filter);
// 短信接收廣播監(jiān)聽
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(BluetoothMapClient.ACTION_MESSAGE_RECEIVED)) {
// 收到短信
// 獲得發(fā)送者的Uri
String senderUri = intent.getStringExtra(BluetoothMapClient.EXTRA_SENDER_CONTACT_URI);
if (senderUri == null) {
senderUri = "<null>";
}
// 獲得發(fā)送者名稱
String senderName = intent.getStringExtra(BluetoothMapClient.EXTRA_SENDER_CONTACT_NAME);
if (senderName == null) {
senderName = "<null>";
}
// 獲得短息內(nèi)容
String message = intent.getStringExtra(android.content.Intent.EXTRA_TEXT);
// 獲得短信接受的藍牙設備
String bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
}
}
};
通過以上代碼即可實現(xiàn)實時短信的接收惯退,若要獲得未讀短信,則需要調(diào)用BluetoothMapClient的getUnreadMessages接口觸發(fā)對應的藍牙設備將未讀短信發(fā)送至我們的車機上从藤,接收處理的上面一致催跪,都是通過廣播來進行接收,就不再闡述呛哟。
針對BluetoothMapClient的對象創(chuàng)建和getUnreadMessages的調(diào)用可參考如下代碼:
public class Test {
private BluetoothMapClient mMapProfile;
private BluetoothAdapter mAdapter;
private final int MAP_CLIENT = 18;
class MapServiceListener implements BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
synchronized (mLock) {
mMapProfile = (BluetoothMapClient) proxy;
}
}
@Override
public void onServiceDisconnected(int profile) {
synchronized (mLock) {
mMapProfile = null;
}
}
}
public Test(Contexst context){
mAdapter = BluetoothAdapter.getDefaultAdapter();
mAdapter.getProfileProxy(mContext, new MapServiceListener(), MAP_CLIENT);
}
// 獲得未讀短信
public void syncUnreadMessages(String address) {
synchronized (mLock) {
// 觸發(fā)同步
BluetoothDevice remoteDevice;
try {
// 獲得對應Address的藍牙遠程設備
remoteDevice = mAdapter.getRemoteDevice(address);
} catch (java.lang.IllegalArgumentException e) {
return;
}
if (mMapProfile != null) {
// 觸發(fā)未讀短信獲取
boolean isSuccess = mMapProfile.getUnreadMessages(remoteDevice);
}
}
}
}
五叠荠、藍牙短信發(fā)送功能開發(fā)
藍牙短信發(fā)送可參考代碼如下(BluetoothMapClient對象創(chuàng)建過程省略):
// 藍牙短信發(fā)送成功廣播注冊
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothMapClient.ACTION_MESSAGE_SENT_SUCCESSFULLY);
filter.addAction(BluetoothMapClient.ACTION_MESSAGE_DELIVERED_SUCCESSFULLY);
BtServiceApplication.getApplication().registerReceiver(mBroadcastReceiver, filter);
// 藍牙短信發(fā)送成功廣播監(jiān)聽
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(BluetoothMapClient.ACTION_MESSAGE_SENT_SUCCESSFULLY)) {
Logcat.d("Message sent successfully");
} else if (action.equals(BluetoothMapClient.ACTION_MESSAGE_DELIVERED_SUCCESSFULLY)) {
Logcat.d("Message delivered successfully");
}
}
};
/**
* 藍牙短信發(fā)送
*
* @param context
* @param address 藍牙設備對應的Address
* @param recipients 收件人號碼
* @param message 短信內(nèi)容
*/
private void sendMessage(Context context, String address, Uri[] recipients, String message) {
BluetoothDevice remoteDevice;
try {
remoteDevice = mAdapter.getRemoteDevice(address);
} catch (java.lang.IllegalArgumentException e) {
Logcat.d(e.toString());
return;
}
if (mMapProfile != null) {
Logcat.d("Sending reply");
if (recipients == null) {
Logcat.d("Recipients is null");
return;
}
if (address == null) {
Logcat.d("BluetoothDevice is null");
return;
}
// 以下兩個Intent設置后匿沛,短信發(fā)送成就會有對應的廣播發(fā)送回來扫责,若只需要一個,另一個可直接設置為null逃呼,簡單看了下源碼鳖孤,目前未看到兩個Intent的明顯差異者娱,后續(xù)再細究
mSentIntent = PendingIntent.getBroadcast(context, 0, mSendIntent, PendingIntent.FLAG_ONE_SHOT);
mDeliveredIntent = PendingIntent.getBroadcast(context, 0, mDeliveryIntent, PendingIntent.FLAG_ONE_SHOT);
mMapProfile.sendMessage(remoteDevice, recipients, message, mSentIntent, mDeliveredIntent);
}
}