Android 12(API 31) 的蓝牙权限做了更改.
2021-7-20 ,Android 12 引入了 BLUETOOTH_SCAN 、 BLUETOOTH_ADVERTISE 和 BLUETOOTH_CONNECT 权限,
可让应用 扫描附近的设备(NearBy),而无需请求位置权限(ACCESS_FINE_LOCATION).
参考: https://developer.android.com/guide/topics/connectivity/bluetooth/permissions (1)如果您的应用程序寻找蓝牙设备,例如 BLE 外围设备,请声明 BLUETOOTH_SCAN 权限。
(2)如果您的应用程序使当前设备可被其他蓝牙设备发现,请声明该 BLUETOOTH_ADVERTISE 权限。
(3)如果您的应用程序与已配对的蓝牙设备通信,请声明 BLUETOOTH_CONNECT 权限。
(4)对于遗留的蓝牙相关权限声明,设置 android:maxSdkVersion为30. 此应用兼容性步骤有助于系统仅向您的应用授予安装在运行 Android 12 或更高版本的设备上时所需的蓝牙权限。
具体使用方法参考指南。
由于这三种蓝牙权限都是 运行时权限,必须 先在应用中 明确请求 用户同意,然后才能查找蓝牙设备.
因此,就产生了 蓝牙连接 权限检查问题,我们从这个角度(BLUETOOTH_CONNECT) 入手, 分析 权限检查机制.
注:(1)在 targetSKD <=30 的应用,是无法声明 上面三个权限的,仅有以下几个( Nearby 附近的设备 权限默认开启???)
(2)Nearby 附近的设备 权限是为了兼容 旧版本Android 系统(旧SDK),所以在SDK<=30默认开启 ???
因此,targetSKD <=30 的应用, 在API31(Android12) 的**_设备_上必须要有这个权限才能使用蓝牙 ??
否则会抛出 安全异常 错误?? 这是因为 AppOpsManager 在**系统层面**还会限制??
(3) targetSDK <=30, 无法使用 checkSelfPermission() 检查 BLUETOOTH_CONNECT 等API31才有的权限.
1. Framework 蓝牙模块
客户端(例如三方应用),想要使用到蓝牙,需要调用 系统蓝牙接口 获取 蓝牙服务.
这里会使用 AIDL 获取 已绑定的 蓝牙服务,
即从系统 Framework 蓝牙 转到了 蓝牙app 进程,
可以看出,也存在 蓝牙应用 作为 服务提供者.
// /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 最终的是实现,是在 蓝牙应用里.
可以看出,是在 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()会进行调用者权限的检查, 信息封装在 attributionSource
2.1 内部权限检查
// /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);
}
会调用它的内部方法:checkPermissionForDataDelivery, 并传入权限为 BLUETOOTH_CONNECT 即 蓝牙连接 的权限
@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;
}
}
这里又调用了 PermissionChecker.checkPermissionForDataDeliveryFromDataSource(), 根据返回结果处理.
(1) 返回 PERMISSION_GRANTED, 则结果返回true,代表 调用者 拥有了蓝牙权限
(2) 返回 PERMISSION_HARD_DENIED, 则会抛出 SecurityException, 并提示 需要 XXX 的权限.
(3) 返回 其它值(只有PERMISSION_SOFT_DENIED这种), 则会返回false ,表示没有 相应的蓝牙权限.
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) 会调用内部方法 checkPermissionForDataDeliveryCommon, 并fromDatasource 参数设置为true传入
(2) 进一步委托给 PermissionCheckerManager, 它也是一个系统服务,在 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, 因此它还是调用 Context.checkPermission 进行检查.
(2) Context.checkPermission 是一个抽象方法, 最终由 ContextImpl 实现.(查看最初传递的 service, 即 AdapterService 对象 )
它需要三个参数,第一个是权限名称,第二个是pid, 第三个是 uid, pid/uid 均由attributionSource提供
(3) AppOpsManager 是谷歌原生的 应用操作(权限) 管理, PermissionManager 也是基于这个实现的(具体参考另外一篇)
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);
}
可见,它会根据 权限名称/pid / uid, 再进行委托给 PermissionManager 去检查.
而 PermissonManager 将会再后续单独介绍.
3. 小结
到此, 我们可以初步得出一个结论:
(1) 应用使用某个 需要权限的功能时, 系统(Framework)会检查 应用有没有这个 权限,没有则抛出异常.
(2) 应用的 pid/uid 信息, 封装在 AttributionSource 对象里。
(3) 委托 PermissionCheckerManager 进行检查, 这里将 pid/uid 取出来, 再调用 Context 进行检查.
(4) 这个 Context 其实是 AdapterService 对象,类推可以是我们应用 中的 Activity、Service 、Application
(5) Context 里又会进行 委托 PermissionManager 进行 真正的检查.
作者:行走中的3卡
最后
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。