You've already forked KernelSU-Next
mirror of
https://github.com/KernelSU-Next/KernelSU-Next.git
synced 2025-08-27 23:46:34 +00:00
manager: Add module UI
This commit is contained in:
4
manager/.idea/compiler.xml
generated
4
manager/.idea/compiler.xml
generated
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="CompilerConfiguration">
|
<component name="CompilerConfiguration">
|
||||||
<bytecodeTargetLevel target="19" />
|
<bytecodeTargetLevel target="11" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
2
manager/.idea/gradle.xml
generated
2
manager/.idea/gradle.xml
generated
@@ -7,7 +7,7 @@
|
|||||||
<option name="testRunner" value="GRADLE" />
|
<option name="testRunner" value="GRADLE" />
|
||||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="gradleJvm" value="19" />
|
<option name="gradleJvm" value="Embedded JDK" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
|
|||||||
2
manager/.idea/misc.xml
generated
2
manager/.idea/misc.xml
generated
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ dependencies {
|
|||||||
implementation("io.coil-kt:coil-compose:2.2.2")
|
implementation("io.coil-kt:coil-compose:2.2.2")
|
||||||
implementation("me.zhanghai.android.appiconloader:appiconloader-coil:1.5.0")
|
implementation("me.zhanghai.android.appiconloader:appiconloader-coil:1.5.0")
|
||||||
|
|
||||||
|
implementation("com.github.topjohnwu.libsu:core:5.0.3")
|
||||||
|
|
||||||
ksp("io.github.raamcosta.compose-destinations:ksp:$composeDestinationsVersion")
|
ksp("io.github.raamcosta.compose-destinations:ksp:$composeDestinationsVersion")
|
||||||
|
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
|
android:extractNativeLibs="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
|
|||||||
@@ -16,3 +16,5 @@ add_library(kernelsu
|
|||||||
find_library(log-lib log)
|
find_library(log-lib log)
|
||||||
|
|
||||||
target_link_libraries(kernelsu ${log-lib})
|
target_link_libraries(kernelsu ${log-lib})
|
||||||
|
|
||||||
|
add_executable(libksu.so su.c)
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
package me.weishu.kernelsu
|
package me.weishu.kernelsu
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.util.Log
|
||||||
import coil.Coil
|
import coil.Coil
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
|
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
|
||||||
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
|
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
lateinit var ksuApp: KernelSUApplication
|
lateinit var ksuApp: KernelSUApplication
|
||||||
|
|
||||||
@@ -24,5 +28,21 @@ class KernelSUApplication : Application() {
|
|||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
install()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
fun createRootShell(): Shell {
|
||||||
|
Shell.enableVerboseLogging = BuildConfig.DEBUG
|
||||||
|
val su = applicationInfo.nativeLibraryDir + File.separator + "libksu.so"
|
||||||
|
val builder = Shell.Builder.create()
|
||||||
|
return builder.build(su)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun install() {
|
||||||
|
val shell = createRootShell()
|
||||||
|
val ksduLib = ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libksud.so"
|
||||||
|
val result = ShellUtils.fastCmdResult(shell, "$ksduLib install")
|
||||||
|
Log.w("KernelSU", "install ksud result: $result")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,229 @@
|
|||||||
package me.weishu.kernelsu.ui.screen
|
package me.weishu.kernelsu.ui.screen
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import android.app.Activity.RESULT_OK
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import android.content.Intent
|
||||||
import androidx.compose.material3.Text
|
import android.util.Log
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.google.accompanist.swiperefresh.SwipeRefresh
|
||||||
|
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import me.weishu.kernelsu.R
|
||||||
|
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
||||||
|
import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Destination
|
@Destination
|
||||||
@Composable
|
@Composable
|
||||||
fun ModuleScreen() {
|
fun ModuleScreen() {
|
||||||
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
|
val viewModel = viewModel<ModuleViewModel>()
|
||||||
Text(text = "Coming Soon..")
|
val snackBarHost = LocalSnackbarHost.current
|
||||||
|
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
if (viewModel.moduleList.isEmpty()) {
|
||||||
|
viewModel.fetchModuleList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopBar()
|
||||||
|
},
|
||||||
|
floatingActionButton = {
|
||||||
|
val moduleInstall = stringResource(id = R.string.module_install)
|
||||||
|
val selectZipLauncher = rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.StartActivityForResult()
|
||||||
|
) {
|
||||||
|
if (it.resultCode != RESULT_OK) {
|
||||||
|
return@rememberLauncherForActivityResult
|
||||||
|
}
|
||||||
|
val data = it.data ?: return@rememberLauncherForActivityResult
|
||||||
|
val uri = data.data ?: return@rememberLauncherForActivityResult
|
||||||
|
|
||||||
|
scope.launch {
|
||||||
|
viewModel.installModule(uri)
|
||||||
|
}
|
||||||
|
Log.i("ModuleScreen", "select zip result: ${it.data}")
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtendedFloatingActionButton(
|
||||||
|
onClick = {
|
||||||
|
// select the zip file to install
|
||||||
|
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||||
|
intent.type = "application/zip"
|
||||||
|
selectZipLauncher.launch(intent)
|
||||||
|
},
|
||||||
|
icon = { Icon(Icons.Filled.Add, moduleInstall) },
|
||||||
|
text = { Text(text = moduleInstall) },
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { innerPadding ->
|
||||||
|
val failedEnable = stringResource(R.string.module_failed_to_enable)
|
||||||
|
val failedDisable = stringResource(R.string.module_failed_to_disable)
|
||||||
|
val swipeState = rememberSwipeRefreshState(viewModel.isRefreshing)
|
||||||
|
// TODO: Replace SwipeRefresh with RefreshIndicator when it's ready
|
||||||
|
SwipeRefresh(
|
||||||
|
state = swipeState,
|
||||||
|
onRefresh = {
|
||||||
|
scope.launch { viewModel.fetchModuleList() }
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(innerPadding)
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
val isEmpty = viewModel.moduleList.isEmpty()
|
||||||
|
if (isEmpty) {
|
||||||
|
swipeState.isRefreshing = false
|
||||||
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
|
Text(stringResource(R.string.module_empty))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LazyColumn {
|
||||||
|
items(viewModel.moduleList) { module ->
|
||||||
|
var isChecked by rememberSaveable(module) { mutableStateOf(module.enabled) }
|
||||||
|
ModuleItem(module,
|
||||||
|
isChecked,
|
||||||
|
onUninstall = {
|
||||||
|
scope.launch {
|
||||||
|
val result = viewModel.uninstallModule(module.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onCheckChanged = {
|
||||||
|
val success = viewModel.toggleModule(module.id, isChecked)
|
||||||
|
if (success) {
|
||||||
|
isChecked = it
|
||||||
|
} else scope.launch {
|
||||||
|
val message = if (isChecked) failedDisable else failedEnable
|
||||||
|
snackBarHost.showSnackbar(message.format(module.name))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun TopBar() {
|
||||||
|
TopAppBar(
|
||||||
|
title = { Text(stringResource(R.string.module)) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ModuleItem(
|
||||||
|
module: ModuleViewModel.ModuleInfo,
|
||||||
|
isChecked: Boolean,
|
||||||
|
onUninstall: (ModuleViewModel.ModuleInfo) -> Unit,
|
||||||
|
onCheckChanged: (Boolean) -> Unit
|
||||||
|
) {
|
||||||
|
ElevatedCard(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp),
|
||||||
|
colors =
|
||||||
|
CardDefaults.elevatedCardColors(containerColor = MaterialTheme.colorScheme.surface)
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 0.dp)) {
|
||||||
|
Row {
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
fontSize = MaterialTheme.typography.titleLarge.fontSize,
|
||||||
|
fontFamily = MaterialTheme.typography.titleLarge.fontFamily,
|
||||||
|
text = module.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
Row {
|
||||||
|
Text(
|
||||||
|
fontFamily = MaterialTheme.typography.titleMedium.fontFamily,
|
||||||
|
fontSize = MaterialTheme.typography.titleMedium.fontSize,
|
||||||
|
text = module.version
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
fontFamily = MaterialTheme.typography.titleMedium.fontFamily,
|
||||||
|
fontSize = MaterialTheme.typography.titleMedium.fontSize,
|
||||||
|
text = module.author
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
Switch(checked = isChecked, onCheckedChange = onCheckChanged)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
fontFamily = MaterialTheme.typography.bodyMedium.fontFamily,
|
||||||
|
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
|
||||||
|
text = module.description,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 4,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Divider(thickness = Dp.Hairline)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.weight(1f, true))
|
||||||
|
|
||||||
|
TextButton(
|
||||||
|
onClick = { onUninstall(module) },
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
fontFamily = MaterialTheme.typography.labelMedium.fontFamily,
|
||||||
|
fontSize = MaterialTheme.typography.labelMedium.fontSize,
|
||||||
|
text = stringResource(R.string.uninstall),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun ModuleItemPreview() {
|
||||||
|
val module = ModuleViewModel.ModuleInfo(
|
||||||
|
id = "id",
|
||||||
|
name = "name",
|
||||||
|
version = "version",
|
||||||
|
versionCode = 1,
|
||||||
|
author = "author",
|
||||||
|
description = "a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a ",
|
||||||
|
enabled = true
|
||||||
|
)
|
||||||
|
ModuleItem(module, true, {}, {})
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
package me.weishu.kernelsu.ui.viewmodel
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.SystemClock
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import me.weishu.kernelsu.ksuApp
|
||||||
|
import org.json.JSONArray
|
||||||
|
import java.io.File
|
||||||
|
import java.text.Collator
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class ModuleViewModel : ViewModel() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "ModuleViewModel"
|
||||||
|
private var modules by mutableStateOf<List<ModuleInfo>>(emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleInfo(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val author: String,
|
||||||
|
val version: String,
|
||||||
|
val versionCode: Int,
|
||||||
|
val description: String,
|
||||||
|
val enabled: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
var isRefreshing by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
|
||||||
|
val moduleList by derivedStateOf {
|
||||||
|
val comparator = compareBy(Collator.getInstance(Locale.getDefault()), ModuleInfo::id)
|
||||||
|
modules.sortedWith(comparator).also {
|
||||||
|
isRefreshing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun fetchModuleList() {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
isRefreshing = true
|
||||||
|
val start = SystemClock.elapsedRealtime()
|
||||||
|
|
||||||
|
val shell = ksuApp.createRootShell()
|
||||||
|
val ksduLib = ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libksud.so"
|
||||||
|
|
||||||
|
val out = shell.newJob().add("$ksduLib module list").to(ArrayList(), null).exec().out
|
||||||
|
val result = out.joinToString("\n")
|
||||||
|
|
||||||
|
Log.i(TAG, "result: $result")
|
||||||
|
|
||||||
|
val array = JSONArray(result)
|
||||||
|
modules = (0 until array.length())
|
||||||
|
.asSequence()
|
||||||
|
.map { array.getJSONObject(it) }
|
||||||
|
.map { obj ->
|
||||||
|
ModuleInfo(
|
||||||
|
obj.getString("id"),
|
||||||
|
obj.getString("name"),
|
||||||
|
obj.getString("author"),
|
||||||
|
obj.getString("version"),
|
||||||
|
obj.getInt("versionCode"),
|
||||||
|
obj.getString("description"),
|
||||||
|
obj.getBoolean("enabled")
|
||||||
|
)
|
||||||
|
}.toList()
|
||||||
|
|
||||||
|
Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}, modules: $modules")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun execKsud(args: String): Boolean {
|
||||||
|
val shell = ksuApp.createRootShell()
|
||||||
|
val ksduLib = ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libksud.so"
|
||||||
|
return ShellUtils.fastCmdResult(shell, "$ksduLib $args")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleModule(id: String, enable: Boolean): Boolean {
|
||||||
|
val cmd = if (enable) {
|
||||||
|
"module enable $id"
|
||||||
|
} else {
|
||||||
|
"module disable $id"
|
||||||
|
}
|
||||||
|
val result = execKsud(cmd)
|
||||||
|
Log.i(TAG, "toggle module $id result: $result")
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun uninstallModule(id: String) : Boolean {
|
||||||
|
val cmd = "module uninstall $id"
|
||||||
|
val result = execKsud(cmd)
|
||||||
|
Log.i(TAG, "uninstall module $id result: $result")
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun installModule(uri: Uri) : Boolean {
|
||||||
|
val resolver = ksuApp.contentResolver
|
||||||
|
with(resolver.openInputStream(uri)) {
|
||||||
|
val file = File(ksuApp.cacheDir, "module.zip")
|
||||||
|
file.outputStream().use { output ->
|
||||||
|
this?.copyTo(output)
|
||||||
|
}
|
||||||
|
val cmd = "module install ${file.absolutePath}"
|
||||||
|
val result = execKsud(cmd)
|
||||||
|
Log.i(TAG, "install module $uri result: $result")
|
||||||
|
|
||||||
|
file.delete()
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,11 @@
|
|||||||
|
|
||||||
<string name="superuser">Superuser</string>
|
<string name="superuser">Superuser</string>
|
||||||
<string name="superuser_failed_to_grant_root">Failed to grant root for %d</string>
|
<string name="superuser_failed_to_grant_root">Failed to grant root for %d</string>
|
||||||
|
<string name="module_failed_to_enable">Failed to enable module: %s</string>
|
||||||
|
<string name="module_failed_to_disable">Failed to disable module: %s</string>
|
||||||
|
<string name="module_empty">No module installed</string>
|
||||||
|
|
||||||
<string name="module">Module</string>
|
<string name="module">Module</string>
|
||||||
|
<string name="uninstall">Uninstall</string>
|
||||||
|
<string name="module_install">Install</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ dependencyResolutionManagement {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven("https://jitpack.io")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
userspace/.gitignore
vendored
2
userspace/.gitignore
vendored
@@ -1,2 +0,0 @@
|
|||||||
/obj
|
|
||||||
/libs
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
LOCAL_PATH := $(call my-dir)
|
|
||||||
|
|
||||||
include $(CLEAR_VARS)
|
|
||||||
LOCAL_MODULE := su
|
|
||||||
LOCAL_SRC_FILES := su.c
|
|
||||||
|
|
||||||
include $(BUILD_EXECUTABLE)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
APP_ABI := arm64-v8a x86_64
|
|
||||||
APP_PLATFORM := android-24
|
|
||||||
APP_STL := none
|
|
||||||
Reference in New Issue
Block a user