前言:

之前几篇博文(Android Runtime Permission 详解、android grantRuntimePermission 详解、


android GrantPermissionsActivity 详解)主要是分析了Runtime permission的控制流程。


对于Runtime permission,在应用需要对应的权限的时候,会给用户一个提示。

CTA 要求对于BT、WLAN、NFC 等也需要给出相应的提示,所以这些permission也需要单独的控制。


这一篇来分析一下Normal permission或者intall time permission 在android M之后单独控制流程。


 


举例说明:

这里我们用蓝牙来举例说明,蓝牙的permission 如下:


    <permission android:name="android.permission.BLUETOOTH_ADMIN"                                       

        android:description="@string/permdesc_bluetoothAdmin"                                           

        android:label="@string/permlab_bluetoothAdmin"                                                  

        android:protectionLevel="normal" /> 

如果需要打开/关闭蓝牙的控制,必须申请这个权限,但是这个权限level 是normal的,并不是runtime permission,不能通过PMS 中的grantRuntimePermission 或者是invokeRuntimePermission 来管理。对于这种normal permission,我们可以利用AppOps 来管理。


 


BT enable:


先来看下BT enable/disable是如何实现的?


framework/base/core/java/android/bluetooth/BluetoothAdapter.java:


    public boolean enableBLE() {

        if (!isBleScanAlwaysAvailable()) return false;

 

        try {

            String packageName = ActivityThread.currentPackageName();

            mManagerService.updateBleAppCount(mToken, true, packageName);

            if (isLeEnabled()) {

                if (DBG) Log.d(TAG, "enableBLE(): Bluetooth already enabled");

                return true;

            }

            if (DBG) Log.d(TAG, "enableBLE(): Calling enable");

            return mManagerService.enable(packageName);

        } catch (RemoteException e) {

            Log.e(TAG, "", e);

        }

 

        return false;

    }

这里是提供用户enable的开关入口,最终会调用到IBluetoothManager.enable:


class BluetoothManagerService extends IBluetoothManager.Stub {}

    public boolean enable(String packageName) throws RemoteException {

        final int callingUid = Binder.getCallingUid();

        final boolean callerSystem = UserHandle.getAppId(callingUid) == Process.SYSTEM_UID;

 

        if (isBluetoothDisallowed()) {

            if (DBG) {

                Slog.d(TAG,"enable(): not enabling - bluetooth disallowed");

            }

            return false;

        }

 

        if (!callerSystem) {

            if (!checkIfCallerIsForegroundUser()) {

                Slog.w(TAG, "enable(): not allowed for non-active and non system user");

                return false;

            }

 

            mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,

                    "Need BLUETOOTH ADMIN permission");

 

            if (!isEnabled() && mPermissionReviewRequired

                    && startConsentUiIfNeeded(packageName, callingUid,

                            BluetoothAdapter.ACTION_REQUEST_ENABLE)) {

                return false;

            }

        }

        ...

        ...

    }

我们通过AppOps 控制,只需要在这里,enable 真正实施之前加一个dialog 确认即可。


AppOpsManager mAppOpsManager = mContext.getSystemService(AppOpsManager.class);

String packages = mContext.getPackageManager().getNameForUid(Binder.getCallingUid());

if ((Binder.getCallingUid() >= Process.FIRST_APPLICATION_UID)

         && (packages.indexOf("android.uid.systemui") != 0)

         && (packages.indexOf("android.uid.system") != 0)) {

    int result = mAppOpsManager.noteOp(AppOpsManager.OP_BLUETOOTH_ADMIN,

          Binder.getCallingUid(), packages);

    if (result == AppOpsManager.MODE_IGNORED) {

        return false;

    }

}

通过AppOps 的noteOp 接口确认当前permission 是否是允许或者禁止状态,返回值分别是AppOpsManager.MODE_ALLOWED 和AppOpsManager.MODE_IGNORED。


 


AppOpsManager.noteOp:


    public int noteOp(int op, int uid, String packageName) {

        try {

            int mode = mService.noteOperation(op, uid, packageName);

            if (mode == MODE_ERRORED) {

                throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));

            }

            return mode;

        } catch (RemoteException e) {

            throw e.rethrowFromSystemServer();

        }

    }

当然还有一个接口:


    public int noteOpNoThrow(int op, int uid, String packageName) {

        try {

            return mService.noteOperation(op, uid, packageName);

        } catch (RemoteException e) {

            throw e.rethrowFromSystemServer();

        }

    }

显而易见,对于返回值MODE_ERRORED 是否进行exception 提醒进行了区分。但是最终调用的都是AppOpsService 中的noteOperation 接口:


    public int noteOperation(int code, int uid, String packageName) {

        verifyIncomingUid(uid);

        verifyIncomingOp(code);

        String resolvedPackageName = resolvePackageName(uid, packageName);

        if (resolvedPackageName == null) {

            return AppOpsManager.MODE_IGNORED;

        }

        return noteOperationUnchecked(code, uid, resolvedPackageName, 0, null);

    }

noteOperationUnchecked 的source code 这里就不给出了,我们可以看到最终会调用到这里,确认此权限在app 中是否会被允许或禁止。


那按照CTA 的要求,我们可以在这里给出用户dialog 提示,并且让用户选择是否打开。


 


具体dialog 的source code 涉及公司保密协议,暂不给出,不过欢迎一起交流。


 


NFC enable 的管控:


根据BT 的经验,对于NFC 可以同样的进行控制:


    private boolean isNfcAllowed() {

        AppOpsManager mAppOpsManager = mContext.getSystemService(AppOpsManager.class);

        String packages = mContext.getPackageManager().getNameForUid(Binder.getCallingUid());

        if ((Binder.getCallingUid() >= Process.FIRST_APPLICATION_UID)

                && (packages.indexOf("android.uid.systemui") != 0)

                && (packages.indexOf("android.uid.system") != 0)) {

            int result = mAppOpsManager.noteOp(AppOpsManager.OP_NFC,

                                                Binder.getCallingUid(), packages);

            if (result == AppOpsManager.MODE_IGNORED) {

                return false;

            }

        }

        return true;

    }

最主要是其中OP_BLUETOOTH_ADMIN 和 OP_NFC 的逻辑控制需要在AppOpsManager 中控制好。


更多文章请关注《万象专栏》