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