Skip to content

Commit 9bc386e

Browse files
committed
feat: automation mode
1 parent dcae919 commit 9bc386e

File tree

95 files changed

+2620
-1084
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+2620
-1084
lines changed

app/build.gradle.kts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ val debugSuffixPairList by lazy {
4747
plugins {
4848
alias(libs.plugins.android.application)
4949
alias(libs.plugins.androidx.room)
50-
alias(libs.plugins.kotlin.android)
50+
alias(libs.plugins.kotlin.parcelize)
5151
alias(libs.plugins.kotlin.serialization)
5252
alias(libs.plugins.kotlin.compose)
5353
alias(libs.plugins.kotlinx.atomicfu)
@@ -89,6 +89,7 @@ android {
8989
buildFeatures {
9090
compose = true
9191
aidl = true
92+
resValues = true
9293
}
9394

9495
val gkdSigningConfig = signingConfigs.create("gkd") {
@@ -106,7 +107,7 @@ android {
106107
keyPassword = project.properties["PLAY_KEY_PASSWORD"].toString()
107108
}
108109
} else {
109-
null
110+
gkdSigningConfig
110111
}
111112

112113
buildTypes {
@@ -141,11 +142,11 @@ android {
141142
resValue("bool", "is_accessibility_tool", "true")
142143
}
143144
create("play") {
144-
signingConfig = playSigningConfig ?: gkdSigningConfig
145+
signingConfig = playSigningConfig
145146
resValue("bool", "is_accessibility_tool", "false")
146147
}
147148
all {
148-
dimension = flavorDimensionList.first()
149+
dimension = flavorDimensions.first()
149150
manifestPlaceholders["channel"] = name
150151
}
151152
}
@@ -154,7 +155,7 @@ android {
154155
targetCompatibility = rootProject.ext["android.javaVersion"] as JavaVersion
155156
}
156157
dependenciesInfo.includeInApk = false
157-
packagingOptions.resources.excludes += setOf(
158+
packaging.resources.excludes += setOf(
158159
// https://github.com/Kotlin/kotlinx.coroutines/issues/2023
159160
"META-INF/**", "**/attach_hotspot_windows.dll",
160161

@@ -233,7 +234,6 @@ dependencies {
233234
compileOnly(project(":hidden_api"))
234235
implementation(libs.rikka.shizuku.api)
235236
implementation(libs.rikka.shizuku.provider)
236-
implementation(libs.rikka.refine.runtime)
237237
implementation(libs.lsposed.hiddenapibypass)
238238

239239
implementation(libs.androidx.room.runtime)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package li.songe.gkd.shizuku;
2+
3+
parcelable CommandResult;
Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
package li.songe.gkd.shizuku;
22

3+
import android.graphics.Bitmap;
4+
import android.graphics.Rect;
5+
import li.songe.gkd.shizuku.CommandResult;
6+
37
interface IUserService {
48
void destroy() = 16777114; // Destroy method defined by Shizuku server
5-
6-
void exit() = 1; // Exit method defined by user
7-
8-
String execCommand(String command) = 2;
9-
}
9+
void exit() = 1;
10+
CommandResult execCommand(String command) = 2;
11+
Bitmap takeScreenshot1(int width, int height) = 3;
12+
Bitmap takeScreenshot2(in Rect crop, int rotation) = 4;
13+
Bitmap takeScreenshot3(in Rect crop) = 5;
14+
}

app/src/main/kotlin/com/google/android/accessibility/selecttospeak/SelectToSpeakService.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,5 @@ package com.google.android.accessibility.selecttospeak
22

33
import li.songe.gkd.service.A11yService
44

5-
// https://github.com/ven-coder/Assists
65
// https://github.com/ven-coder/Assists/issues/12#issuecomment-2684469065
76
class SelectToSpeakService : A11yService()

app/src/main/kotlin/li/songe/gkd/App.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ import android.provider.Settings
1818
import android.view.WindowManager
1919
import android.view.accessibility.AccessibilityManager
2020
import android.view.inputmethod.InputMethodManager
21+
import androidx.core.content.ContextCompat
2122
import kotlinx.coroutines.MainScope
2223
import kotlinx.serialization.Serializable
24+
import li.songe.gkd.a11y.initA11yFeat
2325
import li.songe.gkd.data.selfAppInfo
2426
import li.songe.gkd.notif.initChannel
2527
import li.songe.gkd.service.clearHttpSubs
@@ -151,6 +153,11 @@ class App : Application() {
151153
return resolveAppId(intent)
152154
}
153155

156+
fun checkGrantedPermission(permission: String) = ContextCompat.checkSelfPermission(
157+
this,
158+
permission,
159+
) == PackageManager.PERMISSION_GRANTED
160+
154161
val startTime = System.currentTimeMillis()
155162
var justStarted: Boolean = true
156163
get() {
@@ -180,6 +187,7 @@ class App : Application() {
180187
initStore()
181188
initChannel()
182189
initAppState()
190+
initA11yFeat()
183191
initShizuku()
184192
initSubsState()
185193
initA11yWhiteAppList()

app/src/main/kotlin/li/songe/gkd/MainActivity.kt

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package li.songe.gkd
22

33
import android.app.Activity
4-
import android.app.ActivityManager
5-
import android.content.Context
64
import android.content.Intent
75
import android.os.Bundle
86
import androidx.activity.ComponentActivity
@@ -69,12 +67,10 @@ import li.songe.gkd.a11y.updateTopActivity
6967
import li.songe.gkd.permission.AuthDialog
7068
import li.songe.gkd.permission.updatePermissionState
7169
import li.songe.gkd.service.A11yService
72-
import li.songe.gkd.service.ButtonService
73-
import li.songe.gkd.service.HttpService
74-
import li.songe.gkd.service.ScreenshotService
7570
import li.songe.gkd.service.StatusService
76-
import li.songe.gkd.service.fixRestartService
77-
import li.songe.gkd.service.updateTopAppId
71+
import li.songe.gkd.service.fixRestartAutomatorService
72+
import li.songe.gkd.service.updateTopTaskAppId
73+
import li.songe.gkd.shizuku.automationRegisteredExceptionFlow
7874
import li.songe.gkd.shizuku.shizukuContextFlow
7975
import li.songe.gkd.store.storeFlow
8076
import li.songe.gkd.ui.component.BuildDialog
@@ -105,7 +101,6 @@ import li.songe.gkd.util.shizukuAppId
105101
import li.songe.gkd.util.throttle
106102
import li.songe.gkd.util.toast
107103
import kotlin.concurrent.Volatile
108-
import kotlin.reflect.KClass
109104
import kotlin.reflect.jvm.jvmName
110105

111106
class MainActivity : ComponentActivity() {
@@ -197,7 +192,7 @@ class MainActivity : ComponentActivity() {
197192
watchKeyboardVisible()
198193
StatusService.autoStart()
199194
if (storeFlow.value.enableBlockA11yAppList) {
200-
updateTopAppId(META.appId)
195+
updateTopTaskAppId(META.appId)
201196
}
202197
setContent {
203198
val latestInsets = TopAppBarDefaults.windowInsets
@@ -219,6 +214,7 @@ class MainActivity : ComponentActivity() {
219214
if (!mainVm.termsAcceptedFlow.collectAsState().value) {
220215
TermsAcceptDialog()
221216
} else {
217+
UiAutomationAlreadyRegisteredDlg()
222218
AccessRestrictedSettingsDlg()
223219
ShizukuErrorDialog(mainVm.shizukuErrorFlow)
224220
AuthDialog(mainVm.authReasonFlow)
@@ -290,7 +286,7 @@ class MainActivity : ComponentActivity() {
290286

291287
@Volatile
292288
private var activityVisibleState = 0
293-
fun isActivityVisible() = activityVisibleState > 0
289+
val isActivityVisible get() = activityVisibleState > 0
294290

295291
val activityNavSourceName by lazy { META.appId + ".activity.nav.source" }
296292

@@ -305,25 +301,6 @@ fun Activity.navToMainActivity() {
305301
finish()
306302
}
307303

308-
@Suppress("DEPRECATION")
309-
private fun updateServiceRunning() {
310-
A11yService.isRunning.value = A11yService.instance != null
311-
val list = try {
312-
val manager = app.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
313-
manager.getRunningServices(Int.MAX_VALUE) ?: emptyList()
314-
} catch (_: Exception) {
315-
emptyList()
316-
}
317-
318-
fun checkRunning(cls: KClass<*>): Boolean {
319-
return list.any { it.service.className == cls.jvmName }
320-
}
321-
StatusService.isRunning.value = checkRunning(StatusService::class)
322-
ButtonService.isRunning.value = checkRunning(ButtonService::class)
323-
ScreenshotService.isRunning.value = checkRunning(ScreenshotService::class)
324-
HttpService.isRunning.value = checkRunning(HttpService::class)
325-
}
326-
327304
private val syncStateMutex = Mutex()
328305
fun syncFixState() {
329306
appScope.launchTry(Dispatchers.IO) {
@@ -332,10 +309,9 @@ fun syncFixState() {
332309
}
333310
syncStateMutex.withLock {
334311
updateSystemDefaultAppId()
335-
updateServiceRunning()
336312
shizukuContextFlow.value.grantSelf()
337313
updatePermissionState()
338-
fixRestartService()
314+
fixRestartAutomatorService()
339315
}
340316
}
341317
}
@@ -455,7 +431,7 @@ fun AccessRestrictedSettingsDlg() {
455431
confirmButton = {
456432
TextButton({
457433
accessRestrictedSettingsShowFlow.value = false
458-
mainVm.navigatePage(AuthA11YPageDestination)
434+
mainVm.navigateWebPage(ShortUrlSet.URL2)
459435
}) {
460436
Text(text = "解除")
461437
}
@@ -470,3 +446,25 @@ fun AccessRestrictedSettingsDlg() {
470446
)
471447
}
472448
}
449+
450+
@Composable
451+
fun UiAutomationAlreadyRegisteredDlg() {
452+
if (automationRegisteredExceptionFlow.collectAsState().value != null) {
453+
AlertDialog(
454+
onDismissRequest = {
455+
automationRegisteredExceptionFlow.value = null
456+
},
457+
title = { Text(text = "启动失败") },
458+
text = {
459+
Text(text = "自动化服务启动失败,检测到自动化服务已被其他应用占用,请先关闭已有服务后重试\n\n注:自动化服务只能同时运行一个,请确保没有其他应用或测试框架占用后再启动")
460+
},
461+
confirmButton = {
462+
TextButton(onClick = {
463+
automationRegisteredExceptionFlow.value = null
464+
}) {
465+
Text(text = "我知道了")
466+
}
467+
}
468+
)
469+
}
470+
}

app/src/main/kotlin/li/songe/gkd/MainViewModel.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import li.songe.gkd.permission.canQueryPkgState
3434
import li.songe.gkd.permission.shizukuGrantedState
3535
import li.songe.gkd.service.A11yService
3636
import li.songe.gkd.shizuku.shizukuContextFlow
37+
import li.songe.gkd.shizuku.uiAutomationFlow
3738
import li.songe.gkd.shizuku.updateBinderMutex
3839
import li.songe.gkd.store.createTextFlow
3940
import li.songe.gkd.store.storeFlow
@@ -43,6 +44,7 @@ import li.songe.gkd.ui.component.RuleGroupState
4344
import li.songe.gkd.ui.component.UploadOptions
4445
import li.songe.gkd.ui.home.BottomNavItem
4546
import li.songe.gkd.ui.share.BaseViewModel
47+
import li.songe.gkd.util.AutomatorModeOption
4648
import li.songe.gkd.util.LOCAL_SUBS_ID
4749
import li.songe.gkd.util.LogUtils
4850
import li.songe.gkd.util.OnSimpleLife
@@ -51,6 +53,7 @@ import li.songe.gkd.util.UpdateStatus
5153
import li.songe.gkd.util.appIconMapFlow
5254
import li.songe.gkd.util.clearCache
5355
import li.songe.gkd.util.client
56+
import li.songe.gkd.util.findOption
5457
import li.songe.gkd.util.launchTry
5558
import li.songe.gkd.util.openUri
5659
import li.songe.gkd.util.openWeChatScaner
@@ -324,6 +327,17 @@ class MainViewModel : BaseViewModel(), OnSimpleLife {
324327
list.any { it != A11yService.a11yCn }
325328
}
326329

330+
val automatorModeFlow = storeFlow.mapNew {
331+
AutomatorModeOption.objects.findOption(it.automatorMode)
332+
}
333+
334+
fun updateAutomatorMode(option: AutomatorModeOption) {
335+
if (automatorModeFlow.value == option) return
336+
storeFlow.update { it.copy(automatorMode = option.value, enableAutomator = false) }
337+
A11yService.instance?.shutdown()
338+
uiAutomationFlow.value?.shutdown()
339+
}
340+
327341
init {
328342
// preload
329343
appIconMapFlow.value
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package li.songe.gkd.a11y
2+
3+
import android.graphics.Bitmap
4+
import android.view.accessibility.AccessibilityNodeInfo
5+
import kotlinx.coroutines.CoroutineScope
6+
import li.songe.gkd.util.AutomatorModeOption
7+
8+
interface A11yCommonImpl {
9+
suspend fun screenshot(): Bitmap?
10+
val windowNodeInfo: AccessibilityNodeInfo?
11+
val scope: CoroutineScope
12+
var justStarted: Boolean
13+
val mode: AutomatorModeOption
14+
val ruleEngine: A11yRuleEngine
15+
fun shutdown(temp: Boolean = false)
16+
}

0 commit comments

Comments
 (0)