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 {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
isMinifyEnabled = false
|
isMinifyEnabled = true
|
||||||
|
isShrinkResources = true
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard-rules.pro"
|
"proguard-rules.pro"
|
||||||
)
|
)
|
||||||
|
signingConfig = signingConfigs["debug"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@@ -33,6 +35,7 @@ android {
|
|||||||
}
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
buildConfig = true
|
buildConfig = true
|
||||||
|
viewBinding = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,4 +49,5 @@ dependencies {
|
|||||||
implementation(libs.com.github.topjohnwu.libsu.core)
|
implementation(libs.com.github.topjohnwu.libsu.core)
|
||||||
implementation(libs.com.github.topjohnwu.libsu.service)
|
implementation(libs.com.github.topjohnwu.libsu.service)
|
||||||
implementation(libs.com.github.topjohnwu.libsu.io)
|
implementation(libs.com.github.topjohnwu.libsu.io)
|
||||||
|
implementation(libs.androidx.swiperefreshlayout)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,14 @@
|
|||||||
android:theme="@style/Theme.KsuWebUI"
|
android:theme="@style/Theme.KsuWebUI"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
tools:targetApi="31" >
|
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
|
<activity
|
||||||
android:name=".WebUIActivity"
|
android:name=".WebUIActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
|||||||
@@ -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 {
|
fun createRootShell(globalMnt: Boolean = false): Shell {
|
||||||
Shell.enableVerboseLogging = BuildConfig.DEBUG
|
Shell.enableVerboseLogging = BuildConfig.DEBUG
|
||||||
val builder = Shell.Builder.create()
|
val builder = Shell.Builder.create()
|
||||||
return try {
|
return if (globalMnt) {
|
||||||
if (globalMnt) {
|
|
||||||
builder.build("su", "-g")
|
|
||||||
} else {
|
|
||||||
builder.build("su")
|
|
||||||
}
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
Log.w(TAG, "ksu failed: ", e)
|
|
||||||
try {
|
|
||||||
if (globalMnt) {
|
|
||||||
builder.build("su")
|
|
||||||
} else {
|
|
||||||
builder.build("su", "-mm")
|
builder.build("su", "-mm")
|
||||||
}
|
} else {
|
||||||
} catch (e: Throwable) {
|
builder.build("su")
|
||||||
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">
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<!-- Base application theme. -->
|
<!-- 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>
|
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="o_mr1">shortEdges</item>
|
||||||
</style>
|
</style>
|
||||||
<style name="Theme.KernelSU.WebUI" parent="Theme.KsuWebUI" />
|
<style name="Theme.KernelSU.WebUI" parent="Theme.KsuWebUI" />
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">KsuWebUI</string>
|
<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>
|
</resources>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<!-- Base application theme. -->
|
<!-- 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>
|
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="o_mr1">shortEdges</item>
|
||||||
</style>
|
</style>
|
||||||
<style name="Theme.KernelSU.WebUI" parent="Theme.KsuWebUI">
|
<style name="Theme.KernelSU.WebUI" parent="Theme.KsuWebUI">
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ kotlin = "2.0.20"
|
|||||||
coreKtx = "1.13.1"
|
coreKtx = "1.13.1"
|
||||||
appcompat = "1.7.0"
|
appcompat = "1.7.0"
|
||||||
material = "1.12.0"
|
material = "1.12.0"
|
||||||
|
swiperefreshlayout = "1.1.0"
|
||||||
webkit = "1.12.0"
|
webkit = "1.12.0"
|
||||||
libsu = "6.0.0"
|
libsu = "6.0.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
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" }
|
androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" }
|
||||||
|
|
||||||
com-github-topjohnwu-libsu-core = { group = "com.github.topjohnwu.libsu", name = "core", version.ref = "libsu" }
|
com-github-topjohnwu-libsu-core = { group = "com.github.topjohnwu.libsu", name = "core", version.ref = "libsu" }
|
||||||
|
|||||||
Reference in New Issue
Block a user