相关资源:
android GrantPermissionsActivity 详解
android grantRuntimePermission 详解
前言:
之前几篇博文(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 中控制好。