Grant legacy storage access to the test apps that request for it

Apps requesting full (legacy) external storage will only be allowed
legacy external storage if the app is targeting Q or lower.

Many CTS test apps (that are not testing storage workflow) are violating
the new Scoped Storage resctrictions (around direct file path access)
added by Android Storage team for R.
Since most of these CTS test apps target sdkcurrent (which is > Q) so
these cts tests will fail as we are enforcing storage restrictions in
R and apps cannot opt out of it.

This change allows legacy external storage for test apps (that request
for it) by giving them File Manager permissions (which have full
external storage access).

Bug: 142395442
Test: atest CtsDownloadManagerLegacy
Test: atest android.appsecurity.cts.ExternalStorageHostTest#testMultiUserStorageIsolated
Test: atest android.os.cts.FileAccessPermissionTest#testExternalStorageAccess
Change-Id: I45d37d3d0d2202e3670eb86519f2ce31d23dd662
diff --git a/src/com/android/tradefed/device/TestDevice.java b/src/com/android/tradefed/device/TestDevice.java
index 8d5c71e..d1c5b0b 100644
--- a/src/com/android/tradefed/device/TestDevice.java
+++ b/src/com/android/tradefed/device/TestDevice.java
@@ -27,6 +27,7 @@
 import com.android.tradefed.result.ByteArrayInputStreamSource;
 import com.android.tradefed.result.FileInputStreamSource;
 import com.android.tradefed.result.InputStreamSource;
+import com.android.tradefed.util.AaptParser;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
 import com.android.tradefed.util.KeyguardControllerState;
@@ -194,6 +195,9 @@
                 packageFile.getAbsolutePath(), extraArgs.toString(), getSerialNumber());
         performDeviceAction(String.format("install %s", packageFile.getAbsolutePath()),
                 installAction, MAX_RETRY_ATTEMPTS);
+        List<File> packageFiles = new ArrayList();
+        packageFiles.add(packageFile);
+        allowLegacyStorageForApps(packageFiles);
         return response[0];
     }
 
@@ -332,6 +336,9 @@
                 };
         performDeviceAction(String.format("install %s", packageFile.getAbsolutePath()),
                 installAction, MAX_RETRY_ATTEMPTS);
+        List<File> packageFiles = new ArrayList();
+        packageFiles.add(packageFile);
+        allowLegacyStorageForApps(packageFiles);
         return response[0];
     }
 
@@ -404,9 +411,45 @@
                 String.format("install %s", packageFiles.toString()),
                 installAction,
                 MAX_RETRY_ATTEMPTS);
+        allowLegacyStorageForApps(packageFiles);
         return response[0];
     }
 
+    /**
+     * Allows Legacy External Storage access for apps that request for it.
+     *
+     * <p>Apps that request for legacy external storage access are granted the access by setting
+     * MANAGE_EXTERNAL_STORAGE App Op. This gives the app File manager privileges, File managers
+     * have legacy external storage access.
+     *
+     * @param appFiles List of Files. Apk Files of the apps that are installed.
+     */
+    private void allowLegacyStorageForApps(List<File> appFiles) throws DeviceNotAvailableException {
+        for (File appFile : appFiles) {
+            AaptParser aaptParser = AaptParser.parse(appFile);
+            if (aaptParser != null && aaptParser.isRequestingLegacyStorage()) {
+                // Set the MANAGE_EXTERNAL_STORAGE App Op to MODE_ALLOWED (Code = 0)
+                // for all users.
+                ArrayList<Integer> userIds = listUsers();
+                for (int userId : userIds) {
+                    CommandResult setFileManagerAppOpResult =
+                            executeShellV2Command(
+                                    "appops set --user "
+                                            + userId
+                                            + " --uid "
+                                            + aaptParser.getPackageName()
+                                            + " MANAGE_EXTERNAL_STORAGE 0");
+                    if (!CommandStatus.SUCCESS.equals(setFileManagerAppOpResult.getStatus())) {
+                        CLog.e(
+                                "Failed to set MANAGE_EXTERNAL_STORAGE App Op to"
+                                        + " allow legacy external storage for: %s ; stderr: %s",
+                                aaptParser.getPackageName(), setFileManagerAppOpResult.getStderr());
+                    }
+                }
+            }
+        }
+    }
+
     /** {@inheritDoc} */
     @Override
     public String installPackages(
diff --git a/src/com/android/tradefed/util/AaptParser.java b/src/com/android/tradefed/util/AaptParser.java
index 58a8b39..78182ef 100644
--- a/src/com/android/tradefed/util/AaptParser.java
+++ b/src/com/android/tradefed/util/AaptParser.java
@@ -41,6 +41,9 @@
     private static final Pattern NATIVE_CODE_PATTERN =
             Pattern.compile("native-code: '(.*?)'( '.*?')*");
 
+    private static final Pattern REQUEST_LEGACY_STORAGE_PATTERN =
+            Pattern.compile("requestLegacyExternalStorage.*=\\(.*\\)(.*)", Pattern.MULTILINE);
+
     private static final Pattern ALT_NATIVE_CODE_PATTERN =
             Pattern.compile("alt-native-code: '(.*)'");
     private static final int AAPT_TIMEOUT_MS = 60000;
@@ -52,6 +55,7 @@
     private List<String> mNativeCode = new ArrayList<>();
     private String mLabel;
     private int mSdkVersion = INVALID_SDK;
+    private boolean mRequestLegacyStorage = false;
 
     // @VisibleForTesting
     AaptParser() {
@@ -96,6 +100,16 @@
         return false;
     }
 
+    boolean parseXmlTree(String aaptOut) {
+        Matcher m = REQUEST_LEGACY_STORAGE_PATTERN.matcher(aaptOut);
+        if (m.find()) {
+            // 0xffffffff is true and 0x0 is false
+            mRequestLegacyStorage = m.group(1).equals("0xffffffff");
+        }
+        // REQUEST_LEGACY_STORAGE_PATTERN may or may not be present
+        return true;
+    }
+
     /**
      * Parse info from the apk.
      *
@@ -118,17 +132,38 @@
         if (stderr != null && !stderr.isEmpty()) {
             CLog.e("aapt dump badging stderr: %s", stderr);
         }
-
-        if (CommandStatus.SUCCESS.equals(result.getStatus())) {
-            AaptParser p = new AaptParser();
-            if (p.parse(result.getStdout()))
-                return p;
+        AaptParser p = new AaptParser();
+        if (!CommandStatus.SUCCESS.equals(result.getStatus()) || !p.parse(result.getStdout())) {
+            CLog.e(
+                    "Failed to run aapt on %s. stdout: %s",
+                    apkFile.getAbsoluteFile(), result.getStdout());
             return null;
         }
-        CLog.e(
-                "Failed to run aapt on %s. stdout: %s",
-                apkFile.getAbsoluteFile(), result.getStdout());
-        return null;
+        result =
+                RunUtil.getDefault()
+                        .runTimedCmdRetry(
+                                AAPT_TIMEOUT_MS,
+                                0L,
+                                2,
+                                "aapt",
+                                "dump",
+                                "xmltree",
+                                apkFile.getAbsolutePath(),
+                                "AndroidManifest.xml");
+
+        stderr = result.getStderr();
+        if (stderr != null && !stderr.isEmpty()) {
+            CLog.e("aapt dump xmltree AndroidManifest.xml stderr: %s", stderr);
+        }
+
+        if (!CommandStatus.SUCCESS.equals(result.getStatus())
+                || !p.parseXmlTree(result.getStdout())) {
+            CLog.e(
+                    "Failed to run aapt on %s. stdout: %s",
+                    apkFile.getAbsoluteFile(), result.getStdout());
+            return null;
+        }
+        return p;
     }
 
     public String getPackageName() {
@@ -154,4 +189,13 @@
     public int getSdkVersion() {
         return mSdkVersion;
     }
+
+    /**
+     * Check if the app is requesting legacy storage.
+     *
+     * @return boolean return true if requestLegacyExternalStorage is true in AndroidManifest.xml
+     */
+    public boolean isRequestingLegacyStorage() {
+        return mRequestLegacyStorage;
+    }
 }