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