一键设置 DeviceAdmin/ProfileOwner/DeviceOwner 应用

一键设置 DeviceAdmin/ProfileOwner/DeviceOwner 应用

概述

Android提供了三种设备管理方案,Device Administration(设备管理员), ProfileOwner(配置文件所有者)和 DeviceOwner(设备所有者)。这三种管理方案对应三种等级的管理权限,相对的,等级越高所拥有的管理权限越高,面临的风险也对大,所以,要将一个应用设置成为这些管理设备,也需要不同的权限等级。

设置 Device Administration

要设置一个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

之前介绍过,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可以使一个第三方应用程序拥有系统最高管理权限,面临的风险也是最大的。 要设置一个DeviceOwner程序, 需要很高的权限,系统提供两种方式:

adb shell

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。

使用NFC方式

以下内容摘录自 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。

enforceCanSetDeviceOwnerLocked

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);

}

}

checkDeviceOwnerProvisioningPreConditionLocked

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 应用的能力

上一篇 Android DeviceOwner 应用的能力

下一篇 微信小程序入门级实战开发指南