[Android]Android Enterprise之Lock Task Mode
官网链接:https://developer.android.com/work/dpc/dedicated-devices/lock-task-mode
前言
有个需求,需要在没有系统签名的前提下,完成某个应用始终保持在前台;(行业应用,非流氓应用)
第一反应是如下这个心路历程:
· 将该App设置为HOME,即Launcher;
否决原因:无法屏蔽状态栏;
· 将该App设置为HOME,再设置FullScreen模式;
否决原因:仍然无法完全屏蔽状态栏;
· 将该App设置为HOME,设置FullScreen模式,再在顶部创建一个透明的View,拦截TouchEvent;
否决原因:Android 8.0无法在非System App内创建高于StatusBar的View;
(如果Android 8.0以前,在这里可以直接移步https://stackoverflow.com/questions/7457730/preventing-status-bar-expansion)
探索
反复尝试后,觉得上面的思路是行不通的;
同时,在网上发现了这么一个东西:LockTaskMode
App开发得少,这玩意长啥样都不知道,但是看官网介绍,貌似能用。
这里吐槽一下,这部分功能是属于Android Enterprise的,官网貌似写得很随意,而且应该是从demo里面直接截取的代码片段,很多常量根本不知道是啥;
这里就直接跳过踩坑环节,上代码:
首先LockTaskMode需要App是DeviceOwner或者ProfileOwner;前者是第三方应用能获取到的最高权限,后者在Android 8.0上需要创建一个关联用户并管理一个Work Profile;
然而,这些在LockTaskMode的官网介绍中都没有提到,按照它的指引写出来的代码只能是一堆FC……
这里其实两个Owner都不好拿到:
DeviceOwner需要首先获得Device Admin权限,然后通过如下两种方式之一获取DeviceOwner权限:
1. adb shell dpm set-device-owner com.example.sample/.MyDeviceAdminReceiver
2. 使用NFC Provisioning完成,这个非常复杂,详见谷歌Demo:https://github.com/android/enterprise-samples
ProfileOnwer需要首先获得Device Admin权限,然后创建一个Work Profile,然后将该Work Profile的用户添加为自己的关联用户(Affiliated User)
试了一天,最后还是决定先用adb授权DeviceOwner,先跳过这一步,后续有时间再写个NFC授权应用即可;
上代码:
首先创建一个继承自android.app.admin.DeviceAdminReceiver的DeviceAdmin:
public class DeviceAdmin extends DeviceAdminReceiver {
@Override
public void onEnabled(Context context, Intent intent) {}
@Override
public void onDisabled(Context context, Intent intent) {}
}
然后修改清单配置文件:
<receiver
android:name=".DeviceAdmin"
android:permission="android.permission.BIND_DEVICE_ADMIN">
<meta-data
android:name="android.app.device_admin"
android:resource="@xml/adminmanager" />
<intent-filter>
<action android:name="android.app.action.PROFILE_PROVISIONING_COMPLETE"/>
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
</intent-filter>
</receiver>
res/xml/下创建adminmanager.xml,按需配置如下权限:
<?xml version="1.0" encoding="utf-8"?>
<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
<uses-policies>
<!--锁屏 -->
<force-lock />
<!--限制密码类型-->
<limit-password />
<!-- 监控屏幕解锁尝试次数 -->
<watch-login />
<!-- 重置密码-->
<reset-password />
<!--恢复出厂设置-->
<wipe-data />
<!-- 设置锁定屏幕密码的有效期 -->
<expire-password />
<!-- 设置存储设备加密 -->
<encrypted-storage />
<!-- 停用相机 -->
<disable-camera />
</uses-policies>
</device-admin>
然后在Activity启动过程中通过Intent跳转到DeviceAdmin的授权界面:
DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(DEVICE_POLICY_SERVICE);
ComponentName comName = new ComponentName(getApplicationContext(), DeviceAdmin.class);
if (!dpm.isAdminActive(comName)) {
Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, comName);
intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, "申请DeviceAdmin权限");
startActivityForResult(intent, 10086);
}
并在onActivityResult方法中监听结果:
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (RESULT_OK == resultCode) {
DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(DEVICE_POLICY_SERVICE);
ComponentName comName = new ComponentName(getApplicationContext(), DeviceAdmin.class);
switch (requestCode) {
case 10086:
if (dpm.isDeviceOwnerApp(getPackageName()) || setDeviceOwner(true)) {
dpm.setLockTaskPackages(comName, new String[]{"com.zsui.fullscreendemo"});
} else {
Toast.makeText(this, "设置DeviceOwner失败", Toast.LENGTH_SHORT).show();
}
break;
}
}
}
private boolean setDeviceOwner(boolean enabled) {
boolean result = true;
Runtime runtime = Runtime.getRuntime();
try {
Process proc = runtime.exec("su");
OutputStream os = proc.getOutputStream();
if (enabled) {
os.write("dpm set-device-owner com.zsui.fullscreendemo/.DeviceAdmin".getBytes());
} else {
os.write("dpm remove-active-admin com.zsui.fullscreendemo/.DeviceAdmin".getBytes());
}
os.flush();
byte[] buff = new byte[8192];
int n = proc.getInputStream().read(buff);
} catch (IOException e) {
e.printStackTrace();
result = false;
}
return result;
}
此时,如果app可以获取到root权限,或者通过adb执行了dpm set-device-owner com.zsui.fullscreendemo/.DeviceAdmin,此时应用应该是没有HOME键与RECENT键的,且下拉状态栏无效;
然后问题也来了,咋退出?
这里最好做个Button,然后在其onClickListener里进行反注册:
public void onClick(View v) {
DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(DEVICE_POLICY_SERVICE);
if (dpm.isDeviceOwnerApp(getPackageName())) {
ComponentName comName = new ComponentName(getApplicationContext(), DeviceAdmin.class);
dpm.setLockTaskPackages(comName, new String[]{});
stopLockTask();
} else {
finish();
}
反注册就是把之前传给setLockTaskPackages方法的第二个参数String[]中的包名去掉,严格来说需要判断当前的DeviceOwner有哪些,只去掉自己的那个;这里为了展示方便,就不写那么多了;
时间紧促,本文仅供参考以及本人后续查阅;
如有疑问或者纰漏,欢迎留言讨论、指正;
后续有时间会更新NFC Provisiong的那部分;(极有可能没时间…)
谢谢!