From 9f88505eacf7e8c9c42aa5c8ed62c0b71c35f86f Mon Sep 17 00:00:00 2001 From: Eugen Pechanec Date: Thu, 21 Mar 2024 21:38:34 +0100 Subject: [PATCH] Clearing package data also revokes development permissions on API < 23 This matches the behavior on API 23 and newer. --- .../orchestrator/AndroidTestOrchestrator.java | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/runner/android_test_orchestrator/java/androidx/test/orchestrator/AndroidTestOrchestrator.java b/runner/android_test_orchestrator/java/androidx/test/orchestrator/AndroidTestOrchestrator.java index 23581da79..fc750234b 100644 --- a/runner/android_test_orchestrator/java/androidx/test/orchestrator/AndroidTestOrchestrator.java +++ b/runner/android_test_orchestrator/java/androidx/test/orchestrator/AndroidTestOrchestrator.java @@ -35,8 +35,10 @@ import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.InstrumentationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PermissionInfo; import android.os.Build; import android.os.Bundle; import android.os.Debug; @@ -60,7 +62,9 @@ import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -141,6 +145,8 @@ public final class AndroidTestOrchestrator extends android.app.Instrumentation private static final List RUNTIME_PERMISSIONS = Arrays.asList(permission.WRITE_EXTERNAL_STORAGE, permission.READ_EXTERNAL_STORAGE); + private final List systemDevelopmentPermissionCache = null; + private final OrchestrationResult.Builder resultBuilder = new OrchestrationResult.Builder(); private final OrchestrationResultPrinter resultPrinter = new OrchestrationResultPrinter(); private final OrchestrationListenerManager listenerManager = @@ -384,10 +390,93 @@ public void run() { getSecret(arguments), "pm", Arrays.asList("clear", getTargetInstrPackage(arguments))); + if (Build.VERSION.SDK_INT < 23) { + // pm clear revokes development permissions on API 23+. + // We have to do it manually on older platforms. + revokeDevelopmentPermissions(); + } } }); } + private void revokeDevelopmentPermissions() { + String targetPackage = getTargetPackage(arguments); + List permissions = collectGrantedDevelopmentPermissions(targetPackage); + revokePermissions(targetPackage, permissions); + + String targetInstrPackage = getTargetInstrPackage(arguments); + if (!targetInstrPackage.equals(targetPackage)) { + permissions = collectGrantedDevelopmentPermissions(targetInstrPackage); + revokePermissions(targetInstrPackage, permissions); + } + } + + private List collectGrantedDevelopmentPermissions(String packageName) { + try { + PackageManager pm = getContext().getPackageManager(); + PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS); + + List out = new ArrayList<>(); + String permission; + for (int i = 0, size = packageInfo.requestedPermissions.length; i < size; i++) { + if ((packageInfo.requestedPermissionsFlags[i] & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) { + permission = packageInfo.requestedPermissions[i]; + if (isDevelopmentPermission(permission)) { + out.add(permission); + } + } + } + return out; + } catch (PackageManager.NameNotFoundException ignore) { + return Collections.emptyList(); + } + } + + private boolean isDevelopmentPermission(String permission) { + // We're assuming system-defined permissions don't change. + if (getSystemDevelopmentPermissions().contains(permission)) { + return true; + } + // App-defined permissions can change any time apps get (un)installed. + try { + PermissionInfo permissionInfo = getContext().getPackageManager().getPermissionInfo(permission, 0); + return (permissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0; + } catch (PackageManager.NameNotFoundException ignore) { + return false; + } + } + + private List getSystemDevelopmentPermissions() { + List out = systemDevelopmentPermissionCache; + if (out == null) { + out = new ArrayList<>(); + try { + PackageInfo packageInfo = getContext().getPackageManager() + .getPackageInfo("android", PackageManager.GET_PERMISSIONS); + for (PermissionInfo permissionInfo : packageInfo.permissions) { + if ((permissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) { + out.add(permissionInfo.name); + } + } + } catch (PackageManager.NameNotFoundException ignore) { + } + systemDevelopmentPermissionCache = Collections.unmodifiableList(out); + } + return out; + } + + private void revokePermissions(String packageName, List permissions) { + if (permissions.isEmpty()) { + return; + } + Context context = getContext(); + PackageManager pm = context.getPackageManager(); + String secret = getSecret(arguments); + for (String permission : permissions) { + execShellCommandSync(context, secret, "pm", Arrays.asList("revoke", packageName, permission)); + } + } + @VisibleForTesting static String addTestCoverageSupport(Bundle args, String filename) { // Only do the aggregate coverage mode if coverage was requested AND we're running in isolation