Android Accessibility

fulvio-ambrosanio-733582-unsplash_meitu_1

公司启用钉钉打卡了,刚开始还挺不适应,总是忘记打卡。

所以就想着,这打卡能不能实现自动化,每天都要记住打卡这个动作,一点儿也不猿。


首先,分析一下,钉钉打卡,必须要在公司附近的范围内,其次,只能是拥有 GPS 定位的钉钉 APP 才行,所以,公司 WiFi 的连接断开,刚好可以作为上下班打卡的时机。

钉钉的打卡页面肯定不会允许第三方应用打开,因此要实现自动打卡功能,肯定需要模拟用户发出点击事件。

能模拟点击事件的,首先想到了Android 辅助功能


AccessibilityService

辅助功能(AccessibilityService)是 Android 系统提供给的一种服务,本身是继承 Service 类的。这个服务提供了增强的用户界面,旨在帮助残障人士或者可能暂时无法与设备充分交互的人们。

当然,现在 AccessibilityService 已经基本偏离了它设计的初衷。

借助 AccessibilityService ,可以实现对页面的监听及模拟点击控制等。


基本使用

使用 AccessibilityService 实际上只需要以下三步即可:

1.继承 AccessibilityService
class AutoPunchCardService : AccessibilityService(){
    //可选。系统会在成功连接上你的服务的时候调用这个方法,在这个方法里你可以做一下初始化工作,
    //例如设备的声音震动管理,也可以调用setServiceInfo()进行配置工作
    override fun onServiceConnected() {
        super.onServiceConnected()
        LogUtils.d("onServiceConnected")
    }

    //必须。这个在系统想要中断AccessibilityService返给的响应时会调用。在整个生命周期里会被调用多次。
    override fun onInterrupt() {
        LogUtils.d("onInterrupt")
    }

    //通过这个函数可以接收系统发送来的AccessibilityEvent,
    //接收来的AccessibilityEvent是经过过滤的,过滤是在配置工作时设置的。
    override fun onAccessibilityEvent(event: AccessibilityEvent) {
        LogUtils.d("事件--> $event.eventType ,app包名--> $event.packageName")
        when (event.eventType) {
            AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED  //收到通知栏消息
            -> LogUtils.d("=== 收到通知栏消息")
            AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED    //界面状态改变
            -> LogUtils.d("=== 界面状态改变," + event.toString())
            AccessibilityEvent.TYPE_VIEW_CLICKED   //点击事件
            ->  LogUtils.d("=== 点击事件" + event.toString())
            AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT //文本改变
            -> LogUtils.d("=== 文本改变")
        }//省略其他的一堆可以监听的事件

    }

    //可选。在系统将要关闭这个AccessibilityService会被调用。在这个方法中进行一些释放资源的工作
    override fun onUnbind(intent: Intent?): Boolean {
        LogUtils.d("onUnbind")
        return super.onUnbind(intent)
    }
}


2.新建配置文件

在资源目录 res 下新建 xml 文件夹,新建 accessibility_service_config.xml文件

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/accessibility_service_description"
    android:packageNames="com.alibaba.android.rimet"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFlags="flagDefault"
    android:accessibilityFeedbackType="feedbackSpoken"
    android:notificationTimeout="100"
    android:canRetrieveWindowContent="true"
 android:settingsActivity="com.example.android.accessibility.ServiceSettingsActivity"/>

其中 description 是描述;

packageNames 是要监控的 APP 包名;

accessibilityEventTypes 指监控的的事件,typeAllMask  /  AccessibilityEvent.TYPES_ALL_MASK:全局事件响应


3.AndroidMainifest 中注册
<service
    android:name=".AutoPunchCardService"
    android:description="@string/accessibility_service_description"
    android:label="@string/accessibility_service_label"
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
    <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService"/>
    </intent-filter>

    <meta-data
        android:name="android.accessibilityservice"
        android:resource="@xml/accessibility_service_config"/>
</service>

这样基本上就算配置完成了,不过,想要运行起来,还得用户手动开启辅助设置。


相关方法

1.服务是否开启

AccessibilityService 的服务想要运行,就得让用户手动开启它,那如何判断开启呢?

这需要通过下面的方法:

private fun isAccessibilitySettingsOn(context: Context): Boolean {
    val service = context.packageName + File.separator + AutoPunchCardService::class.java.canonicalName
    val accessibilityEnabled = Settings.Secure.getInt(context.contentResolver,
        Settings.Secure.ACCESSIBILITY_ENABLED)
    val splitter = TextUtils.SimpleStringSplitter(':')
    if (accessibilityEnabled != 1) return false
    val value = Settings.Secure.getString(context.contentResolver,
        Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES) ?: return false
    splitter.setString(value)
    return splitter.contains(service)
}
2.跳转到无障碍设置界面

如果通过判断,发现用户没有开启,就得让用户去打开它, 无障碍设置界面 的设置一般非常深,用户难以到达,这时就得直接打开 无障碍设置界面 的设置页面了。

startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))


模拟点击事件

经过上面的步骤,此时可以正常使用 AccessibilityService,现在来模拟点击事件。

首先,需要寻找界面元素信息,

AccessibilityNodeInfo rootNode = getRootInActiveWindow() 

此方法可以获取当前 Activity 的根节点窗口信息,再通过下面两种方式获取具体的节点信息。

//通过文字,获取控件信息
List<AccessibilityNodeInfo> list = rootNode.findAccessibilityNodeInfosByText("工作");
//通过 id ,获取控件信息,注意 ID 的格式
List<AccessibilityNodeInfo> list = rootNode.findAccessibilityNodeInfosByViewId("com.alibaba.android.rimet:id/home_bottom_tab_button_work");

文本内容很容易获取,看界面就知道,但有时候文本控件不可点击,需要点击父控件,因此文本点击一般这么写:

    private fun click(viewText: String): Boolean {
        val nodeInfo = rootInActiveWindow
        try {//点击前滞留1s
            Thread.sleep(1000)
        } catch (e: InterruptedException) {
            e.printStackTrace()
        }
        if (nodeInfo == null) {
            LogUtils.d("点击失败,rootWindow为空")
            return false
        }

        val list = nodeInfo.findAccessibilityNodeInfosByText(viewText)
        if (list.isEmpty()) {//没有该文字的控件
            LogUtils.d("点击失败," + viewText + "控件列表为空")
            return false
        } else {
            for (info in list) {
                if (viewText == info?.text?.toString()) {
                    return onclick(info)  //遍历点击
                }
            }
            return false
        }
    }

    private fun onclick(view: AccessibilityNodeInfo): Boolean {
        if (view == null) {
            LogUtils.d("node 为空无法点击")
            return false
        }
        if (view.isClickable) {
            view.performAction(AccessibilityNodeInfo.ACTION_CLICK)
            LogUtils.d("view name" + view.className + "+点击成功")
            return true
        } else {
            if (view.parent == null) {
                return false
            }
            return onclick(view.parent)
        }
    }

ViewId 一般需要用到工具 Android Device Monitor 来查看,目前,这个工具在 AS 3.1中打不开了,

需要到 Android SDK/tools/monitor 运行,通过 Hierarchy View 查看 ID 后,需要注意 ID 是书写格式,前面有包名。
最后,模拟点击事件

nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK)

还可以模拟 Home,Back 键

//后退键
performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
//Home键
performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME);
//模拟左滑
performGlobalAction(AccessibilityService.GESTURE_SWIPE_LEFT);


后记

AccessibilityService 确实强大,网上一些微信抢红包,答题负责工具,都是借此完成。

但它不是万能的,钉钉上的打卡页面是 WebView ,因此无法完成节点寻找,更不可能模拟点击事件。

因此,想通过模拟点击来完成打卡, AccessibilityService 无法胜任,最后,还是得 Root 。

通过 Root 后执行 shell 命令来完成模拟点击事件。


设置页面的常量表

为了方便,这里列出设置界面所有的 Action 常量

常量字段示意
ACTION_SETTINGS系统设置界面
ACTION_APN_SETTINGSAPN设置界面
ACTION_LOCATION_SOURCE_SETTINGS定位设置界面
ACTION_AIRPLANE_MODE_SETTINGS更多连接方式设置界面
ACTION_DATA_ROAMING_SETTINGS双卡和移动网络设置界面
ACTION_ACCESSIBILITY_SETTINGS无障碍设置界面
ACTION_SYNC_SETTINGS同步设置界面
ACTION_ADD_ACCOUNT添加账户界面
ACTION_NETWORK_OPERATOR_SETTINGS选取运营商的界面
ACTION_SECURITY_SETTINGS安全设置界面
ACTION_PRIVACY_SETTINGS备份重置设置界面
ACTION_VPN_SETTINGSVPN设置界面,可能不存在
ACTION_WIFI_SETTINGS无线网设置界面
ACTION_WIFI_IP_SETTINGSWIFI的IP设置
ACTION_BLUETOOTH_SETTINGS蓝牙设置
ACTION_CAST_SETTINGS投射设置
ACTION_DATE_SETTINGS日期时间设置
ACTION_SOUND_SETTINGS声音设置
ACTION_DISPLAY_SETTINGS显示设置
ACTION_LOCALE_SETTINGS语言设置
ACTION_VOICE_INPUT_SETTINGS辅助应用和语音输入设置
ACTION_INPUT_METHOD_SETTINGS语言和输入法设置
ACTION_USER_DICTIONARY_SETTINGS个人字典设置界面
ACTION_INTERNAL_STORAGE_SETTINGS存储空间设置的界面
ACTION_SEARCH_SETTINGS搜索设置界面
ACTION_APPLICATION_DEVELOPMENT_SETTINGS开发者选项设置
ACTION_DEVICE_INFO_SETTINGS手机状态信息的界面
ACTION_DREAM_SETTINGS互动屏保设置的界面
ACTION_NOTIFICATION_LISTENER_SETTINGS通知使用权设置的界面
ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS勿扰权限设置的界面
ACTION_CAPTIONING_SETTINGS字幕设置的界面
ACTION_PRINT_SETTINGS打印设置界面
ACTION_BATTERY_SAVER_SETTINGS节电助手界面
ACTION_HOME_SETTINGS主屏幕设置界面


参考

Android AccessibilityService使用注意

(AccessibilityService) Android 辅助功能笔记

Android Accessibility辅助功能类的学习

AccessibilityService从入门到出轨

微信检查被删好友(Android Accessibility 学习实践 )

AutoJS是一个基于JavaScript的Android自动化工具,它可以帮助我们自动化各种Android设备上的操作,包括模拟按键、触摸、滑动、截图等。而钉钉是一款流行的企业办公软件,用于员工考勤和工作通讯。 想要用AutoJS实现钉钉打卡,我们首先需要安装AutoJS应用到我们的Android设备上,并将设备连接到电脑。然后,在AutoJS的IDE中编写打卡脚本。 实现钉钉打卡的脚本大致分为以下几个步骤: 1. 启动钉钉应用:可以使用AutoJS提供的shell函数执行启动钉钉应用的命令。 2. 登录钉钉账号:我们可以通过模拟按键的方式,将登录页面的用户名和密码输入框填入正确的账号信息,并模拟点击登录按钮。 3. 进入打卡界面:脚本可以通过模拟触摸的方式滑动屏幕,将钉钉应用的主页面滑动到打卡入口位置,并模拟点击进入打卡界面。 4. 进行打卡操作:在打卡界面,可以使用模拟点击的方式找到打卡按钮,并点击进行打卡操作。 5. 结束打卡流程:打卡完成后,可以退出钉钉应用,或者返回到主页面,等待下一次打卡。 需要注意的是,由于钉钉的安全机制,可能会对自动化操作进行检测和阻止,因此我们需要在编写脚本时尽量模拟人的行为,避免被发现。 AutoJS提供了丰富的操作API,可以用于实现更加复杂的自动化任务。通过编写脚本,我们可以让AutoJS帮助我们完成繁琐的打卡操作,提高工作效率。但是我们需要遵守公司的规定和要求,确保合法合规使用AutoJS,以免引起不必要的麻烦。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值