• 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 
22 import static java.util.Objects.requireNonNull;
23 
24 import android.annotation.IntDef;
25 import android.annotation.IntRange;
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.annotation.RequiresPermission;
29 import android.annotation.SystemApi;
30 import android.annotation.TestApi;
31 import android.content.Context;
32 import android.content.pm.ApplicationInfo;
33 import android.content.pm.PackageManager;
34 import android.os.Build;
35 import android.os.ParcelFileDescriptor;
36 import android.os.PersistableBundle;
37 import android.sysprop.HypervisorProperties;
38 import android.system.virtualizationservice.VirtualMachineAppConfig;
39 import android.system.virtualizationservice.VirtualMachinePayloadConfig;
40 import android.util.Log;
41 
42 import java.io.File;
43 import java.io.FileInputStream;
44 import java.io.FileNotFoundException;
45 import java.io.FileOutputStream;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.io.OutputStream;
49 import java.lang.annotation.Retention;
50 import java.lang.annotation.RetentionPolicy;
51 import java.util.Objects;
52 import java.util.zip.ZipFile;
53 
54 /**
55  * Represents a configuration of a virtual machine. A configuration consists of hardware
56  * configurations like the number of CPUs and the size of RAM, and software configurations like the
57  * payload to run on the virtual machine.
58  *
59  * @hide
60  */
61 @SystemApi
62 public final class VirtualMachineConfig {
63     private static final String TAG = "VirtualMachineConfig";
64     private static final String[] EMPTY_STRING_ARRAY = {};
65 
66     // These define the schema of the config file persisted on disk.
67     private static final int VERSION = 6;
68     private static final String KEY_VERSION = "version";
69     private static final String KEY_PACKAGENAME = "packageName";
70     private static final String KEY_APKPATH = "apkPath";
71     private static final String KEY_PAYLOADCONFIGPATH = "payloadConfigPath";
72     private static final String KEY_PAYLOADBINARYNAME = "payloadBinaryPath";
73     private static final String KEY_DEBUGLEVEL = "debugLevel";
74     private static final String KEY_PROTECTED_VM = "protectedVm";
75     private static final String KEY_MEMORY_BYTES = "memoryBytes";
76     private static final String KEY_CPU_TOPOLOGY = "cpuTopology";
77     private static final String KEY_ENCRYPTED_STORAGE_BYTES = "encryptedStorageBytes";
78     private static final String KEY_VM_OUTPUT_CAPTURED = "vmOutputCaptured";
79 
80     /** @hide */
81     @Retention(RetentionPolicy.SOURCE)
82     @IntDef(prefix = "DEBUG_LEVEL_", value = {
83             DEBUG_LEVEL_NONE,
84             DEBUG_LEVEL_FULL
85     })
86     public @interface DebugLevel {}
87 
88     /**
89      * Not debuggable at all. No log is exported from the VM. Debugger can't be attached to the app
90      * process running in the VM. This is the default level.
91      *
92      * @hide
93      */
94     @SystemApi public static final int DEBUG_LEVEL_NONE = 0;
95 
96     /**
97      * Fully debuggable. All logs (both logcat and kernel message) are exported. All processes
98      * running in the VM can be attached to the debugger. Rooting is possible.
99      *
100      * @hide
101      */
102     @SystemApi public static final int DEBUG_LEVEL_FULL = 1;
103 
104     /** @hide */
105     @Retention(RetentionPolicy.SOURCE)
106     @IntDef(
107             prefix = "CPU_TOPOLOGY_",
108             value = {
109                 CPU_TOPOLOGY_ONE_CPU,
110                 CPU_TOPOLOGY_MATCH_HOST,
111             })
112     public @interface CpuTopology {}
113 
114     /**
115      * Run VM with 1 vCPU. This is the default option, usually the fastest to boot and consuming the
116      * least amount of resources. Typically the best option for small or ephemeral workloads.
117      *
118      * @hide
119      */
120     @SystemApi public static final int CPU_TOPOLOGY_ONE_CPU = 0;
121 
122     /**
123      * Run VM with vCPU topology matching the physical CPU topology of the host. Usually takes
124      * longer to boot and cosumes more resources compared to a single vCPU. Typically a good option
125      * for long-running workloads that benefit from parallel execution.
126      *
127      * @hide
128      */
129     @SystemApi public static final int CPU_TOPOLOGY_MATCH_HOST = 1;
130 
131     /** Name of a package whose primary APK contains the VM payload. */
132     @Nullable private final String mPackageName;
133 
134     /** Absolute path to the APK file containing the VM payload. */
135     @Nullable private final String mApkPath;
136 
137     @DebugLevel private final int mDebugLevel;
138 
139     /**
140      * Whether to run the VM in protected mode, so the host can't access its memory.
141      */
142     private final boolean mProtectedVm;
143 
144     /**
145      * The amount of RAM to give the VM, in bytes. If this is 0 or negative the default will be
146      * used.
147      */
148     private final long mMemoryBytes;
149 
150     /** CPU topology configuration of the VM. */
151     @CpuTopology private final int mCpuTopology;
152 
153     /**
154      * Path within the APK to the payload config file that defines software aspects of the VM.
155      */
156     @Nullable private final String mPayloadConfigPath;
157 
158     /** Name of the payload binary file within the APK that will be executed within the VM. */
159     @Nullable private final String mPayloadBinaryName;
160 
161     /** The size of storage in bytes. 0 indicates that encryptedStorage is not required */
162     private final long mEncryptedStorageBytes;
163 
164     /** Whether the app can read console and log output. */
165     private final boolean mVmOutputCaptured;
166 
VirtualMachineConfig( @ullable String packageName, @Nullable String apkPath, @Nullable String payloadConfigPath, @Nullable String payloadBinaryName, @DebugLevel int debugLevel, boolean protectedVm, long memoryBytes, @CpuTopology int cpuTopology, long encryptedStorageBytes, boolean vmOutputCaptured)167     private VirtualMachineConfig(
168             @Nullable String packageName,
169             @Nullable String apkPath,
170             @Nullable String payloadConfigPath,
171             @Nullable String payloadBinaryName,
172             @DebugLevel int debugLevel,
173             boolean protectedVm,
174             long memoryBytes,
175             @CpuTopology int cpuTopology,
176             long encryptedStorageBytes,
177             boolean vmOutputCaptured) {
178         // This is only called from Builder.build(); the builder handles parameter validation.
179         mPackageName = packageName;
180         mApkPath = apkPath;
181         mPayloadConfigPath = payloadConfigPath;
182         mPayloadBinaryName = payloadBinaryName;
183         mDebugLevel = debugLevel;
184         mProtectedVm = protectedVm;
185         mMemoryBytes = memoryBytes;
186         mCpuTopology = cpuTopology;
187         mEncryptedStorageBytes = encryptedStorageBytes;
188         mVmOutputCaptured = vmOutputCaptured;
189     }
190 
191     /** Loads a config from a file. */
192     @NonNull
from(@onNull File file)193     static VirtualMachineConfig from(@NonNull File file) throws VirtualMachineException {
194         try (FileInputStream input = new FileInputStream(file)) {
195             return fromInputStream(input);
196         } catch (IOException e) {
197             throw new VirtualMachineException("Failed to read VM config from file", e);
198         }
199     }
200 
201     /** Loads a config from a {@link ParcelFileDescriptor}. */
202     @NonNull
from(@onNull ParcelFileDescriptor fd)203     static VirtualMachineConfig from(@NonNull ParcelFileDescriptor fd)
204             throws VirtualMachineException {
205         try (AutoCloseInputStream input = new AutoCloseInputStream(fd)) {
206             return fromInputStream(input);
207         } catch (IOException e) {
208             throw new VirtualMachineException("failed to read VM config from file descriptor", e);
209         }
210     }
211 
212     /** Loads a config from a stream, for example a file. */
213     @NonNull
fromInputStream(@onNull InputStream input)214     private static VirtualMachineConfig fromInputStream(@NonNull InputStream input)
215             throws IOException, VirtualMachineException {
216         PersistableBundle b = PersistableBundle.readFromStream(input);
217         try {
218             return fromPersistableBundle(b);
219         } catch (NullPointerException | IllegalArgumentException | IllegalStateException e) {
220             throw new VirtualMachineException("Persisted VM config is invalid", e);
221         }
222     }
223 
224     @NonNull
fromPersistableBundle(PersistableBundle b)225     private static VirtualMachineConfig fromPersistableBundle(PersistableBundle b) {
226         int version = b.getInt(KEY_VERSION);
227         if (version > VERSION) {
228             throw new IllegalArgumentException(
229                     "Version " + version + " too high; current is " + VERSION);
230         }
231 
232         String packageName = b.getString(KEY_PACKAGENAME);
233         Builder builder = new Builder(packageName);
234 
235         String apkPath = b.getString(KEY_APKPATH);
236         if (apkPath != null) {
237             builder.setApkPath(apkPath);
238         }
239 
240         String payloadConfigPath = b.getString(KEY_PAYLOADCONFIGPATH);
241         if (payloadConfigPath == null) {
242             builder.setPayloadBinaryName(b.getString(KEY_PAYLOADBINARYNAME));
243         } else {
244             builder.setPayloadConfigPath(payloadConfigPath);
245         }
246 
247         @DebugLevel int debugLevel = b.getInt(KEY_DEBUGLEVEL);
248         if (debugLevel != DEBUG_LEVEL_NONE && debugLevel != DEBUG_LEVEL_FULL) {
249             throw new IllegalArgumentException("Invalid debugLevel: " + debugLevel);
250         }
251         builder.setDebugLevel(debugLevel);
252         builder.setProtectedVm(b.getBoolean(KEY_PROTECTED_VM));
253         long memoryBytes = b.getLong(KEY_MEMORY_BYTES);
254         if (memoryBytes != 0) {
255             builder.setMemoryBytes(memoryBytes);
256         }
257         builder.setCpuTopology(b.getInt(KEY_CPU_TOPOLOGY));
258         long encryptedStorageBytes = b.getLong(KEY_ENCRYPTED_STORAGE_BYTES);
259         if (encryptedStorageBytes != 0) {
260             builder.setEncryptedStorageBytes(encryptedStorageBytes);
261         }
262         builder.setVmOutputCaptured(b.getBoolean(KEY_VM_OUTPUT_CAPTURED));
263 
264         return builder.build();
265     }
266 
267     /** Persists this config to a file. */
serialize(@onNull File file)268     void serialize(@NonNull File file) throws VirtualMachineException {
269         try (FileOutputStream output = new FileOutputStream(file)) {
270             serializeOutputStream(output);
271         } catch (IOException e) {
272             throw new VirtualMachineException("failed to write VM config", e);
273         }
274     }
275 
276     /** Persists this config to a stream, for example a file. */
serializeOutputStream(@onNull OutputStream output)277     private void serializeOutputStream(@NonNull OutputStream output) throws IOException {
278         PersistableBundle b = new PersistableBundle();
279         b.putInt(KEY_VERSION, VERSION);
280         if (mPackageName != null) {
281             b.putString(KEY_PACKAGENAME, mPackageName);
282         }
283         if (mApkPath != null) {
284             b.putString(KEY_APKPATH, mApkPath);
285         }
286         b.putString(KEY_PAYLOADCONFIGPATH, mPayloadConfigPath);
287         b.putString(KEY_PAYLOADBINARYNAME, mPayloadBinaryName);
288         b.putInt(KEY_DEBUGLEVEL, mDebugLevel);
289         b.putBoolean(KEY_PROTECTED_VM, mProtectedVm);
290         b.putInt(KEY_CPU_TOPOLOGY, mCpuTopology);
291         if (mMemoryBytes > 0) {
292             b.putLong(KEY_MEMORY_BYTES, mMemoryBytes);
293         }
294         if (mEncryptedStorageBytes > 0) {
295             b.putLong(KEY_ENCRYPTED_STORAGE_BYTES, mEncryptedStorageBytes);
296         }
297         b.putBoolean(KEY_VM_OUTPUT_CAPTURED, mVmOutputCaptured);
298         b.writeToStream(output);
299     }
300 
301     /**
302      * Returns the absolute path of the APK which should contain the binary payload that will
303      * execute within the VM. Returns null if no specific path has been set.
304      *
305      * @hide
306      */
307     @SystemApi
308     @Nullable
getApkPath()309     public String getApkPath() {
310         return mApkPath;
311     }
312 
313     /**
314      * Returns the path within the APK to the payload config file that defines software aspects of
315      * the VM.
316      *
317      * @hide
318      */
319     @TestApi
320     @Nullable
getPayloadConfigPath()321     public String getPayloadConfigPath() {
322         return mPayloadConfigPath;
323     }
324 
325     /**
326      * Returns the name of the payload binary file, in the {@code lib/<ABI>} directory of the APK,
327      * that will be executed within the VM.
328      *
329      * @hide
330      */
331     @SystemApi
332     @Nullable
getPayloadBinaryName()333     public String getPayloadBinaryName() {
334         return mPayloadBinaryName;
335     }
336 
337     /**
338      * Returns the debug level for the VM.
339      *
340      * @hide
341      */
342     @SystemApi
343     @DebugLevel
getDebugLevel()344     public int getDebugLevel() {
345         return mDebugLevel;
346     }
347 
348     /**
349      * Returns whether the VM's memory will be protected from the host.
350      *
351      * @hide
352      */
353     @SystemApi
isProtectedVm()354     public boolean isProtectedVm() {
355         return mProtectedVm;
356     }
357 
358     /**
359      * Returns the amount of RAM that will be made available to the VM, or 0 if the default size
360      * will be used.
361      *
362      * @hide
363      */
364     @SystemApi
365     @IntRange(from = 0)
getMemoryBytes()366     public long getMemoryBytes() {
367         return mMemoryBytes;
368     }
369 
370     /**
371      * Returns the CPU topology configuration of the VM.
372      *
373      * @hide
374      */
375     @SystemApi
376     @CpuTopology
getCpuTopology()377     public int getCpuTopology() {
378         return mCpuTopology;
379     }
380 
381     /**
382      * Returns whether encrypted storage is enabled or not.
383      *
384      * @hide
385      */
386     @SystemApi
isEncryptedStorageEnabled()387     public boolean isEncryptedStorageEnabled() {
388         return mEncryptedStorageBytes > 0;
389     }
390 
391     /**
392      * Returns the size of encrypted storage (in bytes) available in the VM, or 0 if encrypted
393      * storage is not enabled
394      *
395      * @hide
396      */
397     @SystemApi
398     @IntRange(from = 0)
getEncryptedStorageBytes()399     public long getEncryptedStorageBytes() {
400         return mEncryptedStorageBytes;
401     }
402 
403     /**
404      * Returns whether the app can read the VM console or log output. If not, the VM output is
405      * automatically forwarded to the host logcat.
406      *
407      * @see Builder#setVmOutputCaptured
408      * @hide
409      */
410     @SystemApi
isVmOutputCaptured()411     public boolean isVmOutputCaptured() {
412         return mVmOutputCaptured;
413     }
414 
415     /**
416      * Tests if this config is compatible with other config. Being compatible means that the configs
417      * can be interchangeably used for the same virtual machine; they do not change the VM identity
418      * or secrets. Such changes include varying the number of CPUs or the size of the RAM. Changes
419      * that would alter the identity of the VM (e.g. using a different payload or changing the debug
420      * mode) are considered incompatible.
421      *
422      * @see VirtualMachine#setConfig
423      * @hide
424      */
425     @SystemApi
isCompatibleWith(@onNull VirtualMachineConfig other)426     public boolean isCompatibleWith(@NonNull VirtualMachineConfig other) {
427         if (this == other) {
428             return true;
429         }
430         return this.mDebugLevel == other.mDebugLevel
431                 && this.mProtectedVm == other.mProtectedVm
432                 && this.mEncryptedStorageBytes == other.mEncryptedStorageBytes
433                 && this.mVmOutputCaptured == other.mVmOutputCaptured
434                 && Objects.equals(this.mPayloadConfigPath, other.mPayloadConfigPath)
435                 && Objects.equals(this.mPayloadBinaryName, other.mPayloadBinaryName)
436                 && Objects.equals(this.mPackageName, other.mPackageName)
437                 && Objects.equals(this.mApkPath, other.mApkPath);
438     }
439 
440     /**
441      * Converts this config object into the parcelable type used when creating a VM via the
442      * virtualization service. Notice that the files are not passed as paths, but as file
443      * descriptors because the service doesn't accept paths as it might not have permission to open
444      * app-owned files and that could be abused to run a VM with software that the calling
445      * application doesn't own.
446      */
toVsConfig(@onNull PackageManager packageManager)447     VirtualMachineAppConfig toVsConfig(@NonNull PackageManager packageManager)
448             throws VirtualMachineException {
449         VirtualMachineAppConfig vsConfig = new VirtualMachineAppConfig();
450 
451         String apkPath = (mApkPath != null) ? mApkPath : findPayloadApk(packageManager);
452 
453         try {
454             vsConfig.apk = ParcelFileDescriptor.open(new File(apkPath), MODE_READ_ONLY);
455         } catch (FileNotFoundException e) {
456             throw new VirtualMachineException("Failed to open APK", e);
457         }
458         if (mPayloadBinaryName != null) {
459             VirtualMachinePayloadConfig payloadConfig = new VirtualMachinePayloadConfig();
460             payloadConfig.payloadBinaryName = mPayloadBinaryName;
461             vsConfig.payload =
462                     VirtualMachineAppConfig.Payload.payloadConfig(payloadConfig);
463         } else {
464             vsConfig.payload =
465                     VirtualMachineAppConfig.Payload.configPath(mPayloadConfigPath);
466         }
467         switch (mDebugLevel) {
468             case DEBUG_LEVEL_FULL:
469                 vsConfig.debugLevel = VirtualMachineAppConfig.DebugLevel.FULL;
470                 break;
471             default:
472                 vsConfig.debugLevel = VirtualMachineAppConfig.DebugLevel.NONE;
473                 break;
474         }
475         vsConfig.protectedVm = mProtectedVm;
476         vsConfig.memoryMib = bytesToMebiBytes(mMemoryBytes);
477         switch (mCpuTopology) {
478             case CPU_TOPOLOGY_MATCH_HOST:
479                 vsConfig.cpuTopology = android.system.virtualizationservice.CpuTopology.MATCH_HOST;
480                 break;
481             default:
482                 vsConfig.cpuTopology = android.system.virtualizationservice.CpuTopology.ONE_CPU;
483                 break;
484         }
485         // Don't allow apps to set task profiles ... at least for now.
486         vsConfig.taskProfiles = EMPTY_STRING_ARRAY;
487         return vsConfig;
488     }
489 
findPayloadApk(PackageManager packageManager)490     private String findPayloadApk(PackageManager packageManager) throws VirtualMachineException {
491         ApplicationInfo appInfo;
492         try {
493             appInfo =
494                     packageManager.getApplicationInfo(
495                             mPackageName, PackageManager.ApplicationInfoFlags.of(0));
496         } catch (PackageManager.NameNotFoundException e) {
497             throw new VirtualMachineException("Package not found", e);
498         }
499 
500         String[] splitApkPaths = appInfo.splitSourceDirs;
501         String[] abis = Build.SUPPORTED_64_BIT_ABIS;
502 
503         // If there are split APKs, and we know the payload binary name, see if we can find a
504         // split APK containing the binary.
505         if (mPayloadBinaryName != null && splitApkPaths != null && abis.length != 0) {
506             String[] libraryNames = new String[abis.length];
507             for (int i = 0; i < abis.length; i++) {
508                 libraryNames[i] = "lib/" + abis[i] + "/" + mPayloadBinaryName;
509             }
510 
511             for (String path : splitApkPaths) {
512                 try (ZipFile zip = new ZipFile(path)) {
513                     for (String name : libraryNames) {
514                         if (zip.getEntry(name) != null) {
515                             Log.i(TAG, "Found payload in " + path);
516                             return path;
517                         }
518                     }
519                 } catch (IOException e) {
520                     Log.w(TAG, "Failed to scan split APK: " + path, e);
521                 }
522             }
523         }
524 
525         // This really is the path to the APK, not a directory.
526         return appInfo.sourceDir;
527     }
528 
bytesToMebiBytes(long mMemoryBytes)529     private int bytesToMebiBytes(long mMemoryBytes) {
530         long oneMebi = 1024 * 1024;
531         // We can't express requests for more than 2 exabytes, but then they're not going to succeed
532         // anyway.
533         if (mMemoryBytes > (Integer.MAX_VALUE - 1) * oneMebi) {
534             return Integer.MAX_VALUE;
535         }
536         return (int) ((mMemoryBytes + oneMebi - 1) / oneMebi);
537     }
538 
539     /**
540      * A builder used to create a {@link VirtualMachineConfig}.
541      *
542      * @hide
543      */
544     @SystemApi
545     public static final class Builder {
546         @Nullable private final String mPackageName;
547         @Nullable private String mApkPath;
548         @Nullable private String mPayloadConfigPath;
549         @Nullable private String mPayloadBinaryName;
550         @DebugLevel private int mDebugLevel = DEBUG_LEVEL_NONE;
551         private boolean mProtectedVm;
552         private boolean mProtectedVmSet;
553         private long mMemoryBytes;
554         @CpuTopology private int mCpuTopology = CPU_TOPOLOGY_ONE_CPU;
555         private long mEncryptedStorageBytes;
556         private boolean mVmOutputCaptured = false;
557 
558         /**
559          * Creates a builder for the given context.
560          *
561          * @hide
562          */
563         @SystemApi
Builder(@onNull Context context)564         public Builder(@NonNull Context context) {
565             mPackageName = requireNonNull(context, "context must not be null").getPackageName();
566         }
567 
568         /**
569          * Creates a builder for a specific package. If packageName is null, {@link #setApkPath}
570          * must be called to specify the APK containing the payload.
571          */
Builder(@ullable String packageName)572         private Builder(@Nullable String packageName) {
573             mPackageName = packageName;
574         }
575 
576         /**
577          * Builds an immutable {@link VirtualMachineConfig}
578          *
579          * @hide
580          */
581         @SystemApi
582         @NonNull
build()583         public VirtualMachineConfig build() {
584             String apkPath = null;
585             String packageName = null;
586 
587             if (mApkPath != null) {
588                 apkPath = mApkPath;
589             } else if (mPackageName != null) {
590                 packageName = mPackageName;
591             } else {
592                 // This should never happen, unless we're deserializing a bad config
593                 throw new IllegalStateException("apkPath or packageName must be specified");
594             }
595 
596             if (mPayloadBinaryName == null) {
597                 if (mPayloadConfigPath == null) {
598                     throw new IllegalStateException("setPayloadBinaryName must be called");
599                 }
600             } else {
601                 if (mPayloadConfigPath != null) {
602                     throw new IllegalStateException(
603                             "setPayloadBinaryName and setPayloadConfigPath may not both be called");
604                 }
605             }
606 
607             if (!mProtectedVmSet) {
608                 throw new IllegalStateException("setProtectedVm must be called explicitly");
609             }
610 
611             if (mVmOutputCaptured && mDebugLevel != DEBUG_LEVEL_FULL) {
612                 throw new IllegalStateException("debug level must be FULL to capture output");
613             }
614 
615             return new VirtualMachineConfig(
616                     packageName,
617                     apkPath,
618                     mPayloadConfigPath,
619                     mPayloadBinaryName,
620                     mDebugLevel,
621                     mProtectedVm,
622                     mMemoryBytes,
623                     mCpuTopology,
624                     mEncryptedStorageBytes,
625                     mVmOutputCaptured);
626         }
627 
628         /**
629          * Sets the absolute path of the APK containing the binary payload that will execute within
630          * the VM. If not set explicitly, defaults to the split APK containing the payload, if there
631          * is one, and otherwise the primary APK of the context.
632          *
633          * @hide
634          */
635         @SystemApi
636         @NonNull
setApkPath(@onNull String apkPath)637         public Builder setApkPath(@NonNull String apkPath) {
638             requireNonNull(apkPath, "apkPath must not be null");
639             if (!apkPath.startsWith("/")) {
640                 throw new IllegalArgumentException("APK path must be an absolute path");
641             }
642             mApkPath = apkPath;
643             return this;
644         }
645 
646         /**
647          * Sets the path within the APK to the payload config file that defines software aspects of
648          * the VM. The file is a JSON file; see
649          * packages/modules/Virtualization/microdroid/payload/config/src/lib.rs for the format.
650          *
651          * @hide
652          */
653         @RequiresPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION)
654         @TestApi
655         @NonNull
setPayloadConfigPath(@onNull String payloadConfigPath)656         public Builder setPayloadConfigPath(@NonNull String payloadConfigPath) {
657             mPayloadConfigPath =
658                     requireNonNull(payloadConfigPath, "payloadConfigPath must not be null");
659             return this;
660         }
661 
662         /**
663          * Sets the name of the payload binary file that will be executed within the VM, e.g.
664          * "payload.so". The file must reside in the {@code lib/<ABI>} directory of the APK.
665          *
666          * <p>Note that VMs only support 64-bit code, even if the owning app is running as a 32-bit
667          * process.
668          *
669          * @hide
670          */
671         @SystemApi
672         @NonNull
setPayloadBinaryName(@onNull String payloadBinaryName)673         public Builder setPayloadBinaryName(@NonNull String payloadBinaryName) {
674             if (payloadBinaryName.contains(File.separator)) {
675                 throw new IllegalArgumentException(
676                         "Invalid binary file name: " + payloadBinaryName);
677             }
678             mPayloadBinaryName =
679                     requireNonNull(payloadBinaryName, "payloadBinaryName must not be null");
680             return this;
681         }
682 
683         /**
684          * Sets the debug level. Defaults to {@link #DEBUG_LEVEL_NONE}.
685          *
686          * <p>If {@link #DEBUG_LEVEL_FULL} is set then logs from inside the VM are exported to the
687          * host and adb connections from the host are possible. This is convenient for debugging but
688          * may compromise the integrity of the VM - including bypassing the protections offered by a
689          * {@linkplain #setProtectedVm protected VM}.
690          *
691          * <p>Note that it isn't possible to {@linkplain #isCompatibleWith change} the debug level
692          * of a VM instance; debug and non-debug VMs always have different secrets.
693          *
694          * @hide
695          */
696         @SystemApi
697         @NonNull
setDebugLevel(@ebugLevel int debugLevel)698         public Builder setDebugLevel(@DebugLevel int debugLevel) {
699             if (debugLevel != DEBUG_LEVEL_NONE && debugLevel != DEBUG_LEVEL_FULL) {
700                 throw new IllegalArgumentException("Invalid debugLevel: " + debugLevel);
701             }
702             mDebugLevel = debugLevel;
703             return this;
704         }
705 
706         /**
707          * Sets whether to protect the VM memory from the host. No default is provided, this must be
708          * set explicitly.
709          *
710          * <p>Note that if debugging is {@linkplain #setDebugLevel enabled} for a protected VM, the
711          * VM is not truly protected - direct memory access by the host is prevented, but e.g. the
712          * debugger can be used to access the VM's internals.
713          *
714          * <p>It isn't possible to {@linkplain #isCompatibleWith change} the protected status of a
715          * VM instance; protected and non-protected VMs always have different secrets.
716          *
717          * @see VirtualMachineManager#getCapabilities
718          * @hide
719          */
720         @SystemApi
721         @NonNull
setProtectedVm(boolean protectedVm)722         public Builder setProtectedVm(boolean protectedVm) {
723             if (protectedVm) {
724                 if (!HypervisorProperties.hypervisor_protected_vm_supported().orElse(false)) {
725                     throw new UnsupportedOperationException(
726                             "Protected VMs are not supported on this device.");
727                 }
728             } else {
729                 if (!HypervisorProperties.hypervisor_vm_supported().orElse(false)) {
730                     throw new UnsupportedOperationException(
731                             "Non-protected VMs are not supported on this device.");
732                 }
733             }
734             mProtectedVm = protectedVm;
735             mProtectedVmSet = true;
736             return this;
737         }
738 
739         /**
740          * Sets the amount of RAM to give the VM, in bytes. If not explicitly set then a default
741          * size will be used.
742          *
743          * @hide
744          */
745         @SystemApi
746         @NonNull
setMemoryBytes(@ntRangefrom = 1) long memoryBytes)747         public Builder setMemoryBytes(@IntRange(from = 1) long memoryBytes) {
748             if (memoryBytes <= 0) {
749                 throw new IllegalArgumentException("Memory size must be positive");
750             }
751             mMemoryBytes = memoryBytes;
752             return this;
753         }
754 
755         /**
756          * Sets the CPU topology configuration of the VM. Defaults to {@link #CPU_TOPOLOGY_ONE_CPU}.
757          *
758          * <p>This determines how many virtual CPUs will be created, and their performance and
759          * scheduling characteristics, such as affinity masks. Topology also has an effect on memory
760          * usage as each vCPU requires additional memory to keep its state.
761          *
762          * @hide
763          */
764         @SystemApi
765         @NonNull
setCpuTopology(@puTopology int cpuTopology)766         public Builder setCpuTopology(@CpuTopology int cpuTopology) {
767             if (cpuTopology != CPU_TOPOLOGY_ONE_CPU && cpuTopology != CPU_TOPOLOGY_MATCH_HOST) {
768                 throw new IllegalArgumentException("Invalid cpuTopology: " + cpuTopology);
769             }
770             mCpuTopology = cpuTopology;
771             return this;
772         }
773 
774         /**
775          * Sets the size (in bytes) of encrypted storage available to the VM. If not set, no
776          * encrypted storage is provided.
777          *
778          * <p>The storage is encrypted with a key deterministically derived from the VM identity
779          *
780          * <p>The encrypted storage is persistent across VM reboots as well as device reboots. The
781          * backing file (containing encrypted data) is stored in the app's private data directory.
782          *
783          * <p>Note - There is no integrity guarantee or rollback protection on the storage in case
784          * the encrypted data is modified.
785          *
786          * <p>Deleting the VM will delete the encrypted data - there is no way to recover that data.
787          *
788          * @hide
789          */
790         @SystemApi
791         @NonNull
setEncryptedStorageBytes(@ntRangefrom = 1) long encryptedStorageBytes)792         public Builder setEncryptedStorageBytes(@IntRange(from = 1) long encryptedStorageBytes) {
793             if (encryptedStorageBytes <= 0) {
794                 throw new IllegalArgumentException("Encrypted Storage size must be positive");
795             }
796             mEncryptedStorageBytes = encryptedStorageBytes;
797             return this;
798         }
799 
800         /**
801          * Sets whether to allow the app to read the VM outputs (console / log). Default is {@code
802          * false}.
803          *
804          * <p>By default, console and log outputs of a {@linkplain #setDebugLevel debuggable} VM are
805          * automatically forwarded to the host logcat. Setting this as {@code true} will allow the
806          * app to directly read {@linkplain VirtualMachine#getConsoleOutput console output} and
807          * {@linkplain VirtualMachine#getLogOutput log output}, instead of forwarding them to the
808          * host logcat.
809          *
810          * <p>If you turn on output capture, you must consume data from {@link
811          * VirtualMachine#getConsoleOutput} and {@link VirtualMachine#getLogOutput} - because
812          * otherwise the code in the VM may get blocked when the pipe buffer fills up.
813          *
814          * <p>The {@linkplain #setDebugLevel debug level} must be {@link #DEBUG_LEVEL_FULL} to be
815          * set as true.
816          *
817          * @hide
818          */
819         @SystemApi
820         @NonNull
setVmOutputCaptured(boolean captured)821         public Builder setVmOutputCaptured(boolean captured) {
822             mVmOutputCaptured = captured;
823             return this;
824         }
825     }
826 }
827