【Android】从蓝牙连接权限检查入手,分析权限检查机制

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 进行 真正的检查.

链接:https://www.jianshu.com/p/a0a67482e0d4](https://www.jianshu.com/p/a0a67482e0d4
作者:行走中的3卡

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。

扫描下方二维码免费领取~

在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集
在这里插入图片描述
二、源码解析合集

在这里插入图片描述
三、开源框架合集

在这里插入图片描述

欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值