Add more options in the Select menu

Allow users to select all/none and automatically include new applications.
Close #93 as completed.

Co-authored-by: mywalk <66966897+mywalkb@users.noreply.github.com>
This commit is contained in:
JingMatrix 2024-12-04 20:18:08 +01:00
parent c5ff4c0e3a
commit 1339fc7d3c
8 changed files with 161 additions and 25 deletions

View File

@ -390,4 +390,23 @@ public class ConfigManager {
return ILSPManagerService.DEX2OAT_CRASHED;
}
}
public static boolean getAutoInclude(String packageName) {
try {
return LSPManagerServiceHolder.getService().getAutoInclude(packageName);
} catch (RemoteException e) {
Log.e(App.TAG, Log.getStackTraceString(e));
return false;
}
}
public static boolean setAutoInclude(String packageName, boolean enable) {
try {
LSPManagerServiceHolder.getService().setAutoInclude(packageName, enable);
return true;
} catch (RemoteException e) {
Log.e(App.TAG, Log.getStackTraceString(e));
return false;
}
}
}

View File

@ -292,6 +292,26 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
fragment.showHint(R.string.enable_documentui, true);
return false;
}
} else if (itemId == R.id.select_all) {
var tmpChkList = new HashSet<ApplicationWithEquals>(ConfigManager.getModuleScope(module.packageName));
for (AppInfo info : searchList) {
if (info.packageName.equals("android")) {
fragment.showHint(R.string.reboot_required, true, R.string.reboot, v -> ConfigManager.reboot());
}
tmpChkList.add(info.application);
}
ConfigManager.setModuleScope(module.packageName, module.legacy, tmpChkList);
} else if (itemId == R.id.select_none) {
var tmpChkList = new HashSet<ApplicationWithEquals>(ConfigManager.getModuleScope(module.packageName));
for (AppInfo info : searchList) {
if (tmpChkList.remove(info.application) && info.packageName.equals("android")) {
fragment.showHint(R.string.reboot_required, true, R.string.reboot, v -> ConfigManager.reboot());
}
}
ConfigManager.setModuleScope(module.packageName, module.legacy, tmpChkList);
} else if (itemId == R.id.auto_include) {
item.setChecked(!item.isChecked());
ConfigManager.setAutoInclude(module.packageName, item.isChecked());
} else if (!AppHelper.onOptionsItemSelected(item, preferences)) {
return false;
}
@ -371,6 +391,7 @@ public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<Scope
}
case 0 -> menu.findItem(R.id.item_sort_by_name).setChecked(true);
}
menu.findItem(R.id.auto_include).setChecked(ConfigManager.getAutoInclude(module.packageName));
}
@Override

View File

@ -29,32 +29,44 @@
android:title="@android:string/search_go" />
<item
android:id="@+id/use_recommended"
android:title="@string/use_recommended" />
android:showAsAction="never"
android:title="@string/menu_select">
<menu>
<item
android:id="@+id/use_recommended"
android:title="@string/use_recommended" />
<item
android:id="@+id/select_all"
android:title="@string/menu_select_all" />
<item
android:id="@+id/select_none"
android:title="@string/menu_select_none" />
<item
android:id="@+id/auto_include"
android:checkable="true"
android:title="@string/menu_auto_include" />
</menu>
</item>
<item
android:showAsAction="never"
android:title="@string/menu_hide">
<menu>
<item
android:id="@+id/item_filter_games"
android:checkable="true"
android:checked="true"
android:title="@string/menu_show_games" />
<item
android:id="@+id/item_filter_modules"
android:checkable="true"
android:checked="true"
android:title="@string/menu_show_modules" />
<item
android:id="@+id/item_filter_system"
android:checkable="true"
android:checked="true"
android:title="@string/menu_show_system_apps" />
<item
android:id="@+id/item_filter_denylist"
android:checkable="true"

View File

@ -141,9 +141,13 @@
<string name="menu_show_denylist">Denylist</string>
<string name="failed_to_save_scope_list">Failed to save scope list</string>
<string name="app_version">Version: %1$s</string>
<string name="menu_select">Select</string>
<string name="use_recommended">Recommended</string>
<string name="no_scope_selected_has_recommended">You did not select any app. Select recommended apps?</string>
<string name="use_recommended_message">Select recommended apps?</string>
<string name="menu_select_all">All</string>
<string name="menu_select_none">None</string>
<string name="menu_auto_include">Auto-Include</string>
<string name="module_is_not_activated_yet">Xposed module is not activated yet</string>
<string name="requested_by_module">Recommended</string>
<string name="update_available">Update available: %1$s</string>

View File

@ -32,6 +32,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageParser;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteStatement;
import android.os.Build;
import android.os.Bundle;
@ -141,7 +142,9 @@ public class ConfigManager {
"module_pkg_name text NOT NULL UNIQUE," +
"apk_path text NOT NULL, " +
"enabled BOOLEAN DEFAULT 0 " +
"CHECK (enabled IN (0, 1))" +
"CHECK (enabled IN (0, 1))," +
"auto_include BOOLEAN DEFAULT 0 " +
"CHECK (auto_include IN (0, 1))" +
");");
private final SQLiteStatement createScopeTable = db.compileStatement("CREATE TABLE IF NOT EXISTS scope (" +
"mid integer," +
@ -420,6 +423,20 @@ public class ConfigManager {
db.compileStatement("UPDATE scope SET app_pkg_name = 'system' WHERE app_pkg_name = 'android';").execute();
db.setVersion(3);
});
case 3:
try {
executeInTransaction(() -> {
db.compileStatement("ALTER TABLE modules ADD COLUMN auto_include BOOLEAN DEFAULT 0 CHECK (auto_include IN (0, 1));").execute();
db.setVersion(4);
});
} catch (SQLiteException ex) {
// Fix wrong init code for new column auto_include
if (ex.getMessage().startsWith("duplicate column name: auto_include")) {
db.setVersion(4);
} else {
throw ex;
}
}
default:
break;
}
@ -894,20 +911,7 @@ public class ConfigManager {
public String[] enabledModules() {
try (Cursor cursor = db.query("modules", new String[]{"module_pkg_name"}, "enabled = 1", null, null, null, null)) {
if (cursor == null) {
Log.e(TAG, "query enabled modules failed");
return null;
}
int modulePkgNameIdx = cursor.getColumnIndex("module_pkg_name");
HashSet<String> result = new HashSet<>();
while (cursor.moveToNext()) {
var pkgName = cursor.getString(modulePkgNameIdx);
if (pkgName.equals("lspd")) continue;
result.add(pkgName);
}
return result.toArray(new String[0]);
}
return listModules("enabled");
}
public boolean removeModule(String packageName) {
@ -1223,4 +1227,41 @@ public class ConfigManager {
synchronized SharedMemory getPreloadDex() {
return ConfigFileManager.getPreloadDex(dexObfuscate);
}
public boolean getAutoInclude(String packageName) {
try (Cursor cursor = db.query("modules", new String[]{"auto_include"},
"module_pkg_name = ? and auto_include = 1", new String[]{packageName}, null, null, null, null)) {
return cursor == null || cursor.moveToNext();
}
}
public boolean setAutoInclude(String packageName, boolean enable) {
boolean changed = executeInTransaction(() -> {
ContentValues values = new ContentValues();
values.put("auto_include", enable ? 1 : 0);
return db.update("modules", values, "module_pkg_name = ?", new String[]{packageName}) > 0;
});
return true;
}
public String[] getAutoIncludeModules() {
return listModules("auto_include");
}
private String[] listModules(String column) {
try (Cursor cursor = db.query("modules", new String[]{"module_pkg_name"}, column + " = 1", null, null, null, null)) {
if (cursor == null) {
Log.e(TAG, "query " + column + " modules failed");
return null;
}
int modulePkgNameIdx = cursor.getColumnIndex("module_pkg_name");
HashSet<String> result = new HashSet<>();
while (cursor.moveToNext()) {
var pkgName = cursor.getString(modulePkgNameIdx);
if (pkgName.equals("lspd")) continue;
result.add(pkgName);
}
return result.toArray(new String[0]);
}
}
}

View File

@ -556,4 +556,14 @@ public class LSPManagerService extends ILSPManagerService.Stub {
public boolean isLogWatchdogEnabled() {
return ConfigManager.getInstance().isLogWatchdogEnabled();
}
@Override
public boolean setAutoInclude(String packageName, boolean enabled) {
return ConfigManager.getInstance().setAutoInclude(packageName, enabled);
}
@Override
public boolean getAutoInclude(String packageName) {
return ConfigManager.getInstance().getAutoInclude(packageName);
}
}

View File

@ -45,6 +45,7 @@ import android.telephony.TelephonyManager;
import android.util.Log;
import org.lsposed.daemon.BuildConfig;
import org.lsposed.lspd.models.Application;
import java.io.IOException;
import java.util.Arrays;
@ -151,8 +152,10 @@ public class LSPosedService extends ILSPosedService.Stub {
if (moduleName != null) {
LSPNotificationManager.cancelNotification(UPDATED_CHANNEL_ID, moduleName, userId);
}
break;
}
case Intent.ACTION_PACKAGE_ADDED, Intent.ACTION_PACKAGE_CHANGED -> {
var configManager = ConfigManager.getInstance();
// make sure that the change is for the complete package, not only a
// component
String[] components = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
@ -164,10 +167,32 @@ public class LSPosedService extends ILSPosedService.Stub {
// module to send a broadcast when modules that have not been activated are
// uninstalled.
// If cache not updated, assume it's not xposed module
isXposedModule = ConfigManager.getInstance().updateModuleApkPath(moduleName, ConfigManager.getInstance().getModuleApkPath(applicationInfo), false);
} else if (ConfigManager.getInstance().isUidHooked(uid)) {
// it will auto update obsolete scope from database
ConfigManager.getInstance().updateAppCache();
isXposedModule = configManager.updateModuleApkPath(moduleName, ConfigManager.getInstance().getModuleApkPath(applicationInfo), false);
} else {
if (configManager.isUidHooked(uid)) {
// it will automatically remove obsolete app from database
configManager.updateAppCache();
}
if (intentAction.equals(Intent.ACTION_PACKAGE_ADDED) && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
for (String xposedModule : configManager.getAutoIncludeModules()) {
// For Xposed modules with auto_include set, we always add new applications
// to its scope
var list = configManager.getModuleScope(xposedModule);
if (list != null) {
Application scope = new Application();
scope.packageName = moduleName;
scope.userId = userId;
list.add(scope);
try {
if (!configManager.setModuleScope(xposedModule, list)) {
Log.e(TAG, "failed to set scope for " + xposedModule);
}
} catch(RemoteException re) {
Log.e(TAG, "failed to set scope for " + xposedModule, re);
}
}
}
}
}
broadcastAndShowNotification(moduleName, userId, intent, isXposedModule);
}

View File

@ -91,4 +91,8 @@ interface ILSPManagerService {
void setLogWatchdog(boolean enable) = 49;
boolean isLogWatchdogEnabled() = 50;
boolean getAutoInclude(String packageName) = 51;
boolean setAutoInclude(String packageName, boolean enable) = 52;
}