diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 3238f10..7e9be09 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -17,11 +17,13 @@ android {
buildTypes {
release {
- isMinifyEnabled = false
+ isMinifyEnabled = true
+ isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
+ signingConfig = signingConfigs["debug"]
}
}
compileOptions {
@@ -33,6 +35,7 @@ android {
}
buildFeatures {
buildConfig = true
+ viewBinding = true
}
}
@@ -46,4 +49,5 @@ dependencies {
implementation(libs.com.github.topjohnwu.libsu.core)
implementation(libs.com.github.topjohnwu.libsu.service)
implementation(libs.com.github.topjohnwu.libsu.io)
+ implementation(libs.androidx.swiperefreshlayout)
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ad549e8..3dd94e0 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -15,11 +15,19 @@
android:theme="@style/Theme.KsuWebUI"
android:networkSecurityConfig="@xml/network_security_config"
tools:targetApi="31" >
+
+
+
+
+
+
+
+ android:documentLaunchMode="intoExisting" />
\ No newline at end of file
diff --git a/app/src/main/java/io/github/a13e300/ksuwebui/FileSystemService.kt b/app/src/main/java/io/github/a13e300/ksuwebui/FileSystemService.kt
new file mode 100644
index 0000000..2753f6b
--- /dev/null
+++ b/app/src/main/java/io/github/a13e300/ksuwebui/FileSystemService.kt
@@ -0,0 +1,12 @@
+package io.github.a13e300.ksuwebui
+
+import android.content.Intent
+import android.os.IBinder
+import com.topjohnwu.superuser.ipc.RootService
+import com.topjohnwu.superuser.nio.FileSystemManager
+
+class FileSystemService : RootService() {
+ override fun onBind(intent: Intent): IBinder {
+ return FileSystemManager.getService()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/a13e300/ksuwebui/MainActivity.kt b/app/src/main/java/io/github/a13e300/ksuwebui/MainActivity.kt
new file mode 100644
index 0000000..b295c7b
--- /dev/null
+++ b/app/src/main/java/io/github/a13e300/ksuwebui/MainActivity.kt
@@ -0,0 +1,187 @@
+package io.github.a13e300.ksuwebui
+
+import android.content.ComponentName
+import android.content.Intent
+import android.content.ServiceConnection
+import android.net.Uri
+import android.os.Bundle
+import android.os.IBinder
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.ViewGroup
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.RecyclerView
+import com.topjohnwu.superuser.Shell
+import com.topjohnwu.superuser.internal.MainShell
+import com.topjohnwu.superuser.ipc.RootService
+import com.topjohnwu.superuser.nio.FileSystemManager
+import io.github.a13e300.ksuwebui.databinding.ActivityMainBinding
+import io.github.a13e300.ksuwebui.databinding.ItemModuleBinding
+import kotlin.concurrent.thread
+
+class MainActivity : AppCompatActivity() {
+ private var connection: ServiceConnection? = null
+ private var rootFilesystem: FileSystemManager? = null
+ private lateinit var binding: ActivityMainBinding
+ private var moduleList = emptyList()
+ private lateinit var adapter: Adapter
+ private val prefs by lazy { getSharedPreferences("settings", MODE_PRIVATE) }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityMainBinding.inflate(layoutInflater)
+ adapter = Adapter()
+ binding.list.adapter = adapter
+ setContentView(binding.root)
+ setSupportActionBar(binding.toolbar)
+ binding.swipeRefresh.setOnRefreshListener {
+ refresh()
+ }
+ binding.swipeRefresh.isRefreshing = true
+ refresh()
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu?): Boolean {
+ menuInflater.inflate(R.menu.main_menu, menu)
+ menu!!.findItem(R.id.enable_webview_debugging).apply {
+ isChecked = prefs.getBoolean("enable_web_debugging", BuildConfig.DEBUG)
+ setOnMenuItemClickListener {
+ val newValue = !it.isChecked
+ prefs.edit().putBoolean("enable_web_debugging", newValue).apply()
+ it.isChecked = newValue
+ true
+ }
+ }
+ return true
+ }
+
+ private fun refresh() {
+ moduleList = emptyList()
+ adapter.notifyDataSetChanged()
+ binding.info.setText(R.string.loading)
+ binding.info.isVisible = true
+ fetchModuleList()
+ }
+
+ private fun fetchModuleList() {
+ thread {
+ if (!maybeStartRootService()) return@thread
+ val fs = rootFilesystem!!
+ val mods = mutableListOf()
+ fs.getFile("/data/adb/modules").listFiles()!!.forEach { f ->
+ if (!f.isDirectory) return@forEach
+ if (!fs.getFile(f, "webroot").isDirectory) return@forEach
+ if (fs.getFile(f, "disable").exists()) return@forEach
+ var name = f.name
+ val id = f.name
+ var author = "?"
+ var version = "?"
+ var desc = ""
+ fs.getFile(f, "module.prop").newInputStream().bufferedReader().use {
+ it.lines().forEach { l ->
+ val ls = l.split("=", limit = 2)
+ if (ls.size == 2) {
+ if (ls[0] == "name") name = ls[1]
+ else if (ls[0] == "description") desc = ls[1]
+ else if (ls[0] == "author") author = ls[1]
+ else if (ls[0] == "version") version = ls[1]
+ }
+
+ }
+ }
+ mods.add(Module(name, id, desc, author, version))
+ }
+ runOnUiThread {
+ moduleList = mods
+ adapter.notifyDataSetChanged()
+ binding.swipeRefresh.isRefreshing = false
+ if (mods.isEmpty()) {
+ binding.info.setText(R.string.no_modules)
+ binding.info.isVisible = true
+ } else {
+ binding.info.isVisible = false
+ }
+ }
+ }
+ }
+
+ data class Module(val name: String, val id: String, val desc: String, val author: String, val version: String)
+
+ class ViewHolder(val binding: ItemModuleBinding) : RecyclerView.ViewHolder(binding.root)
+
+ inner class Adapter : RecyclerView.Adapter() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return ViewHolder(
+ ItemModuleBinding.inflate(
+ LayoutInflater.from(parent.context), parent, false
+ )
+ )
+ }
+
+ override fun getItemCount(): Int = moduleList.size
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val item = moduleList[position]
+ val id = item.id
+ val name = item.name
+ holder.binding.name.text = name
+ holder.binding.author.text = "Author: ${item.author}"
+ holder.binding.version.text = "Version: ${item.version}"
+ holder.binding.desc.text = item.desc
+ holder.binding.root.setOnClickListener {
+ startActivity(
+ Intent(this@MainActivity, WebUIActivity::class.java)
+ .setData(Uri.parse("ksuwebui://webui/$id"))
+ .putExtra("id", id)
+ .putExtra("name", name)
+ )
+ }
+ }
+
+ }
+
+ private fun maybeStartRootService(): Boolean {
+ if (connection == null) {
+ val isRoot = Shell.Builder.create().setFlags(Shell.FLAG_MOUNT_MASTER).build().use {
+ it.isRoot
+ }
+
+ if (!isRoot) {
+ runOnUiThread {
+ moduleList = emptyList()
+ adapter.notifyDataSetChanged()
+ binding.info.setText(R.string.please_grant_root)
+ binding.info.isVisible = true
+ binding.swipeRefresh.isRefreshing = false
+ }
+ return false
+ }
+
+ connection = object : ServiceConnection {
+ override fun onServiceConnected(p0: ComponentName, p1: IBinder) {
+ rootFilesystem = FileSystemManager.getRemote(p1)
+ fetchModuleList()
+ }
+
+ override fun onServiceDisconnected(p0: ComponentName) {
+ rootFilesystem = null
+ connection = null
+ }
+
+ }
+ Shell.setDefaultBuilder(Shell.Builder.create().setFlags(Shell.FLAG_MOUNT_MASTER))
+ runOnUiThread {
+ RootService.bind(Intent(this, FileSystemService::class.java), connection!!)
+ }
+ return false
+ }
+ return true
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ connection?.let { RootService.unbind(it) }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/a13e300/ksuwebui/Util.kt b/app/src/main/java/io/github/a13e300/ksuwebui/Util.kt
index 7e9ea08..9b8e2d0 100644
--- a/app/src/main/java/io/github/a13e300/ksuwebui/Util.kt
+++ b/app/src/main/java/io/github/a13e300/ksuwebui/Util.kt
@@ -16,23 +16,9 @@ inline fun withNewRootShell(
fun createRootShell(globalMnt: Boolean = false): Shell {
Shell.enableVerboseLogging = BuildConfig.DEBUG
val builder = Shell.Builder.create()
- return try {
- if (globalMnt) {
- builder.build("su", "-g")
+ return if (globalMnt) {
+ builder.build("su", "-mm")
} else {
builder.build("su")
}
- } catch (e: Throwable) {
- Log.w(TAG, "ksu failed: ", e)
- try {
- if (globalMnt) {
- builder.build("su")
- } else {
- builder.build("su", "-mm")
- }
- } catch (e: Throwable) {
- Log.e(TAG, "su failed: ", e)
- builder.build("sh")
- }
- }
}
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..56a9345
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_module.xml b/app/src/main/res/layout/item_module.xml
new file mode 100644
index 0000000..8a56667
--- /dev/null
+++ b/app/src/main/res/layout/item_module.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml
new file mode 100644
index 0000000..b6240ed
--- /dev/null
+++ b/app/src/main/res/menu/main_menu.xml
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
index 44ea19d..99ede9b 100644
--- a/app/src/main/res/values-night/themes.xml
+++ b/app/src/main/res/values-night/themes.xml
@@ -1,6 +1,6 @@
-
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index cda775f..72c13f2 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,7 @@
KsuWebUI
+ Debugging
+ Please grant root
+ No modules
+ Loading ...
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index f2de2f6..54d6a1c 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,6 +1,6 @@
-