Merge "Add support for checking whether feature is enabled on device" into main
diff --git a/javalib/api/test-current.txt b/javalib/api/test-current.txt
index cf95770..51c2223 100644
--- a/javalib/api/test-current.txt
+++ b/javalib/api/test-current.txt
@@ -17,5 +17,10 @@
     method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setVmConsoleInputSupported(boolean);
   }
 
+  public class VirtualMachineManager {
+    method @RequiresPermission(android.system.virtualmachine.VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION) public boolean isFeatureEnabled(String) throws android.system.virtualmachine.VirtualMachineException;
+    field public static final String FEATURE_PAYLOAD_NOT_ROOT = "com.android.kvm.PAYLOAD_NON_ROOT";
+  }
+
 }
 
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index b7ea22c..c4096da 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -23,11 +23,15 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
+import android.annotation.StringDef;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.annotation.WorkerThread;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.os.RemoteException;
 import android.sysprop.HypervisorProperties;
+import android.system.virtualizationservice.IVirtualizationService;
 import android.util.ArrayMap;
 
 import com.android.internal.annotations.GuardedBy;
@@ -96,6 +100,26 @@
     public static final int CAPABILITY_NON_PROTECTED_VM = 2;
 
     /**
+     * Features provided by {@link VirtualMachineManager}.
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef(
+            prefix = "FEATURE_",
+            value = {FEATURE_PAYLOAD_NOT_ROOT})
+    public @interface Features {}
+
+    /**
+     * Feature to run payload as non-root user.
+     *
+     * @hide
+     */
+    @TestApi
+    public static final String FEATURE_PAYLOAD_NOT_ROOT =
+            IVirtualizationService.FEATURE_PAYLOAD_NON_ROOT;
+
+    /**
      * Returns a set of flags indicating what this implementation of virtualization is capable of.
      *
      * @see #CAPABILITY_PROTECTED_VM
@@ -277,4 +301,22 @@
         }
         return null;
     }
+
+    /**
+     * Returns {@code true} if given {@code featureName} is enabled.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION)
+    public boolean isFeatureEnabled(@Features String featureName) throws VirtualMachineException {
+        synchronized (sCreateLock) {
+            VirtualizationService service = VirtualizationService.getInstance();
+            try {
+                return service.getBinder().isFeatureEnabled(featureName);
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        }
+    }
 }
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index a928dcf..d6183cf 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -72,7 +72,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.function.ThrowingRunnable;
@@ -1130,6 +1129,17 @@
         assertThrows(Exception.class, () -> launchVmAndGetCdis("test_vm"));
     }
 
+    @Test
+    public void isFeatureEnabled_requiresManagePermission() throws Exception {
+        revokePermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION);
+
+        VirtualMachineManager vmm = getVirtualMachineManager();
+        SecurityException e =
+                assertThrows(SecurityException.class, () -> vmm.isFeatureEnabled("whatever"));
+        assertThat(e)
+                .hasMessageThat()
+                .contains("android.permission.MANAGE_VIRTUAL_MACHINE permission");
+    }
 
     private static final UUID MICRODROID_PARTITION_UUID =
             UUID.fromString("cf9afe9a-0662-11ec-a329-c32663a09d75");
@@ -1524,11 +1534,15 @@
     }
 
     @Test
-    @Ignore // Figure out how to run this conditionally
     @CddTest(requirements = {"9.17/C-1-1"})
     public void payloadIsNotRoot() throws Exception {
         assumeSupportedDevice();
 
+        VirtualMachineManager vmm = getVirtualMachineManager();
+        assumeTrue(
+                VirtualMachineManager.FEATURE_PAYLOAD_NOT_ROOT + " not enabled",
+                vmm.isFeatureEnabled(VirtualMachineManager.FEATURE_PAYLOAD_NOT_ROOT));
+
         VirtualMachineConfig config =
                 newVmConfigBuilder()
                         .setPayloadBinaryName("MicrodroidTestNativeLib.so")
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index f5f2718..164977c 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -34,6 +34,7 @@
     IVirtualMachine::{BnVirtualMachine, IVirtualMachine},
     IVirtualMachineCallback::IVirtualMachineCallback,
     IVirtualizationService::IVirtualizationService,
+    IVirtualizationService::FEATURE_PAYLOAD_NON_ROOT,
     MemoryTrimLevel::MemoryTrimLevel,
     Partition::Partition,
     PartitionType::PartitionType,
@@ -264,6 +265,21 @@
         // Delegate to the global service, including checking the permission.
         GLOBAL_SERVICE.getAssignableDevices()
     }
+
+    /// Returns whether given feature is enabled
+    fn isFeatureEnabled(&self, feature: &str) -> binder::Result<bool> {
+        check_manage_access()?;
+
+        // This approach is quite cumbersome, but will do the work for the short term.
+        // TODO(b/298012279): make this scalable.
+        match feature {
+            FEATURE_PAYLOAD_NON_ROOT => Ok(cfg!(payload_not_root)),
+            _ => {
+                warn!("unknown feature {}", feature);
+                Ok(false)
+            }
+        }
+    }
 }
 
 impl VirtualizationService {
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
index df72e49..0ee958d 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
@@ -22,6 +22,8 @@
 import android.system.virtualizationservice.VirtualMachineDebugInfo;
 
 interface IVirtualizationService {
+    const String FEATURE_PAYLOAD_NON_ROOT = "com.android.kvm.PAYLOAD_NON_ROOT";
+
     /**
      * Create the VM with the given config file, and return a handle to it ready to start it. If
      * `consoleOutFd` is provided then console output from the VM will be sent to it. If
@@ -61,4 +63,7 @@
      * Get a list of assignable device types.
      */
     AssignableDevice[] getAssignableDevices();
+
+    /** Returns whether given feature is enabled. */
+    boolean isFeatureEnabled(in String feature);
 }