• 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.res.Configuration;
58 import android.os.Binder;
59 import android.os.IBinder;
60 import android.os.ParcelFileDescriptor;
61 import android.os.RemoteException;
62 import android.os.ServiceSpecificException;
63 import android.system.virtualizationcommon.DeathReason;
64 import android.system.virtualizationcommon.ErrorCode;
65 import android.system.virtualizationservice.IVirtualMachine;
66 import android.system.virtualizationservice.IVirtualMachineCallback;
67 import android.system.virtualizationservice.IVirtualizationService;
68 import android.system.virtualizationservice.MemoryTrimLevel;
69 import android.system.virtualizationservice.PartitionType;
70 import android.system.virtualizationservice.VirtualMachineAppConfig;
71 import android.system.virtualizationservice.VirtualMachineState;
72 import android.util.JsonReader;
73 import android.util.Log;
74 
75 import com.android.internal.annotations.GuardedBy;
76 
77 import java.io.File;
78 import java.io.FileInputStream;
79 import java.io.FileNotFoundException;
80 import java.io.FileOutputStream;
81 import java.io.IOException;
82 import java.io.InputStream;
83 import java.io.InputStreamReader;
84 import java.lang.annotation.Retention;
85 import java.lang.annotation.RetentionPolicy;
86 import java.nio.channels.FileChannel;
87 import java.nio.file.FileAlreadyExistsException;
88 import java.nio.file.FileVisitResult;
89 import java.nio.file.Files;
90 import java.nio.file.Path;
91 import java.nio.file.SimpleFileVisitor;
92 import java.nio.file.attribute.BasicFileAttributes;
93 import java.util.ArrayList;
94 import java.util.Collections;
95 import java.util.List;
96 import java.util.concurrent.Executor;
97 import java.util.concurrent.atomic.AtomicBoolean;
98 import java.util.function.Consumer;
99 import java.util.zip.ZipFile;
100 
101 /**
102  * Represents an VM instance, with its own configuration and state. Instances are persistent and are
103  * created or retrieved via {@link VirtualMachineManager}.
104  *
105  * <p>The {@link #run} method actually starts up the VM and allows the payload code to execute. It
106  * will continue until it exits or {@link #stop} is called. Updates on the state of the VM can be
107  * received using {@link #setCallback}. The app can communicate with the VM using {@link
108  * #connectToVsockServer} or {@link #connectVsock}.
109  *
110  * <p>The payload code running inside the VM has access to a set of native APIs; see the <a
111  * href="https://cs.android.com/android/platform/superproject/+/master:packages/modules/Virtualization/vm_payload/README.md">README
112  * file</a> for details.
113  *
114  * <p>Each VM has a unique secret, computed from the APK that contains the code running in it, the
115  * VM configuration, and a random per-instance salt. The secret can be accessed by the payload code
116  * running inside the VM (using {@code AVmPayload_getVmInstanceSecret}) but is not made available
117  * outside it.
118  *
119  * @hide
120  */
121 @SystemApi
122 public class VirtualMachine implements AutoCloseable {
123     /** The permission needed to create or run a virtual machine. */
124     public static final String MANAGE_VIRTUAL_MACHINE_PERMISSION =
125             "android.permission.MANAGE_VIRTUAL_MACHINE";
126 
127     /**
128      * The permission needed to create a virtual machine with more advanced configuration options.
129      */
130     public static final String USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION =
131             "android.permission.USE_CUSTOM_VIRTUAL_MACHINE";
132 
133     /**
134      * The lowest port number that can be used to communicate with the virtual machine payload.
135      *
136      * @see #connectToVsockServer
137      * @see #connectVsock
138      */
139     @SuppressLint("MinMaxConstant") // Won't change: see man 7 vsock.
140     public static final long MIN_VSOCK_PORT = 1024;
141 
142     /**
143      * The highest port number that can be used to communicate with the virtual machine payload.
144      *
145      * @see #connectToVsockServer
146      * @see #connectVsock
147      */
148     @SuppressLint("MinMaxConstant") // Won't change: see man 7 vsock.
149     public static final long MAX_VSOCK_PORT = (1L << 32) - 1;
150 
151     /**
152      * Status of a virtual machine
153      *
154      * @hide
155      */
156     @Retention(RetentionPolicy.SOURCE)
157     @IntDef(prefix = "STATUS_", value = {
158             STATUS_STOPPED,
159             STATUS_RUNNING,
160             STATUS_DELETED
161     })
162     public @interface Status {}
163 
164     /** The virtual machine has just been created, or {@link #stop} was called on it. */
165     public static final int STATUS_STOPPED = 0;
166 
167     /** The virtual machine is running. */
168     public static final int STATUS_RUNNING = 1;
169 
170     /**
171      * The virtual machine has been deleted. This is an irreversible state. Once a virtual machine
172      * is deleted all its secrets are permanently lost, and it cannot be run. A new virtual machine
173      * with the same name and config may be created, with new and different secrets.
174      */
175     public static final int STATUS_DELETED = 2;
176 
177     private static final String TAG = "VirtualMachine";
178 
179     /** Name of the directory under the files directory where all VMs created for the app exist. */
180     private static final String VM_DIR = "vm";
181 
182     /** Name of the persisted config file for a VM. */
183     private static final String CONFIG_FILE = "config.xml";
184 
185     /** Name of the instance image file for a VM. (Not implemented) */
186     private static final String INSTANCE_IMAGE_FILE = "instance.img";
187 
188     /** Name of the idsig file for a VM */
189     private static final String IDSIG_FILE = "idsig";
190 
191     /** Name of the idsig files for extra APKs. */
192     private static final String EXTRA_IDSIG_FILE_PREFIX = "extra_idsig_";
193 
194     /** Size of the instance image. 10 MB. */
195     private static final long INSTANCE_FILE_SIZE = 10 * 1024 * 1024;
196 
197     /** Name of the file backing the encrypted storage */
198     private static final String ENCRYPTED_STORE_FILE = "storage.img";
199 
200     /** The package which owns this VM. */
201     @NonNull private final String mPackageName;
202 
203     /** Name of this VM within the package. The name should be unique in the package. */
204     @NonNull private final String mName;
205 
206     /**
207      * Path to the directory containing all the files related to this VM.
208      */
209     @NonNull private final File mVmRootPath;
210 
211     /**
212      * Path to the config file for this VM. The config file is where the configuration is persisted.
213      */
214     @NonNull private final File mConfigFilePath;
215 
216     /** Path to the instance image file for this VM. */
217     @NonNull private final File mInstanceFilePath;
218 
219     /** Path to the idsig file for this VM. */
220     @NonNull private final File mIdsigFilePath;
221 
222     /** File that backs the encrypted storage - Will be null if not enabled. */
223     @Nullable private final File mEncryptedStoreFilePath;
224 
225     /**
226      * Unmodifiable list of extra apks. Apks are specified by the vm config, and corresponding
227      * idsigs are to be generated.
228      */
229     @NonNull private final List<ExtraApkSpec> mExtraApks;
230 
231     private class MemoryManagementCallbacks implements ComponentCallbacks2 {
232         @Override
onConfigurationChanged(@onNull Configuration newConfig)233         public void onConfigurationChanged(@NonNull Configuration newConfig) {}
234 
235         @Override
onLowMemory()236         public void onLowMemory() {}
237 
238         @Override
onTrimMemory(int level)239         public void onTrimMemory(int level) {
240             @MemoryTrimLevel int vmTrimLevel;
241 
242             switch (level) {
243                 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
244                     vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_CRITICAL;
245                     break;
246                 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
247                     vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_LOW;
248                     break;
249                 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
250                     vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_MODERATE;
251                     break;
252                 case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
253                 case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
254                 case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
255                     /* Release as much memory as we can. The app is on the LMKD LRU kill list. */
256                     vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_CRITICAL;
257                     break;
258                 default:
259                     /* Treat unrecognised messages as generic low-memory warnings. */
260                     vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_LOW;
261                     break;
262             }
263 
264             synchronized (mLock) {
265                 try {
266                     if (mVirtualMachine != null) {
267                         mVirtualMachine.onTrimMemory(vmTrimLevel);
268                     }
269                 } catch (Exception e) {
270                     /* Caller doesn't want our exceptions. Log them instead. */
271                     Log.w(TAG, "TrimMemory failed: ", e);
272                 }
273             }
274         }
275     }
276 
277     /** Running instance of virtmgr that hosts VirtualizationService for this VM. */
278     @NonNull private final VirtualizationService mVirtualizationService;
279 
280     @NonNull private final MemoryManagementCallbacks mMemoryManagementCallbacks;
281 
282     @NonNull private final Context mContext;
283 
284     // A note on lock ordering:
285     // You can take mLock while holding VirtualMachineManager.sCreateLock, but not vice versa.
286     // We never take any other lock while holding mCallbackLock; therefore you can
287     // take mCallbackLock while holding any other lock.
288 
289     /** Lock protecting our mutable state (other than callbacks). */
290     private final Object mLock = new Object();
291 
292     /** Lock protecting callbacks. */
293     private final Object mCallbackLock = new Object();
294 
295     private final boolean mVmOutputCaptured;
296 
297     /** The configuration that is currently associated with this VM. */
298     @GuardedBy("mLock")
299     @NonNull
300     private VirtualMachineConfig mConfig;
301 
302     /** Handle to the "running" VM. */
303     @GuardedBy("mLock")
304     @Nullable
305     private IVirtualMachine mVirtualMachine;
306 
307     @GuardedBy("mLock")
308     @Nullable
309     private ParcelFileDescriptor mConsoleReader;
310 
311     @GuardedBy("mLock")
312     @Nullable
313     private ParcelFileDescriptor mConsoleWriter;
314 
315     @GuardedBy("mLock")
316     @Nullable
317     private ParcelFileDescriptor mLogReader;
318 
319     @GuardedBy("mLock")
320     @Nullable
321     private ParcelFileDescriptor mLogWriter;
322 
323     @GuardedBy("mLock")
324     private boolean mWasDeleted = false;
325 
326     /** The registered callback */
327     @GuardedBy("mCallbackLock")
328     @Nullable
329     private VirtualMachineCallback mCallback;
330 
331     /** The executor on which the callback will be executed */
332     @GuardedBy("mCallbackLock")
333     @Nullable
334     private Executor mCallbackExecutor;
335 
336     private static class ExtraApkSpec {
337         public final File apk;
338         public final File idsig;
339 
ExtraApkSpec(File apk, File idsig)340         ExtraApkSpec(File apk, File idsig) {
341             this.apk = apk;
342             this.idsig = idsig;
343         }
344     }
345 
346     static {
347         System.loadLibrary("virtualmachine_jni");
348     }
349 
VirtualMachine( @onNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config, @NonNull VirtualizationService service)350     private VirtualMachine(
351             @NonNull Context context,
352             @NonNull String name,
353             @NonNull VirtualMachineConfig config,
354             @NonNull VirtualizationService service)
355             throws VirtualMachineException {
356         mPackageName = context.getPackageName();
357         mName = requireNonNull(name, "Name must not be null");
358         mConfig = requireNonNull(config, "Config must not be null");
359         mVirtualizationService = service;
360 
361         File thisVmDir = getVmDir(context, mName);
362         mVmRootPath = thisVmDir;
363         mConfigFilePath = new File(thisVmDir, CONFIG_FILE);
364         mInstanceFilePath = new File(thisVmDir, INSTANCE_IMAGE_FILE);
365         mIdsigFilePath = new File(thisVmDir, IDSIG_FILE);
366         mExtraApks = setupExtraApks(context, config, thisVmDir);
367         mMemoryManagementCallbacks = new MemoryManagementCallbacks();
368         mContext = context;
369         mEncryptedStoreFilePath =
370                 (config.isEncryptedStorageEnabled())
371                         ? new File(thisVmDir, ENCRYPTED_STORE_FILE)
372                         : null;
373 
374         mVmOutputCaptured = config.isVmOutputCaptured();
375     }
376 
377     /**
378      * Creates a virtual machine from an {@link VirtualMachineDescriptor} object and associates it
379      * with the given name.
380      *
381      * <p>The new virtual machine will be in the same state as the descriptor indicates.
382      *
383      * <p>Once a virtual machine is imported it is persisted until it is deleted by calling {@link
384      * #delete}. The imported virtual machine is in {@link #STATUS_STOPPED} state. To run the VM,
385      * call {@link #run}.
386      */
387     @GuardedBy("VirtualMachineManager.sCreateLock")
388     @NonNull
fromDescriptor( @onNull Context context, @NonNull String name, @NonNull VirtualMachineDescriptor vmDescriptor)389     static VirtualMachine fromDescriptor(
390             @NonNull Context context,
391             @NonNull String name,
392             @NonNull VirtualMachineDescriptor vmDescriptor)
393             throws VirtualMachineException {
394         File vmDir = createVmDir(context, name);
395         try {
396             VirtualMachine vm;
397             try (vmDescriptor) {
398                 VirtualMachineConfig config = VirtualMachineConfig.from(vmDescriptor.getConfigFd());
399                 vm = new VirtualMachine(context, name, config, VirtualizationService.getInstance());
400                 config.serialize(vm.mConfigFilePath);
401                 try {
402                     vm.mInstanceFilePath.createNewFile();
403                 } catch (IOException e) {
404                     throw new VirtualMachineException("failed to create instance image", e);
405                 }
406                 vm.importInstanceFrom(vmDescriptor.getInstanceImgFd());
407 
408                 if (vmDescriptor.getEncryptedStoreFd() != null) {
409                     try {
410                         vm.mEncryptedStoreFilePath.createNewFile();
411                     } catch (IOException e) {
412                         throw new VirtualMachineException(
413                                 "failed to create encrypted storage image", e);
414                     }
415                     vm.importEncryptedStoreFrom(vmDescriptor.getEncryptedStoreFd());
416                 }
417             }
418             return vm;
419         } catch (VirtualMachineException | RuntimeException e) {
420             // If anything goes wrong, delete any files created so far and the VM's directory
421             try {
422                 deleteRecursively(vmDir);
423             } catch (IOException innerException) {
424                 e.addSuppressed(innerException);
425             }
426             throw e;
427         }
428     }
429 
430     /**
431      * Creates a virtual machine with the given name and config. Once a virtual machine is created
432      * it is persisted until it is deleted by calling {@link #delete}. The created virtual machine
433      * is in {@link #STATUS_STOPPED} state. To run the VM, call {@link #run}.
434      */
435     @GuardedBy("VirtualMachineManager.sCreateLock")
436     @NonNull
create( @onNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config)437     static VirtualMachine create(
438             @NonNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config)
439             throws VirtualMachineException {
440         File vmDir = createVmDir(context, name);
441 
442         try {
443             VirtualMachine vm =
444                     new VirtualMachine(context, name, config, VirtualizationService.getInstance());
445             config.serialize(vm.mConfigFilePath);
446             try {
447                 vm.mInstanceFilePath.createNewFile();
448             } catch (IOException e) {
449                 throw new VirtualMachineException("failed to create instance image", e);
450             }
451             if (config.isEncryptedStorageEnabled()) {
452                 try {
453                     vm.mEncryptedStoreFilePath.createNewFile();
454                 } catch (IOException e) {
455                     throw new VirtualMachineException(
456                             "failed to create encrypted storage image", e);
457                 }
458             }
459 
460             IVirtualizationService service = vm.mVirtualizationService.getBinder();
461 
462             try {
463                 service.initializeWritablePartition(
464                         ParcelFileDescriptor.open(vm.mInstanceFilePath, MODE_READ_WRITE),
465                         INSTANCE_FILE_SIZE,
466                         PartitionType.ANDROID_VM_INSTANCE);
467             } catch (FileNotFoundException e) {
468                 throw new VirtualMachineException("instance image missing", e);
469             } catch (RemoteException e) {
470                 throw e.rethrowAsRuntimeException();
471             } catch (ServiceSpecificException | IllegalArgumentException e) {
472                 throw new VirtualMachineException("failed to create instance partition", e);
473             }
474 
475             if (config.isEncryptedStorageEnabled()) {
476                 try {
477                     service.initializeWritablePartition(
478                             ParcelFileDescriptor.open(vm.mEncryptedStoreFilePath, MODE_READ_WRITE),
479                             config.getEncryptedStorageBytes(),
480                             PartitionType.ENCRYPTEDSTORE);
481                 } catch (FileNotFoundException e) {
482                     throw new VirtualMachineException("encrypted storage image missing", e);
483                 } catch (RemoteException e) {
484                     throw e.rethrowAsRuntimeException();
485                 } catch (ServiceSpecificException | IllegalArgumentException e) {
486                     throw new VirtualMachineException(
487                             "failed to create encrypted storage partition", e);
488                 }
489             }
490             return vm;
491         } catch (VirtualMachineException | RuntimeException e) {
492             // If anything goes wrong, delete any files created so far and the VM's directory
493             try {
494                 deleteRecursively(vmDir);
495             } catch (IOException innerException) {
496                 e.addSuppressed(innerException);
497             }
498             throw e;
499         }
500     }
501 
502     /** Loads a virtual machine that is already created before. */
503     @GuardedBy("VirtualMachineManager.sCreateLock")
504     @Nullable
load(@onNull Context context, @NonNull String name)505     static VirtualMachine load(@NonNull Context context, @NonNull String name)
506             throws VirtualMachineException {
507         File thisVmDir = getVmDir(context, name);
508         if (!thisVmDir.exists()) {
509             // The VM doesn't exist.
510             return null;
511         }
512         File configFilePath = new File(thisVmDir, CONFIG_FILE);
513         VirtualMachineConfig config = VirtualMachineConfig.from(configFilePath);
514         VirtualMachine vm =
515                 new VirtualMachine(context, name, config, VirtualizationService.getInstance());
516 
517         if (!vm.mInstanceFilePath.exists()) {
518             throw new VirtualMachineException("instance image missing");
519         }
520         if (config.isEncryptedStorageEnabled() && !vm.mEncryptedStoreFilePath.exists()) {
521             throw new VirtualMachineException("Storage image missing");
522         }
523         return vm;
524     }
525 
526     @GuardedBy("VirtualMachineManager.sCreateLock")
delete(Context context, String name)527     void delete(Context context, String name) throws VirtualMachineException {
528         synchronized (mLock) {
529             checkStopped();
530             // Once we explicitly delete a VM it must remain permanently in the deleted state;
531             // if a new VM is created with the same name (and files) that's unrelated.
532             mWasDeleted = true;
533         }
534         deleteVmDirectory(context, name);
535     }
536 
deleteVmDirectory(Context context, String name)537     static void deleteVmDirectory(Context context, String name) throws VirtualMachineException {
538         try {
539             deleteRecursively(getVmDir(context, name));
540         } catch (IOException e) {
541             throw new VirtualMachineException(e);
542         }
543     }
544 
545     @GuardedBy("VirtualMachineManager.sCreateLock")
546     @NonNull
createVmDir(@onNull Context context, @NonNull String name)547     private static File createVmDir(@NonNull Context context, @NonNull String name)
548             throws VirtualMachineException {
549         File vmDir = getVmDir(context, name);
550         try {
551             // We don't need to undo this even if VM creation fails.
552             Files.createDirectories(vmDir.getParentFile().toPath());
553 
554             // The checking of the existence of this directory and the creation of it is done
555             // atomically. If the directory already exists (i.e. the VM with the same name was
556             // already created), FileAlreadyExistsException is thrown.
557             Files.createDirectory(vmDir.toPath());
558         } catch (FileAlreadyExistsException e) {
559             throw new VirtualMachineException("virtual machine already exists", e);
560         } catch (IOException e) {
561             throw new VirtualMachineException("failed to create directory for VM", e);
562         }
563         return vmDir;
564     }
565 
566     @NonNull
getVmDir(@onNull Context context, @NonNull String name)567     private static File getVmDir(@NonNull Context context, @NonNull String name) {
568         if (name.contains(File.separator) || name.equals(".") || name.equals("..")) {
569             throw new IllegalArgumentException("Invalid VM name: " + name);
570         }
571         File vmRoot = new File(context.getDataDir(), VM_DIR);
572         return new File(vmRoot, name);
573     }
574 
575     /**
576      * Returns the name of this virtual machine. The name is unique in the package and can't be
577      * changed.
578      *
579      * @hide
580      */
581     @SystemApi
582     @NonNull
getName()583     public String getName() {
584         return mName;
585     }
586 
587     /**
588      * Returns the currently selected config of this virtual machine. There can be multiple virtual
589      * machines sharing the same config. Even in that case, the virtual machines are completely
590      * isolated from each other; they have different secrets. It is also possible that a virtual
591      * machine can change its config, which can be done by calling {@link #setConfig}.
592      *
593      * <p>NOTE: This method may block and should not be called on the main thread.
594      *
595      * @hide
596      */
597     @SystemApi
598     @WorkerThread
599     @NonNull
getConfig()600     public VirtualMachineConfig getConfig() {
601         synchronized (mLock) {
602             return mConfig;
603         }
604     }
605 
606     /**
607      * Returns the current status of this virtual machine.
608      *
609      * <p>NOTE: This method may block and should not be called on the main thread.
610      *
611      * @hide
612      */
613     @SystemApi
614     @WorkerThread
615     @Status
getStatus()616     public int getStatus() {
617         IVirtualMachine virtualMachine;
618         synchronized (mLock) {
619             if (mWasDeleted) {
620                 return STATUS_DELETED;
621             }
622             virtualMachine = mVirtualMachine;
623         }
624 
625         int status;
626         if (virtualMachine == null) {
627             status = STATUS_STOPPED;
628         } else {
629             try {
630                 status = stateToStatus(virtualMachine.getState());
631             } catch (RemoteException e) {
632                 throw e.rethrowAsRuntimeException();
633             }
634         }
635         if (status == STATUS_STOPPED && !mVmRootPath.exists()) {
636             // A VM can quite happily keep running if its backing files have been deleted.
637             // But once it stops, it's gone forever.
638             synchronized (mLock) {
639                 dropVm();
640             }
641             return STATUS_DELETED;
642         }
643         return status;
644     }
645 
stateToStatus(@irtualMachineState int state)646     private int stateToStatus(@VirtualMachineState int state) {
647         switch (state) {
648             case VirtualMachineState.STARTING:
649             case VirtualMachineState.STARTED:
650             case VirtualMachineState.READY:
651             case VirtualMachineState.FINISHED:
652                 return STATUS_RUNNING;
653             case VirtualMachineState.NOT_STARTED:
654             case VirtualMachineState.DEAD:
655             default:
656                 return STATUS_STOPPED;
657         }
658     }
659 
660     // Throw an appropriate exception if we have a running VM, or the VM has been deleted.
661     @GuardedBy("mLock")
checkStopped()662     private void checkStopped() throws VirtualMachineException {
663         if (mWasDeleted || !mVmRootPath.exists()) {
664             throw new VirtualMachineException("VM has been deleted");
665         }
666         if (mVirtualMachine == null) {
667             return;
668         }
669         try {
670             if (stateToStatus(mVirtualMachine.getState()) != STATUS_STOPPED) {
671                 throw new VirtualMachineException("VM is not in stopped state");
672             }
673         } catch (RemoteException e) {
674             throw e.rethrowAsRuntimeException();
675         }
676         // It's stopped, but we still have a reference to it - we can fix that.
677         dropVm();
678     }
679 
680     /**
681      * This should only be called when we know our VM has stopped; we no longer need to hold a
682      * reference to it (this allows resources to be GC'd) and we no longer need to be informed of
683      * memory pressure.
684      */
685     @GuardedBy("mLock")
dropVm()686     private void dropVm() {
687         mContext.unregisterComponentCallbacks(mMemoryManagementCallbacks);
688         mVirtualMachine = null;
689     }
690 
691     /** If we have an IVirtualMachine in the running state return it, otherwise throw. */
692     @GuardedBy("mLock")
getRunningVm()693     private IVirtualMachine getRunningVm() throws VirtualMachineException {
694         try {
695             if (mVirtualMachine != null
696                     && stateToStatus(mVirtualMachine.getState()) == STATUS_RUNNING) {
697                 return mVirtualMachine;
698             } else {
699                 if (mWasDeleted || !mVmRootPath.exists()) {
700                     throw new VirtualMachineException("VM has been deleted");
701                 } else {
702                     throw new VirtualMachineException("VM is not in running state");
703                 }
704             }
705         } catch (RemoteException e) {
706             throw e.rethrowAsRuntimeException();
707         }
708     }
709 
710     /**
711      * Registers the callback object to get events from the virtual machine. If a callback was
712      * already registered, it is replaced with the new one.
713      *
714      * @hide
715      */
716     @SystemApi
setCallback( @onNull @allbackExecutor Executor executor, @NonNull VirtualMachineCallback callback)717     public void setCallback(
718             @NonNull @CallbackExecutor Executor executor,
719             @NonNull VirtualMachineCallback callback) {
720         synchronized (mCallbackLock) {
721             mCallback = callback;
722             mCallbackExecutor = executor;
723         }
724     }
725 
726     /**
727      * Clears the currently registered callback.
728      *
729      * @hide
730      */
731     @SystemApi
clearCallback()732     public void clearCallback() {
733         synchronized (mCallbackLock) {
734             mCallback = null;
735             mCallbackExecutor = null;
736         }
737     }
738 
739     /** Executes a callback on the callback executor. */
executeCallback(Consumer<VirtualMachineCallback> fn)740     private void executeCallback(Consumer<VirtualMachineCallback> fn) {
741         final VirtualMachineCallback callback;
742         final Executor executor;
743         synchronized (mCallbackLock) {
744             callback = mCallback;
745             executor = mCallbackExecutor;
746         }
747         if (callback == null || executor == null) {
748             return;
749         }
750         final long restoreToken = Binder.clearCallingIdentity();
751         try {
752             executor.execute(() -> fn.accept(callback));
753         } finally {
754             Binder.restoreCallingIdentity(restoreToken);
755         }
756     }
757 
758     /**
759      * Runs this virtual machine. The returning of this method however doesn't mean that the VM has
760      * actually started running or the OS has booted there. Such events can be notified by
761      * registering a callback using {@link #setCallback} before calling {@code run()}.
762      *
763      * <p>NOTE: This method may block and should not be called on the main thread.
764      *
765      * @throws VirtualMachineException if the virtual machine is not stopped or could not be
766      *     started.
767      * @hide
768      */
769     @SystemApi
770     @WorkerThread
771     @RequiresPermission(MANAGE_VIRTUAL_MACHINE_PERMISSION)
run()772     public void run() throws VirtualMachineException {
773         synchronized (mLock) {
774             checkStopped();
775 
776             try {
777                 mIdsigFilePath.createNewFile();
778                 for (ExtraApkSpec extraApk : mExtraApks) {
779                     extraApk.idsig.createNewFile();
780                 }
781             } catch (IOException e) {
782                 // If the file already exists, exception is not thrown.
783                 throw new VirtualMachineException("Failed to create APK signature file", e);
784             }
785 
786             IVirtualizationService service = mVirtualizationService.getBinder();
787 
788             try {
789                 if (mVmOutputCaptured) {
790                     createVmPipes();
791                 }
792 
793                 VirtualMachineAppConfig appConfig =
794                         getConfig().toVsConfig(mContext.getPackageManager());
795                 appConfig.name = mName;
796 
797                 try {
798                     createIdSigs(service, appConfig);
799                 } catch (FileNotFoundException e) {
800                     throw new VirtualMachineException("Failed to generate APK signature", e);
801                 }
802 
803                 android.system.virtualizationservice.VirtualMachineConfig vmConfigParcel =
804                         android.system.virtualizationservice.VirtualMachineConfig.appConfig(
805                                 appConfig);
806 
807                 mVirtualMachine = service.createVm(vmConfigParcel, mConsoleWriter, mLogWriter);
808                 mVirtualMachine.registerCallback(new CallbackTranslator(service));
809                 mContext.registerComponentCallbacks(mMemoryManagementCallbacks);
810                 mVirtualMachine.start();
811             } catch (IllegalStateException | ServiceSpecificException e) {
812                 throw new VirtualMachineException(e);
813             } catch (RemoteException e) {
814                 throw e.rethrowAsRuntimeException();
815             }
816         }
817     }
818 
createIdSigs(IVirtualizationService service, VirtualMachineAppConfig appConfig)819     private void createIdSigs(IVirtualizationService service, VirtualMachineAppConfig appConfig)
820             throws RemoteException, FileNotFoundException {
821         // Fill the idsig file by hashing the apk
822         service.createOrUpdateIdsigFile(
823                 appConfig.apk, ParcelFileDescriptor.open(mIdsigFilePath, MODE_READ_WRITE));
824 
825         for (ExtraApkSpec extraApk : mExtraApks) {
826             service.createOrUpdateIdsigFile(
827                     ParcelFileDescriptor.open(extraApk.apk, MODE_READ_ONLY),
828                     ParcelFileDescriptor.open(extraApk.idsig, MODE_READ_WRITE));
829         }
830 
831         // Re-open idsig files in read-only mode
832         appConfig.idsig = ParcelFileDescriptor.open(mIdsigFilePath, MODE_READ_ONLY);
833         appConfig.instanceImage = ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_WRITE);
834         if (mEncryptedStoreFilePath != null) {
835             appConfig.encryptedStorageImage =
836                     ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_WRITE);
837         }
838         List<ParcelFileDescriptor> extraIdsigs = new ArrayList<>();
839         for (ExtraApkSpec extraApk : mExtraApks) {
840             extraIdsigs.add(ParcelFileDescriptor.open(extraApk.idsig, MODE_READ_ONLY));
841         }
842         appConfig.extraIdsigs = extraIdsigs;
843     }
844 
845     @GuardedBy("mLock")
createVmPipes()846     private void createVmPipes() throws VirtualMachineException {
847         try {
848             if (mConsoleReader == null || mConsoleWriter == null) {
849                 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
850                 mConsoleReader = pipe[0];
851                 mConsoleWriter = pipe[1];
852             }
853 
854             if (mLogReader == null || mLogWriter == null) {
855                 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
856                 mLogReader = pipe[0];
857                 mLogWriter = pipe[1];
858             }
859         } catch (IOException e) {
860             throw new VirtualMachineException("Failed to create stream for VM", e);
861         }
862     }
863 
864     /**
865      * Returns the stream object representing the console output from the virtual machine. The
866      * console output is only available if the {@link VirtualMachineConfig} specifies that it should
867      * be {@linkplain VirtualMachineConfig#isVmOutputCaptured captured}.
868      *
869      * <p>If you turn on output capture, you must consume data from {@code getConsoleOutput} -
870      * because otherwise the code in the VM may get blocked when the pipe buffer fills up.
871      *
872      * <p>NOTE: This method may block and should not be called on the main thread.
873      *
874      * @throws VirtualMachineException if the stream could not be created, or capturing is turned
875      *     off.
876      * @hide
877      */
878     @SystemApi
879     @WorkerThread
880     @NonNull
getConsoleOutput()881     public InputStream getConsoleOutput() throws VirtualMachineException {
882         if (!mVmOutputCaptured) {
883             throw new VirtualMachineException("Capturing vm outputs is turned off");
884         }
885         synchronized (mLock) {
886             createVmPipes();
887             return new FileInputStream(mConsoleReader.getFileDescriptor());
888         }
889     }
890 
891     /**
892      * Returns the stream object representing the log output from the virtual machine. The log
893      * output is only available if the VirtualMachineConfig specifies that it should be {@linkplain
894      * VirtualMachineConfig#isVmOutputCaptured captured}.
895      *
896      * <p>If you turn on output capture, you must consume data from {@code getLogOutput} - because
897      * otherwise the code in the VM may get blocked when the pipe buffer fills up.
898      *
899      * <p>NOTE: This method may block and should not be called on the main thread.
900      *
901      * @throws VirtualMachineException if the stream could not be created, or capturing is turned
902      *     off.
903      * @hide
904      */
905     @SystemApi
906     @WorkerThread
907     @NonNull
getLogOutput()908     public InputStream getLogOutput() throws VirtualMachineException {
909         if (!mVmOutputCaptured) {
910             throw new VirtualMachineException("Capturing vm outputs is turned off");
911         }
912         synchronized (mLock) {
913             createVmPipes();
914             return new FileInputStream(mLogReader.getFileDescriptor());
915         }
916     }
917 
918     /**
919      * Stops this virtual machine. Stopping a virtual machine is like pulling the plug on a real
920      * computer; the machine halts immediately. Software running on the virtual machine is not
921      * notified of the event. Writes to {@linkplain
922      * VirtualMachineConfig.Builder#setEncryptedStorageBytes encrypted storage} might not be
923      * persisted, and the instance might be left in an inconsistent state.
924      *
925      * <p>For a graceful shutdown, you could request the payload to call {@code exit()}, e.g. via a
926      * {@linkplain #connectToVsockServer binder request}, and wait for {@link
927      * VirtualMachineCallback#onPayloadFinished} to be called.
928      *
929      * <p>A stopped virtual machine can be re-started by calling {@link #run()}.
930      *
931      * <p>NOTE: This method may block and should not be called on the main thread.
932      *
933      * @throws VirtualMachineException if the virtual machine is not running or could not be
934      *     stopped.
935      * @hide
936      */
937     @SystemApi
938     @WorkerThread
stop()939     public void stop() throws VirtualMachineException {
940         synchronized (mLock) {
941             if (mVirtualMachine == null) {
942                 throw new VirtualMachineException("VM is not running");
943             }
944             try {
945                 mVirtualMachine.stop();
946                 dropVm();
947             } catch (RemoteException e) {
948                 throw e.rethrowAsRuntimeException();
949             } catch (ServiceSpecificException e) {
950                 throw new VirtualMachineException(e);
951             }
952         }
953     }
954 
955     /**
956      * Stops this virtual machine, if it is running.
957      *
958      * <p>NOTE: This method may block and should not be called on the main thread.
959      *
960      * @see #stop()
961      * @hide
962      */
963     @SystemApi
964     @WorkerThread
965     @Override
close()966     public void close() {
967         synchronized (mLock) {
968             if (mVirtualMachine == null) {
969                 return;
970             }
971             try {
972                 if (stateToStatus(mVirtualMachine.getState()) == STATUS_RUNNING) {
973                     mVirtualMachine.stop();
974                     dropVm();
975                 }
976             } catch (RemoteException e) {
977                 throw e.rethrowAsRuntimeException();
978             } catch (ServiceSpecificException e) {
979                 // Deliberately ignored; this almost certainly means the VM exited just as
980                 // we tried to stop it.
981                 Log.i(TAG, "Ignoring error on close()", e);
982             }
983         }
984     }
985 
deleteRecursively(File dir)986     private static void deleteRecursively(File dir) throws IOException {
987         // Note: This doesn't follow symlinks, which is important. Instead they are just deleted
988         // (and Files.delete deletes the link not the target).
989         Files.walkFileTree(dir.toPath(), new SimpleFileVisitor<>() {
990             @Override
991             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
992                     throws IOException {
993                 Files.delete(file);
994                 return FileVisitResult.CONTINUE;
995             }
996 
997             @Override
998             public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
999                 // Directory is deleted after we've visited (deleted) all its contents, so it
1000                 // should be empty by now.
1001                 Files.delete(dir);
1002                 return FileVisitResult.CONTINUE;
1003             }
1004         });
1005     }
1006 
1007     /**
1008      * Changes the config of this virtual machine to a new one. This can be used to adjust things
1009      * like the number of CPU and size of the RAM, depending on the situation (e.g. the size of the
1010      * application to run on the virtual machine, etc.)
1011      *
1012      * <p>The new config must be {@linkplain VirtualMachineConfig#isCompatibleWith compatible with}
1013      * the existing config.
1014      *
1015      * <p>NOTE: This method may block and should not be called on the main thread.
1016      *
1017      * @return the old config
1018      * @throws VirtualMachineException if the virtual machine is not stopped, or the new config is
1019      *     incompatible.
1020      * @hide
1021      */
1022     @SystemApi
1023     @WorkerThread
1024     @NonNull
setConfig(@onNull VirtualMachineConfig newConfig)1025     public VirtualMachineConfig setConfig(@NonNull VirtualMachineConfig newConfig)
1026             throws VirtualMachineException {
1027         synchronized (mLock) {
1028             VirtualMachineConfig oldConfig = mConfig;
1029             if (!oldConfig.isCompatibleWith(newConfig)) {
1030                 throw new VirtualMachineException("incompatible config");
1031             }
1032             checkStopped();
1033 
1034             if (oldConfig != newConfig) {
1035                 // Delete any existing file before recreating; that ensures any
1036                 // VirtualMachineDescriptor that refers to the old file does not see the new config.
1037                 mConfigFilePath.delete();
1038                 newConfig.serialize(mConfigFilePath);
1039                 mConfig = newConfig;
1040             }
1041             return oldConfig;
1042         }
1043     }
1044 
1045     @Nullable
nativeConnectToVsockServer(IBinder vmBinder, int port)1046     private static native IBinder nativeConnectToVsockServer(IBinder vmBinder, int port);
1047 
1048     /**
1049      * Connect to a VM's binder service via vsock and return the root IBinder object. Guest VMs are
1050      * expected to set up vsock servers in their payload. After the host app receives the {@link
1051      * VirtualMachineCallback#onPayloadReady}, it can use this method to establish a connection to
1052      * the guest VM.
1053      *
1054      * <p>NOTE: This method may block and should not be called on the main thread.
1055      *
1056      * @throws VirtualMachineException if the virtual machine is not running or the connection
1057      *     failed.
1058      * @hide
1059      */
1060     @SystemApi
1061     @WorkerThread
1062     @NonNull
connectToVsockServer( @ntRangefrom = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)1063     public IBinder connectToVsockServer(
1064             @IntRange(from = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)
1065             throws VirtualMachineException {
1066 
1067         synchronized (mLock) {
1068             IBinder iBinder =
1069                     nativeConnectToVsockServer(getRunningVm().asBinder(), validatePort(port));
1070             if (iBinder == null) {
1071                 throw new VirtualMachineException("Failed to connect to vsock server");
1072             }
1073             return iBinder;
1074         }
1075     }
1076 
1077     /**
1078      * Opens a vsock connection to the VM on the given port.
1079      *
1080      * <p>The caller is responsible for closing the returned {@code ParcelFileDescriptor}.
1081      *
1082      * <p>NOTE: This method may block and should not be called on the main thread.
1083      *
1084      * @throws VirtualMachineException if connecting fails.
1085      * @hide
1086      */
1087     @SystemApi
1088     @WorkerThread
1089     @NonNull
connectVsock( @ntRangefrom = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)1090     public ParcelFileDescriptor connectVsock(
1091             @IntRange(from = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)
1092             throws VirtualMachineException {
1093         synchronized (mLock) {
1094             try {
1095                 return getRunningVm().connectVsock(validatePort(port));
1096             } catch (RemoteException e) {
1097                 throw e.rethrowAsRuntimeException();
1098             } catch (ServiceSpecificException e) {
1099                 throw new VirtualMachineException(e);
1100             }
1101         }
1102     }
1103 
validatePort(long port)1104     private int validatePort(long port) {
1105         // Ports below 1024 are "privileged" (payload code can't bind to these), and port numbers
1106         // are 32-bit unsigned numbers at the OS level, even though we pass them as 32-bit signed
1107         // numbers internally.
1108         if (port < MIN_VSOCK_PORT || port > MAX_VSOCK_PORT) {
1109             throw new IllegalArgumentException("Bad port " + port);
1110         }
1111         return (int) port;
1112     }
1113 
1114     /**
1115      * Returns the root directory where all files related to this {@link VirtualMachine} (e.g.
1116      * {@code instance.img}, {@code apk.idsig}, etc) are stored.
1117      *
1118      * @hide
1119      */
1120     @TestApi
1121     @NonNull
getRootDir()1122     public File getRootDir() {
1123         return mVmRootPath;
1124     }
1125 
1126     /**
1127      * Captures the current state of the VM in a {@link VirtualMachineDescriptor} instance. The VM
1128      * needs to be stopped to avoid inconsistency in its state representation.
1129      *
1130      * <p>The state of the VM is not actually copied until {@link
1131      * VirtualMachineManager#importFromDescriptor} is called. It is recommended that the VM not be
1132      * started until that operation is complete.
1133      *
1134      * <p>NOTE: This method may block and should not be called on the main thread.
1135      *
1136      * @return a {@link VirtualMachineDescriptor} instance that represents the VM's state.
1137      * @throws VirtualMachineException if the virtual machine is not stopped, or the state could not
1138      *     be captured.
1139      * @hide
1140      */
1141     @SystemApi
1142     @WorkerThread
1143     @NonNull
toDescriptor()1144     public VirtualMachineDescriptor toDescriptor() throws VirtualMachineException {
1145         synchronized (mLock) {
1146             checkStopped();
1147             try {
1148                 return new VirtualMachineDescriptor(
1149                         ParcelFileDescriptor.open(mConfigFilePath, MODE_READ_ONLY),
1150                         ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_ONLY),
1151                         mEncryptedStoreFilePath != null
1152                                 ? ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_ONLY)
1153                                 : null);
1154             } catch (IOException e) {
1155                 throw new VirtualMachineException(e);
1156             }
1157         }
1158     }
1159 
1160     @Override
toString()1161     public String toString() {
1162         VirtualMachineConfig config = getConfig();
1163         String payloadConfigPath = config.getPayloadConfigPath();
1164         String payloadBinaryName = config.getPayloadBinaryName();
1165 
1166         StringBuilder result = new StringBuilder();
1167         result.append("VirtualMachine(")
1168                 .append("name:")
1169                 .append(getName())
1170                 .append(", ");
1171         if (payloadBinaryName != null) {
1172             result.append("payload:").append(payloadBinaryName).append(", ");
1173         }
1174         if (payloadConfigPath != null) {
1175             result.append("config:")
1176                     .append(payloadConfigPath)
1177                     .append(", ");
1178         }
1179         result.append("package: ")
1180                 .append(mPackageName)
1181                 .append(")");
1182         return result.toString();
1183     }
1184 
parseExtraApkListFromPayloadConfig(JsonReader reader)1185     private static List<String> parseExtraApkListFromPayloadConfig(JsonReader reader)
1186             throws VirtualMachineException {
1187         /*
1188          * JSON schema from packages/modules/Virtualization/microdroid/payload/config/src/lib.rs:
1189          *
1190          * <p>{ "extra_apks": [ { "path": "/system/app/foo.apk", }, ... ], ... }
1191          */
1192         try {
1193             List<String> apks = new ArrayList<>();
1194 
1195             reader.beginObject();
1196             while (reader.hasNext()) {
1197                 if (reader.nextName().equals("extra_apks")) {
1198                     reader.beginArray();
1199                     while (reader.hasNext()) {
1200                         reader.beginObject();
1201                         String name = reader.nextName();
1202                         if (name.equals("path")) {
1203                             apks.add(reader.nextString());
1204                         } else {
1205                             reader.skipValue();
1206                         }
1207                         reader.endObject();
1208                     }
1209                     reader.endArray();
1210                 } else {
1211                     reader.skipValue();
1212                 }
1213             }
1214             reader.endObject();
1215             return apks;
1216         } catch (IOException e) {
1217             throw new VirtualMachineException(e);
1218         }
1219     }
1220 
1221     /**
1222      * Reads the payload config inside the application, parses extra APK information, and then
1223      * creates corresponding idsig file paths.
1224      */
setupExtraApks( @onNull Context context, @NonNull VirtualMachineConfig config, @NonNull File vmDir)1225     private static List<ExtraApkSpec> setupExtraApks(
1226             @NonNull Context context, @NonNull VirtualMachineConfig config, @NonNull File vmDir)
1227             throws VirtualMachineException {
1228         String configPath = config.getPayloadConfigPath();
1229         if (configPath == null) {
1230             return Collections.emptyList();
1231         }
1232         try (ZipFile zipFile = new ZipFile(context.getPackageCodePath())) {
1233             InputStream inputStream =
1234                     zipFile.getInputStream(zipFile.getEntry(configPath));
1235             List<String> apkList =
1236                     parseExtraApkListFromPayloadConfig(
1237                             new JsonReader(new InputStreamReader(inputStream)));
1238 
1239             List<ExtraApkSpec> extraApks = new ArrayList<>();
1240             for (int i = 0; i < apkList.size(); ++i) {
1241                 extraApks.add(
1242                         new ExtraApkSpec(
1243                                 new File(apkList.get(i)),
1244                                 new File(vmDir, EXTRA_IDSIG_FILE_PREFIX + i)));
1245             }
1246 
1247             return Collections.unmodifiableList(extraApks);
1248         } catch (IOException e) {
1249             throw new VirtualMachineException("Couldn't parse extra apks from the vm config", e);
1250         }
1251     }
1252 
importInstanceFrom(@onNull ParcelFileDescriptor instanceFd)1253     private void importInstanceFrom(@NonNull ParcelFileDescriptor instanceFd)
1254             throws VirtualMachineException {
1255         try (FileChannel instance = new FileOutputStream(mInstanceFilePath).getChannel();
1256                 FileChannel instanceInput = new AutoCloseInputStream(instanceFd).getChannel()) {
1257             instance.transferFrom(instanceInput, /*position=*/ 0, instanceInput.size());
1258         } catch (IOException e) {
1259             throw new VirtualMachineException("failed to transfer instance image", e);
1260         }
1261     }
1262 
importEncryptedStoreFrom(@onNull ParcelFileDescriptor encryptedStoreFd)1263     private void importEncryptedStoreFrom(@NonNull ParcelFileDescriptor encryptedStoreFd)
1264             throws VirtualMachineException {
1265         try (FileChannel storeOutput = new FileOutputStream(mEncryptedStoreFilePath).getChannel();
1266                 FileChannel storeInput = new AutoCloseInputStream(encryptedStoreFd).getChannel()) {
1267             storeOutput.transferFrom(storeInput, /*position=*/ 0, storeInput.size());
1268         } catch (IOException e) {
1269             throw new VirtualMachineException("failed to transfer encryptedstore image", e);
1270         }
1271     }
1272 
1273     /** Map the raw AIDL (& binder) callbacks to what the client expects. */
1274     private class CallbackTranslator extends IVirtualMachineCallback.Stub {
1275         private final IVirtualizationService mService;
1276         private final DeathRecipient mDeathRecipient;
1277 
1278         // The VM should only be observed to die once
1279         private final AtomicBoolean mOnDiedCalled = new AtomicBoolean(false);
1280 
CallbackTranslator(IVirtualizationService service)1281         public CallbackTranslator(IVirtualizationService service) throws RemoteException {
1282             this.mService = service;
1283             this.mDeathRecipient = () -> reportStopped(STOP_REASON_VIRTUALIZATION_SERVICE_DIED);
1284             service.asBinder().linkToDeath(mDeathRecipient, 0);
1285         }
1286 
1287         @Override
onPayloadStarted(int cid)1288         public void onPayloadStarted(int cid) {
1289             executeCallback((cb) -> cb.onPayloadStarted(VirtualMachine.this));
1290         }
1291 
1292         @Override
onPayloadReady(int cid)1293         public void onPayloadReady(int cid) {
1294             executeCallback((cb) -> cb.onPayloadReady(VirtualMachine.this));
1295         }
1296 
1297         @Override
onPayloadFinished(int cid, int exitCode)1298         public void onPayloadFinished(int cid, int exitCode) {
1299             executeCallback((cb) -> cb.onPayloadFinished(VirtualMachine.this, exitCode));
1300         }
1301 
1302         @Override
onError(int cid, int errorCode, String message)1303         public void onError(int cid, int errorCode, String message) {
1304             int translatedError = getTranslatedError(errorCode);
1305             executeCallback((cb) -> cb.onError(VirtualMachine.this, translatedError, message));
1306         }
1307 
1308         @Override
onDied(int cid, int reason)1309         public void onDied(int cid, int reason) {
1310             int translatedReason = getTranslatedReason(reason);
1311             reportStopped(translatedReason);
1312             mService.asBinder().unlinkToDeath(mDeathRecipient, 0);
1313         }
1314 
reportStopped(@irtualMachineCallback.StopReason int reason)1315         private void reportStopped(@VirtualMachineCallback.StopReason int reason) {
1316             if (mOnDiedCalled.compareAndSet(false, true)) {
1317                 executeCallback((cb) -> cb.onStopped(VirtualMachine.this, reason));
1318             }
1319         }
1320 
1321         @VirtualMachineCallback.ErrorCode
getTranslatedError(int reason)1322         private int getTranslatedError(int reason) {
1323             switch (reason) {
1324                 case ErrorCode.PAYLOAD_VERIFICATION_FAILED:
1325                     return ERROR_PAYLOAD_VERIFICATION_FAILED;
1326                 case ErrorCode.PAYLOAD_CHANGED:
1327                     return ERROR_PAYLOAD_CHANGED;
1328                 case ErrorCode.PAYLOAD_CONFIG_INVALID:
1329                     return ERROR_PAYLOAD_INVALID_CONFIG;
1330                 default:
1331                     return ERROR_UNKNOWN;
1332             }
1333         }
1334 
1335         @VirtualMachineCallback.StopReason
getTranslatedReason(int reason)1336         private int getTranslatedReason(int reason) {
1337             switch (reason) {
1338                 case DeathReason.INFRASTRUCTURE_ERROR:
1339                     return STOP_REASON_INFRASTRUCTURE_ERROR;
1340                 case DeathReason.KILLED:
1341                     return STOP_REASON_KILLED;
1342                 case DeathReason.SHUTDOWN:
1343                     return STOP_REASON_SHUTDOWN;
1344                 case DeathReason.START_FAILED:
1345                     return STOP_REASON_START_FAILED;
1346                 case DeathReason.REBOOT:
1347                     return STOP_REASON_REBOOT;
1348                 case DeathReason.CRASH:
1349                     return STOP_REASON_CRASH;
1350                 case DeathReason.PVM_FIRMWARE_PUBLIC_KEY_MISMATCH:
1351                     return STOP_REASON_PVM_FIRMWARE_PUBLIC_KEY_MISMATCH;
1352                 case DeathReason.PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED:
1353                     return STOP_REASON_PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED;
1354                 case DeathReason.MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE:
1355                     return STOP_REASON_MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE;
1356                 case DeathReason.MICRODROID_PAYLOAD_HAS_CHANGED:
1357                     return STOP_REASON_MICRODROID_PAYLOAD_HAS_CHANGED;
1358                 case DeathReason.MICRODROID_PAYLOAD_VERIFICATION_FAILED:
1359                     return STOP_REASON_MICRODROID_PAYLOAD_VERIFICATION_FAILED;
1360                 case DeathReason.MICRODROID_INVALID_PAYLOAD_CONFIG:
1361                     return STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG;
1362                 case DeathReason.MICRODROID_UNKNOWN_RUNTIME_ERROR:
1363                     return STOP_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR;
1364                 case DeathReason.HANGUP:
1365                     return STOP_REASON_HANGUP;
1366                 default:
1367                     return STOP_REASON_UNKNOWN;
1368             }
1369         }
1370     }
1371 }
1372