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 import static android.system.virtualmachine.VirtualMachineCallback.ERROR_PAYLOAD_CHANGED; 23 import static android.system.virtualmachine.VirtualMachineCallback.ERROR_PAYLOAD_INVALID_CONFIG; 24 import static android.system.virtualmachine.VirtualMachineCallback.ERROR_PAYLOAD_VERIFICATION_FAILED; 25 import static android.system.virtualmachine.VirtualMachineCallback.ERROR_UNKNOWN; 26 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_CRASH; 27 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_HANGUP; 28 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_INFRASTRUCTURE_ERROR; 29 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_KILLED; 30 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE; 31 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG; 32 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_PAYLOAD_HAS_CHANGED; 33 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_PAYLOAD_VERIFICATION_FAILED; 34 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR; 35 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED; 36 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_PVM_FIRMWARE_PUBLIC_KEY_MISMATCH; 37 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_REBOOT; 38 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_SHUTDOWN; 39 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_START_FAILED; 40 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_UNKNOWN; 41 import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_VIRTUALIZATION_SERVICE_DIED; 42 43 import static java.util.Objects.requireNonNull; 44 45 import android.annotation.CallbackExecutor; 46 import android.annotation.IntDef; 47 import android.annotation.IntRange; 48 import android.annotation.NonNull; 49 import android.annotation.Nullable; 50 import android.annotation.RequiresPermission; 51 import android.annotation.SuppressLint; 52 import android.annotation.SystemApi; 53 import android.annotation.TestApi; 54 import android.annotation.WorkerThread; 55 import android.content.ComponentCallbacks2; 56 import android.content.Context; 57 import android.content.pm.ApplicationInfo; 58 import android.content.pm.PackageManager; 59 import android.content.res.Configuration; 60 import android.os.Binder; 61 import android.os.IBinder; 62 import android.os.ParcelFileDescriptor; 63 import android.os.RemoteException; 64 import android.os.ServiceSpecificException; 65 import android.system.ErrnoException; 66 import android.system.OsConstants; 67 import android.system.virtualizationcommon.DeathReason; 68 import android.system.virtualizationcommon.ErrorCode; 69 import android.system.virtualizationservice.IVirtualMachine; 70 import android.system.virtualizationservice.IVirtualMachineCallback; 71 import android.system.virtualizationservice.IVirtualizationService; 72 import android.system.virtualizationservice.InputDevice; 73 import android.system.virtualizationservice.PartitionType; 74 import android.system.virtualizationservice.VirtualMachineAppConfig; 75 import android.system.virtualizationservice.VirtualMachineRawConfig; 76 import android.system.virtualizationservice.VirtualMachineState; 77 import android.util.JsonReader; 78 import android.util.Log; 79 import android.util.Pair; 80 import android.view.KeyEvent; 81 import android.view.MotionEvent; 82 83 import com.android.internal.annotations.GuardedBy; 84 85 import libcore.io.IoBridge; 86 import libcore.io.IoUtils; 87 88 import java.io.File; 89 import java.io.FileDescriptor; 90 import java.io.FileInputStream; 91 import java.io.FileNotFoundException; 92 import java.io.FileOutputStream; 93 import java.io.IOException; 94 import java.io.InputStream; 95 import java.io.InputStreamReader; 96 import java.io.OutputStream; 97 import java.lang.annotation.Retention; 98 import java.lang.annotation.RetentionPolicy; 99 import java.nio.ByteBuffer; 100 import java.nio.ByteOrder; 101 import java.nio.channels.FileChannel; 102 import java.nio.charset.StandardCharsets; 103 import java.nio.file.FileAlreadyExistsException; 104 import java.nio.file.FileVisitResult; 105 import java.nio.file.Files; 106 import java.nio.file.Path; 107 import java.nio.file.SimpleFileVisitor; 108 import java.nio.file.attribute.BasicFileAttributes; 109 import java.util.ArrayList; 110 import java.util.Arrays; 111 import java.util.Collection; 112 import java.util.Collections; 113 import java.util.List; 114 import java.util.concurrent.BlockingQueue; 115 import java.util.concurrent.Executor; 116 import java.util.concurrent.ExecutorService; 117 import java.util.concurrent.Executors; 118 import java.util.concurrent.LinkedBlockingQueue; 119 import java.util.concurrent.atomic.AtomicBoolean; 120 import java.util.function.Consumer; 121 import java.util.zip.ZipFile; 122 123 /** 124 * Represents an VM instance, with its own configuration and state. Instances are persistent and are 125 * created or retrieved via {@link VirtualMachineManager}. 126 * 127 * <p>The {@link #run} method actually starts up the VM and allows the payload code to execute. It 128 * will continue until it exits or {@link #stop} is called. Updates on the state of the VM can be 129 * received using {@link #setCallback}. The app can communicate with the VM using {@link 130 * #connectToVsockServer} or {@link #connectVsock}. 131 * 132 * <p>The payload code running inside the VM has access to a set of native APIs; see the <a 133 * href="https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Virtualization/libs/libvm_payload/README.md">README 134 * file</a> for details. 135 * 136 * <p>Each VM has a unique secret, computed from the APK that contains the code running in it, the 137 * VM configuration, and a random per-instance salt. The secret can be accessed by the payload code 138 * running inside the VM (using {@code AVmPayload_getVmInstanceSecret}) but is not made available 139 * outside it. 140 * 141 * @hide 142 */ 143 @SystemApi 144 public class VirtualMachine implements AutoCloseable { 145 /** The permission needed to create or run a virtual machine. */ 146 public static final String MANAGE_VIRTUAL_MACHINE_PERMISSION = 147 "android.permission.MANAGE_VIRTUAL_MACHINE"; 148 149 /** 150 * The permission needed to create a virtual machine with more advanced configuration options. 151 */ 152 public static final String USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION = 153 "android.permission.USE_CUSTOM_VIRTUAL_MACHINE"; 154 155 /** 156 * The lowest port number that can be used to communicate with the virtual machine payload. 157 * 158 * @see #connectToVsockServer 159 * @see #connectVsock 160 */ 161 @SuppressLint("MinMaxConstant") // Won't change: see man 7 vsock. 162 public static final long MIN_VSOCK_PORT = 1024; 163 164 /** 165 * The highest port number that can be used to communicate with the virtual machine payload. 166 * 167 * @see #connectToVsockServer 168 * @see #connectVsock 169 */ 170 @SuppressLint("MinMaxConstant") // Won't change: see man 7 vsock. 171 public static final long MAX_VSOCK_PORT = (1L << 32) - 1; 172 173 private ParcelFileDescriptor mTouchSock; 174 private ParcelFileDescriptor mKeySock; 175 private ParcelFileDescriptor mMouseSock; 176 private ParcelFileDescriptor mSwitchesSock; 177 private ParcelFileDescriptor mTrackpadSock; 178 179 private enum InputEventType { 180 TOUCH, 181 MOUSE, 182 TRACKPAD 183 } 184 185 private BlockingQueue<Pair<InputEventType, MotionEvent>> mInputEventQueue = 186 new LinkedBlockingQueue<>(); 187 188 /** 189 * Status of a virtual machine 190 * 191 * @hide 192 */ 193 @Retention(RetentionPolicy.SOURCE) 194 @IntDef( 195 prefix = "STATUS_", 196 value = {STATUS_STOPPED, STATUS_RUNNING, STATUS_DELETED}) 197 public @interface Status {} 198 199 /** The virtual machine has just been created, or {@link #stop} was called on it. */ 200 public static final int STATUS_STOPPED = 0; 201 202 /** The virtual machine is running. */ 203 public static final int STATUS_RUNNING = 1; 204 205 /** 206 * The virtual machine has been deleted. This is an irreversible state. Once a virtual machine 207 * is deleted all its secrets are permanently lost, and it cannot be run. A new virtual machine 208 * with the same name and config may be created, with new and different secrets. 209 */ 210 public static final int STATUS_DELETED = 2; 211 212 private static final String TAG = "VirtualMachine"; 213 214 /** Name of the directory under the files directory where all VMs created for the app exist. */ 215 private static final String VM_DIR = "vm"; 216 217 /** Name of the persisted config file for a VM. */ 218 private static final String CONFIG_FILE = "config.xml"; 219 220 /** Name of the instance image file for a VM. (Not implemented) */ 221 private static final String INSTANCE_IMAGE_FILE = "instance.img"; 222 223 /** Name of the file for a VM containing Id. */ 224 private static final String INSTANCE_ID_FILE = "instance_id"; 225 226 /** Name of the idsig file for a VM */ 227 private static final String IDSIG_FILE = "idsig"; 228 229 /** Name of the idsig files for extra APKs. */ 230 private static final String EXTRA_IDSIG_FILE_PREFIX = "extra_idsig_"; 231 232 /** Size of the instance image. 10 MB. */ 233 private static final long INSTANCE_FILE_SIZE = 10 * 1024 * 1024; 234 235 /** Name of the file backing the encrypted storage */ 236 private static final String ENCRYPTED_STORE_FILE = "storage.img"; 237 238 /** The package which owns this VM. */ 239 @NonNull private final String mPackageName; 240 241 /** Name of this VM within the package. The name should be unique in the package. */ 242 @NonNull private final String mName; 243 244 /** Path to the directory containing all the files related to this VM. */ 245 @NonNull private final File mVmRootPath; 246 247 /** 248 * Path to the config file for this VM. The config file is where the configuration is persisted. 249 */ 250 @NonNull private final File mConfigFilePath; 251 252 /** Path to the instance image file for this VM. */ 253 @NonNull private final File mInstanceFilePath; 254 255 /** Path to the idsig file for this VM. */ 256 @NonNull private final File mIdsigFilePath; 257 258 /** File that backs the encrypted storage - Will be null if not enabled. */ 259 @Nullable private final File mEncryptedStoreFilePath; 260 261 /** File that contains the Id. This is NULL iff FEATURE_LLPVM is disabled */ 262 @Nullable private final File mInstanceIdPath; 263 264 /** 265 * Unmodifiable list of extra apks. Apks are specified by the vm config, and corresponding 266 * idsigs are to be generated. 267 */ 268 @NonNull private final List<ExtraApkSpec> mExtraApks; 269 270 private class MemoryManagementCallbacks implements ComponentCallbacks2 { 271 @Override onConfigurationChanged(@onNull Configuration newConfig)272 public void onConfigurationChanged(@NonNull Configuration newConfig) {} 273 274 @Override onLowMemory()275 public void onLowMemory() {} 276 277 @Override onTrimMemory(int level)278 public void onTrimMemory(int level) { 279 /* Treat level < TRIM_MEMORY_UI_HIDDEN as generic low-memory warnings */ 280 int percent = 10; 281 282 if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { 283 percent = 30; 284 } 285 286 if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) { 287 /* Release as much memory as we can. The app is on the LMKD LRU kill list. */ 288 percent = 50; 289 } 290 291 setMemoryBalloonByPercent(percent); 292 } 293 } 294 295 /** Running instance of virtmgr that hosts VirtualizationService for this VM. */ 296 @NonNull private final VirtualizationService mVirtualizationService; 297 298 private final MemoryManagementCallbacks mMemoryManagementCallbacks; 299 300 @NonNull private final Context mContext; 301 302 // A note on lock ordering: 303 // You can take mLock while holding VirtualMachineManager.sCreateLock, but not vice versa. 304 // We never take any other lock while holding mCallbackLock; therefore you can 305 // take mCallbackLock while holding any other lock. 306 307 /** Lock protecting our mutable state (other than callbacks). */ 308 private final Object mLock = new Object(); 309 310 /** Lock protecting callbacks. */ 311 private final Object mCallbackLock = new Object(); 312 313 private final boolean mVmOutputCaptured; 314 315 private final boolean mVmConsoleInputSupported; 316 317 private final boolean mConnectVmConsole; 318 319 private final Executor mConsoleExecutor = Executors.newSingleThreadExecutor(); 320 321 private ExecutorService mInputEventExecutor; 322 323 /** The configuration that is currently associated with this VM. */ 324 @GuardedBy("mLock") 325 @NonNull 326 private VirtualMachineConfig mConfig; 327 328 /** Handle to the "running" VM. */ 329 @GuardedBy("mLock") 330 @Nullable 331 private IVirtualMachine mVirtualMachine; 332 333 @GuardedBy("mLock") 334 @Nullable 335 private ParcelFileDescriptor mConsoleOutReader; 336 337 @GuardedBy("mLock") 338 @Nullable 339 private ParcelFileDescriptor mConsoleOutWriter; 340 341 @GuardedBy("mLock") 342 @Nullable 343 private ParcelFileDescriptor mConsoleInReader; 344 345 @GuardedBy("mLock") 346 @Nullable 347 private ParcelFileDescriptor mConsoleInWriter; 348 349 @GuardedBy("mLock") 350 @Nullable 351 private ParcelFileDescriptor mTeeConsoleOutReader; 352 353 @GuardedBy("mLock") 354 @Nullable 355 private ParcelFileDescriptor mTeeConsoleOutWriter; 356 357 @GuardedBy("mLock") 358 @Nullable 359 private ParcelFileDescriptor mPtyFd; 360 361 @GuardedBy("mLock") 362 @Nullable 363 private ParcelFileDescriptor mPtsFd; 364 365 @GuardedBy("mLock") 366 @Nullable 367 private String mPtsName; 368 369 @GuardedBy("mLock") 370 @Nullable 371 private ParcelFileDescriptor mLogReader; 372 373 @GuardedBy("mLock") 374 @Nullable 375 private ParcelFileDescriptor mLogWriter; 376 377 @GuardedBy("mLock") 378 private boolean mWasDeleted = false; 379 380 /** The registered callback */ 381 @GuardedBy("mCallbackLock") 382 @Nullable 383 private VirtualMachineCallback mCallback; 384 385 /** The executor on which the callback will be executed */ 386 @GuardedBy("mCallbackLock") 387 @Nullable 388 private Executor mCallbackExecutor; 389 390 private static class ExtraApkSpec { 391 public final File apk; 392 public final File idsig; 393 ExtraApkSpec(File apk, File idsig)394 ExtraApkSpec(File apk, File idsig) { 395 this.apk = apk; 396 this.idsig = idsig; 397 } 398 } 399 400 static { 401 System.loadLibrary("virtualmachine_jni"); 402 } 403 VirtualMachine( @onNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config, @NonNull VirtualizationService service)404 private VirtualMachine( 405 @NonNull Context context, 406 @NonNull String name, 407 @NonNull VirtualMachineConfig config, 408 @NonNull VirtualizationService service) 409 throws VirtualMachineException { 410 mPackageName = context.getPackageName(); 411 mName = requireNonNull(name, "Name must not be null"); 412 mConfig = requireNonNull(config, "Config must not be null"); 413 mVirtualizationService = service; 414 415 File thisVmDir = getVmDir(context, mName); 416 mVmRootPath = thisVmDir; 417 mConfigFilePath = new File(thisVmDir, CONFIG_FILE); 418 try { 419 mInstanceIdPath = 420 (mVirtualizationService 421 .getBinder() 422 .isFeatureEnabled(IVirtualizationService.FEATURE_LLPVM_CHANGES)) 423 ? new File(thisVmDir, INSTANCE_ID_FILE) 424 : null; 425 } catch (RemoteException e) { 426 throw e.rethrowAsRuntimeException(); 427 } 428 mInstanceFilePath = new File(thisVmDir, INSTANCE_IMAGE_FILE); 429 mIdsigFilePath = new File(thisVmDir, IDSIG_FILE); 430 mExtraApks = setupExtraApks(context, config, thisVmDir); 431 mContext = context; 432 mEncryptedStoreFilePath = 433 (config.isEncryptedStorageEnabled()) 434 ? new File(thisVmDir, ENCRYPTED_STORE_FILE) 435 : null; 436 437 mVmOutputCaptured = config.isVmOutputCaptured(); 438 mVmConsoleInputSupported = config.isVmConsoleInputSupported(); 439 mConnectVmConsole = config.isConnectVmConsole(); 440 441 VirtualMachineCustomImageConfig customImageConfig; 442 customImageConfig = config.getCustomImageConfig(); 443 if (customImageConfig == null || customImageConfig.useAutoMemoryBalloon()) { 444 mMemoryManagementCallbacks = new MemoryManagementCallbacks(); 445 } else { 446 mMemoryManagementCallbacks = null; 447 } 448 } 449 450 /** 451 * Creates a virtual machine from an {@link VirtualMachineDescriptor} object and associates it 452 * with the given name. 453 * 454 * <p>The new virtual machine will be in the same state as the descriptor indicates. 455 * 456 * <p>Once a virtual machine is imported it is persisted until it is deleted by calling {@link 457 * #delete}. The imported virtual machine is in {@link #STATUS_STOPPED} state. To run the VM, 458 * call {@link #run}. 459 */ 460 @GuardedBy("VirtualMachineManager.sCreateLock") 461 @NonNull fromDescriptor( @onNull Context context, @NonNull String name, @NonNull VirtualMachineDescriptor vmDescriptor)462 static VirtualMachine fromDescriptor( 463 @NonNull Context context, 464 @NonNull String name, 465 @NonNull VirtualMachineDescriptor vmDescriptor) 466 throws VirtualMachineException { 467 File vmDir = createVmDir(context, name); 468 try { 469 VirtualMachine vm; 470 try (vmDescriptor) { 471 VirtualMachineConfig config = VirtualMachineConfig.from(vmDescriptor.getConfigFd()); 472 vm = new VirtualMachine(context, name, config, VirtualizationService.getInstance()); 473 config.serialize(vm.mConfigFilePath); 474 try { 475 vm.mInstanceFilePath.createNewFile(); 476 } catch (IOException e) { 477 throw new VirtualMachineException("failed to create instance image", e); 478 } 479 vm.importInstanceFrom(vmDescriptor.getInstanceImgFd()); 480 481 if (vmDescriptor.getEncryptedStoreFd() != null) { 482 try { 483 vm.mEncryptedStoreFilePath.createNewFile(); 484 } catch (IOException e) { 485 throw new VirtualMachineException( 486 "failed to create encrypted storage image", e); 487 } 488 vm.importEncryptedStoreFrom(vmDescriptor.getEncryptedStoreFd()); 489 } 490 if (vm.mInstanceIdPath != null) { 491 vm.importInstanceIdFrom(vmDescriptor.getInstanceIdFd()); 492 vm.claimInstance(); 493 } 494 } 495 return vm; 496 } catch (VirtualMachineException | RuntimeException e) { 497 // If anything goes wrong, delete any files created so far and the VM's directory 498 try { 499 deleteRecursively(vmDir); 500 } catch (Exception innerException) { 501 e.addSuppressed(innerException); 502 } 503 throw e; 504 } 505 } 506 507 /** 508 * Creates a virtual machine with the given name and config. Once a virtual machine is created 509 * it is persisted until it is deleted by calling {@link #delete}. The created virtual machine 510 * is in {@link #STATUS_STOPPED} state. To run the VM, call {@link #run}. 511 */ 512 @GuardedBy("VirtualMachineManager.sCreateLock") 513 @NonNull create( @onNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config)514 static VirtualMachine create( 515 @NonNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config) 516 throws VirtualMachineException { 517 File vmDir = createVmDir(context, name); 518 519 try { 520 VirtualMachine vm = 521 new VirtualMachine(context, name, config, VirtualizationService.getInstance()); 522 config.serialize(vm.mConfigFilePath); 523 try { 524 vm.mInstanceFilePath.createNewFile(); 525 } catch (IOException e) { 526 throw new VirtualMachineException("failed to create instance image", e); 527 } 528 if (config.isEncryptedStorageEnabled()) { 529 try { 530 vm.mEncryptedStoreFilePath.createNewFile(); 531 } catch (IOException e) { 532 throw new VirtualMachineException( 533 "failed to create encrypted storage image", e); 534 } 535 } 536 537 IVirtualizationService service = vm.mVirtualizationService.getBinder(); 538 539 if (vm.mInstanceIdPath != null) { 540 try (FileOutputStream stream = new FileOutputStream(vm.mInstanceIdPath)) { 541 byte[] id = service.allocateInstanceId(); 542 stream.write(id); 543 } catch (FileNotFoundException e) { 544 throw new VirtualMachineException("instance_id file missing", e); 545 } catch (IOException e) { 546 throw new VirtualMachineException("failed to persist instance_id", e); 547 } catch (RemoteException e) { 548 throw e.rethrowAsRuntimeException(); 549 } catch (ServiceSpecificException | IllegalArgumentException e) { 550 throw new VirtualMachineException("failed to create instance_id", e); 551 } 552 } 553 554 try { 555 service.initializeWritablePartition( 556 ParcelFileDescriptor.open(vm.mInstanceFilePath, MODE_READ_WRITE), 557 INSTANCE_FILE_SIZE, 558 PartitionType.ANDROID_VM_INSTANCE); 559 } catch (FileNotFoundException e) { 560 throw new VirtualMachineException("instance image missing", e); 561 } catch (RemoteException e) { 562 throw e.rethrowAsRuntimeException(); 563 } catch (ServiceSpecificException | IllegalArgumentException e) { 564 throw new VirtualMachineException("failed to create instance partition", e); 565 } 566 567 if (config.isEncryptedStorageEnabled()) { 568 try { 569 service.initializeWritablePartition( 570 ParcelFileDescriptor.open(vm.mEncryptedStoreFilePath, MODE_READ_WRITE), 571 config.getEncryptedStorageBytes(), 572 PartitionType.ENCRYPTEDSTORE); 573 } catch (FileNotFoundException e) { 574 throw new VirtualMachineException("encrypted storage image missing", e); 575 } catch (RemoteException e) { 576 throw e.rethrowAsRuntimeException(); 577 } catch (ServiceSpecificException | IllegalArgumentException e) { 578 throw new VirtualMachineException( 579 "failed to create encrypted storage partition", e); 580 } 581 } 582 return vm; 583 } catch (VirtualMachineException | RuntimeException e) { 584 // If anything goes wrong, delete any files created so far and the VM's directory 585 try { 586 vmInstanceCleanup(context, name); 587 } catch (Exception innerException) { 588 e.addSuppressed(innerException); 589 } 590 throw e; 591 } 592 } 593 594 /** Loads a virtual machine that is already created before. */ 595 @GuardedBy("VirtualMachineManager.sCreateLock") 596 @Nullable load(@onNull Context context, @NonNull String name)597 static VirtualMachine load(@NonNull Context context, @NonNull String name) 598 throws VirtualMachineException { 599 File thisVmDir = getVmDir(context, name); 600 if (!thisVmDir.exists()) { 601 // The VM doesn't exist. 602 return null; 603 } 604 File configFilePath = new File(thisVmDir, CONFIG_FILE); 605 VirtualMachineConfig config = VirtualMachineConfig.from(configFilePath); 606 VirtualMachine vm = 607 new VirtualMachine(context, name, config, VirtualizationService.getInstance()); 608 609 if (vm.mInstanceIdPath != null && !vm.mInstanceIdPath.exists()) { 610 throw new VirtualMachineException("instance_id file missing"); 611 } 612 if (!vm.mInstanceFilePath.exists()) { 613 throw new VirtualMachineException("instance image missing"); 614 } 615 if (config.isEncryptedStorageEnabled() && !vm.mEncryptedStoreFilePath.exists()) { 616 throw new VirtualMachineException("Storage image missing"); 617 } 618 return vm; 619 } 620 621 @GuardedBy("VirtualMachineManager.sCreateLock") delete(Context context, String name)622 void delete(Context context, String name) throws VirtualMachineException { 623 synchronized (mLock) { 624 checkStopped(); 625 // Once we explicitly delete a VM it must remain permanently in the deleted state; 626 // if a new VM is created with the same name (and files) that's unrelated. 627 mWasDeleted = true; 628 } 629 vmInstanceCleanup(context, name); 630 } 631 632 // Delete the full VM directory and notify VirtualizationService to remove this 633 // VM instance for housekeeping. 634 @GuardedBy("VirtualMachineManager.sCreateLock") vmInstanceCleanup(Context context, String name)635 static void vmInstanceCleanup(Context context, String name) throws VirtualMachineException { 636 File vmDir = getVmDir(context, name); 637 notifyInstanceRemoval(vmDir, VirtualizationService.getInstance()); 638 try { 639 deleteRecursively(vmDir); 640 } catch (IOException e) { 641 throw new VirtualMachineException(e); 642 } 643 } 644 notifyInstanceRemoval( File vmDirectory, @NonNull VirtualizationService service)645 private static void notifyInstanceRemoval( 646 File vmDirectory, @NonNull VirtualizationService service) { 647 File instanceIdFile = new File(vmDirectory, INSTANCE_ID_FILE); 648 try { 649 byte[] instanceId = Files.readAllBytes(instanceIdFile.toPath()); 650 service.getBinder().removeVmInstance(instanceId); 651 } catch (Exception e) { 652 // Deliberately ignoring error in removing VM instance. This potentially leads to 653 // unaccounted instances in the VS' database. But, nothing much can be done by caller. 654 Log.w(TAG, "Failed to notify VS to remove the VM instance", e); 655 } 656 } 657 658 // Claim the instance. This notifies the global VS about the ownership of this 659 // instance_id for housekeeping purpose. claimInstance()660 void claimInstance() throws VirtualMachineException { 661 if (mInstanceIdPath != null) { 662 IVirtualizationService service = mVirtualizationService.getBinder(); 663 try { 664 byte[] instanceId = Files.readAllBytes(mInstanceIdPath.toPath()); 665 service.claimVmInstance(instanceId); 666 } catch (IOException e) { 667 throw new VirtualMachineException("failed to read instance_id", e); 668 } catch (RemoteException e) { 669 throw e.rethrowAsRuntimeException(); 670 } 671 } 672 } 673 674 @GuardedBy("VirtualMachineManager.sCreateLock") 675 @NonNull createVmDir(@onNull Context context, @NonNull String name)676 private static File createVmDir(@NonNull Context context, @NonNull String name) 677 throws VirtualMachineException { 678 File vmDir = getVmDir(context, name); 679 try { 680 // We don't need to undo this even if VM creation fails. 681 Files.createDirectories(vmDir.getParentFile().toPath()); 682 683 // The checking of the existence of this directory and the creation of it is done 684 // atomically. If the directory already exists (i.e. the VM with the same name was 685 // already created), FileAlreadyExistsException is thrown. 686 Files.createDirectory(vmDir.toPath()); 687 } catch (FileAlreadyExistsException e) { 688 throw new VirtualMachineException("virtual machine already exists", e); 689 } catch (IOException e) { 690 throw new VirtualMachineException("failed to create directory for VM", e); 691 } 692 return vmDir; 693 } 694 695 @NonNull getVmDir(@onNull Context context, @NonNull String name)696 private static File getVmDir(@NonNull Context context, @NonNull String name) { 697 if (name.contains(File.separator) || name.equals(".") || name.equals("..")) { 698 throw new IllegalArgumentException("Invalid VM name: " + name); 699 } 700 File vmRoot = new File(context.getDataDir(), VM_DIR); 701 return new File(vmRoot, name); 702 } 703 704 /** 705 * Returns the name of this virtual machine. The name is unique in the package and can't be 706 * changed. 707 * 708 * @hide 709 */ 710 @SystemApi 711 @NonNull getName()712 public String getName() { 713 return mName; 714 } 715 716 /** 717 * Returns the currently selected config of this virtual machine. There can be multiple virtual 718 * machines sharing the same config. Even in that case, the virtual machines are completely 719 * isolated from each other; they have different secrets. It is also possible that a virtual 720 * machine can change its config, which can be done by calling {@link #setConfig}. 721 * 722 * <p>NOTE: This method may block and should not be called on the main thread. 723 * 724 * @hide 725 */ 726 @SystemApi 727 @WorkerThread 728 @NonNull getConfig()729 public VirtualMachineConfig getConfig() { 730 synchronized (mLock) { 731 return mConfig; 732 } 733 } 734 735 /** 736 * Returns the current status of this virtual machine. 737 * 738 * <p>NOTE: This method may block and should not be called on the main thread. 739 * 740 * @hide 741 */ 742 @SystemApi 743 @WorkerThread 744 @Status getStatus()745 public int getStatus() { 746 IVirtualMachine virtualMachine; 747 synchronized (mLock) { 748 if (mWasDeleted) { 749 return STATUS_DELETED; 750 } 751 virtualMachine = mVirtualMachine; 752 } 753 754 int status; 755 if (virtualMachine == null) { 756 status = STATUS_STOPPED; 757 } else { 758 try { 759 status = stateToStatus(virtualMachine.getState()); 760 } catch (RemoteException e) { 761 status = STATUS_STOPPED; 762 } 763 } 764 if (status == STATUS_STOPPED && !mVmRootPath.exists()) { 765 // A VM can quite happily keep running if its backing files have been deleted. 766 // But once it stops, it's gone forever. 767 synchronized (mLock) { 768 dropVm(); 769 } 770 return STATUS_DELETED; 771 } 772 return status; 773 } 774 stateToStatus(@irtualMachineState int state)775 private int stateToStatus(@VirtualMachineState int state) { 776 switch (state) { 777 case VirtualMachineState.STARTING: 778 case VirtualMachineState.STARTED: 779 case VirtualMachineState.READY: 780 case VirtualMachineState.FINISHED: 781 return STATUS_RUNNING; 782 case VirtualMachineState.NOT_STARTED: 783 case VirtualMachineState.DEAD: 784 default: 785 return STATUS_STOPPED; 786 } 787 } 788 789 // Throw an appropriate exception if we have a running VM, or the VM has been deleted. 790 @GuardedBy("mLock") checkStopped()791 private void checkStopped() throws VirtualMachineException { 792 if (mWasDeleted || !mVmRootPath.exists()) { 793 throw new VirtualMachineException("VM has been deleted"); 794 } 795 if (mVirtualMachine == null) { 796 return; 797 } 798 try { 799 if (stateToStatus(mVirtualMachine.getState()) != STATUS_STOPPED) { 800 throw new VirtualMachineException("VM is not in stopped state"); 801 } 802 } catch (RemoteException e) { 803 throw e.rethrowAsRuntimeException(); 804 } 805 // It's stopped, but we still have a reference to it - we can fix that. 806 dropVm(); 807 } 808 809 /** 810 * This should only be called when we know our VM has stopped; we no longer need to hold a 811 * reference to it (this allows resources to be GC'd) and we no longer need to be informed of 812 * memory pressure. 813 */ 814 @GuardedBy("mLock") dropVm()815 private void dropVm() { 816 if (mInputEventExecutor != null) { 817 mInputEventExecutor.shutdownNow(); 818 mInputEventExecutor = null; 819 } 820 if (mMemoryManagementCallbacks != null) { 821 mContext.unregisterComponentCallbacks(mMemoryManagementCallbacks); 822 } 823 mVirtualMachine = null; 824 } 825 826 /** If we have an IVirtualMachine in the running state return it, otherwise throw. */ 827 @GuardedBy("mLock") getRunningVm()828 private IVirtualMachine getRunningVm() throws VirtualMachineException { 829 try { 830 if (mVirtualMachine != null 831 && stateToStatus(mVirtualMachine.getState()) == STATUS_RUNNING) { 832 return mVirtualMachine; 833 } else { 834 if (mWasDeleted || !mVmRootPath.exists()) { 835 throw new VirtualMachineException("VM has been deleted"); 836 } else { 837 throw new VirtualMachineException("VM is not in running state"); 838 } 839 } 840 } catch (RemoteException e) { 841 throw e.rethrowAsRuntimeException(); 842 } 843 } 844 845 /** 846 * Registers the callback object to get events from the virtual machine. If a callback was 847 * already registered, it is replaced with the new one. 848 * 849 * @hide 850 */ 851 @SystemApi setCallback( @onNull @allbackExecutor Executor executor, @NonNull VirtualMachineCallback callback)852 public void setCallback( 853 @NonNull @CallbackExecutor Executor executor, 854 @NonNull VirtualMachineCallback callback) { 855 synchronized (mCallbackLock) { 856 mCallback = callback; 857 mCallbackExecutor = executor; 858 } 859 } 860 861 /** 862 * Clears the currently registered callback. 863 * 864 * @hide 865 */ 866 @SystemApi clearCallback()867 public void clearCallback() { 868 synchronized (mCallbackLock) { 869 mCallback = null; 870 mCallbackExecutor = null; 871 } 872 } 873 874 /** Executes a callback on the callback executor. */ executeCallback(Consumer<VirtualMachineCallback> fn)875 private void executeCallback(Consumer<VirtualMachineCallback> fn) { 876 final VirtualMachineCallback callback; 877 final Executor executor; 878 synchronized (mCallbackLock) { 879 callback = mCallback; 880 executor = mCallbackExecutor; 881 } 882 if (callback == null || executor == null) { 883 return; 884 } 885 final long restoreToken = Binder.clearCallingIdentity(); 886 try { 887 executor.execute(() -> fn.accept(callback)); 888 } finally { 889 Binder.restoreCallingIdentity(restoreToken); 890 } 891 } 892 893 private android.system.virtualizationservice.VirtualMachineConfig createVirtualMachineConfigForRawFrom(VirtualMachineConfig vmConfig)894 createVirtualMachineConfigForRawFrom(VirtualMachineConfig vmConfig) 895 throws IllegalStateException, IOException { 896 VirtualMachineRawConfig rawConfig = vmConfig.toVsRawConfig(); 897 898 // Handle input devices here 899 List<InputDevice> inputDevices = new ArrayList<>(); 900 if (vmConfig.getCustomImageConfig() != null && rawConfig.displayConfig != null) { 901 if (vmConfig.getCustomImageConfig().useTouch()) { 902 ParcelFileDescriptor[] pfds = ParcelFileDescriptor.createSocketPair(); 903 mTouchSock = pfds[0]; 904 InputDevice.MultiTouch t = new InputDevice.MultiTouch(); 905 t.width = rawConfig.displayConfig.width; 906 t.height = rawConfig.displayConfig.height; 907 t.pfd = pfds[1]; 908 inputDevices.add(InputDevice.multiTouch(t)); 909 } 910 if (vmConfig.getCustomImageConfig().useKeyboard()) { 911 ParcelFileDescriptor[] pfds = ParcelFileDescriptor.createSocketPair(); 912 mKeySock = pfds[0]; 913 InputDevice.Keyboard k = new InputDevice.Keyboard(); 914 k.pfd = pfds[1]; 915 inputDevices.add(InputDevice.keyboard(k)); 916 } 917 if (vmConfig.getCustomImageConfig().useMouse()) { 918 ParcelFileDescriptor[] pfds = ParcelFileDescriptor.createSocketPair(); 919 mMouseSock = pfds[0]; 920 InputDevice.Mouse m = new InputDevice.Mouse(); 921 m.pfd = pfds[1]; 922 inputDevices.add(InputDevice.mouse(m)); 923 } 924 if (vmConfig.getCustomImageConfig().useSwitches()) { 925 ParcelFileDescriptor[] pfds = ParcelFileDescriptor.createSocketPair(); 926 mSwitchesSock = pfds[0]; 927 InputDevice.Switches s = new InputDevice.Switches(); 928 s.pfd = pfds[1]; 929 inputDevices.add(InputDevice.switches(s)); 930 } 931 if (vmConfig.getCustomImageConfig().useTrackpad()) { 932 ParcelFileDescriptor[] pfds = ParcelFileDescriptor.createSocketPair(); 933 mTrackpadSock = pfds[0]; 934 InputDevice.Trackpad t = new InputDevice.Trackpad(); 935 // TODO(b/347253952): make it configurable 936 t.width = 2380; 937 t.height = 1369; 938 t.pfd = pfds[1]; 939 inputDevices.add(InputDevice.trackpad(t)); 940 } 941 } 942 rawConfig.inputDevices = inputDevices.toArray(new InputDevice[0]); 943 944 // Handle network support 945 if (vmConfig.getCustomImageConfig() != null) { 946 rawConfig.networkSupported = vmConfig.getCustomImageConfig().useNetwork(); 947 } 948 949 return android.system.virtualizationservice.VirtualMachineConfig.rawConfig(rawConfig); 950 } 951 InputEvent(short type, short code, int value)952 private static record InputEvent(short type, short code, int value) {} 953 954 /** @hide */ sendKeyEvent(KeyEvent event)955 public boolean sendKeyEvent(KeyEvent event) { 956 if (mKeySock == null) { 957 Log.d(TAG, "mKeySock == null"); 958 return false; 959 } 960 // from include/uapi/linux/input-event-codes.h in the kernel. 961 short EV_SYN = 0x00; 962 short EV_KEY = 0x01; 963 short SYN_REPORT = 0x00; 964 boolean down = event.getAction() != MotionEvent.ACTION_UP; 965 966 return writeEventsToSock( 967 mKeySock, 968 Arrays.asList( 969 new InputEvent(EV_KEY, (short) event.getScanCode(), down ? 1 : 0), 970 new InputEvent(EV_SYN, SYN_REPORT, 0))); 971 } 972 973 /** @hide */ sendMouseEvent(MotionEvent event)974 public boolean sendMouseEvent(MotionEvent event) { 975 try { 976 mInputEventQueue.add( 977 Pair.create(InputEventType.MOUSE, MotionEvent.obtainNoHistory(event))); 978 return true; 979 } catch (Exception e) { 980 Log.e(TAG, e.toString()); 981 return false; 982 } 983 } 984 985 /** @hide */ sendMouseEventInternal(MotionEvent event)986 private boolean sendMouseEventInternal(MotionEvent event) { 987 if (mMouseSock == null) { 988 Log.d(TAG, "mMouseSock == null"); 989 return false; 990 } 991 // from include/uapi/linux/input-event-codes.h in the kernel. 992 short EV_SYN = 0x00; 993 short EV_REL = 0x02; 994 short EV_KEY = 0x01; 995 short REL_X = 0x00; 996 short REL_Y = 0x01; 997 short SYN_REPORT = 0x00; 998 switch (event.getAction()) { 999 case MotionEvent.ACTION_MOVE: 1000 int x = (int) event.getX(); 1001 int y = (int) event.getY(); 1002 return writeEventsToSock( 1003 mMouseSock, 1004 Arrays.asList( 1005 new InputEvent(EV_REL, REL_X, x), 1006 new InputEvent(EV_REL, REL_Y, y), 1007 new InputEvent(EV_SYN, SYN_REPORT, 0))); 1008 case MotionEvent.ACTION_BUTTON_PRESS: 1009 case MotionEvent.ACTION_BUTTON_RELEASE: 1010 short BTN_LEFT = 0x110; 1011 short BTN_RIGHT = 0x111; 1012 short BTN_MIDDLE = 0x112; 1013 short keyCode; 1014 switch (event.getActionButton()) { 1015 case MotionEvent.BUTTON_PRIMARY: 1016 keyCode = BTN_LEFT; 1017 break; 1018 case MotionEvent.BUTTON_SECONDARY: 1019 keyCode = BTN_RIGHT; 1020 break; 1021 case MotionEvent.BUTTON_TERTIARY: 1022 keyCode = BTN_MIDDLE; 1023 break; 1024 default: 1025 Log.d(TAG, event.toString()); 1026 return false; 1027 } 1028 return writeEventsToSock( 1029 mMouseSock, 1030 Arrays.asList( 1031 new InputEvent( 1032 EV_KEY, 1033 keyCode, 1034 event.getAction() == MotionEvent.ACTION_BUTTON_PRESS 1035 ? 1 1036 : 0), 1037 new InputEvent(EV_SYN, SYN_REPORT, 0))); 1038 case MotionEvent.ACTION_SCROLL: 1039 short REL_HWHEEL = 0x06; 1040 short REL_WHEEL = 0x08; 1041 int scrollX = (int) event.getAxisValue(MotionEvent.AXIS_HSCROLL); 1042 int scrollY = (int) event.getAxisValue(MotionEvent.AXIS_VSCROLL); 1043 boolean status = true; 1044 if (scrollX != 0) { 1045 status &= 1046 writeEventsToSock( 1047 mMouseSock, 1048 Arrays.asList( 1049 new InputEvent(EV_REL, REL_HWHEEL, scrollX), 1050 new InputEvent(EV_SYN, SYN_REPORT, 0))); 1051 } else if (scrollY != 0) { 1052 status &= 1053 writeEventsToSock( 1054 mMouseSock, 1055 Arrays.asList( 1056 new InputEvent(EV_REL, REL_WHEEL, scrollY), 1057 new InputEvent(EV_SYN, SYN_REPORT, 0))); 1058 } else { 1059 Log.d(TAG, event.toString()); 1060 return false; 1061 } 1062 return status; 1063 case MotionEvent.ACTION_UP: 1064 case MotionEvent.ACTION_DOWN: 1065 // Ignored because it's handled by ACTION_BUTTON_PRESS and ACTION_BUTTON_RELEASE 1066 return true; 1067 default: 1068 Log.d(TAG, event.toString()); 1069 return false; 1070 } 1071 } 1072 1073 /** @hide */ sendMultiTouchEvent(MotionEvent event)1074 public boolean sendMultiTouchEvent(MotionEvent event) { 1075 try { 1076 mInputEventQueue.add( 1077 Pair.create(InputEventType.TOUCH, MotionEvent.obtainNoHistory(event))); 1078 return true; 1079 } catch (Exception e) { 1080 Log.e(TAG, e.toString()); 1081 return false; 1082 } 1083 } 1084 1085 /** @hide */ sendMultiTouchEventInternal(MotionEvent event)1086 private boolean sendMultiTouchEventInternal(MotionEvent event) { 1087 if (mTouchSock == null) { 1088 Log.d(TAG, "mTouchSock == null"); 1089 return false; 1090 } 1091 // from include/uapi/linux/input-event-codes.h in the kernel. 1092 short EV_SYN = 0x00; 1093 short EV_ABS = 0x03; 1094 short EV_KEY = 0x01; 1095 short BTN_TOUCH = 0x14a; 1096 short ABS_X = 0x00; 1097 short ABS_Y = 0x01; 1098 short SYN_REPORT = 0x00; 1099 short ABS_MT_SLOT = 0x2f; 1100 short ABS_MT_POSITION_X = 0x35; 1101 short ABS_MT_POSITION_Y = 0x36; 1102 short ABS_MT_TRACKING_ID = 0x39; 1103 1104 switch (event.getActionMasked()) { 1105 case MotionEvent.ACTION_MOVE: 1106 List<InputEvent> events = 1107 new ArrayList<>( 1108 event.getPointerCount() * 6 /*InputEvent per a pointer*/ 1109 + 1 /*SYN*/); 1110 for (int actionIdx = 0; actionIdx < event.getPointerCount(); actionIdx++) { 1111 int pointerId = event.getPointerId(actionIdx); 1112 int x = (int) event.getRawX(actionIdx); 1113 int y = (int) event.getRawY(actionIdx); 1114 events.add(new InputEvent(EV_ABS, ABS_MT_SLOT, pointerId)); 1115 events.add(new InputEvent(EV_ABS, ABS_MT_TRACKING_ID, pointerId)); 1116 events.add(new InputEvent(EV_ABS, ABS_MT_POSITION_X, x)); 1117 events.add(new InputEvent(EV_ABS, ABS_MT_POSITION_Y, y)); 1118 events.add(new InputEvent(EV_ABS, ABS_X, x)); 1119 events.add(new InputEvent(EV_ABS, ABS_Y, y)); 1120 } 1121 events.add(new InputEvent(EV_SYN, SYN_REPORT, 0)); 1122 return writeEventsToSock(mTouchSock, events); 1123 case MotionEvent.ACTION_DOWN: 1124 case MotionEvent.ACTION_POINTER_DOWN: 1125 case MotionEvent.ACTION_UP: 1126 case MotionEvent.ACTION_POINTER_UP: 1127 break; 1128 default: 1129 return false; 1130 } 1131 1132 boolean down = 1133 event.getActionMasked() == MotionEvent.ACTION_DOWN 1134 || event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN; 1135 int actionIdx = event.getActionIndex(); 1136 int pointerId = event.getPointerId(actionIdx); 1137 int x = (int) event.getRawX(actionIdx); 1138 int y = (int) event.getRawY(actionIdx); 1139 return writeEventsToSock( 1140 mTouchSock, 1141 Arrays.asList( 1142 new InputEvent(EV_KEY, BTN_TOUCH, down ? 1 : 0), 1143 new InputEvent(EV_ABS, ABS_MT_SLOT, pointerId), 1144 new InputEvent(EV_ABS, ABS_MT_TRACKING_ID, down ? pointerId : -1), 1145 new InputEvent(EV_ABS, ABS_MT_POSITION_X, x), 1146 new InputEvent(EV_ABS, ABS_MT_POSITION_Y, y), 1147 new InputEvent(EV_ABS, ABS_X, x), 1148 new InputEvent(EV_ABS, ABS_Y, y), 1149 new InputEvent(EV_SYN, SYN_REPORT, 0))); 1150 } 1151 1152 /** @hide */ sendLidEvent(boolean close)1153 public boolean sendLidEvent(boolean close) { 1154 if (mSwitchesSock == null) { 1155 Log.d(TAG, "mSwitcheSock == null"); 1156 return false; 1157 } 1158 1159 // from include/uapi/linux/input-event-codes.h in the kernel. 1160 short EV_SYN = 0x00; 1161 short EV_SW = 0x05; 1162 short SW_LID = 0x00; 1163 short SYN_REPORT = 0x00; 1164 return writeEventsToSock( 1165 mSwitchesSock, 1166 Arrays.asList( 1167 new InputEvent(EV_SW, SW_LID, close ? 1 : 0), 1168 new InputEvent(EV_SYN, SYN_REPORT, 0))); 1169 } 1170 1171 /** @hide */ sendTabletModeEvent(boolean tabletMode)1172 public boolean sendTabletModeEvent(boolean tabletMode) { 1173 if (mSwitchesSock == null) { 1174 Log.d(TAG, "mSwitcheSock == null"); 1175 return false; 1176 } 1177 1178 // from include/uapi/linux/input-event-codes.h in the kernel. 1179 short EV_SYN = 0x00; 1180 short EV_SW = 0x05; 1181 short SW_TABLET_MODE = 0x01; 1182 short SYN_REPORT = 0x00; 1183 return writeEventsToSock( 1184 mSwitchesSock, 1185 Arrays.asList( 1186 new InputEvent(EV_SW, SW_TABLET_MODE, tabletMode ? 1 : 0), 1187 new InputEvent(EV_SYN, SYN_REPORT, 0))); 1188 } 1189 1190 /** @hide */ sendTrackpadEvent(MotionEvent event)1191 public boolean sendTrackpadEvent(MotionEvent event) { 1192 try { 1193 mInputEventQueue.add( 1194 Pair.create(InputEventType.TRACKPAD, MotionEvent.obtainNoHistory(event))); 1195 return true; 1196 } catch (Exception e) { 1197 Log.e(TAG, e.toString()); 1198 return false; 1199 } 1200 } 1201 1202 /** @hide */ sendTrackpadEventInternal(MotionEvent event)1203 private boolean sendTrackpadEventInternal(MotionEvent event) { 1204 if (mTrackpadSock == null) { 1205 Log.d(TAG, "mTrackpadSock == null"); 1206 return false; 1207 } 1208 // from include/uapi/linux/input-event-codes.h in the kernel. 1209 short EV_SYN = 0x00; 1210 short EV_ABS = 0x03; 1211 short EV_KEY = 0x01; 1212 short BTN_TOUCH = 0x14a; 1213 short BTN_TOOL_FINGER = 0x145; 1214 short BTN_TOOL_DOUBLETAP = 0x14d; 1215 short BTN_TOOL_TRIPLETAP = 0x14e; 1216 short BTN_TOOL_QUADTAP = 0x14f; 1217 short ABS_X = 0x00; 1218 short ABS_Y = 0x01; 1219 short SYN_REPORT = 0x00; 1220 short ABS_MT_SLOT = 0x2f; 1221 short ABS_MT_TOUCH_MAJOR = 0x30; 1222 short ABS_MT_TOUCH_MINOR = 0x31; 1223 short ABS_MT_WIDTH_MAJOR = 0x32; 1224 short ABS_MT_WIDTH_MINOR = 0x33; 1225 short ABS_MT_ORIENTATION = 0x34; 1226 short ABS_MT_POSITION_X = 0x35; 1227 short ABS_MT_POSITION_Y = 0x36; 1228 short ABS_MT_TOOL_TYPE = 0x37; 1229 short ABS_MT_BLOB_ID = 0x38; 1230 short ABS_MT_TRACKING_ID = 0x39; 1231 short ABS_MT_PRESSURE = 0x3a; 1232 short ABS_MT_DISTANCE = 0x3b; 1233 short ABS_MT_TOOL_X = 0x3c; 1234 short ABS_MT_TOOL_Y = 0x3d; 1235 short ABS_PRESSURE = 0x18; 1236 short ABS_TOOL_WIDTH = 0x1c; 1237 1238 switch (event.getActionMasked()) { 1239 case MotionEvent.ACTION_BUTTON_PRESS: 1240 case MotionEvent.ACTION_BUTTON_RELEASE: 1241 short BTN_LEFT = 0x110; 1242 short keyCode; 1243 switch (event.getActionButton()) { 1244 case MotionEvent.BUTTON_PRIMARY: 1245 keyCode = BTN_LEFT; 1246 break; 1247 default: 1248 Log.d(TAG, event.toString()); 1249 return false; 1250 } 1251 return writeEventsToSock( 1252 mMouseSock, 1253 Arrays.asList( 1254 new InputEvent( 1255 EV_KEY, 1256 keyCode, 1257 event.getAction() == MotionEvent.ACTION_BUTTON_PRESS 1258 ? 1 1259 : 0), 1260 new InputEvent(EV_SYN, SYN_REPORT, 0))); 1261 case MotionEvent.ACTION_MOVE: 1262 List<InputEvent> events = 1263 new ArrayList<>( 1264 event.getPointerCount() * 10 /*InputEvent per a pointer*/ 1265 + 1 /*SYN*/); 1266 for (int actionIdx = 0; actionIdx < event.getPointerCount(); actionIdx++) { 1267 int pointerId = event.getPointerId(actionIdx); 1268 int x = (int) event.getRawX(actionIdx); 1269 int y = (int) event.getRawY(actionIdx); 1270 events.add(new InputEvent(EV_ABS, ABS_MT_SLOT, pointerId)); 1271 events.add(new InputEvent(EV_ABS, ABS_MT_TRACKING_ID, pointerId)); 1272 events.add(new InputEvent(EV_ABS, ABS_MT_POSITION_X, x)); 1273 events.add(new InputEvent(EV_ABS, ABS_MT_POSITION_Y, y)); 1274 events.add( 1275 new InputEvent( 1276 EV_ABS, 1277 ABS_MT_TOUCH_MAJOR, 1278 (short) event.getTouchMajor(actionIdx))); 1279 events.add( 1280 new InputEvent( 1281 EV_ABS, 1282 ABS_MT_TOUCH_MINOR, 1283 (short) event.getTouchMinor(actionIdx))); 1284 events.add(new InputEvent(EV_ABS, ABS_X, x)); 1285 events.add(new InputEvent(EV_ABS, ABS_Y, y)); 1286 events.add( 1287 new InputEvent( 1288 EV_ABS, 1289 ABS_PRESSURE, 1290 (short) (255 * event.getPressure(actionIdx)))); 1291 events.add( 1292 new InputEvent( 1293 EV_ABS, 1294 ABS_MT_PRESSURE, 1295 (short) (255 * event.getPressure(actionIdx)))); 1296 } 1297 events.add(new InputEvent(EV_SYN, SYN_REPORT, 0)); 1298 return writeEventsToSock(mTrackpadSock, events); 1299 case MotionEvent.ACTION_DOWN: 1300 case MotionEvent.ACTION_POINTER_DOWN: 1301 case MotionEvent.ACTION_UP: 1302 case MotionEvent.ACTION_POINTER_UP: 1303 break; 1304 default: 1305 return false; 1306 } 1307 1308 boolean down = 1309 event.getActionMasked() == MotionEvent.ACTION_DOWN 1310 || event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN; 1311 int actionIdx = event.getActionIndex(); 1312 int pointerId = event.getPointerId(actionIdx); 1313 int x = (int) event.getRawX(actionIdx); 1314 int y = (int) event.getRawY(actionIdx); 1315 return writeEventsToSock( 1316 mTrackpadSock, 1317 Arrays.asList( 1318 new InputEvent(EV_KEY, BTN_TOUCH, down ? 1 : 0), 1319 new InputEvent( 1320 EV_KEY, 1321 BTN_TOOL_FINGER, 1322 down && event.getPointerCount() == 1 ? 1 : 0), 1323 new InputEvent( 1324 EV_KEY, BTN_TOOL_DOUBLETAP, event.getPointerCount() == 2 ? 1 : 0), 1325 new InputEvent( 1326 EV_KEY, BTN_TOOL_TRIPLETAP, event.getPointerCount() == 3 ? 1 : 0), 1327 new InputEvent( 1328 EV_KEY, BTN_TOOL_QUADTAP, event.getPointerCount() > 4 ? 1 : 0), 1329 new InputEvent(EV_ABS, ABS_MT_SLOT, pointerId), 1330 new InputEvent(EV_ABS, ABS_MT_TRACKING_ID, down ? pointerId : -1), 1331 new InputEvent(EV_ABS, ABS_MT_TOOL_TYPE, 0 /* MT_TOOL_FINGER */), 1332 new InputEvent(EV_ABS, ABS_MT_POSITION_X, x), 1333 new InputEvent(EV_ABS, ABS_MT_POSITION_Y, y), 1334 new InputEvent( 1335 EV_ABS, ABS_MT_TOUCH_MAJOR, (short) event.getTouchMajor(actionIdx)), 1336 new InputEvent( 1337 EV_ABS, ABS_MT_TOUCH_MINOR, (short) event.getTouchMinor(actionIdx)), 1338 new InputEvent(EV_ABS, ABS_X, x), 1339 new InputEvent(EV_ABS, ABS_Y, y), 1340 new InputEvent( 1341 EV_ABS, ABS_PRESSURE, (short) (255 * event.getPressure(actionIdx))), 1342 new InputEvent( 1343 EV_ABS, 1344 ABS_MT_PRESSURE, 1345 (short) (255 * event.getPressure(actionIdx))), 1346 new InputEvent(EV_SYN, SYN_REPORT, 0))); 1347 } 1348 1349 /** @hide */ getMemoryBalloon()1350 public long getMemoryBalloon() { 1351 long bytes = 0; 1352 1353 if (mMemoryManagementCallbacks != null) { 1354 Log.d(TAG, "Auto balloon enabled in getMemoryBalloon"); 1355 return bytes; 1356 } 1357 1358 synchronized (mLock) { 1359 try { 1360 if (mVirtualMachine != null) { 1361 bytes = mVirtualMachine.getMemoryBalloon(); 1362 } 1363 } catch (RemoteException e) { 1364 Log.w(TAG, "Cannot getMemoryBalloon", e); 1365 } 1366 } 1367 1368 return bytes; 1369 } 1370 1371 /** @hide */ setMemoryBalloon(long bytes)1372 public void setMemoryBalloon(long bytes) { 1373 if (mMemoryManagementCallbacks != null) { 1374 Log.d(TAG, "Auto balloon enabled in setMemoryBalloon"); 1375 return; 1376 } 1377 1378 synchronized (mLock) { 1379 try { 1380 if (mVirtualMachine != null) { 1381 mVirtualMachine.setMemoryBalloon(bytes); 1382 } 1383 } catch (RemoteException e) { 1384 Log.w(TAG, "Cannot setMemoryBalloon", e); 1385 } 1386 } 1387 } 1388 1389 /** @hide */ setMemoryBalloonByPercent(int percent)1390 public void setMemoryBalloonByPercent(int percent) { 1391 if (percent < 0 || percent > 100) { 1392 Log.e(TAG, String.format("Invalid percent value: %d", percent)); 1393 return; 1394 } 1395 synchronized (mLock) { 1396 try { 1397 if (mVirtualMachine != null && mVirtualMachine.isMemoryBalloonEnabled()) { 1398 long bytes = mConfig.getMemoryBytes(); 1399 mVirtualMachine.setMemoryBalloon(bytes * percent / 100); 1400 } 1401 } catch (RemoteException | ServiceSpecificException e) { 1402 Log.w(TAG, "Cannot setMemoryBalloon", e); 1403 } 1404 } 1405 } 1406 writeEventsToSock(ParcelFileDescriptor sock, List<InputEvent> evtList)1407 private boolean writeEventsToSock(ParcelFileDescriptor sock, List<InputEvent> evtList) { 1408 ByteBuffer byteBuffer = 1409 ByteBuffer.allocate(8 /* (type: u16 + code: u16 + value: i32) */ * evtList.size()); 1410 byteBuffer.clear(); 1411 byteBuffer.order(ByteOrder.LITTLE_ENDIAN); 1412 for (InputEvent e : evtList) { 1413 byteBuffer.putShort(e.type); 1414 byteBuffer.putShort(e.code); 1415 byteBuffer.putInt(e.value); 1416 } 1417 try { 1418 IoBridge.write( 1419 sock.getFileDescriptor(), byteBuffer.array(), 0, byteBuffer.array().length); 1420 } catch (IOException e) { 1421 Log.d(TAG, "cannot send event", e); 1422 return false; 1423 } 1424 return true; 1425 } 1426 1427 private android.system.virtualizationservice.VirtualMachineConfig createVirtualMachineConfigForAppFrom( VirtualMachineConfig vmConfig, IVirtualizationService service)1428 createVirtualMachineConfigForAppFrom( 1429 VirtualMachineConfig vmConfig, IVirtualizationService service) 1430 throws RemoteException, IOException, VirtualMachineException { 1431 VirtualMachineAppConfig appConfig = vmConfig.toVsConfig(mContext.getPackageManager()); 1432 appConfig.instanceImage = ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_WRITE); 1433 appConfig.name = mName; 1434 if (mInstanceIdPath != null) { 1435 appConfig.instanceId = Files.readAllBytes(mInstanceIdPath.toPath()); 1436 } else { 1437 // FEATURE_LLPVM_CHANGES is disabled, instance_id is not used. 1438 appConfig.instanceId = new byte[64]; 1439 } 1440 if (mEncryptedStoreFilePath != null) { 1441 appConfig.encryptedStorageImage = 1442 ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_WRITE); 1443 } 1444 1445 if (!vmConfig.getExtraApks().isEmpty()) { 1446 // Extra APKs were specified directly, rather than via config file. 1447 // We've already populated the file names for the extra APKs and IDSigs 1448 // (via setupExtraApks). But we also need to open the APK files and add 1449 // fds for them to the payload config. 1450 // This isn't needed when the extra APKs are specified in a config file - 1451 // then 1452 // Virtualization Manager opens them itself. 1453 List<ParcelFileDescriptor> extraApkFiles = new ArrayList<>(mExtraApks.size()); 1454 for (ExtraApkSpec extraApk : mExtraApks) { 1455 try { 1456 extraApkFiles.add(ParcelFileDescriptor.open(extraApk.apk, MODE_READ_ONLY)); 1457 } catch (FileNotFoundException e) { 1458 throw new VirtualMachineException("Failed to open extra APK", e); 1459 } 1460 } 1461 appConfig.payload.getPayloadConfig().extraApks = extraApkFiles; 1462 } 1463 1464 try { 1465 createIdSigsAndUpdateConfig(service, appConfig); 1466 } catch (FileNotFoundException e) { 1467 throw new VirtualMachineException("Failed to generate APK signature", e); 1468 } 1469 return android.system.virtualizationservice.VirtualMachineConfig.appConfig(appConfig); 1470 } 1471 1472 /** 1473 * Runs this virtual machine. The returning of this method however doesn't mean that the VM has 1474 * actually started running or the OS has booted there. Such events can be notified by 1475 * registering a callback using {@link #setCallback} before calling {@code run()}. There is no 1476 * limit other than available memory that limits the number of virtual machines that can run at 1477 * the same time. 1478 * 1479 * <p>NOTE: This method may block and should not be called on the main thread. 1480 * 1481 * @throws VirtualMachineException if the virtual machine is not stopped or could not be 1482 * started. 1483 * @hide 1484 */ 1485 @SystemApi 1486 @WorkerThread 1487 @RequiresPermission(MANAGE_VIRTUAL_MACHINE_PERMISSION) run()1488 public void run() throws VirtualMachineException { 1489 synchronized (mLock) { 1490 checkStopped(); 1491 1492 try { 1493 mIdsigFilePath.createNewFile(); 1494 for (ExtraApkSpec extraApk : mExtraApks) { 1495 extraApk.idsig.createNewFile(); 1496 } 1497 } catch (IOException e) { 1498 // If the file already exists, exception is not thrown. 1499 throw new VirtualMachineException("Failed to create APK signature file", e); 1500 } 1501 1502 IVirtualizationService service = mVirtualizationService.getBinder(); 1503 1504 try { 1505 if (mConnectVmConsole) { 1506 createPtyConsole(); 1507 } 1508 1509 if (mVmOutputCaptured) { 1510 createVmOutputPipes(); 1511 } 1512 1513 if (mVmConsoleInputSupported) { 1514 createVmInputPipes(); 1515 } 1516 1517 ParcelFileDescriptor consoleOutFd = null; 1518 if (mConnectVmConsole && mVmOutputCaptured) { 1519 // If we are enabling output pipes AND the host console, then we tee the console 1520 // output to both. 1521 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); 1522 mTeeConsoleOutReader = pipe[0]; 1523 mTeeConsoleOutWriter = pipe[1]; 1524 consoleOutFd = mTeeConsoleOutWriter; 1525 TeeWorker tee = 1526 new TeeWorker( 1527 mName + " console", 1528 new FileInputStream(mTeeConsoleOutReader.getFileDescriptor()), 1529 List.of( 1530 new FileOutputStream(mPtyFd.getFileDescriptor()), 1531 new FileOutputStream( 1532 mConsoleOutWriter.getFileDescriptor()))); 1533 // If the VM is stopped then the tee worker thread would get an EOF or read() 1534 // error which would tear down itself. 1535 mConsoleExecutor.execute(tee); 1536 } else if (mConnectVmConsole) { 1537 consoleOutFd = mPtyFd; 1538 } else if (mVmOutputCaptured) { 1539 consoleOutFd = mConsoleOutWriter; 1540 } 1541 mInputEventExecutor = Executors.newSingleThreadExecutor(); 1542 mInputEventExecutor.execute( 1543 () -> { 1544 while (true) { 1545 try { 1546 Pair<InputEventType, MotionEvent> event = 1547 mInputEventQueue.take(); 1548 switch (event.first) { 1549 case TOUCH: 1550 sendMultiTouchEventInternal(event.second); 1551 break; 1552 case TRACKPAD: 1553 sendTrackpadEventInternal(event.second); 1554 break; 1555 case MOUSE: 1556 sendMouseEventInternal(event.second); 1557 break; 1558 } 1559 event.second.recycle(); 1560 } catch (Exception e) { 1561 Log.e(TAG, e.toString()); 1562 } 1563 } 1564 }); 1565 ParcelFileDescriptor consoleInFd = null; 1566 if (mConnectVmConsole) { 1567 consoleInFd = mPtyFd; 1568 } else if (mVmConsoleInputSupported) { 1569 consoleInFd = mConsoleInReader; 1570 } 1571 1572 VirtualMachineConfig vmConfig = getConfig(); 1573 android.system.virtualizationservice.VirtualMachineConfig vmConfigParcel = 1574 vmConfig.getCustomImageConfig() != null 1575 ? createVirtualMachineConfigForRawFrom(vmConfig) 1576 : createVirtualMachineConfigForAppFrom(vmConfig, service); 1577 1578 if (vmConfig.isEncryptedStorageEnabled()) { 1579 service.setEncryptedStorageSize( 1580 ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_WRITE), 1581 vmConfig.getEncryptedStorageBytes()); 1582 } 1583 1584 mVirtualMachine = 1585 service.createVm( 1586 vmConfigParcel, consoleOutFd, consoleInFd, mLogWriter, null); 1587 mVirtualMachine.registerCallback(new CallbackTranslator(service)); 1588 if (mMemoryManagementCallbacks != null) { 1589 mContext.registerComponentCallbacks(mMemoryManagementCallbacks); 1590 } 1591 if (mConnectVmConsole) { 1592 mVirtualMachine.setHostConsoleName(getHostConsoleName()); 1593 } 1594 mVirtualMachine.start(); 1595 } catch (IOException e) { 1596 throw new VirtualMachineException("failed to persist files", e); 1597 } catch (IllegalStateException | ServiceSpecificException e) { 1598 throw new VirtualMachineException(e); 1599 } catch (RemoteException e) { 1600 throw e.rethrowAsRuntimeException(); 1601 } 1602 } 1603 } 1604 createIdSigsAndUpdateConfig( IVirtualizationService service, VirtualMachineAppConfig appConfig)1605 private void createIdSigsAndUpdateConfig( 1606 IVirtualizationService service, VirtualMachineAppConfig appConfig) 1607 throws RemoteException, FileNotFoundException { 1608 // Fill the idsig file by hashing the apk 1609 service.createOrUpdateIdsigFile( 1610 appConfig.apk, ParcelFileDescriptor.open(mIdsigFilePath, MODE_READ_WRITE)); 1611 1612 for (ExtraApkSpec extraApk : mExtraApks) { 1613 service.createOrUpdateIdsigFile( 1614 ParcelFileDescriptor.open(extraApk.apk, MODE_READ_ONLY), 1615 ParcelFileDescriptor.open(extraApk.idsig, MODE_READ_WRITE)); 1616 } 1617 1618 // Re-open idsig files in read-only mode 1619 appConfig.idsig = ParcelFileDescriptor.open(mIdsigFilePath, MODE_READ_ONLY); 1620 List<ParcelFileDescriptor> extraIdsigs = new ArrayList<>(); 1621 for (ExtraApkSpec extraApk : mExtraApks) { 1622 extraIdsigs.add(ParcelFileDescriptor.open(extraApk.idsig, MODE_READ_ONLY)); 1623 } 1624 appConfig.extraIdsigs = extraIdsigs; 1625 } 1626 1627 @GuardedBy("mLock") createVmOutputPipes()1628 private void createVmOutputPipes() throws VirtualMachineException { 1629 try { 1630 if (mConsoleOutReader == null || mConsoleOutWriter == null) { 1631 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); 1632 mConsoleOutReader = pipe[0]; 1633 mConsoleOutWriter = pipe[1]; 1634 } 1635 1636 if (mLogReader == null || mLogWriter == null) { 1637 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); 1638 mLogReader = pipe[0]; 1639 mLogWriter = pipe[1]; 1640 } 1641 } catch (IOException e) { 1642 throw new VirtualMachineException("Failed to create output stream for VM", e); 1643 } 1644 } 1645 1646 @GuardedBy("mLock") createVmInputPipes()1647 private void createVmInputPipes() throws VirtualMachineException { 1648 try { 1649 if (mConsoleInReader == null || mConsoleInWriter == null) { 1650 if (mConnectVmConsole) { 1651 // If we are enabling input pipes AND the host console, then we should just use 1652 // the host pty peer end as the console write end. 1653 createPtyConsole(); 1654 mConsoleInReader = mPtyFd.dup(); 1655 mConsoleInWriter = mPtsFd.dup(); 1656 } else { 1657 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); 1658 mConsoleInReader = pipe[0]; 1659 mConsoleInWriter = pipe[1]; 1660 } 1661 } 1662 } catch (IOException e) { 1663 throw new VirtualMachineException("Failed to create input stream for VM", e); 1664 } 1665 } 1666 1667 @FunctionalInterface 1668 private static interface OpenPtyCallback { apply(FileDescriptor mfd, FileDescriptor sfd, byte[] name)1669 public void apply(FileDescriptor mfd, FileDescriptor sfd, byte[] name); 1670 } 1671 1672 // Opens a pty and set the master end to raw mode and O_NONBLOCK. nativeOpenPtyRawNonblock(OpenPtyCallback resultCallback)1673 private static native void nativeOpenPtyRawNonblock(OpenPtyCallback resultCallback) 1674 throws IOException; 1675 1676 @GuardedBy("mLock") createPtyConsole()1677 private void createPtyConsole() throws VirtualMachineException { 1678 if (mPtyFd != null && mPtsFd != null) { 1679 return; 1680 } 1681 List<FileDescriptor> fd = new ArrayList<>(2); 1682 StringBuilder nameBuilder = new StringBuilder(); 1683 try { 1684 try { 1685 nativeOpenPtyRawNonblock( 1686 (FileDescriptor mfd, FileDescriptor sfd, byte[] ptsName) -> { 1687 fd.add(mfd); 1688 fd.add(sfd); 1689 nameBuilder.append(new String(ptsName, StandardCharsets.UTF_8)); 1690 }); 1691 } catch (Exception e) { 1692 fd.forEach(IoUtils::closeQuietly); 1693 throw e; 1694 } 1695 } catch (IOException e) { 1696 throw new VirtualMachineException( 1697 "Failed to create host console to connect to the VM console", e); 1698 } 1699 mPtyFd = new ParcelFileDescriptor(fd.get(0)); 1700 mPtsFd = new ParcelFileDescriptor(fd.get(1)); 1701 mPtsName = nameBuilder.toString(); 1702 Log.d(TAG, "Serial console device: " + mPtsName); 1703 } 1704 1705 /** 1706 * Returns the name of the peer end (ptsname) of the host console. The host console is only 1707 * available if the {@link VirtualMachineConfig} specifies that a host console should 1708 * {@linkplain VirtualMachineConfig#isConnectVmConsole connect} to the VM console. 1709 * 1710 * @throws VirtualMachineException if the host pseudoterminal could not be created, or 1711 * connecting to the VM console is not enabled. 1712 * @hide 1713 */ 1714 @NonNull getHostConsoleName()1715 private String getHostConsoleName() throws VirtualMachineException { 1716 if (!mConnectVmConsole) { 1717 throw new VirtualMachineException("Host console is not enabled"); 1718 } 1719 synchronized (mLock) { 1720 createPtyConsole(); 1721 return mPtsName; 1722 } 1723 } 1724 1725 /** 1726 * Returns the stream object representing the console output from the virtual machine. The 1727 * console output is only available if the {@link VirtualMachineConfig} specifies that it should 1728 * be {@linkplain VirtualMachineConfig#isVmOutputCaptured captured}. 1729 * 1730 * <p>If you turn on output capture, you must consume data from {@code getConsoleOutput} - 1731 * because otherwise the code in the VM may get blocked when the pipe buffer fills up. 1732 * 1733 * <p>NOTE: This method may block and should not be called on the main thread. 1734 * 1735 * @throws VirtualMachineException if the stream could not be created, or capturing is turned 1736 * off. 1737 * @hide 1738 */ 1739 @SystemApi 1740 @WorkerThread 1741 @NonNull getConsoleOutput()1742 public InputStream getConsoleOutput() throws VirtualMachineException { 1743 if (!mVmOutputCaptured) { 1744 throw new VirtualMachineException("Capturing vm outputs is turned off"); 1745 } 1746 synchronized (mLock) { 1747 createVmOutputPipes(); 1748 return new FileInputStream(mConsoleOutReader.getFileDescriptor()); 1749 } 1750 } 1751 1752 /** 1753 * Returns the stream object representing the console input to the virtual machine. The console 1754 * input is only available if the {@link VirtualMachineConfig} specifies that it should be 1755 * {@linkplain VirtualMachineConfig#isVmConsoleInputSupported supported}. 1756 * 1757 * <p>NOTE: This method may block and should not be called on the main thread. 1758 * 1759 * @throws VirtualMachineException if the stream could not be created, or console input is not 1760 * supported. 1761 * @hide 1762 */ 1763 @TestApi 1764 @WorkerThread 1765 @NonNull getConsoleInput()1766 public OutputStream getConsoleInput() throws VirtualMachineException { 1767 if (!mVmConsoleInputSupported) { 1768 throw new VirtualMachineException("VM console input is not supported"); 1769 } 1770 synchronized (mLock) { 1771 createVmInputPipes(); 1772 return new FileOutputStream(mConsoleInWriter.getFileDescriptor()); 1773 } 1774 } 1775 1776 /** 1777 * Returns the stream object representing the log output from the virtual machine. The log 1778 * output is only available if the VirtualMachineConfig specifies that it should be {@linkplain 1779 * VirtualMachineConfig#isVmOutputCaptured captured}. 1780 * 1781 * <p>If you turn on output capture, you must consume data from {@code getLogOutput} - because 1782 * otherwise the code in the VM may get blocked when the pipe buffer fills up. 1783 * 1784 * <p>NOTE: This method may block and should not be called on the main thread. 1785 * 1786 * @throws VirtualMachineException if the stream could not be created, or capturing is turned 1787 * off. 1788 * @hide 1789 */ 1790 @SystemApi 1791 @WorkerThread 1792 @NonNull getLogOutput()1793 public InputStream getLogOutput() throws VirtualMachineException { 1794 if (!mVmOutputCaptured) { 1795 throw new VirtualMachineException("Capturing vm outputs is turned off"); 1796 } 1797 synchronized (mLock) { 1798 createVmOutputPipes(); 1799 return new FileInputStream(mLogReader.getFileDescriptor()); 1800 } 1801 } 1802 1803 /** 1804 * Stops this virtual machine. Stopping a virtual machine is like pulling the plug on a real 1805 * computer; the machine halts immediately. Software running on the virtual machine is not 1806 * notified of the event. Writes to {@linkplain 1807 * VirtualMachineConfig.Builder#setEncryptedStorageBytes encrypted storage} might not be 1808 * persisted, and the instance might be left in an inconsistent state. 1809 * 1810 * <p>For a graceful shutdown, you could request the payload to call {@code exit()}, e.g. via a 1811 * {@linkplain #connectToVsockServer binder request}, and wait for {@link 1812 * VirtualMachineCallback#onPayloadFinished} to be called. 1813 * 1814 * <p>A stopped virtual machine cannot be re-started. 1815 * 1816 * <p>NOTE: This method may block and should not be called on the main thread. 1817 * 1818 * @throws VirtualMachineException if the virtual machine is not running or could not be 1819 * stopped. 1820 * @hide 1821 */ 1822 @SystemApi 1823 @WorkerThread stop()1824 public void stop() throws VirtualMachineException { 1825 synchronized (mLock) { 1826 if (mVirtualMachine == null) { 1827 throw new VirtualMachineException("VM is not running"); 1828 } 1829 try { 1830 mVirtualMachine.stop(); 1831 dropVm(); 1832 } catch (RemoteException e) { 1833 throw e.rethrowAsRuntimeException(); 1834 } catch (ServiceSpecificException e) { 1835 throw new VirtualMachineException(e); 1836 } 1837 } 1838 } 1839 1840 /** @hide */ suspend()1841 public void suspend() throws VirtualMachineException { 1842 synchronized (mLock) { 1843 if (mVirtualMachine == null) { 1844 throw new VirtualMachineException("VM is not running"); 1845 } 1846 try { 1847 mVirtualMachine.suspend(); 1848 } catch (RemoteException e) { 1849 throw e.rethrowAsRuntimeException(); 1850 } catch (ServiceSpecificException e) { 1851 throw new VirtualMachineException(e); 1852 } 1853 } 1854 } 1855 1856 /** @hide */ resume()1857 public void resume() throws VirtualMachineException { 1858 synchronized (mLock) { 1859 if (mVirtualMachine == null) { 1860 throw new VirtualMachineException("VM is not running"); 1861 } 1862 try { 1863 mVirtualMachine.resume(); 1864 } catch (RemoteException e) { 1865 throw e.rethrowAsRuntimeException(); 1866 } catch (ServiceSpecificException e) { 1867 throw new VirtualMachineException(e); 1868 } 1869 } 1870 } 1871 1872 /** 1873 * Stops this virtual machine, if it is running. 1874 * 1875 * <p>NOTE: This method may block and should not be called on the main thread. 1876 * 1877 * @see #stop() 1878 * @hide 1879 */ 1880 @SystemApi 1881 @WorkerThread 1882 @Override close()1883 public void close() { 1884 synchronized (mLock) { 1885 if (mVirtualMachine == null) { 1886 return; 1887 } 1888 try { 1889 if (stateToStatus(mVirtualMachine.getState()) == STATUS_RUNNING) { 1890 mVirtualMachine.stop(); 1891 dropVm(); 1892 } 1893 } catch (RemoteException | ServiceSpecificException e) { 1894 // Deliberately ignored; this almost certainly means the VM exited just as 1895 // we tried to stop it. 1896 Log.i(TAG, "Ignoring error on close()", e); 1897 } 1898 } 1899 } 1900 deleteRecursively(File dir)1901 private static void deleteRecursively(File dir) throws IOException { 1902 // Note: This doesn't follow symlinks, which is important. Instead they are just deleted 1903 // (and Files.delete deletes the link not the target). 1904 Files.walkFileTree( 1905 dir.toPath(), 1906 new SimpleFileVisitor<>() { 1907 @Override 1908 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 1909 throws IOException { 1910 Files.delete(file); 1911 return FileVisitResult.CONTINUE; 1912 } 1913 1914 @Override 1915 public FileVisitResult postVisitDirectory(Path dir, IOException e) 1916 throws IOException { 1917 // Directory is deleted after we've visited (deleted) all its contents, so 1918 // it 1919 // should be empty by now. 1920 Files.delete(dir); 1921 return FileVisitResult.CONTINUE; 1922 } 1923 }); 1924 } 1925 1926 /** 1927 * Changes the config of this virtual machine to a new one. This can be used to adjust things 1928 * like the number of CPU and size of the RAM, depending on the situation (e.g. the size of the 1929 * application to run on the virtual machine, etc.) 1930 * 1931 * <p>The new config must be {@linkplain VirtualMachineConfig#isCompatibleWith compatible with} 1932 * the existing config. 1933 * 1934 * <p>NOTE: Modification of the encrypted storage size is restricted to expansion only and is an 1935 * irreversible operation. 1936 * <p>NOTE: This method may block and should not be called on the main thread. 1937 * 1938 * @return the old config 1939 * @throws VirtualMachineException if the virtual machine is not stopped, or the new config is 1940 * incompatible. 1941 * @hide 1942 */ 1943 @SystemApi 1944 @WorkerThread 1945 @NonNull setConfig(@onNull VirtualMachineConfig newConfig)1946 public VirtualMachineConfig setConfig(@NonNull VirtualMachineConfig newConfig) 1947 throws VirtualMachineException { 1948 synchronized (mLock) { 1949 VirtualMachineConfig oldConfig = mConfig; 1950 if (!oldConfig.isCompatibleWith(newConfig) 1951 || oldConfig.getEncryptedStorageBytes() 1952 > newConfig.getEncryptedStorageBytes()) { 1953 throw new VirtualMachineException("incompatible config"); 1954 } 1955 checkStopped(); 1956 1957 if (oldConfig != newConfig) { 1958 // Delete any existing file before recreating; that ensures any 1959 // VirtualMachineDescriptor that refers to the old file does not see the new config. 1960 mConfigFilePath.delete(); 1961 newConfig.serialize(mConfigFilePath); 1962 mConfig = newConfig; 1963 } 1964 return oldConfig; 1965 } 1966 } 1967 1968 /** 1969 * Abstracts away the task of creating a vsock connection. Normally, in the same process, you'll 1970 * make the connection and then promote it to a binder as part of the same API call, but if you 1971 * want to pass a connection to another process first, before establishing the RPC binder 1972 * connection, you may implement this method by getting a vsock connection from another process. 1973 * 1974 * <p>It is recommended to convert other types of exceptions (e.g. remote exceptions) to 1975 * VirtualMachineException, so that all connection failures will be visible under the same type 1976 * of exception. 1977 * 1978 * @hide 1979 */ 1980 @SystemApi 1981 @SuppressLint("UnflaggedApi") // already existing functionality exposed, users should flag 1982 public interface VsockConnectionProvider { 1983 /** 1984 * Returns a connection, either from {@link #connectVsock} or from 1985 * the VM owner which would call {@link #connectVsock} on your behalf. 1986 * 1987 * <p>Each call should return a new connection. 1988 */ 1989 @NonNull 1990 @SuppressLint("UnflaggedApi") // already existing functionality exposed, users should flag addConnection()1991 public ParcelFileDescriptor addConnection() throws VirtualMachineException; 1992 } 1993 1994 /** 1995 * Class to make it easy to use JNI, without needing PFD and other classes. 1996 * 1997 * @hide 1998 */ 1999 private static class NativeProviderWrapper { 2000 private VsockConnectionProvider mProvider = null; 2001 2002 // ensures last FD is owned until get is called again 2003 private ParcelFileDescriptor mMoreLifetime = null; 2004 NativeProviderWrapper(VsockConnectionProvider provider)2005 public NativeProviderWrapper(VsockConnectionProvider provider) { 2006 mProvider = provider; 2007 } 2008 connect()2009 int connect() throws VirtualMachineException { 2010 mMoreLifetime = mProvider.addConnection(); 2011 return mMoreLifetime.getFileDescriptor().getInt$(); 2012 } 2013 } 2014 2015 /** 2016 * Connect to a VM's binder service via vsock and return the root IBinder object. Guest VMs are 2017 * expected to set up vsock servers in their payload. After the host app receives the {@link 2018 * VirtualMachineCallback#onPayloadReady}, it can use this method to establish a connection to 2019 * the guest VM. 2020 * 2021 * <p>NOTE: This method may block and should not be called on the main thread. 2022 * 2023 * @throws VirtualMachineException if the virtual machine is not running or the connection 2024 * failed. 2025 * @hide 2026 */ 2027 @SystemApi 2028 @WorkerThread 2029 @NonNull connectToVsockServer( @ntRangefrom = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)2030 public IBinder connectToVsockServer( 2031 @IntRange(from = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port) 2032 throws VirtualMachineException { 2033 VsockConnectionProvider provider = 2034 new VsockConnectionProvider() { 2035 @Override 2036 public ParcelFileDescriptor addConnection() throws VirtualMachineException { 2037 return connectVsock(port); 2038 } 2039 }; 2040 return binderFromPreconnectedClient(provider); 2041 } 2042 2043 @Nullable nativeBinderFromPreconnectedClient( NativeProviderWrapper provider)2044 private static native IBinder nativeBinderFromPreconnectedClient( 2045 NativeProviderWrapper provider); 2046 2047 /** 2048 * Convert existing vsock connection to a binder connection. 2049 * 2050 * <p>See {@linkplain #connectToVsockServer} for details. This method allows 2051 * you to create the connects independently from upgrading them to the 2052 * binder connection. Specifically: 2053 * 2054 * <p>connectToVsockServer = connectToVsock + binderFromPreconnectedClient 2055 * 2056 * <p>This method is useful if you want to pass the vsock connection to 2057 * another process before establishing the RPC binder connection, so that 2058 * you can create a direct connection. 2059 * 2060 * @args 2061 * provider: a provider that provides the vsock connection. This 2062 * provider should return connections from 2063 * {@link #connectVsock}, from the VM owner. 2064 * 2065 * @hide 2066 */ 2067 @SystemApi 2068 @SuppressLint("UnflaggedApi") // already existing functionality exposed, users should flag 2069 @WorkerThread 2070 @NonNull binderFromPreconnectedClient(@onNull VsockConnectionProvider provider)2071 public static IBinder binderFromPreconnectedClient(@NonNull VsockConnectionProvider provider) 2072 throws VirtualMachineException { 2073 IBinder binder = nativeBinderFromPreconnectedClient(new NativeProviderWrapper(provider)); 2074 if (binder == null) { 2075 throw new VirtualMachineException("Failed to connect to vsock server"); 2076 } 2077 return binder; 2078 } 2079 2080 /** 2081 * Opens a vsock connection to the VM on the given port. 2082 * 2083 * <p>The caller is responsible for closing the returned {@code ParcelFileDescriptor}. 2084 * 2085 * <p>NOTE: This method may block and should not be called on the main thread. 2086 * 2087 * @throws VirtualMachineException if connecting fails. 2088 * @hide 2089 */ 2090 @SystemApi 2091 @WorkerThread 2092 @NonNull connectVsock( @ntRangefrom = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)2093 public ParcelFileDescriptor connectVsock( 2094 @IntRange(from = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port) 2095 throws VirtualMachineException { 2096 synchronized (mLock) { 2097 try { 2098 return getRunningVm().connectVsock(validatePort(port)); 2099 } catch (RemoteException e) { 2100 throw e.rethrowAsRuntimeException(); 2101 } catch (ServiceSpecificException e) { 2102 throw new VirtualMachineException(e); 2103 } 2104 } 2105 } 2106 validatePort(long port)2107 private int validatePort(long port) { 2108 // Ports below 1024 are "privileged" (payload code can't bind to these), and port numbers 2109 // are 32-bit unsigned numbers at the OS level, even though we pass them as 32-bit signed 2110 // numbers internally. 2111 if (port < MIN_VSOCK_PORT || port > MAX_VSOCK_PORT) { 2112 throw new IllegalArgumentException("Bad port " + port); 2113 } 2114 return (int) port; 2115 } 2116 2117 /** 2118 * Returns the root directory where all files related to this {@link VirtualMachine} (e.g. 2119 * {@code instance.img}, {@code apk.idsig}, etc) are stored. 2120 * 2121 * @hide 2122 */ 2123 @TestApi 2124 @NonNull getRootDir()2125 public File getRootDir() { 2126 return mVmRootPath; 2127 } 2128 2129 /** 2130 * Enables the VM to request attestation in testing mode. 2131 * 2132 * <p>This function provisions a key pair for the VM attestation testing, a fake certificate 2133 * will be associated to the fake key pair when the VM requests attestation in testing mode. 2134 * 2135 * <p>The provisioned key pair can only be used in subsequent calls to {@link 2136 * AVmPayload_requestAttestationForTesting} within a running VM. 2137 * 2138 * @hide 2139 */ 2140 @TestApi 2141 @RequiresPermission(USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION) enableTestAttestation()2142 public void enableTestAttestation() throws VirtualMachineException { 2143 try { 2144 mVirtualizationService.getBinder().enableTestAttestation(); 2145 } catch (RemoteException e) { 2146 throw e.rethrowAsRuntimeException(); 2147 } 2148 } 2149 2150 /** 2151 * Captures the current state of the VM in a {@link VirtualMachineDescriptor} instance. The VM 2152 * needs to be stopped to avoid inconsistency in its state representation. 2153 * 2154 * <p>The state of the VM is not actually copied until {@link 2155 * VirtualMachineManager#importFromDescriptor} is called. It is recommended that the VM not be 2156 * started until that operation is complete. 2157 * 2158 * <p>NOTE: This method may block and should not be called on the main thread. 2159 * 2160 * @return a {@link VirtualMachineDescriptor} instance that represents the VM's state. 2161 * @throws VirtualMachineException if the virtual machine is not stopped, or the state could not 2162 * be captured. 2163 * @hide 2164 */ 2165 @SystemApi 2166 @WorkerThread 2167 @NonNull toDescriptor()2168 public VirtualMachineDescriptor toDescriptor() throws VirtualMachineException { 2169 synchronized (mLock) { 2170 checkStopped(); 2171 try { 2172 return new VirtualMachineDescriptor( 2173 ParcelFileDescriptor.open(mConfigFilePath, MODE_READ_ONLY), 2174 mInstanceIdPath != null 2175 ? ParcelFileDescriptor.open(mInstanceIdPath, MODE_READ_ONLY) 2176 : null, 2177 ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_ONLY), 2178 mEncryptedStoreFilePath != null 2179 ? ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_ONLY) 2180 : null); 2181 } catch (IOException e) { 2182 throw new VirtualMachineException(e); 2183 } 2184 } 2185 } 2186 2187 @Override toString()2188 public String toString() { 2189 VirtualMachineConfig config = getConfig(); 2190 String payloadConfigPath = config.getPayloadConfigPath(); 2191 String payloadBinaryName = config.getPayloadBinaryName(); 2192 2193 StringBuilder result = new StringBuilder(); 2194 result.append("VirtualMachine(").append("name:").append(getName()).append(", "); 2195 if (payloadBinaryName != null) { 2196 result.append("payload:").append(payloadBinaryName).append(", "); 2197 } 2198 if (payloadConfigPath != null) { 2199 result.append("config:").append(payloadConfigPath).append(", "); 2200 } 2201 result.append("package: ").append(mPackageName).append(")"); 2202 return result.toString(); 2203 } 2204 2205 /** 2206 * Reads the payload config inside the application, parses extra APK information, and then 2207 * creates corresponding idsig file paths. 2208 */ setupExtraApks( @onNull Context context, @NonNull VirtualMachineConfig config, @NonNull File vmDir)2209 private static List<ExtraApkSpec> setupExtraApks( 2210 @NonNull Context context, @NonNull VirtualMachineConfig config, @NonNull File vmDir) 2211 throws VirtualMachineException { 2212 String configPath = config.getPayloadConfigPath(); 2213 List<String> extraApks = config.getExtraApks(); 2214 if (configPath != null) { 2215 return setupExtraApksFromConfigFile(context, vmDir, configPath); 2216 } else if (!extraApks.isEmpty()) { 2217 return setupExtraApksFromList(context, vmDir, extraApks); 2218 } else { 2219 return Collections.emptyList(); 2220 } 2221 } 2222 setupExtraApksFromConfigFile( Context context, File vmDir, String configPath)2223 private static List<ExtraApkSpec> setupExtraApksFromConfigFile( 2224 Context context, File vmDir, String configPath) throws VirtualMachineException { 2225 try (ZipFile zipFile = new ZipFile(context.getPackageCodePath())) { 2226 InputStream inputStream = zipFile.getInputStream(zipFile.getEntry(configPath)); 2227 List<String> apkList = 2228 parseExtraApkListFromPayloadConfig( 2229 new JsonReader(new InputStreamReader(inputStream))); 2230 2231 List<ExtraApkSpec> extraApks = new ArrayList<>(apkList.size()); 2232 for (int i = 0; i < apkList.size(); ++i) { 2233 extraApks.add( 2234 new ExtraApkSpec( 2235 new File(apkList.get(i)), 2236 new File(vmDir, EXTRA_IDSIG_FILE_PREFIX + i))); 2237 } 2238 2239 return extraApks; 2240 } catch (IOException e) { 2241 throw new VirtualMachineException("Couldn't parse extra apks from the vm config", e); 2242 } 2243 } 2244 parseExtraApkListFromPayloadConfig(JsonReader reader)2245 private static List<String> parseExtraApkListFromPayloadConfig(JsonReader reader) 2246 throws VirtualMachineException { 2247 /* 2248 * JSON schema from packages/modules/Virtualization/microdroid/libs/libmicrodroid_payload_metadata/config/src/lib.rs: 2249 * 2250 * <p>{ "extra_apks": [ { "path": "/system/app/foo.apk", }, ... ], ... } 2251 */ 2252 try { 2253 List<String> apks = new ArrayList<>(); 2254 2255 reader.beginObject(); 2256 while (reader.hasNext()) { 2257 if (reader.nextName().equals("extra_apks")) { 2258 reader.beginArray(); 2259 while (reader.hasNext()) { 2260 reader.beginObject(); 2261 String name = reader.nextName(); 2262 if (name.equals("path")) { 2263 apks.add(reader.nextString()); 2264 } else { 2265 reader.skipValue(); 2266 } 2267 reader.endObject(); 2268 } 2269 reader.endArray(); 2270 } else { 2271 reader.skipValue(); 2272 } 2273 } 2274 reader.endObject(); 2275 return apks; 2276 } catch (IOException e) { 2277 throw new VirtualMachineException(e); 2278 } 2279 } 2280 setupExtraApksFromList( Context context, File vmDir, List<String> extraApkInfo)2281 private static List<ExtraApkSpec> setupExtraApksFromList( 2282 Context context, File vmDir, List<String> extraApkInfo) throws VirtualMachineException { 2283 int count = extraApkInfo.size(); 2284 List<ExtraApkSpec> extraApks = new ArrayList<>(count); 2285 for (int i = 0; i < count; i++) { 2286 String packageName = extraApkInfo.get(i); 2287 ApplicationInfo appInfo; 2288 try { 2289 appInfo = 2290 context.getPackageManager() 2291 .getApplicationInfo( 2292 packageName, PackageManager.ApplicationInfoFlags.of(0)); 2293 } catch (PackageManager.NameNotFoundException e) { 2294 throw new VirtualMachineException("Extra APK package not found", e); 2295 } 2296 2297 extraApks.add( 2298 new ExtraApkSpec( 2299 new File(appInfo.sourceDir), 2300 new File(vmDir, EXTRA_IDSIG_FILE_PREFIX + i))); 2301 } 2302 return extraApks; 2303 } 2304 importInstanceIdFrom(@onNull ParcelFileDescriptor instanceIdFd)2305 private void importInstanceIdFrom(@NonNull ParcelFileDescriptor instanceIdFd) 2306 throws VirtualMachineException { 2307 try (FileChannel idOutput = new FileOutputStream(mInstanceIdPath).getChannel(); 2308 FileChannel idInput = new AutoCloseInputStream(instanceIdFd).getChannel()) { 2309 idOutput.transferFrom(idInput, /* position= */ 0, idInput.size()); 2310 } catch (IOException e) { 2311 throw new VirtualMachineException("failed to copy instance_id", e); 2312 } 2313 } 2314 importInstanceFrom(@onNull ParcelFileDescriptor instanceFd)2315 private void importInstanceFrom(@NonNull ParcelFileDescriptor instanceFd) 2316 throws VirtualMachineException { 2317 try (FileChannel instance = new FileOutputStream(mInstanceFilePath).getChannel(); 2318 FileChannel instanceInput = new AutoCloseInputStream(instanceFd).getChannel()) { 2319 instance.transferFrom(instanceInput, /* position= */ 0, instanceInput.size()); 2320 } catch (IOException e) { 2321 throw new VirtualMachineException("failed to transfer instance image", e); 2322 } 2323 } 2324 importEncryptedStoreFrom(@onNull ParcelFileDescriptor encryptedStoreFd)2325 private void importEncryptedStoreFrom(@NonNull ParcelFileDescriptor encryptedStoreFd) 2326 throws VirtualMachineException { 2327 try (FileChannel storeOutput = new FileOutputStream(mEncryptedStoreFilePath).getChannel(); 2328 FileChannel storeInput = new AutoCloseInputStream(encryptedStoreFd).getChannel()) { 2329 storeOutput.transferFrom(storeInput, /* position= */ 0, storeInput.size()); 2330 } catch (IOException e) { 2331 throw new VirtualMachineException("failed to transfer encryptedstore image", e); 2332 } 2333 } 2334 2335 /** Map the raw AIDL (& binder) callbacks to what the client expects. */ 2336 private class CallbackTranslator extends IVirtualMachineCallback.Stub { 2337 private final IVirtualizationService mService; 2338 private final DeathRecipient mDeathRecipient; 2339 2340 // The VM should only be observed to die once 2341 private final AtomicBoolean mOnDiedCalled = new AtomicBoolean(false); 2342 CallbackTranslator(IVirtualizationService service)2343 public CallbackTranslator(IVirtualizationService service) throws RemoteException { 2344 this.mService = service; 2345 this.mDeathRecipient = () -> reportStopped(STOP_REASON_VIRTUALIZATION_SERVICE_DIED); 2346 service.asBinder().linkToDeath(mDeathRecipient, 0); 2347 } 2348 2349 @Override onPayloadStarted(int cid)2350 public void onPayloadStarted(int cid) { 2351 executeCallback((cb) -> cb.onPayloadStarted(VirtualMachine.this)); 2352 } 2353 2354 @Override onPayloadReady(int cid)2355 public void onPayloadReady(int cid) { 2356 executeCallback((cb) -> cb.onPayloadReady(VirtualMachine.this)); 2357 } 2358 2359 @Override onPayloadFinished(int cid, int exitCode)2360 public void onPayloadFinished(int cid, int exitCode) { 2361 executeCallback((cb) -> cb.onPayloadFinished(VirtualMachine.this, exitCode)); 2362 } 2363 2364 @Override onError(int cid, int errorCode, String message)2365 public void onError(int cid, int errorCode, String message) { 2366 int translatedError = getTranslatedError(errorCode); 2367 executeCallback((cb) -> cb.onError(VirtualMachine.this, translatedError, message)); 2368 } 2369 2370 @Override onDied(int cid, int reason)2371 public void onDied(int cid, int reason) { 2372 int translatedReason = getTranslatedReason(reason); 2373 reportStopped(translatedReason); 2374 mService.asBinder().unlinkToDeath(mDeathRecipient, 0); 2375 } 2376 reportStopped(@irtualMachineCallback.StopReason int reason)2377 private void reportStopped(@VirtualMachineCallback.StopReason int reason) { 2378 if (mOnDiedCalled.compareAndSet(false, true)) { 2379 executeCallback((cb) -> cb.onStopped(VirtualMachine.this, reason)); 2380 } 2381 } 2382 2383 @VirtualMachineCallback.ErrorCode getTranslatedError(int reason)2384 private int getTranslatedError(int reason) { 2385 switch (reason) { 2386 case ErrorCode.PAYLOAD_VERIFICATION_FAILED: 2387 return ERROR_PAYLOAD_VERIFICATION_FAILED; 2388 case ErrorCode.PAYLOAD_CHANGED: 2389 return ERROR_PAYLOAD_CHANGED; 2390 case ErrorCode.PAYLOAD_INVALID_CONFIG: 2391 return ERROR_PAYLOAD_INVALID_CONFIG; 2392 default: 2393 return ERROR_UNKNOWN; 2394 } 2395 } 2396 2397 @VirtualMachineCallback.StopReason getTranslatedReason(int reason)2398 private int getTranslatedReason(int reason) { 2399 switch (reason) { 2400 case DeathReason.INFRASTRUCTURE_ERROR: 2401 return STOP_REASON_INFRASTRUCTURE_ERROR; 2402 case DeathReason.KILLED: 2403 return STOP_REASON_KILLED; 2404 case DeathReason.SHUTDOWN: 2405 return STOP_REASON_SHUTDOWN; 2406 case DeathReason.START_FAILED: 2407 return STOP_REASON_START_FAILED; 2408 case DeathReason.REBOOT: 2409 return STOP_REASON_REBOOT; 2410 case DeathReason.CRASH: 2411 return STOP_REASON_CRASH; 2412 case DeathReason.PVM_FIRMWARE_PUBLIC_KEY_MISMATCH: 2413 return STOP_REASON_PVM_FIRMWARE_PUBLIC_KEY_MISMATCH; 2414 case DeathReason.PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED: 2415 return STOP_REASON_PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED; 2416 case DeathReason.MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE: 2417 return STOP_REASON_MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE; 2418 case DeathReason.MICRODROID_PAYLOAD_HAS_CHANGED: 2419 return STOP_REASON_MICRODROID_PAYLOAD_HAS_CHANGED; 2420 case DeathReason.MICRODROID_PAYLOAD_VERIFICATION_FAILED: 2421 return STOP_REASON_MICRODROID_PAYLOAD_VERIFICATION_FAILED; 2422 case DeathReason.MICRODROID_INVALID_PAYLOAD_CONFIG: 2423 return STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG; 2424 case DeathReason.MICRODROID_UNKNOWN_RUNTIME_ERROR: 2425 return STOP_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR; 2426 case DeathReason.HANGUP: 2427 return STOP_REASON_HANGUP; 2428 default: 2429 return STOP_REASON_UNKNOWN; 2430 } 2431 } 2432 } 2433 2434 /** 2435 * Duplicates {@code InputStream} data to multiple {@code OutputStream}. Like the "tee" command. 2436 * 2437 * <p>Supports non-blocking writes to the output streams by ignoring EAGAIN error. 2438 */ 2439 private static class TeeWorker implements Runnable { 2440 private final String mName; 2441 private final InputStream mIn; 2442 private final List<OutputStream> mOuts; 2443 TeeWorker(String name, InputStream in, Collection<OutputStream> outs)2444 TeeWorker(String name, InputStream in, Collection<OutputStream> outs) { 2445 mName = name; 2446 mIn = in; 2447 mOuts = new ArrayList<>(outs); 2448 } 2449 2450 @Override run()2451 public void run() { 2452 byte[] buffer = new byte[2048]; 2453 try { 2454 while (!Thread.interrupted()) { 2455 int len = mIn.read(buffer); 2456 if (len < 0) { 2457 break; 2458 } 2459 for (OutputStream out : mOuts) { 2460 try { 2461 out.write(buffer, 0, len); 2462 } catch (IOException e) { 2463 // EAGAIN is expected because the file description has O_NONBLOCK flag. 2464 if (!isErrnoError(e, OsConstants.EAGAIN)) { 2465 throw e; 2466 } 2467 } 2468 } 2469 } 2470 } catch (Exception e) { 2471 Log.e(TAG, "Tee " + mName, e); 2472 } 2473 } 2474 asErrnoException(Throwable e)2475 private static ErrnoException asErrnoException(Throwable e) { 2476 if (e instanceof ErrnoException) { 2477 return (ErrnoException) e; 2478 } else if (e instanceof IOException) { 2479 // Try to unwrap ErrnoException#rethrowAsIOException() 2480 return asErrnoException(e.getCause()); 2481 } 2482 return null; 2483 } 2484 isErrnoError(Exception e, int expectedValue)2485 private static boolean isErrnoError(Exception e, int expectedValue) { 2486 ErrnoException errno = asErrnoException(e); 2487 return errno != null && errno.errno == expectedValue; 2488 } 2489 } 2490 } 2491