diff --git a/README.md b/README.md index ef106c2..f447d47 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,9 @@ In conclusion, we can get a super user interface similar to standard Android API ## Guide for users * Install the zip from Magisk manager -* Show Management UI: enter `*#*#784784#*#*` in the default dialer app +* Open Management UI: + - Trough a notification, this notification will show when you are in "Developer options" + - Enter `*#*#784784#*#*` in the default dialer app * Play with apps that support Sui ## Guide for application developers diff --git a/module/proguard-rules.pro b/module/proguard-rules.pro index 0ef8874..b643da1 100644 --- a/module/proguard-rules.pro +++ b/module/proguard-rules.pro @@ -17,6 +17,10 @@ public static void main(java.lang.String[], java.nio.ByteBuffer[]); } +-keep class rikka.sui.settings.SettingsProcess { + public static void main(java.lang.String[], java.nio.ByteBuffer[]); +} + -keepnames class * implements android.os.Parcelable -keepclassmembers class * implements android.os.Parcelable { diff --git a/module/src/main/cpp/CMakeLists.txt b/module/src/main/cpp/CMakeLists.txt index d874c29..79cf291 100644 --- a/module/src/main/cpp/CMakeLists.txt +++ b/module/src/main/cpp/CMakeLists.txt @@ -44,7 +44,8 @@ add_library(${MODULE_NAME} SHARED bridge_service.cpp android.cpp plt.c - manager_process.cpp) + manager_process.cpp + settings_process.cpp) target_link_libraries(${MODULE_NAME} log riru::riru nativehelper::nativehelper_header_only) set_target_properties(${MODULE_NAME} PROPERTIES LINK_FLAGS_RELEASE -s) diff --git a/module/src/main/cpp/config.h b/module/src/main/cpp/config.h index ba29f9d..cc53e69 100644 --- a/module/src/main/cpp/config.h +++ b/module/src/main/cpp/config.h @@ -3,9 +3,11 @@ #define ROOT_PATH "/data/adb/sui" #define FALLBACK_DEX_DIR "/data/system/sui" #define MANAGER_APPLICATION_ID "com.android.systemui" +#define SETTINGS_APPLICATION_ID "com.android.settings" #define DEX_NAME "sui.dex" #define DEX_PATH ROOT_PATH "/" DEX_NAME #define RES_PATH ROOT_PATH "/res" #define SYSTEM_PROCESS_CLASSNAME "rikka/sui/systemserver/SystemProcess" #define MANAGER_PROCESS_CLASSNAME "rikka/sui/manager/ManagerProcess" +#define SETTINGS_PROCESS_CLASSNAME "rikka/sui/settings/SettingsProcess" diff --git a/module/src/main/cpp/main.cpp b/module/src/main/cpp/main.cpp index a74d795..6d6fa95 100644 --- a/module/src/main/cpp/main.cpp +++ b/module/src/main/cpp/main.cpp @@ -11,20 +11,21 @@ #include "system_server.h" #include "config.h" #include "manager_process.h" +#include "settings_process.h" static DexFile *dexFile = nullptr; -static std::vector *files = nullptr; +static std::vector *resources_files = nullptr; static void PrepareFiles() { if (dexFile && dexFile->getBytes()) return; dexFile = new DexFile(DEX_PATH); - files = new std::vector(); - files->emplace_back(new File(RES_PATH "/layout/confirmation_dialog.xml")); - files->emplace_back(new File(RES_PATH "/layout/management_dialog.xml")); - files->emplace_back(new File(RES_PATH "/layout/management_app_item.xml")); - files->emplace_back(new File(RES_PATH "/drawable/ic_su_24.xml")); - files->emplace_back(new File(RES_PATH "/drawable/ic_close_24.xml")); + resources_files = new std::vector(); + resources_files->emplace_back(new File(RES_PATH "/layout/confirmation_dialog.xml")); + resources_files->emplace_back(new File(RES_PATH "/layout/management_dialog.xml")); + resources_files->emplace_back(new File(RES_PATH "/layout/management_app_item.xml")); + resources_files->emplace_back(new File(RES_PATH "/drawable/ic_su_24.xml")); + resources_files->emplace_back(new File(RES_PATH "/drawable/ic_close_24.xml")); } static void DestroyFiles(JNIEnv *env) { @@ -33,12 +34,12 @@ static void DestroyFiles(JNIEnv *env) { delete dexFile; dexFile = nullptr; } - if (files) { - for (auto *file : *files) { + if (resources_files) { + for (auto *file : *resources_files) { delete file; } - delete files; - files = nullptr; + delete resources_files; + resources_files = nullptr; } } @@ -84,8 +85,11 @@ static void appProcessPost( JNIEnv *env, const char *from, const char *package_name, const char *app_data_dir, jint uid) { if (strcmp(saved_package_name, MANAGER_APPLICATION_ID) == 0) { - LOGV("%s: uid=%d, package=%s, dir=%s", from, uid, package_name, app_data_dir); - Manager::main(env, app_data_dir, dexFile, files); + LOGV("%s: manager process, uid=%d, package=%s, dir=%s", from, uid, package_name, app_data_dir); + Manager::main(env, app_data_dir, dexFile, resources_files); + } else if (strcmp(saved_package_name, SETTINGS_APPLICATION_ID) == 0) { + LOGV("%s: settings process, uid=%d, package=%s, dir=%s", from, uid, package_name, app_data_dir); + Settings::main(env, app_data_dir, dexFile, resources_files); } else { DestroyFiles(env); } diff --git a/module/src/main/cpp/settings_process.cpp b/module/src/main/cpp/settings_process.cpp new file mode 100644 index 0000000..6a97c57 --- /dev/null +++ b/module/src/main/cpp/settings_process.cpp @@ -0,0 +1,99 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "android.h" +#include "logging.h" +#include "riru.h" +#include "misc.h" +#include "dex_file.h" +#include "bridge_service.h" +#include "binder_hook.h" +#include "config.h" + +namespace Settings { + + static jclass mainClass = nullptr; + + static bool installDex(JNIEnv *env, const char *appDataDir, DexFile *dexFile, std::vector *files) { + if (android::GetApiLevel() >= 26) { + dexFile->createInMemoryDexClassLoader(env); + } else { + char dexDir[PATH_MAX], oatDir[PATH_MAX]; + snprintf(dexDir, PATH_MAX, "%s/sui", appDataDir); + snprintf(oatDir, PATH_MAX, "%s/sui/oat", appDataDir); + dexFile->createDexClassLoader(env, dexDir, DEX_NAME, oatDir); + } + + mainClass = dexFile->findClass(env, SETTINGS_PROCESS_CLASSNAME); + if (!mainClass) { + LOGE("unable to find main class"); + return false; + } + mainClass = (jclass) env->NewGlobalRef(mainClass); + + auto mainMethod = env->GetStaticMethodID(mainClass, "main", "([Ljava/lang/String;[Ljava/nio/ByteBuffer;)V"); + if (!mainMethod) { + LOGE("unable to find main method"); + env->ExceptionDescribe(); + env->ExceptionClear(); + return false; + } + + auto args = env->NewObjectArray(1, env->FindClass("java/lang/String"), nullptr); + char buf[64]; + sprintf(buf, "--version-code=%d", RIRU_MODULE_VERSION); + env->SetObjectArrayElement(args, 0, env->NewStringUTF(buf)); + + auto buffers = env->NewObjectArray(files->size(), env->FindClass("java/nio/ByteBuffer"), nullptr); + for (auto i = 0; i < files->size(); ++i) { + auto file = files->at(i); + env->SetObjectArrayElement(buffers, i, env->NewDirectByteBuffer(file->getBytes(), file->getSize())); + } + + env->CallStaticVoidMethod(mainClass, mainMethod, args, buffers); + if (env->ExceptionCheck()) { + LOGE("unable to call main method"); + env->ExceptionDescribe(); + env->ExceptionClear(); + return false; + } + + return true; + } + + void main(JNIEnv *env, const char *appDataDir, DexFile *dexFile, std::vector *files) { + LOGD("dex size=%" PRIdPTR, dexFile->getSize()); + + if (!dexFile->getBytes()) { + LOGE("no dex"); + return; + } + + LOGV("main: manager"); + + LOGV("install dex"); + + if (!installDex(env, appDataDir, dexFile, files)) { + LOGE("can't install dex"); + return; + } + + LOGV("install dex finished"); + } +} \ No newline at end of file diff --git a/module/src/main/cpp/settings_process.h b/module/src/main/cpp/settings_process.h new file mode 100644 index 0000000..0fb9658 --- /dev/null +++ b/module/src/main/cpp/settings_process.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include "dex_file.h" + +namespace Settings { + void main(JNIEnv *env, const char *appDataDir, DexFile *dexFile, std::vector *files); +} diff --git a/module/src/main/java/rikka/sui/ktx/TextView.kt b/module/src/main/java/rikka/sui/ktx/TextView.kt index 8952dff..aaae88a 100644 --- a/module/src/main/java/rikka/sui/ktx/TextView.kt +++ b/module/src/main/java/rikka/sui/ktx/TextView.kt @@ -2,8 +2,8 @@ package rikka.sui.ktx import android.view.View import android.widget.TextView -import rikka.sui.manager.res.Res.string -import rikka.sui.manager.res.Strings +import rikka.sui.resource.Res +import rikka.sui.resource.Strings private const val tag_countdown = 1599296841 @@ -17,7 +17,7 @@ fun TextView.applyCountdown(countdownSecond: Int, message: CharSequence? = null, if (message != null) text = message } else { isEnabled = false - if (message != null && format != 0) text = String.format(Strings.get(string.brackets_format), message, countdown.toString()) + if (message != null && format != 0) text = String.format(Strings.get(Res.string.brackets_format), message, countdown.toString()) postDelayed(this, 1000) } } diff --git a/module/src/main/java/rikka/sui/manager/ManagerProcess.java b/module/src/main/java/rikka/sui/manager/ManagerProcess.java index a984cef..06ab80f 100644 --- a/module/src/main/java/rikka/sui/manager/ManagerProcess.java +++ b/module/src/main/java/rikka/sui/manager/ManagerProcess.java @@ -18,8 +18,9 @@ import rikka.sui.ktx.HandlerKt; import rikka.sui.manager.dialog.ConfirmationDialog; import rikka.sui.manager.dialog.ManagementDialog; -import rikka.sui.manager.res.Xml; +import rikka.sui.resource.Xml; import rikka.sui.server.ServerConstants; +import rikka.sui.util.BridgeServiceClient; import static rikka.sui.manager.ManagerConstants.LOGGER; diff --git a/module/src/main/java/rikka/sui/manager/dialog/ConfirmationDialog.java b/module/src/main/java/rikka/sui/manager/dialog/ConfirmationDialog.java index 1489066..cf65556 100644 --- a/module/src/main/java/rikka/sui/manager/dialog/ConfirmationDialog.java +++ b/module/src/main/java/rikka/sui/manager/dialog/ConfirmationDialog.java @@ -32,11 +32,11 @@ import rikka.sui.ktx.ResourcesKt; import rikka.sui.ktx.TextViewKt; import rikka.sui.ktx.WindowKt; -import rikka.sui.manager.BridgeServiceClient; -import rikka.sui.manager.res.Res; -import rikka.sui.manager.res.Strings; -import rikka.sui.manager.res.Utils; -import rikka.sui.manager.res.Xml; +import rikka.sui.util.BridgeServiceClient; +import rikka.sui.resource.Res; +import rikka.sui.resource.Strings; +import rikka.sui.resource.Utils; +import rikka.sui.resource.Xml; import rikka.sui.util.UserHandleCompat; import static rikka.shizuku.ShizukuApiConstants.REQUEST_PERMISSION_REPLY_ALLOWED; diff --git a/module/src/main/java/rikka/sui/manager/dialog/ManagementDialog.java b/module/src/main/java/rikka/sui/manager/dialog/ManagementDialog.java index 9159348..5e15d22 100644 --- a/module/src/main/java/rikka/sui/manager/dialog/ManagementDialog.java +++ b/module/src/main/java/rikka/sui/manager/dialog/ManagementDialog.java @@ -47,11 +47,11 @@ import rikka.sui.ktx.HandlerKt; import rikka.sui.ktx.ResourcesKt; import rikka.sui.ktx.WindowKt; -import rikka.sui.manager.BridgeServiceClient; +import rikka.sui.util.BridgeServiceClient; import rikka.sui.manager.WorkerHandler; -import rikka.sui.manager.res.Res; -import rikka.sui.manager.res.Strings; -import rikka.sui.manager.res.Xml; +import rikka.sui.resource.Res; +import rikka.sui.resource.Strings; +import rikka.sui.resource.Xml; import rikka.sui.model.AppInfo; import rikka.sui.server.config.Config; import rikka.sui.util.UserHandleCompat; diff --git a/module/src/main/java/rikka/sui/manager/res/Res.java b/module/src/main/java/rikka/sui/resource/Res.java similarity index 85% rename from module/src/main/java/rikka/sui/manager/res/Res.java rename to module/src/main/java/rikka/sui/resource/Res.java index 7d26619..a485b0b 100644 --- a/module/src/main/java/rikka/sui/manager/res/Res.java +++ b/module/src/main/java/rikka/sui/resource/Res.java @@ -1,4 +1,4 @@ -package rikka.sui.manager.res; +package rikka.sui.resource; public final class Res { @@ -30,6 +30,8 @@ public final static class string { public static final int permission_denied = 10; public static final int permission_hidden = 11; public static final int permission_ask = 12; - public static final int COUNT = 13; + public static final int notification_channel_group_name = 13; + public static final int notification_show_management_text = 14; + public static final int COUNT = 15; } } diff --git a/module/src/main/java/rikka/sui/manager/res/Strings.java b/module/src/main/java/rikka/sui/resource/Strings.java similarity index 72% rename from module/src/main/java/rikka/sui/manager/res/Strings.java rename to module/src/main/java/rikka/sui/resource/Strings.java index ecc13b8..583e16b 100644 --- a/module/src/main/java/rikka/sui/manager/res/Strings.java +++ b/module/src/main/java/rikka/sui/resource/Strings.java @@ -1,22 +1,24 @@ -package rikka.sui.manager.res; +package rikka.sui.resource; import java.util.HashMap; import java.util.Locale; -import static rikka.sui.manager.res.Res.string.COUNT; -import static rikka.sui.manager.res.Res.string.brackets_format; -import static rikka.sui.manager.res.Res.string.close; -import static rikka.sui.manager.res.Res.string.grant_dialog_button_allow_always; -import static rikka.sui.manager.res.Res.string.grant_dialog_button_allow_one_time; -import static rikka.sui.manager.res.Res.string.grant_dialog_button_deny; -import static rikka.sui.manager.res.Res.string.grant_dialog_button_deny_and_dont_ask_again; -import static rikka.sui.manager.res.Res.string.management_title; -import static rikka.sui.manager.res.Res.string.permission_allowed; -import static rikka.sui.manager.res.Res.string.permission_ask; -import static rikka.sui.manager.res.Res.string.permission_denied; -import static rikka.sui.manager.res.Res.string.permission_description; -import static rikka.sui.manager.res.Res.string.permission_hidden; -import static rikka.sui.manager.res.Res.string.permission_warning_template; +import static rikka.sui.resource.Res.string.COUNT; +import static rikka.sui.resource.Res.string.brackets_format; +import static rikka.sui.resource.Res.string.close; +import static rikka.sui.resource.Res.string.grant_dialog_button_allow_always; +import static rikka.sui.resource.Res.string.grant_dialog_button_allow_one_time; +import static rikka.sui.resource.Res.string.grant_dialog_button_deny; +import static rikka.sui.resource.Res.string.grant_dialog_button_deny_and_dont_ask_again; +import static rikka.sui.resource.Res.string.management_title; +import static rikka.sui.resource.Res.string.notification_channel_group_name; +import static rikka.sui.resource.Res.string.notification_show_management_text; +import static rikka.sui.resource.Res.string.permission_allowed; +import static rikka.sui.resource.Res.string.permission_ask; +import static rikka.sui.resource.Res.string.permission_denied; +import static rikka.sui.resource.Res.string.permission_description; +import static rikka.sui.resource.Res.string.permission_hidden; +import static rikka.sui.resource.Res.string.permission_warning_template; public class Strings { @@ -41,6 +43,8 @@ public class Strings { array[permission_denied] = "拒绝"; array[permission_hidden] = "隐藏"; array[permission_ask] = "询问"; + array[notification_channel_group_name] = "显示管理界面"; + array[notification_show_management_text] = "点按以显示超级用户管理界面"; Strings.STRINGS.put("zh-CN", array); array = new String[COUNT]; @@ -57,6 +61,8 @@ public class Strings { array[permission_denied] = "拒絕"; array[permission_hidden] = "隱藏"; array[permission_ask] = "詢問"; + array[notification_channel_group_name] = "顯示管理介面"; + array[notification_show_management_text] = "輕觸以顯示超級使用者管理介面"; Strings.STRINGS.put("zh", array); array = new String[COUNT]; @@ -73,6 +79,8 @@ public class Strings { array[permission_denied] = "Denied"; array[permission_hidden] = "Hidden"; array[permission_ask] = "Ask"; + array[notification_channel_group_name] = "Show management"; + array[notification_show_management_text] = "Tap to show superuser management"; Strings.STRINGS.put("en", array); } diff --git a/module/src/main/java/rikka/sui/manager/res/TextUtilsCompat.java b/module/src/main/java/rikka/sui/resource/TextUtilsCompat.java similarity index 99% rename from module/src/main/java/rikka/sui/manager/res/TextUtilsCompat.java rename to module/src/main/java/rikka/sui/resource/TextUtilsCompat.java index b819ba6..7451895 100644 --- a/module/src/main/java/rikka/sui/manager/res/TextUtilsCompat.java +++ b/module/src/main/java/rikka/sui/resource/TextUtilsCompat.java @@ -15,7 +15,7 @@ */ -package rikka.sui.manager.res; +package rikka.sui.resource; import android.text.Html; import android.text.TextPaint; diff --git a/module/src/main/java/rikka/sui/manager/res/Utils.java b/module/src/main/java/rikka/sui/resource/Utils.java similarity index 98% rename from module/src/main/java/rikka/sui/manager/res/Utils.java rename to module/src/main/java/rikka/sui/resource/Utils.java index aa779cc..60278de 100644 --- a/module/src/main/java/rikka/sui/manager/res/Utils.java +++ b/module/src/main/java/rikka/sui/resource/Utils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package rikka.sui.manager.res; +package rikka.sui.resource; import android.content.Context; import android.content.pm.ApplicationInfo; diff --git a/module/src/main/java/rikka/sui/manager/res/Xml.java b/module/src/main/java/rikka/sui/resource/Xml.java similarity index 97% rename from module/src/main/java/rikka/sui/manager/res/Xml.java rename to module/src/main/java/rikka/sui/resource/Xml.java index 93cec89..6cf9cf2 100644 --- a/module/src/main/java/rikka/sui/manager/res/Xml.java +++ b/module/src/main/java/rikka/sui/resource/Xml.java @@ -1,4 +1,4 @@ -package rikka.sui.manager.res; +package rikka.sui.resource; import android.annotation.SuppressLint; import android.util.Log; diff --git a/module/src/main/java/rikka/sui/settings/SettingsConstants.java b/module/src/main/java/rikka/sui/settings/SettingsConstants.java new file mode 100644 index 0000000..5d78d4f --- /dev/null +++ b/module/src/main/java/rikka/sui/settings/SettingsConstants.java @@ -0,0 +1,8 @@ +package rikka.sui.settings; + +import rikka.sui.util.Logger; + +public class SettingsConstants { + + public static final Logger LOGGER = new Logger("SuiSettings"); +} diff --git a/module/src/main/java/rikka/sui/settings/SettingsProcess.java b/module/src/main/java/rikka/sui/settings/SettingsProcess.java new file mode 100644 index 0000000..799fdc5 --- /dev/null +++ b/module/src/main/java/rikka/sui/settings/SettingsProcess.java @@ -0,0 +1,225 @@ +package rikka.sui.settings; + +import android.app.Activity; +import android.app.ActivityThread; +import android.app.Application; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.graphics.drawable.VectorDrawable; +import android.os.Build; +import android.os.Bundle; +import android.provider.Settings; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import rikka.sui.ktx.DrawableKt; +import rikka.sui.ktx.HandlerKt; +import rikka.sui.resource.Res; +import rikka.sui.resource.Strings; +import rikka.sui.resource.Xml; +import rikka.sui.util.BridgeServiceClient; + +import static rikka.sui.settings.SettingsConstants.LOGGER; + +public class SettingsProcess { + + private static final String SHOW_MANAGEMENT_ACTION = "rikka.sui.SHOW_MANAGEMENT"; + private static final String CHANNEL_SHOW_MANAGEMENT_ID = "rikka.sui:show_management"; + private static final String CHANNEL_GROUP_ID = "rikka.sui"; + private static final int NOTIFICATION_ID = ('_' << 24) | ('S' << 16) | ('U' << 8) | 'I'; + private static final String NOTIFICATION_TAG = "rikka.sui"; + + private static final BroadcastReceiver SHOW_MANAGEMENT_RECEIVER = new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + LOGGER.i("showManagement"); + BridgeServiceClient.showManagement(); + } + }; + + private static void cancelNotification(Context context) { + LOGGER.i("cancelNotification"); + + NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + nm.cancel("rikka.sui", NOTIFICATION_ID); + } + + private static void showNotification(Context context) { + LOGGER.i("showNotification"); + + NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannelGroup group = new NotificationChannelGroup(CHANNEL_GROUP_ID, "Sui"); + nm.createNotificationChannelGroup(group); + + NotificationChannel channel = new NotificationChannel(CHANNEL_SHOW_MANAGEMENT_ID, Strings.get(Res.string.notification_channel_group_name), NotificationManager.IMPORTANCE_MIN); + channel.enableLights(false); + channel.enableVibration(false); + channel.setShowBadge(false); + channel.setBypassDnd(true); + channel.setGroup(CHANNEL_GROUP_ID); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + channel.setAllowBubbles(false); + } + nm.createNotificationChannel(channel); + } + + Notification.Builder builder; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + builder = new Notification.Builder(context, CHANNEL_SHOW_MANAGEMENT_ID); + } else { + builder = new Notification.Builder(context); + builder.setPriority(Notification.PRIORITY_LOW); + } + + Bitmap bitmap; + try { + Drawable drawable = VectorDrawable.createFromXml(context.getResources(), Xml.get(Res.drawable.ic_su_24)); + int size = Math.round(Resources.getSystem().getDisplayMetrics().density * 24); + bitmap = DrawableKt.toBitmap(drawable, size, size, null); + builder.setSmallIcon(Icon.createWithBitmap(bitmap)); + } catch (Throwable e) { + LOGGER.e(e, "create icon"); + builder.setSmallIcon(android.R.drawable.ic_dialog_info); + } + + Intent intent = new Intent(SHOW_MANAGEMENT_ACTION) + .setPackage(context.getPackageName()); + + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT); + + builder.setContentTitle("Sui") + .setContentText(Strings.get(Res.string.notification_show_management_text)) + .setContentIntent(pendingIntent); + + Notification notification = builder.build(); + + nm.notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification); + } + + private static void init() { + Application application = null; + try { + application = ActivityThread.currentActivityThread().getApplication(); + } catch (Exception e) { + LOGGER.w(e, "getApplication"); + } + + if (application == null) { + LOGGER.w("application is null, wait 1s"); + HandlerKt.getWorkerHandler().postDelayed(SettingsProcess::init, 1000); + return; + } + + ResolveInfo ri = application.getPackageManager().resolveActivity( + new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS).setPackage(application.getPackageName()), 0); + + if (ri == null) { + LOGGER.e("cannot find activity for action %s", Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS); + return; + } + + String developmentActivityName = ri.activityInfo.name; + LOGGER.d("activity for %s is %s", Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS, developmentActivityName); + + application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { + + @Override + public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) { + if (savedInstanceState != null) { + return; + } + + Intent intent = activity.getIntent(); + String fragment = intent.getStringExtra(":settings:show_fragment"); + + LOGGER.d("onActivityCreated: %s, action=%s, fragment=%s", + activity.getLocalClassName(), activity.getIntent().getAction(), fragment); + + if (fragment != null && fragment.contains("Development") + || activity.getComponentName().getClassName().contains(developmentActivityName)) { + WorkerHandler.get().post(() -> showNotification(activity)); + } + } + + @Override + public void onActivityStarted(@NonNull Activity activity) { + + } + + @Override + public void onActivityResumed(@NonNull Activity activity) { + + } + + @Override + public void onActivityPaused(@NonNull Activity activity) { + + } + + @Override + public void onActivityStopped(@NonNull Activity activity) { + + } + + @Override + public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) { + + } + + @Override + public void onActivityDestroyed(@NonNull Activity activity) { + if (activity.isChangingConfigurations()) return; + + Intent intent = activity.getIntent(); + String fragment = intent.getStringExtra(":settings:show_fragment"); + + LOGGER.d("onActivityDestroyed: %s, action=%s, fragment=%s", + activity.getLocalClassName(), activity.getIntent().getAction(), fragment); + + if (fragment != null && fragment.contains("Development") + || activity.getComponentName().getClassName().contains(developmentActivityName)) { + WorkerHandler.get().post(() -> cancelNotification(activity)); + } + } + }); + + LOGGER.d("registerActivityLifecycleCallbacks"); + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(SHOW_MANAGEMENT_ACTION); + + try { + application.registerReceiver(SHOW_MANAGEMENT_RECEIVER, intentFilter, + "android.permission.MANAGE_DEVICE_ADMINS", null); + LOGGER.i("registerReceiver"); + } catch (Exception e) { + LOGGER.w(e, "registerReceiver"); + } + } + + public static void main(String[] args, ByteBuffer[] buffers) { + LOGGER.d("main: %s", Arrays.toString(args)); + WorkerHandler.get().postDelayed(SettingsProcess::init, 1000); + + Xml.setBuffers(buffers); + } +} diff --git a/module/src/main/java/rikka/sui/settings/WorkerHandler.java b/module/src/main/java/rikka/sui/settings/WorkerHandler.java new file mode 100644 index 0000000..80fc32c --- /dev/null +++ b/module/src/main/java/rikka/sui/settings/WorkerHandler.java @@ -0,0 +1,20 @@ +package rikka.sui.settings; + +import android.os.Handler; +import android.os.HandlerThread; + +public class WorkerHandler { + + private static final HandlerThread HANDLER_THREAD; + private static final Handler HANDLER; + + static { + HANDLER_THREAD = new HandlerThread("SuiSettings"); + HANDLER_THREAD.start(); + HANDLER = new Handler(HANDLER_THREAD.getLooper()); + } + + public static Handler get() { + return HANDLER; + } +} diff --git a/module/src/main/java/rikka/sui/manager/BridgeServiceClient.java b/module/src/main/java/rikka/sui/util/BridgeServiceClient.java similarity index 87% rename from module/src/main/java/rikka/sui/manager/BridgeServiceClient.java rename to module/src/main/java/rikka/sui/util/BridgeServiceClient.java index 30284e3..1c1e1b1 100644 --- a/module/src/main/java/rikka/sui/manager/BridgeServiceClient.java +++ b/module/src/main/java/rikka/sui/util/BridgeServiceClient.java @@ -1,4 +1,4 @@ -package rikka.sui.manager; +package rikka.sui.util; import android.os.IBinder; import android.os.Parcel; @@ -9,10 +9,8 @@ import java.util.List; import moe.shizuku.server.IShizukuService; -import rikka.shizuku.ShizukuApiConstants; import rikka.sui.model.AppInfo; import rikka.sui.server.ServerConstants; -import rikka.sui.util.ParceledListSlice; public class BridgeServiceClient { @@ -106,4 +104,18 @@ public static List getApplications(int userId) { } return result; } + + public static void showManagement() { + Parcel data = Parcel.obtain(); + try { + data.writeInterfaceToken("moe.shizuku.server.IShizukuService"); + try { + getService().asBinder().transact(ServerConstants.BINDER_TRANSACTION_showManagement, data, null, IBinder.FLAG_ONEWAY); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } finally { + data.recycle(); + } + } } diff --git a/template/magisk_module/README.md b/template/magisk_module/README.md index dedc657..c7a1c18 100644 --- a/template/magisk_module/README.md +++ b/template/magisk_module/README.md @@ -4,6 +4,14 @@ ## Changelog +### v11.3 (2021-01-19) + +- Open management UI trough a notification, this notification will show when you are in "Developer options" + +### v11.2 (2021-01-18) + +- Management UI works more like an `Activity` rather than a window + ### v11.1 (2021-01-16) - Fix permission for multi-process applications