【Android】使用deviceowner 配置手机设置 (Monkey自动化测试删去状态栏、静音、设定输入法、APP自动权限授予、Kiosk模式打开)
背景:初步方法汇总1. 屏幕固定功能[ScreenPinning](/about/versions/android-5.0.html#ScreenPinning)2. 使用BroadcastReceiver打开和关闭WIFI3. 调用DeviceOwner中API,关闭状态栏。DeviceOwner设置和使用概念app结构代码分析1. 在res/xml目录下新建device_admin.xml文件;2. 注册一个广播继承DeviceAdminReceiver;3. 在清单文件里注册广播;4. 在其它类中激活设备管理器5. 设置device-admin权限6. 之后就是激活deviceOwner激活DeviceOwner时遇到的问题1. 最初我直接在PC命令行输入 adb dpm set-…… 这一大串命令的时候,报错 Error: Bad admin: ComponentInfo{com.example.deviceowner/com.example.deviceowner.MyDeviceAdminReceiver}2. 经典错误 java.lang.IllegalStateException: Not allowed to set the device owner because there are already some accounts on the deviceI. 删掉多的userInfoII. 删掉account7. 调用deviceOwner的API背景:
需要使用Monkey进行自动化测试
,但是测试时Monkey
经常会下拉状态栏
导致网络丢失
等一系列问题,当前直接在百度上搜到的方法都是使用adb让屏幕全屏,但是我的测试机并不支持这个行为,所以需要找其它的方法。
初步方法汇总
在谷歌上搜索,找到了三种解决办法:
1. 屏幕固定功能ScreenPinning
这个功能可以在手机设置中开启,开启后可以固定手机只使用这个应用且屏蔽导航栏,开启需要使用虚拟按键。
所谓虚拟按键指的是屏幕下方出现返回、主页面、多任务 这些按钮,与之相对的是手势,也就是一般手机上推屏幕会出现多任务管理这个功能。
使用流程是:开启功能后,打开多任务管理,长按app,会出现锁定按钮,点击即可锁定。关闭是同时长按主页面和返回。
我的情况不太支持使用这个功能,首先我是对一些列app进行连续monkey测试,每次切换app锁定很困难,可能需要使用Appium之类的配合,其次,我需要app的截图,开启这个功能需要使用虚拟按键,app的截图会多出来虚拟按键的部分
2. 使用BroadcastReceiver打开和关闭WIFI
这个是我结合下面两个文章想到的
Android判断wifi状态 监听wifi连接
Android中使用BroadcastReceiver打开和关闭WIFI
完全可以试着写一个app监听wifi状态,在wifi关闭的时候自己设置打开wifi。
但是我没有实现这个想法。
3. 调用DeviceOwner中API,关闭状态栏。
最后一个方法就是使用DeviceOwner的API,
依旧是写个APP,调用Android中设备管理相关类的API
DevicePolicyManager下面的setStatusDisabled 函数
DeviceOwner设置和使用
概念
下面介绍几个概念:
device-owner:特殊的device-admin
设备管理者 device-admin
/guide/topics/admin/device-admin
app结构
常规的就是写一个最简单的app + 可以调用deviceOwner的一些设置
要完成调用deviceOwner首先,需要激活device-admin,此时可以使用一些deviceadmin相关的API设置,比如关闭相机等等,激活完device-admin再激活deviceOwner,之后就可以使用deviceOwner相关的API了
我自己这里是参照大佬的设计http://floatingmuseum.github.io//07/device-admin-practice
也写成了设置界面的样式
但是核心设置其实就几点:/XYScience/DeviceOwner
上面这个大佬的readme写的很清晰,我的app也基本参照两位大佬的代码
代码分析
下面对大佬的代码进行一下讲解
首先是device-admin编写:
1. 在res/xml目录下新建device_admin.xml文件;
<?xml version="1.0" encoding="utf-8"?><device-adminxmlns:android="/apk/res/android"><uses-policies></uses-policies></device-admin>
2. 注册一个广播继承DeviceAdminReceiver;
public class MyDeviceAdminReceiver extends DeviceAdminReceiver{@Overridepublic void onProfileProvisioningComplete(Context context, Intent intent) {super.onProfileProvisioningComplete(context, intent);}@Overridepublic void onEnabled(Context context, Intent intent) {super.onEnabled(context, intent);}@Overridepublic CharSequence onDisableRequested(Context context, Intent intent) {return super.onDisableRequested(context, intent);}@Overridepublic void onDisabled(Context context, Intent intent) {super.onDisabled(context, intent);}@Overridepublic void onPasswordChanged(Context context, Intent intent) {super.onPasswordChanged(context, intent);Logger.d("onPasswordChanged");}@Overridepublic void onPasswordFailed(Context context, Intent intent) {super.onPasswordFailed(context, intent);Logger.d("onPasswordFailed");}@Overridepublic void onPasswordSucceeded(Context context, Intent intent) {super.onPasswordSucceeded(context, intent);Logger.d("onPasswordSucceeded");}@Overridepublic void onPasswordExpiring(Context context, Intent intent) {super.onPasswordExpiring(context, intent);Logger.d("onPasswordExpiring");}/*** 获取ComponentName,DevicePolicyManager的大多数方法都会用到*/public static ComponentName getComponentName(Context context) {return new ComponentName(context.getApplicationContext(), MyDeviceAdminReceiver.class);}}
3. 在清单文件里注册广播;
<receiverandroid:name=".MyDeviceAdminReceiver"android:permission="android.permission.BIND_DEVICE_ADMIN"><meta-dataandroid:name="android.app.device_admin"android:resource="@xml/device_admin"/><intent-filter><action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/><action android:name="android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED"/><action android:name="android.app.action.DEVICE_ADMIN_DISABLED"/></intent-filter></receiver>
这样就完成了一个device-admin广播,之后就是激活和使用
4. 在其它类中激活设备管理器
激活的核心代码:
if (!devicePolicyManager.isAdminActive(comName)) {val intent = Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN)intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, comName)intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, "激活此设备管理员后可免root停用应用")startActivityForResult(intent, 1)} else {Toast.makeText(this, "此App已激活设备管理器", Toast.LENGTH_SHORT).show()}
这段代码可以放在合适的地方使用,下面的代码,
dpm = (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE);
获取dpm在Fragment的onCreate中,
大佬app设置使用了PreferenceFragment(已过时,类似setting结构的fragment),点击对应preference后触发getAdmin()函数,激活deviceadmin,具体代码可以去看第一位大佬的github分析
@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);addPreferencesFromResource(R.xml.pref_deviceadmin);initListener();activity = DeviceAdminFragment.this.getActivity();if (dpm == null) {dpm = (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE);}if (pm == null) {pm = activity.getPackageManager();}mComponentName = MyDeviceAdminReceiver.getComponentName(activity);if(Build.VERSION.SDK_INT>= Build.VERSION_CODES.LOLLIPOP){Logger.d("isProfileOwnerApp:"+dpm.isProfileOwnerApp(activity.getPackageName()));}}private boolean checkDeviceAdminEnabled() {return dpm.isAdminActive(mComponentName);}private void getDeviceAdmin() {if (checkDeviceAdminEnabled()) {ToastUtil.show("已激活");return;}Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN,mComponentName);intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION,getString(R.string.device_admin_description));startActivity(intent);}
5. 设置device-admin权限
在使用device-admin的API时,有些设置需要特殊的权限
权限常量
对应在xml中设置政策:
<device-admin xmlns:android="/apk/res/android" ><uses-policies><!-- 设置密码规则 --><limit-password /><!-- 监视屏幕解锁尝试次数 --><watch-login /><!-- 更改解锁密码 --><reset-password /><!-- 锁定屏幕 --><force-lock /><!-- 清除数据,恢复出厂模式,在不发出警告的情况下 --><wipe-data /><!-- 锁屏密码有效期 --><expire-password /><!-- 对存储的应用数据加密 --><encrypted-storage /><!-- 禁用锁屏信息 --><disable-keyguard-features/><!-- 禁用摄像头 --><disable-camera /></uses-policies></device-admin><!--和<application>同级 -->
6. 之后就是激活deviceOwner
现在一般有三种方式:我自己使用的是第二种adb的方式
利用ADB命令
$adb shell dpm set-device-owner com.example.deviceowner/.MyDeviceAdminReceiver
java.lang.IllegalStateException: Not allowed to set the device owner because there are already some accounts on the device
设置-账号中退出所有账户,然后重新尝试ADB设置
激活DeviceOwner时遇到的问题
1. 最初我直接在PC命令行输入 adb dpm set-…… 这一大串命令的时候,报错 Error: Bad admin: ComponentInfo{com.example.deviceowner/com.example.deviceowner.MyDeviceAdminReceiver}
如下图情况:会有一大堆使用说明最后有一点错误信息
D:\UIDump-master>adb shell dpm set-device-owner com.example.deviceowner/.MyDeviceAdminReceiver
usage: dpm [subcommand] [options]
usage: dpm set-active-admin [ --user <USER_ID> | current ]
usage: dpm set-device-owner [ --user <USER_ID> | currentEXPERIMENTAL] [ --name ]
usage: dpm set-profile-owner [ --user <USER_ID> | current ] [ --name ]
usage: dpm remove-active-admin [ --user <USER_ID> | current ] [ --name ]
dpm set-active-admin: Sets the given component as active admin for an existing user.
dpm set-device-owner: Sets the given component as active admin, and its package as device owner.
dpm set-profile-owner: Sets the given component as active admin and profile owner for an existing user.
dpm remove-active-admin: Disables an active admin, the admin must have declared android:testOnly in the application in its
manifest. This will also remove device and profile owners.
dpm clear-freeze-period-record: clears framework-maintained record of past freeze periods that the device went through. For use during feature development to prevent triggering restriction on setting freeze periods.
dpm force-network-logs: makes all network logs available to the DPC and triggers DeviceAdminReceiver.onNetworkLogsAvailable() if needed.
dpm force-security-logs: makes all security logs available to the DPC and triggers DeviceAdminReceiver.onSecurityLogsAvailable() if needed.
usage: dpm mark-profile-owner-on-organization-owned-device: [ --user <USER_ID> | current ]
Error: Bad admin: ComponentInfo{com.example.deviceowner/com.example.deviceowner.MyDeviceAdminReceiver}
当我先使用adb shell进入手机命令行,再使用dpm命令时就不会了
2. 经典错误 java.lang.IllegalStateException: Not allowed to set the device owner because there are already some accounts on the device
java.lang.IllegalStateException: Not allowed to set the device owner because there are already some accounts on the device
at android.os.Parcel.createExceptionOrNull(Parcel.java:2381)
at android.os.Parcel.createException(Parcel.java:2357)
at android.os.Parcel.readException(Parcel.java:2340)
at android.os.Parcel.readException(Parcel.java:2282)
at android.app.admin.IDevicePolicyManagerStubStubStubProxy.setDeviceOwner(IDevicePolicyManager.java:8665)
at mands.dpm.Dpm.runSetDeviceOwner(Dpm.java:203)
at mands.dpm.Dpm.onRun(Dpm.java:115)
at com.android.internal.os.BaseCommand.run(BaseCommand.java:60)
at mands.dpm.Dpm.main(Dpm.java:41)
at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:438)
这个错误很常见,简单说就是这个DeviceOwner本来就只是希望在新的测试设备上使用,所有不能有其他账户和他绑定。
具体解释可以看 Not allowed to set the device owner because there are already several users on the device
“设备所有者只能在未配置的设备上设置,除非它是由“adb”启动的,在这种情况下,如果没有与设备关联的帐户,我们会允许它”。因此,在使用 dpm 命令之前,请确保您没有与当前用户集关联的任何帐户(例如 Gmail) “
关于账号处理问题,我有看到别人比较详细的处理方法总结:
adb設置DeviceOwner發生問題
下面记录一下我自己采用的方法,就是简单直接全部删除了,本来我想备份一下来着,但是没成果。
I. 删掉多的userInfo
adb shell pm list users
Notallowedtosetthedevice owner because there are already some accounts on thedevice
删掉除了0以外的账户
adb shell pm remove-user 999
II. 删掉account
adb shell dunmpsys account
可以看到所有的账号
直接打开手机设置->里面有accounts,然后删掉就行,有些删除不了的账户可以直接卸载应用
最后再执行dpm就可以了
7. 调用deviceOwner的API
激活deviceOwner后即可调用需要的API,
我自己是使用了三种,状态栏不可用,静音、APP自动权限授予
具体有哪些函数可以去看安卓文档,功能还是很强大的
/reference/android/app/admin/DevicePolicyManager
这里给一个设置静音和状态栏不可用的样例,就是调用相应API
dpm = (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE);
dpm也是放在了onCreate中获取
private void setVolumeMuted() {boolean muted = SPUtil.getBoolean(activity, "muted", false);Logger.d("静音:" + muted);dpm.setMasterVolumeMuted(mComponentName, !muted);SPUtil.editBoolean(activity, "muted", !muted);}@TargetApi(Build.VERSION_CODES.M)private void disableStatusBar() {boolean result = dpm.setStatusBarDisabled(mComponentName,statusbar);statusbar = !statusbar;Logger.d("result:"+result+"...statusbar:"+statusbar);}
【Android】使用deviceowner 配置手机设置 (Monkey自动化测试删去状态栏 设定输入法 静音 APP自动权限授予 Kiosk模式打开)