feat: ###

在应用程序中重构MainActivity和MainViewModel逻辑。

- 重构`MainActivity.kt`以提高代码可读性并删除不必要的导入
- 更新`MainViewModel.kt`以在`launchTargetApp`函数中传递额外参数到`findAndLaunchApp`方法调用
This commit is contained in:
lvlisong 2025-03-24 05:02:16 +08:00
parent 956ca015a0
commit 718915962d
4 changed files with 454 additions and 238 deletions

View File

@ -4,6 +4,14 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-03-23T13:13:04.988067200Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=/home/ls/.android/avd/Pixel_6_API_31.avd" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>

View File

@ -3,13 +3,10 @@ package io.sixminutes.ridicule
import android.app.ActivityManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
@ -141,7 +138,8 @@ class MainActivity : AppCompatActivity() {
windowManager.removeView(it)
isFloatingWindowVisible = false
}
// 停止后台自动点击服务
MyAccessibilityService.getInstance()?.stop()
// 强制将 MainActivity 带到前台
bringMainActivityToFront()
@ -158,6 +156,7 @@ class MainActivity : AppCompatActivity() {
}
}
// 尝试将应用的主Activity任务栈带到前台
private fun bringMainActivityToFront() {
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val tasks = activityManager.getRunningTasks(10) // 获取最近的任务列表

View File

@ -64,7 +64,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
viewModelScope.launch {
delay(1000) // 延迟 1000 毫秒,确保桌面已经显示
try {
MyAccessibilityService.getInstance()?.findAndLaunchApp("breakingnews")
MyAccessibilityService.getInstance()?.findAndLaunchApp("breakingnews", "io.sixminutes.breakingnews", "Show Inter Ad 1: 3689d2816239b64e")
} catch (e: Exception) {
Log.e("ViewModel", "Launch app failed", e)
updateState { it.copy(errorMessage = "Failed to launch app") }

View File

@ -1,59 +1,72 @@
package io.sixminutes.ridicule
import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.GestureDescription
import android.content.Intent
import android.graphics.Path
import android.graphics.Rect
import android.os.Binder
import android.os.Build
import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import android.util.Log
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import android.widget.Toast
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import kotlinx.coroutines.delay
import java.util.LinkedList
import java.util.concurrent.atomic.AtomicBoolean
class MyAccessibilityService : AccessibilityService() {
// region 伴生对象与实例管理
companion object {
private const val TAG = "AccessibilityService"
@Volatile private var instance: MyAccessibilityService? = null
private const val DEFAULT_MAX_RETRIES = 5
private const val DEFAULT_RETRY_INTERVAL = 4000L
// 使用双重校验锁保证线程安全
@Volatile
private var instance: MyAccessibilityService? = null
/**
* 获取服务实例双重校验锁实现线程安全
* @return 当前服务实例或null如果未初始化
*/
fun getInstance(): MyAccessibilityService? = instance ?: synchronized(this) {
instance ?: null
}
}
// endregion
private val binder = MyBinder()
private var targetAppPackageName: String? = null // 目标应用的包名
// region 类成员与初始化
private var targetAppName: String? = null
private var targetAppPackageName: String? = null
private var currentAppPackageName: String? = null
private var currentClassName: String? = null
private var launcherButtonText: String? = null
private val mainHandler by lazy { Handler(Looper.getMainLooper()) }
private var running = false
private val isRetrying = AtomicBoolean(false) // 防止重复执行
// 创建一个专门用于执行后台任务的 HandlerThread
private val backgroundHandlerThread = HandlerThread("RetryOperationThread").apply { start() }
private val backgroundHandler = Handler(backgroundHandlerThread.looper)
inner class MyBinder : Binder() {
fun getService(): MyAccessibilityService = this@MyAccessibilityService
}
override fun onServiceConnected() {
super.onServiceConnected()
instance = this
Log.d(TAG, "Service connected")
startMainActivitySafely() // 注意无障碍服务启动Activity需谨慎
startMainActivitySafely()
}
// endregion
// region 生命周期管理
override fun onDestroy() {
cleanUpResources()
super.onDestroy()
}
override fun onInterrupt() {
Log.w(TAG, "Service interrupted")
}
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
event?.let {
Log.v(TAG, "Event received: ${event.eventType}")
// 添加实际的事件处理逻辑
processAccessibilityEvent(event)
}
// 释放后台线程资源
backgroundHandlerThread.quitSafely()
}
override fun onUnbind(intent: Intent?): Boolean {
@ -61,14 +74,24 @@ class MyAccessibilityService : AccessibilityService() {
return super.onUnbind(intent)
}
override fun onInterrupt() {
Log.w(TAG, "Service interrupted")
}
// endregion
// region 核心功能方法
/**
* 模拟点击屏幕上的指定坐标
* @param x 点击的X坐标
* @param y 点击的Y坐标
* 模拟点击屏幕指定坐标
* @param x X轴坐标像素
* @param y Y轴坐标像素
* 实现要点
* 1. 复用Rect对象提升性能
* 2. 严格回收节点资源
* 3. 校验节点可见性和可点击性
*/
fun simulateTap(x: Int, y: Int) {
var root: AccessibilityNodeInfo? = null
val tempRect = Rect() // 复用 Rect 对象提升性能
val tempRect = Rect()
try {
root = rootInActiveWindow ?: run {
@ -78,24 +101,17 @@ class MyAccessibilityService : AccessibilityService() {
val wrappedRoot = AccessibilityNodeInfoCompat.wrap(root)
try {
val matchingNodes = findNodesByCondition(
root = wrappedRoot,
predicate = { node ->
// 正确使用 bounds 判断逻辑
node.isClickable &&
node.isVisibleToUser() &&
node.getBoundsInScreen(tempRect).let {
!tempRect.isEmpty && tempRect.contains(x, y)
}
}
)
matchingNodes.firstOrNull()?.let { targetNode ->
if (targetNode.performAction(AccessibilityNodeInfo.ACTION_CLICK)) {
findNodesByCondition(wrappedRoot) { node ->
Log.d(TAG, "Checking node: [ID:${node.viewIdResourceName}], bounds: [${node.getBoundsInScreen(tempRect)}], clickable: ${node.isClickable}")
node.isClickable && node.isVisibleToUser() &&
node.run { getBoundsInScreen(tempRect); tempRect.contains(x, y) }
}.firstOrNull()?.apply {
if (performAction(AccessibilityNodeInfo.ACTION_CLICK)) {
Log.d(TAG, "Tap performed at ($x, $y)")
} else {
Log.w(TAG, "Failed to perform click action")
Log.w(TAG, "Failed to perform click at ($x, $y)")
}
recycle()
} ?: Log.w(TAG, "No clickable node found at ($x, $y)")
} finally {
wrappedRoot.recycle()
@ -108,233 +124,426 @@ class MyAccessibilityService : AccessibilityService() {
}
/**
* 根据条件查找节点
* @param root 根节点
* @param predicate 判断条件
* @return 符合条件的节点列表
* 查找并启动应用支持后续按钮点击
* @param appName 应用名称支持模糊匹配
* @param buttonText 可选参数需要点击的按钮文本
* @param maxRetries 最大重试次数默认10次
* @param retryInterval 重试间隔默认1000ms
*/
fun findAndLaunchApp (
appName: String,
packageName: String,
buttonText: String? = null,
) : Boolean {
var success = false
safeRootOperation { root ->
findNodesByCondition(root) { node ->
node.textMatches(appName) || node.contentDescriptionMatches(appName)
}.firstOrNull()?.apply {
if (performAction(AccessibilityNodeInfo.ACTION_CLICK)) {
Log.d(TAG, "Successfully launched: $appName")
targetAppPackageName = packageName
launcherButtonText = buttonText
targetAppName = appName
success = true
running = true
} else {
Log.w(TAG, "Failed to click app: $appName")
}
recycle()
} ?: Log.w(TAG, "App not found: $appName")
}
return success
}
fun stop() {
running = false
}
private fun isTargetAppRunning(): Boolean {
return rootInActiveWindow.let { currentRoot ->
currentAppPackageName = currentRoot.packageName?.toString()
currentAppPackageName == targetAppPackageName
}
}
// region 点击逻辑优化
/**
* 智能点击方案综合节点属性/父容器/坐标点击
* @return 是否点击成功
*/
private fun safeClickNode(node: AccessibilityNodeInfoCompat): Boolean {
// 优先使用标准点击方式
if (tryStandardClick(node)) return true
// 备用方案1尝试点击可点击的父容器
findClickableParent(node)?.let { parent ->
if (parent.performAction(AccessibilityNodeInfo.ACTION_CLICK)) {
Log.d(TAG, "Clicked via parent [ID:${parent.viewIdResourceName}]")
parent.recycle()
return true
}
parent.recycle()
}
// 备用方案2使用坐标点击
return tryCoordinateClick(node)
}
/**
* 尝试标准点击方式
* @return 点击是否成功
*/
private fun tryStandardClick(node: AccessibilityNodeInfoCompat): Boolean {
return if (isNodeTrulyClickable(node)) {
Log.d(TAG, "Attempting standard click")
node.performAction(AccessibilityNodeInfo.ACTION_CLICK)
} else {
false
}
}
/**
* 增强的节点可点击性检查
*/
private fun isNodeTrulyClickable(node: AccessibilityNodeInfoCompat): Boolean {
// 需要同时满足多个条件不同Android版本可能有差异
return node.isClickable &&
node.isVisibleToUser &&
node.isEnabled &&
(Build.VERSION.SDK_INT < Build.VERSION_CODES.P || node.isFocusable)
}
/**
* 深度查找可点击父容器最多向上查找5层
*/
private fun findClickableParent(node: AccessibilityNodeInfoCompat): AccessibilityNodeInfoCompat? {
var current = node.parent
var depth = 0
val maxDepth = 5
while (current != null && depth < maxDepth) {
if (isNodeTrulyClickable(current)) {
return AccessibilityNodeInfoCompat.obtain(current)
}
current = current.parent
depth++
}
return null
}
/**
* 坐标点击带边界校验
*/
private fun tryCoordinateClick(node: AccessibilityNodeInfoCompat): Boolean {
val rect = Rect().apply {
node.getBoundsInScreen(this)
}
// 边界有效性检查
if (rect.isEmpty) {
Log.w(TAG, "Invalid node bounds")
return false
}
val screenMetrics = getScreenMetrics()
if (!rect.intersect(screenMetrics)) {
Log.w(TAG, "Node out of screen bounds")
return false
}
return dispatchGesture(
createClickGesture(rect.centerX(), rect.centerY()), null, null
)
}
/**
* 创建点击手势描述
*/
private fun createClickGesture(x: Int, y: Int): GestureDescription {
val path = Path().apply { moveTo(x.toFloat(), y.toFloat()) }
return GestureDescription.Builder()
.addStroke(
GestureDescription.StrokeDescription(
path,
0L, // 开始时间
50L // 持续时间(毫秒)
)
).build()
}
/**
* 获取屏幕实际显示区域
*/
private fun getScreenMetrics(): Rect {
val metrics = resources.displayMetrics
return Rect(0, 0, metrics.widthPixels, metrics.heightPixels)
}
// endregion
private fun findAndClickButton(
buttonText: String,
maxRetries: Int = DEFAULT_MAX_RETRIES,
retryInterval: Long = DEFAULT_RETRY_INTERVAL
): Boolean {
Log.d(TAG, "Attempting to click button: '$buttonText'")
var success = false
safeRootOperation { root ->
retryOperation(
maxRetries = maxRetries,
interval = retryInterval,
checkCondition = {
// 精确查找可点击节点(包含回收保障)
var found = false
findNodesByCondition(root) { node ->
node.textMatches(buttonText, exact = true) ||
node.contentDescriptionMatches(buttonText, exact = true)
}.firstOrNull()?.apply {
Log.d(TAG, "Found clickable button: '$buttonText' [ID:${viewIdResourceName}]")
Handler(Looper.getMainLooper()).postDelayed({}, 300)
found = safeClickNode(this)
// 添加点击后延迟
Handler(Looper.getMainLooper()).postDelayed({}, 300)
recycle()
}
found
},
onSuccess = {
success = true
Log.i(TAG, "Successfully clicked button: '$buttonText'")
},
onFailure = {
Log.w(TAG, "Failed to click button after $maxRetries attempts: '$buttonText'")
showToast("Failed to click $buttonText")
}
)
}
return success
}
/**
* 通用重试操作模板
* @param maxRetries 最大重试次数
* @param interval 重试间隔
* @param checkCondition 需要检查的条件返回Boolean表示是否成功
* @param onSuccess 成功回调
* @param onFailure 失败回调
*/
private fun retryOperation(
maxRetries: Int,
interval: Long,
checkCondition: () -> Boolean,
onSuccess: () -> Unit,
onFailure: () -> Unit
) {
var retryCount = 0
fun attempt() {
when {
checkCondition() -> onSuccess()
retryCount < maxRetries -> {
retryCount++
Log.d(TAG, "Retry attempt $retryCount")
mainHandler.postDelayed(::attempt, interval)
}
else -> onFailure()
}
}
attempt()
}
/**
* 安全执行根节点操作自动处理资源回收
* @param block 需要在根节点上下文中执行的操作
*/
private inline fun safeRootOperation(block: (AccessibilityNodeInfoCompat) -> Unit) {
try {
rootInActiveWindow?.let { rawRoot ->
val wrappedRoot = AccessibilityNodeInfoCompat.wrap(rawRoot)
try {
block(wrappedRoot)
} finally {
wrappedRoot.recycle()
}
} ?: Log.e(TAG, "No active window available")
} catch (e: Exception) {
Log.e(TAG, "Root operation failed", e)
}
}
// endregion
// region 节点操作工具方法
/**
* 递归查找符合条件的节点
* @param root 起始节点
* @param predicate 节点匹配条件
* @return 匹配的节点列表需要手动回收
*/
private fun findNodesByCondition(
root: AccessibilityNodeInfoCompat,
predicate: (AccessibilityNodeInfoCompat) -> Boolean
): List<AccessibilityNodeInfoCompat> {
val result = mutableListOf<AccessibilityNodeInfoCompat>()
val queue = LinkedList<AccessibilityNodeInfoCompat>().apply { add(root) }
val tempRect = Rect() // 复用 Rect 对象提升性能
while (queue.isNotEmpty()) {
val node = queue.poll()
try {
// 统一使用带参数的 bounds 获取方式
node.getBoundsInScreen(tempRect)
if (predicate(node)) result.add(node)
// 遍历子节点
for (i in 0 until node.childCount) {
node.getChild(i)?.let {
queue.add(AccessibilityNodeInfoCompat.wrap(it.unwrap()))
}
}
} finally {
node.recycle()
fun traverse(node: AccessibilityNodeInfoCompat) {
if (predicate(node)) result.add(AccessibilityNodeInfoCompat.obtain(node))
(0 until node.childCount).mapNotNull { node.getChild(it) }.forEach {
traverse(it)
}
}
traverse(root)
return result
}
/**
* 查找并启动指定应用
* @param appName 应用名称
* 文本匹配扩展方法
* @param text 要匹配的文本
* @param exact 是否精确匹配默认false
*/
/**
* 查找并启动指定应用
* @param appName 应用名称
*/
fun findAndLaunchApp(appName: String) {
try {
rootInActiveWindow?.let { rawRoot ->
// 1. 包装成 Compat 对象
val wrappedRoot = AccessibilityNodeInfoCompat.wrap(rawRoot)
try {
// 2. 显式传递参数
val matchingNodes = findNodesByCondition(
root = wrappedRoot,
predicate = { node ->
// 3. 增强文本匹配逻辑
node.text?.toString().orEmpty().contains(appName, true) ||
node.contentDescription?.toString().orEmpty().contains(appName, true)
}
)
// 4. 处理匹配结果
matchingNodes.firstOrNull()?.let { targetNode ->
if (targetNode.performAction(AccessibilityNodeInfo.ACTION_CLICK)) {
Log.d(TAG, "Successfully launched: $appName")
// 启动重试机制
retryToDetectTargetApp(10, 3000) { success ->
if (success) {
Log.d(TAG, "Target app package name detected: $targetAppPackageName")
} else {
Log.e(TAG, "Failed to detect target app after 10 retries")
showToast("Failed to launch $appName")
}
}
} else {
Log.w(TAG, "Failed to click app: $appName")
}
targetNode.recycle()
} ?: Log.w(TAG, "App not found: $appName")
} finally {
wrappedRoot.recycle() // 确保回收资源
}
} ?: Log.e(TAG, "No root window available")
} catch (e: Exception) {
Log.e(TAG, "findAndLaunchApp error", e)
}
private fun AccessibilityNodeInfoCompat.textMatches(text: String, exact: Boolean = false): Boolean {
return this.text?.toString()?.let {
if (exact) it.equals(text, true) else it.contains(text, true)
} ?: false
}
/**
* 重试检测目标应用
* @param maxRetries 最大重试次数
* @param delayMillis 每次重试的延时毫秒
* @param onResult 结果回调true 表示成功false 表示失败
* 内容描述匹配扩展方法
* @param text 要匹配的文本
* @param exact 是否精确匹配默认false
*/
private fun retryToDetectTargetApp(maxRetries: Int, delayMillis: Long, onResult: (Boolean) -> Unit) {
var retryCount = 0
val retryRunnable = object : Runnable {
override fun run() {
rootInActiveWindow?.let { currentRoot ->
val packageName = currentRoot.packageName?.toString()
if (packageName != null && packageName != targetAppPackageName) {
targetAppPackageName = packageName
onResult(true) // 成功检测到目标应用
return
}
}
retryCount++
if (retryCount < maxRetries) {
Handler(Looper.getMainLooper()).postDelayed(this, delayMillis) // 继续重试
} else {
onResult(false) // 重试次数用尽,失败
}
}
}
// 启动第一次重试
Handler(Looper.getMainLooper()).postDelayed(retryRunnable, delayMillis)
private fun AccessibilityNodeInfoCompat.contentDescriptionMatches(text: String, exact: Boolean = false): Boolean {
return this.contentDescription?.toString()?.let {
if (exact) it.equals(text, true) else it.contains(text, true)
} ?: false
}
// endregion
/**
* 显示 Toast 消息
* @param message 要显示的消息
*/
// region 其他辅助方法
private fun showToast(message: String) {
Handler(Looper.getMainLooper()).post {
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
}
mainHandler.post { Toast.makeText(this, message, Toast.LENGTH_LONG).show() }
}
/**
* 处理无障碍事件
* @param event 无障碍事件
*/
private fun processAccessibilityEvent(event: AccessibilityEvent) {
// 添加具体的事件处理逻辑
when (event.eventType) {
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> {
Log.d(TAG, "Window state changed: ${event.packageName}")
Log.d(TAG, "target app package name: $targetAppPackageName")
// 检测当前活动的应用是否发生了变化
event.packageName?.let { currentPackageName ->
if (targetAppPackageName != null && currentPackageName != targetAppPackageName) {
Log.d(TAG, "Detected app switch from $targetAppPackageName to $currentPackageName")
retryBackToTargetApp(10, 1000) { success ->
if (success) {
Log.d(TAG, "Successfully returned to $targetAppPackageName")
} else {
Log.e(TAG, "Failed to return to $targetAppPackageName after 10 retries")
showToast("Failed to return to target app")
}
}
}
}
}
}
}
/**
* 重试返回目标应用
* @param maxRetries 最大重试次数
* @param delayMillis 每次重试的延时毫秒
* @param onResult 结果回调true 表示成功false 表示失败
*/
private fun retryBackToTargetApp(maxRetries: Int, delayMillis: Long, onResult: (Boolean) -> Unit) {
var retryCount = 0
val retryRunnable = object : Runnable {
override fun run() {
// 检测当前窗口是否已经返回到目标应用
rootInActiveWindow?.let { currentRoot ->
val packageName = currentRoot.packageName?.toString()
if (packageName == targetAppPackageName) {
onResult(true) // 成功返回到目标应用
return
}
}
// 尝试点击返回按钮
performGlobalAction(GLOBAL_ACTION_BACK)
Log.d(TAG, "Performed back action, retry count: $retryCount")
retryCount++
if (retryCount < maxRetries) {
Handler(Looper.getMainLooper()).postDelayed(this, delayMillis) // 继续重试
} else {
onResult(false) // 重试次数用尽,失败
}
}
}
// 启动第一次重试
Handler(Looper.getMainLooper()).postDelayed(retryRunnable, delayMillis)
}
/**
* 安全启动主Activity
*/
private fun startMainActivitySafely() {
try {
startActivity(
Intent(this, MainActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
)
startActivity(Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
})
} catch (e: Exception) {
Log.e(TAG, "Failed to start MainActivity", e)
Log.e(TAG, "Start MainActivity failed", e)
}
}
/**
* 清理资源
*/
private fun cleanUpResources() {
instance = null
targetAppPackageName = null
}
// endregion
/**
* 遍历节点树
* @param block 处理每个节点的函数
*/
private fun AccessibilityNodeInfoCompat.traverse(block: (AccessibilityNodeInfoCompat) -> Boolean) {
if (!block(this)) return
for (i in 0 until childCount) {
getChild(i)?.let { child ->
child.traverse(block)
child.recycle() // 确保每个子节点都被回收
// region 无障碍事件处理
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
event?.let {
when (event.eventType) {
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> handleWindowChange(event)
// 可根据需要扩展其他事件类型处理
}
}
}
private fun handleWindowChange(event: AccessibilityEvent) {
if (!running) return
currentAppPackageName = event.packageName?.toString() ?: return
currentClassName = event.className?.toString() ?: return
// 增强日志输出
Log.d(TAG, """
Window Changed:
Current Package : $currentAppPackageName
Target Package : $targetAppPackageName
Class Name : ${currentClassName?.substringAfterLast('.')}
Full Class Name : $currentClassName
""".trimIndent())
if (targetAppPackageName == null) return
// 当检测到切换到其他应用时
if (currentAppPackageName != targetAppPackageName) {
Log.w(TAG, "Detected switch to non-target app: $currentAppPackageName")
// 先检查 isRetrying如果为 true 则不重复启动
if (isRetrying.compareAndSet(false, true)) {
backgroundHandler.post {
try {
retryOperation(
DEFAULT_MAX_RETRIES,
DEFAULT_RETRY_INTERVAL,
checkCondition = {
val ok = isTargetAppRunning()
ok || performReturnGlobalAction()
Log.d(TAG, "Check $targetAppPackageName is activated: $ok")
ok
},
onSuccess = {
Log.i(TAG, "Successfully returned to target app")
},
onFailure = {
Log.e(TAG, "Failed to return after 10 attempts")
returnToHomeAndRestart()
}
)
} finally {
isRetrying.set(false) // 任务结束后重置
}
}
} else {
Log.w(TAG, "Retry operation is already running, skipping...")
}
}
checkActivity()
}
private fun checkActivity() {
Log.d(TAG, "Current App Package: $currentAppPackageName; Current App Class: $currentClassName; targetAppPackageName: $targetAppPackageName")
if (currentAppPackageName == targetAppPackageName) {
when (currentClassName) {
"io.sixminutes.breakingnews.MainActivity" -> launcherButtonText?.let {
findAndClickButton(
it
)
}
"io.sixminutes.breakingnews.ClickTrackerActivity" -> simulateTap(300, 400)
"com.applovin.adview.AppLovinFullscreenActivity" -> simulateTap(300, 400)
}
}
}
// region 返回策略实现
/**
* 按返回键
*/
private fun performReturnGlobalAction(): Boolean {
if (performGlobalAction(GLOBAL_ACTION_BACK)) {
Log.d(TAG, "Attempted back navigation")
return true
}
return false
}
/**
* 返回桌面并重启应用
*/
private fun returnToHomeAndRestart(): Boolean {
Log.d(TAG, "Used home+restart strategy")
return performGlobalAction(GLOBAL_ACTION_HOME)
&& targetAppName != null
&& targetAppPackageName != null
&& findAndLaunchApp(targetAppName!!, targetAppPackageName!!)
}
// endregion
}