• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.system.virtualmachine;
18 
19 import static android.os.ParcelFileDescriptor.AutoCloseInputStream;
20 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
21 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
22 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