From 3fda835adec7ef3b1bdd55dccee573e9caf238f1 Mon Sep 17 00:00:00 2001 From: lisonge Date: Tue, 24 Sep 2024 17:08:19 +0800 Subject: [PATCH] perf: shizuku state --- .../kotlin/li/songe/gkd/data/GkdAction.kt | 16 +- .../kotlin/li/songe/gkd/data/ResolvedRule.kt | 5 +- .../li/songe/gkd/debug/SnapshotTileService.kt | 8 +- .../songe/gkd/permission/PermissionState.kt | 13 +- .../li/songe/gkd/service/A11yService.kt | 40 +-- .../kotlin/li/songe/gkd/shizuku/ShizukuApi.kt | 235 ++++++++---------- .../li/songe/gkd/shizuku/UserService.kt | 6 +- .../kotlin/li/songe/gkd/ui/AdvancedPage.kt | 62 ++--- .../kotlin/li/songe/gkd/ui/AuthA11yPage.kt | 6 +- 9 files changed, 159 insertions(+), 232 deletions(-) diff --git a/app/src/main/kotlin/li/songe/gkd/data/GkdAction.kt b/app/src/main/kotlin/li/songe/gkd/data/GkdAction.kt index 1ce1fe542d..30ce37d971 100644 --- a/app/src/main/kotlin/li/songe/gkd/data/GkdAction.kt +++ b/app/src/main/kotlin/li/songe/gkd/data/GkdAction.kt @@ -8,7 +8,7 @@ import android.view.ViewConfiguration import android.view.accessibility.AccessibilityNodeInfo import com.blankj.utilcode.util.ScreenUtils import kotlinx.serialization.Serializable -import li.songe.selector.FastQuery +import li.songe.gkd.shizuku.safeTap @Serializable data class GkdAction( @@ -31,7 +31,6 @@ sealed class ActionPerformer(val action: String) { context: AccessibilityService, node: AccessibilityNodeInfo, position: RawSubscription.Position?, - shizukuClickFc: ((x: Float, y: Float) -> Boolean?)? = null, ): ActionResult data object ClickNode : ActionPerformer("clickNode") { @@ -39,7 +38,6 @@ sealed class ActionPerformer(val action: String) { context: AccessibilityService, node: AccessibilityNodeInfo, position: RawSubscription.Position?, - shizukuClickFc: ((x: Float, y: Float) -> Boolean?)? ): ActionResult { return ActionResult( action = action, @@ -53,7 +51,6 @@ sealed class ActionPerformer(val action: String) { context: AccessibilityService, node: AccessibilityNodeInfo, position: RawSubscription.Position?, - shizukuClickFc: ((x: Float, y: Float) -> Boolean?)?, ): ActionResult { val rect = Rect() node.getBoundsInScreen(rect) @@ -64,7 +61,7 @@ sealed class ActionPerformer(val action: String) { action = action, // TODO 在分屏/小窗模式下会点击到应用界面外部导致误触其它应用 result = if (0 <= x && 0 <= y && x <= ScreenUtils.getScreenWidth() && y <= ScreenUtils.getScreenHeight()) { - val result = shizukuClickFc?.invoke(x, y) + val result = safeTap(x, y) if (result != null) { return ActionResult(action, result, true) } @@ -90,7 +87,6 @@ sealed class ActionPerformer(val action: String) { context: AccessibilityService, node: AccessibilityNodeInfo, position: RawSubscription.Position?, - shizukuClickFc: ((x: Float, y: Float) -> Boolean?)? ): ActionResult { if (node.isClickable) { val result = ClickNode.perform(context, node, position) @@ -98,7 +94,7 @@ sealed class ActionPerformer(val action: String) { return result } } - return ClickCenter.perform(context, node, position, shizukuClickFc) + return ClickCenter.perform(context, node, position) } } @@ -107,7 +103,6 @@ sealed class ActionPerformer(val action: String) { context: AccessibilityService, node: AccessibilityNodeInfo, position: RawSubscription.Position?, - shizukuClickFc: ((x: Float, y: Float) -> Boolean?)? ): ActionResult { return ActionResult( action = action, @@ -121,7 +116,6 @@ sealed class ActionPerformer(val action: String) { context: AccessibilityService, node: AccessibilityNodeInfo, position: RawSubscription.Position?, - shizukuClickFc: ((x: Float, y: Float) -> Boolean?)? ): ActionResult { val rect = Rect() node.getBoundsInScreen(rect) @@ -156,7 +150,6 @@ sealed class ActionPerformer(val action: String) { context: AccessibilityService, node: AccessibilityNodeInfo, position: RawSubscription.Position?, - shizukuClickFc: ((x: Float, y: Float) -> Boolean?)? ): ActionResult { if (node.isLongClickable) { val result = LongClickNode.perform(context, node, position) @@ -164,7 +157,7 @@ sealed class ActionPerformer(val action: String) { return result } } - return LongClickCenter.perform(context, node, position, shizukuClickFc) + return LongClickCenter.perform(context, node, position) } } @@ -173,7 +166,6 @@ sealed class ActionPerformer(val action: String) { context: AccessibilityService, node: AccessibilityNodeInfo, position: RawSubscription.Position?, - shizukuClickFc: ((x: Float, y: Float) -> Boolean?)? ): ActionResult { return ActionResult( action = action, diff --git a/app/src/main/kotlin/li/songe/gkd/data/ResolvedRule.kt b/app/src/main/kotlin/li/songe/gkd/data/ResolvedRule.kt index 3c33bd9e27..ab2d1c44cb 100644 --- a/app/src/main/kotlin/li/songe/gkd/data/ResolvedRule.kt +++ b/app/src/main/kotlin/li/songe/gkd/data/ResolvedRule.kt @@ -190,10 +190,9 @@ sealed class ResolvedRule( fun performAction( context: AccessibilityService, - node: AccessibilityNodeInfo, - shizukuClickFc: ((x: Float, y: Float) -> Boolean?)? = null + node: AccessibilityNodeInfo ): ActionResult { - return performer.perform(context, node, rule.position, shizukuClickFc) + return performer.perform(context, node, rule.position) } var matchDelayJob: Job? = null diff --git a/app/src/main/kotlin/li/songe/gkd/debug/SnapshotTileService.kt b/app/src/main/kotlin/li/songe/gkd/debug/SnapshotTileService.kt index ad94766965..658da78fc1 100644 --- a/app/src/main/kotlin/li/songe/gkd/debug/SnapshotTileService.kt +++ b/app/src/main/kotlin/li/songe/gkd/debug/SnapshotTileService.kt @@ -12,6 +12,7 @@ import li.songe.gkd.service.TopActivity import li.songe.gkd.service.getAndUpdateCurrentRules import li.songe.gkd.service.safeActiveWindow import li.songe.gkd.service.updateTopActivity +import li.songe.gkd.shizuku.safeGetTopActivity import li.songe.gkd.util.launchTry import li.songe.gkd.util.toast @@ -46,11 +47,8 @@ class SnapshotTileService : TileService() { } else if (latestAppId != oldAppId) { LogUtils.d("SnapshotTileService::eventExecutor.execute") A11yService.eventExecutor.execute { - updateTopActivity( - A11yService.instance?.getShizukuTopActivity?.invoke() ?: TopActivity( - appId = latestAppId - ) - ) + val topActivity = safeGetTopActivity() ?: TopActivity(appId = latestAppId) + updateTopActivity(topActivity) getAndUpdateCurrentRules() appScope.launchTry(Dispatchers.IO) { captureSnapshot() diff --git a/app/src/main/kotlin/li/songe/gkd/permission/PermissionState.kt b/app/src/main/kotlin/li/songe/gkd/permission/PermissionState.kt index c1dafa1848..060c7c8a86 100644 --- a/app/src/main/kotlin/li/songe/gkd/permission/PermissionState.kt +++ b/app/src/main/kotlin/li/songe/gkd/permission/PermissionState.kt @@ -13,9 +13,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.updateAndGet import li.songe.gkd.app import li.songe.gkd.appScope -import li.songe.gkd.shizuku.newActivityTaskManager -import li.songe.gkd.shizuku.safeGetTasks -import li.songe.gkd.shizuku.shizukuIsSafeOK +import li.songe.gkd.shizuku.shizukuCheckGranted import li.songe.gkd.util.initOrResetAppInfoCache import li.songe.gkd.util.launchTry import kotlin.coroutines.resume @@ -161,14 +159,7 @@ val writeSecureSettingsState by lazy { val shizukuOkState by lazy { PermissionState( - check = { - shizukuIsSafeOK() && (try { - // 打开 shizuku 点击右上角停止, 此时 shizukuIsSafeOK() == true, 因此需要二次检查状态 - newActivityTaskManager()?.safeGetTasks(log = false)?.isNotEmpty() == true - } catch (e: Exception) { - false - }) - }, + check = { shizukuCheckGranted() }, ) } diff --git a/app/src/main/kotlin/li/songe/gkd/service/A11yService.kt b/app/src/main/kotlin/li/songe/gkd/service/A11yService.kt index 8370a1b7eb..e07b180692 100644 --- a/app/src/main/kotlin/li/songe/gkd/service/A11yService.kt +++ b/app/src/main/kotlin/li/songe/gkd/service/A11yService.kt @@ -44,9 +44,8 @@ import li.songe.gkd.data.RuleStatus import li.songe.gkd.data.clearNodeCache import li.songe.gkd.debug.SnapshotExt import li.songe.gkd.permission.shizukuOkState -import li.songe.gkd.shizuku.getShizukuCanUsedFlow -import li.songe.gkd.shizuku.useSafeGetTasksFc -import li.songe.gkd.shizuku.useSafeInputTapFc +import li.songe.gkd.shizuku.safeGetTopActivity +import li.songe.gkd.shizuku.serviceWrapperFlow import li.songe.gkd.util.OnA11yConnected import li.songe.gkd.util.OnA11yEvent import li.songe.gkd.util.OnCreate @@ -90,8 +89,6 @@ class A11yService : AccessibilityService(), OnCreate, OnA11yConnected, OnA11yEve } val scope = CoroutineScope(Dispatchers.Default) - val getShizukuTopActivity by lazy { useGetShizukuActivity() } - val getShizukuClick by lazy { useGetShizukuClick() } init { useRunningState() @@ -101,8 +98,7 @@ class A11yService : AccessibilityService(), OnCreate, OnA11yConnected, OnA11yEve useAutoUpdateSubs() useRuleChangedLog() useAutoCheckShizuku() - getShizukuTopActivity - getShizukuClick + serviceWrapperFlow useMatchRule() } @@ -140,7 +136,7 @@ class A11yService : AccessibilityService(), OnCreate, OnA11yConnected, OnA11yEve return ActionPerformer .getAction(gkdAction.action) - .perform(serviceVal, targetNode, gkdAction.position, instance?.getShizukuClick) + .perform(serviceVal, targetNode, gkdAction.position) } @@ -241,7 +237,7 @@ private fun A11yService.useMatchRule() { if (topActivityFlow.value.appId != rightAppId || (!matchApp && rule is AppRule)) { A11yService.eventExecutor.execute { if (topActivityFlow.value.appId != rightAppId) { - val shizukuTop = getShizukuTopActivity() + val shizukuTop = safeGetTopActivity() if (shizukuTop?.appId == rightAppId) { updateTopActivity(shizukuTop) } else { @@ -270,7 +266,7 @@ private fun A11yService.useMatchRule() { continue } if (rule.status != RuleStatus.StatusOk) break - val actionResult = rule.performAction(context, target, getShizukuClick) + val actionResult = rule.performAction(context, target) if (actionResult.result) { rule.trigger() scope.launch(A11yService.actionThread) { @@ -356,7 +352,7 @@ private fun A11yService.useMatchRule() { } } else { if (storeFlow.value.enableShizukuActivity && fixedEvent.time - lastTriggerShizukuTime > 300) { - val shizukuTop = getShizukuTopActivity() + val shizukuTop = safeGetTopActivity() if (shizukuTop != null && shizukuTop.appId == rightAppId) { if (shizukuTop.activityId == evActivityId) { updateTopActivity( @@ -373,7 +369,7 @@ private fun A11yService.useMatchRule() { } if (rightAppId != topActivityFlow.value.appId) { // 从 锁屏,下拉通知栏 返回等情况, 应用不会发送事件, 但是系统组件会发送事件 - val shizukuTop = getShizukuTopActivity() + val shizukuTop = safeGetTopActivity() if (shizukuTop?.appId == rightAppId) { updateTopActivity(shizukuTop) } else { @@ -429,26 +425,6 @@ private fun A11yService.useRunningState() { } } -private fun A11yService.useGetShizukuActivity(): () -> TopActivity? { - val enableShizukuActivityFlow = storeFlow.map(scope) { s -> s.enableShizukuActivity } - val shizukuActivityUsedFlow = getShizukuCanUsedFlow(scope, enableShizukuActivityFlow) - val safeGetTasksFc by lazy { useSafeGetTasksFc(scope, shizukuActivityUsedFlow) } - // 当锁屏/下拉通知栏时, safeActiveWindow 没有 activityId, 但是此时 shizuku 获取到是前台 app 的 appId 和 activityId - return fun(): TopActivity? { - if (!storeFlow.value.enableShizukuActivity) return null - // 平均耗时 5 ms - val top = safeGetTasksFc()?.lastOrNull()?.topActivity ?: return null - return TopActivity(appId = top.packageName, activityId = top.className) - } -} - -private fun A11yService.useGetShizukuClick(): (Float, Float) -> Boolean? { - val shizukuClickUsedFlow = - getShizukuCanUsedFlow(scope, storeFlow.map(scope) { s -> s.enableShizukuClick }) - val safeInjectClickEventFc = useSafeInputTapFc(scope, shizukuClickUsedFlow) - return safeInjectClickEventFc -} - private fun A11yService.useAutoCheckShizuku() { var lastCheckShizukuTime = 0L onA11yEvent { diff --git a/app/src/main/kotlin/li/songe/gkd/shizuku/ShizukuApi.kt b/app/src/main/kotlin/li/songe/gkd/shizuku/ShizukuApi.kt index e35a20168b..daeafe00bc 100644 --- a/app/src/main/kotlin/li/songe/gkd/shizuku/ShizukuApi.kt +++ b/app/src/main/kotlin/li/songe/gkd/shizuku/ShizukuApi.kt @@ -10,7 +10,6 @@ import android.content.pm.PackageManager import android.os.IBinder import android.view.Display import com.blankj.utilcode.util.LogUtils -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -20,10 +19,12 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import li.songe.gkd.META import li.songe.gkd.app +import li.songe.gkd.appScope import li.songe.gkd.data.DeviceInfo import li.songe.gkd.permission.shizukuOkState +import li.songe.gkd.service.TopActivity import li.songe.gkd.util.json -import li.songe.gkd.util.map +import li.songe.gkd.util.storeFlow import li.songe.gkd.util.toast import rikka.shizuku.Shizuku import rikka.shizuku.ShizukuBinderWrapper @@ -33,12 +34,25 @@ import kotlin.coroutines.suspendCoroutine import kotlin.reflect.full.declaredMemberFunctions import kotlin.reflect.typeOf -fun shizukuIsSafeOK(): Boolean { - return try { +fun shizukuCheckGranted(): Boolean { + val granted = try { Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED } catch (e: Exception) { false } + if (!granted) return false + if (storeFlow.value.enableShizukuActivity) { + return safeGetTopActivity() != null || shizukuCheckActivity() + } + return true +} + +fun shizukuCheckActivity(): Boolean { + return (try { + newActivityTaskManager()?.safeGetTasks(log = false)?.isNotEmpty() == true + } catch (e: Exception) { + false + }) } /** @@ -48,7 +62,7 @@ fun shizukuIsSafeOK(): Boolean { // Context.ACTIVITY_TASK_SERVICE private const val ACTIVITY_TASK_SERVICE = "activity_task" -fun newActivityTaskManager(): IActivityTaskManager? { +private fun newActivityTaskManager(): IActivityTaskManager? { val service = SystemServiceHelper.getSystemService(ACTIVITY_TASK_SERVICE) if (service == null) { LogUtils.d("shizuku 无法获取 $ACTIVITY_TASK_SERVICE") @@ -57,13 +71,19 @@ fun newActivityTaskManager(): IActivityTaskManager? { return service.let(::ShizukuBinderWrapper).let(IActivityTaskManager.Stub::asInterface) } -fun newPackageManager(): IPackageManager? { - val service = SystemServiceHelper.getSystemService("package") - if (service == null) { - LogUtils.d("shizuku 无法获取 package") - return null +private val shizukuActivityUsedFlow by lazy { + combine(shizukuOkState.stateFlow, storeFlow) { shizukuOk, store -> + shizukuOk && store.enableShizukuActivity + }.stateIn(appScope, SharingStarted.Eagerly, false) +} +private val taskManagerFlow by lazy> { + val stateFlow = MutableStateFlow(null) + appScope.launch(Dispatchers.IO) { + shizukuActivityUsedFlow.collect { + stateFlow.value = if (it) newActivityTaskManager() else null + } } - return service.let(::ShizukuBinderWrapper).let(IPackageManager.Stub::asInterface) + stateFlow } /** @@ -73,8 +93,7 @@ fun newPackageManager(): IPackageManager? { * 4: (int, boolean, boolean, int) -> List */ private var getTasksFcType: Int? = null - -fun IActivityTaskManager.safeGetTasks(log: Boolean = true): List? { +private fun IActivityTaskManager.safeGetTasks(log: Boolean = true): List? { if (getTasksFcType == null) { val fcs = this::class.declaredMemberFunctions val parameters = fcs.find { d -> d.name == "getTasks" }?.parameters @@ -110,118 +129,26 @@ fun IActivityTaskManager.safeGetTasks(log: Boolean = true): List, -): StateFlow { - return combine( - shizukuOkState.stateFlow, - enableFlow - ) { shizukuOk, enableShizuku -> - shizukuOk && enableShizuku - }.stateIn(scope, SharingStarted.Eagerly, false) -} - -fun useSafeGetTasksFc( - scope: CoroutineScope, - shizukuCanUsedFlow: StateFlow, -): () -> List? { - val activityTaskManagerFlow = - shizukuCanUsedFlow.map(scope) { if (it) newActivityTaskManager() else null } - return { - if (shizukuCanUsedFlow.value) { - // 避免直接访问方法校验 android.app.IActivityTaskManager 类型 - // 报错 java.lang.ClassNotFoundException:Didn't find class "android.app.IActivityTaskManager" on path: DexPathList - activityTaskManagerFlow.value?.safeGetTasks() - } else { - null - } +fun safeGetTopActivity(): TopActivity? { + if (!shizukuActivityUsedFlow.value) return null + try { + // 避免直接访问方法校验 android.app.IActivityTaskManager 类型 + // 否则某些机型会报错 java.lang.ClassNotFoundException:Didn't find class "android.app.IActivityTaskManager" on path: DexPathList + val taskManager = taskManagerFlow.value ?: return null + val top = taskManager.safeGetTasks()?.lastOrNull()?.topActivity ?: return null + return TopActivity(appId = top.packageName, activityId = top.className) + } catch (e: Exception) { + return null } } -//fun IInputManager.safeClick(x: Float, y: Float): Boolean? { -// // 模拟 abd shell input tap x y 传递的 pressure -// // 下面除了 pressure 的常量来自 MotionEvent obtain 方法 -// val downTime = SystemClock.uptimeMillis() -// val downEvent = MotionEvent.obtain( -// downTime, downTime, MotionEvent.ACTION_DOWN, x, y, 1.0f, 1.0f, 0, 1.0f, 1.0f, 0, 0 -// ) // pressure=1.0f -// val upEvent = MotionEvent.obtain( -// downTime, downTime, MotionEvent.ACTION_UP, x, y, 0f, 1.0f, 0, 1.0f, 1.0f, 0, 0 -// ) // pressure=0f -// return try { -// val r1 = injectInputEvent(downEvent, 2) -// val r2 = injectInputEvent(upEvent, 2) -// r1 && r2 -// } catch (e: Exception) { -// LogUtils.d(e) -// null -// } finally { -// downEvent.recycle() -// upEvent.recycle() -// } -//} - -//fun useSafeInjectClickEventFc( -// scope: CoroutineScope, -// usedFlow: StateFlow, -//): (x: Float, y: Float) -> Boolean? { -// val inputManagerFlow = usedFlow.map(scope) { if (it) newInputManager() else null } -// return { x, y -> -// if (usedFlow.value) { -// inputManagerFlow.value?.safeClick(x, y) -// } else { -// null -// } -// } -//} - -// 在 大麦 https://i.gkd.li/i/14605104 上测试产生如下 3 种情况 -// 1. 点击不生效: 使用传统无障碍屏幕点击, 此种点击可被 大麦 通过 View.setAccessibilityDelegate 屏蔽 -// 2. 点击概率生效: 使用 Shizuku 获取到的 InputManager.injectInputEvent 发出点击, 概率失效/生效, 原因未知 -// 3. 点击生效: 使用 Shizuku 获取到的 shell input tap x y 发出点击 by useSafeInputTapFc, 暂未找到屏蔽方案 -fun useSafeInputTapFc( - scope: CoroutineScope, - usedFlow: StateFlow, -): (x: Float, y: Float) -> Boolean? { - val serviceWrapperFlow = MutableStateFlow(null) - scope.launch(Dispatchers.IO) { - usedFlow.collect { - if (it) { - val serviceWrapper = newUserService() - serviceWrapperFlow.value = serviceWrapper - } else { - serviceWrapperFlow.value?.destroy() - serviceWrapperFlow.value = null - } - } - } - return { x, y -> - if (usedFlow.value) { - try { - val result = serviceWrapperFlow.value?.userService?.execCommand("input tap $x $y") - if (result != null) { - json.decodeFromString(result).code == 0 - } else { - null - } - } catch (e: Exception) { - e.printStackTrace() - null - } - } else { - null - } +fun newPackageManager(): IPackageManager? { + val service = SystemServiceHelper.getSystemService("package") + if (service == null) { + LogUtils.d("shizuku 无法获取 package") + return null } + return service.let(::ShizukuBinderWrapper).let(IPackageManager.Stub::asInterface) } data class UserServiceWrapper( @@ -231,14 +158,16 @@ data class UserServiceWrapper( ) { fun destroy() { try { - Shizuku.unbindUserService(serviceArgs, connection, false) + LogUtils.d("unbindUserService", serviceArgs) + userService.exit() + Shizuku.unbindUserService(serviceArgs, connection, true) } catch (e: Exception) { e.printStackTrace() } } } -suspend fun newUserService(): UserServiceWrapper = suspendCoroutine { continuation -> +private suspend fun serviceWrapper(): UserServiceWrapper = suspendCoroutine { continuation -> val serviceArgs = Shizuku.UserServiceArgs( ComponentName( app, @@ -252,7 +181,6 @@ suspend fun newUserService(): UserServiceWrapper = suspendCoroutine { continuati val connection = object : ServiceConnection { override fun onServiceConnected(componentName: ComponentName, binder: IBinder?) { - // Shizuku.unbindUserService 并不会移除 connection, 导致后续调用此函数时 此方法仍然被调用 LogUtils.d("onServiceConnected", componentName) resumeFc ?: return if (binder?.pingBinder() == true) { @@ -274,4 +202,61 @@ suspend fun newUserService(): UserServiceWrapper = suspendCoroutine { continuati } } Shizuku.bindUserService(serviceArgs, connection) -} \ No newline at end of file +} + +suspend fun shizukuCheckUserService(): Boolean { + return safeTap(0f, 0f) == true || try { + val wrapper = serviceWrapper() + try { + wrapper.userService.execCommandForResult("input tap 0 0") == true + } finally { + wrapper.destroy() + } + } catch (e: Exception) { + false + } +} + +private val shizukuServiceUsedFlow by lazy { + combine(shizukuOkState.stateFlow, storeFlow) { shizukuOk, store -> + shizukuOk && store.enableShizukuClick + }.stateIn(appScope, SharingStarted.Eagerly, false) +} + +val serviceWrapperFlow by lazy> { + val stateFlow = MutableStateFlow(null) + appScope.launch(Dispatchers.IO) { + shizukuServiceUsedFlow.collect { + stateFlow.value?.destroy() + stateFlow.value = null + if (it) { + stateFlow.value = serviceWrapper() + } + } + } + stateFlow +} + +// 在 大麦 https://i.gkd.li/i/14605104 上测试产生如下 3 种情况 +// 1. 点击不生效: 使用传统无障碍屏幕点击, 此种点击可被 大麦 通过 View.setAccessibilityDelegate 屏蔽 +// 2. 点击概率生效: 使用 Shizuku 获取到的 InputManager.injectInputEvent 发出点击, 概率失效/生效, 原因未知 +// 3. 点击生效: 使用 Shizuku 获取到的 shell input tap x y 发出点击 by safeTap, 暂未找到屏蔽方案 +fun safeTap(x: Float, y: Float): Boolean? { + val userService = serviceWrapperFlow.value?.userService ?: return null + return userService.execCommandForResult("input tap $x $y") +} + +private fun IUserService.execCommandForResult(command: String): Boolean? { + return try { + val result = execCommand(command) + LogUtils.d("safeTap", result) + if (result != null) { + json.decodeFromString(result).code == 0 + } else { + null + } + } catch (e: Exception) { + e.printStackTrace() + null + } +} diff --git a/app/src/main/kotlin/li/songe/gkd/shizuku/UserService.kt b/app/src/main/kotlin/li/songe/gkd/shizuku/UserService.kt index f9c634f6b5..d57132a01b 100644 --- a/app/src/main/kotlin/li/songe/gkd/shizuku/UserService.kt +++ b/app/src/main/kotlin/li/songe/gkd/shizuku/UserService.kt @@ -7,8 +7,10 @@ import androidx.annotation.Keep import kotlinx.serialization.encodeToString import li.songe.gkd.util.json import java.io.DataOutputStream +import kotlin.system.exitProcess +@Suppress("unused") class UserService : IUserService.Stub { /** * Constructor is required. @@ -22,11 +24,9 @@ class UserService : IUserService.Stub { Log.i("UserService", "constructor with Context: context=$context") } - /** - * Reserved destroy method - */ override fun destroy() { Log.i("UserService", "destroy") + exitProcess(0) } override fun exit() { diff --git a/app/src/main/kotlin/li/songe/gkd/ui/AdvancedPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/AdvancedPage.kt index f6021669a7..3d144e5f4c 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/AdvancedPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/AdvancedPage.kt @@ -74,10 +74,8 @@ import li.songe.gkd.permission.canDrawOverlaysState import li.songe.gkd.permission.notificationState import li.songe.gkd.permission.requiredPermission import li.songe.gkd.permission.shizukuOkState -import li.songe.gkd.shizuku.CommandResult -import li.songe.gkd.shizuku.newActivityTaskManager -import li.songe.gkd.shizuku.newUserService -import li.songe.gkd.shizuku.safeGetTasks +import li.songe.gkd.shizuku.shizukuCheckActivity +import li.songe.gkd.shizuku.shizukuCheckUserService import li.songe.gkd.ui.component.AuthCard import li.songe.gkd.ui.component.SettingItem import li.songe.gkd.ui.component.TextSwitch @@ -88,7 +86,6 @@ import li.songe.gkd.ui.style.titleItemPadding import li.songe.gkd.util.LocalNavController import li.songe.gkd.util.ProfileTransitions import li.songe.gkd.util.buildLogFile -import li.songe.gkd.util.json import li.songe.gkd.util.launchAsFn import li.songe.gkd.util.launchTry import li.songe.gkd.util.openUri @@ -606,29 +603,23 @@ fun AdvancedPage() { @Composable private fun ShizukuFragment(enabled: Boolean = true) { val store by storeFlow.collectAsState() - TextSwitch(title = "Shizuku-界面识别", + TextSwitch( + title = "Shizuku-界面识别", subtitle = "更准确识别界面ID", checked = store.enableShizukuActivity, enabled = enabled, - onCheckedChange = { enableShizuku -> - if (enableShizuku) { - appScope.launchTry(Dispatchers.IO) { - // 校验方法是否适配, 再允许使用 shizuku - val tasks = - newActivityTaskManager()?.safeGetTasks()?.firstOrNull() - if (tasks != null) { - storeFlow.value = store.copy( - enableShizukuActivity = true - ) - } else { - toast("Shizuku-界面识别校验失败,无法使用") - } + onCheckedChange = appScope.launchAsFn(Dispatchers.IO) { + if (it) { + toast("检测中") + if (!shizukuCheckActivity()) { + toast("检测失败,无法使用") + return@launchAsFn } - } else { - storeFlow.value = store.copy( - enableShizukuActivity = false - ) + toast("已启用") } + storeFlow.value = store.copy( + enableShizukuActivity = it + ) }) TextSwitch( @@ -636,23 +627,18 @@ private fun ShizukuFragment(enabled: Boolean = true) { subtitle = "变更 clickCenter 为强制模拟点击", checked = store.enableShizukuClick, enabled = enabled, - onCheckedChange = { enableShizuku -> - if (enableShizuku) { - appScope.launchTry(Dispatchers.IO) { - val service = newUserService() - val result = service.userService.execCommand("input tap 0 0") - service.destroy() - if (json.decodeFromString(result).code == 0) { - storeFlow.update { it.copy(enableShizukuClick = true) } - } else { - toast("Shizuku-模拟点击校验失败,无法使用") - } + onCheckedChange = appScope.launchAsFn(Dispatchers.IO) { + if (it) { + toast("检测中") + if (!shizukuCheckUserService()) { + toast("检测失败,无法使用") + return@launchAsFn } - } else { - storeFlow.value = store.copy( - enableShizukuClick = false - ) + toast("已启用") } + storeFlow.value = store.copy( + enableShizukuClick = it + ) }) diff --git a/app/src/main/kotlin/li/songe/gkd/ui/AuthA11yPage.kt b/app/src/main/kotlin/li/songe/gkd/ui/AuthA11yPage.kt index b8101c3340..61c1d3b54c 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/AuthA11yPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/AuthA11yPage.kt @@ -261,9 +261,9 @@ private val commandText by lazy { "adb pm grant ${META.appId} android.permission private suspend fun MainActivity.grantPermissionByShizuku() { if (shizukuOkState.stateFlow.value) { try { - val service = newPackageManager() - if (service != null) { - service.grantRuntimePermission( + val manager = newPackageManager() + if (manager != null) { + manager.grantRuntimePermission( META.appId, "android.permission.WRITE_SECURE_SETTINGS", 0, // maybe others