From 89d255d18aef57e70501c8d76c3726c949acc592 Mon Sep 17 00:00:00 2001 From: LoveSy Date: Tue, 3 Jan 2023 20:51:20 +0800 Subject: [PATCH] Implement scope interfaces --- .../org/lsposed/lspd/impl/LSPosedContext.java | 2 - .../lsposed/lspd/service/ConfigManager.java | 37 +++++++- .../lspd/service/LSPModuleService.java | 21 ++++- .../lspd/service/LSPNotificationManager.java | 90 +++++++++++++++++-- .../lsposed/lspd/service/LSPosedService.java | 56 ++++++++++++ .../res/drawable/ic_baseline_block_24.xml | 5 ++ .../res/drawable/ic_baseline_check_24.xml | 5 ++ .../res/drawable/ic_baseline_close_24.xml | 5 ++ daemon/src/main/res/values/strings.xml | 6 ++ .../service/IXposedScopeCallback.aidl | 9 ++ .../libxposed/service/IXposedService.aidl | 4 +- 11 files changed, 222 insertions(+), 18 deletions(-) create mode 100644 daemon/src/main/res/drawable/ic_baseline_block_24.xml create mode 100644 daemon/src/main/res/drawable/ic_baseline_check_24.xml create mode 100644 daemon/src/main/res/drawable/ic_baseline_close_24.xml create mode 100644 libxposed/service/src/main/aidl/io/github/libxposed/service/IXposedScopeCallback.aidl diff --git a/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java b/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java index 432c7283..4d4cf6ee 100644 --- a/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java +++ b/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java @@ -45,7 +45,6 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; -import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Method; @@ -63,7 +62,6 @@ import io.github.libxposed.XposedUtils; public class LSPosedContext extends XposedContext { private static final String TAG = "LSPosedContext"; - private static final int PER_USER_RANGE = 100000; public static boolean isSystemServer; public static String appDir; public static String processName; diff --git a/daemon/src/main/java/org/lsposed/lspd/service/ConfigManager.java b/daemon/src/main/java/org/lsposed/lspd/service/ConfigManager.java index 11b68b98..3bdb0d68 100644 --- a/daemon/src/main/java/org/lsposed/lspd/service/ConfigManager.java +++ b/daemon/src/main/java/org/lsposed/lspd/service/ConfigManager.java @@ -458,24 +458,22 @@ public class ConfigManager { config.compute(group, (g, prefs) -> { HashMap newPrefs = prefs == null ? new HashMap<>() : new HashMap<>(prefs); executeInTransaction(() -> { - var contents = new ContentValues(); for (var entry : values.entrySet()) { var key = entry.getKey(); var value = entry.getValue(); if (value instanceof Serializable) { newPrefs.put(key, value); + var contents = new ContentValues(); contents.put("`group`", group); contents.put("`key`", key); contents.put("data", SerializationUtils.serialize((Serializable) value)); contents.put("module_pkg_name", moduleName); contents.put("user_id", String.valueOf(userId)); + db.insertWithOnConflict("configs", null, contents, SQLiteDatabase.CONFLICT_REPLACE); } else { newPrefs.remove(key); db.delete("configs", "module_pkg_name=? and user_id=? and `group`=? and `key`=?", new String[]{moduleName, String.valueOf(userId), group, key}); } - if (contents.size() > 0) { - db.insertWithOnConflict("configs", null, contents, SQLiteDatabase.CONFLICT_REPLACE); - } } var bundle = new Bundle(); bundle.putSerializable("config", (Serializable) config); @@ -841,6 +839,37 @@ public class ConfigManager { return true; } + public boolean setModuleScope(String packageName, String scopePackageName, int userId) { + if (scopePackageName == null) return false; + int mid = getModuleId(packageName); + if (mid == -1) return false; + if (scopePackageName.equals("android") && userId != 0) return false; + executeInTransaction(() -> { + ContentValues values = new ContentValues(); + values.put("mid", mid); + values.put("app_pkg_name", scopePackageName); + values.put("user_id", userId); + db.insertWithOnConflict("scope", null, values, SQLiteDatabase.CONFLICT_IGNORE); + }); + // Called by xposed service, should be async + updateCaches(false); + return true; + } + + public boolean removeModuleScope(String packageName, String scopePackageName, int userId) { + if (scopePackageName == null) return false; + int mid = getModuleId(packageName); + if (mid == -1) return false; + if (scopePackageName.equals("android") && userId != 0) return false; + executeInTransaction(() -> { + db.delete("scope", "mid = ? AND app_pkg_name = ? AND user_id = ?", new String[]{String.valueOf(mid), scopePackageName, String.valueOf(userId)}); + }); + // Called by xposed service, should be async + updateCaches(false); + return true; + } + + public String[] enabledModules() { try (Cursor cursor = db.query("modules", new String[]{"module_pkg_name"}, "enabled = 1", null, null, null, null)) { if (cursor == null) { diff --git a/daemon/src/main/java/org/lsposed/lspd/service/LSPModuleService.java b/daemon/src/main/java/org/lsposed/lspd/service/LSPModuleService.java index eb2210c6..58ba1246 100644 --- a/daemon/src/main/java/org/lsposed/lspd/service/LSPModuleService.java +++ b/daemon/src/main/java/org/lsposed/lspd/service/LSPModuleService.java @@ -42,6 +42,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import io.github.libxposed.service.IXposedScopeCallback; import io.github.libxposed.service.IXposedService; public class LSPModuleService extends IXposedService.Stub { @@ -147,9 +148,23 @@ public class LSPModuleService extends IXposedService.Stub { } @Override - public void requestScope(String packageName) throws RemoteException { - ensureModule(); - // TODO + public void requestScope(String packageName, IXposedScopeCallback callback) throws RemoteException { + var userId = ensureModule(); + LSPNotificationManager.requestModuleScope(loadedModule.packageName, userId, packageName, callback); + callback.onScopeRequestPrompted(packageName); + } + + @Override + public String removeScope(String packageName) throws RemoteException { + var userId = ensureModule(); + try { + if (!ConfigManager.getInstance().removeModuleScope(loadedModule.packageName, packageName, userId)) { + return "Invalid request"; + } + return null; + } catch (Throwable e) { + return e.getMessage(); + } } @Override diff --git a/daemon/src/main/java/org/lsposed/lspd/service/LSPNotificationManager.java b/daemon/src/main/java/org/lsposed/lspd/service/LSPNotificationManager.java index f94c97b6..390ca2b0 100644 --- a/daemon/src/main/java/org/lsposed/lspd/service/LSPNotificationManager.java +++ b/daemon/src/main/java/org/lsposed/lspd/service/LSPNotificationManager.java @@ -19,6 +19,7 @@ import android.graphics.drawable.Icon; import android.graphics.drawable.LayerDrawable; import android.net.Uri; import android.os.Build; +import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; @@ -27,20 +28,25 @@ import org.lsposed.daemon.R; import org.lsposed.lspd.util.FakeContext; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import io.github.libxposed.service.IXposedScopeCallback; public class LSPNotificationManager { private static final String UPDATED_CHANNEL_ID = "lsposed_module_updated"; + private static final String SCOPE_CHANNEL_ID = "lsposed_module_scope"; private static final String STATUS_CHANNEL_ID = "lsposed_status"; private static final int STATUS_NOTIFICATION_ID = 2000; private static final String opPkg = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ? "android" : "com.android.settings"; - private static final HashMap notificationIds = new HashMap<>(); + private static final Map notificationIds = new ConcurrentHashMap<>(); private static int previousNotificationId = STATUS_NOTIFICATION_ID; static final String openManagerAction = UUID.randomUUID().toString(); + static final String moduleScope = UUID.randomUUID().toString(); private static INotificationManager notificationManager = null; private static IBinder binder = null; @@ -121,6 +127,16 @@ public class LSPNotificationManager { list.add(status); } + var scope = new NotificationChannel(SCOPE_CHANNEL_ID, + context.getString(R.string.scope_channel_name), + NotificationManager.IMPORTANCE_HIGH); + scope.setShowBadge(false); + if (hasNotificationChannelForSystem(nm, SCOPE_CHANNEL_ID)) { + nm.updateNotificationChannelForPackage("android", 1000, scope); + } else { + list.add(scope); + } + nm.createNotificationChannelsForPackage("android", 1000, new ParceledListSlice<>(list)); } @@ -169,12 +185,22 @@ public class LSPNotificationManager { return PendingIntent.getBroadcast(new FakeContext(), 3, intent, flags); } - private static String getNotificationIdKey(String modulePackageName, int moduleUserId) { - return modulePackageName + ":" + moduleUserId; + private static PendingIntent getModuleScopeIntent(String modulePackageName, int moduleUserId, String scopePackageName, String action, IXposedScopeCallback callback) { + var intent = new Intent(moduleScope); + intent.setData(new Uri.Builder().scheme("module").encodedAuthority(modulePackageName + ":" + moduleUserId).encodedPath(scopePackageName).appendQueryParameter("action", action).build()); + var extras = new Bundle(); + extras.putBinder("callback", callback.asBinder()); + intent.putExtras(extras); + int flags = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE; + return PendingIntent.getBroadcast(new FakeContext(), 4, intent, flags); } - private static int pushAndGetNotificationId(String modulePackageName, int moduleUserId) { - var idKey = getNotificationIdKey(modulePackageName, moduleUserId); + private static String getNotificationIdKey(String channel, String modulePackageName, int moduleUserId) { + return channel + "/" + modulePackageName + ":" + moduleUserId; + } + + private static int pushAndGetNotificationId(String channel, String modulePackageName, int moduleUserId) { + var idKey = getNotificationIdKey(channel, modulePackageName, moduleUserId); // previousNotificationId start with 2001 // https://android.googlesource.com/platform/frameworks/base/+/master/proto/src/system_messages.proto // https://android.googlesource.com/platform/system/core/+/master/libcutils/include/private/android_filesystem_config.h @@ -218,7 +244,7 @@ public class LSPNotificationManager { var nm = getNotificationManager(); createNotificationChannel(nm); nm.enqueueNotificationWithTag("android", opPkg, modulePackageName, - pushAndGetNotificationId(modulePackageName, moduleUserId), + pushAndGetNotificationId(UPDATED_CHANNEL_ID, modulePackageName, moduleUserId), notification, 0); } catch (Throwable e) { Log.e(TAG, "notify module updated", e); @@ -227,7 +253,7 @@ public class LSPNotificationManager { static void cancelUpdatedNotification(String modulePackageName, int moduleUserId) { try { - var idKey = getNotificationIdKey(modulePackageName, moduleUserId); + var idKey = getNotificationIdKey(UPDATED_CHANNEL_ID, modulePackageName, moduleUserId); var idValue = notificationIds.get(idKey); if (idValue == null) return; var nm = getNotificationManager(); @@ -241,4 +267,52 @@ public class LSPNotificationManager { Log.e(TAG, "cancel notification", e); } } + + static void requestModuleScope(String modulePackageName, int moduleUserId, String scopePackageName, IXposedScopeCallback callback) { + try { + var context = new FakeContext(); + var userInfo = UserService.getUserInfo(moduleUserId); + String userName = userInfo != null ? userInfo.name : String.valueOf(moduleUserId); + String title = context.getString(R.string.xposed_module_request_scope_title); + String content = context.getString(R.string.xposed_module_request_scope_content, modulePackageName, userName, scopePackageName); + + var style = new Notification.BigTextStyle(); + style.bigText(content); + + var notification = new Notification.Builder(context, SCOPE_CHANNEL_ID) + .setContentTitle(title) + .setContentText(content) + .setSmallIcon(getNotificationIcon()) + .setVisibility(Notification.VISIBILITY_SECRET) + .setColor(0xFFF48FB1) + .setAutoCancel(true) + .setTimeoutAfter(1000 * 60 * 60) + .setStyle(style) + .setDeleteIntent(getModuleScopeIntent(modulePackageName, moduleUserId, scopePackageName, "delete", callback)) + .setActions(new Notification.Action.Builder( + Icon.createWithResource(context, R.drawable.ic_baseline_check_24), + context.getString(R.string.scope_approve), + getModuleScopeIntent(modulePackageName, moduleUserId, scopePackageName, "approve", callback)) + .build(), + new Notification.Action.Builder( + Icon.createWithResource(context, R.drawable.ic_baseline_close_24), + context.getString(R.string.scope_deny), + getModuleScopeIntent(modulePackageName, moduleUserId, scopePackageName, "deny", callback)) + .build(), + new Notification.Action.Builder( + Icon.createWithResource(context, R.drawable.ic_baseline_block_24), + context.getString(R.string.nerver_ask_again), + getModuleScopeIntent(modulePackageName, moduleUserId, scopePackageName, "block", callback)) + .build() + ).build(); + notification.extras.putString("android.substName", "LSPosed"); + var nm = getNotificationManager(); + createNotificationChannel(nm); + nm.enqueueNotificationWithTag("android", opPkg, modulePackageName, + pushAndGetNotificationId(SCOPE_CHANNEL_ID, modulePackageName, moduleUserId), + notification, 0); + } catch (RemoteException e) { + Log.e(TAG, "request module scope", e); + } + } } diff --git a/daemon/src/main/java/org/lsposed/lspd/service/LSPosedService.java b/daemon/src/main/java/org/lsposed/lspd/service/LSPosedService.java index 50716cdd..2d87aa9e 100644 --- a/daemon/src/main/java/org/lsposed/lspd/service/LSPosedService.java +++ b/daemon/src/main/java/org/lsposed/lspd/service/LSPosedService.java @@ -26,6 +26,7 @@ import static org.lsposed.lspd.service.ServiceManager.getExecutorService; import android.app.IApplicationThread; import android.app.IUidObserver; +import android.content.ContentValues; import android.content.IIntentReceiver; import android.content.Intent; import android.content.IntentFilter; @@ -48,6 +49,7 @@ import java.util.List; import java.util.function.Consumer; import hidden.HiddenApiBridge; +import io.github.libxposed.service.IXposedScopeCallback; public class LSPosedService extends ILSPosedService.Stub { private static final int AID_NOBODY = 9999; @@ -225,6 +227,50 @@ public class LSPosedService extends ILSPosedService.Stub { LSPManagerService.openManager(intent.getData()); } + private void dispatchModuleScope(Intent intent) { + Log.d(TAG, "dispatchModuleScope: " + intent); + var data = intent.getData(); + var extras = intent.getExtras(); + if (extras == null || data == null) return; + var callback = extras.getBinder("callback"); + var s = data.getEncodedAuthority().split(":", 2); + if (s.length != 2) return; + var packageName = s[0]; + int userId; + try { + userId = Integer.parseInt(s[1]); + } catch (NumberFormatException e) { + return; + } + var scopePackageName = data.getPath(); + var action = data.getQueryParameter("action"); + if (scopePackageName == null || action == null) return; + + try { + switch (action) { + case "allow": + ConfigManager.getInstance().setModuleScope(packageName, scopePackageName, userId); + IXposedScopeCallback.Stub.asInterface(callback).onScopeRequestApproved(scopePackageName); + break; + case "deny": + IXposedScopeCallback.Stub.asInterface(callback).onScopeRequestDenied(scopePackageName); + break; + case "delete": + IXposedScopeCallback.Stub.asInterface(callback).onScopeRequestTimeout(scopePackageName); + break; + case "block": + // TODO + break; + } + } catch (Throwable e) { + try { + IXposedScopeCallback.Stub.asInterface(callback).onScopeRequestFailed(scopePackageName, e.getMessage()); + } catch (Throwable ignored) { + // callback died + } + } + } + private void registerReceiver(List filters, String requiredPermission, int userId, Consumer task) { var receiver = new IIntentReceiver.Stub() { @Override @@ -317,6 +363,15 @@ public class LSPosedService extends ILSPosedService.Stub { Log.d(TAG, "registered open manager receiver"); } + private void registerModuleScopeReceiver() { + var intentFilter = new IntentFilter(LSPNotificationManager.moduleScope); + var moduleFilter = new IntentFilter(intentFilter); + moduleFilter.addDataScheme("module"); + + registerReceiver(List.of(intentFilter, moduleFilter), "android.permission.BRICK", 0, this::dispatchModuleScope); + Log.d(TAG, "registered module scope receiver"); + } + private void registerUidObserver() { try { ActivityManagerService.registerUidObserver(new IUidObserver.Stub() { @@ -356,6 +411,7 @@ public class LSPosedService extends ILSPosedService.Stub { registerBootCompleteReceiver(); registerUserChangeReceiver(); registerOpenManagerReceiver(); + registerModuleScopeReceiver(); registerUidObserver(); } diff --git a/daemon/src/main/res/drawable/ic_baseline_block_24.xml b/daemon/src/main/res/drawable/ic_baseline_block_24.xml new file mode 100644 index 00000000..1e478d2a --- /dev/null +++ b/daemon/src/main/res/drawable/ic_baseline_block_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/daemon/src/main/res/drawable/ic_baseline_check_24.xml b/daemon/src/main/res/drawable/ic_baseline_check_24.xml new file mode 100644 index 00000000..cf143d4d --- /dev/null +++ b/daemon/src/main/res/drawable/ic_baseline_check_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/daemon/src/main/res/drawable/ic_baseline_close_24.xml b/daemon/src/main/res/drawable/ic_baseline_close_24.xml new file mode 100644 index 00000000..844b6b62 --- /dev/null +++ b/daemon/src/main/res/drawable/ic_baseline_close_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/daemon/src/main/res/values/strings.xml b/daemon/src/main/res/values/strings.xml index 31d6dbdf..59caef47 100644 --- a/daemon/src/main/res/values/strings.xml +++ b/daemon/src/main/res/values/strings.xml @@ -11,4 +11,10 @@ LSPosed status LSPosed loaded Tap the notification to open manager + Scope Request + %1$s on user %2$s requests to add %3$s to its scope. + Scope request + Approve + Deny + Never Ask Again diff --git a/libxposed/service/src/main/aidl/io/github/libxposed/service/IXposedScopeCallback.aidl b/libxposed/service/src/main/aidl/io/github/libxposed/service/IXposedScopeCallback.aidl new file mode 100644 index 00000000..6dcf980a --- /dev/null +++ b/libxposed/service/src/main/aidl/io/github/libxposed/service/IXposedScopeCallback.aidl @@ -0,0 +1,9 @@ +package io.github.libxposed.service; + +interface IXposedScopeCallback { + oneway void onScopeRequestPrompted(String packageName) = 1; + oneway void onScopeRequestApproved(String packageName) = 2; + oneway void onScopeRequestDenied(String packageName) = 3; + oneway void onScopeRequestTimeout(String packageName) = 4; + oneway void onScopeRequestFailed(String packageName, String message) = 5; +} diff --git a/libxposed/service/src/main/aidl/io/github/libxposed/service/IXposedService.aidl b/libxposed/service/src/main/aidl/io/github/libxposed/service/IXposedService.aidl index eeac9d25..02b25f11 100644 --- a/libxposed/service/src/main/aidl/io/github/libxposed/service/IXposedService.aidl +++ b/libxposed/service/src/main/aidl/io/github/libxposed/service/IXposedService.aidl @@ -1,4 +1,5 @@ package io.github.libxposed.service; +import io.github.libxposed.service.IXposedScopeCallback; interface IXposedService { const int API = 100; @@ -13,7 +14,8 @@ interface IXposedService { // scope utilities List getScope() = 10; - oneway void requestScope(String packageName) = 11; + oneway void requestScope(String packageName, IXposedScopeCallback callback) = 11; + String removeScope(String packageName) = 12; // remote preference utilities Bundle requestRemotePreferences(String group) = 20;