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 com.android.microdroid.demo; 18 19 import android.app.Application; 20 import android.os.Bundle; 21 import android.os.IBinder; 22 import android.os.RemoteException; 23 import android.system.virtualmachine.VirtualMachine; 24 import android.system.virtualmachine.VirtualMachineCallback; 25 import android.system.virtualmachine.VirtualMachineConfig; 26 import android.system.virtualmachine.VirtualMachineException; 27 import android.system.virtualmachine.VirtualMachineManager; 28 import android.util.Log; 29 import android.view.View; 30 import android.widget.Button; 31 import android.widget.CheckBox; 32 import android.widget.ScrollView; 33 import android.widget.TextView; 34 35 import androidx.appcompat.app.AppCompatActivity; 36 import androidx.lifecycle.AndroidViewModel; 37 import androidx.lifecycle.LiveData; 38 import androidx.lifecycle.MutableLiveData; 39 import androidx.lifecycle.ViewModelProvider; 40 41 import com.android.microdroid.testservice.ITestService; 42 43 import java.io.BufferedReader; 44 import java.io.IOException; 45 import java.io.InputStream; 46 import java.io.InputStreamReader; 47 import java.util.concurrent.ExecutorService; 48 import java.util.concurrent.Executors; 49 50 /** 51 * This app is to demonstrate the use of APIs in the android.system.virtualmachine library. 52 * Currently, this app starts a virtual machine running Microdroid and shows the console output from 53 * the virtual machine to the UI. 54 */ 55 public class MainActivity extends AppCompatActivity { 56 private static final String TAG = "MicrodroidDemo"; 57 58 @Override onCreate(Bundle savedInstanceState)59 protected void onCreate(Bundle savedInstanceState) { 60 super.onCreate(savedInstanceState); 61 setContentView(R.layout.activity_main); 62 Button runStopButton = findViewById(R.id.runStopButton); 63 TextView consoleView = findViewById(R.id.consoleOutput); 64 TextView logView = findViewById(R.id.logOutput); 65 TextView payloadView = findViewById(R.id.payloadOutput); 66 ScrollView scrollConsoleView = findViewById(R.id.scrollConsoleOutput); 67 ScrollView scrollLogView = findViewById(R.id.scrollLogOutput); 68 69 VirtualMachineModel model = new ViewModelProvider(this).get(VirtualMachineModel.class); 70 71 // When the button is clicked, run or stop the VM 72 runStopButton.setOnClickListener( 73 v -> { 74 Integer status = model.getStatus().getValue(); 75 if (status != null && status == VirtualMachine.STATUS_RUNNING) { 76 model.stop(); 77 } else { 78 CheckBox debugModeCheckBox = findViewById(R.id.debugMode); 79 CheckBox protectedModeCheckBox = findViewById(R.id.protectedMode); 80 final boolean debug = debugModeCheckBox.isChecked(); 81 final boolean protectedVm = protectedModeCheckBox.isChecked(); 82 model.run(debug, protectedVm); 83 } 84 }); 85 86 // When the VM status is updated, change the label of the button 87 model.getStatus() 88 .observeForever( 89 status -> { 90 if (status == VirtualMachine.STATUS_RUNNING) { 91 runStopButton.setText("Stop"); 92 // Clear the outputs from the previous run 93 consoleView.setText(""); 94 logView.setText(""); 95 payloadView.setText(""); 96 } else { 97 runStopButton.setText("Run"); 98 } 99 }); 100 101 // When the console, log, or payload output is updated, append the new line to the 102 // corresponding text view. 103 model.getConsoleOutput() 104 .observeForever( 105 line -> { 106 consoleView.append(line + "\n"); 107 scrollConsoleView.fullScroll(View.FOCUS_DOWN); 108 }); 109 model.getLogOutput() 110 .observeForever( 111 line -> { 112 logView.append(line + "\n"); 113 scrollLogView.fullScroll(View.FOCUS_DOWN); 114 }); 115 model.getPayloadOutput().observeForever(line -> payloadView.append(line + "\n")); 116 } 117 118 /** Reads data from an input stream and posts it to the output data */ 119 static class Reader implements Runnable { 120 private final String mName; 121 private final MutableLiveData<String> mOutput; 122 private final InputStream mStream; 123 Reader(String name, MutableLiveData<String> output, InputStream stream)124 Reader(String name, MutableLiveData<String> output, InputStream stream) { 125 mName = name; 126 mOutput = output; 127 mStream = stream; 128 } 129 130 @Override run()131 public void run() { 132 try { 133 BufferedReader reader = new BufferedReader(new InputStreamReader(mStream)); 134 String line; 135 while ((line = reader.readLine()) != null && !Thread.interrupted()) { 136 mOutput.postValue(line); 137 } 138 } catch (IOException e) { 139 Log.e(TAG, "Exception while posting " + mName + " output: " + e.getMessage()); 140 } 141 } 142 } 143 144 /** Models a virtual machine and outputs from it. */ 145 public static class VirtualMachineModel extends AndroidViewModel { 146 private static final String VM_NAME = "demo_vm"; 147 private VirtualMachine mVirtualMachine; 148 private final MutableLiveData<String> mConsoleOutput = new MutableLiveData<>(); 149 private final MutableLiveData<String> mLogOutput = new MutableLiveData<>(); 150 private final MutableLiveData<String> mPayloadOutput = new MutableLiveData<>(); 151 private final MutableLiveData<Integer> mStatus = new MutableLiveData<>(); 152 private ExecutorService mExecutorService; 153 VirtualMachineModel(Application app)154 public VirtualMachineModel(Application app) { 155 super(app); 156 mStatus.setValue(VirtualMachine.STATUS_DELETED); 157 } 158 159 /** Runs a VM */ run(boolean debug, boolean protectedVm)160 public void run(boolean debug, boolean protectedVm) { 161 // Create a VM and run it. 162 mExecutorService = Executors.newFixedThreadPool(4); 163 164 VirtualMachineCallback callback = 165 new VirtualMachineCallback() { 166 // store reference to ExecutorService to avoid race condition 167 private final ExecutorService mService = mExecutorService; 168 169 @Override 170 public void onPayloadStarted(VirtualMachine vm) {} 171 172 @Override 173 public void onPayloadReady(VirtualMachine vm) { 174 // This check doesn't 100% prevent race condition or UI hang. 175 // However, it's fine for demo. 176 if (mService.isShutdown()) { 177 return; 178 } 179 mPayloadOutput.postValue("(Payload is ready. Testing VM service...)"); 180 181 mService.execute(() -> testVmService(vm)); 182 } 183 184 private void testVmService(VirtualMachine vm) { 185 IBinder binder; 186 try { 187 binder = vm.connectToVsockServer(ITestService.PORT); 188 } catch (Exception e) { 189 if (!Thread.interrupted()) { 190 mPayloadOutput.postValue( 191 String.format( 192 "(VM service connection failed: %s)", 193 e.getMessage())); 194 } 195 return; 196 } 197 198 try { 199 ITestService testService = ITestService.Stub.asInterface(binder); 200 int ret = testService.addInteger(123, 456); 201 mPayloadOutput.postValue( 202 String.format( 203 "(VM payload service: %d + %d = %d)", 204 123, 456, ret)); 205 } catch (RemoteException e) { 206 mPayloadOutput.postValue( 207 String.format( 208 "(Exception while testing VM's binder service:" 209 + " %s)", 210 e.getMessage())); 211 } 212 } 213 214 @Override 215 public void onPayloadFinished(VirtualMachine vm, int exitCode) { 216 // This check doesn't 100% prevent race condition, but is fine for demo. 217 if (!mService.isShutdown()) { 218 mPayloadOutput.postValue( 219 String.format( 220 "(Payload finished. exit code: %d)", exitCode)); 221 } 222 } 223 224 @Override 225 public void onError(VirtualMachine vm, int errorCode, String message) { 226 // This check doesn't 100% prevent race condition, but is fine for demo. 227 if (!mService.isShutdown()) { 228 mPayloadOutput.postValue( 229 String.format( 230 "(Error occurred. code: %d, message: %s)", 231 errorCode, message)); 232 } 233 } 234 235 @Override 236 public void onStopped(VirtualMachine vm, int reason) { 237 mService.shutdownNow(); 238 mStatus.postValue(VirtualMachine.STATUS_STOPPED); 239 } 240 }; 241 242 try { 243 VirtualMachineConfig.Builder builder = 244 new VirtualMachineConfig.Builder(getApplication()); 245 builder.setPayloadBinaryName("MicrodroidTestNativeLib.so"); 246 builder.setProtectedVm(protectedVm); 247 248 if (debug) { 249 builder.setDebugLevel(VirtualMachineConfig.DEBUG_LEVEL_FULL); 250 builder.setVmOutputCaptured(true); 251 } 252 VirtualMachineConfig config = builder.build(); 253 VirtualMachineManager vmm = 254 getApplication().getSystemService(VirtualMachineManager.class); 255 mVirtualMachine = vmm.getOrCreate(VM_NAME, config); 256 try { 257 mVirtualMachine.setConfig(config); 258 } catch (VirtualMachineException e) { 259 vmm.delete(VM_NAME); 260 mVirtualMachine = vmm.create(VM_NAME, config); 261 } 262 mVirtualMachine.run(); 263 mVirtualMachine.setCallback(Executors.newSingleThreadExecutor(), callback); 264 mStatus.postValue(mVirtualMachine.getStatus()); 265 266 if (debug) { 267 InputStream console = mVirtualMachine.getConsoleOutput(); 268 InputStream log = mVirtualMachine.getLogOutput(); 269 mExecutorService.execute(new Reader("console", mConsoleOutput, console)); 270 mExecutorService.execute(new Reader("log", mLogOutput, log)); 271 } 272 } catch (VirtualMachineException e) { 273 throw new RuntimeException(e); 274 } 275 } 276 277 /** Stops the running VM */ stop()278 public void stop() { 279 try { 280 mVirtualMachine.stop(); 281 } catch (VirtualMachineException e) { 282 // Consume 283 } 284 mVirtualMachine = null; 285 mExecutorService.shutdownNow(); 286 mStatus.postValue(VirtualMachine.STATUS_STOPPED); 287 } 288 289 /** Returns the console output from the VM */ getConsoleOutput()290 public LiveData<String> getConsoleOutput() { 291 return mConsoleOutput; 292 } 293 294 /** Returns the log output from the VM */ getLogOutput()295 public LiveData<String> getLogOutput() { 296 return mLogOutput; 297 } 298 299 /** Returns the payload output from the VM */ getPayloadOutput()300 public LiveData<String> getPayloadOutput() { 301 return mPayloadOutput; 302 } 303 304 /** Returns the status of the VM */ getStatus()305 public LiveData<Integer> getStatus() { 306 return mStatus; 307 } 308 } 309 } 310