Add API for configuring a VM to run in debug mode

Bug: 185211964
Test: run the demo app
Change-Id: I960839037b2f23dbce1552199d9c9e59c36053e2
diff --git a/demo/java/com/android/microdroid/demo/MainActivity.java b/demo/java/com/android/microdroid/demo/MainActivity.java
index 976e37e..6373b55 100644
--- a/demo/java/com/android/microdroid/demo/MainActivity.java
+++ b/demo/java/com/android/microdroid/demo/MainActivity.java
@@ -22,6 +22,10 @@
 import android.system.virtualmachine.VirtualMachineConfig;
 import android.system.virtualmachine.VirtualMachineException;
 import android.system.virtualmachine.VirtualMachineManager;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.ScrollView;
 import android.widget.TextView;
 
 import androidx.appcompat.app.AppCompatActivity;
@@ -47,38 +51,77 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
+        TextView consoleView = (TextView) findViewById(R.id.consoleOutput);
+        Button runStopButton = (Button) findViewById(R.id.runStopButton);
+        ScrollView scrollView = (ScrollView) findViewById(R.id.scrollview);
 
-        // Whenthe console model is updated, append the new line to the text view.
-        TextView view = (TextView) findViewById(R.id.textview);
+        // When the console model is updated, append the new line to the text view.
         VirtualMachineModel model = new ViewModelProvider(this).get(VirtualMachineModel.class);
         model.getConsoleOutput()
                 .observeForever(
                         new Observer<String>() {
                             @Override
                             public void onChanged(String line) {
-                                view.append(line + "\n");
+                                consoleView.append(line + "\n");
+                                scrollView.fullScroll(View.FOCUS_DOWN);
                             }
                         });
+
+        // When the VM status is updated, change the label of the button
+        model.getStatus()
+                .observeForever(
+                        new Observer<VirtualMachine.Status>() {
+                            @Override
+                            public void onChanged(VirtualMachine.Status status) {
+                                if (status == VirtualMachine.Status.RUNNING) {
+                                    runStopButton.setText("Stop");
+                                } else {
+                                    runStopButton.setText("Run");
+                                    consoleView.setText("");
+                                }
+                            }
+                        });
+
+        // When the button is clicked, run or stop the VM
+        runStopButton.setOnClickListener(
+                new View.OnClickListener() {
+                    public void onClick(View v) {
+                        if (model.getStatus().getValue() == VirtualMachine.Status.RUNNING) {
+                            model.stop();
+                        } else {
+                            CheckBox debugModeCheckBox = (CheckBox) findViewById(R.id.debugMode);
+                            final boolean debug = debugModeCheckBox.isChecked();
+                            model.run(debug);
+                        }
+                    }
+                });
     }
 
     /** Models a virtual machine and console output from it. */
     public static class VirtualMachineModel extends AndroidViewModel {
-        private final VirtualMachine mVirtualMachine;
+        private VirtualMachine mVirtualMachine;
         private final MutableLiveData<String> mConsoleOutput = new MutableLiveData<>();
+        private final MutableLiveData<VirtualMachine.Status> mStatus = new MutableLiveData<>();
 
         public VirtualMachineModel(Application app) {
             super(app);
+            mStatus.setValue(VirtualMachine.Status.DELETED);
+        }
 
+        /** Runs a VM */
+        public void run(boolean debug) {
             // Create a VM and run it.
             // TODO(jiyong): remove the call to idsigPath
             try {
-                VirtualMachineConfig config =
+                VirtualMachineConfig.Builder builder =
                         new VirtualMachineConfig.Builder(getApplication(), "assets/vm_config.json")
                                 .idsigPath("/data/local/tmp/virt/MicrodroidDemoApp.apk.idsig")
-                                .build();
+                                .debugMode(debug);
+                VirtualMachineConfig config = builder.build();
                 VirtualMachineManager vmm = VirtualMachineManager.getInstance(getApplication());
                 mVirtualMachine = vmm.create("demo_vm", config);
                 mVirtualMachine.run();
+                mStatus.postValue(mVirtualMachine.getStatus());
             } catch (VirtualMachineException e) {
                 throw new RuntimeException(e);
             }
@@ -105,8 +148,25 @@
                     });
         }
 
+        /** Stops the running VM */
+        public void stop() {
+            try {
+                mVirtualMachine.stop();
+            } catch (VirtualMachineException e) {
+                // Consume
+            }
+            mVirtualMachine = null;
+            mStatus.postValue(VirtualMachine.Status.STOPPED);
+        }
+
+        /** Returns the console output from the VM */
         public LiveData<String> getConsoleOutput() {
             return mConsoleOutput;
         }
+
+        /** Returns the status of the VM */
+        public LiveData<VirtualMachine.Status> getStatus() {
+            return mStatus;
+        }
     }
 }
diff --git a/demo/res/layout/activity_main.xml b/demo/res/layout/activity_main.xml
index 026382f..cd30f35 100644
--- a/demo/res/layout/activity_main.xml
+++ b/demo/res/layout/activity_main.xml
@@ -9,18 +9,43 @@
     android:textAlignment="textStart"
     tools:context=".MainActivity">
 
-    <ScrollView
-        android:id="@+id/scrollview"
+    <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="match_parent">
+        android:layout_height="match_parent"
+        android:orientation="vertical">
 
-        <TextView
-            android:id="@+id/textview"
+        <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:background="#FFEB3B"
-            android:fontFamily="monospace"
-            android:textColor="#000000" />
-    </ScrollView>
+            android:orientation="horizontal">
+
+            <Button
+                android:id="@+id/runStopButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Run" />
+
+            <CheckBox
+                android:id="@+id/debugMode"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="Debug mode" />
+        </LinearLayout>
+
+        <ScrollView
+            android:id="@+id/scrollview"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+            <TextView
+                android:id="@+id/consoleOutput"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="#FFEB3B"
+                android:fontFamily="monospace"
+                android:textColor="#000000" />
+        </ScrollView>
+    </LinearLayout>
 
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index 062cd04..b5f04a2 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -43,10 +43,12 @@
     private static final String KEY_APKPATH = "apkPath";
     private static final String KEY_IDSIGPATH = "idsigPath";
     private static final String KEY_PAYLOADCONFIGPATH = "payloadConfigPath";
+    private static final String KEY_DEBUGMODE = "debugMode";
 
     // Paths to the APK and its idsig file of this application.
     private final String mApkPath;
     private final String mIdsigPath;
+    private final boolean mDebugMode;
 
     /**
      * Path within the APK to the payload config file that defines software aspects of this config.
@@ -55,10 +57,12 @@
 
     // TODO(jiyong): add more items like # of cpu, size of ram, debuggability, etc.
 
-    private VirtualMachineConfig(String apkPath, String idsigPath, String payloadConfigPath) {
+    private VirtualMachineConfig(
+            String apkPath, String idsigPath, String payloadConfigPath, boolean debugMode) {
         mApkPath = apkPath;
         mIdsigPath = idsigPath;
         mPayloadConfigPath = payloadConfigPath;
+        mDebugMode = debugMode;
     }
 
     /** Loads a config from a stream, for example a file. */
@@ -81,7 +85,8 @@
         if (payloadConfigPath == null) {
             throw new VirtualMachineException("No payloadConfigPath");
         }
-        return new VirtualMachineConfig(apkPath, idsigPath, payloadConfigPath);
+        final boolean debugMode = b.getBoolean(KEY_DEBUGMODE);
+        return new VirtualMachineConfig(apkPath, idsigPath, payloadConfigPath, debugMode);
     }
 
     /** Persists this config to a stream, for example a file. */
@@ -91,6 +96,7 @@
         b.putString(KEY_APKPATH, mApkPath);
         b.putString(KEY_IDSIGPATH, mIdsigPath);
         b.putString(KEY_PAYLOADCONFIGPATH, mPayloadConfigPath);
+        b.putBoolean(KEY_DEBUGMODE, mDebugMode);
         b.writeToStream(output);
     }
 
@@ -110,6 +116,7 @@
         parcel.apk = ParcelFileDescriptor.open(new File(mApkPath), MODE_READ_ONLY);
         parcel.idsig = ParcelFileDescriptor.open(new File(mIdsigPath), MODE_READ_ONLY);
         parcel.configPath = mPayloadConfigPath;
+        parcel.debug = mDebugMode;
         return parcel;
     }
 
@@ -117,6 +124,7 @@
     public static class Builder {
         private Context mContext;
         private String mPayloadConfigPath;
+        private boolean mDebugMode;
         private String mIdsigPath; // TODO(jiyong): remove this
         // TODO(jiyong): add more items like # of cpu, size of ram, debuggability, etc.
 
@@ -124,6 +132,13 @@
         public Builder(Context context, String payloadConfigPath) {
             mContext = context;
             mPayloadConfigPath = payloadConfigPath;
+            mDebugMode = false;
+        }
+
+        /** Enables or disables the debug mode */
+        public Builder debugMode(boolean enableOrDisable) {
+            mDebugMode = enableOrDisable;
+            return this;
         }
 
         // TODO(jiyong): remove this. Apps shouldn't need to set the path to the idsig file. It
@@ -137,7 +152,7 @@
         /** Builds an immutable {@link VirtualMachineConfig} */
         public VirtualMachineConfig build() {
             final String apkPath = mContext.getPackageCodePath();
-            return new VirtualMachineConfig(apkPath, mIdsigPath, mPayloadConfigPath);
+            return new VirtualMachineConfig(apkPath, mIdsigPath, mPayloadConfigPath, mDebugMode);
         }
     }
 }