From 77e18a38abe5b8fb74861078e790a3c298b9610c Mon Sep 17 00:00:00 2001 From: Muntashir Al-Islam Date: Thu, 3 Dec 2020 12:00:17 +0600 Subject: [PATCH] [rules] Set component to its default state when unblocked --- .../details/AppDetailsViewModel.java | 16 +- .../rules/compontents/ComponentsBlocker.java | 211 +++++++++++++----- 2 files changed, 166 insertions(+), 61 deletions(-) diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/details/AppDetailsViewModel.java b/app/src/main/java/io/github/muntashirakon/AppManager/details/AppDetailsViewModel.java index 4f38110eac6..ec374511d7d 100644 --- a/app/src/main/java/io/github/muntashirakon/AppManager/details/AppDetailsViewModel.java +++ b/app/src/main/java/io/github/muntashirakon/AppManager/details/AppDetailsViewModel.java @@ -269,14 +269,22 @@ public void updateRulesForComponent(String componentName, RulesStorageManager.Ty synchronized (blockerLocker) { waitForBlockerOrExit(); blocker.setMutable(); - if (blocker.hasComponent(componentName)) { // Remove from the list - blocker.removeComponent(componentName); - } else { // Add to the list + if (blocker.hasComponent(componentName)) { + // Component is in the list + if (blocker.isComponentBlocked(componentName)) { + // Remove from the list + blocker.removeComponent(componentName); + } else { + // The component isn't being blocked, simply remove it + blocker.removeEntry(componentName); + } + } else { + // Add to the list blocker.addComponent(componentName, type); } // Apply rules if global blocking enable or already applied if ((Boolean) AppPref.get(AppPref.PrefKey.PREF_GLOBAL_BLOCKING_ENABLED_BOOL) - || (ruleApplicationStatus != null && ruleApplicationStatus.getValue() == RULE_APPLIED)) { + || (ruleApplicationStatus != null && RULE_APPLIED == ruleApplicationStatus.getValue())) { blocker.applyRules(true); } // Set new status diff --git a/app/src/main/java/io/github/muntashirakon/AppManager/rules/compontents/ComponentsBlocker.java b/app/src/main/java/io/github/muntashirakon/AppManager/rules/compontents/ComponentsBlocker.java index fd694f1b439..a8243f0ce17 100644 --- a/app/src/main/java/io/github/muntashirakon/AppManager/rules/compontents/ComponentsBlocker.java +++ b/app/src/main/java/io/github/muntashirakon/AppManager/rules/compontents/ComponentsBlocker.java @@ -33,7 +33,6 @@ import androidx.annotation.NonNull; import io.github.muntashirakon.AppManager.AppManager; import io.github.muntashirakon.AppManager.logs.Log; -import io.github.muntashirakon.AppManager.misc.Users; import io.github.muntashirakon.AppManager.rules.RulesStorageManager; import io.github.muntashirakon.AppManager.runner.Runner; import io.github.muntashirakon.AppManager.runner.RunnerUtils; @@ -43,19 +42,16 @@ /** * Block application components: activities, broadcasts, services and providers. - *

- * Activities, broadcasts and services are blocked via Intent Firewall (which is superior to the - * pm disable component method). Rules for each package is saved as a separate - * xml file which is named after the package name and saved to /data/system/ifw and - * /sdcard/Android/data/io.github.muntashirakon.AppManager/files/ifw. By default, data is read from - * both directories but written only to the latter directory unless - * {@link ComponentsBlocker#applyRules(boolean)} is called in which case data is - * saved to the former directory (rules are applied automatically once they're copied there). - *
- * Providers are blocked via pm disable component method since there's no way to - * block them via intent firewall. blocked providers are only kept in the /sdcard/Android/data/io.github.muntashirakon.AppManager/files/ifw - * directory. The file name is the same as above but with .txt extension instead of .xml and each - * provider is saved in a new line. + *

+ * Activities, broadcasts and services are blocked via Intent Firewall (which is superior to + * pm disable component). Rules for each package is saved as a separate tsv file + * named after its package name and saved to {@code /data/data/${applicationId}/files/conf}. In case + * of activities, broadcasts and services, the rules are finally saved to {@link #SYSTEM_RULES_PATH} + * via {@link #LOCAL_RULES_PATH}. + *

+ * Providers are blocked via pm disable provider since there's no way to block + * them via Intent Firewall. Blocked providers are only kept in the + * {@code /data/data/${applicationId}/files/conf} directory. * * @see IntentFirewall.java */ @@ -68,11 +64,40 @@ public final class ComponentsBlocker extends RulesStorageManager { @SuppressLint("StaticFieldLeak") private static ComponentsBlocker INSTANCE; + /** + * Get a new or existing IMMUTABLE instance of {@link ComponentsBlocker}. The existing instance + * will only be returned if the existing instance has the same package name as the original. + * This read rules from the {@link #SYSTEM_RULES_PATH}. If reading rules is necessary, use + * {@link #getInstance(String, int, boolean)} with the last argument set to true. It is also + * possible to make this instance mutable by calling {@link #setMutable()} and once set mutable, + * closing this instance will commit the changes automatically. To prevent this, + * {@link #setReadOnly()} should be called before closing the instance. + * + * @param packageName The package whose instance is to be returned + * @param userHandle The user to whom the rules belong + * @return New or existing immutable instance for the package + * @see #getInstance(String, int, boolean) + * @see #getMutableInstance(String, int) + */ @NonNull public static ComponentsBlocker getInstance(@NonNull String packageName, int userHandle) { return getInstance(packageName, userHandle, false); } + /** + * Get a new or existing MUTABLE instance of {@link ComponentsBlocker}. This DOES NOT read rules + * from the {@link #SYSTEM_RULES_PATH}. This is essentially the same as calling + * {@link #getInstance(String, int, boolean)} with the last argument set to {@code true} and + * calling {@link #setMutable()} after that. Closing this instance will commit the changes + * automatically. To prevent this, {@link #setReadOnly()} should be called before closing + * the instance. + * + * @param packageName The package whose instance is to be returned + * @param userHandle The user to whom the rules belong + * @return New or existing mutable instance for the package + * @see #getInstance(String, int) + * @see #getInstance(String, int, boolean) + */ @NonNull public static ComponentsBlocker getMutableInstance(@NonNull String packageName, int userHandle) { ComponentsBlocker componentsBlocker = getInstance(packageName, userHandle, true); @@ -80,8 +105,23 @@ public static ComponentsBlocker getMutableInstance(@NonNull String packageName, return componentsBlocker; } + /** + * Get a new or existing IMMUTABLE instance of {@link ComponentsBlocker}. The existing instance + * will only be returned if the existing instance has the same package name as the original. It + * is also possible to make this instance mutable by calling {@link #setMutable()} and once set + * mutable, closing this instance will commit the changes automatically. To prevent this, + * {@link #setReadOnly()} should be called before closing the instance. + * + * @param packageName The package whose instance is to be returned + * @param userHandle The user to whom the rules belong + * @param noLoadFromDisk Whether not to load rules from the {@link #SYSTEM_RULES_PATH} + * @return New or existing immutable instance for the package + * @see #getInstance(String, int) + * @see #getMutableInstance(String, int) + */ @NonNull public static ComponentsBlocker getInstance(@NonNull String packageName, int userHandle, boolean noLoadFromDisk) { + // TODO(3/12/20): Handle multiple users if (INSTANCE == null) { try { getLocalIfwRulesPath(); @@ -101,6 +141,14 @@ public static ComponentsBlocker getInstance(@NonNull String packageName, int use return INSTANCE; } + /** + * Get locally stored IFW rules path. Currently set to + * {@code /sdcard/Android/data/${applicationId}/files/ifw} or + * {@code /data/data/${applicationId}/files/ifw} depending on the availability of the external + * storage. This path is only used briefly, before writing to the {@link #SYSTEM_RULES_PATH}. + * + * @throws FileNotFoundException If there's no path available where rules can be stored locally + */ public static void getLocalIfwRulesPath() throws FileNotFoundException { Context context = AppManager.getContext(); // FIXME: Move from getExternalFilesDir to getCacheDir @@ -124,32 +172,52 @@ protected ComponentsBlocker(Context context, String packageName, int userHandle) } /** - * Apply all rules configured within App Manager. This includes the external IFW path as well as - * the internal conf path. In v2.6, the former path will be removed. + * Apply all rules configured within App Manager. This also includes {@link #SYSTEM_RULES_PATH}. * - * @param context Application Context + * @param context Application Context * @param userHandle The user to apply rules */ public static void applyAllRules(@NonNull Context context, int userHandle) { // Apply all rules from conf folder - PrivilegedFile confPath = new PrivilegedFile(context.getFilesDir(), "conf"); - String[] packageNamesWithTSV = confPath.list((dir, name) -> name.endsWith(".tsv")); - if (packageNamesWithTSV != null) { + File confPath = new File(context.getFilesDir(), "conf"); + String[] packageNamesWithTSVExt = confPath.list((dir, name) -> name.endsWith(".tsv")); + if (packageNamesWithTSVExt != null) { // Apply rules - String packageName; - for (String s : packageNamesWithTSV) { - packageName = s.substring(0, s.lastIndexOf(".tsv")); - try (ComponentsBlocker cb = getMutableInstance(packageName, userHandle)) { + for (String packageNameWithTSVExt : packageNamesWithTSVExt) { + try (ComponentsBlocker cb = getMutableInstance(IOUtils.trimExtension(packageNameWithTSVExt), userHandle)) { cb.applyRules(true); } } } } + /** + * Check if the given component exists in the rules. It does not necessarily mean that the + * component is being blocked. + * + * @param componentName The component name to check + * @return {@code true} if exists, {@code false} otherwise + * + * @see #isComponentBlocked(String) + */ public boolean hasComponent(String componentName) { return hasName(componentName); } + /** + * Whether the given component is blocked. + * @param componentName The component name to check + * @return {@code true} if blocked, {@code false} otherwise + */ + public boolean isComponentBlocked(String componentName) { + return hasComponent(componentName) && get(componentName).extra == COMPONENT_BLOCKED; + } + + /** + * Get number of components among other rules. + * + * @return Number of components + */ public int componentCount() { int count = 0; for (Entry entry : getAll()) { @@ -163,27 +231,43 @@ public int componentCount() { return count; } + /** + * Add the given component to the rules list, does nothing if the instance is immutable. + * + * @param componentName The component to add + * @param componentType Component type + * + * @see #addEntry(Entry) + */ public void addComponent(String componentName, RulesStorageManager.Type componentType) { if (!readOnly) setComponent(componentName, componentType, COMPONENT_TO_BE_BLOCKED); } + /** + * Suggest removal of the given component from the rules, does nothing if the instance is + * immutable or the component does not exist. The rules are only applied when {@link #commit()} + * is called. + * + * @param componentName The component to remove + * @see #removeEntry(Entry) + * @see #removeEntry(String) + */ public void removeComponent(String componentName) { if (readOnly) return; if (hasName(componentName)) { - if (get(componentName).type == Type.PROVIDER) // Preserve for later - setComponent(componentName, Type.PROVIDER, COMPONENT_TO_BE_UNBLOCKED); - else removeEntry(componentName); + setComponent(componentName, get(componentName).type, COMPONENT_TO_BE_UNBLOCKED); } } /** - * Save the disabled components locally (not applied to the system) + * Save the disabled components in the {@link #LOCAL_RULES_PATH}. * * @throws IOException If it fails to write to the destination file */ private void saveDisabledComponents() throws IOException { if (readOnly) throw new IOException("Saving disabled components in read only mode."); if (componentCount() == 0) { + // No components set, delete if already exists if (localRulesFile.exists()) //noinspection ResultOfMethodCallIgnored localRulesFile.delete(); return; @@ -192,6 +276,7 @@ private void saveDisabledComponents() throws IOException { StringBuilder services = new StringBuilder(); StringBuilder receivers = new StringBuilder(); for (RulesStorageManager.Entry component : getAllComponents()) { + // Ignore components that needs unblocking if (component.extra == COMPONENT_TO_BE_UNBLOCKED) continue; String componentFilter = " \n"; RulesStorageManager.Type componentType = component.type; @@ -228,9 +313,9 @@ private void saveDisabledComponents() throws IOException { * rules file in the system IFW directory as well, but since all controls are now inside the app * itself, it's no longer deemed necessary to check the existence of the file. Besides, previous * implementation (which was similar to Watt's) did not take providers into account, which are - * blocked via pm. + * blocked via {@code pm}. * - * @return True if applied, false otherwise + * @return {@code true} if there's no pending rules, {@code false} otherwise */ public boolean isRulesApplied() { List entries = getAllComponents(); @@ -241,42 +326,49 @@ public boolean isRulesApplied() { /** * Apply the currently modified rules if the the argument apply is true. Since IFW is used, when - * apply is true, the IFW rules are saved to the system directory and components that are set to - * be removed or unblocked will be removed (or for providers, enabled and removed). If apply is - * set to false, all rules will be removed but before that all components will be set to their - * default state (ie., the state described in the app manifest). + * apply is true, the IFW rules are saved to {@link #SYSTEM_RULES_PATH} and components that are + * set to be removed or unblocked will be removed. If apply is set to false, all rules will be + * removed but before that all components will be set to their default state (ie., the state + * described in the app manifest). * * @param apply Whether to apply the rules or remove them altogether */ public void applyRules(boolean apply) { try { - // Save disabled components + // Save blocked IFW components if (apply) saveDisabledComponents(); - // Apply/Remove rules + // Apply/Remove IFW rules if (apply && localRulesFile.exists()) { // Apply rules - Runner.runCommand(String.format(Runner.TOYBOX + " cp \"%s\" %s && " + Runner.TOYBOX + " chmod 0666 %s%s.xml && am force-stop %s", + Runner.runCommand(String.format(Runner.TOYBOX + " cp \"%s\" %s && " + + Runner.TOYBOX + " chmod 0666 %s%s.xml && " + + RunnerUtils.CMD_FORCE_STOP_PACKAGE, localRulesFile.getAbsolutePath(), SYSTEM_RULES_PATH, SYSTEM_RULES_PATH, - packageName, packageName)); + packageName, RunnerUtils.userHandleToUser(userHandle), packageName)); } else { // Remove rules if remove is called or applied with no rules - Runner.runCommand(String.format(Runner.TOYBOX + " test -e '%s%s.xml' && " + Runner.TOYBOX + " rm -rf %s%s.xml && am force-stop %s", - SYSTEM_RULES_PATH, packageName, SYSTEM_RULES_PATH, packageName, packageName)); + Runner.runCommand(String.format(Runner.TOYBOX + " test -e '%s%s.xml' && " + + Runner.TOYBOX + " rm -rf %s%s.xml && " + + RunnerUtils.CMD_FORCE_STOP_PACKAGE, + SYSTEM_RULES_PATH, packageName, SYSTEM_RULES_PATH, packageName, + RunnerUtils.userHandleToUser(userHandle), packageName)); } if (localRulesFile.exists()) //noinspection ResultOfMethodCallIgnored localRulesFile.delete(); // Enable/disable components if (apply) { - // Disable providers - List disabledProviders = getAll(RulesStorageManager.Type.PROVIDER); - Log.d(TAG, "Providers: " + disabledProviders.toString()); - for (RulesStorageManager.Entry provider : disabledProviders) { - if (provider.extra == COMPONENT_TO_BE_UNBLOCKED) { // Enable components that are removed - RunnerUtils.enableComponent(packageName, provider.name, Users.getCurrentUserHandle()); - removeEntry(provider); - } else { - RunnerUtils.disableComponent(packageName, provider.name, Users.getCurrentUserHandle()); - setComponent(provider.name, provider.type, COMPONENT_BLOCKED); + // Enable the components that need removal and disable requested providers + List allEntries = getAllComponents(); + Log.d(TAG, "All: " + allEntries.toString()); + for (RulesStorageManager.Entry entry : allEntries) { + if (entry.extra == COMPONENT_TO_BE_UNBLOCKED) { + // Enable components that are removed + RunnerUtils.enableComponent(packageName, entry.name, userHandle); + removeEntry(entry); + } else if (entry.type == Type.PROVIDER) { + // Disable providers + RunnerUtils.disableComponent(packageName, entry.name, userHandle); + setComponent(entry.name, entry.type, COMPONENT_BLOCKED); } } } else { @@ -284,7 +376,9 @@ public void applyRules(boolean apply) { List allEntries = getAllComponents(); Log.d(TAG, "All: " + allEntries.toString()); for (RulesStorageManager.Entry entry : allEntries) { - RunnerUtils.enableComponent(packageName, entry.name, Users.getCurrentUserHandle()); // Enable components if they're disabled by other methods + // Enable components if they're disabled by other methods. + // IFW rules are already removed above. + RunnerUtils.enableComponent(packageName, entry.name, userHandle); if (entry.extra == COMPONENT_TO_BE_UNBLOCKED) removeEntry(entry); else setComponent(entry.name, entry.type, COMPONENT_TO_BE_BLOCKED); } @@ -295,30 +389,33 @@ public void applyRules(boolean apply) { } /** - * Retrieve a set of disabled components from local source. If it's available in the system, - * save a copy to the local source and then retrieve the components + * Retrieve a set of disabled components from the {@link #SYSTEM_RULES_PATH}. If they are + * available add them to the rules, overridden if necessary. */ private void retrieveDisabledComponents() { if (!AppPref.isRootEnabled()) return; Log.d(TAG, "Retrieving disabled components for package " + packageName); PrivilegedFile rulesFile = new PrivilegedFile(SYSTEM_RULES_PATH, packageName + ".xml"); - String ruleXmlString = null; + String ruleXmlString; if (rulesFile.exists()) { - // Copy system rules to access them locally + // Read system rules ruleXmlString = IOUtils.getFileContent(rulesFile); Log.d(TAG, "IFW: Retrieved components for package " + packageName + "\n" + ruleXmlString); - } + } else ruleXmlString = null; if (TextUtils.isEmpty(ruleXmlString)) { - // Load from App Manager's saved rules + // System doesn't have any rules. + // Load the rules saved inside App Manager for (RulesStorageManager.Entry entry : getAllComponents()) { setComponent(entry.name, entry.type, COMPONENT_TO_BE_BLOCKED); } return; } try { + //noinspection ConstantConditions ruleXmlString is never null here try (InputStream rulesStream = new ByteArrayInputStream(ruleXmlString.getBytes())) { HashMap components = ComponentUtils.readIFWRules(rulesStream, packageName); for (String componentName : components.keySet()) { + // Override existing rule for the component if it exists setComponent(componentName, components.get(componentName), COMPONENT_BLOCKED); } }