如题,简单实现,请善待作者,底部有安卓包地址,看下配置界面:
一句话解释:利用安卓的无障碍功能,通过文字找到组件,然后触发点击事件完事。
首页代码:
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)
}
}
}
}
代码很简单,就不再详细解释说明了,跑起来试试吧,安装包见代码