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

如何检测android用户使用的自动点击应用程序?,android,android-studio,automation,detect,fraud-prevention,Android,Android Studio,Automation,Detect,Fraud Prevention,一些android用户使用play store上的自动点击器应用程序多次点击 所以我想检测/阻止试图使用自动点击器的用户。android中有没有检测此类用户的方法?如果有,那么我们如何检测这些用户?没有API来检测自动点击器是否正在运行。所有自动单击器都使用辅助功能服务来模拟单击,并且有一个API允许您检测是否有任何辅助功能服务正在运行。问题是,这些服务还包括屏幕阅读器和其他对残疾人有用的工具。你绝对不应该仅仅因为用户正在使用辅助功能服务就阻止他们 但是,您也可以检测到“不自然”的点击和手势。没

一些android用户使用play store上的自动点击器应用程序多次点击


所以我想检测/阻止试图使用自动点击器的用户。android中有没有检测此类用户的方法?如果有,那么我们如何检测这些用户?

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

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

当您检测到该用户是
a) 使用辅助功能服务
b) 执行非自然的单击
你可以合理地假设他使用的是自动舔食器。然后你可以阻止他的触摸或者做任何你想做的事情

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

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

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时,请记住,即使对于autoclicker,报告的坐标和时间也可能会带来很小的误差。例如,您可以使用以下实现:

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
    }

}

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

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

当您检测到该用户是
a) 使用辅助功能服务
b) 执行非自然的单击
你可以合理地假设他使用的是自动舔食器。然后你可以阻止他的触摸或者做任何你想做的事情

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

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

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时,请记住,即使对于autoclicker,报告的坐标和时间也可能会带来很小的误差。例如,您可以使用以下实现:

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
    }

}

我做了一个修改,将初始化手势开始移动到向上的动作中,并删除向下的动作部分。不确定为什么不调用ACTION\u DOWN。我做了一个修改,将initialize gestureStart移动到ACTION\u UP并删除ACTION\u DOWN部分。不知道为什么不叫行动。