Permission check failed
現(xiàn)象描述
Android 10 手機(jī)連接GATT
后欣舵,讀寫characteristic
結(jié)果返回true
,但是確沒有callback
返回糙俗,logcat
打印如下:
02-20 04:26:15.599 3815 3834 W BtGatt.GattService: writeCharacteristic() - permission check failed!
源碼分析
BluetoothGatt#writeCharacteristic
android.bluetooth.BluetoothGatt.java
/**
* Writes a given characteristic and its values to the associated remote device.
*
* <p>Once the write operation has been completed, the
* {@link BluetoothGattCallback#onCharacteristicWrite} callback is invoked,
* reporting the result of the operation.
*
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
*
* @param characteristic Characteristic to write on the remote device
* @return true, if the write operation was initiated successfully
*/
public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == 0
&& (characteristic.getProperties()
& BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == 0) {
return false;
}
if (VDBG) Log.d(TAG, "writeCharacteristic() - uuid: " + characteristic.getUuid());
if (mService == null || mClientIf == 0 || characteristic.getValue() == null) return false;
BluetoothGattService service = characteristic.getService();
if (service == null) return false;
BluetoothDevice device = service.getDevice();
if (device == null) return false;
synchronized (mDeviceBusyLock) {
if (mDeviceBusy) return false;
mDeviceBusy = true;
}
try {
mService.writeCharacteristic(mClientIf, device.getAddress(),
characteristic.getInstanceId(), characteristic.getWriteType(),
AUTHENTICATION_NONE, characteristic.getValue());
} catch (RemoteException e) {
Log.e(TAG, "", e);
mDeviceBusy = false;
return false;
}
return true;
}
GattService#writeCharacteristic
/android/platform/packages/apps/Bluetooth/src/com/android/bluetooth/gatt/GattService.java
void writeCharacteristic(int clientIf, String address, int handle, int writeType, int authReq,
byte[] value) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (VDBG) {
Log.d(TAG, "writeCharacteristic() - address=" + address);
}
if (mReliableQueue.contains(address)) {
writeType = 3; // Prepared write
}
Integer connId = mClientMap.connIdByAddress(clientIf, address);
if (connId == null) {
Log.e(TAG, "writeCharacteristic() - No connection for " + address + "...");
return;
}
if (!permissionCheck(connId, handle)) {
Log.w(TAG, "writeCharacteristic() - permission check failed!");
return;
}
gattClientWriteCharacteristicNative(connId, handle, writeType, authReq, value);
}
從上面的代碼可以看到鱼蝉,writeCharacteristic() - permission check failed!
是在permissionCheck(connId, handle)
返回失敗的情況下才打印的措拇。
permissionCheck
/android/platform/packages/apps/Bluetooth/src/com/android/bluetooth/gatt/GattService.java
private boolean permissionCheck(int connId, int handle) {
Set<Integer> restrictedHandles = mRestrictedHandles.get(connId);
if (restrictedHandles == null || !restrictedHandles.contains(handle)) {
return true;
}
return (checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)
== PERMISSION_GRANTED);
}
從上面的代碼可以看出檢查流程如下:
首先檢查
restrictedHandles
是否包含當(dāng)前操作的 handle拷获,由于handle對(duì)應(yīng)的UUID在restrictedHandles
中淆九,所以開始檢查BLUETOOTH_PRIVILEGED
權(quán)限但是只有系統(tǒng)應(yīng)用才可以申請(qǐng)獲取
BLUETOOTH_PRIVILEGED
權(quán)限纹安,所以最后依然返回false
.
第三方應(yīng)用是無(wú)法獲取BLUETOOTH_PRIVILEGED
權(quán)限的,所以問(wèn)題應(yīng)該是出在restrictedHandles
上考余。
restrictedHandles
/**
* Set of restricted (which require a BLUETOOTH_PRIVILEGED permission) handles per connectionId.
*/
private final Map<Integer, Set<Integer>> mRestrictedHandles = new HashMap<>();
void onGetGattDb(int connId, ArrayList<GattDbElement> db) throws RemoteException {
String address = mClientMap.addressByConnId(connId);
if (DBG) {
Log.d(TAG, "onGetGattDb() - address=" + address);
}
ClientMap.App app = mClientMap.getByConnId(connId);
if (app == null || app.callback == null) {
Log.e(TAG, "app or callback is null");
return;
}
List<BluetoothGattService> dbOut = new ArrayList<BluetoothGattService>();
Set<Integer> restrictedIds = new HashSet<>();
BluetoothGattService currSrvc = null;
BluetoothGattCharacteristic currChar = null;
boolean isRestrictedSrvc = false;
boolean isHidSrvc = false;
boolean isRestrictedChar = false;
for (GattDbElement el : db) {
switch (el.type) {
case GattDbElement.TYPE_PRIMARY_SERVICE:
case GattDbElement.TYPE_SECONDARY_SERVICE:
if (DBG) {
Log.d(TAG, "got service with UUID=" + el.uuid + " id: " + el.id);
}
currSrvc = new BluetoothGattService(el.uuid, el.id, el.type);
dbOut.add(currSrvc);
isRestrictedSrvc =
isFidoSrvcUuid(el.uuid) || isAndroidTvRemoteSrvcUuid(el.uuid);
isHidSrvc = isHidSrvcUuid(el.uuid);
if (isRestrictedSrvc) {
restrictedIds.add(el.id);
}
break;
case GattDbElement.TYPE_CHARACTERISTIC:
if (DBG) {
Log.d(TAG, "got characteristic with UUID=" + el.uuid + " id: " + el.id);
}
currChar = new BluetoothGattCharacteristic(el.uuid, el.id, el.properties, 0);
currSrvc.addCharacteristic(currChar);
isRestrictedChar = isRestrictedSrvc || (isHidSrvc && isHidCharUuid(el.uuid));
if (isRestrictedChar) {
restrictedIds.add(el.id);
}
break;
case GattDbElement.TYPE_DESCRIPTOR:
if (DBG) {
Log.d(TAG, "got descriptor with UUID=" + el.uuid + " id: " + el.id);
}
currChar.addDescriptor(new BluetoothGattDescriptor(el.uuid, el.id, 0));
if (isRestrictedChar) {
restrictedIds.add(el.id);
}
break;
case GattDbElement.TYPE_INCLUDED_SERVICE:
if (DBG) {
Log.d(TAG, "got included service with UUID=" + el.uuid + " id: " + el.id
+ " startHandle: " + el.startHandle);
}
currSrvc.addIncludedService(
new BluetoothGattService(el.uuid, el.startHandle, el.type));
break;
default:
Log.e(TAG, "got unknown element with type=" + el.type + " and UUID=" + el.uuid
+ " id: " + el.id);
}
}
if (!restrictedIds.isEmpty()) {
mRestrictedHandles.put(connId, restrictedIds);
}
// Search is complete when there was error, or nothing more to process
app.callback.onSearchComplete(address, dbOut, 0 /* status */);
}
從上面的代碼看到restrictedHandles
里面包含了需要被過(guò)濾的UUID先嬉,從前面的permissionCheck已經(jīng)知道,這些被限制的UUID只有系統(tǒng)應(yīng)用才可以訪問(wèn)楚堤。
我們測(cè)試設(shè)備的服務(wù)里面確實(shí)沒有需要被限制的UUID為什么也會(huì)被過(guò)濾疫蔓?
繼續(xù)跟蹤定位發(fā)現(xiàn),restrictedHandles
只有put
操作身冬,沒有remove
或者clear
操作衅胀。于是懷疑是restrictedHandles
緩存導(dǎo)致的,模擬場(chǎng)景如下:
首先先連接一個(gè)HID設(shè)備酥筝,查詢到的服務(wù)里面包含被限制的Service(這里以HID為例)滚躯,連接成功后,connId=0x09.
斷開HID設(shè)備嘿歌,connId=0x09被釋放掸掏。
連接一個(gè)新的LE設(shè)備,服務(wù)里面沒有需要被限制的Service.連接成功后宙帝,connId也是0x09丧凤。
-
從前面的
onGetGattDb
可以看出,雖然新的設(shè)備沒有需要被限制的Service步脓,但是由于restrictedHandles
沒有被清空愿待,兩次的connId也是一樣的,導(dǎo)致后面判斷的時(shí)候依然會(huì)被過(guò)濾靴患。if (!restrictedIds.isEmpty()) { mRestrictedHandles.put(connId, restrictedIds); }
為什么其他Android設(shè)備正常仍侥,只有 Android 10有問(wèn)題?
前面我們分析的代碼就是Android 10 的。mRestrictedHandles
也是在Android 10才加上的鸳君。
Android 10 以前版本的 permissionCheck
是直接檢查UUID访圃,不會(huì)受前一次連接的緩存影響。
boolean permissionCheck(int connId, int handle) {
List<BluetoothGattService> db = mGattClientDatabases.get(connId);
if (db == null) {
return true;
}
for (BluetoothGattService service : db) {
for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
if (handle == characteristic.getInstanceId()) {
return !((isRestrictedCharUuid(characteristic.getUuid())
|| isRestrictedSrvcUuid(service.getUuid()))
&& (0 != checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)));
}
for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors()) {
if (handle == descriptor.getInstanceId()) {
return !((isRestrictedCharUuid(characteristic.getUuid())
|| isRestrictedSrvcUuid(service.getUuid())) && (0
!= checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED)));
}
}
}
}
return true;
}
結(jié)論
從上面的分析可以知道相嵌,這個(gè)問(wèn)題是Android 10 引入的 BUG腿时,Android 10 以前的系統(tǒng)沒有這個(gè)問(wèn)題况脆。第三方APP連接LE設(shè)備后,如果前一次連接了一個(gè)HID設(shè)備批糟,且當(dāng)前連接的connId和上一次連接的connId相同格了,就會(huì)觸發(fā)這個(gè)BUG。
解決方案
重新開關(guān)藍(lán)牙徽鼎,這樣BluetoothManagerService就會(huì)重新bind盛末,緩存會(huì)被清除。
確保App 或者第三方應(yīng)用不會(huì)去連接HID設(shè)備否淤,減小觸發(fā)BUG的機(jī)率悄但。
上面兩個(gè)方法都不能從根本上解決問(wèn)題,最終我們還是要等到Google
更新patch來(lái)修復(fù)這個(gè)BUG石抡。