manager: implement app profile api call

This commit is contained in:
weishu
2023-05-17 11:23:46 +08:00
parent f2cb841b8a
commit 41265b0203
16 changed files with 395 additions and 267 deletions

View File

@@ -3,15 +3,16 @@
#include <sys/prctl.h> #include <sys/prctl.h>
#include <android/log.h> #include <android/log.h>
#include <cstring>
#include "ksu.h" #include "ksu.h"
#define LOG_TAG "KernelSu" #define LOG_TAG "KernelSU"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
extern "C" extern "C"
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_becomeManager(JNIEnv *env, jclass clazz, jstring pkg) { Java_me_weishu_kernelsu_Natives_becomeManager(JNIEnv *env, jobject, jstring pkg) {
auto cpkg = env->GetStringUTFChars(pkg, nullptr); auto cpkg = env->GetStringUTFChars(pkg, nullptr);
auto result = become_manager(cpkg); auto result = become_manager(cpkg);
env->ReleaseStringUTFChars(pkg, cpkg); env->ReleaseStringUTFChars(pkg, cpkg);
@@ -20,13 +21,13 @@ Java_me_weishu_kernelsu_Natives_becomeManager(JNIEnv *env, jclass clazz, jstring
extern "C" extern "C"
JNIEXPORT jint JNICALL JNIEXPORT jint JNICALL
Java_me_weishu_kernelsu_Natives_getVersion(JNIEnv *env, jclass clazz) { Java_me_weishu_kernelsu_Natives_getVersion(JNIEnv *env, jobject) {
return get_version(); return get_version();
} }
extern "C" extern "C"
JNIEXPORT jintArray JNICALL JNIEXPORT jintArray JNICALL
Java_me_weishu_kernelsu_Natives_getAllowList(JNIEnv *env, jclass clazz) { Java_me_weishu_kernelsu_Natives_getAllowList(JNIEnv *env, jobject) {
int uids[1024]; int uids[1024];
int size = 0; int size = 0;
bool result = get_allow_list(uids, &size); bool result = get_allow_list(uids, &size);
@@ -56,7 +57,7 @@ Java_me_weishu_kernelsu_Natives_getDenyList(JNIEnv *env, jclass clazz) {
extern "C" extern "C"
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_allowRoot(JNIEnv *env, jclass clazz, jint uid, jboolean allow) { Java_me_weishu_kernelsu_Natives_allowRoot(JNIEnv *env, jobject clazz, jint uid, jboolean allow) {
return allow_su(uid, allow); return allow_su(uid, allow);
} }
@@ -66,43 +67,173 @@ Java_me_weishu_kernelsu_Natives_isSafeMode(JNIEnv *env, jclass clazz) {
return is_safe_mode(); return is_safe_mode();
} }
static void fillIntArray(JNIEnv* env, jobject list, int *data, int count) {
auto cls = env->GetObjectClass(list);
auto add = env->GetMethodID(cls, "add", "(Ljava/lang/Object;)Z");
auto integerCls = env->FindClass("java/lang/Integer");
auto constructor = env->GetMethodID(integerCls, "<init>", "(I)V");
for (int i = 0; i < count; ++i) {
auto integer = env->NewObject(integerCls, constructor, data[i]);
env->CallBooleanMethod(list, add, integer);
}
}
static int getListSize(JNIEnv *env, jobject list) {
auto cls = env->GetObjectClass(list);
auto size = env->GetMethodID(cls, "size", "()I");
return env->CallIntMethod(list, size);
}
extern "C"
JNIEXPORT jobject JNICALL
Java_me_weishu_kernelsu_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, jint uid) {
if (env->GetStringLength(pkg) > KSU_MAX_PACKAGE_NAME) {
return nullptr;
}
p_key_t key = {};
auto cpkg = env->GetStringUTFChars(pkg, nullptr);
strcpy(key, cpkg);
env->ReleaseStringUTFChars(pkg, cpkg);
app_profile profile = {};
strcpy(profile.key, key);
profile.current_uid = uid;
if (!get_app_profile(key, &profile)) {
return nullptr;
}
auto cls = env->FindClass("me/weishu/kernelsu/Natives$Profile");
auto constructor = env->GetMethodID(cls, "<init>", "()V");
auto obj = env->NewObject(cls, constructor);
auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;");
auto currentUidField = env->GetFieldID(cls, "currentUid", "I");
auto allowSuField = env->GetFieldID(cls, "allowSu", "Z");
auto rootUseDefaultField = env->GetFieldID(cls, "rootUseDefault", "Z");
auto rootTemplateField = env->GetFieldID(cls, "rootTemplate", "Ljava/lang/String;");
auto uidField = env->GetFieldID(cls, "uid", "I");
auto gidField = env->GetFieldID(cls, "gid", "I");
auto groupsField = env->GetFieldID(cls, "groups", "Ljava/util/List;");
auto capabilitiesField = env->GetFieldID(cls, "capabilities", "Ljava/util/List;");
auto domainField = env->GetFieldID(cls, "context", "Ljava/lang/String;");
// auto namespacesField = env->GetFieldID(cls, "namespace", "I");
auto nonRootUseDefaultField = env->GetFieldID(cls, "nonRootUseDefault", "Z");
auto umountModulesField = env->GetFieldID(cls, "umountModules", "Z");
env->SetObjectField(obj, keyField, env->NewStringUTF(profile.key));
env->SetIntField(obj, currentUidField, profile.current_uid);
auto allowSu = profile.allow_su;
if (allowSu) {
env->SetBooleanField(obj, rootUseDefaultField, (jboolean) profile.root_profile.use_default);
if (strlen(profile.root_profile.template_name) > 0) {
env->SetObjectField(obj, rootTemplateField,
env->NewStringUTF(profile.root_profile.template_name));
}
env->SetIntField(obj, uidField, profile.root_profile.uid);
env->SetIntField(obj, gidField, profile.root_profile.gid);
jobject groupList = env->GetObjectField(obj, groupsField);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
}
fillIntArray(env, groupList, profile.root_profile.groups, profile.root_profile.groups_count);
jobject capList = env->GetObjectField(obj, capabilitiesField);
fillIntArray(env, capList, profile.root_profile.capabilities, 2);
env->SetObjectField(obj, domainField,
env->NewStringUTF(profile.root_profile.selinux_domain));
// env->SetIntField(obj, namespacesField, profile.root_profile.namespaces);
env->SetBooleanField(obj, allowSuField, profile.allow_su);
} else {
env->SetBooleanField(obj, nonRootUseDefaultField,
(jboolean) profile.non_root_profile.use_default);
env->SetBooleanField(obj, umountModulesField, profile.non_root_profile.umount_modules);
}
return obj;
}
extern "C" extern "C"
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_isAllowlistMode(JNIEnv *env, jclass clazz) { Java_me_weishu_kernelsu_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) {
return is_allowlist_mode(); auto cls = env->FindClass("me/weishu/kernelsu/Natives$Profile");
}
extern "C" auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;");
JNIEXPORT jboolean JNICALL auto currentUidField = env->GetFieldID(cls, "currentUid", "I");
Java_me_weishu_kernelsu_Natives_setAllowlistMode(JNIEnv *env, jclass clazz, jboolean is_allowlist) { auto allowSuField = env->GetFieldID(cls, "allowSu", "Z");
return set_allowlist_mode(is_allowlist);
} auto rootUseDefaultField = env->GetFieldID(cls, "rootUseDefault", "Z");
extern "C" auto rootTemplateField = env->GetFieldID(cls, "rootTemplate", "Ljava/lang/String;");
JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_addUidToAllowlist(JNIEnv *env, jclass clazz, jint uid) { auto uidField = env->GetFieldID(cls, "uid", "I");
return add_to_allow_list(uid); auto gidField = env->GetFieldID(cls, "gid", "I");
} auto groupsField = env->GetFieldID(cls, "groups", "Ljava/util/List;");
extern "C" auto capabilitiesField = env->GetFieldID(cls, "capabilities", "Ljava/util/List;");
JNIEXPORT jboolean JNICALL auto domainField = env->GetFieldID(cls, "context", "Ljava/lang/String;");
Java_me_weishu_kernelsu_Natives_removeUidFromAllowlist(JNIEnv *env, jclass clazz, jint uid) { // auto namespacesField = env->GetFieldID(cls, "namespaces", "I");
return remove_from_allow_list(uid);
} auto nonRootUseDefaultField = env->GetFieldID(cls, "nonRootUseDefault", "Z");
extern "C" auto umountModulesField = env->GetFieldID(cls, "umountModules", "Z");
JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_addUidToDenylist(JNIEnv *env, jclass clazz, jint uid) { auto key = env->GetObjectField(profile, keyField);
return add_to_deny_list(uid); if (!key) {
} return false;
extern "C" }
JNIEXPORT jboolean JNICALL if (env->GetStringLength((jstring) key) > KSU_MAX_PACKAGE_NAME) {
Java_me_weishu_kernelsu_Natives_removeUidFromDenylist(JNIEnv *env, jclass clazz, jint uid) { return false;
return remove_from_deny_list(uid); }
}
extern "C" auto cpkg = env->GetStringUTFChars((jstring) key, nullptr);
JNIEXPORT jboolean JNICALL p_key_t p_key = {};
Java_me_weishu_kernelsu_Natives_isUidInAllowlist(JNIEnv *env, jclass clazz, jint uid) { strcpy(p_key, cpkg);
return is_in_allow_list(uid); env->ReleaseStringUTFChars((jstring) key, cpkg);
}
extern "C" auto currentUid = env->GetIntField(profile, currentUidField);
JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_isUidInDenylist(JNIEnv *env, jclass clazz, jint uid) { auto uid = env->GetIntField(profile, uidField);
return is_in_deny_list(uid); auto gid = env->GetIntField(profile, gidField);
auto groups = env->GetObjectField(profile, groupsField);
auto capabilities = env->GetObjectField(profile, capabilitiesField);
auto domain = env->GetObjectField(profile, domainField);
auto allowSu = env->GetBooleanField(profile, allowSuField);
auto umountModules = env->GetBooleanField(profile, umountModulesField);
app_profile p = {};
strcpy(p.key, p_key);
p.allow_su = allowSu;
p.current_uid = currentUid;
if (allowSu) {
p.root_profile.use_default = env->GetBooleanField(profile, rootUseDefaultField);
auto templateName = env->GetObjectField(profile, rootTemplateField);
if (templateName) {
auto ctemplateName = env->GetStringUTFChars((jstring) templateName, nullptr);
strcpy(p.root_profile.template_name, ctemplateName);
env->ReleaseStringUTFChars((jstring) templateName, ctemplateName);
}
p.root_profile.uid = uid;
p.root_profile.gid = gid;
p.root_profile.groups_count = getListSize(env, groups);
auto cdomain = env->GetStringUTFChars((jstring) domain, nullptr);
strcpy(p.root_profile.selinux_domain, cdomain);
env->ReleaseStringUTFChars((jstring) domain, cdomain);
// p.root_profile.namespaces = env->GetIntField(profile, namespacesField);
} else {
p.non_root_profile.use_default = env->GetBooleanField(profile, nonRootUseDefaultField);
p.non_root_profile.umount_modules = umountModules;
}
return set_app_profile(&p);
} }

View File

@@ -22,17 +22,8 @@
#define CMD_GET_DENY_LIST 6 #define CMD_GET_DENY_LIST 6
#define CMD_CHECK_SAFEMODE 9 #define CMD_CHECK_SAFEMODE 9
#define CMD_GET_WORK_MODE 10 #define CMD_GET_APP_PROFILE 10
#define CMD_SET_WORK_MODE 11 #define CMD_SET_APP_PROFILE 11
#define CMD_IN_ALLOW_LIST 12
#define CMD_IN_DENY_LIST 13
#define CMD_ADD_ALLOW_LIST 14
#define CMD_REMOVE_ALLOW_LIST 15
#define CMD_ADD_DENY_LIST 16
#define CMD_REMOVE_DENY_LIST 17
#define CMD_GET_APP_PROFILE 18
#define CMD_SET_APP_PROFILE 19
static bool ksuctl(int cmd, void* arg1, void* arg2) { static bool ksuctl(int cmd, void* arg1, void* arg2) {
int32_t result = 0; int32_t result = 0;
@@ -82,50 +73,6 @@ bool set_app_profile(const app_profile *profile) {
return ksuctl(CMD_SET_APP_PROFILE, (void*) profile, nullptr); return ksuctl(CMD_SET_APP_PROFILE, (void*) profile, nullptr);
} }
bool get_app_profile(int32_t key, app_profile *profile) { bool get_app_profile(p_key_t key, app_profile *profile) {
return ksuctl(CMD_GET_APP_PROFILE, (void*) profile, nullptr); return ksuctl(CMD_GET_APP_PROFILE, (void*) profile, nullptr);
} }
bool get_default_non_root_app_profile(app_profile *profile) {
return get_app_profile(DEFAULT_NON_ROOT_PROFILE_KEY, profile);
}
bool get_default_root_app_profile(app_profile *profile) {
return get_app_profile(DEFAULT_ROOT_PROFILE_KEY, profile);
}
bool is_allowlist_mode() {
int32_t mode = -1;
ksuctl(CMD_GET_WORK_MODE, &mode, nullptr);
// for kernel that doesn't support allowlist mode, return -1 and it is always allowlist mode
return mode <= 0;
}
bool set_allowlist_mode(bool allowlist_mode) {
int32_t mode = allowlist_mode ? 0 : 1;
return ksuctl(CMD_SET_WORK_MODE, &mode, nullptr);
}
bool is_in_allow_list(int uid) {
return ksuctl(CMD_IN_ALLOW_LIST, &uid, nullptr);
}
bool is_in_deny_list(int uid) {
return ksuctl(CMD_IN_DENY_LIST, &uid, nullptr);
}
bool add_to_allow_list(int uid) {
return ksuctl(CMD_ADD_ALLOW_LIST, &uid, nullptr);
}
bool remove_from_allow_list(int uid) {
return ksuctl(CMD_REMOVE_ALLOW_LIST, &uid, nullptr);
}
bool add_to_deny_list(int uid) {
return ksuctl(CMD_ADD_DENY_LIST, &uid, nullptr);
}
bool remove_from_deny_list(int uid) {
return ksuctl(CMD_REMOVE_DENY_LIST, &uid, nullptr);
}

View File

@@ -5,7 +5,7 @@
#ifndef KERNELSU_KSU_H #ifndef KERNELSU_KSU_H
#define KERNELSU_KSU_H #define KERNELSU_KSU_H
bool become_manager(const char*); bool become_manager(const char *);
int get_version(); int get_version();
@@ -17,33 +17,24 @@ bool get_deny_list(int *uids, int *size);
bool is_safe_mode(); bool is_safe_mode();
bool is_allowlist_mode(); #define KSU_MAX_PACKAGE_NAME 256
bool set_allowlist_mode(bool allowlist_mode);
bool is_in_allow_list(int uid);
bool is_in_deny_list(int uid);
bool add_to_allow_list(int uid);
bool remove_from_allow_list(int uid);
bool add_to_deny_list(int uid);
bool remove_from_deny_list(int uid);
// NGROUPS_MAX for Linux is 65535 generally, but we only supports 32 groups. // NGROUPS_MAX for Linux is 65535 generally, but we only supports 32 groups.
#define KSU_MAX_GROUPS 32 #define KSU_MAX_GROUPS 32
#define KSU_SELINUX_DOMAIN 64 #define KSU_SELINUX_DOMAIN 64
#define DEFAULT_ROOT_PROFILE_KEY 0 using p_key_t = char[KSU_MAX_PACKAGE_NAME];
#define DEFAULT_NON_ROOT_PROFILE_KEY 9999 // This UID means NOBODY in Android
struct app_profile { struct app_profile {
int32_t key; // this is usually the uid of the app, but can be other value for special apps // this is usually the package of the app, but can be other value for special apps
p_key_t key;
int32_t current_uid;
bool allow_su;
union {
struct {
bool use_default;
char template_name[KSU_MAX_PACKAGE_NAME];
int32_t uid; int32_t uid;
int32_t gid; int32_t gid;
@@ -51,15 +42,21 @@ struct app_profile {
int32_t groups_count; int32_t groups_count;
// kernel_cap_t is u32[2] // kernel_cap_t is u32[2]
uint64_t capabilities; int32_t capabilities[2];
char selinux_domain[KSU_SELINUX_DOMAIN]; char selinux_domain[KSU_SELINUX_DOMAIN];
bool allow_su; int32_t namespaces;
bool mount_module; } root_profile;
struct {
bool use_default;
bool umount_modules;
} non_root_profile;
};
}; };
bool set_app_profile(const app_profile *profile); bool set_app_profile(const app_profile *profile);
bool get_app_profile(int32_t key, app_profile *profile); bool get_app_profile(p_key_t key, app_profile *profile);
#endif //KERNELSU_KSU_H #endif //KERNELSU_KSU_H

View File

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

View 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("")
}
}

View File

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

View File

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

View File

@@ -11,36 +11,30 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R import me.weishu.kernelsu.R
import me.weishu.kernelsu.profile.AppProfile
import me.weishu.kernelsu.ui.component.SwitchItem import me.weishu.kernelsu.ui.component.SwitchItem
@Composable @Composable
fun AppProfileConfig( fun AppProfileConfig(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
fixedName: Boolean, fixedName: Boolean,
profile: AppProfile, profile: Natives.Profile,
onProfileChange: (AppProfile) -> Unit, onProfileChange: (Natives.Profile) -> Unit,
) { ) {
Column(modifier = modifier) { Column(modifier = modifier) {
if (!fixedName) { if (!fixedName) {
OutlinedTextField( OutlinedTextField(
label = { Text(stringResource(R.string.profile_name)) }, label = { Text(stringResource(R.string.profile_name)) },
value = profile.profileName, value = profile.name,
onValueChange = { onProfileChange(profile.copy(profileName = it)) } 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( SwitchItem(
title = stringResource(R.string.profile_unmount_modules), title = stringResource(R.string.profile_unmount_modules),
checked = profile.unmountModules, checked = profile.umountModules,
onCheckedChange = { onProfileChange(profile.copy(unmountModules = it)) } onCheckedChange = { onProfileChange(profile.copy(umountModules = it)) }
) )
} }
} }
@@ -48,7 +42,7 @@ fun AppProfileConfig(
@Preview @Preview
@Composable @Composable
private fun AppProfileConfigPreview() { private fun AppProfileConfigPreview() {
var profile by remember { mutableStateOf(AppProfile("")) } var profile by remember { mutableStateOf(Natives.Profile("")) }
AppProfileConfig(fixedName = true, profile = profile) { AppProfileConfig(fixedName = true, profile = profile) {
profile = it profile = it
} }

View File

@@ -21,31 +21,31 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R import me.weishu.kernelsu.R
import me.weishu.kernelsu.profile.RootProfile
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun RootProfileConfig( fun RootProfileConfig(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
fixedName: Boolean, fixedName: Boolean,
profile: RootProfile, profile: Natives.Profile,
onProfileChange: (RootProfile) -> Unit, onProfileChange: (Natives.Profile) -> Unit,
) { ) {
Column(modifier = modifier) { Column(modifier = modifier) {
if (!fixedName) { if (!fixedName) {
OutlinedTextField( OutlinedTextField(
label = { Text(stringResource(R.string.profile_name)) }, label = { Text(stringResource(R.string.profile_name)) },
value = profile.profileName, value = profile.name,
onValueChange = { onProfileChange(profile.copy(profileName = it)) } onValueChange = { onProfileChange(profile.copy(name = it)) }
) )
} }
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
val currentNamespace = when (profile.namespace) { val currentNamespace = when (profile.namespace) {
RootProfile.Namespace.Inherited -> stringResource(R.string.profile_namespace_inherited) Natives.Profile.Namespace.Inherited -> stringResource(R.string.profile_namespace_inherited)
RootProfile.Namespace.Global -> stringResource(R.string.profile_namespace_global) Natives.Profile.Namespace.Global -> stringResource(R.string.profile_namespace_global)
RootProfile.Namespace.Individual -> stringResource(R.string.profile_namespace_individual) Natives.Profile.Namespace.Individual -> stringResource(R.string.profile_namespace_individual)
} }
ListItem(headlineContent = { ListItem(headlineContent = {
ExposedDropdownMenuBox( ExposedDropdownMenuBox(
@@ -70,21 +70,21 @@ fun RootProfileConfig(
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(R.string.profile_namespace_inherited)) }, text = { Text(stringResource(R.string.profile_namespace_inherited)) },
onClick = { onClick = {
onProfileChange(profile.copy(namespace = RootProfile.Namespace.Inherited)) onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.Inherited))
expanded = false expanded = false
}, },
) )
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(R.string.profile_namespace_global)) }, text = { Text(stringResource(R.string.profile_namespace_global)) },
onClick = { onClick = {
onProfileChange(profile.copy(namespace = RootProfile.Namespace.Global)) onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.Global))
expanded = false expanded = false
}, },
) )
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(R.string.profile_namespace_individual)) }, text = { Text(stringResource(R.string.profile_namespace_individual)) },
onClick = { onClick = {
onProfileChange(profile.copy(namespace = RootProfile.Namespace.Individual)) onProfileChange(profile.copy(namespace = Natives.Profile.Namespace.Individual))
expanded = false expanded = false
}, },
) )
@@ -97,7 +97,18 @@ fun RootProfileConfig(
label = { Text("uid") }, label = { Text("uid") },
value = profile.uid.toString(), value = profile.uid.toString(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), 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") }, label = { Text("gid") },
value = profile.gid.toString(), value = profile.gid.toString(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), 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 = { ListItem(headlineContent = {
OutlinedTextField( OutlinedTextField(
label = { Text("groups") }, label = { Text("groups") },
value = profile.groups.toString(), value = profile.groups.joinToString(","),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), 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( OutlinedTextField(
label = { Text("context") }, label = { Text("context") },
value = profile.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 @Preview
@Composable @Composable
private fun RootProfileConfigPreview() { private fun RootProfileConfigPreview() {
var profile by remember { mutableStateOf(RootProfile("")) } var profile by remember { mutableStateOf(Natives.Profile("")) }
RootProfileConfig(fixedName = true, profile = profile) { RootProfileConfig(fixedName = true, profile = profile) {
profile = it profile = it
} }

View File

@@ -1,5 +1,6 @@
package me.weishu.kernelsu.ui.screen package me.weishu.kernelsu.ui.screen
import android.util.Log
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@@ -49,8 +50,6 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.weishu.kernelsu.Natives import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R 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.SwitchItem
import me.weishu.kernelsu.ui.component.profile.AppProfileConfig import me.weishu.kernelsu.ui.component.profile.AppProfileConfig
import me.weishu.kernelsu.ui.component.profile.RootProfileConfig import me.weishu.kernelsu.ui.component.profile.RootProfileConfig
@@ -70,8 +69,15 @@ fun AppProfileScreen(
val context = LocalContext.current val context = LocalContext.current
val snackbarHost = LocalSnackbarHost.current val snackbarHost = LocalSnackbarHost.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val failToGrantRoot = stringResource(R.string.superuser_failed_to_grant_root) val failToUpdateAppProfile =
var isRootGranted by rememberSaveable { mutableStateOf(appInfo.onAllowList) } 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( Scaffold(
topBar = { TopBar { navigator.popBackStack() } } topBar = { TopBar { navigator.popBackStack() } }
@@ -95,14 +101,13 @@ fun AppProfileScreen(
.height(48.dp) .height(48.dp)
) )
}, },
isRootGranted = isRootGranted, profile = profile,
onSwitchRootPermission = { grant -> onProfileChange = {
scope.launch { scope.launch {
val success = Natives.allowRoot(appInfo.uid, grant) if (!Natives.setAppProfile(it)) {
if (success) { snackbarHost.showSnackbar(failToUpdateAppProfile.format(appInfo.uid))
isRootGranted = grant
} else { } else {
snackbarHost.showSnackbar(failToGrantRoot.format(appInfo.uid)) profile = it
} }
} }
}, },
@@ -117,9 +122,11 @@ private fun AppProfileInner(
packageName: String, packageName: String,
appLabel: String, appLabel: String,
appIcon: @Composable () -> Unit, appIcon: @Composable () -> Unit,
isRootGranted: Boolean, profile: Natives.Profile,
onSwitchRootPermission: (Boolean) -> Unit, onProfileChange: (Natives.Profile) -> Unit,
) { ) {
val isRootGranted = profile.allowSu
Column(modifier = modifier) { Column(modifier = modifier) {
ListItem( ListItem(
headlineContent = { Text(appLabel) }, headlineContent = { Text(appLabel) },
@@ -131,14 +138,32 @@ private fun AppProfileInner(
icon = Icons.Filled.Security, icon = Icons.Filled.Security,
title = stringResource(id = R.string.superuser), title = stringResource(id = R.string.superuser),
checked = isRootGranted, checked = isRootGranted,
onCheckedChange = onSwitchRootPermission, onCheckedChange = { onProfileChange(profile.copy(allowSu = it)) },
) )
Crossfade(targetState = isRootGranted, label = "") { current -> Crossfade(targetState = isRootGranted, label = "") { current ->
Column { Column {
if (current) { if (current) {
var mode by rememberSaveable { mutableStateOf(Mode.Default) } val mode = if (profile.rootUseDefault) {
ProfileBox(mode, true) { mode = it } 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 -> Crossfade(targetState = mode, label = "") { currentMode ->
if (currentMode == Mode.Template) { if (currentMode == Mode.Template) {
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
@@ -163,11 +188,10 @@ private fun AppProfileInner(
} }
}) })
} else if (mode == Mode.Custom) { } else if (mode == Mode.Custom) {
var profile by rememberSaveable { mutableStateOf(RootProfile("@$packageName")) }
RootProfileConfig( RootProfileConfig(
fixedName = true, fixedName = true,
profile = profile, profile = profile,
onProfileChange = { profile = it } onProfileChange = onProfileChange
) )
} }
} }
@@ -176,11 +200,10 @@ private fun AppProfileInner(
ProfileBox(mode, false) { mode = it } ProfileBox(mode, false) { mode = it }
Crossfade(targetState = mode, label = "") { currentMode -> Crossfade(targetState = mode, label = "") { currentMode ->
if (currentMode == Mode.Custom) { if (currentMode == Mode.Custom) {
var profile by rememberSaveable { mutableStateOf(AppProfile(packageName)) }
AppProfileConfig( AppProfileConfig(
fixedName = true, fixedName = true,
profile = profile, profile = profile,
onProfileChange = { profile = it } onProfileChange = onProfileChange
) )
} }
} }
@@ -256,12 +279,15 @@ private fun ProfileBox(
@Preview @Preview
@Composable @Composable
private fun AppProfilePreview() { private fun AppProfilePreview() {
var isRootGranted by remember { mutableStateOf(false) } var profile by remember { mutableStateOf(Natives.Profile("")) }
AppProfileInner( AppProfileInner(
packageName = "icu.nullptr.test", packageName = "icu.nullptr.test",
appLabel = "Test", appLabel = "Test",
appIcon = { Icon(Icons.Filled.Android, null) }, appIcon = { Icon(Icons.Filled.Android, null) },
isRootGranted = isRootGranted, profile = profile,
onSwitchRootPermission = { isRootGranted = it }, onProfileChange = {
profile = it
},
) )
} }

View File

@@ -56,7 +56,7 @@ fun HomeScreen(navigator: DestinationsNavigator) {
SideEffect { SideEffect {
if (isManager) install() if (isManager) install()
} }
val ksuVersion = if (isManager) Natives.getVersion() else null val ksuVersion = if (isManager) Natives.version else null
StatusCard(kernelVersion, ksuVersion) StatusCard(kernelVersion, ksuVersion)
InfoCard() InfoCard()
@@ -141,7 +141,7 @@ private fun StatusCard(kernelVersion: KernelVersion, ksuVersion: Int?) {
) { ) {
when { when {
ksuVersion != null -> { ksuVersion != null -> {
val appendText = if (Natives.isSafeMode()) { val appendText = if (Natives.isSafeMode) {
" [${stringResource(id = R.string.safe_mode)}]" " [${stringResource(id = R.string.safe_mode)}]"
} else { } else {
"" ""

View File

@@ -50,8 +50,8 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
} }
} }
val isSafeMode = Natives.isSafeMode() val isSafeMode = Natives.isSafeMode
val isKSUVersionInvalid = Natives.getVersion() < 0 val isKSUVersionInvalid = Natives.version < 0
val hasMagisk = hasMagisk() val hasMagisk = hasMagisk()
val hideInstallButton = isSafeMode || isKSUVersionInvalid || hasMagisk val hideInstallButton = isSafeMode || isKSUVersionInvalid || hasMagisk

View File

@@ -71,7 +71,7 @@ fun getModuleCount(): Int {
} }
fun getSuperuserCount(): Int { fun getSuperuserCount(): Int {
return Natives.getAllowList().size return Natives.allowList.size
} }
fun toggleModule(id: String, enable: Boolean): Boolean { fun toggleModule(id: String, enable: Boolean): Boolean {

View File

@@ -72,9 +72,9 @@ fun getBugreportFile(context: Context): File {
pw.println("Nodename: ${uname.nodename}") pw.println("Nodename: ${uname.nodename}")
pw.println("Sysname: ${uname.sysname}") pw.println("Sysname: ${uname.sysname}")
val ksuKernel = Natives.getVersion() val ksuKernel = Natives.version
pw.println("KernelSU: $ksuKernel") pw.println("KernelSU: $ksuKernel")
val safeMode = Natives.isSafeMode() val safeMode = Natives.isSafeMode
pw.println("SafeMode: $safeMode") pw.println("SafeMode: $safeMode")
} }

View File

@@ -116,8 +116,8 @@ class SuperUserViewModel : ViewModel() {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val pm = ksuApp.packageManager val pm = ksuApp.packageManager
val allowList = Natives.getAllowList().toSet() val allowList = Natives.allowList.toSet()
val denyList = Natives.getDenyList().toSet() val denyList = Natives.denyList.toSet()
Log.i(TAG, "allowList: $allowList") Log.i(TAG, "allowList: $allowList")
Log.i(TAG, "denyList: $denyList") Log.i(TAG, "denyList: $denyList")
val start = SystemClock.elapsedRealtime() val start = SystemClock.elapsedRealtime()

View File

@@ -76,4 +76,6 @@
<string name="profile_namespace_individual">Individual</string> <string name="profile_namespace_individual">Individual</string>
<string name="profile_unmount_modules">Unmount modules</string> <string name="profile_unmount_modules">Unmount modules</string>
<string name="profile_allow_root_request">Allow root request</string> <string name="profile_allow_root_request">Allow root request</string>
<string name="failed_to_update_root_profile">Failed to update root profile for %s</string>
<string name="failed_to_update_app_profile">Failed to update app profile for %s</string>
</resources> </resources>