Android 12(API 31) 的藍牙權(quán)限做了個更改.
2021-7-20?·?Android 12 引入了 BLUETOOTH_SCAN 、 BLUETOOTH_ADVERTISE 和 BLUETOOTH_CONNECT 權(quán)限元暴,
可讓應用 掃描附近的設(shè)備(NearBy)舞骆,而無需請求位置權(quán)限(ACCESS_FINE_LOCATION).
參考: https://developer.android.com/guide/topics/connectivity/bluetooth/permissions
(1)如果您的應用程序?qū)ふ宜{牙設(shè)備固灵,例如 BLE 外圍設(shè)備鲫趁,請聲明 BLUETOOTH_SCAN 權(quán)限踱葛。
(2)如果您的應用程序使當前設(shè)備可被其他藍牙設(shè)備發(fā)現(xiàn)土陪,請聲明該 BLUETOOTH_ADVERTISE 權(quán)限煤蹭。
(3)如果您的應用程序與已配對的藍牙設(shè)備通信笔喉,請聲明 BLUETOOTH_CONNECT 權(quán)限取视。
(4)對于遺留的藍牙相關(guān)權(quán)限聲明,設(shè)置 android:maxSdkVersion為30. 此應用兼容性步驟有助于系統(tǒng)僅向您的應用授予安裝在運行 Android 12 或更高版本的設(shè)備上時所需的藍牙權(quán)限常挚。
具體使用方法參考指南作谭。
由于這三種藍牙權(quán)限都是 運行時權(quán)限,必須 先在應用中 明確請求 用戶同意奄毡,然后才能查找藍牙設(shè)備.
因此折欠,就產(chǎn)生了 藍牙連接 權(quán)限檢查問題,我們從這個角度(BLUETOOTH_CONNECT) 入手吼过, 分析 權(quán)限檢查機制.
注:(1)在 targetSKD <=30 的應用锐秦,是無法聲明 上面三個權(quán)限的,僅有以下幾個( Nearby 附近的設(shè)備 權(quán)限默認開啟???)
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
<uses-permission android:name="android.permission.BLUETOOTH_DEBUG"/>
(2)Nearby 附近的設(shè)備 權(quán)限是為了兼容 舊版本Android 系統(tǒng)(舊SDK)盗忱,所以在SDK<=30默認開啟 ???
因此酱床,targetSKD <=30 的應用, 在API31(Android12) 的設(shè)備上必須要有這個權(quán)限才能使用藍牙 趟佃?扇谣?
否則會拋出 安全異常 錯誤?? 這是因為 AppOpsManager 在系統(tǒng)層面**還會限制??
(3) targetSDK <=30, 無法使用 checkSelfPermission() 檢查 BLUETOOTH_CONNECT 等API31才有的權(quán)限.
1. Framework 藍牙模塊
客戶端(例如三方應用),想要使用到藍牙闲昭,需要調(diào)用 系統(tǒng)藍牙接口 獲取 藍牙服務(wù).
這里會使用 AIDL 獲取 已綁定的 藍牙服務(wù),
即從系統(tǒng) Framework 藍牙 轉(zhuǎn)到了 藍牙app 進程,
可以看出罐寨,也存在 藍牙應用 作為 服務(wù)提供者.
// /frameworks/base/core/java/android/bluetooth/
package android.bluetooth;
public final class BluetoothAdapter {
public Set<BluetoothDevice> getBondedDevices() {
if (getState() != STATE_ON) {
return toDeviceSet(Arrays.asList());
}
try {
mServiceLock.readLock().lock();
if (mService != null) {
return toDeviceSet(Attributable.setAttributionSource(
Arrays.asList(mService.getBondedDevices(mAttributionSource)),
mAttributionSource));
}
return toDeviceSet(Arrays.asList());
} catch (RemoteException e) {
Log.e(TAG, "", e);
} finally {
mServiceLock.readLock().unlock();
}
return null;
}
這里注意 mService.getBondedDevices(mAttributionSource), 下面會分析.
2. App 藍牙模塊
上面的getBondedDevices 最終的是實現(xiàn),是在 藍牙應用里.
可以看出序矩,是在 IBluetooth.Stub 里的方法.
// /packages/apps/Bluetooth/src/com/android/bluetooth/btservice/AdapterService.java
public class AdapterService extends Service {
package com.android.bluetooth.btservice;
public static class AdapterServiceBinder extends IBluetooth.Stub {
@Override
public BluetoothDevice[] getBondedDevices(AttributionSource attributionSource) {
// don't check caller, may be called from system UI
AdapterService service = getService();
if (service == null || !Utils.checkConnectPermissionForDataDelivery(
service, attributionSource, "AdapterService getBondedDevices")) {
return new BluetoothDevice[0];
}
return service.getBondedDevicesWoCustomDevice(); /* SS_BLE_FEATURE_P50 */
}
這里注意 Utils.checkConnectPermissionForDataDelivery()會進行調(diào)用者權(quán)限的檢查, 信息封裝在 attributionSource
2.1 內(nèi)部權(quán)限檢查
// /packages/apps/Bluetooth/src/com/android/bluetooth/Utils.java
package com.android.bluetooth;
public final class Utils {
@SuppressLint("AndroidFrameworkRequiresPermission")
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public static boolean checkConnectPermissionForDataDelivery(
Context context, AttributionSource attributionSource, String message) {
return checkPermissionForDataDelivery(context, BLUETOOTH_CONNECT,
attributionSource, message);
}
會調(diào)用它的內(nèi)部方法:checkPermissionForDataDelivery, 并傳入權(quán)限為 BLUETOOTH_CONNECT 即 藍牙連接 的權(quán)限
@SuppressLint("AndroidFrameworkRequiresPermission")
private static boolean checkPermissionForDataDelivery(Context context, String permission,
AttributionSource attributionSource, String message) {
// STOPSHIP(b/188391719): enable this security enforcement
// attributionSource.enforceCallingUid();
final int result = PermissionChecker.checkPermissionForDataDeliveryFromDataSource(
context, permission, PID_UNKNOWN,
new AttributionSource(context.getAttributionSource(), attributionSource), message);
if (result == PERMISSION_GRANTED) {
return true;
}
final String msg = "Need " + permission + " permission for " + attributionSource + ": "
+ message;
if (result == PERMISSION_HARD_DENIED) {
throw new SecurityException(msg);
} else {
Log.w(TAG, msg);
return false;
}
}
這里又調(diào)用了 PermissionChecker.checkPermissionForDataDeliveryFromDataSource(), 根據(jù)返回結(jié)果處理.
(1) 返回 PERMISSION_GRANTED, 則結(jié)果返回true衩茸,代表 調(diào)用者 擁有了藍牙權(quán)限
(2) 返回 PERMISSION_HARD_DENIED, 則會拋出 SecurityException, 并提示 需要 XXX 的權(quán)限.
(3) 返回 其它值(只有PERMISSION_SOFT_DENIED這種), 則會返回false ,表示沒有 相應的藍牙權(quán)限.
2.2 委托PermissionChecker
package android.content;
public final class PermissionChecker {
public static final int PERMISSION_GRANTED = PermissionCheckerManager.PERMISSION_GRANTED;
public static final int PERMISSION_SOFT_DENIED =
PermissionCheckerManager.PERMISSION_SOFT_DENIED;
public static final int PERMISSION_HARD_DENIED =
PermissionCheckerManager.PERMISSION_HARD_DENIED;
public static int checkPermissionForDataDeliveryFromDataSource(@NonNull Context context,
@NonNull String permission, int pid, @NonNull AttributionSource attributionSource,
@Nullable String message) {
return checkPermissionForDataDeliveryCommon(context, permission, attributionSource,
message, false /*startDataDelivery*/, /*fromDatasource*/ true);
}
@SuppressWarnings("ConstantConditions")
private static int checkPermissionForDataDeliveryCommon(@NonNull Context context,
@NonNull String permission, @NonNull AttributionSource attributionSource,
@Nullable String message, boolean startDataDelivery, boolean fromDatasource) {
return context.getSystemService(PermissionCheckerManager.class).checkPermission(permission,
attributionSource.asState(), message, true /*forDataDelivery*/, startDataDelivery,
fromDatasource, AppOpsManager.OP_NONE);
}
分析:
(1) 會調(diào)用內(nèi)部方法 checkPermissionForDataDeliveryCommon, 并fromDatasource 參數(shù)設(shè)置為true傳入
(2) 進一步委托給 PermissionCheckerManager, 它也是一個系統(tǒng)服務(wù)贮泞,在 SystemServiceRegistry 代碼塊中注冊.
package android.app;
@SystemApi
public final class SystemServiceRegistry {
static {
registerService(Context.PERMISSION_CHECKER_SERVICE, PermissionCheckerManager.class,
new CachedServiceFetcher<PermissionCheckerManager>() {
@Override
public PermissionCheckerManager createService(ContextImpl ctx)
throws ServiceNotFoundException {
return new PermissionCheckerManager(ctx.getOuterContext());
}});
2.3 委托 PermissionCheckerManager
package android.permission;
public class PermissionCheckerManager {
public static final int PERMISSION_GRANTED = IPermissionChecker.PERMISSION_GRANTED;
public static final int PERMISSION_SOFT_DENIED = IPermissionChecker.PERMISSION_SOFT_DENIED;
public static final int PERMISSION_HARD_DENIED = IPermissionChecker.PERMISSION_HARD_DENIED;
@PermissionResult
public int checkPermission(@NonNull String permission,
@NonNull AttributionSourceState attributionSource, @Nullable String message,
boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource,
int attributedOp) {
Objects.requireNonNull(permission);
Objects.requireNonNull(attributionSource);
// Fast path for non-runtime, non-op permissions where the attribution chain has
// length one. This is the majority of the cases and we want these to be fast by
// hitting the local in process permission cache.
if (AppOpsManager.permissionToOpCode(permission) == AppOpsManager.OP_NONE) {
if (fromDatasource) {
if (attributionSource.next != null && attributionSource.next.length > 0) {
return mContext.checkPermission(permission, attributionSource.next[0].pid,
attributionSource.next[0].uid) == PackageManager.PERMISSION_GRANTED
? PERMISSION_GRANTED : PERMISSION_HARD_DENIED;
}
} else {
return (mContext.checkPermission(permission, attributionSource.pid,
attributionSource.uid) == PackageManager.PERMISSION_GRANTED)
? PERMISSION_GRANTED : PERMISSION_HARD_DENIED;
}
}
try {
return mService.checkPermission(permission, attributionSource, message, forDataDelivery,
startDataDelivery, fromDatasource, attributedOp);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
return PERMISSION_HARD_DENIED;
}
分析:
(1) 由上面可以知, fromDatasource == true, 因此它還是調(diào)用 Context.checkPermission 進行檢查.
(2) Context.checkPermission 是一個抽象方法, 最終由 ContextImpl 實現(xiàn).(查看最初傳遞的 service, 即 AdapterService 對象 )
它需要三個參數(shù),第一個是權(quán)限名稱幔烛,第二個是pid, 第三個是 uid, pid/uid 均由attributionSource提供
(3) AppOpsManager 是谷歌原生的 應用操作(權(quán)限) 管理啃擦, PermissionManager 也是基于這個實現(xiàn)的(具體參考另外一篇)
package android.content;
class ContextImpl extends Context {
@Override
public int checkPermission(String permission, int pid, int uid) {
if (permission == null) {
throw new IllegalArgumentException("permission is null");
}
if (mParams.isRenouncedPermission(permission)
&& pid == android.os.Process.myPid() && uid == android.os.Process.myUid()) {
Log.v(TAG, "Treating renounced permission " + permission + " as denied");
return PERMISSION_DENIED;
}
return PermissionManager.checkPermission(permission, pid, uid);
}
可見,它會根據(jù) 權(quán)限名稱/pid / uid, 再進行委托給 PermissionManager 去檢查.
而 PermissonManager 將會再后續(xù)單獨介紹.
3. 小結(jié)
到此, 我們可以初步得出一個結(jié)論:
(1) 應用使用某個 需要權(quán)限的功能時, 系統(tǒng)(Framework)會檢查 應用有沒有這個 權(quán)限饿悬,沒有則拋出異常.
(2) 應用的 pid/uid 信息, 封裝在 AttributionSource 對象里令蛉。
(3) 委托 PermissionCheckerManager 進行檢查, 這里將 pid/uid 取出來, 再調(diào)用 Context 進行檢查.
(4) 這個 Context 其實是 AdapterService 對象,類推可以是我們應用 中的 Activity狡恬、Service 珠叔、Application
(5) Context 里又會進行 委托 PermissionManager 進行 真正的檢查.
參考:
Android 12 源碼:http://aosp.opersys.com/xref/android-12.0.0_r2/