1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.system.virtualmachine;
18 
19 import static android.os.ParcelFileDescriptor.AutoCloseInputStream;
20 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
21 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
22 
23 import static java.util.Objects.requireNonNull;
24 
25 import android.Manifest;
26 import android.annotation.FlaggedApi;
27 import android.annotation.IntDef;
28 import android.annotation.IntRange;
29 import android.annotation.NonNull;
30 import android.annotation.Nullable;
31 import android.annotation.RequiresPermission;
32 import android.annotation.StringDef;
33 import android.annotation.SystemApi;
34 import android.annotation.TestApi;
35 import android.content.Context;
36 import android.content.pm.ApplicationInfo;
37 import android.content.pm.PackageManager;
38 import android.os.Build;
39 import android.os.ParcelFileDescriptor;
40 import android.os.PersistableBundle;
41 import android.sysprop.HypervisorProperties;
42 import android.system.virtualizationservice.DiskImage;
43 import android.system.virtualizationservice.Partition;
44 import android.system.virtualizationservice.VirtualMachineAppConfig;
45 import android.system.virtualizationservice.VirtualMachinePayloadConfig;
46 import android.system.virtualizationservice.VirtualMachineRawConfig;
47 import android.text.TextUtils;
48 import android.util.Log;
49 
50 import com.android.system.virtualmachine.flags.Flags;
51 
52 import java.io.File;
53 import java.io.FileInputStream;
54 import java.io.FileNotFoundException;
55 import java.io.FileOutputStream;
56 import java.io.IOException;
57 import java.io.InputStream;
58 import java.io.OutputStream;
59 import java.lang.annotation.Retention;
60 import java.lang.annotation.RetentionPolicy;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.Collections;
64 import java.util.List;
65 import java.util.Objects;
66 import java.util.Optional;
67 import java.util.zip.ZipFile;
68 
69 /**
70  * Represents a configuration of a virtual machine. A configuration consists of hardware
71  * configurations like the number of CPUs and the size of RAM, and software configurations like the
72  * payload to run on the virtual machine.
73  *
74  * @hide
75  */
76 @SystemApi
77 public final class VirtualMachineConfig {
78     private static final String TAG = "VirtualMachineConfig";
79 
80     private static String[] EMPTY_STRING_ARRAY = {};
81     private static final String U_BOOT_PREBUILT_PATH = "/apex/com.android.virt/etc/u-boot.bin";
82 
83     // These define the schema of the config file persisted on disk.
84     // Please bump up the version number when adding a new key.
85     private static final int VERSION = 9;
86     private static final String KEY_VERSION = "version";
87     private static final String KEY_PACKAGENAME = "packageName";
88     private static final String KEY_APKPATH = "apkPath";
89     private static final String KEY_PAYLOADCONFIGPATH = "payloadConfigPath";
90     private static final String KEY_CUSTOMIMAGECONFIG = "customImageConfig";
91     private static final String KEY_PAYLOADBINARYNAME = "payloadBinaryPath";
92     private static final String KEY_DEBUGLEVEL = "debugLevel";
93     private static final String KEY_PROTECTED_VM = "protectedVm";
94     private static final String KEY_MEMORY_BYTES = "memoryBytes";
95     private static final String KEY_CPU_TOPOLOGY = "cpuTopology";
96     private static final String KEY_CONSOLE_INPUT_DEVICE = "consoleInputDevice";
97     private static final String KEY_ENCRYPTED_STORAGE_BYTES = "encryptedStorageBytes";
98     private static final String KEY_VM_OUTPUT_CAPTURED = "vmOutputCaptured";
99     private static final String KEY_VM_CONSOLE_INPUT_SUPPORTED = "vmConsoleInputSupported";
100     private static final String KEY_CONNECT_VM_CONSOLE = "connectVmConsole";
101     private static final String KEY_VENDOR_DISK_IMAGE_PATH = "vendorDiskImagePath";
102     private static final String KEY_OS = "os";
103     private static final String KEY_EXTRA_APKS = "extraApks";
104     private static final String KEY_NETWORK_SUPPORTED = "networkSupported";
105     private static final String KEY_SHOULD_BOOST_UCLAMP = "shouldBoostUclamp";
106 
107     /** @hide */
108     @Retention(RetentionPolicy.SOURCE)
109     @IntDef(prefix = "DEBUG_LEVEL_", value = {
110             DEBUG_LEVEL_NONE,
111             DEBUG_LEVEL_FULL
112     })
113     public @interface DebugLevel {}
114 
115     /**
116      * Not debuggable at all. No log is exported from the VM. Debugger can't be attached to the app
117      * process running in the VM. This is the default level.
118      *
119      * @hide
120      */
121     @SystemApi public static final int DEBUG_LEVEL_NONE = 0;
122 
123     /**
124      * Fully debuggable. All logs (both logcat and kernel message) are exported. All processes
125      * running in the VM can be attached to the debugger. Rooting is possible.
126      *
127      * @hide
128      */
129     @SystemApi public static final int DEBUG_LEVEL_FULL = 1;
130 
131     /** @hide */
132     @Retention(RetentionPolicy.SOURCE)
133     @IntDef(
134             prefix = "CPU_TOPOLOGY_",
135             value = {
136                 CPU_TOPOLOGY_ONE_CPU,
137                 CPU_TOPOLOGY_MATCH_HOST,
138             })
139     public @interface CpuTopology {}
140 
141     /**
142      * Run VM with 1 vCPU. This is the default option, usually the fastest to boot and consuming the
143      * least amount of resources. Typically the best option for small or ephemeral workloads.
144      *
145      * @hide
146      */
147     @SystemApi public static final int CPU_TOPOLOGY_ONE_CPU = 0;
148 
149     /**
150      * Run VM with vCPU topology matching the physical CPU topology of the host. Usually takes
151      * longer to boot and consumes more resources compared to a single vCPU. Typically a good option
152      * for long-running workloads that benefit from parallel execution.
153      *
154      * @hide
155      */
156     @SystemApi public static final int CPU_TOPOLOGY_MATCH_HOST = 1;
157 
158     /** Name of a package whose primary APK contains the VM payload. */
159     @Nullable private final String mPackageName;
160 
161     /** Absolute path to the APK file containing the VM payload. */
162     @Nullable private final String mApkPath;
163 
164     private final List<String> mExtraApks;
165 
166     @DebugLevel private final int mDebugLevel;
167 
168     /**
169      * Whether to run the VM in protected mode, so the host can't access its memory.
170      */
171     private final boolean mProtectedVm;
172 
173     /**
174      * The amount of RAM to give the VM, in bytes. If this is 0 or negative the default will be
175      * used.
176      */
177     private final long mMemoryBytes;
178 
179     /** CPU topology configuration of the VM. */
180     @CpuTopology private final int mCpuTopology;
181 
182     /** The serial device for VM console input. */
183     @Nullable private final String mConsoleInputDevice;
184 
185     /**
186      * Path within the APK to the payload config file that defines software aspects of the VM.
187      */
188     @Nullable private final String mPayloadConfigPath;
189 
190     /** Name of the payload binary file within the APK that will be executed within the VM. */
191     @Nullable private final String mPayloadBinaryName;
192 
193     /** The custom image config file to launch the custom VM. */
194     @Nullable private final VirtualMachineCustomImageConfig mCustomImageConfig;
195 
196     /** The size of storage in bytes. 0 indicates that encryptedStorage is not required */
197     private final long mEncryptedStorageBytes;
198 
199     /** Whether the app can read console and log output. */
200     private final boolean mVmOutputCaptured;
201 
202     /** Whether the app can write console input to the VM */
203     private final boolean mVmConsoleInputSupported;
204 
205     /** Whether to connect the VM console to a host console. */
206     private final boolean mConnectVmConsole;
207 
208     @Nullable private final File mVendorDiskImage;
209 
210     /** OS name of the VM using payload binaries. */
211     @NonNull @OsName private final String mOs;
212 
213     /** Whether to run the VM with supporting network feature or not. */
214     private final boolean mNetworkSupported;
215 
216     private final boolean mShouldBoostUclamp;
217 
218     @Retention(RetentionPolicy.SOURCE)
219     @StringDef(
220             prefix = "MICRODROID",
221             value = {MICRODROID})
222     private @interface OsName {}
223 
224     /**
225      * OS name of microdroid using microdroid kernel.
226      *
227      * @see Builder#setOs
228      * @hide
229      */
230     @TestApi
231     @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
232     @OsName
233     public static final String MICRODROID = "microdroid";
234 
VirtualMachineConfig( @ullable String packageName, @Nullable String apkPath, List<String> extraApks, @Nullable String payloadConfigPath, @Nullable String payloadBinaryName, @Nullable VirtualMachineCustomImageConfig customImageConfig, @DebugLevel int debugLevel, boolean protectedVm, long memoryBytes, @CpuTopology int cpuTopology, @Nullable String consoleInputDevice, long encryptedStorageBytes, boolean vmOutputCaptured, boolean vmConsoleInputSupported, boolean connectVmConsole, @Nullable File vendorDiskImage, @NonNull @OsName String os, boolean networkSupported, boolean shouldBoostUclamp)235     private VirtualMachineConfig(
236             @Nullable String packageName,
237             @Nullable String apkPath,
238             List<String> extraApks,
239             @Nullable String payloadConfigPath,
240             @Nullable String payloadBinaryName,
241             @Nullable VirtualMachineCustomImageConfig customImageConfig,
242             @DebugLevel int debugLevel,
243             boolean protectedVm,
244             long memoryBytes,
245             @CpuTopology int cpuTopology,
246             @Nullable String consoleInputDevice,
247             long encryptedStorageBytes,
248             boolean vmOutputCaptured,
249             boolean vmConsoleInputSupported,
250             boolean connectVmConsole,
251             @Nullable File vendorDiskImage,
252             @NonNull @OsName String os,
253             boolean networkSupported,
254             boolean shouldBoostUclamp) {
255         // This is only called from Builder.build(); the builder handles parameter validation.
256         mPackageName = packageName;
257         mApkPath = apkPath;
258         mExtraApks =
259                 extraApks.isEmpty()
260                         ? Collections.emptyList()
261                         : Collections.unmodifiableList(
262                                 Arrays.asList(extraApks.toArray(new String[0])));
263         mPayloadConfigPath = payloadConfigPath;
264         mPayloadBinaryName = payloadBinaryName;
265         mCustomImageConfig = customImageConfig;
266         mDebugLevel = debugLevel;
267         mProtectedVm = protectedVm;
268         mMemoryBytes = memoryBytes;
269         mCpuTopology = cpuTopology;
270         mConsoleInputDevice = consoleInputDevice;
271         mEncryptedStorageBytes = encryptedStorageBytes;
272         mVmOutputCaptured = vmOutputCaptured;
273         mVmConsoleInputSupported = vmConsoleInputSupported;
274         mConnectVmConsole = connectVmConsole;
275         mVendorDiskImage = vendorDiskImage;
276         mOs = os;
277         mNetworkSupported = networkSupported;
278         mShouldBoostUclamp = shouldBoostUclamp;
279     }
280 
281     /** Loads a config from a file. */
282     @NonNull
from(@onNull File file)283     static VirtualMachineConfig from(@NonNull File file) throws VirtualMachineException {
284         try (FileInputStream input = new FileInputStream(file)) {
285             return fromInputStream(input);
286         } catch (IOException e) {
287             throw new VirtualMachineException("Failed to read VM config from file", e);
288         }
289     }
290 
291     /** Loads a config from a {@link ParcelFileDescriptor}. */
292     @NonNull
from(@onNull ParcelFileDescriptor fd)293     static VirtualMachineConfig from(@NonNull ParcelFileDescriptor fd)
294             throws VirtualMachineException {
295         try (AutoCloseInputStream input = new AutoCloseInputStream(fd)) {
296             return fromInputStream(input);
297         } catch (IOException e) {
298             throw new VirtualMachineException("failed to read VM config from file descriptor", e);
299         }
300     }
301 
302     /** Loads a config from a stream, for example a file. */
303     @NonNull
fromInputStream(@onNull InputStream input)304     private static VirtualMachineConfig fromInputStream(@NonNull InputStream input)
305             throws IOException, VirtualMachineException {
306         PersistableBundle b = PersistableBundle.readFromStream(input);
307         try {
308             return fromPersistableBundle(b);
309         } catch (NullPointerException | IllegalArgumentException | IllegalStateException e) {
310             throw new VirtualMachineException("Persisted VM config is invalid", e);
311         }
312     }
313 
314     @NonNull
fromPersistableBundle(PersistableBundle b)315     private static VirtualMachineConfig fromPersistableBundle(PersistableBundle b) {
316         int version = b.getInt(KEY_VERSION);
317         if (version > VERSION) {
318             throw new IllegalArgumentException(
319                     "Version " + version + " too high; current is " + VERSION);
320         }
321 
322         String packageName = b.getString(KEY_PACKAGENAME);
323         Builder builder = new Builder(packageName);
324 
325         String apkPath = b.getString(KEY_APKPATH);
326         if (apkPath != null) {
327             builder.setApkPath(apkPath);
328         }
329 
330         String payloadConfigPath = b.getString(KEY_PAYLOADCONFIGPATH);
331         String payloadBinaryName = b.getString(KEY_PAYLOADBINARYNAME);
332         PersistableBundle customImageConfigBundle = b.getPersistableBundle(KEY_CUSTOMIMAGECONFIG);
333         if (customImageConfigBundle != null) {
334             builder.setCustomImageConfig(
335                     VirtualMachineCustomImageConfig.from(customImageConfigBundle));
336         } else if (payloadConfigPath != null) {
337             builder.setPayloadConfigPath(payloadConfigPath);
338         } else {
339             builder.setPayloadBinaryName(payloadBinaryName);
340         }
341 
342         @DebugLevel int debugLevel = b.getInt(KEY_DEBUGLEVEL);
343         if (debugLevel != DEBUG_LEVEL_NONE && debugLevel != DEBUG_LEVEL_FULL) {
344             throw new IllegalArgumentException("Invalid debugLevel: " + debugLevel);
345         }
346         builder.setDebugLevel(debugLevel);
347         builder.setProtectedVm(b.getBoolean(KEY_PROTECTED_VM));
348         long memoryBytes = b.getLong(KEY_MEMORY_BYTES);
349         if (memoryBytes != 0) {
350             builder.setMemoryBytes(memoryBytes);
351         }
352         builder.setCpuTopology(b.getInt(KEY_CPU_TOPOLOGY));
353         String consoleInputDevice = b.getString(KEY_CONSOLE_INPUT_DEVICE);
354         if (consoleInputDevice != null) {
355             builder.setConsoleInputDevice(consoleInputDevice);
356         }
357         long encryptedStorageBytes = b.getLong(KEY_ENCRYPTED_STORAGE_BYTES);
358         if (encryptedStorageBytes != 0) {
359             builder.setEncryptedStorageBytes(encryptedStorageBytes);
360         }
361         builder.setVmOutputCaptured(b.getBoolean(KEY_VM_OUTPUT_CAPTURED));
362         builder.setVmConsoleInputSupported(b.getBoolean(KEY_VM_CONSOLE_INPUT_SUPPORTED));
363         builder.setConnectVmConsole(b.getBoolean(KEY_CONNECT_VM_CONSOLE));
364 
365         String vendorDiskImagePath = b.getString(KEY_VENDOR_DISK_IMAGE_PATH);
366         if (vendorDiskImagePath != null) {
367             builder.setVendorDiskImage(new File(vendorDiskImagePath));
368         }
369 
370         builder.setOs(b.getString(KEY_OS));
371 
372         String[] extraApks = b.getStringArray(KEY_EXTRA_APKS);
373         if (extraApks != null) {
374             for (String extraApk : extraApks) {
375                 builder.addExtraApk(extraApk);
376             }
377         }
378 
379         builder.setNetworkSupported(b.getBoolean(KEY_NETWORK_SUPPORTED));
380 
381         builder.setShouldBoostUclamp(b.getBoolean(KEY_SHOULD_BOOST_UCLAMP));
382         return builder.build();
383     }
384 
385     /** Persists this config to a file. */
serialize(@onNull File file)386     void serialize(@NonNull File file) throws VirtualMachineException {
387         try (FileOutputStream output = new FileOutputStream(file)) {
388             serializeOutputStream(output);
389         } catch (IOException e) {
390             throw new VirtualMachineException("failed to write VM config", e);
391         }
392     }
393 
394     /** Persists this config to a stream, for example a file. */
serializeOutputStream(@onNull OutputStream output)395     private void serializeOutputStream(@NonNull OutputStream output) throws IOException {
396         PersistableBundle b = new PersistableBundle();
397         b.putInt(KEY_VERSION, VERSION);
398         if (mPackageName != null) {
399             b.putString(KEY_PACKAGENAME, mPackageName);
400         }
401         if (mApkPath != null) {
402             b.putString(KEY_APKPATH, mApkPath);
403         }
404         b.putString(KEY_PAYLOADCONFIGPATH, mPayloadConfigPath);
405         b.putString(KEY_PAYLOADBINARYNAME, mPayloadBinaryName);
406         if (mCustomImageConfig != null) {
407             b.putPersistableBundle(KEY_CUSTOMIMAGECONFIG, mCustomImageConfig.toPersistableBundle());
408         }
409         b.putInt(KEY_DEBUGLEVEL, mDebugLevel);
410         b.putBoolean(KEY_PROTECTED_VM, mProtectedVm);
411         b.putInt(KEY_CPU_TOPOLOGY, mCpuTopology);
412         if (mConsoleInputDevice != null) {
413             b.putString(KEY_CONSOLE_INPUT_DEVICE, mConsoleInputDevice);
414         }
415         if (mMemoryBytes > 0) {
416             b.putLong(KEY_MEMORY_BYTES, mMemoryBytes);
417         }
418         if (mEncryptedStorageBytes > 0) {
419             b.putLong(KEY_ENCRYPTED_STORAGE_BYTES, mEncryptedStorageBytes);
420         }
421         b.putBoolean(KEY_VM_OUTPUT_CAPTURED, mVmOutputCaptured);
422         b.putBoolean(KEY_VM_CONSOLE_INPUT_SUPPORTED, mVmConsoleInputSupported);
423         b.putBoolean(KEY_CONNECT_VM_CONSOLE, mConnectVmConsole);
424         if (mVendorDiskImage != null) {
425             b.putString(KEY_VENDOR_DISK_IMAGE_PATH, mVendorDiskImage.getAbsolutePath());
426         }
427         b.putString(KEY_OS, mOs);
428         if (!mExtraApks.isEmpty()) {
429             String[] extraApks = mExtraApks.toArray(new String[0]);
430             b.putStringArray(KEY_EXTRA_APKS, extraApks);
431         }
432         b.putBoolean(KEY_NETWORK_SUPPORTED, mNetworkSupported);
433         b.putBoolean(KEY_SHOULD_BOOST_UCLAMP, mShouldBoostUclamp);
434         b.writeToStream(output);
435     }
436 
437     /**
438      * Returns the absolute path of the APK which should contain the binary payload that will
439      * execute within the VM. Returns null if no specific path has been set.
440      *
441      * @hide
442      */
443     @SystemApi
444     @Nullable
getApkPath()445     public String getApkPath() {
446         return mApkPath;
447     }
448 
449     /**
450      * Returns the package names of any extra APKs that have been requested for the VM. They are
451      * returned in the order in which they were added via {@link Builder#addExtraApk}.
452      *
453      * @hide
454      */
455     @TestApi
456     @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
457     @NonNull
getExtraApks()458     public List<String> getExtraApks() {
459         return mExtraApks;
460     }
461 
462     /**
463      * Returns the path within the APK to the payload config file that defines software aspects of
464      * the VM.
465      *
466      * @hide
467      */
468     @TestApi
469     @Nullable
getPayloadConfigPath()470     public String getPayloadConfigPath() {
471         return mPayloadConfigPath;
472     }
473 
474     /**
475      * Returns the custom image config to launch the custom VM.
476      *
477      * @hide
478      */
479     @Nullable
getCustomImageConfig()480     public VirtualMachineCustomImageConfig getCustomImageConfig() {
481         return mCustomImageConfig;
482     }
483 
484     /**
485      * Returns the name of the payload binary file, in the {@code lib/<ABI>} directory of the APK,
486      * that will be executed within the VM.
487      *
488      * @hide
489      */
490     @SystemApi
491     @Nullable
getPayloadBinaryName()492     public String getPayloadBinaryName() {
493         return mPayloadBinaryName;
494     }
495 
496     /**
497      * Returns the debug level for the VM.
498      *
499      * @hide
500      */
501     @SystemApi
502     @DebugLevel
getDebugLevel()503     public int getDebugLevel() {
504         return mDebugLevel;
505     }
506 
507     /**
508      * Returns whether the VM's memory will be protected from the host.
509      *
510      * @hide
511      */
512     @SystemApi
isProtectedVm()513     public boolean isProtectedVm() {
514         return mProtectedVm;
515     }
516 
517     /**
518      * Returns the amount of RAM that will be made available to the VM, or 0 if the default size
519      * will be used.
520      *
521      * @hide
522      */
523     @SystemApi
524     @IntRange(from = 0)
getMemoryBytes()525     public long getMemoryBytes() {
526         return mMemoryBytes;
527     }
528 
529     /**
530      * Returns the CPU topology configuration of the VM.
531      *
532      * @hide
533      */
534     @SystemApi
535     @CpuTopology
getCpuTopology()536     public int getCpuTopology() {
537         return mCpuTopology;
538     }
539 
540     /**
541      * Returns whether encrypted storage is enabled or not.
542      *
543      * @hide
544      */
545     @SystemApi
isEncryptedStorageEnabled()546     public boolean isEncryptedStorageEnabled() {
547         return mEncryptedStorageBytes > 0;
548     }
549 
550     /**
551      * Returns the size of encrypted storage (in bytes) available in the VM, or 0 if encrypted
552      * storage is not enabled
553      *
554      * @hide
555      */
556     @SystemApi
557     @IntRange(from = 0)
getEncryptedStorageBytes()558     public long getEncryptedStorageBytes() {
559         return mEncryptedStorageBytes;
560     }
561 
562     /**
563      * Returns whether the app can read the VM console or log output. If not, the VM output is
564      * automatically forwarded to the host logcat.
565      *
566      * @see Builder#setVmOutputCaptured
567      * @hide
568      */
569     @SystemApi
isVmOutputCaptured()570     public boolean isVmOutputCaptured() {
571         return mVmOutputCaptured;
572     }
573 
574     /**
575      * Returns whether the app can write to the VM console.
576      *
577      * @see Builder#setVmConsoleInputSupported
578      * @hide
579      */
580     @TestApi
isVmConsoleInputSupported()581     public boolean isVmConsoleInputSupported() {
582         return mVmConsoleInputSupported;
583     }
584 
585     /**
586      * Returns whether to connect the VM console to a host console.
587      *
588      * @see Builder#setConnectVmConsole
589      * @hide
590      */
isConnectVmConsole()591     public boolean isConnectVmConsole() {
592         return mConnectVmConsole;
593     }
594 
595     /**
596      * Returns the OS of the VM.
597      *
598      * @see Builder#setOs
599      * @hide
600      */
601     @TestApi
602     @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
603     @NonNull
604     @OsName
getOs()605     public String getOs() {
606         return mOs;
607     }
608 
609     /**
610      * Returns whether the network feature is supported to the VM or not.
611      *
612      * @hide
613      */
614     @TestApi
isNetworkSupported()615     public boolean isNetworkSupported() {
616         return mNetworkSupported;
617     }
618 
619     /**
620      * Tests if this config is compatible with other config. Being compatible means that the configs
621      * can be interchangeably used for the same virtual machine; they do not change the VM identity
622      * or secrets. Such changes include varying the number of CPUs or the size of the RAM. Changes
623      * that would alter the identity of the VM (e.g. using a different payload or changing the debug
624      * mode) are considered incompatible.
625      *
626      * @see VirtualMachine#setConfig
627      * @hide
628      */
629     @SystemApi
isCompatibleWith(@onNull VirtualMachineConfig other)630     public boolean isCompatibleWith(@NonNull VirtualMachineConfig other) {
631         if (this == other) {
632             return true;
633         }
634         return this.mDebugLevel == other.mDebugLevel
635                 && this.mProtectedVm == other.mProtectedVm
636                 && this.mEncryptedStorageBytes == other.mEncryptedStorageBytes
637                 && this.mVmOutputCaptured == other.mVmOutputCaptured
638                 && this.mVmConsoleInputSupported == other.mVmConsoleInputSupported
639                 && this.mConnectVmConsole == other.mConnectVmConsole
640                 && this.mConsoleInputDevice == other.mConsoleInputDevice
641                 && (this.mVendorDiskImage == null) == (other.mVendorDiskImage == null)
642                 && Objects.equals(this.mPayloadConfigPath, other.mPayloadConfigPath)
643                 && Objects.equals(this.mPayloadBinaryName, other.mPayloadBinaryName)
644                 && Objects.equals(this.mPackageName, other.mPackageName)
645                 && Objects.equals(this.mOs, other.mOs)
646                 && Objects.equals(this.mExtraApks, other.mExtraApks);
647     }
648 
openOrNull(File file, int mode)649     private ParcelFileDescriptor openOrNull(File file, int mode) {
650         try {
651             return ParcelFileDescriptor.open(file, mode);
652         } catch (FileNotFoundException e) {
653             Log.d(TAG, "cannot open", e);
654             return null;
655         }
656     }
657 
toVsRawConfig()658     VirtualMachineRawConfig toVsRawConfig() throws IllegalStateException, IOException {
659         VirtualMachineRawConfig config = new VirtualMachineRawConfig();
660         VirtualMachineCustomImageConfig customImageConfig = getCustomImageConfig();
661         requireNonNull(customImageConfig);
662         config.name = Optional.ofNullable(customImageConfig.getName()).orElse("");
663         config.instanceId = new byte[64];
664         config.kernel =
665                 Optional.ofNullable(customImageConfig.getKernelPath())
666                         .map(
667                                 (path) -> {
668                                     try {
669                                         return ParcelFileDescriptor.open(
670                                                 new File(path), MODE_READ_ONLY);
671                                     } catch (FileNotFoundException e) {
672                                         throw new RuntimeException(e);
673                                     }
674                                 })
675                         .orElse(null);
676 
677         config.initrd =
678                 Optional.ofNullable(customImageConfig.getInitrdPath())
679                         .map((path) -> openOrNull(new File(path), MODE_READ_ONLY))
680                         .orElse(null);
681         config.bootloader =
682                 Optional.ofNullable(customImageConfig.getBootloaderPath())
683                         .map((path) -> openOrNull(new File(path), MODE_READ_ONLY))
684                         .orElse(null);
685 
686         if (config.kernel == null && config.bootloader == null) {
687             config.bootloader = openOrNull(new File(U_BOOT_PREBUILT_PATH), MODE_READ_ONLY);
688         }
689 
690         config.params =
691                 Optional.ofNullable(customImageConfig.getParams())
692                         .map((params) -> TextUtils.join(" ", params))
693                         .orElse("");
694         config.disks =
695                 new DiskImage
696                         [Optional.ofNullable(customImageConfig.getDisks())
697                                 .map(arr -> arr.length)
698                                 .orElse(0)];
699         for (int i = 0; i < config.disks.length; i++) {
700             config.disks[i] = new DiskImage();
701             config.disks[i].writable = customImageConfig.getDisks()[i].isWritable();
702 
703             config.disks[i].image =
704                     ParcelFileDescriptor.open(
705                             new File(customImageConfig.getDisks()[i].getImagePath()),
706                             config.disks[i].writable ? MODE_READ_WRITE : MODE_READ_ONLY);
707             config.disks[i].partitions = new Partition[0];
708         }
709 
710         config.displayConfig =
711                 Optional.ofNullable(customImageConfig.getDisplayConfig())
712                         .map(dc -> dc.toParcelable())
713                         .orElse(null);
714         config.gpuConfig =
715                 Optional.ofNullable(customImageConfig.getGpuConfig())
716                         .map(dc -> dc.toParcelable())
717                         .orElse(null);
718         config.protectedVm = this.mProtectedVm;
719         config.memoryMib = bytesToMebiBytes(mMemoryBytes);
720         config.cpuTopology = (byte) this.mCpuTopology;
721         config.consoleInputDevice = mConsoleInputDevice;
722         config.devices = EMPTY_STRING_ARRAY;
723         config.networkSupported = this.mNetworkSupported;
724         config.platformVersion = "~1.0";
725         return config;
726     }
727 
728     /**
729      * Converts this config object into the parcelable type used when creating a VM via the
730      * virtualization service. Notice that the files are not passed as paths, but as file
731      * descriptors because the service doesn't accept paths as it might not have permission to open
732      * app-owned files and that could be abused to run a VM with software that the calling
733      * application doesn't own.
734      */
toVsConfig(@onNull PackageManager packageManager)735     VirtualMachineAppConfig toVsConfig(@NonNull PackageManager packageManager)
736             throws VirtualMachineException {
737         VirtualMachineAppConfig vsConfig = new VirtualMachineAppConfig();
738 
739         String apkPath = (mApkPath != null) ? mApkPath : findPayloadApk(packageManager);
740 
741         try {
742             vsConfig.apk = ParcelFileDescriptor.open(new File(apkPath), MODE_READ_ONLY);
743         } catch (FileNotFoundException e) {
744             throw new VirtualMachineException("Failed to open APK", e);
745         }
746         if (mPayloadBinaryName != null) {
747             VirtualMachinePayloadConfig payloadConfig = new VirtualMachinePayloadConfig();
748             payloadConfig.payloadBinaryName = mPayloadBinaryName;
749             payloadConfig.extraApks = Collections.emptyList();
750             vsConfig.payload =
751                     VirtualMachineAppConfig.Payload.payloadConfig(payloadConfig);
752         } else {
753             vsConfig.payload =
754                     VirtualMachineAppConfig.Payload.configPath(mPayloadConfigPath);
755         }
756         vsConfig.osName = mOs;
757         switch (mDebugLevel) {
758             case DEBUG_LEVEL_FULL:
759                 vsConfig.debugLevel = VirtualMachineAppConfig.DebugLevel.FULL;
760                 break;
761             default:
762                 vsConfig.debugLevel = VirtualMachineAppConfig.DebugLevel.NONE;
763                 break;
764         }
765         vsConfig.protectedVm = mProtectedVm;
766         vsConfig.memoryMib = bytesToMebiBytes(mMemoryBytes);
767         switch (mCpuTopology) {
768             case CPU_TOPOLOGY_MATCH_HOST:
769                 vsConfig.cpuTopology = android.system.virtualizationservice.CpuTopology.MATCH_HOST;
770                 break;
771             default:
772                 vsConfig.cpuTopology = android.system.virtualizationservice.CpuTopology.ONE_CPU;
773                 break;
774         }
775 
776         if (mVendorDiskImage != null || mNetworkSupported) {
777             VirtualMachineAppConfig.CustomConfig customConfig =
778                     new VirtualMachineAppConfig.CustomConfig();
779             customConfig.devices = EMPTY_STRING_ARRAY;
780             if (mVendorDiskImage != null) {
781                 try {
782                     customConfig.vendorImage =
783                             ParcelFileDescriptor.open(mVendorDiskImage, MODE_READ_ONLY);
784                 } catch (FileNotFoundException e) {
785                     throw new VirtualMachineException(
786                             "Failed to open vendor disk image "
787                                     + mVendorDiskImage.getAbsolutePath(),
788                             e);
789                 }
790             }
791             customConfig.networkSupported = mNetworkSupported;
792             vsConfig.customConfig = customConfig;
793         }
794 
795         vsConfig.boostUclamp = mShouldBoostUclamp;
796         return vsConfig;
797     }
798 
findPayloadApk(PackageManager packageManager)799     private String findPayloadApk(PackageManager packageManager) throws VirtualMachineException {
800         ApplicationInfo appInfo;
801         try {
802             appInfo =
803                     packageManager.getApplicationInfo(
804                             mPackageName, PackageManager.ApplicationInfoFlags.of(0));
805         } catch (PackageManager.NameNotFoundException e) {
806             throw new VirtualMachineException("Package not found", e);
807         }
808 
809         String[] splitApkPaths = appInfo.splitSourceDirs;
810         String[] abis = Build.SUPPORTED_64_BIT_ABIS;
811 
812         // If there are split APKs, and we know the payload binary name, see if we can find a
813         // split APK containing the binary.
814         if (mPayloadBinaryName != null && splitApkPaths != null && abis.length != 0) {
815             String[] libraryNames = new String[abis.length];
816             for (int i = 0; i < abis.length; i++) {
817                 libraryNames[i] = "lib/" + abis[i] + "/" + mPayloadBinaryName;
818             }
819 
820             for (String path : splitApkPaths) {
821                 try (ZipFile zip = new ZipFile(path)) {
822                     for (String name : libraryNames) {
823                         if (zip.getEntry(name) != null) {
824                             Log.i(TAG, "Found payload in " + path);
825                             return path;
826                         }
827                     }
828                 } catch (IOException e) {
829                     Log.w(TAG, "Failed to scan split APK: " + path, e);
830                 }
831             }
832         }
833 
834         // This really is the path to the APK, not a directory.
835         return appInfo.sourceDir;
836     }
837 
bytesToMebiBytes(long mMemoryBytes)838     private int bytesToMebiBytes(long mMemoryBytes) {
839         long oneMebi = 1024 * 1024;
840         // We can't express requests for more than 2 exabytes, but then they're not going to succeed
841         // anyway.
842         if (mMemoryBytes > (Integer.MAX_VALUE - 1) * oneMebi) {
843             return Integer.MAX_VALUE;
844         }
845         return (int) ((mMemoryBytes + oneMebi - 1) / oneMebi);
846     }
847 
848     /**
849      * A builder used to create a {@link VirtualMachineConfig}.
850      *
851      * @hide
852      */
853     @SystemApi
854     public static final class Builder {
855         @OsName private final String DEFAULT_OS = MICRODROID;
856 
857         @Nullable private final String mPackageName;
858         @Nullable private String mApkPath;
859         private final List<String> mExtraApks = new ArrayList<>();
860         @Nullable private String mPayloadConfigPath;
861         @Nullable private VirtualMachineCustomImageConfig mCustomImageConfig;
862         @Nullable private String mPayloadBinaryName;
863         @DebugLevel private int mDebugLevel = DEBUG_LEVEL_NONE;
864         private boolean mProtectedVm;
865         private boolean mProtectedVmSet;
866         private long mMemoryBytes;
867         @CpuTopology private int mCpuTopology = CPU_TOPOLOGY_ONE_CPU;
868         @Nullable private String mConsoleInputDevice;
869         private long mEncryptedStorageBytes;
870         private boolean mVmOutputCaptured = false;
871         private boolean mVmConsoleInputSupported = false;
872         private boolean mConnectVmConsole = false;
873         @Nullable private File mVendorDiskImage;
874         @NonNull @OsName private String mOs = DEFAULT_OS;
875         private boolean mNetworkSupported;
876         private boolean mShouldBoostUclamp = false;
877 
878         /**
879          * Creates a builder for the given context.
880          *
881          * @hide
882          */
883         @SystemApi
Builder(@onNull Context context)884         public Builder(@NonNull Context context) {
885             mPackageName = requireNonNull(context, "context must not be null").getPackageName();
886         }
887 
888         /**
889          * Creates a builder for a specific package. If packageName is null, {@link #setApkPath}
890          * must be called to specify the APK containing the payload.
891          */
Builder(@ullable String packageName)892         private Builder(@Nullable String packageName) {
893             mPackageName = packageName;
894         }
895 
896         /**
897          * Builds an immutable {@link VirtualMachineConfig}
898          *
899          * @hide
900          */
901         @SystemApi
902         @NonNull
build()903         public VirtualMachineConfig build() {
904             String apkPath = null;
905             String packageName = null;
906 
907             if (mApkPath != null) {
908                 apkPath = mApkPath;
909             } else if (mPackageName != null) {
910                 packageName = mPackageName;
911             } else {
912                 // This should never happen, unless we're deserializing a bad config
913                 throw new IllegalStateException("apkPath or packageName must be specified");
914             }
915             if (mCustomImageConfig != null) {
916                 if (mPayloadBinaryName != null || mPayloadConfigPath != null) {
917                     throw new IllegalStateException(
918                             "setCustomImageConfig and (setPayloadBinaryName or"
919                                     + " setPayloadConfigPath) may not both be called");
920                 }
921             } else if (mPayloadBinaryName == null) {
922                 if (mPayloadConfigPath == null) {
923                     throw new IllegalStateException("setPayloadBinaryName must be called");
924                 }
925                 if (!mExtraApks.isEmpty()) {
926                     throw new IllegalStateException(
927                             "setPayloadConfigPath and addExtraApk may not both be called");
928                 }
929             } else {
930                 if (mPayloadConfigPath != null) {
931                     throw new IllegalStateException(
932                             "setPayloadBinaryName and setPayloadConfigPath may not both be called");
933                 }
934             }
935 
936             if (!mProtectedVmSet) {
937                 throw new IllegalStateException("setProtectedVm must be called explicitly");
938             }
939 
940             if (mVmOutputCaptured && mDebugLevel != DEBUG_LEVEL_FULL) {
941                 throw new IllegalStateException("debug level must be FULL to capture output");
942             }
943 
944             if (mVmConsoleInputSupported && mDebugLevel != DEBUG_LEVEL_FULL) {
945                 throw new IllegalStateException("debug level must be FULL to use console input");
946             }
947 
948             if (mConnectVmConsole && mDebugLevel != DEBUG_LEVEL_FULL) {
949                 throw new IllegalStateException(
950                         "debug level must be FULL to connect to the console");
951             }
952 
953             if (mNetworkSupported && mProtectedVm) {
954                 throw new IllegalStateException("network is not supported on pVM");
955             }
956 
957             return new VirtualMachineConfig(
958                     packageName,
959                     apkPath,
960                     mExtraApks,
961                     mPayloadConfigPath,
962                     mPayloadBinaryName,
963                     mCustomImageConfig,
964                     mDebugLevel,
965                     mProtectedVm,
966                     mMemoryBytes,
967                     mCpuTopology,
968                     mConsoleInputDevice,
969                     mEncryptedStorageBytes,
970                     mVmOutputCaptured,
971                     mVmConsoleInputSupported,
972                     mConnectVmConsole,
973                     mVendorDiskImage,
974                     mOs,
975                     mNetworkSupported,
976                     mShouldBoostUclamp);
977         }
978 
979         /**
980          * Sets the absolute path of the APK containing the binary payload that will execute within
981          * the VM. If not set explicitly, defaults to the split APK containing the payload, if there
982          * is one, and otherwise the primary APK of the context.
983          *
984          * @hide
985          */
986         @SystemApi
987         @NonNull
setApkPath(@onNull String apkPath)988         public Builder setApkPath(@NonNull String apkPath) {
989             requireNonNull(apkPath, "apkPath must not be null");
990             if (!apkPath.startsWith("/")) {
991                 throw new IllegalArgumentException("APK path must be an absolute path");
992             }
993             mApkPath = apkPath;
994             return this;
995         }
996 
997         /**
998          * Specify the package name of an extra APK to be included in the VM. Each extra APK is
999          * mounted, in unzipped form, inside the VM, allowing access to the code and/or data within
1000          * it. The VM entry point must be in the main APK.
1001          *
1002          * @hide
1003          */
1004         @TestApi
1005         @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
1006         @NonNull
addExtraApk(@onNull String packageName)1007         public Builder addExtraApk(@NonNull String packageName) {
1008             mExtraApks.add(requireNonNull(packageName, "extra APK package name must not be null"));
1009             return this;
1010         }
1011 
1012         /**
1013          * Sets the path within the APK to the payload config file that defines software aspects of
1014          * the VM. The file is a JSON file; see
1015          * packages/modules/Virtualization/microdroid/payload/config/src/lib.rs for the format.
1016          *
1017          * @hide
1018          */
1019         @RequiresPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION)
1020         @TestApi
1021         @NonNull
setPayloadConfigPath(@onNull String payloadConfigPath)1022         public Builder setPayloadConfigPath(@NonNull String payloadConfigPath) {
1023             mPayloadConfigPath =
1024                     requireNonNull(payloadConfigPath, "payloadConfigPath must not be null");
1025             return this;
1026         }
1027 
1028         /**
1029          * Sets the custom config file to launch the custom VM.
1030          *
1031          * @hide
1032          */
1033         @RequiresPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION)
1034         @NonNull
setCustomImageConfig( @onNull VirtualMachineCustomImageConfig customImageConfig)1035         public Builder setCustomImageConfig(
1036                 @NonNull VirtualMachineCustomImageConfig customImageConfig) {
1037             this.mCustomImageConfig = customImageConfig;
1038             return this;
1039         }
1040 
1041         /**
1042          * Sets the name of the payload binary file that will be executed within the VM, e.g.
1043          * "payload.so". The file must reside in the {@code lib/<ABI>} directory of the APK.
1044          *
1045          * <p>Note that VMs only support 64-bit code, even if the owning app is running as a 32-bit
1046          * process.
1047          *
1048          * @hide
1049          */
1050         @SystemApi
1051         @NonNull
setPayloadBinaryName(@onNull String payloadBinaryName)1052         public Builder setPayloadBinaryName(@NonNull String payloadBinaryName) {
1053             requireNonNull(payloadBinaryName, "payloadBinaryName must not be null");
1054             if (payloadBinaryName.contains(File.separator)) {
1055                 throw new IllegalArgumentException(
1056                         "Invalid binary file name: " + payloadBinaryName);
1057             }
1058             mPayloadBinaryName = payloadBinaryName;
1059             return this;
1060         }
1061 
1062         /**
1063          * Sets the debug level. Defaults to {@link #DEBUG_LEVEL_NONE}.
1064          *
1065          * <p>If {@link #DEBUG_LEVEL_FULL} is set then logs from inside the VM are exported to the
1066          * host and adb connections from the host are possible. This is convenient for debugging but
1067          * may compromise the integrity of the VM - including bypassing the protections offered by a
1068          * {@linkplain #setProtectedVm protected VM}.
1069          *
1070          * <p>Note that it isn't possible to {@linkplain #isCompatibleWith change} the debug level
1071          * of a VM instance; debug and non-debug VMs always have different secrets.
1072          *
1073          * @hide
1074          */
1075         @SystemApi
1076         @NonNull
setDebugLevel(@ebugLevel int debugLevel)1077         public Builder setDebugLevel(@DebugLevel int debugLevel) {
1078             if (debugLevel != DEBUG_LEVEL_NONE && debugLevel != DEBUG_LEVEL_FULL) {
1079                 throw new IllegalArgumentException("Invalid debugLevel: " + debugLevel);
1080             }
1081             mDebugLevel = debugLevel;
1082             return this;
1083         }
1084 
1085         /**
1086          * Sets whether to protect the VM memory from the host. No default is provided, this must be
1087          * set explicitly.
1088          *
1089          * <p>Note that if debugging is {@linkplain #setDebugLevel enabled} for a protected VM, the
1090          * VM is not truly protected - direct memory access by the host is prevented, but e.g. the
1091          * debugger can be used to access the VM's internals.
1092          *
1093          * <p>It isn't possible to {@linkplain #isCompatibleWith change} the protected status of a
1094          * VM instance; protected and non-protected VMs always have different secrets.
1095          *
1096          * @see VirtualMachineManager#getCapabilities
1097          * @hide
1098          */
1099         @SystemApi
1100         @NonNull
setProtectedVm(boolean protectedVm)1101         public Builder setProtectedVm(boolean protectedVm) {
1102             if (protectedVm) {
1103                 if (!HypervisorProperties.hypervisor_protected_vm_supported().orElse(false)) {
1104                     throw new UnsupportedOperationException(
1105                             "Protected VMs are not supported on this device.");
1106                 }
1107             } else {
1108                 if (!HypervisorProperties.hypervisor_vm_supported().orElse(false)) {
1109                     throw new UnsupportedOperationException(
1110                             "Non-protected VMs are not supported on this device.");
1111                 }
1112             }
1113             mProtectedVm = protectedVm;
1114             mProtectedVmSet = true;
1115             return this;
1116         }
1117 
1118         /**
1119          * Sets the amount of RAM to give the VM, in bytes. If not explicitly set then a default
1120          * size will be used.
1121          *
1122          * @hide
1123          */
1124         @SystemApi
1125         @NonNull
setMemoryBytes(@ntRangefrom = 1) long memoryBytes)1126         public Builder setMemoryBytes(@IntRange(from = 1) long memoryBytes) {
1127             if (memoryBytes <= 0) {
1128                 throw new IllegalArgumentException("Memory size must be positive");
1129             }
1130             mMemoryBytes = memoryBytes;
1131             return this;
1132         }
1133 
1134         /**
1135          * Sets the CPU topology configuration of the VM. Defaults to {@link #CPU_TOPOLOGY_ONE_CPU}.
1136          *
1137          * <p>This determines how many virtual CPUs will be created, and their performance and
1138          * scheduling characteristics, such as affinity masks. Topology also has an effect on memory
1139          * usage as each vCPU requires additional memory to keep its state.
1140          *
1141          * @hide
1142          */
1143         @SystemApi
1144         @NonNull
setCpuTopology(@puTopology int cpuTopology)1145         public Builder setCpuTopology(@CpuTopology int cpuTopology) {
1146             if (cpuTopology != CPU_TOPOLOGY_ONE_CPU && cpuTopology != CPU_TOPOLOGY_MATCH_HOST) {
1147                 throw new IllegalArgumentException("Invalid cpuTopology: " + cpuTopology);
1148             }
1149             mCpuTopology = cpuTopology;
1150             return this;
1151         }
1152 
1153         /**
1154          * Sets the serial device for VM console input.
1155          *
1156          * @see android.system.virtualizationservice.ConsoleInputDevice
1157          * @hide
1158          */
setConsoleInputDevice(@ullable String consoleInputDevice)1159         public Builder setConsoleInputDevice(@Nullable String consoleInputDevice) {
1160             mConsoleInputDevice = consoleInputDevice;
1161             return this;
1162         }
1163 
1164         /**
1165          * Sets the size (in bytes) of encrypted storage available to the VM. If not set, no
1166          * encrypted storage is provided.
1167          *
1168          * <p>The storage is encrypted with a key deterministically derived from the VM identity
1169          *
1170          * <p>The encrypted storage is persistent across VM reboots as well as device reboots. The
1171          * backing file (containing encrypted data) is stored in the app's private data directory.
1172          *
1173          * <p>Note - There is no integrity guarantee or rollback protection on the storage in case
1174          * the encrypted data is modified.
1175          *
1176          * <p>Deleting the VM will delete the encrypted data - there is no way to recover that data.
1177          *
1178          * @hide
1179          */
1180         @SystemApi
1181         @NonNull
setEncryptedStorageBytes(@ntRangefrom = 1) long encryptedStorageBytes)1182         public Builder setEncryptedStorageBytes(@IntRange(from = 1) long encryptedStorageBytes) {
1183             if (encryptedStorageBytes <= 0) {
1184                 throw new IllegalArgumentException("Encrypted Storage size must be positive");
1185             }
1186             mEncryptedStorageBytes = encryptedStorageBytes;
1187             return this;
1188         }
1189 
1190         /**
1191          * Sets whether to allow the app to read the VM outputs (console / log). Default is {@code
1192          * false}.
1193          *
1194          * <p>By default, console and log outputs of a {@linkplain #setDebugLevel debuggable} VM are
1195          * automatically forwarded to the host logcat. Setting this as {@code true} will allow the
1196          * app to directly read {@linkplain VirtualMachine#getConsoleOutput console output} and
1197          * {@linkplain VirtualMachine#getLogOutput log output}, instead of forwarding them to the
1198          * host logcat.
1199          *
1200          * <p>If you turn on output capture, you must consume data from {@link
1201          * VirtualMachine#getConsoleOutput} and {@link VirtualMachine#getLogOutput} - because
1202          * otherwise the code in the VM may get blocked when the pipe buffer fills up.
1203          *
1204          * <p>The {@linkplain #setDebugLevel debug level} must be {@link #DEBUG_LEVEL_FULL} to be
1205          * set as true.
1206          *
1207          * @hide
1208          */
1209         @SystemApi
1210         @NonNull
setVmOutputCaptured(boolean captured)1211         public Builder setVmOutputCaptured(boolean captured) {
1212             mVmOutputCaptured = captured;
1213             return this;
1214         }
1215 
1216         /**
1217          * Sets whether to allow the app to write to the VM console. Default is {@code false}.
1218          *
1219          * <p>Setting this as {@code true} will allow the app to directly write into {@linkplain
1220          * VirtualMachine#getConsoleInput console input}.
1221          *
1222          * <p>The {@linkplain #setDebugLevel debug level} must be {@link #DEBUG_LEVEL_FULL} to be
1223          * set as true.
1224          *
1225          * @hide
1226          */
1227         @TestApi
1228         @NonNull
setVmConsoleInputSupported(boolean supported)1229         public Builder setVmConsoleInputSupported(boolean supported) {
1230             mVmConsoleInputSupported = supported;
1231             return this;
1232         }
1233 
1234         /**
1235          * Sets whether to connect the VM console to a host console. Default is {@code false}.
1236          *
1237          * <p>Setting this as {@code true} will allow the shell to directly communicate with the VM
1238          * console through the connected host console.
1239          *
1240          * <p>The {@linkplain #setDebugLevel debug level} must be {@link #DEBUG_LEVEL_FULL} to be
1241          * set as true.
1242          *
1243          * @hide
1244          */
1245         @NonNull
setConnectVmConsole(boolean supported)1246         public Builder setConnectVmConsole(boolean supported) {
1247             mConnectVmConsole = supported;
1248             return this;
1249         }
1250 
1251         /**
1252          * Sets the path to the disk image with vendor-specific modules.
1253          *
1254          * @hide
1255          */
1256         @TestApi
1257         @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
1258         @RequiresPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION)
1259         @NonNull
setVendorDiskImage(@onNull File vendorDiskImage)1260         public Builder setVendorDiskImage(@NonNull File vendorDiskImage) {
1261             mVendorDiskImage =
1262                     requireNonNull(vendorDiskImage, "vendor disk image must not be null");
1263             return this;
1264         }
1265 
1266         /**
1267          * Sets an OS for the VM. Defaults to {@code "microdroid"}.
1268          *
1269          * <p>See {@link VirtualMachineManager#getSupportedOSList} for available OS names.
1270          *
1271          * @hide
1272          */
1273         @TestApi
1274         @FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
1275         @RequiresPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION)
1276         @NonNull
setOs(@onNull @sName String os)1277         public Builder setOs(@NonNull @OsName String os) {
1278             mOs = requireNonNull(os, "os must not be null");
1279             return this;
1280         }
1281 
1282         /**
1283          * Sets whether to support network feature to VM. Default is {@code false}.
1284          *
1285          * @hide
1286          */
1287         @TestApi
1288         @RequiresPermission(
1289                 allOf = {
1290                     VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION,
1291                     Manifest.permission.INTERNET
1292                 })
1293         @NonNull
setNetworkSupported(boolean networkSupported)1294         public Builder setNetworkSupported(boolean networkSupported) {
1295             mNetworkSupported = networkSupported;
1296             return this;
1297         }
1298 
1299         /** @hide */
setShouldBoostUclamp(boolean shouldBoostUclamp)1300         public Builder setShouldBoostUclamp(boolean shouldBoostUclamp) {
1301             mShouldBoostUclamp = shouldBoostUclamp;
1302             return this;
1303         }
1304     }
1305 }
1306