From 10a2ae56a66607ff824f2cda3e71038f800a4beb Mon Sep 17 00:00:00 2001 From: LoveSy Date: Fri, 6 Aug 2021 13:55:37 +0800 Subject: [PATCH] Preparation for Module api (#506) Co-authored-by: vvb2060 --- .../lspd/service/ILSPApplicationService.aidl | 8 +- .../aidl/org/lsposed/lspd/service/Module.aidl | 8 ++ .../lsposed/lspd/service/ModuleConfig.aidl | 4 + .../android/xposed/XRemotePreference.java | 129 ++++++++++++++++++ .../de/robv/android/xposed/XposedInit.java | 6 +- .../lspd/config/ApplicationServiceClient.java | 9 +- .../config/LSPApplicationServiceClient.java | 20 +-- .../lspd/hooker/LoadedApkGetCLHooker.java | 2 +- .../lsposed/lspd/service/ConfigManager.java | 93 +++++++++++-- .../lspd/service/LSPApplicationService.java | 18 ++- .../lspd/service/LSPModuleService.java | 5 +- .../lsposed/lspd/service/ServiceManager.java | 8 +- 12 files changed, 269 insertions(+), 41 deletions(-) create mode 100644 core/src/main/aidl/org/lsposed/lspd/service/Module.aidl create mode 100644 core/src/main/aidl/org/lsposed/lspd/service/ModuleConfig.aidl create mode 100644 core/src/main/java/de/robv/android/xposed/XRemotePreference.java diff --git a/core/src/main/aidl/org/lsposed/lspd/service/ILSPApplicationService.aidl b/core/src/main/aidl/org/lsposed/lspd/service/ILSPApplicationService.aidl index ccd49347..d5e3f476 100644 --- a/core/src/main/aidl/org/lsposed/lspd/service/ILSPApplicationService.aidl +++ b/core/src/main/aidl/org/lsposed/lspd/service/ILSPApplicationService.aidl @@ -1,16 +1,20 @@ package org.lsposed.lspd.service; +import org.lsposed.lspd.service.Module; + interface ILSPApplicationService { - IBinder requestModuleBinder(); + IBinder requestModuleBinder(String name); //TODO: after array copy bug fixed, use array instead of list boolean requestManagerBinder(String packageName, String path, out List binder); boolean isResourcesHookEnabled(); - Map getModulesList(String processName); + List getModulesList(String processName); String getPrefsPath(String packageName); ParcelFileDescriptor getModuleLogger(); + + Bundle requestRemotePreference(String packageName, int userId, IBinder callback); } diff --git a/core/src/main/aidl/org/lsposed/lspd/service/Module.aidl b/core/src/main/aidl/org/lsposed/lspd/service/Module.aidl new file mode 100644 index 00000000..c0af5b86 --- /dev/null +++ b/core/src/main/aidl/org/lsposed/lspd/service/Module.aidl @@ -0,0 +1,8 @@ +package org.lsposed.lspd.service; +import org.lsposed.lspd.service.ModuleConfig; + +parcelable Module { + String name; + String apk; + ModuleConfig config; +} diff --git a/core/src/main/aidl/org/lsposed/lspd/service/ModuleConfig.aidl b/core/src/main/aidl/org/lsposed/lspd/service/ModuleConfig.aidl new file mode 100644 index 00000000..31c06664 --- /dev/null +++ b/core/src/main/aidl/org/lsposed/lspd/service/ModuleConfig.aidl @@ -0,0 +1,4 @@ +package org.lsposed.lspd.service; + +parcelable ModuleConfig { +} diff --git a/core/src/main/java/de/robv/android/xposed/XRemotePreference.java b/core/src/main/java/de/robv/android/xposed/XRemotePreference.java new file mode 100644 index 00000000..08012623 --- /dev/null +++ b/core/src/main/java/de/robv/android/xposed/XRemotePreference.java @@ -0,0 +1,129 @@ +package de.robv.android.xposed; + +import static org.lsposed.lspd.config.LSPApplicationServiceClient.serviceClient; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.os.RemoteException; + +import androidx.annotation.Nullable; + +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; + +import io.github.xposed.xposedservice.IXRemotePreferenceCallback; + +@SuppressWarnings("unchecked") +public class XRemotePreference implements SharedPreferences { + + private Map mMap = new ConcurrentHashMap<>(); + + private static final Object CONTENT = new Object(); + final WeakHashMap mListeners = new WeakHashMap<>(); + + IXRemotePreferenceCallback callback = new IXRemotePreferenceCallback.Stub() { + @Override + synchronized public void onUpdate(Bundle bundle) { + if (bundle.containsKey("map")) + mMap = (ConcurrentHashMap) bundle.getSerializable("map"); + if (bundle.containsKey("diff")) { + for (var key : bundle.getStringArrayList("diff")) { + synchronized (mListeners) { + mListeners.forEach((listener, __) -> { + listener.onSharedPreferenceChanged(XRemotePreference.this, key); + }); + } + } + } + } + }; + + public XRemotePreference(String packageName) { + this(packageName, 0); + } + + public XRemotePreference(String packageName, int userId) { + try { + Bundle output = serviceClient.requestRemotePreference(packageName, userId, callback.asBinder()); + callback.onUpdate(output); + } catch (RemoteException e) { + XposedBridge.log(e); + } + } + + @Override + public Map getAll() { + return new TreeMap<>(mMap); + } + + @Nullable + @Override + public String getString(String key, @Nullable String defValue) { + var v = (String) mMap.getOrDefault(key, defValue); + if (v != null) return v; + return defValue; + } + + @Nullable + @Override + public Set getStringSet(String key, @Nullable Set defValues) { + var v = (Set) mMap.getOrDefault(key, defValues); + if (v != null) return v; + return defValues; + } + + @Override + public int getInt(String key, int defValue) { + var v = (Integer) mMap.getOrDefault(key, defValue); + if (v != null) return v; + return defValue; + } + + @Override + public long getLong(String key, long defValue) { + var v = (Long) mMap.getOrDefault(key, defValue); + if (v != null) return v; + return defValue; + } + + @Override + public float getFloat(String key, float defValue) { + var v = (Float) mMap.getOrDefault(key, defValue); + if (v != null) return v; + return defValue; + } + + @Override + public boolean getBoolean(String key, boolean defValue) { + var v = (Boolean) mMap.getOrDefault(key, defValue); + if (v != null) return v; + return defValue; + } + + @Override + public boolean contains(String key) { + return mMap.containsKey(key); + } + + @Override + public Editor edit() { + throw new UnsupportedOperationException("Read only implementation"); + } + + @Override + public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { + synchronized (mListeners) { + mListeners.put(listener, CONTENT); + } + } + + @Override + public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { + synchronized (mListeners) { + mListeners.remove(listener); + } + + } +} diff --git a/core/src/main/java/de/robv/android/xposed/XposedInit.java b/core/src/main/java/de/robv/android/xposed/XposedInit.java index db71f757..d32e7fba 100644 --- a/core/src/main/java/de/robv/android/xposed/XposedInit.java +++ b/core/src/main/java/de/robv/android/xposed/XposedInit.java @@ -221,7 +221,9 @@ public final class XposedInit { synchronized (moduleLoadLock) { var moduleList = serviceClient.getModulesList(); ArraySet newLoadedApk = new ArraySet<>(); - moduleList.forEach((name, apk) -> { + moduleList.forEach(module -> { + var apk = module.apk; + var name = module.name; if (loadedModules.contains(apk)) { newLoadedApk.add(apk); } else { @@ -386,7 +388,7 @@ public final class XposedInit { } catch (ClassNotFoundException ignored) { } - return initNativeModule(mcl, apk) && initModule(mcl, name, apk); + return initNativeModule(mcl, name) && initModule(mcl, name, apk); } public final static HashSet loadedPackagesInProcess = new HashSet<>(1); diff --git a/core/src/main/java/org/lsposed/lspd/config/ApplicationServiceClient.java b/core/src/main/java/org/lsposed/lspd/config/ApplicationServiceClient.java index f59a2f18..18114d9b 100644 --- a/core/src/main/java/org/lsposed/lspd/config/ApplicationServiceClient.java +++ b/core/src/main/java/org/lsposed/lspd/config/ApplicationServiceClient.java @@ -4,16 +4,15 @@ import android.os.IBinder; import android.os.ParcelFileDescriptor; import org.lsposed.lspd.service.ILSPApplicationService; - +import org.lsposed.lspd.service.Module; import java.util.List; -import java.util.Map; abstract public class ApplicationServiceClient implements ILSPApplicationService { public static ApplicationServiceClient serviceClient = null; @Override - abstract public IBinder requestModuleBinder(); + abstract public IBinder requestModuleBinder(String name); @Override abstract public boolean requestManagerBinder(String packageName, String path, List binder); @@ -22,9 +21,9 @@ abstract public class ApplicationServiceClient implements ILSPApplicationService abstract public boolean isResourcesHookEnabled(); @Override - abstract public Map getModulesList(String processName); + abstract public List getModulesList(String processName); - abstract public Map getModulesList(); + abstract public List getModulesList(); @Override abstract public String getPrefsPath(String packageName); diff --git a/core/src/main/java/org/lsposed/lspd/config/LSPApplicationServiceClient.java b/core/src/main/java/org/lsposed/lspd/config/LSPApplicationServiceClient.java index 149a9dbb..c8b98205 100644 --- a/core/src/main/java/org/lsposed/lspd/config/LSPApplicationServiceClient.java +++ b/core/src/main/java/org/lsposed/lspd/config/LSPApplicationServiceClient.java @@ -19,16 +19,17 @@ package org.lsposed.lspd.config; +import android.os.Bundle; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import org.lsposed.lspd.service.ILSPApplicationService; +import org.lsposed.lspd.service.Module; import org.lsposed.lspd.util.Utils; import java.util.Collections; import java.util.List; -import java.util.Map; public class LSPApplicationServiceClient extends ApplicationServiceClient { static ILSPApplicationService service = null; @@ -60,9 +61,9 @@ public class LSPApplicationServiceClient extends ApplicationServiceClient { } @Override - public IBinder requestModuleBinder() { + public IBinder requestModuleBinder(String name) { try { - return service.requestModuleBinder(); + return service.requestModuleBinder(name); } catch (RemoteException | NullPointerException ignored) { } return null; @@ -87,17 +88,15 @@ public class LSPApplicationServiceClient extends ApplicationServiceClient { } @Override - public Map getModulesList(String processName) { + public List getModulesList(String processName) { try { - //noinspection unchecked return service.getModulesList(processName); } catch (RemoteException | NullPointerException ignored) { } - return Collections.emptyMap(); + return Collections.emptyList(); } - @Override - public Map getModulesList() { + public List getModulesList() { return getModulesList(processName); } @@ -119,6 +118,11 @@ public class LSPApplicationServiceClient extends ApplicationServiceClient { return null; } + @Override + public Bundle requestRemotePreference(String packageName, int userId, IBinder callback) throws RemoteException { + return null; + } + @Override public IBinder asBinder() { return serviceBinder; diff --git a/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkGetCLHooker.java b/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkGetCLHooker.java index 53152edc..6af06168 100644 --- a/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkGetCLHooker.java +++ b/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkGetCLHooker.java @@ -86,7 +86,7 @@ public class LoadedApkGetCLHooker extends XC_MethodHook { lpparam.appInfo = loadedApk.getApplicationInfo(); lpparam.isFirstApplication = this.isFirstApplication; - IBinder moduleBinder = serviceClient.requestModuleBinder(); + IBinder moduleBinder = serviceClient.requestModuleBinder(lpparam.packageName); if (moduleBinder != null) { hookNewXSP(lpparam); } diff --git a/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java b/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java index 34144012..58543f07 100644 --- a/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java +++ b/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java @@ -42,14 +42,17 @@ import android.util.Pair; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.apache.commons.lang3.SerializationUtils; import org.lsposed.lspd.BuildConfig; import org.lsposed.lspd.models.Application; +import org.lsposed.lspd.service.Module; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.io.Serializable; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.file.Files; @@ -61,6 +64,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -184,11 +188,21 @@ public class ConfigManager { "user_id integer NOT NULL," + "PRIMARY KEY (mid, app_pkg_name, user_id)" + ");"); + private static final SQLiteStatement createConfigTable = db.compileStatement("CREATE TABLE IF NOT EXISTS config (" + + "module_pkg_name text NOT NULL," + + "user_id integer NOT NULL," + + "`group` text NOT NULL," + + "`key` text NOT NULL," + + "data blob NOT NULL," + + "PRIMARY KEY (module_pkg_name, user_id)" + + ");"); - private final Map> cachedScope = new ConcurrentHashMap<>(); + private final Map> cachedScope = new ConcurrentHashMap<>(); private final Map cachedModule = new ConcurrentHashMap<>(); + private final Map, Map>> cachedConfig = new ConcurrentHashMap<>(); + private void updateCaches(boolean sync) { synchronized (this) { requestScopeCacheTime = requestModuleCacheTime = SystemClock.elapsedRealtime(); @@ -230,13 +244,17 @@ public class ConfigManager { } } - public Map getModulesForSystemServer() { - HashMap modules = new HashMap<>(); + public List getModulesForSystemServer() { + List modules = new LinkedList<>(); try (Cursor cursor = db.query("scope INNER JOIN modules ON scope.mid = modules.mid", new String[]{"module_pkg_name", "apk_path"}, "app_pkg_name=? AND enabled=1", new String[]{"android"}, null, null, null)) { int apkPathIdx = cursor.getColumnIndex("apk_path"); int pkgNameIdx = cursor.getColumnIndex("module_pkg_name"); while (cursor.moveToNext()) { - modules.put(cursor.getString(pkgNameIdx), cursor.getString(apkPathIdx)); + var module = new Module(); + module.name = cursor.getString(pkgNameIdx); + module.apk = cursor.getString(apkPathIdx); + module.config = null; + modules.add(module); } } return modules; @@ -343,6 +361,7 @@ public class ConfigManager { private void createTables() { createModulesTable.execute(); createScopeTable.execute(); + createConfigTable.execute(); } private List getAssociatedProcesses(Application app) throws RemoteException { @@ -354,6 +373,52 @@ public class ConfigManager { return processes; } + private @NonNull + Map> fetchModuleConfig(String name, int user_id) { + var config = new ConcurrentHashMap>(); + + try (Cursor cursor = db.query("config", new String[]{"group", "key", "data"}, + "module_pkg_name = ? and user_id = ?", new String[]{name, String.valueOf(user_id)}, null, null, null)) { + if (cursor == null) { + Log.e(TAG, "db cache failed"); + return config; + } + int groupIdx = cursor.getColumnIndex("group"); + int keyIdx = cursor.getColumnIndex("key"); + int dataIdx = cursor.getColumnIndex("data"); + while (cursor.moveToNext()) { + var group = cursor.getString(groupIdx); + var key = cursor.getString(keyIdx); + var data = cursor.getBlob(dataIdx); + var object = SerializationUtils.deserialize(data); + if (object == null) continue; + config.computeIfAbsent(group, g -> new ConcurrentHashMap<>()).put(key, object); + } + } + return config; + } + + public void updateModulePrefs(String moduleName, int userId, String group, String key, Object value) { + var config = cachedConfig.computeIfAbsent(new Pair<>(moduleName, userId), module -> fetchModuleConfig(module.first, module.second)); + var prefs = config.computeIfAbsent(group, g -> new ConcurrentHashMap<>()); + if (value instanceof Serializable) { + prefs.put(key, value); + var values = new ContentValues(); + values.put("group", group); + values.put("key", key); + values.put("value", SerializationUtils.serialize((Serializable) value)); + db.updateWithOnConflict("config", values, "module_pkg_name=? and user_id=?", new String[]{moduleName, String.valueOf(userId)}, SQLiteDatabase.CONFLICT_REPLACE); + } else { + prefs.remove(key); + db.delete("config", "module_pkg_name=? and user_id=?", new String[]{moduleName, String.valueOf(userId)}); + } + } + + public ConcurrentHashMap getModulePrefs(String moduleName, int userId, String group) { + var config = cachedConfig.computeIfAbsent(new Pair<>(moduleName, userId), module -> fetchModuleConfig(module.first, module.second)); + return config.getOrDefault(group, null); + } + private synchronized void cacheModules() { // skip caching when pm is not yet available if (!packageStarted) return; @@ -440,12 +505,16 @@ public class ConfigManager { continue; } for (ProcessScope processScope : processesScope) { - cachedScope.computeIfAbsent(processScope, ignored -> new HashMap<>()).put(module_pkg, apk_path); + var module = new Module(); + module.name = module_pkg; + module.apk = apk_path; + module.config = null; + cachedScope.computeIfAbsent(processScope, ignored -> new LinkedList<>()).add(module); if (module_pkg.equals(app.packageName)) { var appId = processScope.uid % PER_USER_RANGE; for (var user : UserService.getUsers()) { cachedScope.computeIfAbsent(new ProcessScope(processScope.processName, user.id * PER_USER_RANGE + appId), - ignored -> new HashMap<>()).put(module_pkg, apk_path); + ignored -> new LinkedList<>()).add(module); } } } @@ -459,15 +528,15 @@ public class ConfigManager { } } Log.d(TAG, "cached Scope"); - cachedScope.forEach((ps, module) -> { + cachedScope.forEach((ps, modules) -> { Log.d(TAG, ps.processName + "/" + ps.uid); - module.forEach((pkg_name, apk_path) -> Log.d(TAG, "\t" + pkg_name)); + modules.forEach(module -> Log.d(TAG, "\t" + module.name)); }); } // This is called when a new process created, use the cached result - public Map getModulesForProcess(String processName, int uid) { - return isManager(uid) ? Collections.emptyMap() : cachedScope.getOrDefault(new ProcessScope(processName, uid), Collections.emptyMap()); + public List getModulesForProcess(String processName, int uid) { + return isManager(uid) ? Collections.emptyList() : cachedScope.getOrDefault(new ProcessScope(processName, uid), Collections.emptyList()); } // This is called when a new process created, use the cached result @@ -759,8 +828,8 @@ public class ConfigManager { return cachedModule.containsKey(uid % PER_USER_RANGE); } - public boolean isModule(String packageName) { - return cachedModule.containsValue(packageName); + public boolean isModule(int uid, String name) { + return name.equals(cachedModule.getOrDefault(uid % PER_USER_RANGE, null)); } private void recursivelyChown(File file, int uid, int gid) throws ErrnoException { diff --git a/core/src/main/java/org/lsposed/lspd/service/LSPApplicationService.java b/core/src/main/java/org/lsposed/lspd/service/LSPApplicationService.java index 1d286bd9..e1d5aa12 100644 --- a/core/src/main/java/org/lsposed/lspd/service/LSPApplicationService.java +++ b/core/src/main/java/org/lsposed/lspd/service/LSPApplicationService.java @@ -19,6 +19,7 @@ package org.lsposed.lspd.service; +import android.os.Bundle; import static org.lsposed.lspd.service.ServiceManager.TAG; import android.os.IBinder; @@ -71,7 +72,7 @@ public class LSPApplicationService extends ILSPApplicationService.Stub { } @Override - public Map getModulesList(String processName) throws RemoteException { + public List getModulesList(String processName) throws RemoteException { ensureRegistered(); int callingUid = getCallingUid(); if (callingUid == 1000 && processName.equals("android")) { @@ -93,15 +94,20 @@ public class LSPApplicationService extends ILSPApplicationService.Stub { } @Override - public IBinder requestModuleBinder() throws RemoteException { + public Bundle requestRemotePreference(String packageName, int userId, IBinder callback) throws RemoteException { ensureRegistered(); - if (ConfigManager.getInstance().isModule(getCallingUid())) { - ConfigManager.getInstance().ensureModulePrefsPermission(getCallingUid()); - return ServiceManager.getModuleService(); - } return null; } + @Override + public IBinder requestModuleBinder(String name) throws RemoteException { + ensureRegistered(); + if (ConfigManager.getInstance().isModule(getCallingUid(), name)) { + ConfigManager.getInstance().ensureModulePrefsPermission(getCallingUid()); + return ServiceManager.getModuleService(name); + } else return null; + } + @Override public boolean requestManagerBinder(String packageName, String path, List binder) throws RemoteException { ensureRegistered(); diff --git a/core/src/main/java/org/lsposed/lspd/service/LSPModuleService.java b/core/src/main/java/org/lsposed/lspd/service/LSPModuleService.java index dc3e19bb..eff59bbc 100644 --- a/core/src/main/java/org/lsposed/lspd/service/LSPModuleService.java +++ b/core/src/main/java/org/lsposed/lspd/service/LSPModuleService.java @@ -26,7 +26,10 @@ import io.github.xposed.xposedservice.IXposedService; public class LSPModuleService extends IXposedService.Stub { - public LSPModuleService() { + final private String name; + + public LSPModuleService(String name) { + this.name = name; } @Override diff --git a/core/src/main/java/org/lsposed/lspd/service/ServiceManager.java b/core/src/main/java/org/lsposed/lspd/service/ServiceManager.java index fef8327d..e7f2d4a0 100644 --- a/core/src/main/java/org/lsposed/lspd/service/ServiceManager.java +++ b/core/src/main/java/org/lsposed/lspd/service/ServiceManager.java @@ -35,11 +35,12 @@ import com.android.internal.os.BinderInternal; import org.lsposed.lspd.BuildConfig; +import java.util.concurrent.ConcurrentHashMap; import hidden.HiddenApiBridge; public class ServiceManager { private static LSPosedService mainService = null; - private static LSPModuleService moduleService = null; + final private static ConcurrentHashMap moduleServices = new ConcurrentHashMap<>(); private static LSPApplicationService applicationService = null; private static LSPManagerService managerService = null; private static LSPSystemServerService systemServerService = null; @@ -81,7 +82,6 @@ public class ServiceManager { Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); Looper.prepareMainLooper(); mainService = new LSPosedService(); - moduleService = new LSPModuleService(); applicationService = new LSPApplicationService(); managerService = new LSPManagerService(); systemServerService = new LSPSystemServerService(); @@ -129,8 +129,8 @@ public class ServiceManager { throw new RuntimeException("Main thread loop unexpectedly exited"); } - public static LSPModuleService getModuleService() { - return moduleService; + public static LSPModuleService getModuleService(String module) { + return moduleServices.computeIfAbsent(module, LSPModuleService::new); } public static LSPApplicationService getApplicationService() {