Skip to content

Commit

Permalink
Replace RxShell with FlowShell (#1529)
Browse files Browse the repository at this point in the history
* FlowShell

* `FlowProcess` implementation with tests

TODO: root handling?

* Wip

* wip

* wip

* Further work on process and shell
WIP: Next up is a command shell

* Working implementation
Remove rxshell dependency

* Fix cancellation behavior
If we prematurely cancel a command and submit another one, the previous commends output may confuse the next one.

* Fix output and errors being missed due to `onStart` triggering too early, we need `onSubscription`

* Clean up debug output

* Fix flaky tests

* Fix shared shell closing behavior
  • Loading branch information
d4rken authored Jan 22, 2025
1 parent c372ef8 commit 456aaf9
Show file tree
Hide file tree
Showing 29 changed files with 1,386 additions and 139 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package eu.darken.sdmse.common

import android.os.Build
import eu.darken.rxshell.cmd.Cmd
import eu.darken.rxshell.cmd.RxCmdShell
import eu.darken.flowshell.core.cmd.FlowCmd
import eu.darken.flowshell.core.cmd.FlowCmdShell
import eu.darken.flowshell.core.cmd.execute
import eu.darken.flowshell.core.process.FlowProcess
import eu.darken.sdmse.common.debug.logging.Logging.Priority.INFO
import eu.darken.sdmse.common.debug.logging.log
import eu.darken.sdmse.common.debug.logging.logTag
Expand Down Expand Up @@ -38,13 +40,13 @@ class MountMaster @Inject constructor(
}

log(TAG) { "Checking for mount-master support..." }
val result = Cmd.builder("su --help").execute(RxCmdShell.builder().root(true).build())
if (result.exitCode != Cmd.ExitCode.OK) {
log(TAG, INFO) { "mount-master check failed: ${result.merge()}" }
val result = FlowCmd("su --help").execute(FlowCmdShell("su"))
if (result.exitCode != FlowProcess.ExitCode.OK) {
log(TAG, INFO) { "mount-master check failed: ${result.merged}" }
return false
}

val supported = result.merge().any { it.contains("--mount-master") }
val supported = result.merged.any { it.contains("--mount-master") }
log(TAG, INFO) { "mount-master is required and current support status is supported=$supported" }
return supported
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import android.content.Context
import androidx.annotation.Keep
import dagger.Lazy
import dagger.hilt.android.qualifiers.ApplicationContext
import eu.darken.rxshell.cmd.Cmd
import eu.darken.rxshell.cmd.RxCmdShell
import eu.darken.flowshell.core.cmd.FlowCmd
import eu.darken.flowshell.core.cmd.execute
import eu.darken.sdmse.common.adb.AdbServiceConnection
import eu.darken.sdmse.common.debug.logging.Logging.Priority.INFO
import eu.darken.sdmse.common.debug.logging.log
Expand All @@ -16,6 +16,7 @@ import eu.darken.sdmse.common.pkgs.pkgops.ipc.PkgOpsConnection
import eu.darken.sdmse.common.pkgs.pkgops.ipc.PkgOpsHost
import eu.darken.sdmse.common.shell.ipc.ShellOpsConnection
import eu.darken.sdmse.common.shell.ipc.ShellOpsHost
import kotlinx.coroutines.runBlocking
import javax.inject.Inject
import javax.inject.Singleton

Expand All @@ -35,8 +36,8 @@ class AdbServiceHost @Inject constructor(
override fun checkBase(): String {
val sb = StringBuilder()
sb.append("Our pkg: ${context.packageName}\n")
val ids = Cmd.builder("id").submit(RxCmdShell.Builder().build()).blockingGet()
sb.append("Shell ids are: ${ids.merge()}\n")
val ids = runBlocking { FlowCmd("id").execute() }
sb.append("Shell ids are: ${ids.merged}\n")
val result = sb.toString()
log(TAG) { "checkBase(): $result" }
return result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import dagger.hilt.android.qualifiers.ApplicationContext
import eu.darken.rxshell.cmd.Cmd
import eu.darken.flowshell.core.cmd.FlowCmd
import eu.darken.flowshell.core.cmd.execute
import eu.darken.flowshell.core.process.FlowProcess
import eu.darken.sdmse.common.debug.Bugs
import eu.darken.sdmse.common.debug.Bugs.isDryRun
import eu.darken.sdmse.common.debug.logging.Logging.Priority.ERROR
Expand Down Expand Up @@ -65,8 +67,8 @@ class PkgOpsHost @Inject constructor(
log(TAG, ERROR) { "isRunning($packageName): runningAppProcesses failed due to $e " }
runBlocking {
sharedShell.useRes {
Cmd.builder("pidof $packageName").execute(it)
}.exitCode == Cmd.ExitCode.OK
FlowCmd("pidof $packageName").execute(it)
}.exitCode == FlowProcess.ExitCode.OK
}
}
log(TAG, VERBOSE) { "isRunning(packageName=$packageName)=$result" }
Expand Down Expand Up @@ -162,10 +164,10 @@ class PkgOpsHost @Inject constructor(
log(TAG, VERBOSE) { "grantPermission($packageName, $handleId, $permissionId)..." }
val result = runBlocking {
sharedShell.useRes {
Cmd.builder("pm grant --user $handleId $packageName $permissionId").execute(it)
FlowCmd("pm grant --user $handleId $packageName $permissionId").execute(it)
}
}
result.exitCode == Cmd.ExitCode.OK
result.exitCode == FlowProcess.ExitCode.OK
} catch (e: Exception) {
log(TAG, ERROR) { "grantPermission($packageName, $handleId, $permissionId) failed: ${e.asLog()}" }
throw e.wrapToPropagate()
Expand All @@ -175,10 +177,10 @@ class PkgOpsHost @Inject constructor(
log(TAG, VERBOSE) { "revokePermission($packageName, $handleId, $permissionId)..." }
val result = runBlocking {
sharedShell.useRes {
Cmd.builder("pm revoke --user $handleId $packageName $permissionId").execute(it)
FlowCmd("pm revoke --user $handleId $packageName $permissionId").execute(it)
}
}
result.exitCode == Cmd.ExitCode.OK
result.exitCode == FlowProcess.ExitCode.OK
} catch (e: Exception) {
log(TAG, ERROR) { "revokePermission($packageName, $handleId, $permissionId) failed: ${e.asLog()}" }
throw e.wrapToPropagate()
Expand All @@ -188,10 +190,10 @@ class PkgOpsHost @Inject constructor(
log(TAG, VERBOSE) { "setAppOps($packageName, $handleId, $key, $value)..." }
val result = runBlocking {
sharedShell.useRes {
Cmd.builder("appops set --user $handleId $packageName $key $value ").execute(it)
FlowCmd("appops set --user $handleId $packageName $key $value ").execute(it)
}
}
result.exitCode == Cmd.ExitCode.OK
result.exitCode == FlowProcess.ExitCode.OK
} catch (e: Exception) {
log(TAG, ERROR) { "setAppOps($packageName, $handleId, $key, $value) failed: ${e.asLog()}" }
throw e.wrapToPropagate()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import android.content.Context
import androidx.annotation.Keep
import dagger.Lazy
import dagger.hilt.android.qualifiers.ApplicationContext
import eu.darken.rxshell.cmd.Cmd
import eu.darken.rxshell.cmd.RxCmdShell
import eu.darken.flowshell.core.cmd.FlowCmd
import eu.darken.flowshell.core.cmd.execute
import eu.darken.sdmse.common.debug.logging.log
import eu.darken.sdmse.common.debug.logging.logTag
import eu.darken.sdmse.common.files.local.ipc.FileOpsConnection
Expand All @@ -14,6 +14,7 @@ import eu.darken.sdmse.common.pkgs.pkgops.ipc.PkgOpsConnection
import eu.darken.sdmse.common.pkgs.pkgops.ipc.PkgOpsHost
import eu.darken.sdmse.common.shell.ipc.ShellOpsConnection
import eu.darken.sdmse.common.shell.ipc.ShellOpsHost
import kotlinx.coroutines.runBlocking
import javax.inject.Inject
import javax.inject.Singleton

Expand All @@ -29,8 +30,8 @@ class RootServiceHost @Inject constructor(
override fun checkBase(): String {
val sb = StringBuilder()
sb.append("Our pkg: ${context.packageName}\n")
val ids = Cmd.builder("id").submit(RxCmdShell.Builder().build()).blockingGet()
sb.append("Shell ids are: ${ids.merge()}\n")
val ids = runBlocking { FlowCmd("id").execute() }
sb.append("Shell ids are: ${ids.merged}\n")
val result = sb.toString()
log(TAG) { "checkBase(): $result" }
return result
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package eu.darken.sdmse.common.shell

import eu.darken.rxshell.cmd.Cmd
import eu.darken.rxshell.cmd.RxCmdShell
import eu.darken.flowshell.core.cmd.FlowCmd
import eu.darken.flowshell.core.cmd.execute
import eu.darken.sdmse.common.adb.AdbManager
import eu.darken.sdmse.common.adb.AdbUnavailableException
import eu.darken.sdmse.common.adb.canUseAdbNow
Expand Down Expand Up @@ -66,9 +66,7 @@ class ShellOps @Inject constructor(
var result: ShellOpsResult? = null
if (mode == Mode.NORMAL) {
log(TAG, VERBOSE) { "execute(mode->NORMAL): $cmd" }
result = cmd.toRxCmdBuilder()
.execute(RxCmdShell.builder().build())
.toShellOpsResult()
result = cmd.toFlowCmd().execute().toShellOpsResult()
}

if (result == null && rootManager.canUseRootNow() && mode == Mode.ROOT) {
Expand All @@ -94,10 +92,10 @@ class ShellOps @Inject constructor(
}
}

private fun ShellOpsCmd.toRxCmdBuilder() = Cmd.builder(cmds)
private fun ShellOpsCmd.toFlowCmd() = FlowCmd(cmds)

private fun Cmd.Result.toShellOpsResult() = ShellOpsResult(
exitCode = exitCode,
private fun FlowCmd.Result.toShellOpsResult() = ShellOpsResult(
exitCode = exitCode.value,
output = output,
errors = errors
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package eu.darken.sdmse.common.shell.ipc

import eu.darken.rxshell.cmd.Cmd
import eu.darken.flowshell.core.cmd.FlowCmd
import eu.darken.flowshell.core.cmd.execute
import eu.darken.sdmse.common.coroutine.AppScope
import eu.darken.sdmse.common.coroutine.DispatcherProvider
import eu.darken.sdmse.common.debug.Bugs
Expand All @@ -25,10 +26,10 @@ class ShellOpsHost @Inject constructor(
override fun execute(cmd: ShellOpsCmd): ShellOpsResult = try {
runBlocking {
val result = sharedShell.useRes {
Cmd.builder(cmd.cmds).execute(it)
FlowCmd(cmd.cmds).execute(it)
}
ShellOpsResult(
exitCode = result.exitCode,
exitCode = result.exitCode.value,
output = result.output,
errors = result.errors
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package eu.darken.sdmse.common.shell.ipc

import android.os.Parcelable
import eu.darken.rxshell.cmd.Cmd
import kotlinx.parcelize.Parcelize

@Parcelize
Expand All @@ -12,5 +11,5 @@ data class ShellOpsResult(
) : Parcelable {

val isSuccess: Boolean
get() = exitCode == Cmd.ExitCode.OK
get() = exitCode == 0
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import android.os.Build
import android.os.Process
import android.util.Base64
import androidx.annotation.RequiresApi
import eu.darken.rxshell.cmd.Cmd
import eu.darken.flowshell.core.cmd.FlowCmd
import eu.darken.sdmse.common.debug.logging.Logging.Priority.ERROR
import eu.darken.sdmse.common.debug.logging.Logging.Priority.WARN
import eu.darken.sdmse.common.debug.logging.asLog
Expand All @@ -17,7 +17,7 @@ import eu.darken.sdmse.common.parcel.marshall
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import java.util.*
import java.util.UUID
import kotlin.reflect.KClass

/**
Expand All @@ -28,7 +28,7 @@ import kotlin.reflect.KClass
*/

@SuppressLint("PrivateApi")
class RootHostCmdBuilder<Host : BaseRootHost> constructor(
class RootHostCmdBuilder<Host : BaseRootHost>(
private val context: Context,
private val rootHost: KClass<Host>,
) {
Expand All @@ -43,14 +43,12 @@ class RootHostCmdBuilder<Host : BaseRootHost> constructor(
Class.forName("android.os.SystemProperties")
}

@get:RequiresApi(26)
private val isVndkLite by lazy {
classSystemProperties
.getDeclaredMethod("getBoolean", String::class.java, Boolean::class.java)
.invoke(null, "ro.vndk.lite", false) as Boolean
}

@get:RequiresApi(26)
private val vndkVersion by lazy {
classSystemProperties
.getDeclaredMethod("get", String::class.java, String::class.java)
Expand Down Expand Up @@ -185,7 +183,7 @@ class RootHostCmdBuilder<Host : BaseRootHost> constructor(
fun build(
withRelocation: Boolean,
initialOptions: RootHostInitArgs,
): Cmd.Builder {
): FlowCmd {
log { "build(relocate=$withRelocation, ${initialOptions})" }
val cmds = mutableListOf<String>()

Expand All @@ -209,7 +207,7 @@ class RootHostCmdBuilder<Host : BaseRootHost> constructor(

cmds.add(launchCmd)

return Cmd.builder(cmds)
return FlowCmd(cmds)
}

private fun buildLaunchCmd(isDebug: Boolean, processPath: String): String {
Expand Down
Loading

0 comments on commit 456aaf9

Please sign in to comment.