From 4654a11462ac805cef07fa412fd896ee0a2af531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E5=88=BA=E8=9E=88?= Date: Fri, 29 Nov 2024 22:31:52 +0800 Subject: [PATCH] perf: a11y mutex, shizuku userService --- .../li/songe/gkd/service/GkdTileService.kt | 100 ++++++++++-------- .../kotlin/li/songe/gkd/shizuku/ShizukuApi.kt | 91 +++++++++------- .../kotlin/li/songe/gkd/ui/AuthA11yPage.kt | 7 +- .../main/kotlin/li/songe/gkd/ui/AuthA11yVm.kt | 3 +- 4 files changed, 112 insertions(+), 89 deletions(-) diff --git a/app/src/main/kotlin/li/songe/gkd/service/GkdTileService.kt b/app/src/main/kotlin/li/songe/gkd/service/GkdTileService.kt index a72839cee..19ed5c8be 100644 --- a/app/src/main/kotlin/li/songe/gkd/service/GkdTileService.kt +++ b/app/src/main/kotlin/li/songe/gkd/service/GkdTileService.kt @@ -11,6 +11,8 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import li.songe.gkd.accessRestrictedSettingsShowFlow import li.songe.gkd.app import li.songe.gkd.appScope @@ -106,62 +108,68 @@ private fun enableA11yService() { ) } +val modifyA11yMutex by lazy { Mutex() } + fun switchA11yService() = appScope.launchTry(Dispatchers.IO) { - if (!writeSecureSettingsState.updateAndGet()) { - toast("请先授予[写入安全设置权限]") - return@launchTry - } - val names = getServiceNames() - storeFlow.update { it.copy(enableService = !A11yService.isRunning.value) } - if (A11yService.isRunning.value) { - names.remove(a11yClsName) - updateServiceNames(names) - toast("关闭无障碍") - } else { - enableA11yService() - if (names.contains(a11yClsName)) { // 当前无障碍异常, 重启服务 + modifyA11yMutex.withLock { + if (!writeSecureSettingsState.updateAndGet()) { + toast("请先授予[写入安全设置权限]") + return@launchTry + } + val names = getServiceNames() + storeFlow.update { it.copy(enableService = !A11yService.isRunning.value) } + if (A11yService.isRunning.value) { names.remove(a11yClsName) updateServiceNames(names) - delay(500) - } - names.add(a11yClsName) - updateServiceNames(names) - delay(300) - if (!A11yService.isRunning.value) { - toast("开启无障碍失败") - accessRestrictedSettingsShowFlow.value = true - return@launchTry + toast("关闭无障碍") + } else { + enableA11yService() + if (names.contains(a11yClsName)) { // 当前无障碍异常, 重启服务 + names.remove(a11yClsName) + updateServiceNames(names) + delay(500) + } + names.add(a11yClsName) + updateServiceNames(names) + delay(300) + if (!A11yService.isRunning.value) { + toast("开启无障碍失败") + accessRestrictedSettingsShowFlow.value = true + return@launchTry + } + toast("开启无障碍") } - toast("开启无障碍") } } fun fixRestartService() = appScope.launchTry(Dispatchers.IO) { - // 1. 服务没有运行 - // 2. 用户配置开启了服务 - // 3. 有写入系统设置权限 - if (!A11yService.isRunning.value && storeFlow.value.enableService && writeSecureSettingsState.updateAndGet()) { - val t = System.currentTimeMillis() - if (t - lastRestartA11yServiceTimeFlow.value < 5_000) return@launchTry - lastRestartA11yServiceTimeFlow.value = t - val names = getServiceNames() - val a11yBroken = names.contains(a11yClsName) - if (a11yBroken) { - // 无障碍出现故障, 重启服务 - names.remove(a11yClsName) + modifyA11yMutex.withLock { + // 1. 服务没有运行 + // 2. 用户配置开启了服务 + // 3. 有写入系统设置权限 + if (!A11yService.isRunning.value && storeFlow.value.enableService && writeSecureSettingsState.updateAndGet()) { + val t = System.currentTimeMillis() + if (t - lastRestartA11yServiceTimeFlow.value < 3_000) return@launchTry + lastRestartA11yServiceTimeFlow.value = t + val names = getServiceNames() + val a11yBroken = names.contains(a11yClsName) + if (a11yBroken) { + // 无障碍出现故障, 重启服务 + names.remove(a11yClsName) + updateServiceNames(names) + // 必须等待一段时间, 否则概率不会触发系统重启无障碍服务 + delay(500) + } + names.add(a11yClsName) updateServiceNames(names) - // 必须等待一段时间, 否则概率不会触发系统重启无障碍服务 - delay(500) - } - names.add(a11yClsName) - updateServiceNames(names) - delay(300) - if (!A11yService.isRunning.value) { - toast("重启无障碍失败") - accessRestrictedSettingsShowFlow.value = true - return@launchTry + delay(300) + if (!A11yService.isRunning.value) { + toast("重启无障碍失败") + accessRestrictedSettingsShowFlow.value = true + return@launchTry + } + toast("重启无障碍") } - toast("重启无障碍") } } 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 659c856e0..4c45a6096 100644 --- a/app/src/main/kotlin/li/songe/gkd/shizuku/ShizukuApi.kt +++ b/app/src/main/kotlin/li/songe/gkd/shizuku/ShizukuApi.kt @@ -15,7 +15,10 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.flow.updateAndGet import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeoutOrNull import li.songe.gkd.META import li.songe.gkd.appScope import li.songe.gkd.data.DeviceInfo @@ -153,45 +156,48 @@ fun safeGetTopActivity(): TopActivity? { // return service.let(::ShizukuBinderWrapper).let(IPackageManager.Stub::asInterface) //} +private fun unbindUserService(serviceArgs: Shizuku.UserServiceArgs, connection: ServiceConnection) { + LogUtils.d("unbindUserService", serviceArgs) + Shizuku.unbindUserService(serviceArgs, connection, false) + Shizuku.unbindUserService(serviceArgs, connection, true) +} + data class UserServiceWrapper( val userService: IUserService, val connection: ServiceConnection, val serviceArgs: Shizuku.UserServiceArgs ) { fun destroy() { - try { - LogUtils.d("unbindUserService", serviceArgs) - userService.exit() - Shizuku.unbindUserService(serviceArgs, connection, true) - } catch (e: Exception) { - e.printStackTrace() - } + unbindUserService(serviceArgs, connection) + } + + fun execCommandForResult(command: String): Boolean? { + return userService.execCommandForResult(command) } } -private suspend fun serviceWrapper(): UserServiceWrapper = suspendCoroutine { continuation -> +private suspend fun buildServiceWrapper(): UserServiceWrapper? { val serviceArgs = Shizuku .UserServiceArgs(UserService::class.componentName) .daemon(false) .processNameSuffix("shizuku-user-service") .debuggable(META.debuggable) .version(META.versionCode) - - var resumeFc: ((UserServiceWrapper) -> Unit)? = { continuation.resume(it) } - + LogUtils.d("buildServiceWrapper", serviceArgs) + var resumeCallback: ((UserServiceWrapper) -> Unit)? = null val connection = object : ServiceConnection { override fun onServiceConnected(componentName: ComponentName, binder: IBinder?) { LogUtils.d("onServiceConnected", componentName) - resumeFc ?: return + resumeCallback ?: return if (binder?.pingBinder() == true) { - resumeFc?.invoke( + resumeCallback?.invoke( UserServiceWrapper( IUserService.Stub.asInterface(binder), this, serviceArgs ) ) - resumeFc = null + resumeCallback = null } else { LogUtils.d("invalid binder for $componentName received") } @@ -201,27 +207,16 @@ private suspend fun serviceWrapper(): UserServiceWrapper = suspendCoroutine { co LogUtils.d("onServiceDisconnected", componentName) } } - Shizuku.bindUserService(serviceArgs, connection) -} - -suspend fun execCommandForResult(command: String): Boolean { - serviceWrapperFlow.value?.userService?.let { - return it.execCommandForResult(command) == true - } - val wrapper = serviceWrapper() - return try { - wrapper.userService.execCommandForResult(command) == true - } finally { - wrapper.destroy() - } -} - -suspend fun shizukuCheckUserService(): Boolean { - return safeTap(0f, 0f) == true || try { - execCommandForResult("input tap 0 0") - } catch (e: Exception) { - e.printStackTrace() - false + return withTimeoutOrNull(1000) { + suspendCoroutine { continuation -> + resumeCallback = { continuation.resume(it) } + Shizuku.bindUserService(serviceArgs, connection) + } + }.apply { + if (this == null) { + toast("Shizuku获取绑定服务超时失败") + unbindUserService(serviceArgs, connection) + } } } @@ -231,27 +226,41 @@ private val shizukuServiceUsedFlow by lazy { }.stateIn(appScope, SharingStarted.Eagerly, false) } -val serviceWrapperFlow by lazy> { +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.update { it ?: buildServiceWrapper() } + } else { + stateFlow.update { it?.destroy(); null } } } } stateFlow } +suspend fun shizukuCheckUserService(): Boolean { + return try { + execCommandForResult("input tap 0 0") + } catch (e: Exception) { + e.printStackTrace() + false + } +} + +suspend fun execCommandForResult(command: String): Boolean { + return serviceWrapperFlow.updateAndGet { + it ?: buildServiceWrapper() + }?.execCommandForResult(command) == true +} + // 在 大麦 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") + return serviceWrapperFlow.value?.execCommandForResult("input tap $x $y") } private fun IUserService.execCommandForResult(command: String): Boolean? { 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 259ce360d..02a3577d1 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/AuthA11yPage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/AuthA11yPage.kt @@ -279,7 +279,12 @@ fun AuthA11yPage() { } } -private val innerCommandText by lazy { "pm grant ${META.appId} android.permission.WRITE_SECURE_SETTINGS; appops set ${META.appId} ACCESS_RESTRICTED_SETTINGS allow" } +private val innerCommandText by lazy { + arrayOf( + "appops set ${META.appId} ACCESS_RESTRICTED_SETTINGS allow", + "pm grant ${META.appId} android.permission.WRITE_SECURE_SETTINGS" + ).joinToString("; ") +} private val commandText by lazy { "adb shell \"${innerCommandText}\"" } private fun successAuthExec() { diff --git a/app/src/main/kotlin/li/songe/gkd/ui/AuthA11yVm.kt b/app/src/main/kotlin/li/songe/gkd/ui/AuthA11yVm.kt index c2dcb39bd..55b0dbc94 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/AuthA11yVm.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/AuthA11yVm.kt @@ -2,6 +2,7 @@ package li.songe.gkd.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.isActive @@ -10,7 +11,7 @@ import li.songe.gkd.permission.writeSecureSettingsState class AuthA11yVm : ViewModel() { init { - viewModelScope.launch { + viewModelScope.launch(Dispatchers.IO) { while (isActive) { writeSecureSettingsState.updateAndGet() delay(1000)