[core] Add more manager service interfaces
This commit is contained in:
parent
374c8cac11
commit
e7e32351ea
|
|
@ -11,7 +11,7 @@ interface ILSPApplicationService {
|
|||
|
||||
boolean isResourcesHookEnabled() = 5;
|
||||
|
||||
List<String> getModulesList() = 6;
|
||||
String[] getModulesList() = 6;
|
||||
|
||||
String getPrefsPath(String packageName) = 7;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue