virtiofs: Request permission to access storage

start the VM after the permission is granted.

If the permission is denied, then do not share
emulated storage.

Bug: 372171883
Test: Launch app with configured shared paths.

1: Grant permission

Verify paths are mounted in guest

2: Reject permission

Verify paths are not shared by crosvm.

Change-Id: Idbd4bf2f8d1ea0f75987bf6cad00be718b061d74
Signed-off-by: Akilesh Kailash <akailash@google.com>
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index af41c85..e278165 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -26,8 +26,11 @@
 import android.graphics.Bitmap;
 import android.graphics.drawable.Icon;
 import android.graphics.fonts.FontStyle;
+import android.net.Uri;
 import android.net.http.SslError;
 import android.os.Bundle;
+import android.os.Environment;
+import android.provider.Settings;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.Log;
@@ -44,6 +47,10 @@
 import android.webkit.WebViewClient;
 import android.widget.Toast;
 
+import androidx.activity.result.ActivityResult;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+
 import com.android.virtualization.vmlauncher.InstallUtils;
 import com.android.virtualization.vmlauncher.VmLauncherService;
 import com.android.virtualization.vmlauncher.VmLauncherServices;
@@ -81,6 +88,7 @@
     private WebView mWebView;
     private AccessibilityManager mAccessibilityManager;
     private static final int POST_NOTIFICATIONS_PERMISSION_REQUEST_CODE = 101;
+    private ActivityResultLauncher<Intent> manageExternalStorageActivityResultLauncher;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -111,12 +119,41 @@
         connectToTerminalService();
         readClientCertificate();
 
+        manageExternalStorageActivityResultLauncher =
+                registerForActivityResult(
+                        new ActivityResultContracts.StartActivityForResult(),
+                        (ActivityResult result) -> {
+                            if (Environment.isExternalStorageManager()) {
+                                Toast.makeText(this, "Storage permission set!", Toast.LENGTH_SHORT)
+                                        .show();
+                            } else {
+                                Toast.makeText(
+                                                this,
+                                                "Storage permission not set",
+                                                Toast.LENGTH_SHORT)
+                                        .show();
+                            }
+                            startVm();
+                        });
+
         // if installer is launched, it will be handled in onActivityResult
         if (!launchInstaller) {
-            startVm();
+            if (!Environment.isExternalStorageManager()) {
+                requestStoragePermissions(this, manageExternalStorageActivityResultLauncher);
+            } else {
+                startVm();
+            }
         }
     }
 
+    private void requestStoragePermissions(
+            Context context, ActivityResultLauncher<Intent> activityResultLauncher) {
+        Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
+        Uri uri = Uri.fromParts("package", context.getPackageName(), null);
+        intent.setData(uri);
+        activityResultLauncher.launch(intent);
+    }
+
     private URL getTerminalServiceUrl() {
         Configuration config = getResources().getConfiguration();
 
@@ -406,7 +443,11 @@
                 Log.e(TAG, "Failed to start VM. Installer returned error.");
                 finish();
             }
-            startVm();
+            if (!Environment.isExternalStorageManager()) {
+                requestStoragePermissions(this, manageExternalStorageActivityResultLauncher);
+            } else {
+                startVm();
+            }
         }
     }
 
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/ConfigJson.java b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/ConfigJson.java
index 5d6b13f..a259fe2 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/ConfigJson.java
+++ b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/ConfigJson.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.graphics.Rect;
+import android.os.Environment;
 import android.system.virtualmachine.VirtualMachineConfig;
 import android.system.virtualmachine.VirtualMachineCustomImageConfig;
 import android.system.virtualmachine.VirtualMachineCustomImageConfig.AudioConfig;
@@ -157,22 +158,40 @@
     private static class SharedPathJson {
         private SharedPathJson() {}
 
-        // Package ID of Terminal app.
-        private static final String TERMINAL_PACKAGE_ID =
-                "com.google.android.virtualization.terminal";
         private String sharedPath;
+        private static final int GUEST_UID = 1000;
+        private static final int GUEST_GID = 100;
 
         private SharedPath toConfig(Context context) {
             try {
-                int uid =
-                        context.getPackageManager()
-                                .getPackageUidAsUser(TERMINAL_PACKAGE_ID, context.getUserId());
-
-                return new SharedPath(sharedPath, uid, uid, 0, 0, 0007, "android", "android");
+                int terminalUid = getTerminalUid(context);
+                if (sharedPath.contains("emulated")) {
+                    if (Environment.isExternalStorageManager()) {
+                        int currentUserId = context.getUserId();
+                        String path = sharedPath + "/" + currentUserId + "/Download";
+                        return new SharedPath(
+                                path,
+                                terminalUid,
+                                terminalUid,
+                                GUEST_UID,
+                                GUEST_GID,
+                                0007,
+                                "android",
+                                "android");
+                    }
+                    return null;
+                }
+                return new SharedPath(
+                        sharedPath, terminalUid, terminalUid, 0, 0, 0007, "internal", "internal");
             } catch (NameNotFoundException e) {
                 return null;
             }
         }
+
+        private int getTerminalUid(Context context) throws NameNotFoundException {
+            return context.getPackageManager()
+                    .getPackageUidAsUser(context.getPackageName(), context.getUserId());
+        }
     }
 
     private static class InputJson {