无障碍简单实现Android钉钉自动打卡

如题,简单实现,请善待作者,底部有安卓包地址,看下配置界面:

一句话解释:利用安卓的无障碍功能,通过文字找到组件,然后触发点击事件完事。

首页代码:


class MainActivity : AppCompatActivity() {

    private var units = arrayOf<String>()
    private var unitStr = "秒"
    private var dateStr = ""
    private var timeStr = ""
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        units = resources.getStringArray(R.array.unit)
        unitStr = units[0]
        open.setOnClickListener {
            if (TextUtils.isEmpty(dateStr) || TextUtils.isEmpty(timeStr)) {
                Toast.makeText(this@MainActivity, "请选择打卡日期", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            }

            if (TextUtils.isEmpty(comp.text.toString().trim())) {
                Toast.makeText(this@MainActivity, "请输入公司名称(钉钉首页底部中间按钮的名称)", Toast.LENGTH_SHORT)
                    .show()
                return@setOnClickListener
            }
            val time =
                DateUtils.parse("$dateStr $timeStr")
            val intervalTime = repeat.value * getUnitTime()
            var msg = "任务已启动"
            if (time <= System.currentTimeMillis()) {
                msg = "请更正打卡时间"
            } else {
                CleverService.getInstance()?.start(time, intervalTime, comp.text.toString().trim())
                date.text =
                    "${comp.text.toString().trim()}\n打卡时间:${DateUtils.format(
                        time
                    )}\n${if (intervalTime > 0) "每隔${repeat.value}${unitStr}打卡" else "不重复"}"
            }
            Toast.makeText(this@MainActivity, msg, Toast.LENGTH_SHORT).show()
        }
        dateBtn.setOnClickListener {
            showDatePickDlg()
        }

        timeBtn.setOnClickListener {
            showTimePickDlg()
        }

        setRepeatPicker()
        setUnit()
        requestPermissions(
            arrayOf(
                Manifest.permission.WRITE_EXTERNAL_STORAGE
            ), 200
        )
    }

    fun getUnitTime(): Long {
        return when (unitStr) {
            "天" -> 24 * 60 * 60 * 1000L
            "时" -> 60 * 60 * 1000L
            "分" -> 60 * 1000L
            "秒" -> 1000L
            else -> 0
        }
    }

    fun setRepeatPicker() {
        repeat.maxValue = 60
        repeat.minValue = 0
        repeat.setOnValueChangedListener { picker, oldVal, newVal ->

        }
    }

    fun setUnit() {
        unit.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
            override fun onNothingSelected(parent: AdapterView<*>?) {
            }

            override fun onItemSelected(
                parent: AdapterView<*>?,
                view: View?,
                position: Int,
                id: Long
            ) {
                unitStr = units[position]
            }

        }
    }

    fun showDatePickDlg() {
        val calendar: Calendar = Calendar.getInstance()
        val datePickerDialog = DatePickerDialog(
            this,
            OnDateSetListener { view, year, monthOfYear, dayOfMonth ->
                dateStr = "$year-${monthOfYear + 1}-$dayOfMonth"
                dateBtn.text = dateStr
            },
            calendar.get(Calendar.YEAR),
            calendar.get(Calendar.MONTH),
            calendar.get(Calendar.DAY_OF_MONTH)
        )
        datePickerDialog.show()
    }

    fun showTimePickDlg() {
        val calendar: Calendar = Calendar.getInstance()
        val datePickerDialog = TimePickerDialog(
            this,
            TimePickerDialog.OnTimeSetListener { view, hourOfDay, minute ->
                timeStr = "$hourOfDay:$minute"
                timeBtn.text = timeStr
            },
            calendar.get(Calendar.HOUR_OF_DAY),
            calendar.get(Calendar.MINUTE), true
        )
        datePickerDialog.show()
    }

    override fun onResume() {
        super.onResume()
        //全局弹窗
//        checkDialogPermission()
        checkAccess()
    }

    var dialog: AlertDialog? = null
    private fun checkAccess() {
        Log.e("Main", "checkAccess")
        if (Build.VERSION_CODES.N <= Build.VERSION.SDK_INT && CleverService.getInstance() == null && (dialog == null || !dialog!!.isShowing)) {
            dialog = AlertDialog.Builder(this)
                .setTitle("提示")
                .setMessage("应用功能需要获取无障碍权限,请设置")
                .setPositiveButton("去设置") { dialog, which ->
                    if (CleverService.getInstance() == null) {
                        val intentAccess =
                            Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
                        startActivityForResult(
                            intentAccess,
                            Constants.REQUEST_CODE_ACCESS_PERMISSION
                        )
                    }
                    dialog.dismiss()
                }.create();
            dialog?.setCancelable(false)
            dialog?.setCanceledOnTouchOutside(false)
            dialog?.show()
        } else {

        }
    }

    private fun checkDialogPermission() {
        if (Build.VERSION_CODES.M <= Build.VERSION.SDK_INT && !Settings.canDrawOverlays(this)) {
            val dialog = AlertDialog.Builder(this)
                .setTitle("提示")
                .setMessage("应用功能需要获取弹窗权限,请设置")
                .setPositiveButton("去设置") { dialog, which -> //启动Activity让用户授权
                    val intent = Intent(
                        Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                        Uri.parse("package:$packageName")
                    )
                    startActivityForResult(intent, Constants.REQUEST_CODE_OVERLAY_PERMISSION)
                    dialog.dismiss()
                }.create();
            dialog.setMessage("应用功能需要获取弹窗权限,请设置")
            dialog.setCancelable(false)
            dialog.setCanceledOnTouchOutside(false)
            dialog.show()
        } else {
        }
    }


    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        if (requestCode == 200) {
            var granted = true
            for (i in grantResults) {
                if (i != PackageManager.PERMISSION_GRANTED) {
                    granted = false
                    break
                }
            }
            if (granted) {
                //无障碍
                checkAccess()
            } else {
                finish()
            }
        }
    }
}

打卡服务:

class CleverService : AccessibilityService() {
    companion object {
        const val TAG: String = "CleverService"
        const val NOTIFICATION_ID = 1
        private var mInstance: CleverService? = null

        fun getInstance(): CleverService? {
            return mInstance;
        }
    }

    private var mBuilder: NotificationCompat.Builder? = null
    private var mNotificationManager: NotificationManager? = null
    var openClock: Boolean = false

    var intervalMillis: Long = 0//重复间隔
    var nextClockTimeInMillis: Long = 0
    var name: String = ""

    private fun cancelNotification() {
        if (mNotificationManager == null) {
            mNotificationManager =
                getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        }
        mNotificationManager!!.cancel(NOTIFICATION_ID)
    }

    private fun showNotification() {
        mNotificationManager =
            getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager?
        if (mBuilder == null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                val mChannel = NotificationChannel(
                    "muyu",
                    "闹钟",
                    NotificationManager.IMPORTANCE_HIGH
                )
                mBuilder =
                    NotificationCompat.Builder(this, "muyu")
                mNotificationManager?.createNotificationChannel(mChannel)
            } else {
                mBuilder = NotificationCompat.Builder(this, "muyu")
            }
            mBuilder?.setSmallIcon(R.mipmap.ic_launcher)
        }
        val mIntent = Intent(Constants.ACTION_CLOCK_STOP_RECEIVER)
        val pendingIntent = PendingIntent.getBroadcast(
            this,
            0,
            mIntent,
            PendingIntent.FLAG_CANCEL_CURRENT
        )
        mBuilder?.setContentIntent(pendingIntent)
        mBuilder?.setDeleteIntent(pendingIntent)
        mBuilder?.setAutoCancel(true)
        //        mBuilder.setFullScreenIntent(pendingIntent, true);//设置滑动式通知
        mBuilder?.setOngoing(true)
        updateNotification("定时打卡服务,请勿取消", "通知取消后,可能导致打卡程序不能正常运行")
    }

    /**
     * @param triggerAtMillis 首次执行时间
     */
    fun start(triggerAtMillis: Long, intervalMillis: Long, name: String) {
        this.intervalMillis = intervalMillis
        this.name = name
        if (TextUtils.isEmpty(name)) return
        clock(triggerAtMillis)
    }

    private fun clock(delayMillis: Long) {
        log("启动闹钟下次打卡时间:${DateUtils.format(delayMillis)}")
        nextClockTimeInMillis = delayMillis
        val alarmManager = getSystemService(Service.ALARM_SERVICE) as AlarmManager
        val intent = Intent(Constants.ACTION_CLOCK_START_RECEIVER)
        val mPendingIntent =
            PendingIntent.getBroadcast(this, 100, intent, PendingIntent.FLAG_UPDATE_CURRENT)
        alarmManager.setExact(
            AlarmManager.RTC_WAKEUP,
            delayMillis,
            mPendingIntent
        )
        updateNotification("正在执行定时打卡任务,请勿取消", "下次打卡时间:${DateUtils.format(delayMillis)}")
    }

    fun stop() {
        val alarmManager = getSystemService(Service.ALARM_SERVICE) as AlarmManager
        val intent = Intent(Constants.ACTION_CLOCK_STOP_RECEIVER)
        val mPendingIntent =
            PendingIntent.getBroadcast(this, 101, intent, PendingIntent.FLAG_UPDATE_CURRENT)
        alarmManager.cancel(mPendingIntent)
        updateNotification("定时打卡服务,请勿取消", "")
    }

    fun updateNotification(title: String, text: String) {
        if (!TextUtils.isEmpty(title)) {
            mBuilder?.setContentTitle(title)
        }
        if (!TextUtils.isEmpty(text)) {
            mBuilder?.setContentText(text)
        }
        mNotificationManager?.notify(NOTIFICATION_ID, mBuilder?.build())
        log("[updateNotification] title: $title, text: $text")
    }

    val clockReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            if (intent?.action == Constants.ACTION_CLOCK_START_RECEIVER) {
                log("时间到,开始打卡")
                if (intervalMillis > 0) {//重复
                    clock(System.currentTimeMillis() + intervalMillis)
                }
                openClock = true
                restartApp(
                    "com.alibaba.android.rimet",
                    "com.alibaba.android.rimet.biz.LaunchHomeActivity"
                )
            } else {
                log("停止服务")
                openClock = false
            }
        }
    }

    private fun restartApp(pkg: String, cls: String) {
        log("[restartApp]")
        kill(pkg)
        val timer = Timer()
        timer.schedule(object : TimerTask() {
            override fun run() {
                start(pkg, cls)
            }
        }, 500)
    }

    private fun start(pkg: String, cls: String) {
        log("[start]")
        try {
            val newIntent = Intent(Intent.ACTION_MAIN)
            val componentName = ComponentName(pkg, cls)
            newIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
            newIntent.component = componentName
            startActivity(newIntent)
        } catch (e: ActivityNotFoundException) {
            e.printStackTrace()
            Toast.makeText(this, "请检查是否安装钉钉", Toast.LENGTH_SHORT).show()
        }
    }

    private fun kill(pkg: String) {
        val activityManager =
            getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
        activityManager.killBackgroundProcesses(pkg)
    }

    override fun onServiceConnected() {
        super.onServiceConnected()
        log("[onServiceConnected]")
        mInstance = this;
        val filter = IntentFilter(Constants.ACTION_CLOCK_START_RECEIVER)
        filter.addAction(Constants.ACTION_CLOCK_STOP_RECEIVER)
        registerReceiver(clockReceiver, filter)
        showNotification()
    }

    override fun onDestroy() {
        super.onDestroy()
        log("[onDestroy]")
        mInstance = null
        unregisterReceiver(clockReceiver)
    }

    override fun onInterrupt() {
    }

    override fun onAccessibilityEvent(event: AccessibilityEvent?) {
        if (!openClock) return
        when (event?.eventType) {
            AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED -> {
                rootInActiveWindow?.let {
                    findChild(it)
                }
            }
        }
    }

    fun log(msg: Any) {
        Log.e(TAG, "$msg")
//        Logger.getInstance().log(
//            msg = "$TAG: $msg"
//        )
    }

    val strs = arrayListOf<String>("钉钉", "XX工作", "考勤打卡", "上班09:00")
    fun findChild(info: AccessibilityNodeInfo) {
        val count = info.childCount
        for (i in 0 until count) {
            val childInfo = info.getChild(i)
            val rect = Rect()
            val text = childInfo?.text?.toString()
//            if (strs.contains(text)) {
//                log((text ?: "") + ",可见性:" + childInfo?.isVisibleToUser)
//            }
            if ("钉钉" == text) {
                log(
                    "第 $i 个控件的子控件数:" + text + "," + childInfo.getBoundsInScreen(rect) + ", rect = " + rect + ", parent = " + childInfo.parent.childCount
                )
                val performResult = childInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK)
                log("钉钉 performResult = $performResult")
            } else if (name == text && childInfo.isVisibleToUser) {
                log(
                    "第 $i 个控件的子控件数:" + text + "," + childInfo.getBoundsInScreen(rect) + ", rect = " + rect + ", parent = " + childInfo.parent.childCount
                )
                val performResult =
                    childInfo.parent?.performAction(AccessibilityNodeInfo.ACTION_CLICK)
                log("$name performResult = $performResult")
            } else if ("考勤打卡" == text && childInfo.isVisibleToUser) {
                log(
                    "第 $i 个控件的子控件数:" + childInfo.text + "," + childInfo.getBoundsInScreen(
                        rect
                    ) + ", rect = " + rect + ", parent = " + childInfo.parent.childCount
                )
                val performResult =
                    childInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK)
                log("考勤打卡 performResult = $performResult")
            } else if ("下班打卡" == text && childInfo.isVisibleToUser) {
                log(
                    "第 $i 个控件的子控件数:" + childInfo.text + "," + childInfo.getBoundsInScreen(
                        rect
                    ) + ", rect = " + rect + ", parent = " + childInfo.parent.childCount
                )
                val performResult =
                    childInfo.parent?.performAction(AccessibilityNodeInfo.ACTION_CLICK) ?: false
                log("下班打卡 performResult = $performResult")
                openClock = false//打卡完毕重置
                updateNotification("下班打卡成功", "下次打卡时间:${DateUtils.format(nextClockTimeInMillis)}")
            } else if ("上班打卡" == text && childInfo.isVisibleToUser) {
                log(
                    "第 $i 个控件的子控件数:" + childInfo.text + "," + childInfo.getBoundsInScreen(
                        rect
                    ) + ", rect = " + rect + ", parent = " + childInfo.parent.childCount
                )
                val performResult =
                    childInfo.parent?.performAction(AccessibilityNodeInfo.ACTION_CLICK) ?: false
                log("上班打卡 performResult = $performResult")
                openClock = false//打卡完毕重置
                updateNotification("上班打卡成功", "下次打卡时间:${DateUtils.format(nextClockTimeInMillis)}")
            }

            if (childInfo != null) {
                findChild(childInfo)
            }
        }
    }

}

代码很简单,就不再详细解释说明了,跑起来试试吧,安装包见代码

代码地址

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr大伟哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值