Skip to content

Commit a797400

Browse files
authored
Merge pull request #2116 from keymapperorg/claude/2081-getevent-debug-screen
Add Expert Mode debug screen for raw getevent output (#2081)
2 parents e178bbf + 6532c66 commit a797400

11 files changed

Lines changed: 814 additions & 0 deletions

File tree

base/src/main/java/io/github/sds100/keymapper/base/BaseMainNavHost.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import io.github.sds100.keymapper.base.actions.uielement.InteractUiElementScreen
2525
import io.github.sds100.keymapper.base.actions.uielement.InteractUiElementViewModel
2626
import io.github.sds100.keymapper.base.constraints.ChooseConstraintScreen
2727
import io.github.sds100.keymapper.base.constraints.ChooseConstraintViewModel
28+
import io.github.sds100.keymapper.base.debug.GetEventScreen
2829
import io.github.sds100.keymapper.base.expertmode.ExpertModeScreen
2930
import io.github.sds100.keymapper.base.expertmode.ExpertModeSetupScreen
3031
import io.github.sds100.keymapper.base.logging.LogScreen
@@ -165,6 +166,14 @@ fun BaseMainNavHost(
165166
)
166167
}
167168

169+
composable<NavDestination.GetEvent> {
170+
GetEventScreen(
171+
modifier = Modifier.fillMaxSize(),
172+
viewModel = hiltViewModel(),
173+
onBackClick = { navController.popBackStack() },
174+
)
175+
}
176+
168177
composable<NavDestination.ChooseSetting> {
169178
ChooseSettingScreen(
170179
modifier = Modifier.fillMaxSize(),

base/src/main/java/io/github/sds100/keymapper/base/BaseViewModelHiltModule.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import io.github.sds100.keymapper.base.constraints.ConfigConstraintsUseCaseImpl
2525
import io.github.sds100.keymapper.base.constraints.CreateConstraintUseCase
2626
import io.github.sds100.keymapper.base.constraints.CreateConstraintUseCaseImpl
2727
import io.github.sds100.keymapper.base.constraints.DisplayConstraintUseCase
28+
import io.github.sds100.keymapper.base.debug.GetEventOutputUseCase
29+
import io.github.sds100.keymapper.base.debug.GetEventOutputUseCaseImpl
2830
import io.github.sds100.keymapper.base.expertmode.ExpertModeSetupDelegateImpl
2931
import io.github.sds100.keymapper.base.expertmode.SystemBridgeSetupDelegate
3032
import io.github.sds100.keymapper.base.expertmode.SystemBridgeSetupUseCase
@@ -185,6 +187,10 @@ abstract class BaseViewModelHiltModule {
185187
@ViewModelScoped
186188
abstract fun bindShareLogcatUseCase(impl: ShareLogcatUseCaseImpl): ShareLogcatUseCase
187189

190+
@Binds
191+
@ViewModelScoped
192+
abstract fun bindGetEventOutputUseCase(impl: GetEventOutputUseCaseImpl): GetEventOutputUseCase
193+
188194
@Binds
189195
@ViewModelScoped
190196
abstract fun bindOnboardingTipDelegate(impl: OnboardingTipDelegateImpl): OnboardingTipDelegate
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package io.github.sds100.keymapper.base.debug
2+
3+
import android.content.Context
4+
import androidx.core.net.toUri
5+
import dagger.hilt.android.qualifiers.ApplicationContext
6+
import io.github.sds100.keymapper.base.actions.ExecuteShellCommandUseCase
7+
import io.github.sds100.keymapper.base.utils.ShareUtils
8+
import io.github.sds100.keymapper.base.utils.getFullMessage
9+
import io.github.sds100.keymapper.base.utils.ui.ResourceProvider
10+
import io.github.sds100.keymapper.common.BuildConfigProvider
11+
import io.github.sds100.keymapper.common.models.ShellExecutionMode
12+
import io.github.sds100.keymapper.common.utils.handle
13+
import io.github.sds100.keymapper.data.Keys
14+
import io.github.sds100.keymapper.data.repositories.PreferenceRepository
15+
import io.github.sds100.keymapper.system.clipboard.ClipboardAdapter
16+
import io.github.sds100.keymapper.system.files.FileAdapter
17+
import io.github.sds100.keymapper.system.files.FileUtils
18+
import io.github.sds100.keymapper.system.files.IFile
19+
import javax.inject.Inject
20+
import kotlinx.coroutines.Dispatchers
21+
import kotlinx.coroutines.flow.Flow
22+
import kotlinx.coroutines.flow.map
23+
import kotlinx.coroutines.withContext
24+
25+
interface GetEventOutputUseCase {
26+
val deviceInfoOutput: Flow<String>
27+
val eventsOutput: Flow<String>
28+
29+
suspend fun refreshDeviceInfo()
30+
suspend fun recordEvents()
31+
suspend fun stopRecording()
32+
fun copyOutput(output: String)
33+
suspend fun shareOutput(output: String)
34+
}
35+
36+
class GetEventOutputUseCaseImpl @Inject constructor(
37+
@ApplicationContext private val context: Context,
38+
private val executeShellCommandUseCase: ExecuteShellCommandUseCase,
39+
private val preferenceRepository: PreferenceRepository,
40+
private val clipboardAdapter: ClipboardAdapter,
41+
private val fileAdapter: FileAdapter,
42+
private val buildConfigProvider: BuildConfigProvider,
43+
private val resourceProvider: ResourceProvider,
44+
) : GetEventOutputUseCase {
45+
46+
companion object {
47+
private const val MAX_COPY_OUTPUT_LENGTH = 150_000
48+
}
49+
50+
override val deviceInfoOutput: Flow<String> = preferenceRepository
51+
.get(Keys.getEventDeviceInfoOutput)
52+
.map { it.orEmpty() }
53+
54+
override val eventsOutput: Flow<String> = preferenceRepository
55+
.get(Keys.getEventEventsOutput)
56+
.map { it.orEmpty() }
57+
58+
override suspend fun refreshDeviceInfo() {
59+
val output = executeShellCommandUseCase.execute(
60+
command = "getevent -il",
61+
executionMode = ShellExecutionMode.ADB,
62+
timeoutMillis = 30_000L,
63+
).handle(
64+
onSuccess = { it.stdout },
65+
onError = { "Error: ${it.getFullMessage(resourceProvider)}" },
66+
)
67+
preferenceRepository.set(Keys.getEventDeviceInfoOutput, output)
68+
}
69+
70+
override suspend fun recordEvents() {
71+
val output = executeShellCommandUseCase.execute(
72+
command = "getevent -lt",
73+
executionMode = ShellExecutionMode.ADB,
74+
timeoutMillis = 300_000L,
75+
).handle(
76+
onSuccess = { it.stdout },
77+
onError = { "" },
78+
)
79+
if (output.isNotEmpty()) {
80+
preferenceRepository.set(Keys.getEventEventsOutput, output)
81+
}
82+
}
83+
84+
override suspend fun stopRecording() {
85+
executeShellCommandUseCase.execute(
86+
command = "pkill -x getevent || true",
87+
executionMode = ShellExecutionMode.ADB,
88+
timeoutMillis = 5_000L,
89+
)
90+
}
91+
92+
override fun copyOutput(output: String) {
93+
clipboardAdapter.copy(
94+
"getevent output",
95+
output.takeLast(MAX_COPY_OUTPUT_LENGTH),
96+
)
97+
}
98+
99+
override suspend fun shareOutput(output: String) {
100+
withContext(Dispatchers.IO) {
101+
val fileName = "getevent/key_mapper_getevent_${FileUtils.createFileDate()}.txt"
102+
val file: IFile = fileAdapter.getPrivateFile(fileName)
103+
file.createFile()
104+
file.outputStream()?.bufferedWriter()?.use { it.write(output) }
105+
106+
val publicUri = fileAdapter.getPublicUriForPrivateFile(file).toUri()
107+
ShareUtils.shareFile(context, publicUri, buildConfigProvider.packageName)
108+
}
109+
}
110+
}

0 commit comments

Comments
 (0)