This commit is contained in:
5ec1cff
2024-10-04 00:07:42 +08:00
parent 3559f0c8bf
commit 8400636caa
12 changed files with 319 additions and 20 deletions

View File

@@ -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)
}

View File

@@ -15,11 +15,19 @@
android:theme="@style/Theme.KsuWebUI"
android:networkSecurityConfig="@xml/network_security_config"
tools:targetApi="31" >
<activity android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".WebUIActivity"
android:exported="true"
android:autoRemoveFromRecents="true"
android:documentLaunchMode="intoExisting"/>
android:documentLaunchMode="intoExisting" />
</application>
</manifest>

View File

@@ -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()
}
}

View File

@@ -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<Module>()
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<Module>()
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<ViewHolder>() {
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) }
}
}

View File

@@ -16,23 +16,9 @@ inline fun <T> 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")
}
}
}

View File

@@ -0,0 +1,37 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:menu="@menu/main_menu" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/swipe_refresh"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" >
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_anchor="@id/list"
android:id="@+id/info"
android:visibility="gone"
app:layout_anchorGravity="center" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="8dp"
style="?attr/materialCardViewElevatedStyle">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<TextView
android:id="@+id/name"
style="?textAppearanceHeadlineSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/author"
style="?textAppearanceListItemSecondary"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/version"
style="?textAppearanceListItemSecondary"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="4dp"
android:id="@+id/divider"/>
<TextView
android:id="@+id/desc"
style="?textAppearanceListItemSecondary"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:checkable="true"
android:id="@+id/enable_webview_debugging"
android:title="@string/enable_webview_debugging"
app:showAsAction="never" />
</menu>

View File

@@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.KsuWebUI" parent="android:Theme.Material.NoActionBar">
<style name="Theme.KsuWebUI" parent="Theme.Material3.DayNight.NoActionBar">
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="o_mr1">shortEdges</item>
</style>
<style name="Theme.KernelSU.WebUI" parent="Theme.KsuWebUI" />

View File

@@ -1,3 +1,7 @@
<resources>
<string name="app_name">KsuWebUI</string>
<string name="enable_webview_debugging">Debugging</string>
<string name="please_grant_root">Please grant root</string>
<string name="no_modules">No modules</string>
<string name="loading">Loading ...</string>
</resources>

View File

@@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.KsuWebUI" parent="android:Theme.Material.Light.NoActionBar">
<style name="Theme.KsuWebUI" parent="Theme.Material3.DayNight.NoActionBar">
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="o_mr1">shortEdges</item>
</style>
<style name="Theme.KernelSU.WebUI" parent="Theme.KsuWebUI">

View File

@@ -4,12 +4,14 @@ kotlin = "2.0.20"
coreKtx = "1.13.1"
appcompat = "1.7.0"
material = "1.12.0"
swiperefreshlayout = "1.1.0"
webkit = "1.12.0"
libsu = "6.0.0"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "swiperefreshlayout" }
androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" }
com-github-topjohnwu-libsu-core = { group = "com.github.topjohnwu.libsu", name = "core", version.ref = "libsu" }