Android提供了三种设备管理方案,Device Administration(设备管理员), ProfileOwner(配置文件所有者)和 DeviceOwner(设备所有者)。这三种管理方案对应三种等级的管理权限,相对的,等级越高所拥有的管理权限越高,面临的风险也对大,所以,要将一个应用设置成为这些管理设备,也需要不同的权限等级。
要设置一个DeviceAdmin 所需要权限相对来说是最小的,Android系统提供了一种方案:
Intent intent = new Intent(
DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
mComponentName);
intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, "提示文字");
startActivityForResult(intent, 1);
该方法将激活DeviceAdmin的动作委托给系统Settings,有界面提示用户是否激活/停用/卸载一个设备管理应用。这种方案其实是一种动态权限,由用户决定是否启用设备管理。在特殊行业中,有些操作不应该让用户决定,由管理平台在后台一键设置。
如果一个应用的进程所属system,那么就和系统Settings具有相同的权限,我们可以在系统中添加一个这样的程序,用来一键设置自己的DeviceAdmin程序。
/**
* 设置DeviceAdmin
* packageName: 应用程序包名
* policyReceiver: 继承了DeviceAdminReceiver的类名
*/
public static void setActiveAdmin(String packageName, String policyReceiver) {
// 1. 将包名和类名转换为ComponentName
ComponentName component = new ComponentName(packageName, policyReceiver);
// 2. 通过ComponentName获取ActivityInfo,如果为空,说明参数传递有误,系统中根本不存在这个应用,直接返回
ActivityInfo ai = null;
try {
ai = iPackageManager.getReceiverInfo(component,
PackageManager.GET_META_DATA |
PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS |
PackageManager.MATCH_DIRECT_BOOT_UNAWARE |
PackageManager.MATCH_DIRECT_BOOT_AWARE, getMyUserId());
} catch (RemoteException e) {
debug("Unable to load component: " + component);
}
// 3. 调用DevicePolicyManager的现有方法setActiveAdmin(系统级应用有权限)
mDevicePolicyManager.setActiveAdmin(component, true /*refreshing*/, mUserId);
}
/**
* 通过包名判断一个程序是否为激活的DeviceAdmin
* packageName: 应用程序包名
* return
* true: 是
* false: 不是
*/
public static boolean packageHasActiveAdmins(String packageName) {
// packageHasActiveAdmins方法标记为@hide,只有系统应用可以调用
DevicePolicyManager devicePolicyManager = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
return devicePolicyManager.packageHasActiveAdmins(packageName);
}
了解更多DeviceAdmin的权限以及API的使用详情,请转到往期文章Android Device Administration 应用的能力
之前介绍过,ProfileOwner 在国内可能不适用(Android DevicePolicyManager 设备管理中已做出说明),所以设置一个ProfileOwner程序有几条途径不做深入讲解,这里只给出一个方案:
属于system进程的系统应用,有设置ProfileOwner程序的能力,可以在系统中实现一个系统应用,暴露一个一键设置ProfileOwner的接口:
/**
* 设置ProfileOwner
* packageName: 应用程序包名
* policyReceiver: 继承了DeviceAdminReceiver的类
* deviceUserName: 为ProfileOwner设置一个名字,如不设置传null
* return
* true: 设置成功
* false: 设置失败
* (限于篇幅这里只给出了关键代码,一部分逻辑、变量代码省略)
*/
public static boolean setProfileOwner(String packageName, String policyReceiver, String deviceUserName) {
// 1. 将包名和类名转换为ComponentName
ComponentName component = new ComponentName(packageName, policyReceiver);
// 2. 通过ComponentName获取ActivityInfo,如果为空,说明参数传递有误,系统中根本不存在这个应用,直接返回
ActivityInfo ai = null;
try {
ai = iPackageManager.getReceiverInfo(component,
PackageManager.GET_META_DATA |
PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS |
PackageManager.MATCH_DIRECT_BOOT_UNAWARE |
PackageManager.MATCH_DIRECT_BOOT_AWARE, getMyUserId());
} catch (RemoteException e) {
debug("Unable to load component: " + component);
}
// 3. 调用DevicePolicyManager的现有方法setActiveAdmin(系统级应用有权限)
mDevicePolicyManager.setActiveAdmin(component, true /*refreshing*/, mUserId);
// 4. 设置setProfileOwner
try {
result = mDevicePolicyManager.setProfileOwner(component, deviceUserName, mUserId);
debug("set package " + component + " as profile owner result: " + result);
} catch (RemoteException | IllegalArgumentException | IllegalStateException e) {
debug("Error: setProfileOwner failed to " + component.flattenToShortString() + " Error Info: " + e);
// 如果设置ProfileOwner异常,则将设置的DeviceAdmin程序也一并取消
try {
mDevicePolicyManager.removeActiveAdmin(component, mUserId);
} catch (RemoteException e2) {
debug("Error: removeActiveAdmin failed to " + component.toString() + " Error Info: " + e2);
}
}
if (result) {
try {
mDevicePolicyManager.setUserProvisioningState(DevicePolicyManager.STATE_USER_SETUP_FINALIZED, mUserId);
} catch (RemoteException e) {
debug("Error: setUserProvisioningState failed to " + component.toString() + " Error Info: " + e);
}
debug("Success: Active admin and profile owner set to " + component.toShortString() + " for user " + mUserId);
}
}
当一个应用成为了ProfileOwner应用,它就拥有了所有的ProfileOwner的能力。了解更多ProfileOwner的权限以及API的使用详情,请转至往期文章Android ProfileOwner 应用的能力。
DeviceOwner可以使一个第三方应用程序拥有系统最高管理权限,面临的风险也是最大的。 要设置一个DeviceOwner程序, 需要很高的权限,系统提供两种方式:
msm8953_64:/ #
msm8953_64:/ # dpm set-device-owner --name Test com.action.deviceadmin/.DPMTestReceiver
-----------------------mName Test
mComponent: {com.action.deviceadmin/com.action.deviceadmin.DPMTestReceiver}, mName: Test, mUserId: 0
Success: Device owner set to package ComponentInfo{com.action.deviceadmin/com.action.deviceadmin.DPMTestReceiver}
Active admin set to component {com.action.deviceadmin/com.action.deviceadmin.DPMTestReceiver}
msm8953_64:/ #
通过dpm命令设置一个DeviceOwner。
以下内容摘录自 https://mp.weixin.qq.com/s?__biz=MzAxMTE3MDkyMA==&mid=506926154&idx=1&sn=55ea2cfc894db74dcf296233a3e74a6f#rd 的文章内容。
用NFC传输的方式来使一个App成为DeviceOwner(设备所有者),我们需要两部手机。
首先,两台设备都要支持NFC并激活了NFC,并且激活了Android Beam功能(在设置里的NFC and payment里)。
第一台设备(Mobile A)是要在其上安装App,并使这个App成为Device Owner的。这个App可以是任意的一个App(我们的例子中是一个叫作Kiosk Mode Demo的App。
第二台设备(Mobile B)是要provision那台Mobile A的(使Mobile A上的App成为Device Owner),算是数据传输方/服务提供方。Mobile B上安装了我们的SetDeviceOwner这个App。
然后,在那个SetDeviceOwner的App里的源码中,比较关键的设置是下面几个:
EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME
对应要成为Device Owner的App的完整包名,例如:com.enmingx.test
EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LOCATION
对应要成为Device Owner的App的下载URL,例如:http://www.dropbox.com/xxx
EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM
对应要成为Device Owner的App的checksum(校验码),要计算校验码,可以用adb命令:
cat MY_APP.apk | openssl dgst -binary -sha1 | openssl base64 | tr ‘+/’ ‘-_’ | tr -d ‘=’
EXTRA_PROVISIONING_WIFI_SSID
对应用于下载要成为Device Owner的App的WiFi的名称
EXTRA_PROVISIONING_WIFI_SECURITY_TYPE
对应用于下载要成为Device Owner的App的安全类型,比如WPA或WPA2
最后,在那个SetDeviceOwner的App源码里,把这些数据都“打包”到一个NFC Bundle中,用NFC技术来传输到另一台手机。
你应该知道如何使用NFC来进行数据传输吧:
让两个手机足够接近,背靠背,然后会听到清脆的一声“叮”,显示"Touch to beam",然后你轻触作为传输方的那台设备的屏幕,就开始传输了。
为了成功使一台设备上的App成为Device Owner,这台设备必须从来没被配置过(当然更不能被Root过),也不能被设置过Device Owner或Profile Owner。如果已经配置过了,可以恢复出场设置。
开始操作:
对Mobile A恢复出厂设置(Factory Reset),一般在Settings(设置)里就可以选择(比较文明的方式);也可以用比较粗暴的方式,按键的方式(一般是同时按住 音量向上键+Home键+电源键 几秒,然后会出现选项,可以选择)。
当Mobile A的恢复出厂设置结束后,Mobile A会出现初始设置的界面。此时,Mobile A就是处于unprovisioned(还没被设置)的状态。
在Mobile B上,安装我们的SetDeviceOwner这个App,也就是要使其他设备的App成为Device Owner的,术语叫做“Device owner provisioning”。
在Mobile B上开启SetDeviceOwner这个App,点击“Generate checksum”按钮,会生成checksum(校验码)。
Mobile A处于初始配置状态,两台设备的屏幕都是打开的。将两台设备(Mobile A和Mobile B)背靠背,足够近,直到听到清脆的“叮”的声响,然后在Mobile B上轻触屏幕,即开始从Mobile B向Mobile A进行NFC的数据传输。
当NFC传输完成后(一般瞬间就完成了),Mobile A上会显示配置Device Owner的界面,标题貌似是Set up your profile(记不清了…),点击Set up按钮之后会问你要不要Encrypt设备(对数据加密),点击“是”(OK),然后选择快速Encrypt还是对所有数据Encrypt(加密所有数据会很慢),一般都选Fast Encryption就好。然后开始对手机的数据加密,不要问为什么,就是必须要这步。
加密完成后,Mobile A会重启。然后,因为之前我们传输过去的数据里面指定了WiFi的SSID和密码,而且也指定了那个要成为Device Owner的App的下载链接(URL),因此,会显示让你配置选择WiFi,请选择你之前指定的那个WiFi,并连接。
一旦WiFi成功连接上Internet,就会开始下载指定的App。下载完成后会开始安装,然后会使这个App成为Device Owner。
如果你看到一个Toast跳出来说:Device Owner enabled,那么就OK了。恭喜,你的App已经成为了Mobile A的Device Owner了。
可以看到,按照官方的方法设置一个DeviceOwner程序,要么得拥有Shell这种级别的权限,要么就使用NFC传输的这种繁杂的方式。对于行业需求,NFC的方式显然不可行。
跟踪dpm的源码,设置DeviceOwner 的方法最终调用到DevicePolicyManagerService.java中:
@Override
public boolean setDeviceOwner(ComponentName admin, String ownerName, int userId) {
if (!mHasFeature) {
return false;
}
if (admin == null
|| !isPackageInstalledForUser(admin.getPackageName(), userId)) {
throw new IllegalArgumentException("Invalid component " + admin
+ " for device owner");
}
final boolean hasIncompatibleAccountsOrNonAdb =
hasIncompatibleAccountsOrNonAdbNoLock(userId, admin);
synchronized (this) {
enforceCanSetDeviceOwnerLocked(admin, userId, hasIncompatibleAccountsOrNonAdb);
...
省略大量其它代码
...
}
}
setDeviceOwner方法在正在设置DeviceOwenr之前会做很多权限检查,以保证安全性,只要调用方法enforceCanSetDeviceOwnerLocked 和 checkDeviceOwnerProvisioningPreConditionLocked。
private void enforceCanSetDeviceOwnerLocked(@Nullable ComponentName owner, int userId,
boolean hasIncompatibleAccountsOrNonAdb) {
if (!isAdb()) {
enforceCanManageProfileAndDeviceOwners();
}
final int code = checkDeviceOwnerProvisioningPreConditionLocked(
owner, userId, isAdb(), hasIncompatibleAccountsOrNonAdb);
switch (code) {
case CODE_OK:
return;
case CODE_HAS_DEVICE_OWNER:
throw new IllegalStateException(
"Trying to set the device owner, but device owner is already set.");
case CODE_USER_HAS_PROFILE_OWNER:
throw new IllegalStateException("Trying to set the device owner, but the user "
+ "already has a profile owner.");
case CODE_USER_NOT_RUNNING:
throw new IllegalStateException("User not running: " + userId);
case CODE_NOT_SYSTEM_USER:
throw new IllegalStateException("User is not system user");
case CODE_USER_SETUP_COMPLETED:
throw new IllegalStateException(
"Cannot set the device owner if the device is already set-up");
case CODE_NONSYSTEM_USER_EXISTS:
throw new IllegalStateException("Not allowed to set the device owner because there "
+ "are already several users on the device");
case CODE_ACCOUNTS_NOT_EMPTY:
throw new IllegalStateException("Not allowed to set the device owner because there "
+ "are already some accounts on the device");
case CODE_HAS_PAIRED:
throw new IllegalStateException("Not allowed to set the device owner because this "
+ "device has already paired");
default:
throw new IllegalStateException("Unexpected @ProvisioningPreCondition " + code);
}
}
private int checkDeviceOwnerProvisioningPreConditionLocked(@Nullable ComponentName owner,
int deviceOwnerUserId, boolean isAdb, boolean hasIncompatibleAccountsOrNonAdb) {
if (mOwners.hasDeviceOwner()) {
return CODE_HAS_DEVICE_OWNER;
}
if (mOwners.hasProfileOwner(deviceOwnerUserId)) {
return CODE_USER_HAS_PROFILE_OWNER;
}
if (!mUserManager.isUserRunning(new UserHandle(deviceOwnerUserId))) {
return CODE_USER_NOT_RUNNING;
}
if (mIsWatch && hasPaired(UserHandle.USER_SYSTEM)) {
return CODE_HAS_PAIRED;
}
if (isAdb) {
// if shell command runs after user setup completed check device status. Otherwise, OK.
if (mIsWatch || hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
if (!mInjector.userManagerIsSplitSystemUser()) {
if (mUserManager.getUserCount() > 1) {
return CODE_NONSYSTEM_USER_EXISTS;
}
if (hasIncompatibleAccountsOrNonAdb) {
return CODE_ACCOUNTS_NOT_EMPTY;
}
} else {
// STOPSHIP Do proper check in split user mode
}
}
return CODE_OK;
} else {
if (!mInjector.userManagerIsSplitSystemUser()) {
// In non-split user mode, DO has to be user 0
if (deviceOwnerUserId != UserHandle.USER_SYSTEM) {
return CODE_NOT_SYSTEM_USER;
}
// In non-split user mode, only provision DO before setup wizard completes
if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
return CODE_USER_SETUP_COMPLETED;
}
} else {
// STOPSHIP Do proper check in split user mode
}
return CODE_OK;
}
}
细看以上两个方法的权限检测,很多权限在检测之前会先判断 isAdb,如果是adb,很多权限都会跳过。让我们看看这个isAdb 是怎么来的。
private boolean isAdb() {
final int callingUid = mInjector.binderGetCallingUid();
return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
}
判断流程非常简单,就是获得调用方进程的Uid,如果是SHELL_UID 或者 ROOT_UID,那么就认为有设置DeviceOwner 的权限,跳过权限检查,直接设置。
**
* Defines the root UID.
* @hide
*/
public static final int ROOT_UID = 0;
/**
* Defines the UID/GID under which system code runs.
*/
public static final int SYSTEM_UID = 1000;
/**
* Defines the UID/GID under which the telephony code runs.
*/
public static final int PHONE_UID = 1001;
/**
* Defines the UID/GID for the user shell.
* @hide
*/
public static final int SHELL_UID = 2000;
以上是系统中定义的各大内部专属进程的UID值。属于system进程的应用的UID值为1000,没有权限设置DeviceOwner。而Android系统中设置DeviceOwner 的入口也只有这里。
要实现一键设置DeviceOwner程序的功能,只能修改DevicePolicyManagerService 的代码。我们可以制定一个方案,既然 SHELL 和 ROOT进程能够设置DeviceOwner,那么我们添加的系统进程也可以“冒充SHELL进程”, 只需要添加如下几行代码即可:
private boolean isAdb() {
final int callingUid = mInjector.binderGetCallingUid();
// 获取调用放的进程id -- pid
final int callingPid = mInjector.binderGetCallingPid();
return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID || fixedProcess(callingPid);
}
fixedProcess(callingPid) 方法比较 我们自定义的系统进程的PID 和调用方的PID是否一致,如果一致则认为有权限设置DeviceOwner,也即是说,只有ROOT、SHEL 和 自定义系统进程 拥有设置DeviceOwner 的权限。应用进程的PID是系统生成的,不同的应用程序进程ID不可能重复,同一个应用被杀死后再开起来,进程ID也会不同,系统会以递增的形式分配进程ID。所以不用担心该ID被冒名顶替。fixedProcess方法会取得自定义系统进程事先保存好的PID。
添加了上述规避权限的流程,接下来只需要在自定义系统进程中添加一个设置DeviceOwner的接口即可:
/**
* 设置DeviceOwner
* packageName: 应用程序包名
* policyReceiver: 继承了DeviceAdminReceiver的类
* deviceUserName: 为DeviceOwner设置一个名字,如不设置传null
* return
* true: 设置成功
* false: 设置失败
* (限于篇幅这里只给出了关键代码,一部分逻辑、变量代码省略)
*/
public static boolean setDeviceOwner(String packageName, String policyReceiver, String deviceUserName) {
// 1. 保存本应用的进程号,在设置DeviceOwner时的权限检测中取出,视为Shell、Root。
saveMdmPid(android.os.Process.myPid());
// 2. 将包名和类名转换为ComponentName
ComponentName component = new ComponentName(packageName, policyReceiver);
// 3. 通过ComponentName获取ActivityInfo,如果为空,说明参数传递有误,系统中根本不存在这个应用,直接返回
ActivityInfo ai = null;
try {
ai = iPackageManager.getReceiverInfo(component,
PackageManager.GET_META_DATA |
PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS |
PackageManager.MATCH_DIRECT_BOOT_UNAWARE |
PackageManager.MATCH_DIRECT_BOOT_AWARE, getMyUserId());
} catch (RemoteException e) {
debug("Unable to load component: " + component);
}
// 4. 调用DevicePolicyManager的现有方法setActiveAdmin(需要系统级应用有权限)
mDevicePolicyManager.setActiveAdmin(component, true /*refreshing*/, mUserId);
// 5. 调用DevicePolicyManager的setDeviceOwner接口
try {
result = mDevicePolicyManager.setDeviceOwner(component, deviceUserName, mUserId);
debug("set package " + component + " as device owner result: " + result);
} catch (RemoteException | IllegalArgumentException | IllegalStateException e) {
debug("Error: setDeviceOwner failed to " + component.flattenToShortString() + " Error Info: " + e);
// Need to remove the admin that we just added.
try {
mDevicePolicyManager.removeActiveAdmin(component, mUserId);
} catch (RemoteException e2) {
debug("Error: removeActiveAdmin failed to " + component.toString() + " Error Info: " + e2);
}
}
if (result) {
try {
mDevicePolicyManager.setUserProvisioningState(DevicePolicyManager.STATE_USER_SETUP_FINALIZED, mUserId);
} catch (RemoteException e) {
debug("Error: setUserProvisioningState failed to " + component.toString() + " Error Info: " + e);
}
debug("Success: Device owner set to package " + component);
debug("Active admin set to component " + component.toShortString());
}
}
如此,一键设置DeviceOwner 应用的接口就实现了。
对于一个应用程序来说,成为DeviceOwner应用即拥有了系统最高软件管理权限。了解更多DeviceOwner的权限以及API的使用详情,请回顾往期博客Android DeviceOwner 应用的能力