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() 116 .observeForever( 117 line -> payloadView.append(line + "\n")); 118 } 119 120 /** Reads data from an input stream and posts it to the output data */ 121 static class Reader implements Runnable { 122 private final String mName; 123 private final MutableLiveData<String> mOutput; 124 private final InputStream mStream; 125 Reader(String name, MutableLiveData<String> output, InputStream stream)126 Reader(String name, MutableLiveData<String> output, InputStream stream) { 127 mName = name; 128 mOutput = output; 129 mStream = stream; 130 } 131 132 @Override run()133 public void run() { 134 try { 135 BufferedReader reader = new BufferedReader(new InputStreamReader(mStream)); 136 String line; 137 while ((line = reader.readLine()) != null && !Thread.interrupted()) { 138 mOutput.postValue(line); 139 } 140 } catch (IOException e) { 141 Log.e(TAG, "Exception while posting " + mName + " output: " + e.getMessage()); 142 } 143 } 144 } 145 146 /** Models a virtual machine and outputs from it. */ 147 public static class VirtualMachineModel extends AndroidViewModel { 148 private static final String VM_NAME = "demo_vm"; 149 private VirtualMachine mVirtualMachine; 150 private final MutableLiveData<String> mConsoleOutput = new MutableLiveData<>(); 151 private final MutableLiveData<String> mLogOutput = new MutableLiveData<>(); 152 private final MutableLiveData<String> mPayloadOutput = new MutableLiveData<>(); 153 private final MutableLiveData<Integer> mStatus = new MutableLiveData<>(); 154 private ExecutorService mExecutorService; 155 VirtualMachineModel(Application app)156 public VirtualMachineModel(Application app) { 157 super(app); 158 mStatus.setValue(VirtualMachine.STATUS_DELETED); 159 } 160 161 /** Runs a VM */ run(boolean debug, boolean protectedVm)162 public void run(boolean debug, boolean protectedVm) { 163 // Create a VM and run it. 164 mExecutorService = Executors.newFixedThreadPool(4); 165 166 VirtualMachineCallback callback = 167 new VirtualMachineCallback() { 168 // store reference to ExecutorService to avoid race condition 169 private final ExecutorService mService = mExecutorService; 170 171 @Override 172 public void onPayloadStarted(VirtualMachine vm) {} 173 174 @Override 175 public void onPayloadReady(VirtualMachine vm) { 176 // This check doesn't 100% prevent race condition or UI hang. 177 // However, it's fine for demo. 178 if (mService.isShutdown()) { 179 return; 180 } 181 mPayloadOutput.postValue("(Payload is ready. Testing VM service...)"); 182 183 mService.execute(() -> testVmService(vm)); 184 } 185 186 private void testVmService(VirtualMachine vm) { 187 IBinder binder; 188 try { 189 binder = vm.connectToVsockServer(ITestService.PORT); 190 } catch (Exception e) { 191 if (!Thread.interrupted()) { 192 mPayloadOutput.postValue( 193 String.format( 194 "(VM service connection failed: %s)", 195 e.getMessage())); 196 } 197 return; 198 } 199 200 try { 201 ITestService testService = ITestService.Stub.asInterface(binder); 202 int ret = testService.addInteger(123, 456); 203 mPayloadOutput.postValue( 204 String.format( 205 "(VM payload service: %d + %d = %d)", 206 123, 456, ret)); 207 } catch (RemoteException e) { 208 mPayloadOutput.postValue( 209 String.format( 210 "(Exception while testing VM's binder service:" 211 + " %s)", 212 e.getMessage())); 213 } 214 } 215 216 @Override 217 public void onPayloadFinished(VirtualMachine vm, int exitCode) { 218 // This check doesn't 100% prevent race condition, but is fine for demo. 219 if (!mService.isShutdown()) { 220 mPayloadOutput.postValue( 221 String.format( 222 "(Payload finished. exit code: %d)", exitCode)); 223 } 224 } 225 226 @Override 227 public void onError(VirtualMachine vm, int errorCode, String message) { 228 // This check doesn't 100% prevent race condition, but is fine for demo. 229 if (!mService.isShutdown()) { 230 mPayloadOutput.postValue( 231 String.format( 232 "(Error occurred. code: %d, message: %s)", 233 errorCode, message)); 234 } 235 } 236 237 @Override 238 public void onStopped(VirtualMachine vm, int reason) { 239 mService.shutdownNow(); 240 mStatus.postValue(VirtualMachine.STATUS_STOPPED); 241 } 242 }; 243 244 try { 245 VirtualMachineConfig.Builder builder = 246 new VirtualMachineConfig.Builder(getApplication()); 247 builder.setPayloadBinaryName("MicrodroidTestNativeLib.so"); 248 builder.setProtectedVm(protectedVm); 249 250 if (debug) { 251 builder.setDebugLevel(VirtualMachineConfig.DEBUG_LEVEL_FULL); 252 builder.setVmOutputCaptured(true); 253 } 254 VirtualMachineConfig config = builder.build(); 255 VirtualMachineManager vmm = 256 getApplication().getSystemService(VirtualMachineManager.class); 257 mVirtualMachine = vmm.getOrCreate(VM_NAME, config); 258 try { 259 mVirtualMachine.setConfig(config); 260 } catch (VirtualMachineException e) { 261 vmm.delete(VM_NAME); 262 mVirtualMachine = vmm.create(VM_NAME, config); 263 } 264 mVirtualMachine.run(); 265 mVirtualMachine.setCallback(Executors.newSingleThreadExecutor(), callback); 266 mStatus.postValue(mVirtualMachine.getStatus()); 267 268 if (debug) { 269 InputStream console = mVirtualMachine.getConsoleOutput(); 270 InputStream log = mVirtualMachine.getLogOutput(); 271 mExecutorService.execute(new Reader("console", mConsoleOutput, console)); 272 mExecutorService.execute(new Reader("log", mLogOutput, log)); 273 } 274 } catch (VirtualMachineException e) { 275 throw new RuntimeException(e); 276 } 277 } 278 279 /** Stops the running VM */ stop()280 public void stop() { 281 try { 282 mVirtualMachine.stop(); 283 } catch (VirtualMachineException e) { 284 // Consume 285 } 286 mVirtualMachine = null; 287 mExecutorService.shutdownNow(); 288 mStatus.postValue(VirtualMachine.STATUS_STOPPED); 289 } 290 291 /** Returns the console output from the VM */ getConsoleOutput()292 public LiveData<String> getConsoleOutput() { 293 return mConsoleOutput; 294 } 295 296 /** Returns the log output from the VM */ getLogOutput()297 public LiveData<String> getLogOutput() { 298 return mLogOutput; 299 } 300 301 /** Returns the payload output from the VM */ getPayloadOutput()302 public LiveData<String> getPayloadOutput() { 303 return mPayloadOutput; 304 } 305 306 /** Returns the status of the VM */ getStatus()307 public LiveData<Integer> getStatus() { 308 return mStatus; 309 } 310 } 311 } 312