Preparation for Module api (#506)

Co-authored-by: vvb2060 <vvb2060@gmail.com>
This commit is contained in:
LoveSy 2021-08-06 13:55:37 +08:00 committed by GitHub
parent a70e2595bd
commit 10a2ae56a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 269 additions and 41 deletions

View File

@ -1,16 +1,20 @@
package org.lsposed.lspd.service; package org.lsposed.lspd.service;
import org.lsposed.lspd.service.Module;
interface ILSPApplicationService { interface ILSPApplicationService {
IBinder requestModuleBinder(); IBinder requestModuleBinder(String name);
//TODO: after array copy bug fixed, use array instead of list //TODO: after array copy bug fixed, use array instead of list
boolean requestManagerBinder(String packageName, String path, out List<IBinder> binder); boolean requestManagerBinder(String packageName, String path, out List<IBinder> binder);
boolean isResourcesHookEnabled(); boolean isResourcesHookEnabled();
Map getModulesList(String processName); List<Module> getModulesList(String processName);
String getPrefsPath(String packageName); String getPrefsPath(String packageName);
ParcelFileDescriptor getModuleLogger(); ParcelFileDescriptor getModuleLogger();
Bundle requestRemotePreference(String packageName, int userId, IBinder callback);
} }

View File

@ -0,0 +1,8 @@
package org.lsposed.lspd.service;
import org.lsposed.lspd.service.ModuleConfig;
parcelable Module {
String name;
String apk;
ModuleConfig config;
}

View File

@ -0,0 +1,4 @@
package org.lsposed.lspd.service;
parcelable ModuleConfig {
}

View File

@ -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<String, Object> mMap = new ConcurrentHashMap<>();
private static final Object CONTENT = new Object();
final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners = new WeakHashMap<>();
IXRemotePreferenceCallback callback = new IXRemotePreferenceCallback.Stub() {
@Override
synchronized public void onUpdate(Bundle bundle) {
if (bundle.containsKey("map"))
mMap = (ConcurrentHashMap<String, Object>) 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<String, ?> 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<String> getStringSet(String key, @Nullable Set<String> defValues) {
var v = (Set<String>) 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);
}
}
}

View File

@ -221,7 +221,9 @@ public final class XposedInit {
synchronized (moduleLoadLock) { synchronized (moduleLoadLock) {
var moduleList = serviceClient.getModulesList(); var moduleList = serviceClient.getModulesList();
ArraySet<String> newLoadedApk = new ArraySet<>(); ArraySet<String> newLoadedApk = new ArraySet<>();
moduleList.forEach((name, apk) -> { moduleList.forEach(module -> {
var apk = module.apk;
var name = module.name;
if (loadedModules.contains(apk)) { if (loadedModules.contains(apk)) {
newLoadedApk.add(apk); newLoadedApk.add(apk);
} else { } else {
@ -386,7 +388,7 @@ public final class XposedInit {
} catch (ClassNotFoundException ignored) { } catch (ClassNotFoundException ignored) {
} }
return initNativeModule(mcl, apk) && initModule(mcl, name, apk); return initNativeModule(mcl, name) && initModule(mcl, name, apk);
} }
public final static HashSet<String> loadedPackagesInProcess = new HashSet<>(1); public final static HashSet<String> loadedPackagesInProcess = new HashSet<>(1);

View File

@ -4,16 +4,15 @@ import android.os.IBinder;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import org.lsposed.lspd.service.ILSPApplicationService; import org.lsposed.lspd.service.ILSPApplicationService;
import org.lsposed.lspd.service.Module;
import java.util.List; import java.util.List;
import java.util.Map;
abstract public class ApplicationServiceClient implements ILSPApplicationService { abstract public class ApplicationServiceClient implements ILSPApplicationService {
public static ApplicationServiceClient serviceClient = null; public static ApplicationServiceClient serviceClient = null;
@Override @Override
abstract public IBinder requestModuleBinder(); abstract public IBinder requestModuleBinder(String name);
@Override @Override
abstract public boolean requestManagerBinder(String packageName, String path, List<IBinder> binder); abstract public boolean requestManagerBinder(String packageName, String path, List<IBinder> binder);
@ -22,9 +21,9 @@ abstract public class ApplicationServiceClient implements ILSPApplicationService
abstract public boolean isResourcesHookEnabled(); abstract public boolean isResourcesHookEnabled();
@Override @Override
abstract public Map getModulesList(String processName); abstract public List getModulesList(String processName);
abstract public Map<String, String> getModulesList(); abstract public List<Module> getModulesList();
@Override @Override
abstract public String getPrefsPath(String packageName); abstract public String getPrefsPath(String packageName);

View File

@ -19,16 +19,17 @@
package org.lsposed.lspd.config; package org.lsposed.lspd.config;
import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.os.RemoteException; import android.os.RemoteException;
import org.lsposed.lspd.service.ILSPApplicationService; import org.lsposed.lspd.service.ILSPApplicationService;
import org.lsposed.lspd.service.Module;
import org.lsposed.lspd.util.Utils; import org.lsposed.lspd.util.Utils;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
public class LSPApplicationServiceClient extends ApplicationServiceClient { public class LSPApplicationServiceClient extends ApplicationServiceClient {
static ILSPApplicationService service = null; static ILSPApplicationService service = null;
@ -60,9 +61,9 @@ public class LSPApplicationServiceClient extends ApplicationServiceClient {
} }
@Override @Override
public IBinder requestModuleBinder() { public IBinder requestModuleBinder(String name) {
try { try {
return service.requestModuleBinder(); return service.requestModuleBinder(name);
} catch (RemoteException | NullPointerException ignored) { } catch (RemoteException | NullPointerException ignored) {
} }
return null; return null;
@ -87,17 +88,15 @@ public class LSPApplicationServiceClient extends ApplicationServiceClient {
} }
@Override @Override
public Map<String, String> getModulesList(String processName) { public List<Module> getModulesList(String processName) {
try { try {
//noinspection unchecked
return service.getModulesList(processName); return service.getModulesList(processName);
} catch (RemoteException | NullPointerException ignored) { } catch (RemoteException | NullPointerException ignored) {
} }
return Collections.emptyMap(); return Collections.emptyList();
} }
@Override public List<Module> getModulesList() {
public Map<String, String> getModulesList() {
return getModulesList(processName); return getModulesList(processName);
} }
@ -119,6 +118,11 @@ public class LSPApplicationServiceClient extends ApplicationServiceClient {
return null; return null;
} }
@Override
public Bundle requestRemotePreference(String packageName, int userId, IBinder callback) throws RemoteException {
return null;
}
@Override @Override
public IBinder asBinder() { public IBinder asBinder() {
return serviceBinder; return serviceBinder;

View File

@ -86,7 +86,7 @@ public class LoadedApkGetCLHooker extends XC_MethodHook {
lpparam.appInfo = loadedApk.getApplicationInfo(); lpparam.appInfo = loadedApk.getApplicationInfo();
lpparam.isFirstApplication = this.isFirstApplication; lpparam.isFirstApplication = this.isFirstApplication;
IBinder moduleBinder = serviceClient.requestModuleBinder(); IBinder moduleBinder = serviceClient.requestModuleBinder(lpparam.packageName);
if (moduleBinder != null) { if (moduleBinder != null) {
hookNewXSP(lpparam); hookNewXSP(lpparam);
} }

View File

@ -42,14 +42,17 @@ import android.util.Pair;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.apache.commons.lang3.SerializationUtils;
import org.lsposed.lspd.BuildConfig; import org.lsposed.lspd.BuildConfig;
import org.lsposed.lspd.models.Application; import org.lsposed.lspd.models.Application;
import org.lsposed.lspd.service.Module;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Serializable;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.channels.FileLock; import java.nio.channels.FileLock;
import java.nio.file.Files; import java.nio.file.Files;
@ -61,6 +64,7 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -184,11 +188,21 @@ public class ConfigManager {
"user_id integer NOT NULL," + "user_id integer NOT NULL," +
"PRIMARY KEY (mid, app_pkg_name, user_id)" + "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<ProcessScope, Map<String, String>> cachedScope = new ConcurrentHashMap<>(); private final Map<ProcessScope, List<Module>> cachedScope = new ConcurrentHashMap<>();
private final Map<Integer, String> cachedModule = new ConcurrentHashMap<>(); private final Map<Integer, String> cachedModule = new ConcurrentHashMap<>();
private final Map<Pair<String, Integer>, Map<String, ConcurrentHashMap<String, Object>>> cachedConfig = new ConcurrentHashMap<>();
private void updateCaches(boolean sync) { private void updateCaches(boolean sync) {
synchronized (this) { synchronized (this) {
requestScopeCacheTime = requestModuleCacheTime = SystemClock.elapsedRealtime(); requestScopeCacheTime = requestModuleCacheTime = SystemClock.elapsedRealtime();
@ -230,13 +244,17 @@ public class ConfigManager {
} }
} }
public Map<String, String> getModulesForSystemServer() { public List<Module> getModulesForSystemServer() {
HashMap<String, String> modules = new HashMap<>(); List<Module> 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)) { 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 apkPathIdx = cursor.getColumnIndex("apk_path");
int pkgNameIdx = cursor.getColumnIndex("module_pkg_name"); int pkgNameIdx = cursor.getColumnIndex("module_pkg_name");
while (cursor.moveToNext()) { 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; return modules;
@ -343,6 +361,7 @@ public class ConfigManager {
private void createTables() { private void createTables() {
createModulesTable.execute(); createModulesTable.execute();
createScopeTable.execute(); createScopeTable.execute();
createConfigTable.execute();
} }
private List<ProcessScope> getAssociatedProcesses(Application app) throws RemoteException { private List<ProcessScope> getAssociatedProcesses(Application app) throws RemoteException {
@ -354,6 +373,52 @@ public class ConfigManager {
return processes; return processes;
} }
private @NonNull
Map<String, ConcurrentHashMap<String, Object>> fetchModuleConfig(String name, int user_id) {
var config = new ConcurrentHashMap<String, ConcurrentHashMap<String, Object>>();
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<String, Object> 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() { private synchronized void cacheModules() {
// skip caching when pm is not yet available // skip caching when pm is not yet available
if (!packageStarted) return; if (!packageStarted) return;
@ -440,12 +505,16 @@ public class ConfigManager {
continue; continue;
} }
for (ProcessScope processScope : processesScope) { 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)) { if (module_pkg.equals(app.packageName)) {
var appId = processScope.uid % PER_USER_RANGE; var appId = processScope.uid % PER_USER_RANGE;
for (var user : UserService.getUsers()) { for (var user : UserService.getUsers()) {
cachedScope.computeIfAbsent(new ProcessScope(processScope.processName, user.id * PER_USER_RANGE + appId), 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"); Log.d(TAG, "cached Scope");
cachedScope.forEach((ps, module) -> { cachedScope.forEach((ps, modules) -> {
Log.d(TAG, ps.processName + "/" + ps.uid); 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 // This is called when a new process created, use the cached result
public Map<String, String> getModulesForProcess(String processName, int uid) { public List<Module> getModulesForProcess(String processName, int uid) {
return isManager(uid) ? Collections.emptyMap() : cachedScope.getOrDefault(new ProcessScope(processName, uid), Collections.emptyMap()); 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 // 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); return cachedModule.containsKey(uid % PER_USER_RANGE);
} }
public boolean isModule(String packageName) { public boolean isModule(int uid, String name) {
return cachedModule.containsValue(packageName); return name.equals(cachedModule.getOrDefault(uid % PER_USER_RANGE, null));
} }
private void recursivelyChown(File file, int uid, int gid) throws ErrnoException { private void recursivelyChown(File file, int uid, int gid) throws ErrnoException {

View File

@ -19,6 +19,7 @@
package org.lsposed.lspd.service; package org.lsposed.lspd.service;
import android.os.Bundle;
import static org.lsposed.lspd.service.ServiceManager.TAG; import static org.lsposed.lspd.service.ServiceManager.TAG;
import android.os.IBinder; import android.os.IBinder;
@ -71,7 +72,7 @@ public class LSPApplicationService extends ILSPApplicationService.Stub {
} }
@Override @Override
public Map<String, String> getModulesList(String processName) throws RemoteException { public List<Module> getModulesList(String processName) throws RemoteException {
ensureRegistered(); ensureRegistered();
int callingUid = getCallingUid(); int callingUid = getCallingUid();
if (callingUid == 1000 && processName.equals("android")) { if (callingUid == 1000 && processName.equals("android")) {
@ -93,15 +94,20 @@ public class LSPApplicationService extends ILSPApplicationService.Stub {
} }
@Override @Override
public IBinder requestModuleBinder() throws RemoteException { public Bundle requestRemotePreference(String packageName, int userId, IBinder callback) throws RemoteException {
ensureRegistered(); ensureRegistered();
if (ConfigManager.getInstance().isModule(getCallingUid())) {
ConfigManager.getInstance().ensureModulePrefsPermission(getCallingUid());
return ServiceManager.getModuleService();
}
return null; 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 @Override
public boolean requestManagerBinder(String packageName, String path, List<IBinder> binder) throws RemoteException { public boolean requestManagerBinder(String packageName, String path, List<IBinder> binder) throws RemoteException {
ensureRegistered(); ensureRegistered();

View File

@ -26,7 +26,10 @@ import io.github.xposed.xposedservice.IXposedService;
public class LSPModuleService extends IXposedService.Stub { public class LSPModuleService extends IXposedService.Stub {
public LSPModuleService() { final private String name;
public LSPModuleService(String name) {
this.name = name;
} }
@Override @Override

View File

@ -35,11 +35,12 @@ import com.android.internal.os.BinderInternal;
import org.lsposed.lspd.BuildConfig; import org.lsposed.lspd.BuildConfig;
import java.util.concurrent.ConcurrentHashMap;
import hidden.HiddenApiBridge; import hidden.HiddenApiBridge;
public class ServiceManager { public class ServiceManager {
private static LSPosedService mainService = null; private static LSPosedService mainService = null;
private static LSPModuleService moduleService = null; final private static ConcurrentHashMap<String, LSPModuleService> moduleServices = new ConcurrentHashMap<>();
private static LSPApplicationService applicationService = null; private static LSPApplicationService applicationService = null;
private static LSPManagerService managerService = null; private static LSPManagerService managerService = null;
private static LSPSystemServerService systemServerService = null; private static LSPSystemServerService systemServerService = null;
@ -81,7 +82,6 @@ public class ServiceManager {
Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
Looper.prepareMainLooper(); Looper.prepareMainLooper();
mainService = new LSPosedService(); mainService = new LSPosedService();
moduleService = new LSPModuleService();
applicationService = new LSPApplicationService(); applicationService = new LSPApplicationService();
managerService = new LSPManagerService(); managerService = new LSPManagerService();
systemServerService = new LSPSystemServerService(); systemServerService = new LSPSystemServerService();
@ -129,8 +129,8 @@ public class ServiceManager {
throw new RuntimeException("Main thread loop unexpectedly exited"); throw new RuntimeException("Main thread loop unexpectedly exited");
} }
public static LSPModuleService getModuleService() { public static LSPModuleService getModuleService(String module) {
return moduleService; return moduleServices.computeIfAbsent(module, LSPModuleService::new);
} }
public static LSPApplicationService getApplicationService() { public static LSPApplicationService getApplicationService() {