Android 藍(lán)牙短信功能開發(fā)

Android藍(lán)牙短信功能開發(fā)


由于我司是做車機中控的,目前需要在車機上實現(xiàn)與藍(lán)牙手機相連,并通過藍(lán)牙進(jìn)行短信發(fā)送和接收的功能,針對這一功能的實現(xiàn)方式做個簡單的記錄。

文檔目錄說明


以下開發(fā)基于Android 9.0 車機版本,其他版本或存在不同

一、藍(lán)牙短信協(xié)議規(guī)范


相關(guān)藍(lán)牙協(xié)議:MAP(MESSAGE ACCESS PROFILE):藍(lán)牙短信訪問協(xié)議規(guī)范。

借助MAP協(xié)議規(guī)范,可在車機上通過連接的遠(yuǎn)程設(shè)備收發(fā)短信。目前不會將短信內(nèi)容存儲在 IVI 本地存儲空間,而是每當(dāng)連接的遠(yuǎn)程設(shè)備收到短信時,IVI 會接收相應(yīng)短信并對其進(jìn)行解析,然后在 intent 中廣播消息內(nèi)容,應(yīng)用便可收到相應(yīng)內(nèi)容。

要連接到移動設(shè)備以收發(fā)短信,IVI 必須啟動 MAP 連接。 MapClientService 中的 MAXIMUM_CONNECTED_DEVICES 指定了 IVI 允許同時連接的 MAP 設(shè)備數(shù)量上限。同時獲得 IVI 和移動設(shè)備的授權(quán)時每個連接才能傳輸消息。

協(xié)議SDK代碼在源碼內(nèi)的路徑:frameworks/base/core/java/android/bluetooth/BluetoothMapClient.java

協(xié)議服務(wù)代碼在源碼內(nèi)的路徑:packages/apps/Bluetooth/src/com/android/bluetooth/mapclient/MapClientService.java

二、協(xié)議SDK文件接口說明

接口名 描述
connect 連接指定設(shè)備
disconnect 斷開指定設(shè)備
isConnected 判斷指定設(shè)備是否連接,連接則返回true,否則false
getConnectedDevices 獲有已連接設(shè)備列表
getDevicesMatchingConnectionStates 獲得與指定狀態(tài)匹配的設(shè)備
getConnectionState 獲得指定設(shè)備的連接狀態(tài)
setPriority 設(shè)置設(shè)備MAP協(xié)議的優(yōu)先級
getPriority 獲得設(shè)備MAP協(xié)議的優(yōu)先級
sendMessage 使用指定設(shè)備發(fā)送消息至指定的聯(lián)系人
getUnreadMessages 獲得未讀消息

其中我們需要重點關(guān)注的接口如下:

    /**
     * 向指定的電話號碼發(fā)送SMS消息
     * 
     * @param device 藍(lán)牙設(shè)備
     * @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 藍(lán)牙設(shè)備
     * @return 如果消息入隊則返回 true,錯誤則返回 false
     */
    public boolean getUnreadMessages(BluetoothDevice device)
    

三、藍(lán)牙MapClient協(xié)議支持

若需要車機設(shè)備與支持MAP的藍(lán)牙設(shè)備連接后,可進(jìn)行短信收發(fā),那么我們車機系統(tǒng)就需要在藍(lán)牙配對時支持MapClient協(xié)議規(guī)范,那么我們需要修改或overlay frameworks/base/core/res/res/values/config.xml內(nèi)的enable_pbap_pce_profile配置值為true(AutoMotive內(nèi)該值默認(rèn)overlay為true),這樣我們在進(jìn)行藍(lán)牙設(shè)備連接時才會將MapClientProfile協(xié)議加入到可連接的藍(lán)牙協(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);
                }
            }
            ....
                
    }
    ....        
}

之后進(jìn)行連接就可以在車機的藍(lán)牙設(shè)備詳情頁面看到對應(yīng)的協(xié)議開關(guān)選項,如一個Text Messages選項:

勾選之后就會進(jìn)行藍(lán)牙短信協(xié)議的連接。

MapClient連接設(shè)備狀態(tài)監(jiān)聽

當(dāng)MapClient設(shè)備連接狀態(tài)變化時,會發(fā)送BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED廣播,我們需要監(jiān)聽該廣播,并記錄當(dāng)前連接的藍(lán)牙設(shè)備,以備后續(xù)的未讀短信獲取短信發(fā)送功能的開發(fā),具體的代碼如下:

    // 記錄當(dāng)前連接的藍(lán)牙設(shè)備
    private BluetoothDevice mBluetoothDevice;

    // MapClient設(shè)備連接狀態(tài)變化廣播注冊
    IntentFilter filter = new IntentFilter();
    filter.addAction(BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED);
            BtServiceApplication.getApplication().registerReceiver(mBroadcastReceiver, filter);
            
    // MapClient設(shè)備連接狀態(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) {
                    // 設(shè)備連接
                    mBluetoothDevice = (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                } else if(intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0) == BluetoothProfile.STATE_DISCONNECTED){
                    // 設(shè)備斷開
                    BluetoothDevice bluetoothDevice = (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                    if(bluetoothDevice!=null && mBluetoothDevice.getAddress().equals(bluetoothDevice.getAddress())){
                    mBluetoothDevice = null;
                }
            }
            }
        }
    };
            

四、藍(lán)牙短信接收功能開發(fā)

根據(jù)官方協(xié)議來看,目前可支持的藍(lán)牙短信接收功能有如下兩個:

  1. 獲取所有未讀短信;
  2. 實時短信接收;
    針對已讀短信僅通過現(xiàn)有的協(xié)議還無法實現(xiàn),后續(xù)有機會再繼續(xù)研究。
    根據(jù)協(xié)議規(guī)范說明可知,藍(lán)牙短信收實際上是通過廣播的形式進(jìn)行接收的,所以我們只需要在代碼監(jiān)聽對應(yīng)的廣播信息,解析內(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);
                // 獲得短信接受的藍(lán)牙設(shè)備
                String bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            }
        }
    };

通過以上代碼即可實現(xiàn)實時短信的接收,若要獲得未讀短信,則需要調(diào)用BluetoothMapClientgetUnreadMessages接口觸發(fā)對應(yīng)的藍(lán)牙設(shè)備將未讀短信發(fā)送至我們的車機上,接收處理的上面一致,都是通過廣播來進(jìn)行接收,就不再闡述。

針對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 {
                // 獲得對應(yīng)Address的藍(lán)牙遠(yuǎn)程設(shè)備
                remoteDevice = mAdapter.getRemoteDevice(address);
            } catch (java.lang.IllegalArgumentException e) {
                return;
            }

            if (mMapProfile != null) {
                // 觸發(fā)未讀短信獲取
                boolean isSuccess = mMapProfile.getUnreadMessages(remoteDevice);
            }
        }
    }
}

五、藍(lán)牙短信發(fā)送功能開發(fā)

藍(lán)牙短信發(fā)送可參考代碼如下(BluetoothMapClient對象創(chuàng)建過程省略):

    // 藍(lán)牙短信發(fā)送成功廣播注冊
    IntentFilter filter = new IntentFilter();
    filter.addAction(BluetoothMapClient.ACTION_MESSAGE_SENT_SUCCESSFULLY);
    filter.addAction(BluetoothMapClient.ACTION_MESSAGE_DELIVERED_SUCCESSFULLY);
    BtServiceApplication.getApplication().registerReceiver(mBroadcastReceiver, filter);
        
    // 藍(lán)牙短信發(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");
            }
        }
    };
    

    /**
     * 藍(lán)牙短信發(fā)送
     * 
     * @param context   
     * @param address    藍(lán)牙設(shè)備對應(yīng)的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設(shè)置后,短信發(fā)送成就會有對應(yīng)的廣播發(fā)送回來,若只需要一個,另一個可直接設(shè)置為null,簡單看了下源碼,目前未看到兩個Intent的明顯差異,后續(xù)再細(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);
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容