Skip to content

Commit

Permalink
重构支持 uiautomator
Browse files Browse the repository at this point in the history
  • Loading branch information
Krosxx committed Apr 26, 2023
1 parent 4fe450b commit 549c4ba
Show file tree
Hide file tree
Showing 57 changed files with 2,458 additions and 487 deletions.
17 changes: 17 additions & 0 deletions .idea/deploymentTargetDropDown.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions .idea/jarRepositories.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions accessibility-api/build.gradle
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'org.jetbrains.kotlin.android'

android {
compileSdkVersion 33
buildToolsVersion "30.0.3"
compileSdk 33

defaultConfig {
minSdkVersion 16
Expand All @@ -26,11 +26,12 @@ android {

dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.core:core-ktx:1.10.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.jakewharton.timber:timber:5.0.1'

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'

api project(":core")
}
Original file line number Diff line number Diff line change
@@ -1,39 +1,51 @@
@file:Suppress("unused")

package cn.vove7.andro_accessibility_api

import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.GestureDescription
import android.annotation.SuppressLint
import android.content.Context
import android.view.View
import android.graphics.Bitmap
import android.os.Build
import android.os.Handler
import android.util.SparseArray
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import cn.vove7.andro_accessibility_api.api.BaseServiceApi
import cn.vove7.andro_accessibility_api.utils.NeedAccessibilityException
import cn.vove7.andro_accessibility_api.utils.jumpAccessibilityServiceSettings
import cn.vove7.andro_accessibility_api.utils.whileWaitTime
import cn.vove7.andro_accessibility_api.viewnode.ViewNode
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import android.view.accessibility.AccessibilityWindowInfo
import androidx.annotation.RequiresApi
import cn.vove7.auto.core.AppScope
import cn.vove7.auto.core.AutoApi
import cn.vove7.auto.core.OnPageUpdate
import cn.vove7.auto.core.PageUpdateMonitor
import cn.vove7.auto.core.utils.AutoGestureDescription
import cn.vove7.auto.core.utils.convert
import cn.vove7.auto.core.utils.jumpAccessibilityServiceSettings
import cn.vove7.auto.core.utils.whileWaitTime
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
import kotlin.math.min
import cn.vove7.auto.core.utils.GestureResultCallback as GestureCallback

/**
*
*
* Created by Vove on 2018/6/18
*/
@Suppress("MemberVisibilityCanBePrivate")
abstract class AccessibilityApi : AccessibilityService(), BaseServiceApi {
// implement of BaseServiceApi
override val _baseService: AccessibilityService get() = this
abstract class AccessibilityApi : AccessibilityService(), AutoApi {

abstract val enableListenPageUpdate: Boolean

abstract val enableListenAppScope: Boolean
override fun performAction(action: Int) = this.performGlobalAction(action)
override fun rootInActiveWindow() = rootInActiveWindow

var currentScope: AppScope? = null
private set
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun windows(): List<AccessibilityWindowInfo>? = windows

// activity or dialog
var currentPage: String? = null
private set
get() = currentScope?.packageName
private val pageListener: OnPageUpdate = ::onPageUpdate

override fun onServiceConnected() {
if (this::class.java == BASE_SERVICE_CLS) {
Expand All @@ -42,10 +54,14 @@ abstract class AccessibilityApi : AccessibilityService(), BaseServiceApi {
if (isEnableGestureService() && this::class.java == GESTURE_SERVICE_CLS) {
gestureService = this
}
registerImpl()
PageUpdateMonitor.enableListenPageUpdate = enableListenPageUpdate
PageUpdateMonitor.addOnPageUpdateListener(pageListener)
}

override fun onDestroy() {
super.onDestroy()
PageUpdateMonitor.removeOnPageUpdateListener(pageListener)
if (this::class.java == BASE_SERVICE_CLS) {
baseService = null
}
Expand All @@ -54,14 +70,6 @@ abstract class AccessibilityApi : AccessibilityService(), BaseServiceApi {
}
}

/**
* ViewNode with rootInActiveWindow
*/
val activeWinNode: ViewNode? get() = ViewNode.activeWinNode()

// 适应 多窗口 分屏
val rootNodeOfAllWindows get() = ViewNode.getRoot()

override fun getRootInActiveWindow(): AccessibilityNodeInfo? {
return try {
super.getRootInActiveWindow()
Expand All @@ -71,61 +79,40 @@ abstract class AccessibilityApi : AccessibilityService(), BaseServiceApi {
}
}

/**
* 更新当前[currentScope]
* @param pkg String
* @param pageName String Activity or Dialog
*/
private fun updateCurrentApp(pkg: String, pageName: String) {
if (currentScope?.packageName == pkg && pageName == currentScope?.pageName) {
return
}
if (
pageName.startsWith("android.widget") ||
pageName.startsWith("android.view") ||
pageName.startsWith("android.inputmethodservice") ||
pageIsView(pageName)
) {
return
}

currentScope = currentScope?.also {
it.pageName = pageName
it.packageName = pkg
} ?: AppScope(pkg, pageName)

onPageUpdate(currentScope!!)
}

/**
* Activity or Dialog update
* @param currentScope AppScope
*/
open fun onPageUpdate(currentScope: AppScope) {}

private fun pageIsView(pageName: String): Boolean = try {
View::class.java.isAssignableFrom(Class.forName(pageName))
} catch (e: ClassNotFoundException) {
false
}

/**
* @param event AccessibilityEvent?
*/
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
if (!enableListenAppScope) return
event ?: return

if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
// 界面切换
val classNameStr = event.className
val pkg = event.packageName as String?
if (!classNameStr.isNullOrBlank() && pkg != null) {
GlobalScope.launch {
updateCurrentApp(pkg, classNameStr.toString())
if (!enableListenPageUpdate || event == null) return
PageUpdateMonitor.onAccessibilityEvent(event)
}

override fun takeScreenshot(): Bitmap? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return runBlocking {
suspendCoroutine<Bitmap> { cont ->
super.takeScreenshot(0, appCtx.mainExecutor, object : TakeScreenshotCallback {
override fun onSuccess(screenshot: ScreenshotResult) {
val bitmap = Bitmap.wrapHardwareBuffer(screenshot.hardwareBuffer, screenshot.colorSpace)
if (bitmap != null) {
cont.resume(bitmap)
}
}

override fun onFailure(errorCode: Int) {
cont.resumeWithException(RuntimeException("takeScreenshot failed, code: $errorCode"))
}
})
}
}
}
return null
}

override fun onInterrupt() {
Expand Down Expand Up @@ -177,7 +164,7 @@ abstract class AccessibilityApi : AccessibilityService(), BaseServiceApi {
}

// currentAppScope
val currentScope get() = baseService?.currentScope
val currentScope get() = AutoApi.currentScope

// Service is enable
val isBaseServiceEnable: Boolean
Expand Down Expand Up @@ -229,4 +216,61 @@ abstract class AccessibilityApi : AccessibilityService(), BaseServiceApi {

}

override suspend fun doGesturesAsync(gesture: AutoGestureDescription, callback: GestureCallback?, handler: Handler?) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
throw IllegalStateException("dispatchGesture require android N+")
}
requireGesture.dispatchGesture(gesture.convert(), callback?.let { cb ->
@RequiresApi(Build.VERSION_CODES.N)
object : GestureResultCallback() {
override fun onCompleted(gestureDescription: GestureDescription?) {
cb.onCompleted(gesture)
}

override fun onCancelled(gestureDescription: GestureDescription?) {
cb.onCancelled(gesture)
}
}
}, handler)
}

@RequiresApi(Build.VERSION_CODES.R)
override fun windowsOnAllDisplays(): SparseArray<List<AccessibilityWindowInfo>> {
return requireBase.windowsOnAllDisplays
}
}


fun requireBaseAccessibility(autoJump: Boolean = false) {
AccessibilityApi.requireBaseAccessibility(autoJump)
}

suspend fun waitBaseAccessibility(waitMillis: Long = 30000) {
AccessibilityApi.waitAccessibility(waitMillis, AccessibilityApi.BASE_SERVICE_CLS)
}

fun requireGestureAccessibility(autoJump: Boolean = false) {
AccessibilityApi.requireGestureAccessibility(autoJump)
}

suspend fun waitGestureAccessibility(waitMillis: Long = 30000) {
AccessibilityApi.waitAccessibility(waitMillis, AccessibilityApi.GESTURE_SERVICE_CLS)
}

suspend fun waitAccessibility(waitMillis: Long = 30000, cls: Class<*>): Boolean {
return AccessibilityApi.waitAccessibility(waitMillis, cls)
}


/**
* 无障碍服务未运行异常
* @constructor
*/
open class NeedAccessibilityException(name: String?) : RuntimeException("无障碍服务未运行: $name")

class NeedBaseAccessibilityException :
NeedAccessibilityException(AccessibilityApi.BASE_SERVICE_CLS.name)

class NeedGestureAccessibilityException :
NeedAccessibilityException(AccessibilityApi.GESTURE_SERVICE_CLS.name)

This file was deleted.

Loading

0 comments on commit 549c4ba

Please sign in to comment.