如何检测 android 用户使用的自动点击器应用程序?

How to detect auto clicker app used by android user?

一些 android 用户使用 Play 商店中提供的自动点击器应用程序进行多次点击。

所以我想 detect/block 尝试使用自动 clicker.Is 的用户 android 有什么方法可以检测到此类用户?如果是,那么我们如何检测这些用户?

没有 API 来检测自动点击器是否为 运行。所有自动点击器都使用无障碍服务来模拟点击,并且有一个 API 允许您检测是否有任何无障碍服务 运行。问题是,这些服务还包括屏幕阅读器和其他对残疾人有用的工具。您绝对不应该仅仅因为用户使用无障碍服务就阻止他们。

但是,您也可以检测到“不自然”的点击和手势。没有人可以在屏幕上从完全相同的点开始执行多个手势,在完全相同的点结束并在每个手势上花费完全相同的时间。

当您检测到该用户是
a) 使用无障碍服务
b) 执行不自然的点击
您可以合理地假设他正在使用自动点击器。然后你可以阻止他的触摸或做任何你想做的事。

为了检测不自然的触摸,您必须分析所有传入的触摸事件。获得所有这些接触的最简单方法是覆盖根容器的 onInterceptTouchEvent 方法。

例如,创建此 class 并将其用作布局的根目录:

class AutoclickerFrameLayout @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {

    override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
        AutoclickerDetector.recordEvent(event)
        // If you return true in this method touches will be blocked
        return false
    }

}

在分析 MotionEvents 时请记住,即使对于自动点击器,报告的坐标和时间也可能略有不同。例如,您可以使用此实现:

object AutoclickerDetector {

    fun isAccessibilityServiceEnabled(context: Context): Boolean {
        val am = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
        val enabledServices = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK)
        return enabledServices.isNotEmpty()
    }

    var isDetectingSimilarGestures = false
        private set

    private data class Touch(
            val x: Float,
            val y: Float,
            val time: Long
    ) {

        fun isSimilar(other: Touch): Boolean {
            return abs(this.x - other.x) < 1.0f
                    && abs(this.y - other.y) < 1.0f
        }

    }

    private data class Gesture(
            val start: Touch,
            val end: Touch
    ) {

        val duration: Long = end.time - start.time

        fun isSimilar(other: Gesture): Boolean {
            return this.start.isSimilar(other.start)
                    && this.end.isSimilar(other.end)
                    && abs(this.duration - other.duration) < 50
        }

    }

    private var gestureStart: Touch? = null
    private val recentGestures = ArrayList<Gesture>()

    fun recordEvent(event: MotionEvent) {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                gestureStart = Touch(event.rawX, event.rawY, System.currentTimeMillis())
            }
            MotionEvent.ACTION_UP -> {
                gestureStart?.let { gestureStart ->
                    val gestureEnd = Touch(event.rawX, event.rawY, System.currentTimeMillis())
                    recentGestures.add(Gesture(gestureStart, gestureEnd))
                    trimGestureHistory()
                    checkSimilarGestures()
                }
                gestureStart = null
            }
        }
    }

    private const val HISTORY_SIZE = 20

    private fun trimGestureHistory() {
        while (recentGestures.size > HISTORY_SIZE) {
            recentGestures.removeAt(0)
        }
    }

    private fun checkSimilarGestures() {
        recentGestures.forEachIndexed { i, searchGesture ->
            var similarCount = 0
            recentGestures.forEachIndexed { j, compareGesture ->
                if (i != j && searchGesture.isSimilar(compareGesture)) {
                    similarCount++
                }
            }
            if (similarCount > 2) {
                // There is no way user can physically perform almost exactly the same gesture
                // 3 times amongst 20 last gestures
                isDetectingSimilarGestures = true
                return
            }
        }
        isDetectingSimilarGestures = false
    }

}

然后在您的主要代码中,您可以检查自动点击器当前是否正常工作:

AutoclickerDetector.isAccessibilityServiceEnabled(context) 
    && AutoclickerDetector.isDetectingSimilarGestures