[core] ConfigManager in Java
This commit is contained in:
parent
d85b4d3584
commit
3d2164b599
|
|
@ -0,0 +1,253 @@
|
|||
package io.github.lsposed.lspd.service;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.database.sqlite.SQLiteStatement;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.FileObserver;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
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.Service.TAG;
|
||||
|
||||
public class ConfigManager {
|
||||
static ConfigManager instance = null;
|
||||
|
||||
final private File configPath = new File("/data/adb/lspd/config");
|
||||
final private SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(new File(configPath, "modules_config.db"), null);
|
||||
|
||||
final private File resourceHookSwitch = new File(configPath, "enable_resources");
|
||||
private boolean resourceHook = false;
|
||||
|
||||
final private File variantSwitch = new File(configPath, "variant");
|
||||
private int variant = -1;
|
||||
|
||||
final private File verboseLogSwitch = new File(configPath, "verbose_log");
|
||||
private boolean verboseLog = false;
|
||||
|
||||
final private File selinuxPath = new File("/sys/fs/selinux/enforce");
|
||||
// only check on boot
|
||||
final private boolean isPermissive;
|
||||
|
||||
final FileObserver configObserver = new FileObserver(configPath) {
|
||||
@Override
|
||||
public void onEvent(int event, @Nullable String path) {
|
||||
updateConfig();
|
||||
cacheScopes();
|
||||
}
|
||||
};
|
||||
|
||||
int readInt(File file, int defaultValue) {
|
||||
try {
|
||||
return Integer.parseInt(new String(Files.readAllBytes(file.toPath())));
|
||||
} catch (IOException | NumberFormatException e) {
|
||||
Log.e(TAG, Log.getStackTraceString(e));
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
void writeInt(File file, int value) {
|
||||
try {
|
||||
Files.write(file.toPath(), String.valueOf(value).getBytes(), StandardOpenOption.CREATE_NEW);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, Log.getStackTraceString(e));
|
||||
}
|
||||
}
|
||||
|
||||
void updateConfig() {
|
||||
resourceHook = resourceHookSwitch.exists();
|
||||
variant = readInt(variantSwitch, -1);
|
||||
verboseLog = readInt(verboseLogSwitch, 0) == 1;
|
||||
}
|
||||
|
||||
SQLiteStatement createEnabledModulesTable = db.compileStatement("CREATE TABLE IF NOT EXISTS enabled_modules (" +
|
||||
"mid int PRIMARY KEY AUTOINCREMENT," +
|
||||
"package_name text NOT NULL UNIQUE," +
|
||||
"apk_path text NOT NULL" +
|
||||
");");
|
||||
SQLiteStatement createScopeTable = db.compileStatement("CREATE TABLE IF NOT EXISTS scope (" +
|
||||
"mid int," +
|
||||
"uid int," +
|
||||
"PRIMARY KEY (mid, uid)" +
|
||||
");");
|
||||
|
||||
ConcurrentHashMap<Integer, ArrayList<String>> modulesForUid;
|
||||
|
||||
static ConfigManager getInstance() {
|
||||
if (instance == null)
|
||||
instance = new ConfigManager();
|
||||
return instance;
|
||||
}
|
||||
|
||||
private ConfigManager() {
|
||||
createTables();
|
||||
updateConfig();
|
||||
isPermissive = readInt(selinuxPath, 1) == 0;
|
||||
}
|
||||
|
||||
private void createTables() {
|
||||
createEnabledModulesTable.execute();
|
||||
createScopeTable.execute();
|
||||
}
|
||||
|
||||
private synchronized void cacheScopes() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// This is called when a new process created, use the cached result
|
||||
public List<String> getModulesPathForUid(int uid) {
|
||||
return modulesForUid.getOrDefault(uid, null);
|
||||
}
|
||||
|
||||
// This is called when a new process created, use the cached result
|
||||
// The signature matches Riru's
|
||||
public boolean shouldSkipUid(int uid) {
|
||||
return !modulesForUid.containsKey(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;
|
||||
}
|
||||
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) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("apk_path", apkPath);
|
||||
int count = db.updateWithOnConflict("enabled_modules", values, "package_name = ?", new String[]{packageName}, SQLiteDatabase.CONFLICT_REPLACE);
|
||||
if (count > 1) {
|
||||
cacheScopes();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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 (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);
|
||||
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();
|
||||
values.put("mid", mid);
|
||||
values.put("uid", id);
|
||||
db.insertWithOnConflict("scope", null, values, SQLiteDatabase.CONFLICT_IGNORE);
|
||||
}
|
||||
} finally {
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
cacheScopes();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean removeModule(String packageName) {
|
||||
try {
|
||||
db.beginTransaction();
|
||||
int mid = getModuleId(packageName);
|
||||
if (mid == -1) return false;
|
||||
db.delete("enabled_modules", "mid = ?", new String[]{String.valueOf(mid)});
|
||||
db.delete("scope", "mid = ?", new String[]{String.valueOf(mid)});
|
||||
} finally {
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
cacheScopes();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean removeApps(int uid) {
|
||||
int count = db.delete("scope", "uid = ?", new String[]{String.valueOf(uid)});
|
||||
if (count > 1) {
|
||||
cacheScopes();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setResourceHook(boolean resourceHook) {
|
||||
writeInt(resourceHookSwitch, resourceHook ? 1 : 0);
|
||||
}
|
||||
|
||||
public void setVerboseLog(boolean verboseLog) {
|
||||
writeInt(verboseLogSwitch, verboseLog ? 1 : 0);
|
||||
}
|
||||
|
||||
public void setVariant(int variant) {
|
||||
writeInt(variantSwitch, variant);
|
||||
}
|
||||
|
||||
public boolean isPermissive() {
|
||||
return isPermissive;
|
||||
}
|
||||
|
||||
public boolean resourceHook() {
|
||||
return resourceHook;
|
||||
}
|
||||
|
||||
public boolean verboseLog() {
|
||||
return verboseLog;
|
||||
}
|
||||
|
||||
public int variant() {
|
||||
return variant;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue