跳至主要內容

UI界面与常见权限互动

约 1317 字大约 4 分钟老猫

拟解决的问题

运行脚本前,我们往往需要用户开启一些必须的权限。由于用户水平不同,造成部分人群使用困难,甚至不知道需要先开启权限,这与无障碍权限“以人为本”的初衷相违背,因此我们需要一个直观的引导界面,以及方便的跳转方案,降低沟通成本,提升用户使用体验。

解决思路

1)枚举常见权限

2)获取权限状态

3)开启或跳转到权限开启界面

4)将权限状态与ui界面关联互动

具体步骤

我们运行脚本,常见的权限主要有无障碍服务、悬浮窗权限、前台服务、无障碍稳定模式、截图权限、查看使用统计权限、后台弹出权限(小米手机的miui系统),下面将对这7个权限的状态和开启办法逐一进行讨论。

无障碍服务

无障碍服务是否开启可以通过auto.serviceauto.rootInActiveWindow获取,开启方式在上一篇博客中已经进行了详细的介绍,这里不再赘述。

悬浮窗权限

悬浮窗权限的判断autojspro已经有封装好的方法,可以直接用floaty.checkPermission()的返回值进行判断,此权限的跳转,这里介绍两种方法,一种是autojspro自带的基于context.startActivity封装的floaty.requestPermission(),用起来比较简单。另外介绍一种基于activity.startActivityForResult()封装的方法,可以在activity结束的回调里进行自己的操作,相对复杂,但是一些情形下用起来比较方便,同时也可以初步学习activity.startActivityForResult()的用法,不仅可以用在权限获取,在媒体库选取等方面也同样适用。这里仅展示原理以及相关代码,不做过多细致的讲解。

使用activity.startActivityForResult()有两个核心部分,一个发起带有标记的intent,另一个接收此标记的监听。

let mIntent = app.intent({
    action: "android.settings.action.MANAGE_OVERLAY_PERMISSION",
    data: "package:" + context.getPackageName(),
});
//这里把数字1作为标记
activity.startActivityForResult(mIntent, 1);
activity.getEventEmitter().on("activity_result", (requestCode, resultCode, data) => {
    if (requestCode == 1) {
        //requestCode为1说明是跳转到开启悬浮窗权限的activity结束的回调
        toast(floaty.checkPermission());
        //这里可以进行ui界面的同步
    }
});

前台服务

前台服务的开启和查询,官方的Settings模块都进行了很好地封装,我们可以直接使用。

查询是否开启前台服务:$settings.isEnabled(‘foreground_service’)

设置开启前台服务:$settings.setEnabled(‘foreground_service’, true);

无障碍稳定模式

无障碍有一个稳定模式,使用场景不多,这里可以看安卓文档AccessibilityServiceInfoopen in new window里的相关说明,大致意思就是不访问不重要的视图。

稳定模式的查询可以通过$settings.isEnabled('stable_mode')来查询。

设置开启稳定模式:$settings.setEnabled(‘stable_mode’, true);

截图权限

目前新版的autojspro的images模块也支持了截图权限的查询、申请和注销,使用起来也很方便

查询是否有截图权限:$images.getScreenCaptureOptions()

请求截图权限:$images.requestScreenCapture(options)

注销截图权限:$images.stopScreenCapture()

这里特别提一下,安卓11以上设备,一般可以不申请截图权限,直接使用无障碍权限可以获取到ajtojspro的image对象

安卓11+使用无障碍权限截图:$automator.takeScreenshot()

作者注:这种方式截图频繁有限制,一般是1秒一次。

查看使用统计权限

这个权限一般使用较少,但个别时候也有大作用,比如为了currentPackage()获取最近包名更准确,或者获取某一应用的运行状态。

由于官方没有提供此权限的查询办法,这里我查阅安卓文档,自行封装了一份,可以判断是否有此权限

checkSystemService("usage_stats")
function checkSystemService(service) {
    importClass(android.app.AppOpsManager);
    appOps = context.getSystemService(context.APP_OPS_SERVICE);
    mode = appOps.checkOpNoThrow("android:get_" + service, android.os.Process.myUid(), context.getPackageName());
    return (granted = mode == AppOpsManager.MODE_ALLOWED);
}

跳转直接使用intent即可

app.startActivity({
    action: "android.settings.USAGE_ACCESS_SETTINGS",
});

后台弹出权限(miui系统)

这个权限主要是会影响到app.launchPackage(packageName)open in new window打开未启动的应用,因此必要时我们可以判断是否有该权限,然后对用户进行提示,此权限由于是miui系统自己创造的,且没有文档说明,研究起来用了不少时间,这里我直接贴出来源码

checkMiuiPermission(10021);
function checkMiuiPermission(flag) {
    //flag为10021是后台弹出界面,为10016是NFC权限
    importClass(android.app.AppOpsManager);
    let appOps = context.getSystemService(context.APP_OPS_SERVICE);
    try {
        let myClass = util.java.array("java.lang.Class", 3);
        myClass[0] = java.lang.Integer.TYPE;
        myClass[1] = java.lang.Integer.TYPE;
        myClass[2] = java.lang.Class.forName("java.lang.String");
        let method = appOps.getClass().getMethod("checkOpNoThrow", myClass);
        let op = new java.lang.Integer(flag);
        result = method.invoke(appOps, op, new java.lang.Integer(android.os.Process.myUid()), context.getPackageName());
        return result == AppOpsManager.MODE_ALLOWED;
    } catch (err) {
        console.error(err);
    }
}