You've already forked KsuWebUIStandalone
mirror of
https://github.com/5ec1cff/KsuWebUIStandalone.git
synced 2025-09-06 06:37:11 +00:00
main ui
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
187
app/src/main/java/io/github/a13e300/ksuwebui/MainActivity.kt
Normal file
187
app/src/main/java/io/github/a13e300/ksuwebui/MainActivity.kt
Normal 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) }
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
37
app/src/main/res/layout/activity_main.xml
Normal file
37
app/src/main/res/layout/activity_main.xml
Normal 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>
|
||||
50
app/src/main/res/layout/item_module.xml
Normal file
50
app/src/main/res/layout/item_module.xml
Normal 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>
|
||||
9
app/src/main/res/menu/main_menu.xml
Normal file
9
app/src/main/res/menu/main_menu.xml
Normal 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>
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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" }
|
||||
|
||||
Reference in New Issue
Block a user