manager: implement app profile api call
This commit is contained in:
@@ -1,42 +0,0 @@
|
||||
package me.weishu.kernelsu;
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
* @date 2022/12/8.
|
||||
*/
|
||||
public final class Natives {
|
||||
|
||||
static {
|
||||
System.loadLibrary("kernelsu");
|
||||
}
|
||||
|
||||
// become root manager, return true if success.
|
||||
public static native boolean becomeManager(String pkg);
|
||||
|
||||
public static native int getVersion();
|
||||
|
||||
// get the uid list of allowed su processes.
|
||||
public static native int[] getAllowList();
|
||||
|
||||
public static native int[] getDenyList();
|
||||
|
||||
public static native boolean allowRoot(int uid, boolean allow);
|
||||
|
||||
public static native boolean isSafeMode();
|
||||
|
||||
public static native boolean isAllowlistMode();
|
||||
|
||||
public static native boolean setAllowlistMode(boolean isAllowlist);
|
||||
|
||||
public static native boolean isUidInAllowlist(int uid);
|
||||
|
||||
public static native boolean isUidInDenylist(int uid);
|
||||
|
||||
public static native boolean addUidToAllowlist(int uid);
|
||||
|
||||
public static native boolean removeUidFromAllowlist(int uid);
|
||||
|
||||
public static native boolean addUidToDenylist(int uid);
|
||||
|
||||
public static native boolean removeUidFromDenylist(int uid);
|
||||
}
|
||||
75
manager/app/src/main/java/me/weishu/kernelsu/Natives.kt
Normal file
75
manager/app/src/main/java/me/weishu/kernelsu/Natives.kt
Normal file
@@ -0,0 +1,75 @@
|
||||
package me.weishu.kernelsu
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
* @date 2022/12/8.
|
||||
*/
|
||||
object Natives {
|
||||
const val DEFAULT_ROOT_PROFILE_KEY = "_root_default_"
|
||||
const val DEFAULT_NON_ROOT_PROFILE_KEY = "_non_root_default_"
|
||||
|
||||
init {
|
||||
System.loadLibrary("kernelsu")
|
||||
}
|
||||
|
||||
// become root manager, return true if success.
|
||||
external fun becomeManager(pkg: String?): Boolean
|
||||
val version: Int
|
||||
external get
|
||||
|
||||
// get the uid list of allowed su processes.
|
||||
val allowList: IntArray
|
||||
external get
|
||||
val denyList: IntArray
|
||||
external get
|
||||
|
||||
external fun allowRoot(uid: Int, allow: Boolean): Boolean
|
||||
val isSafeMode: Boolean
|
||||
external get
|
||||
|
||||
/**
|
||||
* Get the profile of the given package.
|
||||
* @param key usually the package name
|
||||
* @return return null if failed.
|
||||
*/
|
||||
external fun getAppProfile(key: String?, uid: Int): Profile
|
||||
external fun setAppProfile(profile: Profile?): Boolean
|
||||
|
||||
|
||||
@Immutable
|
||||
@Parcelize
|
||||
data class Profile(
|
||||
// and there is a default profile for root and non-root
|
||||
val name: String,
|
||||
// current uid for the package, this is convivent for kernel to check
|
||||
// if the package name doesn't match uid, then it should be invalidated.
|
||||
val currentUid: Int = 0,
|
||||
|
||||
// if this is true, kernel will grant root permission to this package
|
||||
val allowSu: Boolean = false,
|
||||
|
||||
// these are used for root profile
|
||||
val rootUseDefault: Boolean = true,
|
||||
val rootTemplate: String? = null,
|
||||
val uid: Int = 0,
|
||||
val gid: Int = 0,
|
||||
val groups: List<Int> = mutableListOf(),
|
||||
val capabilities: List<Int> = mutableListOf(),
|
||||
val context: String = "su",
|
||||
val namespace: Namespace = Namespace.Inherited,
|
||||
|
||||
val nonRootUseDefault: Boolean = true,
|
||||
val umountModules: Boolean = false,
|
||||
) : Parcelable {
|
||||
enum class Namespace {
|
||||
Inherited,
|
||||
Global,
|
||||
Individual,
|
||||
}
|
||||
constructor(): this("")
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package me.weishu.kernelsu.profile
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Immutable
|
||||
@Parcelize
|
||||
data class AppProfile(
|
||||
val profileName: String,
|
||||
val allowRootRequest: Boolean = false,
|
||||
val unmountModules: Boolean = false,
|
||||
) : Parcelable
|
||||
@@ -1,23 +0,0 @@
|
||||
package me.weishu.kernelsu.profile
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Immutable
|
||||
@Parcelize
|
||||
data class RootProfile(
|
||||
val profileName: String,
|
||||
val namespace: Namespace = Namespace.Inherited,
|
||||
val uid: Int = 0,
|
||||
val gid: Int = 0,
|
||||
val groups: Int = 0,
|
||||
val capabilities: List<String> = emptyList(),
|
||||
val context: String = "u:r:su:s0",
|
||||
) : Parcelable {
|
||||
enum class Namespace {
|
||||
Inherited,
|
||||
Global,
|
||||
Individual,
|
||||
}
|
||||
}
|
||||
@@ -11,36 +11,30 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.profile.AppProfile
|
||||
import me.weishu.kernelsu.ui.component.SwitchItem
|
||||
|
||||
@Composable
|
||||
fun AppProfileConfig(
|
||||
modifier: Modifier = Modifier,
|
||||
fixedName: Boolean,
|
||||
profile: AppProfile,
|
||||
onProfileChange: (AppProfile) -> Unit,
|
||||
profile: Natives.Profile,
|
||||
onProfileChange: (Natives.Profile) -> Unit,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
if (!fixedName) {
|
||||
OutlinedTextField(
|
||||
label = { Text(stringResource(R.string.profile_name)) },
|
||||
value = profile.profileName,
|
||||
onValueChange = { onProfileChange(profile.copy(profileName = it)) }
|
||||
value = profile.name,
|
||||
onValueChange = { onProfileChange(profile.copy(name = it)) }
|
||||
)
|
||||
}
|
||||
|
||||
SwitchItem(
|
||||
title = stringResource(R.string.profile_allow_root_request),
|
||||
checked = profile.allowRootRequest,
|
||||
onCheckedChange = { onProfileChange(profile.copy(allowRootRequest = it)) }
|
||||
)
|
||||
|
||||
SwitchItem(
|
||||
title = stringResource(R.string.profile_unmount_modules),
|
||||
checked = profile.unmountModules,
|
||||
onCheckedChange = { onProfileChange(profile.copy(unmountModules = it)) }
|
||||
checked = profile.umountModules,
|
||||
onCheckedChange = { onProfileChange(profile.copy(umountModules = it)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -48,7 +42,7 @@ fun AppProfileConfig(
|
||||
@Preview
|
||||
@Composable
|
||||
private fun AppProfileConfigPreview() {
|
||||
var profile by remember { mutableStateOf(AppProfile("")) }
|
||||
var profile by remember { mutableStateOf(Natives.Profile("")) }
|
||||
AppProfileConfig(fixedName = true, profile = profile) {
|
||||
profile = it
|
||||
}
|
||||
|
||||
@@ -21,31 +21,31 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.profile.RootProfile
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun RootProfileConfig(
|
||||
modifier: Modifier = Modifier,
|
||||
fixedName: Boolean,
|
||||
profile: RootProfile,
|
||||
onProfileChange: (RootProfile) -> Unit,
|
||||
profile: Natives.Profile,
|
||||
onProfileChange: (Natives.Profile) -> Unit,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
if (!fixedName) {
|
||||
OutlinedTextField(
|
||||
label = { Text(stringResource(R.string.profile_name)) },
|
||||
value = profile.profileName,
|
||||
onValueChange = { onProfileChange(profile.copy(profileName = it)) }
|
||||
value = profile.name,
|
||||
onValueChange = { onProfileChange(profile.copy(name = it)) }
|
||||
)
|
||||
}
|
||||
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
val currentNamespace = when (profile.namespace) {
|
||||
RootProfile.Namespace.Inherited -> stringResource(R.string.profile_namespace_inherited)
|
||||
RootProfile.Namespace.Global -> stringResource(R.string.profile_namespace_global)
|
||||
RootProfile.Namespace.Individual -> stringResource(R.string.profile_namespace_individual)
|
||||
Natives.Profile.Namespace.Inherited -> stringResource(R.string.profile_namespace_inherited)
|
||||
Natives.Profile.Namespace.Global -> stringResource(R.string.profile_namespace_global)
|
||||
Natives.Profile.Namespace.Individual -> stringResource(R.string.profile_namespace_individual)
|
||||
}
|
||||
ListItem(headlineContent = {
|
||||
ExposedDropdownMenuBox(
|
||||
@@ -70,21 +70,21 @@ fun RootProfileConfig(
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.profile_namespace_inherited)) },
|
||||
onClick = {
|
||||
onProfileChange(profile.copy(namespace = RootProfile.Namespace.Inherited))
|
||||
onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.Inherited))
|
||||
expanded = false
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.profile_namespace_global)) },
|
||||
onClick = {
|
||||
onProfileChange(profile.copy(namespace = RootProfile.Namespace.Global))
|
||||
onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.Global))
|
||||
expanded = false
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.profile_namespace_individual)) },
|
||||
onClick = {
|
||||
onProfileChange(profile.copy(namespace = RootProfile.Namespace.Individual))
|
||||
onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.Individual))
|
||||
expanded = false
|
||||
},
|
||||
)
|
||||
@@ -97,7 +97,18 @@ fun RootProfileConfig(
|
||||
label = { Text("uid") },
|
||||
value = profile.uid.toString(),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
onValueChange = { onProfileChange(profile.copy(uid = it.toInt())) }
|
||||
onValueChange = {
|
||||
if (it.isNotEmpty()) {
|
||||
it.filter { symbol ->
|
||||
symbol.isDigit()
|
||||
}.let { filtered ->
|
||||
filtered.ifEmpty { "0" }
|
||||
}.let { value ->
|
||||
onProfileChange(profile.copy(uid = value.toInt(), rootUseDefault = false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
)
|
||||
})
|
||||
|
||||
@@ -106,16 +117,37 @@ fun RootProfileConfig(
|
||||
label = { Text("gid") },
|
||||
value = profile.gid.toString(),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
onValueChange = { onProfileChange(profile.copy(gid = it.toInt())) }
|
||||
onValueChange = {
|
||||
if (it.isNotEmpty()) {
|
||||
it.filter { symbol ->
|
||||
symbol.isDigit()
|
||||
}.let { filtered ->
|
||||
filtered.ifEmpty { "0" }
|
||||
}.let { value ->
|
||||
onProfileChange(profile.copy(gid = value.toInt(), rootUseDefault = false))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
ListItem(headlineContent = {
|
||||
OutlinedTextField(
|
||||
label = { Text("groups") },
|
||||
value = profile.groups.toString(),
|
||||
value = profile.groups.joinToString(","),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
onValueChange = { onProfileChange(profile.copy(groups = it.toInt())) }
|
||||
onValueChange = { s ->
|
||||
if (s.isNotEmpty()) {
|
||||
s.filter { symbol ->
|
||||
symbol.isDigit() || symbol == ','
|
||||
}.let { filtered ->
|
||||
filtered.ifEmpty { "0" }
|
||||
}.let { value ->
|
||||
val groups = value.split(',').filter { it.isNotEmpty() }.map { it.toInt() }
|
||||
onProfileChange(profile.copy(groups = groups, rootUseDefault = false))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -123,7 +155,9 @@ fun RootProfileConfig(
|
||||
OutlinedTextField(
|
||||
label = { Text("context") },
|
||||
value = profile.context,
|
||||
onValueChange = { onProfileChange(profile.copy(context = it)) }
|
||||
onValueChange = {
|
||||
onProfileChange(profile.copy(context = it, rootUseDefault = false))
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -132,7 +166,7 @@ fun RootProfileConfig(
|
||||
@Preview
|
||||
@Composable
|
||||
private fun RootProfileConfigPreview() {
|
||||
var profile by remember { mutableStateOf(RootProfile("")) }
|
||||
var profile by remember { mutableStateOf(Natives.Profile("")) }
|
||||
RootProfileConfig(fixedName = true, profile = profile) {
|
||||
profile = it
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package me.weishu.kernelsu.ui.screen
|
||||
|
||||
import android.util.Log
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -49,8 +50,6 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.launch
|
||||
import me.weishu.kernelsu.Natives
|
||||
import me.weishu.kernelsu.R
|
||||
import me.weishu.kernelsu.profile.AppProfile
|
||||
import me.weishu.kernelsu.profile.RootProfile
|
||||
import me.weishu.kernelsu.ui.component.SwitchItem
|
||||
import me.weishu.kernelsu.ui.component.profile.AppProfileConfig
|
||||
import me.weishu.kernelsu.ui.component.profile.RootProfileConfig
|
||||
@@ -70,8 +69,15 @@ fun AppProfileScreen(
|
||||
val context = LocalContext.current
|
||||
val snackbarHost = LocalSnackbarHost.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val failToGrantRoot = stringResource(R.string.superuser_failed_to_grant_root)
|
||||
var isRootGranted by rememberSaveable { mutableStateOf(appInfo.onAllowList) }
|
||||
val failToUpdateAppProfile =
|
||||
stringResource(R.string.failed_to_update_app_profile).format(appInfo.label)
|
||||
|
||||
val packageName = appInfo.packageName
|
||||
var profile by rememberSaveable {
|
||||
mutableStateOf(Natives.getAppProfile(packageName, appInfo.uid))
|
||||
}
|
||||
|
||||
Log.i("mylog", "profile: $profile")
|
||||
|
||||
Scaffold(
|
||||
topBar = { TopBar { navigator.popBackStack() } }
|
||||
@@ -95,14 +101,13 @@ fun AppProfileScreen(
|
||||
.height(48.dp)
|
||||
)
|
||||
},
|
||||
isRootGranted = isRootGranted,
|
||||
onSwitchRootPermission = { grant ->
|
||||
profile = profile,
|
||||
onProfileChange = {
|
||||
scope.launch {
|
||||
val success = Natives.allowRoot(appInfo.uid, grant)
|
||||
if (success) {
|
||||
isRootGranted = grant
|
||||
if (!Natives.setAppProfile(it)) {
|
||||
snackbarHost.showSnackbar(failToUpdateAppProfile.format(appInfo.uid))
|
||||
} else {
|
||||
snackbarHost.showSnackbar(failToGrantRoot.format(appInfo.uid))
|
||||
profile = it
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -117,9 +122,11 @@ private fun AppProfileInner(
|
||||
packageName: String,
|
||||
appLabel: String,
|
||||
appIcon: @Composable () -> Unit,
|
||||
isRootGranted: Boolean,
|
||||
onSwitchRootPermission: (Boolean) -> Unit,
|
||||
profile: Natives.Profile,
|
||||
onProfileChange: (Natives.Profile) -> Unit,
|
||||
) {
|
||||
val isRootGranted = profile.allowSu
|
||||
|
||||
Column(modifier = modifier) {
|
||||
ListItem(
|
||||
headlineContent = { Text(appLabel) },
|
||||
@@ -131,14 +138,32 @@ private fun AppProfileInner(
|
||||
icon = Icons.Filled.Security,
|
||||
title = stringResource(id = R.string.superuser),
|
||||
checked = isRootGranted,
|
||||
onCheckedChange = onSwitchRootPermission,
|
||||
onCheckedChange = { onProfileChange(profile.copy(allowSu = it)) },
|
||||
)
|
||||
|
||||
Crossfade(targetState = isRootGranted, label = "") { current ->
|
||||
Column {
|
||||
if (current) {
|
||||
var mode by rememberSaveable { mutableStateOf(Mode.Default) }
|
||||
ProfileBox(mode, true) { mode = it }
|
||||
val mode = if (profile.rootUseDefault) {
|
||||
Mode.Default
|
||||
} else if (profile.rootTemplate != null) {
|
||||
Mode.Template
|
||||
} else {
|
||||
Mode.Custom
|
||||
}
|
||||
ProfileBox(mode, true) {
|
||||
when (it) {
|
||||
Mode.Default -> {
|
||||
onProfileChange(profile.copy(rootUseDefault = true))
|
||||
}
|
||||
Mode.Template -> {
|
||||
onProfileChange(profile.copy(rootUseDefault = false))
|
||||
}
|
||||
else -> {
|
||||
onProfileChange(profile.copy(rootUseDefault = false))
|
||||
}
|
||||
}
|
||||
}
|
||||
Crossfade(targetState = mode, label = "") { currentMode ->
|
||||
if (currentMode == Mode.Template) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
@@ -163,11 +188,10 @@ private fun AppProfileInner(
|
||||
}
|
||||
})
|
||||
} else if (mode == Mode.Custom) {
|
||||
var profile by rememberSaveable { mutableStateOf(RootProfile("@$packageName")) }
|
||||
RootProfileConfig(
|
||||
fixedName = true,
|
||||
profile = profile,
|
||||
onProfileChange = { profile = it }
|
||||
onProfileChange = onProfileChange
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -176,11 +200,10 @@ private fun AppProfileInner(
|
||||
ProfileBox(mode, false) { mode = it }
|
||||
Crossfade(targetState = mode, label = "") { currentMode ->
|
||||
if (currentMode == Mode.Custom) {
|
||||
var profile by rememberSaveable { mutableStateOf(AppProfile(packageName)) }
|
||||
AppProfileConfig(
|
||||
fixedName = true,
|
||||
profile = profile,
|
||||
onProfileChange = { profile = it }
|
||||
onProfileChange = onProfileChange
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -256,12 +279,15 @@ private fun ProfileBox(
|
||||
@Preview
|
||||
@Composable
|
||||
private fun AppProfilePreview() {
|
||||
var isRootGranted by remember { mutableStateOf(false) }
|
||||
var profile by remember { mutableStateOf(Natives.Profile("")) }
|
||||
AppProfileInner(
|
||||
packageName = "icu.nullptr.test",
|
||||
appLabel = "Test",
|
||||
appIcon = { Icon(Icons.Filled.Android, null) },
|
||||
isRootGranted = isRootGranted,
|
||||
onSwitchRootPermission = { isRootGranted = it },
|
||||
profile = profile,
|
||||
onProfileChange = {
|
||||
profile = it
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
SideEffect {
|
||||
if (isManager) install()
|
||||
}
|
||||
val ksuVersion = if (isManager) Natives.getVersion() else null
|
||||
val ksuVersion = if (isManager) Natives.version else null
|
||||
|
||||
StatusCard(kernelVersion, ksuVersion)
|
||||
InfoCard()
|
||||
@@ -141,7 +141,7 @@ private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
|
||||
) {
|
||||
when {
|
||||
ksuVersion != null -> {
|
||||
val appendText = if (Natives.isSafeMode()) {
|
||||
val appendText = if (Natives.isSafeMode) {
|
||||
" [${stringResource(id = R.string.safe_mode)}]"
|
||||
} else {
|
||||
""
|
||||
|
||||
@@ -50,8 +50,8 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
}
|
||||
|
||||
val isSafeMode = Natives.isSafeMode()
|
||||
val isKSUVersionInvalid = Natives.getVersion() < 0
|
||||
val isSafeMode = Natives.isSafeMode
|
||||
val isKSUVersionInvalid = Natives.version < 0
|
||||
val hasMagisk = hasMagisk()
|
||||
|
||||
val hideInstallButton = isSafeMode || isKSUVersionInvalid || hasMagisk
|
||||
|
||||
@@ -71,7 +71,7 @@ fun getModuleCount(): Int {
|
||||
}
|
||||
|
||||
fun getSuperuserCount(): Int {
|
||||
return Natives.getAllowList().size
|
||||
return Natives.allowList.size
|
||||
}
|
||||
|
||||
fun toggleModule(id: String, enable: Boolean): Boolean {
|
||||
|
||||
@@ -72,9 +72,9 @@ fun getBugreportFile(context: Context): File {
|
||||
pw.println("Nodename: ${uname.nodename}")
|
||||
pw.println("Sysname: ${uname.sysname}")
|
||||
|
||||
val ksuKernel = Natives.getVersion()
|
||||
val ksuKernel = Natives.version
|
||||
pw.println("KernelSU: $ksuKernel")
|
||||
val safeMode = Natives.isSafeMode()
|
||||
val safeMode = Natives.isSafeMode
|
||||
pw.println("SafeMode: $safeMode")
|
||||
}
|
||||
|
||||
|
||||
@@ -116,8 +116,8 @@ class SuperUserViewModel : ViewModel() {
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
val pm = ksuApp.packageManager
|
||||
val allowList = Natives.getAllowList().toSet()
|
||||
val denyList = Natives.getDenyList().toSet()
|
||||
val allowList = Natives.allowList.toSet()
|
||||
val denyList = Natives.denyList.toSet()
|
||||
Log.i(TAG, "allowList: $allowList")
|
||||
Log.i(TAG, "denyList: $denyList")
|
||||
val start = SystemClock.elapsedRealtime()
|
||||
|
||||
Reference in New Issue
Block a user