Skip to content

Commit 92c6bc4

Browse files
committed
Feat:
- Added App opener and killer. - Added desktop icon for windows(?) and linux(?) -- definitely untested.
1 parent 20eea46 commit 92c6bc4

File tree

3 files changed

+139
-73
lines changed

3 files changed

+139
-73
lines changed

composeApp/src/desktopMain/kotlin/id/neotica/modernadb/data/adb/android/AdbInput.kt

Lines changed: 91 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import java.io.File
44
import java.util.concurrent.TimeUnit
55

66
object AdbInput {
7+
8+
// --- ADB Configuration ---
79
var selectedDevice: String? = null
810

911
private val adbExecutablePath: String by lazy {
@@ -22,51 +24,7 @@ object AdbInput {
2224
found?.absolutePath ?: error("ADB not found in common locations. Please install Android SDK.")
2325
}
2426

25-
private fun exec(command: String, waitAfter: Long = 100): Process {
26-
val commandParts = mutableListOf<String>()
27-
commandParts.add(adbExecutablePath) // Always start with the full path to adb
28-
29-
selectedDevice?.let { deviceId ->
30-
if (command != "devices") {
31-
commandParts.add("-s")
32-
commandParts.add(deviceId)
33-
}
34-
}
35-
36-
// Add the the command parts
37-
if (command.startsWith("install ")) {
38-
// Split the command into only two parts: "install" and the file path.
39-
// The 'limit = 2' is crucial as it stops splitting after the first space.
40-
val parts = command.split(" ", limit = 2)
41-
commandParts.addAll(parts)
42-
} else {
43-
// Keep the original logic for all other commands.
44-
commandParts.addAll(command.split(" "))
45-
}
46-
47-
println("DEBUG: Executing command parts: $commandParts")
48-
49-
val processBuilder = ProcessBuilder(commandParts)
50-
51-
val environment = processBuilder.environment()
52-
val userHome = System.getProperty("user.home")
53-
environment["HOME"] = userHome
54-
environment["ANDROID_SDK_HOME"] = "$userHome/.android"
55-
56-
val process = processBuilder.redirectErrorStream(true).start()
57-
58-
val finished = process.waitFor(5, TimeUnit.SECONDS)
59-
if (!finished) {
60-
println("DEBUG: Process timed out. Forcibly destroying.")
61-
process.destroyForcibly()
62-
}
63-
64-
Thread.sleep(waitAfter)
65-
return process
66-
}
67-
68-
// --- ADB Public Methods ---
69-
27+
// --- Device Management ---
7028
fun getDevices(): String {
7129
return try {
7230
val process = exec("devices")
@@ -90,7 +48,12 @@ object AdbInput {
9048
}
9149
}
9250

93-
//power
51+
fun connectWireless(port: String): String {
52+
val connect = exec("connect $port")
53+
return connect.inputStream.bufferedReader().readText()
54+
}
55+
56+
// --- Power & Boot Commands ---
9457
fun powerButton() = exec("shell input keyevent 26")
9558
fun longPressPowerButton() = exec("shell input keyevent --longpress 26")
9659
fun shutdownLegacy() = exec("shell shutdown")
@@ -101,7 +64,8 @@ object AdbInput {
10164

10265
fun isAwake(): Boolean {
10366
return try {
104-
val output = exec("shell dumpsys power | grep \"mWakefulness=\"").inputStream.bufferedReader().readText()
67+
val output = exec("shell dumpsys power | grep \"mWakefulness=\"")
68+
.inputStream.bufferedReader().readText()
10569
output.contains("Awake")
10670
} catch (e: Exception) {
10771
println("ERROR reading isAwake stream: ${e.message}")
@@ -118,57 +82,117 @@ object AdbInput {
11882
println("Screen is OFF. Sending wakeup command...")
11983
exec("shell input keyevent KEYCODE_WAKEUP")
12084
Thread.sleep(500)
121-
unlock(password) //recursive calls, edit later to have attempts.
85+
unlock(password) // recursive call (can be improved later)
12286
}
12387
}
12488

89+
// --- Text Input ---
12590
fun sendText(message: String) {
12691
val formatted = formatMessage(message)
12792
exec("shell input text \"$formatted\"")
12893
}
12994

13095
fun sendEnter() = exec("shell input keyevent KEYCODE_ENTER")
13196
fun sendKey(keyCode: Int) = exec("shell input keyevent $keyCode")
132-
fun logCat(): String {
133-
val read = exec("shell logcat").inputStream.bufferedReader().readText()
134-
println("logcat $read")
135-
return read
136-
}
97+
98+
// --- Navigation / System UI ---
13799
fun homeButton() = exec("shell am start -W -c android.intent.category.HOME -a android.intent.action.MAIN")
138100
fun backButton() = exec("shell input keyevent KEYCODE_BACK")
139101
fun recentButton() = exec("shell am start -n com.android.systemui/com.android.systemui.recents.RecentsActivity")
140102
fun switchApp() = exec("shell input keyevent KEYCODE_APP_SWITCH")
103+
141104
fun nextButton() = exec("shell input keyevent 22")
142105
fun prevButton() = exec("shell input keyevent 21")
143106
fun upButton() = exec("shell input keyevent 19")
144107
fun downButton() = exec("shell input keyevent 20")
108+
145109
fun backspaceButton() = exec("shell input keyevent 67")
146110
fun tabButton() = exec("shell input keyevent 61")
147-
fun selectAll() = exec("shell input keycombination 113 29")
148111
fun shiftTab() = exec("shell input keycombination 59 61")
112+
fun selectAll() = exec("shell input keycombination 113 29")
113+
149114
fun touchInput(x: Int, y: Int) = exec("shell input tap $x $y")
115+
116+
fun holdInputTime(
117+
startX: Int,
118+
startY: Int,
119+
endX: Int? = startX,
120+
endY: Int? = startY,
121+
time: Int? = 500
122+
) = exec("shell input swipe $startX $startY $endX $endY $time")
123+
124+
fun swipeUp() = holdInputTime(500, 2000, 500, 500, 100)
125+
fun swipeDown() = holdInputTime(500, 500, 500, 2000, 100)
126+
127+
// --- APK / App Management ---
128+
fun install(apk: String): String {
129+
val install = exec("install $apk")
130+
return install.inputStream.bufferedReader().readText()
131+
}
132+
133+
fun openApp(packageName: String, activityName: String = ""): String {
134+
val cmd = if (activityName.isNotEmpty()) {
135+
"shell am start -n $packageName/.$activityName"
136+
} else {
137+
"shell am start $packageName"
138+
}
139+
140+
val open = exec(cmd)
141+
return open.inputStream.bufferedReader().readText()
142+
}
143+
150144
fun forceClose() {
151145
switchApp()
152146
holdInputTime(500, 1000, endY = 100, time = 100)
153147
}
154-
fun swipeUp() = holdInputTime(500, 2000, 500, 500, 100)
155-
fun swipeDown() = holdInputTime(500, 500, 500, 2000, 100)
156-
fun holdInputTime(
157-
startX: Int, startY: Int, endX: Int? = startX, endY: Int? = startY, time: Int? = 500
158-
) = exec("shell input swipe $startX $startY $endX $endY $time")
159148

160149
fun activityManager() = exec("shell am start -a android.intent.action.VIEW")
161150

162-
//adb connect wifi adb
163-
fun connectWireless(port: String): String {
164-
val connect = exec("connect $port")
165-
return connect.inputStream.bufferedReader().readText()
151+
// --- Logcat ---
152+
fun logCat(): String {
153+
val read = exec("shell logcat").inputStream.bufferedReader().readText()
154+
println("logcat $read")
155+
return read
166156
}
167157

168-
//apk install
169-
fun install(apk: String): String {
170-
val install = exec("install $apk")
171-
return install.inputStream.bufferedReader().readText()
158+
// --- Private Helpers ---
159+
private fun exec(command: String, waitAfter: Long = 100): Process {
160+
val commandParts = mutableListOf<String>()
161+
commandParts.add(adbExecutablePath)
162+
163+
selectedDevice?.let { deviceId ->
164+
if (command != "devices") {
165+
commandParts.add("-s")
166+
commandParts.add(deviceId)
167+
}
168+
}
169+
170+
if (command.startsWith("install ")) {
171+
val parts = command.split(" ", limit = 2)
172+
commandParts.addAll(parts)
173+
} else {
174+
commandParts.addAll(command.split(" "))
175+
}
176+
177+
println("DEBUG: Executing command parts: $commandParts")
178+
179+
val processBuilder = ProcessBuilder(commandParts)
180+
181+
val environment = processBuilder.environment()
182+
val userHome = System.getProperty("user.home")
183+
environment["HOME"] = userHome
184+
environment["ANDROID_SDK_HOME"] = "$userHome/.android"
185+
186+
val process = processBuilder.redirectErrorStream(true).start()
187+
val finished = process.waitFor(5, TimeUnit.SECONDS)
188+
189+
if (!finished) {
190+
println("DEBUG: Process timed out. Forcibly destroying.")
191+
process.destroyForcibly()
192+
}
193+
194+
Thread.sleep(waitAfter)
195+
return process
172196
}
173197

174198
private fun formatMessage(input: String): String {

composeApp/src/desktopMain/kotlin/id/neotica/modernadb/main.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ import androidx.compose.foundation.layout.fillMaxSize
66
import androidx.compose.ui.Modifier
77
import androidx.compose.ui.window.Window
88
import androidx.compose.ui.window.application
9+
import dev.icerock.moko.resources.compose.painterResource
910
import id.neotica.modernadb.presentation.theme.DarkBackground
11+
import id.neotica.modernadb.res.MR
1012

1113
fun main() = application {
1214
Window(
1315
onCloseRequest = ::exitApplication,
1416
title = "ModernADB",
15-
// transparent = true,
16-
// undecorated = true
17+
icon = painterResource(MR.images.ModernADB)
1718
) {
1819
Box(
1920
Modifier.fillMaxSize()

composeApp/src/desktopMain/kotlin/id/neotica/modernadb/presentation/DeviceListView.kt

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import id.neotica.modernadb.presentation.components.NeoCardSolid
2929
import id.neotica.modernadb.presentation.components.NeoIcon
3030
import id.neotica.modernadb.presentation.components.NeoText
3131
import id.neotica.modernadb.presentation.components.NeoTextFieldColor
32+
import id.neotica.modernadb.presentation.components.NeoToolTip
3233
import id.neotica.modernadb.presentation.theme.DarkPrimary
3334
import id.neotica.modernadb.res.MR
3435
import id.neotica.modernadb.utils.toast.ToastDurationType
@@ -93,7 +94,6 @@ fun DeviceListView() {
9394
)
9495
}
9596
}
96-
9797
}
9898
}
9999

@@ -110,6 +110,8 @@ fun DeviceListView() {
110110
}
111111
var expanded by remember { mutableStateOf(false) }
112112
var port by remember { mutableStateOf("") }
113+
var packageName by remember { mutableStateOf("") }
114+
var outputStatus by remember { mutableStateOf("") }
113115
Text(
114116
text = "···",
115117
modifier = Modifier
@@ -143,12 +145,51 @@ fun DeviceListView() {
143145
ButtonBasic("Connect") {
144146
val connect = AdbInput.connectWireless(port)
145147
println("$connect")
148+
outputStatus = connect
146149
}
147-
}
148-
}
149150

151+
NeoText(
152+
text = "Open app using package"
153+
)
154+
TextField(
155+
colors = NeoTextFieldColor(),
156+
value = packageName,
157+
label = { NeoText("Package Name") },
158+
onValueChange = {
159+
if (!it.contains('\n')) {
160+
packageName = it
161+
}
162+
},
163+
)
164+
var activityName by remember { mutableStateOf("") }
165+
TextField(
166+
colors = NeoTextFieldColor(),
167+
value = activityName,
168+
label = { NeoText("Activity Name") },
169+
onValueChange = {
170+
if (!it.contains('\n')) {
171+
activityName = it
172+
}
173+
},
174+
)
175+
Row(
176+
horizontalArrangement = Arrangement.spacedBy(16.dp),
177+
) {
178+
ButtonBasic("Open") {
179+
val open = AdbInput.openApp(packageName, activityName)
180+
println("$open")
181+
outputStatus = open
182+
}
183+
NeoToolTip("Currently Only Working on Android 14 and up that use swipe navigation.\n Only kill current screen because its using manual swipe.") {
184+
ButtonBasic("Kill") {
185+
AdbInput.forceClose()
186+
}
187+
}
150188

189+
}
190+
NeoText("output: $outputStatus")
191+
}
192+
}
151193
}
152-
153194
}
154195
}

0 commit comments

Comments
 (0)