Add support for checking whether feature is enabled on device

In case of AVF development most of the features will be protected for
build time flags, meaning that we need to provide a way for tests to
query whether device build actually has the feature enabled.

This is achieved by providing isFeatureEnabled @TestApi. As part of this
change I've also switched the payloadIsNotRoot test to use this new API
as an example of how the API can be used in tests.

Bug: 298012279
Bug: 298008232
Test: atest MicrodroidTests
Change-Id: If9afc013e178439f45627c1ac7dfe50242799f09
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 97151d7..1ddf129 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);
 }