Move VmLauncherService to vm_launcher_lib

In this commit, just move the related code to vm_launcher_lib

Bug: 368281954
Test: run terminal app
Change-Id: I560aefea1efffa97aba9667c1c4ebfce9570153d
diff --git a/android/TerminalApp/Android.bp b/android/TerminalApp/Android.bp
index 3ae014e..1a7c581 100644
--- a/android/TerminalApp/Android.bp
+++ b/android/TerminalApp/Android.bp
@@ -9,8 +9,10 @@
     static_libs: [
         "vm_launcher_lib",
     ],
-    sdk_version: "system_current",
+    platform_apis: true,
+    privileged: true,
     optimize: {
+        proguard_flags_files: ["proguard.flags"],
         shrink_resources: true,
     },
     apex_available: [
diff --git a/android/TerminalApp/AndroidManifest.xml b/android/TerminalApp/AndroidManifest.xml
index c92da67..e338c49 100644
--- a/android/TerminalApp/AndroidManifest.xml
+++ b/android/TerminalApp/AndroidManifest.xml
@@ -2,9 +2,13 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.virtualization.terminal" >
 
+    <uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
+    <uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
     <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="com.android.virtualization.vmlauncher.permission.USE_VM_LAUNCHER"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
 
+    <uses-feature android:name="android.software.virtualization_framework" android:required="true" />
     <application
 	android:label="@string/app_name"
         android:icon="@mipmap/ic_launcher"
@@ -27,6 +31,20 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity-alias>
+
+        <service
+            android:name="com.android.virtualization.vmlauncher.VmLauncherService"
+            android:enabled="true"
+            android:exported="false"
+            android:foregroundServiceType="specialUse">
+            <property
+                android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
+                android:value="Run VM instances" />
+            <intent-filter>
+                <action android:name="android.virtualization.START_VM_LAUNCHER_SERVICE" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </service>
     </application>
 
 </manifest>
diff --git a/android/TerminalApp/proguard.flags b/android/TerminalApp/proguard.flags
new file mode 100644
index 0000000..13ec24e
--- /dev/null
+++ b/android/TerminalApp/proguard.flags
@@ -0,0 +1,7 @@
+# Keep the no-args constructor of the deserialized class
+-keepclassmembers class com.android.virtualization.vmlauncher.ConfigJson {
+  <init>();
+}
+-keepclassmembers class com.android.virtualization.vmlauncher.ConfigJson$* {
+  <init>();
+}
diff --git a/android/VmLauncherApp/Android.bp b/android/VmLauncherApp/Android.bp
index 7dd2473..2e8cc93 100644
--- a/android/VmLauncherApp/Android.bp
+++ b/android/VmLauncherApp/Android.bp
@@ -11,7 +11,7 @@
         "android.system.virtualizationservice_internal-java",
         // TODO(b/331708504): will be removed when AVF framework handles surface
         "libcrosvm_android_display_service-java",
-        "gson",
+        "vm_launcher_lib",
     ],
     libs: [
         "framework-virtualization.impl",
diff --git a/android/VmLauncherApp/AndroidManifest.xml b/android/VmLauncherApp/AndroidManifest.xml
index 583fce7..4fb4b5c 100644
--- a/android/VmLauncherApp/AndroidManifest.xml
+++ b/android/VmLauncherApp/AndroidManifest.xml
@@ -6,8 +6,6 @@
     <uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
     <uses-feature android:name="android.software.virtualization_framework" android:required="true" />
 
     <permission android:name="com.android.virtualization.vmlauncher.permission.USE_VM_LAUNCHER"
@@ -28,20 +26,6 @@
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
-        <service
-            android:name=".VmLauncherService"
-            android:enabled="true"
-            android:exported="true"
-            android:permission="com.android.virtualization.vmlauncher.permission.USE_VM_LAUNCHER"
-            android:foregroundServiceType="specialUse">
-            <property
-                android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
-                android:value="Run VM instances" />
-            <intent-filter>
-                <action android:name="android.virtualization.START_VM_LAUNCHER_SERVICE" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </service>
 
     </application>
 
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/ConfigJson.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/ConfigJson.java
deleted file mode 100644
index 6d39b46..0000000
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/ConfigJson.java
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.virtualization.vmlauncher;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.system.virtualmachine.VirtualMachineConfig;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig.AudioConfig;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig.Disk;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig.DisplayConfig;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig.GpuConfig;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig.Partition;
-import android.util.DisplayMetrics;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-
-import com.google.gson.Gson;
-import com.google.gson.annotations.SerializedName;
-
-import java.io.FileReader;
-import java.util.Arrays;
-
-/** This class and its inner classes model vm_config.json. */
-class ConfigJson {
-    private static final boolean DEBUG = true;
-
-    private ConfigJson() {}
-
-    @SerializedName("protected")
-    private boolean isProtected;
-
-    private String name;
-    private String cpu_topology;
-    private String platform_version;
-    private int memory_mib = 1024;
-    private String console_input_device;
-    private String bootloader;
-    private String kernel;
-    private String initrd;
-    private String params;
-    private boolean debuggable;
-    private boolean console_out;
-    private boolean connect_console;
-    private boolean network;
-    private InputJson input;
-    private AudioJson audio;
-    private DiskJson[] disks;
-    private DisplayJson display;
-    private GpuJson gpu;
-
-    /** Parses JSON file at jsonPath */
-    static ConfigJson from(String jsonPath) {
-        try (FileReader r = new FileReader(jsonPath)) {
-            return new Gson().fromJson(r, ConfigJson.class);
-        } catch (Exception e) {
-            throw new RuntimeException("Failed to parse " + jsonPath, e);
-        }
-    }
-
-    private int getCpuTopology() {
-        switch (cpu_topology) {
-            case "one_cpu":
-                return VirtualMachineConfig.CPU_TOPOLOGY_ONE_CPU;
-            case "match_host":
-                return VirtualMachineConfig.CPU_TOPOLOGY_MATCH_HOST;
-            default:
-                throw new RuntimeException("invalid cpu topology: " + cpu_topology);
-        }
-    }
-
-    private int getDebugLevel() {
-        return debuggable
-                ? VirtualMachineConfig.DEBUG_LEVEL_FULL
-                : VirtualMachineConfig.DEBUG_LEVEL_NONE;
-    }
-
-    /** Converts this parsed JSON into VirtualMachieConfig */
-    VirtualMachineConfig toConfig(Context context) {
-        return new VirtualMachineConfig.Builder(context)
-                .setProtectedVm(isProtected)
-                .setMemoryBytes((long) memory_mib * 1024 * 1024)
-                .setConsoleInputDevice(console_input_device)
-                .setCpuTopology(getCpuTopology())
-                .setCustomImageConfig(toCustomImageConfig(context))
-                .setDebugLevel(getDebugLevel())
-                .setVmOutputCaptured(console_out)
-                .setConnectVmConsole(connect_console)
-                .build();
-    }
-
-    private VirtualMachineCustomImageConfig toCustomImageConfig(Context context) {
-        VirtualMachineCustomImageConfig.Builder builder =
-                new VirtualMachineCustomImageConfig.Builder();
-
-        builder.setName(name)
-                .setBootloaderPath(bootloader)
-                .setKernelPath(kernel)
-                .setInitrdPath(initrd)
-                .useNetwork(network);
-
-        if (input != null) {
-            builder.useTouch(input.touchscreen)
-                    .useKeyboard(input.keyboard)
-                    .useMouse(input.mouse)
-                    .useTrackpad(input.trackpad)
-                    .useSwitches(input.switches);
-        }
-
-        if (audio != null) {
-            builder.setAudioConfig(audio.toConfig());
-        }
-
-        if (display != null) {
-            builder.setDisplayConfig(display.toConfig(context));
-        }
-
-        if (gpu != null) {
-            builder.setGpuConfig(gpu.toConfig());
-        }
-
-        if (params != null) {
-            Arrays.stream(params.split(" ")).forEach(builder::addParam);
-        }
-
-        if (disks != null) {
-            Arrays.stream(disks).map(d -> d.toConfig()).forEach(builder::addDisk);
-        }
-
-        return builder.build();
-    }
-
-    private static class InputJson {
-        private InputJson() {}
-
-        private boolean touchscreen;
-        private boolean keyboard;
-        private boolean mouse;
-        private boolean switches;
-        private boolean trackpad;
-    }
-
-    private static class AudioJson {
-        private AudioJson() {}
-
-        private boolean microphone;
-        private boolean speaker;
-
-        private AudioConfig toConfig() {
-            return new AudioConfig.Builder()
-                    .setUseMicrophone(microphone)
-                    .setUseSpeaker(speaker)
-                    .build();
-        }
-    }
-
-    private static class DiskJson {
-        private DiskJson() {}
-
-        private boolean writable;
-        private String image;
-        private PartitionJson[] partitions;
-
-        private Disk toConfig() {
-            Disk d = writable ? Disk.RWDisk(image) : Disk.RODisk(image);
-            for (PartitionJson pj : partitions) {
-                boolean writable = this.writable && pj.writable;
-                d.addPartition(new Partition(pj.label, pj.path, writable, pj.guid));
-            }
-            return d;
-        }
-    }
-
-    private static class PartitionJson {
-        private PartitionJson() {}
-
-        private boolean writable;
-        private String label;
-        private String path;
-        private String guid;
-    }
-
-    private static class DisplayJson {
-        private DisplayJson() {}
-
-        private float scale;
-        private int refresh_rate;
-        private int width_pixels;
-        private int height_pixels;
-
-        private DisplayConfig toConfig(Context context) {
-            WindowManager wm = context.getSystemService(WindowManager.class);
-            WindowMetrics metrics = wm.getCurrentWindowMetrics();
-            Rect dispBounds = metrics.getBounds();
-
-            int width = width_pixels > 0 ? width_pixels : dispBounds.right;
-            int height = height_pixels > 0 ? height_pixels : dispBounds.bottom;
-
-            int dpi = (int) (DisplayMetrics.DENSITY_DEFAULT * metrics.getDensity());
-            if (scale > 0.0f) {
-                dpi = (int) (dpi * scale);
-            }
-
-            int refreshRate = (int) context.getDisplay().getRefreshRate();
-            if (this.refresh_rate != 0) {
-                refreshRate = this.refresh_rate;
-            }
-
-            return new DisplayConfig.Builder()
-                    .setWidth(width)
-                    .setHeight(height)
-                    .setHorizontalDpi(dpi)
-                    .setVerticalDpi(dpi)
-                    .setRefreshRate(refreshRate)
-                    .build();
-        }
-    }
-
-    private static class GpuJson {
-        private GpuJson() {}
-
-        private String backend;
-        private String pci_address;
-        private String renderer_features;
-        private boolean renderer_use_egl = true;
-        private boolean renderer_use_gles = true;
-        private boolean renderer_use_glx = false;
-        private boolean renderer_use_surfaceless = true;
-        private boolean renderer_use_vulkan = false;
-        private String[] context_types;
-
-        private GpuConfig toConfig() {
-            return new GpuConfig.Builder()
-                    .setBackend(backend)
-                    .setPciAddress(pci_address)
-                    .setRendererFeatures(renderer_features)
-                    .setRendererUseEgl(renderer_use_egl)
-                    .setRendererUseGles(renderer_use_gles)
-                    .setRendererUseGlx(renderer_use_glx)
-                    .setRendererUseSurfaceless(renderer_use_surfaceless)
-                    .setRendererUseVulkan(renderer_use_vulkan)
-                    .setContextTypes(context_types)
-                    .build();
-        }
-    }
-}
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/Logger.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/Logger.java
deleted file mode 100644
index e1cb285..0000000
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/Logger.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.virtualization.vmlauncher;
-
-import android.system.virtualmachine.VirtualMachine;
-import android.system.virtualmachine.VirtualMachineConfig;
-import android.system.virtualmachine.VirtualMachineException;
-import android.util.Log;
-
-import libcore.io.Streams;
-
-import java.io.BufferedOutputStream;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.util.concurrent.ExecutorService;
-
-/**
- * Forwards VM's console output to a file on the Android side, and VM's log output to Android logd.
- */
-class Logger {
-    private Logger() {}
-
-    static void setup(VirtualMachine vm, Path path, ExecutorService executor) {
-        if (vm.getConfig().getDebugLevel() != VirtualMachineConfig.DEBUG_LEVEL_FULL) {
-            return;
-        }
-
-        try {
-            InputStream console = vm.getConsoleOutput();
-            OutputStream file = Files.newOutputStream(path, StandardOpenOption.CREATE);
-            executor.submit(() -> Streams.copy(console, new LineBufferedOutputStream(file)));
-
-            InputStream log = vm.getLogOutput();
-            executor.submit(() -> writeToLogd(log, vm.getName()));
-        } catch (VirtualMachineException | IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private static boolean writeToLogd(InputStream input, String vmName) throws IOException {
-        BufferedReader reader = new BufferedReader(new InputStreamReader(input));
-        String line;
-        while ((line = reader.readLine()) != null && !Thread.interrupted()) {
-            Log.d(vmName, line);
-        }
-        // TODO: find out why javac complains when the return type of this method is void. It
-        // (incorrectly?) thinks that IOException should be caught inside the lambda.
-        return true;
-    }
-
-    private static class LineBufferedOutputStream extends BufferedOutputStream {
-        LineBufferedOutputStream(OutputStream out) {
-            super(out);
-        }
-
-        @Override
-        public void write(byte[] buf, int off, int len) throws IOException {
-            super.write(buf, off, len);
-            for (int i = 0; i < len; ++i) {
-                if (buf[off + i] == '\n') {
-                    flush();
-                    break;
-                }
-            }
-        }
-    }
-}
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/Runner.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/Runner.java
deleted file mode 100644
index a5f58fe..0000000
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/Runner.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.virtualization.vmlauncher;
-
-import android.content.Context;
-import android.system.virtualmachine.VirtualMachine;
-import android.system.virtualmachine.VirtualMachineCallback;
-import android.system.virtualmachine.VirtualMachineConfig;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig;
-import android.system.virtualmachine.VirtualMachineException;
-import android.system.virtualmachine.VirtualMachineManager;
-import android.util.Log;
-
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ForkJoinPool;
-
-/** Utility class for creating a VM and waiting for it to finish. */
-class Runner {
-    private static final String TAG = MainActivity.TAG;
-    private final VirtualMachine mVirtualMachine;
-    private final Callback mCallback;
-
-    private Runner(VirtualMachine vm, Callback cb) {
-        mVirtualMachine = vm;
-        mCallback = cb;
-    }
-
-    /** Create a virtual machine of the given config, under the given context. */
-    static Runner create(Context context, VirtualMachineConfig config)
-            throws VirtualMachineException {
-        // context may already be the app context, but calling this again is not harmful.
-        // See b/359439878 on why vmm should be obtained from the app context.
-        Context appContext = context.getApplicationContext();
-        VirtualMachineManager vmm = appContext.getSystemService(VirtualMachineManager.class);
-        VirtualMachineCustomImageConfig customConfig = config.getCustomImageConfig();
-        if (customConfig == null) {
-            throw new RuntimeException("CustomImageConfig is missing");
-        }
-
-        String name = customConfig.getName();
-        if (name == null || name.isEmpty()) {
-            throw new RuntimeException("Virtual machine's name is missing in the config");
-        }
-
-        VirtualMachine vm = vmm.getOrCreate(name, config);
-        try {
-            vm.setConfig(config);
-        } catch (VirtualMachineException e) {
-            vmm.delete(name);
-            vm = vmm.create(name, config);
-            Log.w(TAG, "Re-creating virtual machine (" + name + ")", e);
-        }
-
-        Callback cb = new Callback();
-        vm.setCallback(ForkJoinPool.commonPool(), cb);
-        vm.run();
-        return new Runner(vm, cb);
-    }
-
-    /** Give access to the underlying VirtualMachine object. */
-    VirtualMachine getVm() {
-        return mVirtualMachine;
-    }
-
-    /** Get future about VM's exit status. */
-    CompletableFuture<Boolean> getExitStatus() {
-        return mCallback.mFinishedSuccessfully;
-    }
-
-    private static class Callback implements VirtualMachineCallback {
-        final CompletableFuture<Boolean> mFinishedSuccessfully = new CompletableFuture<>();
-
-        @Override
-        public void onPayloadStarted(VirtualMachine vm) {
-            // This event is only from Microdroid-based VM. Custom VM shouldn't emit this.
-        }
-
-        @Override
-        public void onPayloadReady(VirtualMachine vm) {
-            // This event is only from Microdroid-based VM. Custom VM shouldn't emit this.
-        }
-
-        @Override
-        public void onPayloadFinished(VirtualMachine vm, int exitCode) {
-            // This event is only from Microdroid-based VM. Custom VM shouldn't emit this.
-        }
-
-        @Override
-        public void onError(VirtualMachine vm, int errorCode, String message) {
-            Log.e(TAG, "Error from VM. code: " + errorCode + " (" + message + ")");
-            mFinishedSuccessfully.complete(false);
-        }
-
-        @Override
-        public void onStopped(VirtualMachine vm, int reason) {
-            Log.d(TAG, "VM stopped. Reason: " + reason);
-            mFinishedSuccessfully.complete(true);
-        }
-    }
-}
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmLauncherService.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmLauncherService.java
deleted file mode 100644
index 5e78f99..0000000
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmLauncherService.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.virtualization.vmlauncher;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.Service;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.ParcelFileDescriptor;
-import android.os.ResultReceiver;
-import android.system.virtualmachine.VirtualMachine;
-import android.system.virtualmachine.VirtualMachineConfig;
-import android.system.virtualmachine.VirtualMachineException;
-import android.util.Log;
-
-import java.io.BufferedReader;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.nio.file.Path;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-public class VmLauncherService extends Service {
-    private static final String TAG = "VmLauncherService";
-    // TODO: this path should be from outside of this service
-    private static final String VM_CONFIG_PATH = "/data/local/tmp/vm_config.json";
-
-    private static final int RESULT_START = 0;
-    private static final int RESULT_STOP = 1;
-    private static final int RESULT_ERROR = 2;
-    private static final int RESULT_IPADDR = 3;
-    private static final String KEY_VM_IP_ADDR = "ip_addr";
-
-    private ExecutorService mExecutorService;
-    private VirtualMachine mVirtualMachine;
-    private ResultReceiver mResultReceiver;
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        return null;
-    }
-
-    private void startForeground() {
-        NotificationManager notificationManager = getSystemService(NotificationManager.class);
-        NotificationChannel notificationChannel =
-                new NotificationChannel(TAG, TAG, NotificationManager.IMPORTANCE_LOW);
-        notificationManager.createNotificationChannel(notificationChannel);
-        startForeground(
-                this.hashCode(),
-                new Notification.Builder(this, TAG)
-                        .setChannelId(TAG)
-                        .setSmallIcon(android.R.drawable.ic_dialog_info)
-                        .setContentText("A VM " + mVirtualMachine.getName() + " is running")
-                        .build());
-    }
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        if (isVmRunning()) {
-            Log.d(TAG, "there is already the running VM instance");
-            return START_NOT_STICKY;
-        }
-        mExecutorService = Executors.newCachedThreadPool();
-
-        ConfigJson json = ConfigJson.from(VM_CONFIG_PATH);
-        VirtualMachineConfig config = json.toConfig(this);
-
-        Runner runner;
-        try {
-            runner = Runner.create(this, config);
-        } catch (VirtualMachineException e) {
-            Log.e(TAG, "cannot create runner", e);
-            stopSelf();
-            return START_NOT_STICKY;
-        }
-        mVirtualMachine = runner.getVm();
-        mResultReceiver =
-                intent.getParcelableExtra(Intent.EXTRA_RESULT_RECEIVER, ResultReceiver.class);
-
-        runner.getExitStatus()
-                .thenAcceptAsync(
-                        success -> {
-                            if (mResultReceiver != null) {
-                                mResultReceiver.send(success ? RESULT_STOP : RESULT_ERROR, null);
-                            }
-                            if (!success) {
-                                stopSelf();
-                            }
-                        });
-        Path logPath = getFileStreamPath(mVirtualMachine.getName() + ".log").toPath();
-        Logger.setup(mVirtualMachine, logPath, mExecutorService);
-
-        startForeground();
-
-        mResultReceiver.send(RESULT_START, null);
-        if (config.getCustomImageConfig().useNetwork()) {
-            Handler handler = new Handler(Looper.getMainLooper());
-            gatherIpAddrFromVm(handler);
-        }
-        return START_NOT_STICKY;
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        if (isVmRunning()) {
-            try {
-                mVirtualMachine.stop();
-                stopForeground(STOP_FOREGROUND_REMOVE);
-            } catch (VirtualMachineException e) {
-                Log.e(TAG, "failed to stop a VM instance", e);
-            }
-            mExecutorService.shutdownNow();
-            mExecutorService = null;
-            mVirtualMachine = null;
-        }
-    }
-
-    private boolean isVmRunning() {
-        return mVirtualMachine != null
-                && mVirtualMachine.getStatus() == VirtualMachine.STATUS_RUNNING;
-    }
-
-    // TODO(b/359523803): Use AVF API to get ip addr when it exists
-    private void gatherIpAddrFromVm(Handler handler) {
-        handler.postDelayed(
-                () -> {
-                    if (!isVmRunning()) {
-                        Log.d(TAG, "A virtual machine instance isn't running");
-                        return;
-                    }
-                    int INTERNAL_VSOCK_SERVER_PORT = 1024;
-                    try (ParcelFileDescriptor pfd =
-                            mVirtualMachine.connectVsock(INTERNAL_VSOCK_SERVER_PORT)) {
-                        try (BufferedReader input =
-                                new BufferedReader(
-                                        new InputStreamReader(
-                                                new FileInputStream(pfd.getFileDescriptor())))) {
-                            String vmIpAddr = input.readLine().strip();
-                            Bundle b = new Bundle();
-                            b.putString(KEY_VM_IP_ADDR, vmIpAddr);
-                            mResultReceiver.send(RESULT_IPADDR, b);
-                            return;
-                        } catch (IOException e) {
-                            Log.e(TAG, e.toString());
-                        }
-                    } catch (Exception e) {
-                        Log.e(TAG, e.toString());
-                    }
-                    gatherIpAddrFromVm(handler);
-                },
-                1000);
-    }
-}