From 41265b0203f61a7428a01898e742d89a2feff30a Mon Sep 17 00:00:00 2001 From: weishu Date: Wed, 17 May 2023 11:23:46 +0800 Subject: [PATCH] manager: implement app profile api call --- manager/app/src/main/cpp/jni.cc | 207 ++++++++++++++---- manager/app/src/main/cpp/ksu.cc | 59 +---- manager/app/src/main/cpp/ksu.h | 63 +++--- .../main/java/me/weishu/kernelsu/Natives.java | 42 ---- .../main/java/me/weishu/kernelsu/Natives.kt | 75 +++++++ .../me/weishu/kernelsu/profile/AppProfile.kt | 13 -- .../me/weishu/kernelsu/profile/RootProfile.kt | 23 -- .../ui/component/profile/AppProfileConfig.kt | 22 +- .../ui/component/profile/RootProfileConfig.kt | 68 ++++-- .../weishu/kernelsu/ui/screen/AppProfile.kt | 70 ++++-- .../java/me/weishu/kernelsu/ui/screen/Home.kt | 4 +- .../me/weishu/kernelsu/ui/screen/Module.kt | 4 +- .../java/me/weishu/kernelsu/ui/util/KsuCli.kt | 2 +- .../me/weishu/kernelsu/ui/util/LogEvent.kt | 4 +- .../ui/viewmodel/SuperUserViewModel.kt | 4 +- manager/app/src/main/res/values/strings.xml | 2 + 16 files changed, 395 insertions(+), 267 deletions(-) delete mode 100644 manager/app/src/main/java/me/weishu/kernelsu/Natives.java create mode 100644 manager/app/src/main/java/me/weishu/kernelsu/Natives.kt delete mode 100644 manager/app/src/main/java/me/weishu/kernelsu/profile/AppProfile.kt delete mode 100644 manager/app/src/main/java/me/weishu/kernelsu/profile/RootProfile.kt diff --git a/manager/app/src/main/cpp/jni.cc b/manager/app/src/main/cpp/jni.cc index 58d5c461..db1d5b9b 100644 --- a/manager/app/src/main/cpp/jni.cc +++ b/manager/app/src/main/cpp/jni.cc @@ -3,15 +3,16 @@ #include #include +#include #include "ksu.h" -#define LOG_TAG "KernelSu" +#define LOG_TAG "KernelSU" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) extern "C" 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 result = become_manager(cpkg); env->ReleaseStringUTFChars(pkg, cpkg); @@ -20,13 +21,13 @@ Java_me_weishu_kernelsu_Natives_becomeManager(JNIEnv *env, jclass clazz, jstring extern "C" 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(); } extern "C" 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 size = 0; bool result = get_allow_list(uids, &size); @@ -56,7 +57,7 @@ Java_me_weishu_kernelsu_Natives_getDenyList(JNIEnv *env, jclass clazz) { extern "C" 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); } @@ -66,43 +67,173 @@ Java_me_weishu_kernelsu_Natives_isSafeMode(JNIEnv *env, jclass clazz) { return is_safe_mode(); } -extern "C" -JNIEXPORT jboolean JNICALL -Java_me_weishu_kernelsu_Natives_isAllowlistMode(JNIEnv *env, jclass clazz) { - return is_allowlist_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, "", "(I)V"); + for (int i = 0; i < count; ++i) { + auto integer = env->NewObject(integerCls, constructor, data[i]); + env->CallBooleanMethod(list, add, integer); + } } -extern "C" -JNIEXPORT jboolean JNICALL -Java_me_weishu_kernelsu_Natives_setAllowlistMode(JNIEnv *env, jclass clazz, jboolean is_allowlist) { - return set_allowlist_mode(is_allowlist); + +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 jboolean JNICALL -Java_me_weishu_kernelsu_Natives_addUidToAllowlist(JNIEnv *env, jclass clazz, jint uid) { - return add_to_allow_list(uid); +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, "", "()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" JNIEXPORT jboolean JNICALL -Java_me_weishu_kernelsu_Natives_removeUidFromAllowlist(JNIEnv *env, jclass clazz, jint uid) { - return remove_from_allow_list(uid); -} -extern "C" -JNIEXPORT jboolean JNICALL -Java_me_weishu_kernelsu_Natives_addUidToDenylist(JNIEnv *env, jclass clazz, jint uid) { - return add_to_deny_list(uid); -} -extern "C" -JNIEXPORT jboolean JNICALL -Java_me_weishu_kernelsu_Natives_removeUidFromDenylist(JNIEnv *env, jclass clazz, jint uid) { - return remove_from_deny_list(uid); -} -extern "C" -JNIEXPORT jboolean JNICALL -Java_me_weishu_kernelsu_Natives_isUidInAllowlist(JNIEnv *env, jclass clazz, jint uid) { - return is_in_allow_list(uid); -} -extern "C" -JNIEXPORT jboolean JNICALL -Java_me_weishu_kernelsu_Natives_isUidInDenylist(JNIEnv *env, jclass clazz, jint uid) { - return is_in_deny_list(uid); +Java_me_weishu_kernelsu_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) { + auto cls = env->FindClass("me/weishu/kernelsu/Natives$Profile"); + + 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, "namespaces", "I"); + + auto nonRootUseDefaultField = env->GetFieldID(cls, "nonRootUseDefault", "Z"); + auto umountModulesField = env->GetFieldID(cls, "umountModules", "Z"); + + auto key = env->GetObjectField(profile, keyField); + if (!key) { + return false; + } + if (env->GetStringLength((jstring) key) > KSU_MAX_PACKAGE_NAME) { + return false; + } + + auto cpkg = env->GetStringUTFChars((jstring) key, nullptr); + p_key_t p_key = {}; + strcpy(p_key, cpkg); + env->ReleaseStringUTFChars((jstring) key, cpkg); + + auto currentUid = env->GetIntField(profile, currentUidField); + + auto uid = env->GetIntField(profile, uidField); + 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); } \ No newline at end of file diff --git a/manager/app/src/main/cpp/ksu.cc b/manager/app/src/main/cpp/ksu.cc index e6c1d7c6..747cabd4 100644 --- a/manager/app/src/main/cpp/ksu.cc +++ b/manager/app/src/main/cpp/ksu.cc @@ -22,17 +22,8 @@ #define CMD_GET_DENY_LIST 6 #define CMD_CHECK_SAFEMODE 9 -#define CMD_GET_WORK_MODE 10 -#define CMD_SET_WORK_MODE 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 +#define CMD_GET_APP_PROFILE 10 +#define CMD_SET_APP_PROFILE 11 static bool ksuctl(int cmd, void* arg1, void* arg2) { 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); } -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); } - -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); -} \ No newline at end of file diff --git a/manager/app/src/main/cpp/ksu.h b/manager/app/src/main/cpp/ksu.h index b85e2684..0926fa55 100644 --- a/manager/app/src/main/cpp/ksu.h +++ b/manager/app/src/main/cpp/ksu.h @@ -5,7 +5,7 @@ #ifndef KERNELSU_KSU_H #define KERNELSU_KSU_H -bool become_manager(const char*); +bool become_manager(const char *); int get_version(); @@ -17,49 +17,46 @@ bool get_deny_list(int *uids, int *size); bool is_safe_mode(); -bool is_allowlist_mode(); - -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); - +#define KSU_MAX_PACKAGE_NAME 256 // NGROUPS_MAX for Linux is 65535 generally, but we only supports 32 groups. #define KSU_MAX_GROUPS 32 #define KSU_SELINUX_DOMAIN 64 -#define DEFAULT_ROOT_PROFILE_KEY 0 -#define DEFAULT_NON_ROOT_PROFILE_KEY 9999 // This UID means NOBODY in Android +using p_key_t = char[KSU_MAX_PACKAGE_NAME]; struct app_profile { - int32_t key; // this is usually the uid of the app, but can be other value for special apps - - int32_t uid; - int32_t gid; - - int32_t groups[KSU_MAX_GROUPS]; - int32_t groups_count; - - // kernel_cap_t is u32[2] - uint64_t capabilities; - char selinux_domain[KSU_SELINUX_DOMAIN]; - + // 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; - bool mount_module; + + union { + struct { + bool use_default; + char template_name[KSU_MAX_PACKAGE_NAME]; + int32_t uid; + int32_t gid; + + int32_t groups[KSU_MAX_GROUPS]; + int32_t groups_count; + + // kernel_cap_t is u32[2] + int32_t capabilities[2]; + char selinux_domain[KSU_SELINUX_DOMAIN]; + + int32_t namespaces; + } root_profile; + + struct { + bool use_default; + bool umount_modules; + } non_root_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 diff --git a/manager/app/src/main/java/me/weishu/kernelsu/Natives.java b/manager/app/src/main/java/me/weishu/kernelsu/Natives.java deleted file mode 100644 index 1af5de15..00000000 --- a/manager/app/src/main/java/me/weishu/kernelsu/Natives.java +++ /dev/null @@ -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); -} diff --git a/manager/app/src/main/java/me/weishu/kernelsu/Natives.kt b/manager/app/src/main/java/me/weishu/kernelsu/Natives.kt new file mode 100644 index 00000000..72a7b453 --- /dev/null +++ b/manager/app/src/main/java/me/weishu/kernelsu/Natives.kt @@ -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 = mutableListOf(), + val capabilities: List = 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("") + } +} \ No newline at end of file diff --git a/manager/app/src/main/java/me/weishu/kernelsu/profile/AppProfile.kt b/manager/app/src/main/java/me/weishu/kernelsu/profile/AppProfile.kt deleted file mode 100644 index 7511df81..00000000 --- a/manager/app/src/main/java/me/weishu/kernelsu/profile/AppProfile.kt +++ /dev/null @@ -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 diff --git a/manager/app/src/main/java/me/weishu/kernelsu/profile/RootProfile.kt b/manager/app/src/main/java/me/weishu/kernelsu/profile/RootProfile.kt deleted file mode 100644 index dca64b28..00000000 --- a/manager/app/src/main/java/me/weishu/kernelsu/profile/RootProfile.kt +++ /dev/null @@ -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 = emptyList(), - val context: String = "u:r:su:s0", -) : Parcelable { - enum class Namespace { - Inherited, - Global, - Individual, - } -} diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/AppProfileConfig.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/AppProfileConfig.kt index 2e5ccd35..a3ada44d 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/AppProfileConfig.kt +++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/AppProfileConfig.kt @@ -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 } diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/RootProfileConfig.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/RootProfileConfig.kt index 387b932b..b01263ee 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/RootProfileConfig.kt +++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/component/profile/RootProfileConfig.kt @@ -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 } diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/AppProfile.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/AppProfile.kt index 96ebd55c..ef29f92a 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/AppProfile.kt +++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/AppProfile.kt @@ -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 + }, ) } + diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Home.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Home.kt index 77fc1e6a..a1c29338 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Home.kt +++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Home.kt @@ -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 { "" diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Module.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Module.kt index b0c7f06f..40d81340 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Module.kt +++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Module.kt @@ -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 diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt index 4cf9bc0b..5805e460 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt +++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt @@ -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 { diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/LogEvent.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/util/LogEvent.kt index 1151d8ac..17d3ec9c 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/LogEvent.kt +++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/util/LogEvent.kt @@ -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") } diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/SuperUserViewModel.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/SuperUserViewModel.kt index 3748e993..84b3a562 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/SuperUserViewModel.kt +++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/SuperUserViewModel.kt @@ -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() diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml index d15d3772..fd3684b2 100644 --- a/manager/app/src/main/res/values/strings.xml +++ b/manager/app/src/main/res/values/strings.xml @@ -76,4 +76,6 @@ Individual Unmount modules Allow root request + Failed to update root profile for %s + Failed to update app profile for %s