[core] Add more manager service interfaces

This commit is contained in:
LoveSy 2021-02-17 01:51:27 +08:00 committed by tehcneko
parent 374c8cac11
commit e7e32351ea
8 changed files with 237 additions and 86 deletions

View File

@ -11,7 +11,7 @@ interface ILSPApplicationService {
boolean isResourcesHookEnabled() = 5;
List<String> getModulesList() = 6;
String[] getModulesList() = 6;
String getPrefsPath(String packageName) = 7;

View File

@ -353,7 +353,7 @@ public final class XposedInit {
topClassLoader = parent;
}
List<String> moduleList = serviceClient.getModulesList();
String[] moduleList = serviceClient.getModulesList();
ArraySet<String> newLoadedApk = new ArraySet<>();
for (String apk : moduleList)
if (loadedModules.contains(apk)) {

View File

@ -23,10 +23,15 @@ public class LSPApplicationServiceClient implements ILSPApplicationService {
if (serviceClient == null && binder != null && serviceBinder == null && service == null) {
serviceBinder = binder;
try {
serviceBinder.linkToDeath(() -> {
serviceBinder = null;
service = null;
}, 0);
serviceBinder.linkToDeath(
new IBinder.DeathRecipient() {
@Override
public void binderDied() {
serviceBinder.unlinkToDeath(this, 0);
serviceBinder = null;
service = null;
}
}, 0);
} catch (RemoteException e) {
Utils.logE("link to death error: ", e);
}
@ -84,12 +89,12 @@ public class LSPApplicationServiceClient implements ILSPApplicationService {
}
@Override
public List<String> getModulesList() {
public String[] getModulesList() {
try {
return service.getModulesList();
} catch (RemoteException | NullPointerException ignored) {
}
return Collections.emptyList();
return new String[0];
}
@Override

View File

@ -23,30 +23,12 @@ package io.github.lsposed.lspd.hooker;
import android.os.IBinder;
import de.robv.android.xposed.XposedHelpers;
import io.github.lsposed.lspd.core.EdxpImpl;
import io.github.lsposed.lspd.core.Main;
import io.github.lsposed.lspd.util.Utils;
public class XposedInstallerHooker {
public static void hookXposedInstaller(final ClassLoader classLoader, IBinder binder) {
final String variant;
switch (Main.getEdxpVariant()) {
case EdxpImpl.YAHFA:
variant = "YAHFA";
break;
case EdxpImpl.SANDHOOK:
variant = "SandHook";
break;
case EdxpImpl.NONE:
default:
variant = "Unknown";
break;
}
Utils.logI("Found LSPosed Manager, hooking it");
// LSPosed Manager R
try {
Class<?> serviceClass = XposedHelpers.findClass("io.github.lsposed.manager.receivers.LSPosedManagerServiceClient", classLoader);
XposedHelpers.setStaticObjectField(serviceClass, "binder", binder);

View File

@ -3,9 +3,9 @@ package io.github.lsposed.lspd.service;
import android.content.ContentValues;
import android.content.pm.PackageInfo;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.database.sqlite.SQLiteStatement;
import android.database.sqlite.SQLiteDatabase;
import android.os.FileObserver;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@ -21,8 +21,6 @@ import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import static io.github.lsposed.lspd.service.ServiceManager.TAG;
@ -130,10 +128,12 @@ public class ConfigManager {
updateManager();
}
private final SQLiteStatement createEnabledModulesTable = db.compileStatement("CREATE TABLE IF NOT EXISTS enabled_modules (" +
private final SQLiteStatement createModulesTable = db.compileStatement("CREATE TABLE IF NOT EXISTS modules (" +
"mid integer PRIMARY KEY AUTOINCREMENT," +
"package_name text NOT NULL UNIQUE," +
"apk_path text NOT NULL" +
"apk_path text NOT NULL, " +
"enabled BOOLEAN DEFAULT 0 " +
"CHECK (enabled IN (0, 1))" +
");");
private final SQLiteStatement createScopeTable = db.compileStatement("CREATE TABLE IF NOT EXISTS scope (" +
"mid integer," +
@ -154,96 +154,104 @@ public class ConfigManager {
updateConfig();
isPermissive = readInt(selinuxPath, 1) == 0;
configObserver.startWatching();
cacheScopes();
}
private void createTables() {
createEnabledModulesTable.execute();
createModulesTable.execute();
createScopeTable.execute();
}
private synchronized void cacheScopes() {
modulesForUid.clear();
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
builder.setTables("scope INNER JOIN enabled_modules ON scope.mid = enabled_modules.mid");
Cursor cursor = builder.query(db, new String[]{"scope.uid", "enabled_modules.apk_path"},
null, null, null, null, null);
if (cursor == null) {
Log.e(TAG, "db cache failed");
return;
}
int uid_idx = cursor.getColumnIndex("scope.uid");
int apk_path_idx = cursor.getColumnIndex("enabled_modules.apk_path");
while (cursor.moveToNext()) {
int uid = cursor.getInt(uid_idx);
String apk_path = cursor.getString(apk_path_idx);
modulesForUid.computeIfAbsent(uid, ignored -> new ArrayList<>()).add(apk_path);
try (Cursor cursor = db.query("scope INNER JOIN modules ON scope.mid = modules.mid", new String[]{"uid", "apk_path"},
"enabled = ?", new String[]{"1"}, null, null, null)) {
if (cursor == null) {
Log.e(TAG, "db cache failed");
return;
}
int uid_idx = cursor.getColumnIndex("uid");
int apk_path_idx = cursor.getColumnIndex("apk_path");
while (cursor.moveToNext()) {
int uid = cursor.getInt(uid_idx);
String apk_path = cursor.getString(apk_path_idx);
modulesForUid.computeIfAbsent(uid, ignored -> new ArrayList<>()).add(apk_path);
}
}
}
// This is called when a new process created, use the cached result
public List<String> getModulesPathForUid(int uid) {
return isManager(uid) ? new ArrayList<>() : modulesForUid.getOrDefault(uid, null);
public String[] getModulesPathForUid(int uid) {
return isManager(uid) ? new String[0] : modulesForUid.getOrDefault(uid, new ArrayList<>()).toArray(new String[0]);
}
// This is called when a new process created, use the cached result
// The signature matches Riru's
public boolean shouldSkipUid(int uid) {
Log.d(TAG, modulesForUid.keySet().size() + "");
for (Integer id : modulesForUid.keySet()) {
Log.d(TAG, id.toString());
}
return !modulesForUid.containsKey(uid) && !isManager(uid);
}
// This should only be called by manager, so we don't need to cache it
public Set<Integer> getModuleScope(String packageName) {
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
builder.setTables("scope INNER JOIN enabled_modules ON scope.mid = enabled_modules.mid");
Cursor cursor = builder.query(db, new String[]{"scope.uid"},
null, null, null, null, null);
if (cursor == null) {
Log.e(TAG, "db cache failed");
return null;
public int[] getModuleScope(String packageName) {
int mid = getModuleId(packageName);
if (mid == -1) return null;
try (Cursor cursor = db.query("scope INNER JOIN modules ON scope.mid = modules.mid", new String[]{"uid"},
"scope.mid = ?", new String[]{String.valueOf(mid)}, null, null, null)) {
if (cursor == null) {
return null;
}
int uid_idx = cursor.getColumnIndex("uid");
HashSet<Integer> result = new HashSet<>();
while (cursor.moveToNext()) {
int uid = cursor.getInt(uid_idx);
result.add(uid);
}
return result.stream().mapToInt(i -> i).toArray();
}
int uid_idx = cursor.getColumnIndex("scope.uid");
HashSet<Integer> result = new HashSet<>();
while (cursor.moveToNext()) {
int uid = cursor.getInt(uid_idx);
result.add(uid);
}
return result;
}
public boolean updateModuleApkPath(String packageName, String apkPath) {
if (db.inTransaction()) {
Log.w(TAG, "update module apk path should not be called inside transaction");
return false;
}
ContentValues values = new ContentValues();
values.put("package_name", packageName);
values.put("apk_path", apkPath);
int count = db.updateWithOnConflict("enabled_modules", values, "package_name = ?", new String[]{packageName}, SQLiteDatabase.CONFLICT_REPLACE);
int count = (int) db.insertWithOnConflict("modules", null, values, SQLiteDatabase.CONFLICT_IGNORE);
if (count < 0) {
count = db.updateWithOnConflict("modules", values, "package_name=?", new String[]{packageName}, SQLiteDatabase.CONFLICT_IGNORE);
}
if (count >= 1) {
cacheScopes();
return true;
}
return false;
return count >= 0;
}
// Only be called before updating modules. No need to cache.
private int getModuleId(String packageName) {
try {
db.beginTransaction();
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
Cursor cursor = builder.query(db, new String[]{"mid"}, "package_name = ?", new String[]{packageName}, null, null, null);
if (db.inTransaction()) {
Log.w(TAG, "get module id should not be called inside transaction");
return -1;
}
try (Cursor cursor = db.query("modules", new String[]{"mid"}, "package_name=?", new String[]{packageName}, null, null, null)) {
if (cursor == null) return -1;
if (cursor.getCount() != 1) return -1;
cursor.moveToFirst();
return cursor.getInt(cursor.getColumnIndex("mid"));
} finally {
db.setTransactionSuccessful();
db.endTransaction();
}
}
public boolean setModuleScope(String packageName, String apkPath, List<Integer> uid) {
if (uid == null || uid.isEmpty()) return false;
updateModuleApkPath(packageName, apkPath);
public boolean setModuleScope(String packageName, int[] uid) {
if (uid == null || uid.length == 0) return false;
int mid = getModuleId(packageName);
if (mid == -1) return false;
try {
db.beginTransaction();
int mid = getModuleId(packageName);
if (mid == -1) return false;
db.delete("scope", "mid = ?", new String[]{String.valueOf(mid)});
for (int id : uid) {
ContentValues values = new ContentValues();
@ -259,12 +267,27 @@ public class ConfigManager {
return true;
}
public String[] enabledModules() {
try (Cursor cursor = db.query("modules", new String[]{"package_name"}, "enabled = ?", new String[]{"1"}, null, null, null)) {
if (cursor == null) {
Log.e(TAG, "db cache failed");
return null;
}
int pkg_idx = cursor.getColumnIndex("package_name");
HashSet<String> result = new HashSet<>();
while (cursor.moveToNext()) {
result.add(cursor.getString(pkg_idx));
}
return result.toArray(new String[0]);
}
}
public boolean removeModule(String packageName) {
int mid = getModuleId(packageName);
if (mid == -1) return false;
try {
db.beginTransaction();
int mid = getModuleId(packageName);
if (mid == -1) return false;
db.delete("enabled_modules", "mid = ?", new String[]{String.valueOf(mid)});
db.delete("modules", "mid = ?", new String[]{String.valueOf(mid)});
db.delete("scope", "mid = ?", new String[]{String.valueOf(mid)});
} finally {
db.setTransactionSuccessful();
@ -274,6 +297,39 @@ public class ConfigManager {
return true;
}
public boolean disableModule(String packageName) {
int mid = getModuleId(packageName);
if (mid == -1) return false;
try {
db.beginTransaction();
ContentValues values = new ContentValues();
values.put("enabled", 0);
db.update("modules", values, "mid = ?", new String[]{String.valueOf(mid)});
} finally {
db.setTransactionSuccessful();
db.endTransaction();
}
cacheScopes();
return true;
}
public boolean enableModule(String packageName, String apkPath) {
if (!updateModuleApkPath(packageName, apkPath)) return false;
int mid = getModuleId(packageName);
if (mid == -1) return false;
try {
db.beginTransaction();
ContentValues values = new ContentValues();
values.put("enabled", 1);
db.update("modules", values, "mid = ?", new String[]{String.valueOf(mid)});
} finally {
db.setTransactionSuccessful();
db.endTransaction();
}
cacheScopes();
return true;
}
public boolean removeApp(int uid) {
int count = db.delete("scope", "uid = ?", new String[]{String.valueOf(uid)});
if (count >= 1) {

View File

@ -23,10 +23,14 @@ public class LSPApplicationService extends ILSPApplicationService.Stub {
int uid = Binder.getCallingUid();
int pid = Binder.getCallingPid();
cache.add(new Pair<>(uid, pid));
handle.linkToDeath(() -> {
Log.d(TAG, "pid=" + pid + " uid=" + uid + " is dead.");
cache.remove(new Pair<>(uid, pid));
handles.remove(handle);
handle.linkToDeath(new DeathRecipient() {
@Override
public void binderDied() {
Log.d(TAG, "pid=" + pid + " uid=" + uid + " is dead.");
cache.remove(new Pair<>(uid, pid));
handles.remove(handle);
handle.unlinkToDeath(this, 0);
}
}, 0);
}
@ -43,7 +47,7 @@ public class LSPApplicationService extends ILSPApplicationService.Stub {
}
@Override
public List<String> getModulesList() throws RemoteException {
public String[] getModulesList() throws RemoteException {
ensureRegistered();
return ConfigManager.getInstance().getModulesPathForUid(Binder.getCallingUid());
}

View File

@ -2,8 +2,11 @@ package io.github.lsposed.lspd.service;
import android.content.pm.PackageInfo;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import java.util.List;
import de.robv.android.xposed.XposedBridge;
import io.github.lsposed.lspd.ILSPManagerService;
import io.github.lsposed.lspd.utils.ParceledListSlice;
@ -27,4 +30,76 @@ public class LSPManagerService extends ILSPManagerService.Stub {
public ParceledListSlice<PackageInfo> getInstalledPackagesFromAllUsers(int flags) throws RemoteException {
return PackageService.getInstalledPackagesFromAllUsers(flags);
}
@Override
public String[] enabledModules() {
return ConfigManager.getInstance().enabledModules();
}
@Override
public boolean enableModule(String packageName) throws RemoteException {
PackageInfo pkgInfo = PackageService.getPackageInfo(packageName, 0, 0);
if (pkgInfo == null) return false;
return ConfigManager.getInstance().enableModule(packageName, pkgInfo.applicationInfo.sourceDir);
}
@Override
public boolean setModuleScope(String packageName, int[] uid) {
return ConfigManager.getInstance().setModuleScope(packageName, uid);
}
@Override
public int[] getModuleScope(String packageName) {
return ConfigManager.getInstance().getModuleScope(packageName);
}
@Override
public boolean disableModule(String packageName) {
return ConfigManager.getInstance().disableModule(packageName);
}
@Override
public boolean isResourceHook() {
return ConfigManager.getInstance().resourceHook();
}
@Override
public void setResourceHook(boolean enabled) {
ConfigManager.getInstance().setResourceHook(enabled);
}
@Override
public boolean isVerboseLog() {
return ConfigManager.getInstance().verboseLog();
}
@Override
public void setVerboseLog(boolean enabled) {
ConfigManager.getInstance().setVerboseLog(enabled);
}
@Override
public int getVariant() {
return ConfigManager.getInstance().variant();
}
@Override
public void setVariant(int variant) {
ConfigManager.getInstance().setVariant(variant);
}
@Override
public boolean isPermissive() {
return ConfigManager.getInstance().isPermissive();
}
@Override
public ParcelFileDescriptor getVerboseLog() {
return ConfigManager.getInstance().getVerboseLog();
}
@Override
public ParcelFileDescriptor getModulesLog() {
return ConfigManager.getInstance().getModulesLog();
}
}

View File

@ -4,5 +4,34 @@ import io.github.lsposed.lspd.utils.ParceledListSlice;
interface ILSPManagerService {
int getVersion() = 1;
ParceledListSlice<PackageInfo> getInstalledPackagesFromAllUsers(int flags) = 2;
String[] enabledModules() = 3;
boolean enableModule(String packageName) = 4;
boolean disableModule(String packageName) = 5;
boolean setModuleScope(String packageName, in int[] uid) = 6;
int[] getModuleScope(String packageName) = 7;
boolean isResourceHook() = 9;
void setResourceHook(boolean enabled) = 10;
boolean isVerboseLog() = 11;
void setVerboseLog(boolean enabled) = 12;
int getVariant() = 13;
void setVariant(int variant) = 14;
boolean isPermissive() = 15;
ParcelFileDescriptor getVerboseLog() = 16;
ParcelFileDescriptor getModulesLog() = 17;
}