Implement scope interfaces

This commit is contained in:
LoveSy 2023-01-03 20:51:20 +08:00 committed by LoveSy
parent 9abf3f2b16
commit 89d255d18a
11 changed files with 222 additions and 18 deletions

View File

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

View File

@ -458,24 +458,22 @@ public class ConfigManager {
config.compute(group, (g, prefs) -> {
HashMap<String, Object> 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) {

View File

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

View File

@ -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<String, Integer> notificationIds = new HashMap<>();
private static final Map<String, Integer> 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);
}
}
}

View File

@ -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<IntentFilter> filters, String requiredPermission, int userId, Consumer<Intent> 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();
}

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM4,12c0,-4.42 3.58,-8 8,-8 1.85,0 3.55,0.63 4.9,1.69L5.69,16.9C4.63,15.55 4,13.85 4,12zM12,20c-1.85,0 -3.55,-0.63 -4.9,-1.69L18.31,7.1C19.37,8.45 20,10.15 20,12c0,4.42 -3.58,8 -8,8z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View File

@ -11,4 +11,10 @@
<string name="status_channel_name">LSPosed status</string>
<string name="lsposed_running_notification_title">LSPosed loaded</string>
<string name="lsposed_running_notification_content">Tap the notification to open manager</string>
<string name="xposed_module_request_scope_title">Scope Request</string>
<string name="xposed_module_request_scope_content">%1$s on user %2$s requests to add %3$s to its scope.</string>
<string name="scope_channel_name">Scope request</string>
<string name="scope_approve">Approve</string>
<string name="scope_deny">Deny</string>
<string name="nerver_ask_again">Never Ask Again</string>
</resources>

View File

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

View File

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