淺談「Companion Device Pairing」
目標
學習如何使用「Companion Device Pairing」。
概要
Companion Device Pairing,是一組關於「藍牙」、「BLE」、「WLAN」…等設備的「新配對方式」,它僅能在執行於「Android 8.0,API 26」以後的 Android 設備,詳細介紹可以參考「Android Dev Summit ‘19」。
在官方文件的說明中,它不僅是擁有更佳的隱私權與安全性,它亦解決困擾多數開發者許久的「位置權限問題」以及在「Android 8.0」以後,背景執行受到限制的問題。
正文
權限問題
在過去,若我們要「發現藍牙設備」,我們需要獲得除了「BLUETOOTH」以外的兩個權限,分別是「BLUETOOTH_ADMIN」,如下:
以及「LOCATION」,如下:
備註:「ACCESS_COARSE_LOCATION」與「ACCESS_FINE_LOCATION」皆屬於「LOCATION」權限組;而藍牙功能所需求的「LOCATION」權限亦會隨著版本不同而有所差異。
「BLUETOOTH_ADMIN」的存取,不僅賦予應用程式掃描的功能,同時它亦提供了應用程式更多的權限,包含讀、寫…等。
而「LOCATION」權限的存取就更令人匪夷所思了,筆者認為若是「WiFi」相關能夠理解,但為什麼僅是「發現藍牙設備」,卻需要擁有位置的權限?
事實上,該議題曾被議論過,例如「Why Location permission are required for Ble scan in android Marshmallow onwards」、「Location needs to be enabled for Bluetooth Low Energy Scanning on Android 6.0」…等;甚至有不少開發人員認為這並不合理,覺得這是「Google」的錯誤。
而「Google」的回覆始終都堅持他們並沒有錯,「LOCATION」權限的存取是必要的,呵呵。
不論你的立場如何?至少「Google」推出「Companion Device Pairing」來替眾開發者解套。
在「Companion Device Pairing」的說明文件上,它明確的指出這件事情,如下圖:
在藍牙概覽中,也有這樣的描述:
背景執行受到限制的問題
曾經,「Android」四大元件之一的「Service」是至高天,曾經的它,強大無比。
但是由於「安全性」與「隱私權」的抬頭,「Service」逐漸跌落神壇,一路從「Android 6.0,API 23」開始,到「Android 8.0, API 26」的致命一擊,如下圖:
詳細可以參考官方文件:「Background Execution Limits」和「Background Location Limits」。
然而,已經殘廢的「Service」並有沒有就此雨過天晴,「Android 10,API 29」,又是一刀。
連曾經紅極一時的「Service」子類「IntentService」在即將到來的「API 30」也要被廢棄了,如下:
事實上,關於「Service」的改動並不僅於上述,但這並非本文的主要,筆者就不再過多描述。
然而,由於「後台背景程序的限制」改動,導致現在「無法從『Receiver』喚起『Service』」,但這對許多藍牙裝置並不友善,例如藍牙音響、藍牙耳機、藍牙鍵盤與滑鼠,甚至是一些藍牙類型的穿戴式裝置。
同樣地,「Companion Device Pairing」也為其解套。
開發者們可藉由宣告「REQUEST_COMPANION_RUN_IN_BACKGROUND」和「REQUEST_COMPANION_USE_DATA_IN_BACKGROUND」的權限,來執行部份關於背景相關的操作。
容易地與設備關聯與與解除關聯
在過去,若我們要與藍牙設備關聯或解除關聯,常可能因為思慮不周導致非預期的情況。
而在「Companion Device Pairing」的官方文件中就提到,開發者們可以簡單的藉由呼叫「CompanionDeviceManager」類別中的「associate()」方法以及「disassociate()」方法來與「藍牙設備」關聯與解除關聯。
實作「Companion Device Pairing」
宣告「Permission」
雖然「CDP」不需要「BLUETOOTH_ADMIN」和「LOCATION」權限,但它能需要其它基本權限。
例如實作「WLAN」就需要網路及其相關的權限,而本文範為藍牙設備,因此,我們就需要在「Manifest」清單中聲明「BLUETOOTH」權限,如下:
宣告「feature」
要實作「Companion Device Pairing」的功能,首先要在「Manifest」清單中聲明「android.software.companion_device_setup」特性,如下:
完成了上述的聲明後,我們就可以在開始撰寫程式碼了。
首先,要實作「CDP」,就必須先取得「CompanionDeviceManager」,取得方式如下:
「CDP」中,用來關聯「設備」的方法是「associate()」,如下:
「CompanionDeviceManager#associate()」總共需要傳入三個參數,其分別是「AssociationRequest」、「CompanionDeviceManager.Callback」以及「Handler」,如下:
首先是「AssociationRequest」,該參數是設置藍牙設備的配對詳細資訊,若要取得該類別,需要透過其「Builder」,如下:
而在「Builder」中,有兩個重要的設定方法,分別為「addDeviceFilter()」以及「setSingleDevice()」。
而「addDeviceFilter()」中,需要傳入一個參數「DeviceFilter」,該參數適用於特定配對,換句話說,我們利用該參數來過濾出我們所需要的設備。
若要取得「DeviceFilter」,我們必須藉由「BluetoothDeviceFilter.Builder」來建立,其可設定過濾的條件,包含設備名稱、設備位址或 UUID,如下:
備註:建立「DeviceFilter」時,開發者可以依照自己的需求設定過濾條件,並不需要將所有條件都進行設定。
該參數可以為「null」,其代表著「不進行任何過濾」,如下:
設定「DeviceFilter」後,「BluetoothDeviceFilter.Builder」還有另外一個方法需要設定,即「setSingleDevice()」。
「setSingleDevice()」需要傳入的參數為布林值,該設定關係到「CDP」彈出時,設備清單上的設備數量,「true」為僅顯示一台設備,程式碼如下:
畫面顯示效果如下:
而「false」則是多台設備,畫面顯示效果如下:
接著是「CompanionDeviceManager#associate()」的第二個參數,與上述的第一個參數「AssociationRequest」一樣,其值不能為「null」。
其第二個參數為「Callback」,「CompanionDeviceManager.Callback」,由此可知,該方法是一個異步調用。
在該「Callback」中,有兩個方法必須去實作,分別為「onDeviceFound()」以及「onFailure()」,前者是當設備被查找到時的處理,後者是查找失敗的處理,實作如下:
「onDeviceFound()」傳入的是「IntentSender」,因此,我們通常會再藉由「StartIntentSenderForResult」方法將它傳至「onActivityResult」,如下:
事實上,這裡的處理方式跟傳統藍牙設備的「BroadcastReceiver」很類似,透過「CompanionDeviceManager.EXTRA_DEVICE」來取得我們選擇的設備。
至於該設備會是怎樣的型態,則是取決於我們所選擇的設備項目,如下:
備註:「createBond()」需要「BLUETOOTH_ADMIN」權限。
最後是「CompanionDeviceManager#associate()」的第三個參數。
事實上,該參數只是個「Handler」,如敘述所說,我們不應該在「Main Thread」上執行任何非「UI」相關的任務,尤其是耗時任務。
由於只是範例程式碼,因此筆者沒有實作這部份,而是放入「null」,但在專案上,請盡量避免這樣的程式撰寫。
總結
相較於藍牙舊有的撰寫方式,「Companion Device Pairing」是屬於比較新的機制,它不僅提供了一項設備配對的新選擇。
更重要的是,在「Companion Device Pairing」的出現後,以往困擾開發者許久的「權限問題」及「後台作業限制」終於有了解套的作法。
無法否認的是它仍有著不少限制,但在「注重隱私權及安全性」的現今,這幾乎是必然的結果。
其它
或許該機制比較新,可能並非所有設備都可以使用。
事實上,本來筆者是要將此應用於「Android TV APPs」的開發上,但是卻發現筆者所使用的設備並不支援該特性。
其打印的結果為「False」。
順帶一提,筆者所使用的設備為「ADT-3」。
起初,筆者懷疑是因為「Android TV」的原因;廢話不多說,規格的事情,就找「Android Compatibility Definition Document」。
「ADT-3」是「Android 10」的設備,參考文件「連結」,如下圖:
根據文件項目「3.16. Companion Device Pairing」:
其「Device Type Id」為「C」,「C」是「core」,如下:
因此,根據其規格文件的描述,SDK 版本為10 的所有「Android」設備都應該要具備此功能。
此外,筆者在搜尋時,也搜尋到其「CTS」相關的「源碼」,如下:
目前它似乎是被歸類在「CTS Verifier」。
至於,為什麼「ADT-3」不具備該功能?這筆者無法解釋,就目前所知,筆者認為其應該具備「CDP」功能。
完整程式碼連結:My GitHub