Merge "Do not hard-code `image.raw` filename in build script." into main
diff --git a/OWNERS b/OWNERS
index 81217f3..af71ad6 100644
--- a/OWNERS
+++ b/OWNERS
@@ -31,6 +31,4 @@
# Ferrochrome
per-file android/TerminalApp/**=jiyong@google.com,jeongik@google.com
-per-file android/VmLauncherApp/**=jiyong@google.com,jeongik@google.com
-per-file libs/vm_launcher_lib/**=jiyong@google.com,jeongik@google.com
per-file build/debian/**=jiyong@google.com,jeongik@google.com
diff --git a/TEST_MAPPING b/TEST_MAPPING
index e197b25..8cb01d7 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -72,11 +72,6 @@
"name": "TerminalAppTests"
}
],
- "ferrochrome-postsubmit": [
- {
- "name": "ferrochrome-tests"
- }
- ],
"postsubmit": [
{
"name": "CtsMicrodroidDisabledTestCases"
diff --git a/android/TerminalApp/Android.bp b/android/TerminalApp/Android.bp
index 3b5f9b8..2711af0 100644
--- a/android/TerminalApp/Android.bp
+++ b/android/TerminalApp/Android.bp
@@ -11,15 +11,22 @@
resource_dirs: ["res"],
asset_dirs: ["assets"],
static_libs: [
- "VmTerminalApp.aidl-java",
- "vm_launcher_lib",
"androidx-constraintlayout_constraintlayout",
- "com.google.android.material_material",
"androidx.window_window",
+ "apache-commons-compress",
+ "com.google.android.material_material",
+ "debian-service-grpclib-lite",
+ "gson",
+ "VmTerminalApp.aidl-java",
],
jni_libs: [
"libforwarder_host_jni",
],
+ libs: [
+ "androidx.annotation_annotation",
+ "framework-virtualization.impl",
+ "framework-annotations-lib",
+ ],
use_embedded_native_libs: true,
platform_apis: true,
privileged: true,
diff --git a/android/TerminalApp/AndroidManifest.xml b/android/TerminalApp/AndroidManifest.xml
index 1af6c8a..dad07ee 100644
--- a/android/TerminalApp/AndroidManifest.xml
+++ b/android/TerminalApp/AndroidManifest.xml
@@ -46,14 +46,11 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
- <activity android:name=".SettingsActivity">
- </activity>
- <activity android:name=".SettingsDiskResizeActivity">
- </activity>
- <activity android:name=".SettingsPortForwardingActivity">
- </activity>
- <activity android:name=".SettingsRecoveryActivity">
- </activity>
+ <activity android:name=".SettingsActivity" />
+ <activity android:name=".SettingsDiskResizeActivity" />
+ <activity android:name=".SettingsPortForwardingActivity" />
+ <activity android:name=".SettingsRecoveryActivity" />
+ <activity android:name=".ErrorActivity" />
<property
android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
android:value="true" />
@@ -79,7 +76,7 @@
android:stopWithTask="true" />
<service
- android:name="com.android.virtualization.vmlauncher.VmLauncherService"
+ android:name=".VmLauncherService"
android:exported="false"
android:foregroundServiceType="specialUse"
android:stopWithTask="true" >
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/ConfigJson.java b/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java
similarity index 95%
rename from libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/ConfigJson.java
rename to android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java
index a259fe2..e1342e9 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/ConfigJson.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.virtualization.vmlauncher;
+package com.android.virtualization.terminal;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -94,21 +94,20 @@
: VirtualMachineConfig.DEBUG_LEVEL_NONE;
}
- /** Converts this parsed JSON into VirtualMachieConfig */
- VirtualMachineConfig toConfig(Context context) {
+ /** Converts this parsed JSON into VirtualMachieConfig Builder */
+ VirtualMachineConfig.Builder toConfigBuilder(Context context) {
return new VirtualMachineConfig.Builder(context)
.setProtectedVm(isProtected)
.setMemoryBytes((long) memory_mib * 1024 * 1024)
.setConsoleInputDevice(console_input_device)
.setCpuTopology(getCpuTopology())
- .setCustomImageConfig(toCustomImageConfig(context))
+ .setCustomImageConfig(toCustomImageConfigBuilder(context).build())
.setDebugLevel(getDebugLevel())
.setVmOutputCaptured(console_out)
- .setConnectVmConsole(connect_console)
- .build();
+ .setConnectVmConsole(connect_console);
}
- private VirtualMachineCustomImageConfig toCustomImageConfig(Context context) {
+ VirtualMachineCustomImageConfig.Builder toCustomImageConfigBuilder(Context context) {
VirtualMachineCustomImageConfig.Builder builder =
new VirtualMachineCustomImageConfig.Builder();
@@ -152,7 +151,7 @@
.filter(Objects::nonNull)
.forEach(builder::addSharedPath);
}
- return builder.build();
+ return builder;
}
private static class SharedPathJson {
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/DebianServiceImpl.java b/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.java
similarity index 91%
rename from libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/DebianServiceImpl.java
rename to android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.java
index 68ff2ec..0b65cf6 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/DebianServiceImpl.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.virtualization.vmlauncher;
+package com.android.virtualization.terminal;
import android.content.Context;
import android.content.SharedPreferences;
@@ -22,13 +22,13 @@
import androidx.annotation.Keep;
-import com.android.virtualization.vmlauncher.proto.DebianServiceGrpc;
-import com.android.virtualization.vmlauncher.proto.ForwardingRequestItem;
-import com.android.virtualization.vmlauncher.proto.IpAddr;
-import com.android.virtualization.vmlauncher.proto.QueueOpeningRequest;
-import com.android.virtualization.vmlauncher.proto.ReportVmActivePortsRequest;
-import com.android.virtualization.vmlauncher.proto.ReportVmActivePortsResponse;
-import com.android.virtualization.vmlauncher.proto.ReportVmIpAddrResponse;
+import com.android.virtualization.terminal.proto.DebianServiceGrpc;
+import com.android.virtualization.terminal.proto.ForwardingRequestItem;
+import com.android.virtualization.terminal.proto.IpAddr;
+import com.android.virtualization.terminal.proto.QueueOpeningRequest;
+import com.android.virtualization.terminal.proto.ReportVmActivePortsRequest;
+import com.android.virtualization.terminal.proto.ReportVmActivePortsResponse;
+import com.android.virtualization.terminal.proto.ReportVmIpAddrResponse;
import io.grpc.stub.StreamObserver;
@@ -49,7 +49,6 @@
private SharedPreferences.OnSharedPreferenceChangeListener mPortForwardingListener;
private final DebianServiceCallback mCallback;
-
static {
System.loadLibrary("forwarder_host_jni");
}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/ErrorActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/ErrorActivity.java
new file mode 100644
index 0000000..ee1f1ad
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/ErrorActivity.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 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.terminal;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class ErrorActivity extends BaseActivity {
+ public static final String EXTRA_CAUSE = "cause";
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_error);
+
+ View button = findViewById(R.id.recovery);
+ button.setOnClickListener((event) -> launchRecoveryActivity());
+ }
+
+ @Override
+ protected void onNewIntent(@NonNull Intent intent) {
+ super.onNewIntent(intent);
+ setIntent(intent);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ Intent intent = getIntent();
+ Exception e = intent.getParcelableExtra(EXTRA_CAUSE, Exception.class);
+ TextView cause = findViewById(R.id.cause);
+ if (e != null) {
+ cause.setText(getString(R.string.error_code, e.toString()));
+ } else {
+ cause.setText(null);
+ }
+ }
+
+ private void launchRecoveryActivity() {
+ Intent intent = new Intent(this, SettingsRecoveryActivity.class);
+ startActivity(intent);
+ }
+}
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/InstallUtils.java b/android/TerminalApp/java/com/android/virtualization/terminal/InstallUtils.java
similarity index 93%
rename from libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/InstallUtils.java
rename to android/TerminalApp/java/com/android/virtualization/terminal/InstallUtils.java
index d55d268..b17e636 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/InstallUtils.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstallUtils.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.virtualization.vmlauncher;
+package com.android.virtualization.terminal;
import android.content.Context;
import android.os.Environment;
@@ -43,6 +43,7 @@
private static final String VM_CONFIG_FILENAME = "vm_config.json";
private static final String COMPRESSED_PAYLOAD_FILENAME = "images.tar.gz";
private static final String ROOTFS_FILENAME = "root_part";
+ private static final String BACKUP_FILENAME = "root_part_backup";
private static final String INSTALLATION_COMPLETED_FILENAME = "completed";
private static final String PAYLOAD_DIR = "linux";
@@ -54,8 +55,11 @@
return Files.exists(getInstallationCompletedPath(context));
}
- public static void unInstall(Context context) throws IOException {
- Files.delete(getInstallationCompletedPath(context));
+ public static void backupRootFs(Context context) throws IOException {
+ Files.move(
+ getRootfsFile(context).toPath(),
+ getBackupFile(context).toPath(),
+ StandardCopyOption.REPLACE_EXISTING);
}
public static boolean createInstalledMarker(Context context) {
@@ -91,6 +95,10 @@
return new File(context.getFilesDir(), PAYLOAD_DIR);
}
+ public static File getBackupFile(Context context) {
+ return new File(context.getFilesDir(), BACKUP_FILENAME);
+ }
+
private static Path getInstallationCompletedPath(Context context) {
return getInternalStorageDir(context).toPath().resolve(INSTALLATION_COMPLETED_FILENAME);
}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
index 83c6b4c..a1d4cb6 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
@@ -35,7 +35,6 @@
import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.virtualization.vmlauncher.InstallUtils;
import com.google.android.material.progressindicator.LinearProgressIndicator;
import com.google.android.material.snackbar.Snackbar;
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.java b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.java
index f97f16f..f839c64 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.java
@@ -29,7 +29,6 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.GuardedBy;
-import com.android.virtualization.vmlauncher.InstallUtils;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/Logger.java b/android/TerminalApp/java/com/android/virtualization/terminal/Logger.java
similarity index 97%
rename from libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/Logger.java
rename to android/TerminalApp/java/com/android/virtualization/terminal/Logger.java
index e1cb285..2c0149e 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/Logger.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/Logger.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.virtualization.vmlauncher;
+package com.android.virtualization.terminal;
import android.system.virtualmachine.VirtualMachine;
import android.system.virtualmachine.VirtualMachineConfig;
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index 2f0d301..eb0e7e2 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -28,6 +28,7 @@
import android.graphics.fonts.FontStyle;
import android.net.Uri;
import android.net.http.SslError;
+import android.os.Build;
import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.Environment;
@@ -35,6 +36,7 @@
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
+import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -46,16 +48,12 @@
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
-import android.widget.Toast;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.virtualization.vmlauncher.InstallUtils;
-import com.android.virtualization.vmlauncher.VmLauncherService;
-import com.android.virtualization.vmlauncher.VmLauncherServices;
import com.google.android.material.appbar.MaterialToolbar;
@@ -142,6 +140,17 @@
}
}
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (Build.isDebuggable() && event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) {
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ launchErrorActivity(new Exception("Debug: KeyEvent.KEYCODE_UNKNOWN"));
+ }
+ return true;
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
private void requestStoragePermissions(
Context context, ActivityResultLauncher<Intent> activityResultLauncher) {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
@@ -369,16 +378,13 @@
@Override
public void onVmStop() {
- Toast.makeText(this, R.string.vm_stop_message, Toast.LENGTH_SHORT).show();
Log.i(TAG, "onVmStop()");
- finish();
}
@Override
public void onVmError() {
- Toast.makeText(this, R.string.vm_error_message, Toast.LENGTH_SHORT).show();
Log.i(TAG, "onVmError()");
- finish();
+ launchErrorActivity(new Exception("onVmError"));
}
@Override
@@ -425,6 +431,13 @@
}
}
+ private void launchErrorActivity(Exception e) {
+ Intent intent = new Intent(this, ErrorActivity.class);
+ intent.putExtra(ErrorActivity.EXTRA_CAUSE, e);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+ this.startActivity(intent);
+ }
+
private boolean installIfNecessary() {
// If payload from external storage exists(only for debuggable build) or there is no
// installed image, launch installer activity.
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/Runner.java b/android/TerminalApp/java/com/android/virtualization/terminal/Runner.java
similarity index 98%
rename from libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/Runner.java
rename to android/TerminalApp/java/com/android/virtualization/terminal/Runner.java
index 9b97fee..a2247b1 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/Runner.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/Runner.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.virtualization.vmlauncher;
+package com.android.virtualization.terminal;
import android.content.Context;
import android.system.virtualmachine.VirtualMachine;
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt
index 7ccce9c..817808f 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsDiskResizeActivity.kt
@@ -27,7 +27,6 @@
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
-import com.android.virtualization.vmlauncher.InstallUtils
import com.google.android.material.button.MaterialButton
import com.google.android.material.slider.Slider
import java.util.regex.Pattern
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt
index 95bcbbc..ef76e03 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt
@@ -18,12 +18,15 @@
import android.content.Intent
import android.os.Bundle
import android.util.Log
+import android.view.View
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
-import com.android.virtualization.vmlauncher.InstallUtils
import com.google.android.material.card.MaterialCardView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.snackbar.Snackbar
import java.io.IOException
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -34,30 +37,90 @@
super.onCreate(savedInstanceState)
setContentView(R.layout.settings_recovery)
val resetCard = findViewById<MaterialCardView>(R.id.settings_recovery_reset_card)
- val dialog = MaterialAlertDialogBuilder(this)
- .setTitle(R.string.settings_recovery_reset_dialog_title)
- .setMessage(R.string.settings_recovery_reset_dialog_message)
- .setPositiveButton(R.string.settings_recovery_reset_dialog_confirm) { _, _ ->
- // This coroutine will be killed when the activity is killed. The behavior is both acceptable
- // either removing is done or not
- lifecycleScope.launch(Dispatchers.IO) {
- try {
- InstallUtils.unInstall(this@SettingsRecoveryActivity)
- // Restart terminal
- val intent =
- baseContext.packageManager.getLaunchIntentForPackage(baseContext.packageName)
- intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
- finish()
- startActivity(intent)
- } catch (e: IOException) {
- Log.e(TAG, "VM image reset failed.")
+ resetCard.setOnClickListener {
+ var backupRootfs = false
+ val dialog = MaterialAlertDialogBuilder(this)
+ .setTitle(R.string.settings_recovery_reset_dialog_title)
+ .setMultiChoiceItems(arrayOf(getString(R.string.settings_recovery_reset_dialog_backup_option)), booleanArrayOf(backupRootfs)) {_, _, checked ->
+ backupRootfs = checked
+ }
+ .setPositiveButton(R.string.settings_recovery_reset_dialog_confirm) { _, _ ->
+ // This coroutine will be killed when the activity is killed. The behavior is both acceptable
+ // either removing is done or not
+ runInBackgroundAndRestartApp {
+ uninstall(backupRootfs)
}
}
- }
- .setNegativeButton(R.string.settings_recovery_reset_dialog_cancel) { dialog, _ -> dialog.dismiss() }
- .create()
- resetCard.setOnClickListener {
+ .setNegativeButton(R.string.settings_recovery_reset_dialog_cancel) { dialog, _ -> dialog.dismiss() }
+ .create()
dialog.show()
}
+ val resetBackupCard = findViewById<View>(R.id.settings_recovery_reset_backup_card)
+ resetBackupCard.isVisible = InstallUtils.getBackupFile(this).exists()
+
+ resetBackupCard.setOnClickListener {
+ val dialog = MaterialAlertDialogBuilder(this)
+ .setTitle(R.string.settings_recovery_remove_backup_title)
+ .setMessage(R.string.settings_recovery_remove_backup_sub_title)
+ .setPositiveButton(R.string.settings_recovery_reset_dialog_confirm) { _, _ ->
+ runInBackgroundAndRestartApp {
+ removeBackup()
+ }
+ }
+ .setNegativeButton(R.string.settings_recovery_reset_dialog_cancel) { dialog, _ -> dialog.dismiss() }
+ .create()
+ dialog.show()
+ }
+ }
+
+ private fun removeBackup(): Unit {
+ if (!InstallUtils.getBackupFile(this@SettingsRecoveryActivity).delete()) {
+ Snackbar.make(
+ findViewById(android.R.id.content),
+ R.string.settings_recovery_error_during_removing_backup,
+ Snackbar.LENGTH_SHORT
+ ).show();
+ Log.e(TAG, "cannot remove backup")
+ }
+ }
+
+ private fun uninstall(backupRootfs: Boolean): Unit {
+ var backupDone = false
+ try {
+ if (backupRootfs) {
+ InstallUtils.backupRootFs(this@SettingsRecoveryActivity)
+ backupDone = true
+ }
+ InstallUtils.deleteInstallation(this@SettingsRecoveryActivity)
+ } catch (e: IOException) {
+ val errorMsgId = if (backupRootfs && !backupDone) R.string.settings_recovery_error_due_to_backup
+ else R.string.settings_recovery_error;
+ Snackbar.make(
+ findViewById(android.R.id.content),
+ errorMsgId,
+ Snackbar.LENGTH_SHORT
+ ).show();
+ Log.e(TAG, "cannot recovery ", e)
+ }
+ }
+
+ private fun runInBackgroundAndRestartApp(backgroundWork: suspend CoroutineScope.() -> Unit): Unit {
+ findViewById<View>(R.id.setting_recovery_card_container).visibility = View.INVISIBLE
+ findViewById<View>(R.id.recovery_boot_progress).visibility = View.VISIBLE
+ lifecycleScope.launch(Dispatchers.IO) {
+ backgroundWork()
+ }.invokeOnCompletion {
+ runOnUiThread {
+ findViewById<View>(R.id.setting_recovery_card_container).visibility =
+ View.VISIBLE
+ findViewById<View>(R.id.recovery_boot_progress).visibility = View.INVISIBLE
+ // Restart terminal
+ val intent =
+ baseContext.packageManager.getLaunchIntentForPackage(baseContext.packageName)
+ intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ finish()
+ startActivity(intent)
+ }
+ }
}
}
\ No newline at end of file
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
similarity index 90%
rename from libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java
rename to android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
index 846fd26..25afcb7 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.virtualization.vmlauncher;
+package com.android.virtualization.terminal;
import android.app.Notification;
import android.app.Service;
@@ -24,6 +24,8 @@
import android.os.ResultReceiver;
import android.system.virtualmachine.VirtualMachine;
import android.system.virtualmachine.VirtualMachineConfig;
+import android.system.virtualmachine.VirtualMachineCustomImageConfig;
+import android.system.virtualmachine.VirtualMachineCustomImageConfig.Disk;
import android.system.virtualmachine.VirtualMachineException;
import android.util.Log;
@@ -85,7 +87,15 @@
mExecutorService = Executors.newCachedThreadPool();
ConfigJson json = ConfigJson.from(InstallUtils.getVmConfigPath(this));
- VirtualMachineConfig config = json.toConfig(this);
+ VirtualMachineConfig.Builder configBuilder = json.toConfigBuilder(this);
+ VirtualMachineCustomImageConfig.Builder customImageConfigBuilder =
+ json.toCustomImageConfigBuilder(this);
+ File backupFile = InstallUtils.getBackupFile(this);
+ if (backupFile.exists()) {
+ customImageConfigBuilder.addDisk(Disk.RWDisk(backupFile.getPath()));
+ configBuilder.setCustomImageConfig(customImageConfigBuilder.build());
+ }
+ VirtualMachineConfig config = configBuilder.build();
Runner runner;
try {
@@ -113,8 +123,8 @@
Path logPath = getFileStreamPath(mVirtualMachine.getName() + ".log").toPath();
Logger.setup(mVirtualMachine, logPath, mExecutorService);
- Notification notification = intent.getParcelableExtra(EXTRA_NOTIFICATION,
- Notification.class);
+ Notification notification =
+ intent.getParcelableExtra(EXTRA_NOTIFICATION, Notification.class);
startForeground(notification);
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherServices.java b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherServices.java
similarity index 95%
rename from libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherServices.java
rename to android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherServices.java
index 6eca2b3..d6c6786 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherServices.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherServices.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.virtualization.vmlauncher;
+package com.android.virtualization.terminal;
import android.app.Notification;
import android.content.Context;
@@ -67,8 +67,8 @@
context.stopService(i);
}
- public static void startVmLauncherService(Context context, VmLauncherServiceCallback callback,
- Notification notification) {
+ public static void startVmLauncherService(
+ Context context, VmLauncherServiceCallback callback, Notification notification) {
Intent i = buildVmLauncherServiceIntent(context);
if (i == null) {
return;
diff --git a/android/TerminalApp/proguard.flags b/android/TerminalApp/proguard.flags
index 8433e82..88b8a9c 100644
--- a/android/TerminalApp/proguard.flags
+++ b/android/TerminalApp/proguard.flags
@@ -11,8 +11,8 @@
#-keep class com.google.gson.stream.** { *; }
# Application classes that will be serialized/deserialized over Gson
--keep class com.android.virtualization.vmlauncher.ConfigJson { <fields>; }
--keep class com.android.virtualization.vmlauncher.ConfigJson$* { <fields>; }
+-keep class com.android.virtualization.terminal.ConfigJson { <fields>; }
+-keep class com.android.virtualization.terminal.ConfigJson$* { <fields>; }
# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
diff --git a/android/TerminalApp/res/layout/activity_error.xml b/android/TerminalApp/res/layout/activity_error.xml
new file mode 100644
index 0000000..1b5026e
--- /dev/null
+++ b/android/TerminalApp/res/layout/activity_error.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 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.
+ -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
+ tools:context=".ErrorActivity">
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/error_title"
+ android:layout_marginVertical="24dp"
+ android:layout_marginHorizontal="24dp"
+ android:layout_alignParentTop="true"
+ android:hyphenationFrequency="normal"
+ android:textSize="48sp" />
+
+ <TextView
+ android:id="@+id/desc"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/error_desc"
+ android:lineSpacingExtra="5sp"
+ android:layout_marginTop="20dp"
+ android:layout_marginHorizontal="48dp"
+ android:layout_below="@id/title"
+ android:textSize="20sp" />
+
+ <TextView
+ android:id="@+id/cause"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:lineSpacingExtra="5sp"
+ android:layout_marginTop="24dp"
+ android:layout_marginHorizontal="60dp"
+ android:layout_below="@id/desc"
+ android:textSize="14sp" />
+
+ <Button
+ android:id="@+id/recovery"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentEnd="true"
+ android:layout_marginBottom="32dp"
+ android:layout_marginHorizontal="40dp"
+ android:backgroundTint="?attr/colorPrimaryDark"
+ android:text="@string/settings_recovery_title" />
+
+</RelativeLayout>
diff --git a/android/TerminalApp/res/layout/settings_disk_resize.xml b/android/TerminalApp/res/layout/settings_disk_resize.xml
index d80f4f9..21ff070 100644
--- a/android/TerminalApp/res/layout/settings_disk_resize.xml
+++ b/android/TerminalApp/res/layout/settings_disk_resize.xml
@@ -29,6 +29,7 @@
android:layout_width="wrap_content"
android:text="@string/settings_disk_resize_title"
android:textSize="48sp"
+ android:hyphenationFrequency="normal"
android:layout_marginBottom="24dp"/>
<androidx.constraintlayout.widget.ConstraintLayout
@@ -38,16 +39,21 @@
<TextView
android:id="@+id/settings_disk_resize_resize_gb_assigned"
android:layout_height="wrap_content"
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:textSize="14sp"
+ android:singleLine="false"
+ app:layout_constraintWidth_percent="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@+id/settings_disk_resize_disk_size_slider"/>
<TextView
android:id="@+id/settings_disk_resize_resize_gb_max"
android:layout_height="wrap_content"
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:textSize="14sp"
+ android:singleLine="false"
+ android:gravity="end"
+ app:layout_constraintWidth_percent="0.5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@+id/settings_disk_resize_disk_size_slider"/>
diff --git a/android/TerminalApp/res/layout/settings_list_item.xml b/android/TerminalApp/res/layout/settings_list_item.xml
index b48c5d3..645efbb 100644
--- a/android/TerminalApp/res/layout/settings_list_item.xml
+++ b/android/TerminalApp/res/layout/settings_list_item.xml
@@ -28,7 +28,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
- android:layout_height="88dp"
+ android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="16dp">
diff --git a/android/TerminalApp/res/layout/settings_port_forwarding.xml b/android/TerminalApp/res/layout/settings_port_forwarding.xml
index 199b8cb..98ba02c 100644
--- a/android/TerminalApp/res/layout/settings_port_forwarding.xml
+++ b/android/TerminalApp/res/layout/settings_port_forwarding.xml
@@ -28,6 +28,7 @@
android:layout_width="wrap_content"
android:text="@string/settings_port_forwarding_title"
android:textSize="48sp"
+ android:hyphenationFrequency="normal"
android:layout_marginBottom="24dp"/>
<androidx.recyclerview.widget.RecyclerView
@@ -35,4 +36,4 @@
android:layout_marginHorizontal="16dp"
android:layout_width="match_parent"
android:layout_height="match_parent" />
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/android/TerminalApp/res/layout/settings_recovery.xml b/android/TerminalApp/res/layout/settings_recovery.xml
index 4cce61d..c72447f 100644
--- a/android/TerminalApp/res/layout/settings_recovery.xml
+++ b/android/TerminalApp/res/layout/settings_recovery.xml
@@ -16,7 +16,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_marginEnd="24dp"
@@ -28,48 +28,101 @@
android:layout_width="wrap_content"
android:text="@string/settings_recovery_title"
android:textSize="48sp"
+ android:hyphenationFrequency="normal"
android:layout_marginStart="24dp"
android:layout_marginBottom="24dp"/>
-
- <com.google.android.material.card.MaterialCardView
- android:id="@+id/settings_recovery_reset_card"
- app:strokeWidth="0dp"
- app:cardCornerRadius="0dp"
- app:checkedIcon="@null"
- android:focusable="true"
- android:checkable="true"
- android:layout_height="wrap_content"
- android:layout_width="match_parent">
-
- <androidx.constraintlayout.widget.ConstraintLayout
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <com.google.android.material.progressindicator.CircularProgressIndicator
+ android:id="@+id/recovery_boot_progress"
+ android:indeterminate="true"
+ android:layout_gravity="center"
+ android:visibility="invisible"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <LinearLayout
+ android:id="@+id/setting_recovery_card_container"
android:layout_width="match_parent"
- android:layout_height="88dp"
- android:layout_marginEnd="16dp"
- android:layout_marginStart="24dp">
-
- <TextView
- android:id="@+id/settings_recovery_reset_title"
- android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+ <!-- TODO: consider custom view for settings item -->
+ <com.google.android.material.card.MaterialCardView
+ android:id="@+id/settings_recovery_reset_card"
+ app:strokeWidth="0dp"
+ app:cardCornerRadius="0dp"
+ app:checkedIcon="@null"
+ android:focusable="true"
+ android:checkable="true"
android:layout_height="wrap_content"
- android:layout_marginTop="20dp"
- android:layout_marginStart="24dp"
- android:textSize="20sp"
- android:text="@string/settings_recovery_reset_title"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@+id/settings_recovery_reset_sub_title"
- app:layout_constraintStart_toStartOf="parent" />
-
- <TextView
- android:id="@+id/settings_recovery_reset_sub_title"
- android:layout_width="0dp"
+ android:layout_width="match_parent">
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="16dp"
+ android:layout_marginStart="24dp">
+ <TextView
+ android:id="@+id/settings_recovery_reset_title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ android:layout_marginStart="24dp"
+ android:textSize="20sp"
+ android:text="@string/settings_recovery_reset_title"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@+id/settings_recovery_reset_sub_title"
+ app:layout_constraintStart_toStartOf="parent" />
+ <TextView
+ android:id="@+id/settings_recovery_reset_sub_title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:textSize="14sp"
+ android:layout_marginBottom="20dp"
+ android:layout_marginStart="24dp"
+ android:text="@string/settings_recovery_reset_sub_title"
+ app:layout_constraintTop_toBottomOf="@+id/settings_recovery_reset_title"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent" />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+ </com.google.android.material.card.MaterialCardView>
+ <com.google.android.material.card.MaterialCardView
+ android:id="@+id/settings_recovery_reset_backup_card"
+ app:strokeWidth="0dp"
+ app:cardCornerRadius="0dp"
+ app:checkedIcon="@null"
+ android:focusable="true"
+ android:checkable="true"
android:layout_height="wrap_content"
- android:textSize="14sp"
- android:layout_marginBottom="20dp"
- android:layout_marginStart="24dp"
- android:text="@string/settings_recovery_reset_sub_title"
- app:layout_constraintTop_toBottomOf="@+id/settings_recovery_reset_title"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent" />
- </androidx.constraintlayout.widget.ConstraintLayout>
- </com.google.android.material.card.MaterialCardView>
-</LinearLayout>
\ No newline at end of file
+ android:layout_width="match_parent">
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="16dp"
+ android:layout_marginStart="24dp">
+ <TextView
+ android:id="@+id/settings_recovery_reset_backup_title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ android:layout_marginStart="24dp"
+ android:textSize="20sp"
+ android:text="@string/settings_recovery_remove_backup_title"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@+id/settings_recovery_reset_backup_sub_title"
+ app:layout_constraintStart_toStartOf="parent" />
+ <TextView
+ android:id="@+id/settings_recovery_reset_backup_sub_title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:textSize="14sp"
+ android:layout_marginBottom="20dp"
+ android:layout_marginStart="24dp"
+ android:text="@string/settings_recovery_remove_backup_sub_title"
+ app:layout_constraintTop_toBottomOf="@+id/settings_recovery_reset_backup_title"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent" />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+ </com.google.android.material.card.MaterialCardView>
+ </LinearLayout>
+ </FrameLayout>
+</LinearLayout>
diff --git a/android/TerminalApp/res/values/strings.xml b/android/TerminalApp/res/values/strings.xml
index 4dd33f4..300cbbc 100644
--- a/android/TerminalApp/res/values/strings.xml
+++ b/android/TerminalApp/res/values/strings.xml
@@ -108,6 +108,13 @@
<!-- Settings menu sub title for removing backup data(/mnt/backup is the path which is supposed not to be translated) [CHAR LIMIT=none] -->
<string name="settings_recovery_remove_backup_sub_title">Clean up <xliff:g id="path" example="/mnt/backup">/mnt/backup</xliff:g></string>
+ <!-- Error page that shows error page [CHAR LIMIT=none] -->
+ <string name="error_title">Unrecoverable Error</string>
+ <!-- Error page that shows error page [CHAR LIMIT=none] -->
+ <string name="error_desc">Failed to recover from an error.\nYou can try restart the app, or try one of recovery option.</string>
+ <!-- Error page that shows detailed error code (error reason) for bugreport. [CHAR LIMIT=none] -->
+ <string name="error_code">Error code: <xliff:g id="error_code" example="ACCESS_DENIED">%s</xliff:g></string>
+
<!-- Notification action button for settings [CHAR LIMIT=20] -->
<string name="service_notification_settings">Settings</string>
<!-- Notification title for foreground service notification [CHAR LIMIT=none] -->
diff --git a/android/VmLauncherApp/Android.bp b/android/VmLauncherApp/Android.bp
deleted file mode 100644
index 2e8cc93..0000000
--- a/android/VmLauncherApp/Android.bp
+++ /dev/null
@@ -1,29 +0,0 @@
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_app {
- name: "VmLauncherApp",
- srcs: ["java/**/*.java"],
- resource_dirs: ["res"],
- static_libs: [
- // TODO(b/330257000): will be removed when binder RPC is used
- "android.system.virtualizationservice_internal-java",
- // TODO(b/331708504): will be removed when AVF framework handles surface
- "libcrosvm_android_display_service-java",
- "vm_launcher_lib",
- ],
- libs: [
- "framework-virtualization.impl",
- "framework-annotations-lib",
- ],
- platform_apis: true,
- privileged: true,
- apex_available: [
- "com.android.virt",
- ],
- optimize: {
- proguard_flags_files: ["proguard.flags"],
- shrink_resources: true,
- },
-}
diff --git a/android/VmLauncherApp/AndroidManifest.xml b/android/VmLauncherApp/AndroidManifest.xml
deleted file mode 100644
index 4fb4b5c..0000000
--- a/android/VmLauncherApp/AndroidManifest.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.virtualization.vmlauncher" >
-
- <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="android.permission.RECORD_AUDIO" />
- <uses-feature android:name="android.software.virtualization_framework" android:required="true" />
-
- <permission android:name="com.android.virtualization.vmlauncher.permission.USE_VM_LAUNCHER"
- android:protectionLevel="signature|preinstalled"/>
-
- <application
- android:label="VmLauncherApp">
- <activity android:name=".MainActivity"
- android:screenOrientation="landscape"
- android:configChanges="orientation|screenSize|keyboard|keyboardHidden|navigation|uiMode"
- android:theme="@style/MyTheme"
- android:resizeableActivity="false"
- android:permission="com.android.virtualization.vmlauncher.permission.USE_VM_LAUNCHER"
- android:exported="true">
- <intent-filter>
- <action android:name="android.virtualization.VM_LAUNCHER" />
- <action android:name="android.virtualization.VM_OPEN_URL" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </activity>
-
- </application>
-
-</manifest>
diff --git a/android/VmLauncherApp/README.md b/android/VmLauncherApp/README.md
deleted file mode 100644
index 0109f37..0000000
--- a/android/VmLauncherApp/README.md
+++ /dev/null
@@ -1,19 +0,0 @@
-# VM launcher app
-
-## Building
-
-This app is now part of the virt APEX.
-
-## Enabling
-
-This app is disabled by default. To re-enable it, execute the following command.
-
-```
-adb root
-adb shell pm enable com.android.virtualization.vmlauncher/.MainActivity
-```
-
-## Running
-
-Copy vm config json file to /data/local/tmp/vm_config.json.
-And then, run the app, check log meesage.
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/ClipboardHandler.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/ClipboardHandler.java
deleted file mode 100644
index def464e..0000000
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/ClipboardHandler.java
+++ /dev/null
@@ -1,84 +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.ClipData;
-import android.content.ClipboardManager;
-import android.content.Context;
-import android.util.Log;
-
-import java.nio.charset.StandardCharsets;
-
-/** Provide methods to synchronize clipboard across Android and VM. */
-class ClipboardHandler {
- private static final String TAG = MainActivity.TAG;
- private final ClipboardManager mClipboardManager;
- private final VmAgent mVmAgent;
-
- ClipboardHandler(Context context, VmAgent vmAgent) {
- mClipboardManager = context.getSystemService(ClipboardManager.class);
- mVmAgent = vmAgent;
- }
-
- private VmAgent.Connection getConnection() throws InterruptedException {
- return mVmAgent.connect();
- }
-
- /** Read a text clip from Android's clipboard and send it to VM. */
- void writeClipboardToVm() {
- if (!mClipboardManager.hasPrimaryClip()) {
- return;
- }
-
- ClipData clip = mClipboardManager.getPrimaryClip();
- String text = clip.getItemAt(0).getText().toString();
- // TODO: remove this trailing null character. The size is already encoded in the header.
- text = text + '\0';
- // TODO: use UTF-8 encoding
- byte[] data = text.getBytes();
-
- try {
- getConnection().sendData(VmAgent.WRITE_CLIPBOARD_TYPE_TEXT_PLAIN, data);
- } catch (InterruptedException | RuntimeException e) {
- Log.e(TAG, "Failed to write clipboard data to VM", e);
- }
- }
-
- /** Read a text clip from VM and paste it to Android's clipboard. */
- void readClipboardFromVm() {
- VmAgent.Data data;
- try {
- data = getConnection().sendAndReceive(VmAgent.READ_CLIPBOARD_FROM_VM, null);
- } catch (InterruptedException | RuntimeException e) {
- Log.e(TAG, "Failed to read clipboard data from VM", e);
- return;
- }
-
- switch (data.type) {
- case VmAgent.WRITE_CLIPBOARD_TYPE_EMPTY:
- Log.d(TAG, "clipboard data from VM is empty");
- break;
- case VmAgent.WRITE_CLIPBOARD_TYPE_TEXT_PLAIN:
- String text = new String(data.data, StandardCharsets.UTF_8);
- ClipData clip = ClipData.newPlainText(null, text);
- mClipboardManager.setPrimaryClip(clip);
- break;
- default:
- Log.e(TAG, "Unknown clipboard response type: " + data.type);
- }
- }
-}
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/DisplayProvider.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/DisplayProvider.java
deleted file mode 100644
index 6eba709..0000000
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/DisplayProvider.java
+++ /dev/null
@@ -1,210 +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.crosvm.ICrosvmAndroidDisplayService;
-import android.graphics.PixelFormat;
-import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.system.virtualizationservice_internal.IVirtualizationServiceInternal;
-import android.util.Log;
-import android.view.SurfaceControl;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-
-import libcore.io.IoBridge;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-/** Presents Android-side surface where VM can use as a display */
-class DisplayProvider {
- private static final String TAG = MainActivity.TAG;
- private final SurfaceView mMainView;
- private final SurfaceView mCursorView;
- private final IVirtualizationServiceInternal mVirtService;
- private CursorHandler mCursorHandler;
-
- DisplayProvider(SurfaceView mainView, SurfaceView cursorView) {
- mMainView = mainView;
- mCursorView = cursorView;
-
- mMainView.setSurfaceLifecycle(SurfaceView.SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT);
- mMainView.getHolder().addCallback(new Callback(Callback.SurfaceKind.MAIN));
-
- mCursorView.setSurfaceLifecycle(SurfaceView.SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT);
- mCursorView.getHolder().addCallback(new Callback(Callback.SurfaceKind.CURSOR));
- mCursorView.getHolder().setFormat(PixelFormat.RGBA_8888);
- // TODO: do we need this z-order?
- mCursorView.setZOrderMediaOverlay(true);
-
- IBinder b = ServiceManager.waitForService("android.system.virtualizationservice");
- mVirtService = IVirtualizationServiceInternal.Stub.asInterface(b);
- try {
- // To ensure that the previous display service is removed.
- mVirtService.clearDisplayService();
- } catch (RemoteException e) {
- throw new RuntimeException("Failed to clear prior display service", e);
- }
- }
-
- void notifyDisplayIsGoingToInvisible() {
- // When the display is going to be invisible (by putting in the background), save the frame
- // of the main surface so that we can re-draw it next time the display becomes visible. This
- // is to save the duration of time where nothing is drawn by VM.
- try {
- getDisplayService().saveFrameForSurface(false /* forCursor */);
- } catch (RemoteException e) {
- throw new RuntimeException("Failed to save frame for the main surface", e);
- }
- }
-
- private synchronized ICrosvmAndroidDisplayService getDisplayService() {
- try {
- IBinder b = mVirtService.waitDisplayService();
- return ICrosvmAndroidDisplayService.Stub.asInterface(b);
- } catch (Exception e) {
- throw new RuntimeException("Error while getting display service", e);
- }
- }
-
- private class Callback implements SurfaceHolder.Callback {
- enum SurfaceKind {
- MAIN,
- CURSOR
- }
-
- private final SurfaceKind mSurfaceKind;
-
- Callback(SurfaceKind kind) {
- mSurfaceKind = kind;
- }
-
- private boolean isForCursor() {
- return mSurfaceKind == SurfaceKind.CURSOR;
- }
-
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- try {
- getDisplayService().setSurface(holder.getSurface(), isForCursor());
- } catch (Exception e) {
- // TODO: don't consume this exception silently. For some unknown reason, setSurface
- // call above throws IllegalArgumentException and that fails the surface
- // configuration.
- Log.e(TAG, "Failed to present surface " + mSurfaceKind + " to VM", e);
- }
-
- try {
- switch (mSurfaceKind) {
- case MAIN:
- getDisplayService().drawSavedFrameForSurface(isForCursor());
- break;
- case CURSOR:
- ParcelFileDescriptor stream = createNewCursorStream();
- getDisplayService().setCursorStream(stream);
- break;
- }
- } catch (Exception e) {
- // TODO: don't consume exceptions here too
- Log.e(TAG, "Failed to configure surface " + mSurfaceKind, e);
- }
- }
-
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
- // TODO: support resizeable display. We could actually change the display size that the
- // VM sees, or keep the size and render it by fitting it in the new surface.
- }
-
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {
- try {
- getDisplayService().removeSurface(isForCursor());
- } catch (RemoteException e) {
- throw new RuntimeException("Error while destroying surface for " + mSurfaceKind, e);
- }
- }
- }
-
- private ParcelFileDescriptor createNewCursorStream() {
- if (mCursorHandler != null) {
- mCursorHandler.interrupt();
- }
- ParcelFileDescriptor[] pfds;
- try {
- pfds = ParcelFileDescriptor.createSocketPair();
- } catch (IOException e) {
- throw new RuntimeException("Failed to create socketpair for cursor stream", e);
- }
- mCursorHandler = new CursorHandler(pfds[0]);
- mCursorHandler.start();
- return pfds[1];
- }
-
- /**
- * Thread reading cursor coordinate from a stream, and updating the position of the cursor
- * surface accordingly.
- */
- private class CursorHandler extends Thread {
- private final ParcelFileDescriptor mStream;
- private final SurfaceControl mCursor;
- private final SurfaceControl.Transaction mTransaction;
-
- CursorHandler(ParcelFileDescriptor stream) {
- mStream = stream;
- mCursor = DisplayProvider.this.mCursorView.getSurfaceControl();
- mTransaction = new SurfaceControl.Transaction();
-
- SurfaceControl main = DisplayProvider.this.mMainView.getSurfaceControl();
- mTransaction.reparent(mCursor, main).apply();
- }
-
- @Override
- public void run() {
- try {
- ByteBuffer byteBuffer = ByteBuffer.allocate(8 /* (x: u32, y: u32) */);
- byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
- while (true) {
- if (Thread.interrupted()) {
- Log.d(TAG, "CursorHandler thread interrupted!");
- return;
- }
- byteBuffer.clear();
- int bytes =
- IoBridge.read(
- mStream.getFileDescriptor(),
- byteBuffer.array(),
- 0,
- byteBuffer.array().length);
- if (bytes == -1) {
- Log.e(TAG, "cannot read from cursor stream, stop the handler");
- return;
- }
- float x = (float) (byteBuffer.getInt() & 0xFFFFFFFF);
- float y = (float) (byteBuffer.getInt() & 0xFFFFFFFF);
- mTransaction.setPosition(mCursor, x, y).apply();
- }
- } catch (IOException e) {
- Log.e(TAG, "failed to run CursorHandler", e);
- }
- }
- }
-}
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/InputForwarder.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/InputForwarder.java
deleted file mode 100644
index 1be362b..0000000
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/InputForwarder.java
+++ /dev/null
@@ -1,148 +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.hardware.input.InputManager;
-import android.os.Handler;
-import android.system.virtualmachine.VirtualMachine;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig;
-import android.util.Log;
-import android.view.InputDevice;
-import android.view.KeyEvent;
-import android.view.View;
-
-/** Forwards input events (touch, mouse, ...) from Android to VM */
-class InputForwarder {
- private static final String TAG = MainActivity.TAG;
- private final Context mContext;
- private final VirtualMachine mVirtualMachine;
- private InputManager.InputDeviceListener mInputDeviceListener;
-
- private boolean isTabletMode = false;
-
- InputForwarder(
- Context context,
- VirtualMachine vm,
- View touchReceiver,
- View mouseReceiver,
- View keyReceiver) {
- mContext = context;
- mVirtualMachine = vm;
-
- VirtualMachineCustomImageConfig config = vm.getConfig().getCustomImageConfig();
- if (config.useTouch()) {
- setupTouchReceiver(touchReceiver);
- }
- if (config.useMouse() || config.useTrackpad()) {
- setupMouseReceiver(mouseReceiver);
- }
- if (config.useKeyboard()) {
- setupKeyReceiver(keyReceiver);
- }
- if (config.useSwitches()) {
- // Any view's handler is fine.
- setupTabletModeHandler(touchReceiver.getHandler());
- }
- }
-
- void cleanUp() {
- if (mInputDeviceListener != null) {
- InputManager im = mContext.getSystemService(InputManager.class);
- im.unregisterInputDeviceListener(mInputDeviceListener);
- mInputDeviceListener = null;
- }
- }
-
- private void setupTouchReceiver(View receiver) {
- receiver.setOnTouchListener(
- (v, event) -> {
- return mVirtualMachine.sendMultiTouchEvent(event);
- });
- }
-
- private void setupMouseReceiver(View receiver) {
- receiver.requestUnbufferedDispatch(InputDevice.SOURCE_ANY);
- receiver.setOnCapturedPointerListener(
- (v, event) -> {
- int eventSource = event.getSource();
- if ((eventSource & InputDevice.SOURCE_CLASS_POSITION) != 0) {
- return mVirtualMachine.sendTrackpadEvent(event);
- }
- return mVirtualMachine.sendMouseEvent(event);
- });
- }
-
- private void setupKeyReceiver(View receiver) {
- receiver.setOnKeyListener(
- (v, code, event) -> {
- // TODO: this is guest-os specific. It shouldn't be handled here.
- if (isVolumeKey(code)) {
- return false;
- }
- return mVirtualMachine.sendKeyEvent(event);
- });
- }
-
- private static boolean isVolumeKey(int keyCode) {
- return keyCode == KeyEvent.KEYCODE_VOLUME_UP
- || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
- || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE;
- }
-
- private void setupTabletModeHandler(Handler handler) {
- InputManager im = mContext.getSystemService(InputManager.class);
- mInputDeviceListener =
- new InputManager.InputDeviceListener() {
- @Override
- public void onInputDeviceAdded(int deviceId) {
- setTabletModeConditionally();
- }
-
- @Override
- public void onInputDeviceRemoved(int deviceId) {
- setTabletModeConditionally();
- }
-
- @Override
- public void onInputDeviceChanged(int deviceId) {
- setTabletModeConditionally();
- }
- };
- im.registerInputDeviceListener(mInputDeviceListener, handler);
- }
-
- private static boolean hasPhysicalKeyboard() {
- for (int id : InputDevice.getDeviceIds()) {
- InputDevice d = InputDevice.getDevice(id);
- if (!d.isVirtual() && d.isEnabled() && d.isFullKeyboard()) {
- return true;
- }
- }
- return false;
- }
-
- void setTabletModeConditionally() {
- boolean tabletModeNeeded = !hasPhysicalKeyboard();
- if (tabletModeNeeded != isTabletMode) {
- String mode = tabletModeNeeded ? "tablet mode" : "desktop mode";
- Log.d(TAG, "switching to " + mode);
- isTabletMode = tabletModeNeeded;
- mVirtualMachine.sendTabletModeEvent(tabletModeNeeded);
- }
- }
-}
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java
deleted file mode 100644
index fb75533..0000000
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java
+++ /dev/null
@@ -1,194 +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 static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
-import android.Manifest.permission;
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.system.virtualmachine.VirtualMachine;
-import android.system.virtualmachine.VirtualMachineConfig;
-import android.system.virtualmachine.VirtualMachineException;
-import android.util.Log;
-import android.view.SurfaceView;
-import android.view.View;
-import android.view.Window;
-import android.view.WindowInsets;
-import android.view.WindowInsetsController;
-
-import java.nio.file.Path;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-public class MainActivity extends Activity {
- static final String TAG = "VmLauncherApp";
- // TODO: this path should be from outside of this activity
- private static final String VM_CONFIG_PATH = "/data/local/tmp/vm_config.json";
-
- private static final int RECORD_AUDIO_PERMISSION_REQUEST_CODE = 101;
-
- private static final String ACTION_VM_LAUNCHER = "android.virtualization.VM_LAUNCHER";
- private static final String ACTION_VM_OPEN_URL = "android.virtualization.VM_OPEN_URL";
-
- private ExecutorService mExecutorService;
- private VirtualMachine mVirtualMachine;
- private InputForwarder mInputForwarder;
- private DisplayProvider mDisplayProvider;
- private VmAgent mVmAgent;
- private ClipboardHandler mClipboardHandler;
- private OpenUrlHandler mOpenUrlHandler;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- Log.d(TAG, "onCreate intent: " + getIntent());
- checkAndRequestRecordAudioPermission();
- 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) {
- throw new RuntimeException(e);
- }
- mVirtualMachine = runner.getVm();
- runner.getExitStatus()
- .thenAcceptAsync(
- success -> {
- setResult(success ? RESULT_OK : RESULT_CANCELED);
- finish();
- });
-
- // Setup UI
- setContentView(R.layout.activity_main);
- SurfaceView mainView = findViewById(R.id.surface_view);
- SurfaceView cursorView = findViewById(R.id.cursor_surface_view);
- View touchView = findViewById(R.id.background_touch_view);
- makeFullscreen();
-
- // Connect the views to the VM
- mInputForwarder = new InputForwarder(this, mVirtualMachine, touchView, mainView, mainView);
- mDisplayProvider = new DisplayProvider(mainView, cursorView);
-
- Path logPath = getFileStreamPath(mVirtualMachine.getName() + ".log").toPath();
- Logger.setup(mVirtualMachine, logPath, mExecutorService);
-
- mVmAgent = new VmAgent(mVirtualMachine);
- mClipboardHandler = new ClipboardHandler(this, mVmAgent);
- mOpenUrlHandler = new OpenUrlHandler(mVmAgent);
- handleIntent(getIntent());
- }
-
- private void makeFullscreen() {
- Window w = getWindow();
- w.setDecorFitsSystemWindows(false);
- WindowInsetsController insetsCtrl = w.getInsetsController();
- insetsCtrl.hide(WindowInsets.Type.systemBars());
- insetsCtrl.setSystemBarsBehavior(
- WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- mInputForwarder.setTabletModeConditionally();
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- mDisplayProvider.notifyDisplayIsGoingToInvisible();
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- try {
- mVirtualMachine.suspend();
- } catch (VirtualMachineException e) {
- Log.e(TAG, "Failed to suspend VM" + e);
- }
- }
-
- @Override
- protected void onRestart() {
- super.onRestart();
- try {
- mVirtualMachine.resume();
- } catch (VirtualMachineException e) {
- Log.e(TAG, "Failed to resume VM" + e);
- }
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- mExecutorService.shutdownNow();
- mInputForwarder.cleanUp();
- mOpenUrlHandler.shutdown();
- Log.d(TAG, "destroyed");
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasFocus) {
- super.onWindowFocusChanged(hasFocus);
-
- // TODO: explain why we have to do this on every focus change
- if (hasFocus) {
- SurfaceView mainView = findViewById(R.id.surface_view);
- mainView.requestPointerCapture();
- }
-
- // TODO: remove executor here. Let clipboard handler handle this.
- mExecutorService.execute(
- () -> {
- if (hasFocus) {
- mClipboardHandler.writeClipboardToVm();
- } else {
- mClipboardHandler.readClipboardFromVm();
- }
- });
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- Log.d(TAG, "onNewIntent intent: " + intent);
- handleIntent(intent);
- }
-
- private void handleIntent(Intent intent) {
- if (ACTION_VM_OPEN_URL.equals(intent.getAction())) {
- String url = intent.getStringExtra(Intent.EXTRA_TEXT);
- if (url != null) {
- mOpenUrlHandler.sendUrlToVm(url);
- }
- }
- }
-
- private void checkAndRequestRecordAudioPermission() {
- if (getApplicationContext().checkSelfPermission(permission.RECORD_AUDIO)
- != PERMISSION_GRANTED) {
- requestPermissions(
- new String[] {permission.RECORD_AUDIO}, RECORD_AUDIO_PERMISSION_REQUEST_CODE);
- }
- }
-}
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/OpenUrlHandler.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/OpenUrlHandler.java
deleted file mode 100644
index fb0c6bf..0000000
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/OpenUrlHandler.java
+++ /dev/null
@@ -1,50 +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.util.Log;
-
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-class OpenUrlHandler {
- private static final String TAG = MainActivity.TAG;
-
- private final VmAgent mVmAgent;
- private final ExecutorService mExecutorService;
-
- OpenUrlHandler(VmAgent vmAgent) {
- mVmAgent = vmAgent;
- mExecutorService = Executors.newSingleThreadExecutor();
- }
-
- void shutdown() {
- mExecutorService.shutdownNow();
- }
-
- void sendUrlToVm(String url) {
- mExecutorService.execute(
- () -> {
- try {
- mVmAgent.connect().sendData(VmAgent.OPEN_URL, url.getBytes());
- Log.d(TAG, "Successfully sent URL to the VM");
- } catch (InterruptedException | RuntimeException e) {
- Log.e(TAG, "Failed to send URL to the VM", e);
- }
- });
- }
-}
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmAgent.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmAgent.java
deleted file mode 100644
index af1d298..0000000
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmAgent.java
+++ /dev/null
@@ -1,145 +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.os.ParcelFileDescriptor;
-import android.os.SystemClock;
-import android.system.virtualmachine.VirtualMachine;
-import android.system.virtualmachine.VirtualMachineException;
-import android.util.Log;
-
-import libcore.io.Streams;
-
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-/**
- * Agent running in the VM. This class provides connection to the agent and ways to communicate with
- * it.
- */
-class VmAgent {
- private static final String TAG = MainActivity.TAG;
- private static final int DATA_SHARING_SERVICE_PORT = 3580;
- private static final int HEADER_SIZE = 8; // size of the header
- private static final int SIZE_OFFSET = 4; // offset of the size field in the header
- private static final long RETRY_INTERVAL_MS = 1_000;
-
- static final byte READ_CLIPBOARD_FROM_VM = 0;
- static final byte WRITE_CLIPBOARD_TYPE_EMPTY = 1;
- static final byte WRITE_CLIPBOARD_TYPE_TEXT_PLAIN = 2;
- static final byte OPEN_URL = 3;
-
- private final VirtualMachine mVirtualMachine;
-
- VmAgent(VirtualMachine vm) {
- mVirtualMachine = vm;
- }
-
- /**
- * Connects to the agent and returns the established communication channel. This can block.
- *
- * @throws InterruptedException If the current thread was interrupted
- */
- Connection connect() throws InterruptedException {
- boolean shouldLog = true;
- while (true) {
- if (Thread.interrupted()) {
- throw new InterruptedException();
- }
- try {
- return new Connection(mVirtualMachine.connectVsock(DATA_SHARING_SERVICE_PORT));
- } catch (VirtualMachineException e) {
- if (shouldLog) {
- shouldLog = false;
- Log.d(TAG, "Still waiting for VM agent to start", e);
- }
- }
- SystemClock.sleep(RETRY_INTERVAL_MS);
- }
- }
-
- static class Data {
- final int type;
- final byte[] data;
-
- Data(int type, byte[] data) {
- this.type = type;
- this.data = data;
- }
- }
-
- /** Represents a connection to the agent */
- class Connection {
- private final ParcelFileDescriptor mConn;
-
- private Connection(ParcelFileDescriptor conn) {
- mConn = conn;
- }
-
- /** Send data of a given type. This can block. */
- void sendData(byte type, byte[] data) {
- // Byte 0: Data type
- // Byte 1-3: Padding alignment & Reserved for other use cases in the future
- // Byte 4-7: Data size of the payload
- ByteBuffer header = ByteBuffer.allocate(HEADER_SIZE);
- header.clear();
- header.order(ByteOrder.LITTLE_ENDIAN);
- header.put(0, type);
- int dataSize = data == null ? 0 : data.length;
- header.putInt(SIZE_OFFSET, dataSize);
-
- try (OutputStream out = new FileOutputStream(mConn.getFileDescriptor())) {
- out.write(header.array());
- if (data != null) {
- out.write(data);
- }
- } catch (IOException e) {
- throw new RuntimeException("Failed to send message of type: " + type, e);
- }
- }
-
- /** Read data from agent. This can block. */
- Data readData() {
- ByteBuffer header = ByteBuffer.allocate(HEADER_SIZE);
- header.clear();
- header.order(ByteOrder.LITTLE_ENDIAN);
- byte[] data;
-
- try (InputStream in = new FileInputStream(mConn.getFileDescriptor())) {
- Streams.readFully(in, header.array());
- byte type = header.get(0);
- int dataSize = header.getInt(SIZE_OFFSET);
- data = new byte[dataSize];
- Streams.readFully(in, data);
- return new Data(type, data);
- } catch (IOException e) {
- throw new RuntimeException("Failed to read data", e);
- }
- }
-
- /** Convenient method for sending data and then reading response for it. This can block. */
- Data sendAndReceive(byte type, byte[] data) {
- sendData(type, data);
- return readData();
- }
- }
-}
diff --git a/android/VmLauncherApp/proguard.flags b/android/VmLauncherApp/proguard.flags
deleted file mode 100644
index b93240c..0000000
--- a/android/VmLauncherApp/proguard.flags
+++ /dev/null
@@ -1,33 +0,0 @@
-##---------------Begin: proguard configuration for Gson ----------
-# Gson uses generic type information stored in a class file when working with fields. Proguard
-# removes such information by default, so configure it to keep all of it.
--keepattributes Signature
-
-# For using GSON @Expose annotation
--keepattributes *Annotation*
-
-# Gson specific classes
--dontwarn sun.misc.**
-#-keep class com.google.gson.stream.** { *; }
-
-# Application classes that will be serialized/deserialized over Gson
--keep class com.android.virtualization.vmlauncher.ConfigJson { <fields>; }
--keep class com.android.virtualization.vmlauncher.ConfigJson$* { <fields>; }
-
-# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
-# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
--keep class * extends com.google.gson.TypeAdapter
--keep class * implements com.google.gson.TypeAdapterFactory
--keep class * implements com.google.gson.JsonSerializer
--keep class * implements com.google.gson.JsonDeserializer
-
-# Prevent R8 from leaving Data object members always null
--keepclassmembers,allowobfuscation class * {
- @com.google.gson.annotations.SerializedName <fields>;
-}
-
-# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
--keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
--keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
-
-##---------------End: proguard configuration for Gson ----------
\ No newline at end of file
diff --git a/android/VmLauncherApp/res/layout/activity_main.xml b/android/VmLauncherApp/res/layout/activity_main.xml
deleted file mode 100644
index a80ece0..0000000
--- a/android/VmLauncherApp/res/layout/activity_main.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<merge xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context=".MainActivity">
- <View
- android:id="@+id/background_touch_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- />
- <SurfaceView
- android:id="@+id/surface_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:focusable="true"
- android:focusableInTouchMode="true"
- android:focusedByDefault="true"
- android:defaultFocusHighlightEnabled="true">
- <requestFocus />
- </SurfaceView>
- <!-- A cursor size in virtio-gpu spec is always 64x64 -->
- <SurfaceView
- android:id="@+id/cursor_surface_view"
- android:layout_width="64px"
- android:layout_height="64px">
- </SurfaceView>
-
-</merge>
diff --git a/android/VmLauncherApp/res/values/themes.xml b/android/VmLauncherApp/res/values/themes.xml
deleted file mode 100644
index c10b6d9..0000000
--- a/android/VmLauncherApp/res/values/themes.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-<resources xmlns:tools="http://schemas.android.com/tools">
- <style name="MyTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar">
- <item name="android:navigationBarColor">
- @android:color/transparent
- </item>
- <item name="android:statusBarColor">
- @android:color/transparent
- </item>
- <item name="android:windowLayoutInDisplayCutoutMode">
- always
- </item>
- </style>
-</resources>
diff --git a/android/forwarder_host/src/forwarder_host.rs b/android/forwarder_host/src/forwarder_host.rs
index 7496a02..2138957 100644
--- a/android/forwarder_host/src/forwarder_host.rs
+++ b/android/forwarder_host/src/forwarder_host.rs
@@ -378,7 +378,7 @@
/// JNI function for running forwarder_host.
#[no_mangle]
-pub extern "C" fn Java_com_android_virtualization_vmlauncher_DebianServiceImpl_runForwarderHost(
+pub extern "C" fn Java_com_android_virtualization_terminal_DebianServiceImpl_runForwarderHost(
env: JNIEnv,
_class: JObject,
cid: jint,
@@ -396,7 +396,7 @@
/// JNI function for terminating forwarder_host.
#[no_mangle]
-pub extern "C" fn Java_com_android_virtualization_vmlauncher_DebianServiceImpl_terminateForwarderHost(
+pub extern "C" fn Java_com_android_virtualization_terminal_DebianServiceImpl_terminateForwarderHost(
_env: JNIEnv,
_class: JObject,
) {
@@ -405,7 +405,7 @@
/// JNI function for updating listening ports.
#[no_mangle]
-pub extern "C" fn Java_com_android_virtualization_vmlauncher_DebianServiceImpl_updateListeningPorts(
+pub extern "C" fn Java_com_android_virtualization_terminal_DebianServiceImpl_updateListeningPorts(
env: JNIEnv,
_class: JObject,
ports: JIntArray,
diff --git a/build/debian/fai_config/files/etc/systemd/system/backup_mount.service/AVF b/build/debian/fai_config/files/etc/systemd/system/backup_mount.service/AVF
new file mode 100644
index 0000000..966df49
--- /dev/null
+++ b/build/debian/fai_config/files/etc/systemd/system/backup_mount.service/AVF
@@ -0,0 +1,14 @@
+[Unit]
+Description=Mount backup rootfs
+After=network.target
+After=virtiofs_internal.service
+
+[Service]
+Type=oneshot
+User=root
+Group=root
+ExecStart=/bin/bash -c '[ -e "/dev/vdb" ] && (mkdir -p /mnt/backup; chown 1000:100 /mnt/backup; mount /dev/vdb /mnt/backup) || (rm -rf /mnt/backup)'
+RemainAfterExit=yes
+
+[Install]
+WantedBy=multi-user.target
diff --git a/build/debian/fai_config/scripts/AVF/10-systemd b/build/debian/fai_config/scripts/AVF/10-systemd
index 1605381..a514299 100755
--- a/build/debian/fai_config/scripts/AVF/10-systemd
+++ b/build/debian/fai_config/scripts/AVF/10-systemd
@@ -9,3 +9,4 @@
ln -s /etc/systemd/system/virtiofs.service $target/etc/systemd/system/multi-user.target.wants/virtiofs.service
ln -s /etc/systemd/system/forwarder_guest_launcher.service $target/etc/systemd/system/multi-user.target.wants/forwarder_guest_launcher.service
ln -s /etc/systemd/system/virtiofs_internal.service $target/etc/systemd/system/multi-user.target.wants/virtiofs_internal.service
+ln -s /etc/systemd/system/backup_mount.service $target/etc/systemd/system/multi-user.target.wants/backup_mount.service
diff --git a/docs/custom_vm.md b/docs/custom_vm.md
index b02fbf7..9a9ede4 100644
--- a/docs/custom_vm.md
+++ b/docs/custom_vm.md
@@ -24,305 +24,3 @@
The `vm` command also has other subcommands for debugging; run
`/apex/com.android.virt/bin/vm help` for details.
-
-### Running Debian
-1. Download an ARM64 image from https://cloud.debian.org/images/cloud/ (We tested nocloud image)
-
-2. Resize the image
-```shell
-truncate -s 20G debian.img
-virt-resize --expand /dev/sda1 <download_image_file> debian.img
-```
-
-3. Copy the image file
-```shell
-tar cfS debian.img.tar debian.img
-adb push debian.img.tar /data/local/tmp/
-adb shell tar xf /data/local/tmp/debian.img.tar -C /data/local/tmp/
-adb shell rm /data/local/tmp/debian.img.tar
-adb shell chmod a+w /data/local/tmp/debian.img
-rm debian.img.tar
-```
-
-Note: we tar and untar to keep the image file sparse.
-
-4. Make the VM config file
-```shell
-cat > vm_config.json <<EOF
-{
- "name": "debian",
- "disks": [
- {
- "image": "/data/local/tmp/debian.img",
- "partitions": [],
- "writable": true
- }
- ],
- "protected": false,
- "cpu_topology": "match_host",
- "platform_version": "~1.0",
- "memory_mib": 8096,
- "debuggable": true,
- "console_out": true,
- "connect_console": true,
- "console_input_device": "ttyS0",
- "network": true,
- "input": {
- "touchscreen": true,
- "keyboard": true,
- "mouse": true,
- "trackpad": true,
- "switches": true
- },
- "audio": {
- "speaker": true,
- "microphone": true
- },
- "gpu": {
- "backend": "virglrenderer",
- "context_types": ["virgl2"]
- },
- "display": {
- "refresh_rate": "30"
- }
-}
-EOF
-adb push vm_config.json /data/local/tmp/
-```
-
-5. Launch VmLauncherApp(the detail will be explain below)
-
-6. For console, we can refer to `Debugging` section below. (id: root)
-
-7. For graphical shell, you need to install xfce(for now, only xfce is tested)
-```
-apt install task-xfce-desktop
-dpkg --configure -a (if necessary)
-systemctl set-default graphical.target
-
-# need non-root user for graphical shell
-adduser linux
-# optional
-adduser linux sudo
-reboot
-```
-
-## Graphical VMs
-
-To run OSes with graphics support, simply
-`packages/modules/Virtualization/tests/ferrochrome/ferrochrome.sh --forever`.
-It prepares and launches the ChromiumOS, which is the only officially supported
-guest payload. We will be adding more OSes in the future.
-
-If you want to do so by yourself, follow the instruction below.
-
-### Prepare a guest image
-
-As of today (April 2024), ChromiumOS is the only officially supported guest
-payload. We will be adding more OSes in the future.
-
-#### Download ChromiumOS from build server
-
-Download
-https://storage.googleapis.com/chromiumos-image-archive/ferrochrome-public/R128-15926.0.0/chromiumos_test_image.tar.xz.
-The above will download ferrochrome test image with version `R128-15926.0.0`.
-
-To download latest version, use following code.
-
-```sh
-URL=https://storage.googleapis.com/chromiumos-image-archive/ferrochrome-public
-LATEST_VERSION=$(curl -s ${URL}/LATEST-main)
-curl -O ${URL}/${LATEST_VERSION}/chromiumos_test_image.tar.xz
-```
-
-To navigate build server artifacts,
-[install gsutil](https://cloud.google.com/storage/docs/gsutil_install).
-`gs://chromiumos-image-archive/ferrochrome-public` is the top level directory for ferrochrome build.
-
-#### Build ChromiumOS for VM
-
-First, check out source code from the ChromiumOS and Chromium projects.
-
-* Checking out Chromium: https://www.chromium.org/developers/how-tos/get-the-code/
-* Checking out ChromiumOS: https://www.chromium.org/chromium-os/developer-library/guides/development/developer-guide/
-
-Important: When you are at the step “Set up gclient args” in the Chromium checkout instruction, configure .gclient as follows.
-
-```
-$ cat ~/chromium/.gclient
-solutions = [
- {
- "name": "src",
- "url": "https://chromium.googlesource.com/chromium/src.git",
- "managed": False,
- "custom_deps": {},
- "custom_vars": {},
- },
-]
-target_os = ['chromeos']
-```
-
-In this doc, it is assumed that ChromiumOS is checked out at `~/chromiumos` and
-Chromium is at `~/chromium`. If you downloaded to different places, you can
-create symlinks.
-
-Then enter into the cros sdk.
-
-```
-$ cd ~/chromiumos
-$ cros_sdk --chrome-root=$(readlink -f ~/chromium)
-```
-
-Now you are in the cros sdk. `(cr)` below means that the commands should be
-executed inside the sdk.
-
-First, choose the target board. `ferrochrome` is the name of the virtual board
-for AVF-compatible VM.
-
-```
-(cr) setup_board --board=ferrochrome
-```
-
-Then, tell the cros sdk that you want to build chrome (the browser) from the
-local checkout and also with your local modifications instead of prebuilts.
-
-```
-(cr) CHROME_ORIGIN=LOCAL_SOURCE
-(cr) ACCEPT_LICENSES='*'
-(cr) cros workon -b ferrochrome start \
-chromeos-base/chromeos-chrome \
-chromeos-base/chrome-icu
-```
-
-Optionally, if you have touched the kernel source code (which is under
-~/chromiumos/src/third_party/kernel/v5.15), you have to tell the cros sdk that
-you want it also to be built from the modified source code, not from the
-official HEAD.
-
-```
-(cr) cros workon -b ferrochrome start chromeos-kernel-5_15
-```
-
-Finally, build individual packages, and build the disk image out of the packages.
-
-```
-(cr) cros build-packages --board=ferrochrome --chromium --accept-licenses='*'
-(cr) cros build-image --board=ferrochrome --no-enable-rootfs-verification test
-```
-
-This takes some time. When the build is done, exit from the sdk.
-
-Note: If build-packages doesn’t seem to include your local changes, try
-invoking emerge directly:
-
-```
-(cr) emerge-ferrochrome -av chromeos-base/chromeos-chrome
-```
-
-Don’t forget to call `build-image` afterwards.
-
-You need ChromiumOS disk image: ~/chromiumos/src/build/images/ferrochrome/latest/chromiumos_test_image.bin
-
-### Create a guest VM configuration
-
-Push the kernel and the main image to the Android device.
-
-```
-$ adb push ~/chromiumos/src/build/images/ferrochrome/latest/chromiumos_test_image.bin /data/local/tmp/
-```
-
-Create a VM config file as below.
-
-```
-$ cat > vm_config.json; adb push vm_config.json /data/local/tmp
-{
- "name": "cros",
- "disks": [
- {
- "image": "/data/local/tmp/chromiumos_test_image.bin",
- "partitions": [],
- "writable": true
- }
- ],
- "protected": false,
- "cpu_topology": "match_host",
- "platform_version": "~1.0",
- "memory_mib": 8096,
- "debuggable": true,
- "console_out": true,
- "connect_console": true,
- "console_input_device": "hvc0",
- "network": true,
- "input": {
- "touchscreen": true,
- "keyboard": true,
- "mouse": true,
- "trackpad": true,
- "switches": true
- },
- "audio": {
- "speaker": true,
- "microphone": true
- },
- "gpu": {
- "backend": "virglrenderer",
- "context_types": ["virgl2"]
- },
- "display": {
- "scale": "0.77",
- "refresh_rate": "30"
- }
-}
-```
-
-### Running the VM
-
-1. Grant permission to the `VmLauncherApp` if the virt apex is Google-signed.
- ```shell
- $ adb shell su root pm grant com.google.android.virtualization.vmlauncher android.permission.USE_CUSTOM_VIRTUAL_MACHINE
- ```
-
-2. Ensure your device is connected to the Internet.
-
-3. Launch the app with adb.
- ```shell
- $ adb shell su root am start-activity -a android.virtualization.VM_LAUNCHER
- ```
-
-If it doesn’t work well, try
-
-```
-$ adb shell pm clear com.android.virtualization.vmlauncher
-# or
-$ adb shell pm clear com.google.android.virtualization.vmlauncher
-```
-
-### Debugging
-
-To open the serial console (interactive terminal):
-```shell
-$ adb shell -t /apex/com.android.virt/bin/vm console
-```
-
-To see console logs only, check
-`/data/user/${current_user_id}/com{,.google}.android.virtualization.vmlauncher/files/${vm_name}.log`
-
-You can monitor console out as follows
-
-```shell
-$ adb shell 'su root tail +0 -F /data/user/$(am get-current-user)/com{,.google}.android.virtualization.vmlauncher/files/${vm_name}.log'
-```
-
-For ChromiumOS, you can enter to the console via SSH connection. Check your IP
-address of ChromiumOS VM from the ethernet network setting page and follow
-commands below.
-
-```shell
-$ adb kill-server ; adb start-server
-$ adb shell nc -s localhost -L -p 9222 nc ${CHROMIUMOS_IPV4_ADDR} 22 # This command won't be terminated.
-$ adb forward tcp:9222 tcp:9222
-$ ssh -oProxyCommand=none -o UserKnownHostsFile=/dev/null root@localhost -p 9222
-```
-
-For ChromiumOS, you would need to login after enthering its console.
-The user ID and the password is `root` and `test0000` respectively.
diff --git a/guest/forwarder_guest_launcher/src/main.rs b/guest/forwarder_guest_launcher/src/main.rs
index c3fdd7e..16b05b4 100644
--- a/guest/forwarder_guest_launcher/src/main.rs
+++ b/guest/forwarder_guest_launcher/src/main.rs
@@ -31,7 +31,7 @@
use tonic::Request;
mod debian_service {
- tonic::include_proto!("com.android.virtualization.vmlauncher.proto");
+ tonic::include_proto!("com.android.virtualization.terminal.proto");
}
const NON_PREVILEGED_PORT_RANGE_START: i32 = 1024;
diff --git a/guest/ip_addr_reporter/src/main.rs b/guest/ip_addr_reporter/src/main.rs
index 2c782d3..62a7aef 100644
--- a/guest/ip_addr_reporter/src/main.rs
+++ b/guest/ip_addr_reporter/src/main.rs
@@ -3,7 +3,7 @@
use clap::Parser;
pub mod api {
- tonic::include_proto!("com.android.virtualization.vmlauncher.proto");
+ tonic::include_proto!("com.android.virtualization.terminal.proto");
}
#[derive(Parser)]
diff --git a/guest/trusty/common/Android.bp b/guest/trusty/common/Android.bp
new file mode 100644
index 0000000..0541ed5
--- /dev/null
+++ b/guest/trusty/common/Android.bp
@@ -0,0 +1,7 @@
+prebuilt_etc {
+ name: "early_vms.xml",
+ src: "early_vms.xml",
+ filename: "early_vms.xml",
+ relative_install_path: "avf",
+ system_ext_specific: true,
+}
diff --git a/guest/trusty/common/early_vms.xml b/guest/trusty/common/early_vms.xml
new file mode 100644
index 0000000..1ed324c
--- /dev/null
+++ b/guest/trusty/common/early_vms.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 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.
+-->
+<early_vms>
+ <early_vm>
+ <name>trusty_security_vm_launcher</name>
+ <cid>200</cid>
+ <path>/system_ext/bin/trusty_security_vm_launcher</path>
+ </early_vm>
+</early_vms>
diff --git a/libs/debian_service/proto/DebianService.proto b/libs/debian_service/proto/DebianService.proto
index bf05ebe..61bcece 100644
--- a/libs/debian_service/proto/DebianService.proto
+++ b/libs/debian_service/proto/DebianService.proto
@@ -16,9 +16,9 @@
syntax = "proto3";
-package com.android.virtualization.vmlauncher.proto;
+package com.android.virtualization.terminal.proto;
-option java_package = "com.android.virtualization.vmlauncher.proto";
+option java_package = "com.android.virtualization.terminal.proto";
option java_multiple_files = true;
service DebianService {
diff --git a/libs/vm_launcher_lib/Android.bp b/libs/vm_launcher_lib/Android.bp
deleted file mode 100644
index 7dced4e..0000000
--- a/libs/vm_launcher_lib/Android.bp
+++ /dev/null
@@ -1,23 +0,0 @@
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-java_library {
- name: "vm_launcher_lib",
- srcs: ["java/**/*.java"],
- apex_available: [
- "//apex_available:platform",
- "com.android.virt",
- ],
- platform_apis: true,
- static_libs: [
- "gson",
- "debian-service-grpclib-lite",
- "apache-commons-compress",
- ],
- libs: [
- "androidx.annotation_annotation",
- "framework-virtualization.impl",
- "framework-annotations-lib",
- ],
-}
diff --git a/microfuchsia/microfuchsiad/Android.bp b/microfuchsia/microfuchsiad/Android.bp
index ddf360d..2c2d2f2 100644
--- a/microfuchsia/microfuchsiad/Android.bp
+++ b/microfuchsia/microfuchsiad/Android.bp
@@ -19,6 +19,13 @@
"liblibc",
"libvmclient",
],
+ cfgs: [
+ // Enable this to configure microfuchsia VM instances with an interactive serial console. This console can
+ // be attached to using the 'vm console' command.
+ // Warning - enabling this will cause the VM to stall out unless a console is attached.
+ // See b/379163126 for details.
+ // "enable_console",
+ ],
apex_available: [
"com.android.microfuchsia",
],
diff --git a/microfuchsia/microfuchsiad/src/instance_starter.rs b/microfuchsia/microfuchsiad/src/instance_starter.rs
index 61a024f..8216039 100644
--- a/microfuchsia/microfuchsiad/src/instance_starter.rs
+++ b/microfuchsia/microfuchsiad/src/instance_starter.rs
@@ -31,7 +31,7 @@
pub struct MicrofuchsiaInstance {
_vm_instance: VmInstance,
_lazy_service_guard: LazyServiceGuard,
- _pty: Pty,
+ _pty: Option<Pty>,
}
pub struct InstanceStarter {
@@ -64,10 +64,14 @@
let initrd = Some(ParcelFileDescriptor::new(initrd_fd));
// Prepare a pty for console input/output.
- let pty = openpty()?;
- let console_in = Some(pty.leader.try_clone().context("cloning pty")?);
- let console_out = Some(pty.leader.try_clone().context("cloning pty")?);
-
+ let (pty, console_in, console_out) = if cfg!(enable_console) {
+ let pty = openpty()?;
+ let console_in = Some(pty.leader.try_clone().context("cloning pty")?);
+ let console_out = Some(pty.leader.try_clone().context("cloning pty")?);
+ (Some(pty), console_in, console_out)
+ } else {
+ (None, None, None)
+ };
let config = VirtualMachineConfig::RawConfig(VirtualMachineRawConfig {
name: "Microfuchsia".into(),
instanceId: instance_id,
@@ -80,7 +84,7 @@
memoryMib: 256,
cpuTopology: CpuTopology::ONE_CPU,
platformVersion: "1.0.0".into(),
- // Fuchsia uses serial for console by default.
+ #[cfg(enable_console)]
consoleInputDevice: Some("ttyS0".into()),
..Default::default()
});
@@ -94,10 +98,12 @@
None,
)
.context("Failed to create VM")?;
- vm_instance
- .vm
- .setHostConsoleName(&pty.follower_name)
- .context("Setting host console name")?;
+ if let Some(pty) = &pty {
+ vm_instance
+ .vm
+ .setHostConsoleName(&pty.follower_name)
+ .context("Setting host console name")?;
+ }
vm_instance.start().context("Starting VM")?;
Ok(MicrofuchsiaInstance {
diff --git a/tests/Terminal/src/com/android/virtualization/terminal/TerminalAppTest.java b/tests/Terminal/src/com/android/virtualization/terminal/TerminalAppTest.java
index fd07973..3c0461d 100644
--- a/tests/Terminal/src/com/android/virtualization/terminal/TerminalAppTest.java
+++ b/tests/Terminal/src/com/android/virtualization/terminal/TerminalAppTest.java
@@ -27,7 +27,6 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.microdroid.test.common.MetricsProcessor;
-import com.android.virtualization.vmlauncher.InstallUtils;
import org.junit.After;
import org.junit.Before;
diff --git a/tests/ferrochrome/Android.bp b/tests/ferrochrome/Android.bp
deleted file mode 100644
index f1b7f27..0000000
--- a/tests/ferrochrome/Android.bp
+++ /dev/null
@@ -1,28 +0,0 @@
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-sh_test_host {
- name: "ferrochrome-tests",
- src: ":ferrochrome-tests.sh",
- test_suites: ["general-tests"],
- test_options: {
- unit_test: false,
- },
- per_testcase_directory: true,
- data: ["assets/vm_config.json"],
- data_bins: ["ferrochrome-precondition-checker.sh"],
-}
-
-// Workaround for enabling verbose logging only on CI
-genrule {
- name: "ferrochrome-tests.sh",
- srcs: ["ferrochrome.sh"],
- out: ["ferrochrome-tests"],
- cmd: "sed '2 i set -x' $(in) > $(out)",
-}
-
-sh_binary_host {
- name: "ferrochrome-precondition-checker.sh",
- src: "ferrochrome-precondition-checker.sh",
-}
diff --git a/tests/ferrochrome/AndroidTest.xml b/tests/ferrochrome/AndroidTest.xml
deleted file mode 100644
index 6c975be..0000000
--- a/tests/ferrochrome/AndroidTest.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 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.
--->
-<configuration description="Host driven tests for ferrochrome">
- <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
- <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
- <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-
- <!-- 'adb root' to enable vmlauncher -->
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
- <option name="force-root" value="true"/>
- </target_preparer>
-
- <!-- Check assumptions here, because we can't skip tests in shell test -->
- <target_preparer class="com.android.tradefed.targetprep.RunHostScriptTargetPreparer">
- <option name="script-file" value="ferrochrome-precondition-checker.sh" />
- </target_preparer>
-
- <!-- Explicitly clean up ferrochrome image when done.
- It's too large (6.5G+), so this may break further tests. -->
- <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <option name="throw-if-cmd-fail" value="false" />
- <option name="run-command" value="mkdir /data/local/tmp" />
- <option name="teardown-command" value="pkill vmlauncher" />
- <option name="teardown-command" value="rm /data/local/tmp/chromiumos_base_image.bin" />
- <option name="teardown-command" value="rm -rf /data/local/tmp/ferrochrome_screenshots" />
- </target_preparer>
-
- <test class="com.android.tradefed.testtype.binary.ExecutableHostTest">
- <option name="binary" value="ferrochrome-tests" />
- <option name="relative-path-execution" value="true" />
- <option name="runtime-hint" value="10m" />
- <option name="per-binary-timeout" value="20m" />
- </test>
-
- <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
- <option name="directory-keys" value="/data/local/tmp/ferrochrome_screenshots" />
- <option name="collect-on-run-ended-only" value="true" />
- </metrics_collector>
-</configuration>
-
diff --git a/tests/ferrochrome/assets/vm_config.json b/tests/ferrochrome/assets/vm_config.json
deleted file mode 100644
index 53e3b72..0000000
--- a/tests/ferrochrome/assets/vm_config.json
+++ /dev/null
@@ -1,38 +0,0 @@
-{
- "name": "cros",
- "disks": [
- {
- "image": "/data/local/tmp/chromiumos_base_image.bin",
- "partitions": [],
- "writable": true
- }
- ],
- "protected": false,
- "cpu_topology": "match_host",
- "platform_version": "~1.0",
- "memory_mib": 8096,
- "debuggable": true,
- "console_out": true,
- "connect_console": true,
- "console_input_device": "hvc0",
- "network": true,
- "input": {
- "touchscreen": true,
- "keyboard": true,
- "mouse": true,
- "trackpad": true,
- "switches": true
- },
- "audio": {
- "speaker": true,
- "microphone": true
- },
- "gpu": {
- "backend": "virglrenderer",
- "context_types": ["virgl2"]
- },
- "display": {
- "scale": "0.77",
- "refresh_rate": "30"
- }
-}
diff --git a/tests/ferrochrome/ferrochrome-precondition-checker.sh b/tests/ferrochrome/ferrochrome-precondition-checker.sh
deleted file mode 100644
index d3f7f5a..0000000
--- a/tests/ferrochrome/ferrochrome-precondition-checker.sh
+++ /dev/null
@@ -1,58 +0,0 @@
-#!/bin/bash
-
-# Copyright 2024 Google Inc. All rights reserved.
-#
-# 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.
-
-
-## Precondition checks for running ferrochrome
-## Used by CI for skipping tests.
-
-REQUIRED_DISK_SPACE=7340032 # Requires 7G, while image is 6.5G
-
-# `adb root` always returns exit code 0
-if [[ "$(adb root)" == *"cannot"* ]]; then
- >&2 echo "Failed to run adb root"
- exit 1
-fi
-
-# `pm resolve-activity` always returns exit code 0
-resolved_activity=$(adb shell pm resolve-activity -a android.virtualization.VM_LAUNCHER)
-if [[ "${resolved_activity}" == "No activity found" ]]; then
- >&2 echo "Failed to find vmlauncher"
- exit 1
-fi
-
-free_space=$(adb shell df /data/local | tail -1 | awk '{print $4}')
-if [[ ${free_space} -lt ${REQUIRED_DISK_SPACE} ]]; then
- >&2 echo "Insufficient space on DUT. Need ${REQUIRED_DISK_SPACE}, but was ${free_space}"
- exit 1
-fi
-
-free_space=$(df /tmp | tail -1 | awk '{print $4}')
-if [[ ${free_space} -lt ${REQUIRED_DISK_SPACE} ]]; then
- >&2 echo "Insufficient space on host. Need ${REQUIRED_DISK_SPACE}, but was ${free_space}"
- exit 1
-fi
-
-cpu_abi=$(adb shell getprop ro.product.cpu.abi)
-if [[ "${cpu_abi}" != "arm64"* ]]; then
- >&2 echo "Unsupported architecture. Requires arm64, but was ${cpu_abi}"
- exit 1
-fi
-
-device=$(adb shell getprop ro.product.vendor.device)
-if [[ "${device}" == "vsock_"* ]]; then
- >&2 echo "Unsupported device. Cuttlefish isn't supported"
- exit 1
-fi
diff --git a/tests/ferrochrome/ferrochrome.sh b/tests/ferrochrome/ferrochrome.sh
deleted file mode 100755
index 03630dd..0000000
--- a/tests/ferrochrome/ferrochrome.sh
+++ /dev/null
@@ -1,192 +0,0 @@
-#!/bin/bash
-
-# Copyright 2024 Google Inc. All rights reserved.
-#
-# 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.
-
-## Booting tests for ferrochrome
-## Keep this file synced with docs/custom_vm.md
-
-set -e
-
-FECR_GS_URL="https://storage.googleapis.com/chromiumos-image-archive/ferrochrome-public"
-FECR_DEFAULT_VERSION="R128-15958.0.0"
-FECR_DEFAULT_SCREENSHOT_DIR="/data/local/tmp/ferrochrome_screenshots" # Hardcoded at AndroidTest.xml
-FECR_TEST_IMAGE="chromiumos_test_image"
-FECR_BASE_IMAGE="chromiumos_base_image"
-FECR_DEVICE_DIR="/data/local/tmp"
-FECR_IMAGE_VM_CONFIG_JSON="chromiumos_base_image.bin" # hardcoded at vm_config.json
-FECR_CONFIG_PATH="/data/local/tmp/vm_config.json" # hardcoded at VmLauncherApp
-FECR_CONSOLE_LOG_PATH="files/cros.log" # log file name is ${vm_name}.log
-FECR_TEST_IMAGE_BOOT_COMPLETED_LOG="Have fun and send patches!"
-FECR_BASE_IMAGE_BOOT_COMPLETED_LOG="Chrome started, our work is done, exiting"
-FECR_BOOT_TIMEOUT="300" # 5 minutes (300 seconds)
-ACTION_NAME="android.virtualization.VM_LAUNCHER"
-
-# Match this with AndroidTest.xml and assets/vm_config.json
-FECR_DEFAULT_IMAGE="${FECR_BASE_IMAGE}"
-FECR_DEFAULT_BOOT_COMPLETED_LOG="${FECR_BASE_IMAGE_BOOT_COMPLETED_LOG}"
-
-fecr_clean_up() {
- trap - INT
-
- # Reset screen always on
- adb shell svc power stayon false
-
- if [[ -d ${fecr_dir} && -z ${fecr_keep} ]]; then
- rm -rf ${fecr_dir}
- fi
-}
-
-print_usage() {
- echo "ferochrome: Launches ferrochrome image"
- echo ""
- echo "By default, this downloads ${FECR_DEFAULT_VERSION} with version ${FECR_DEFAULT_VERSION},"
- echo "launches, and waits for boot completed."
- echo "When done, removes downloaded image on host while keeping pushed image on device."
- echo ""
- echo "Usage: ferrochrome [options]"
- echo ""
- echo "Options"
- echo " --help or -h: This message"
- echo " --dir DIR: Use ferrochrome images at the dir instead of downloading"
- echo " --verbose: Verbose log message (set -x)"
- echo " --skip: Skipping downloading and/or pushing images"
- echo " --version \${version}: ferrochrome version to be downloaded"
- echo " --keep: Keep downloaded ferrochrome image"
- echo " --test: Download test image instead"
- echo " --forever: Keep ferrochrome running forever. Used for manual test"
-}
-
-fecr_version="${FECR_DEFAULT_VERSION}"
-fecr_dir=""
-fecr_keep=""
-fecr_skip=""
-fecr_script_path=$(dirname ${0})
-fecr_verbose=""
-fecr_image="${FECR_DEFAULT_IMAGE}"
-fecr_boot_completed_log="${FECR_DEFAULT_BOOT_COMPLETED_LOG}"
-fecr_screenshot_dir="${FECR_DEFAULT_SCREENSHOT_DIR}"
-fecr_forever=""
-
-# Parse parameters
-while (( "${#}" )); do
- case "${1}" in
- --verbose)
- fecr_verbose="true"
- ;;
- --version)
- shift
- fecr_version="${1}"
- ;;
- --dir)
- shift
- fecr_dir="${1}"
- fecr_keep="true"
- ;;
- --keep)
- fecr_keep="true"
- ;;
- --skip)
- fecr_skip="true"
- ;;
- --test)
- fecr_image="${FECR_TEST_IMAGE}"
- fecr_boot_completed_log="${FECR_TEST_IMAGE_BOOT_COMPLETED_LOG}"
- ;;
- --forever)
- fecr_forever="true"
- ;;
- -h|--help)
- print_usage
- exit 0
- ;;
- *)
- print_usage
- exit 1
- ;;
- esac
- shift
-done
-
-trap fecr_clean_up INT
-trap fecr_clean_up EXIT
-
-if [[ -n "${fecr_verbose}" ]]; then
- set -x
-fi
-
-. "${fecr_script_path}/ferrochrome-precondition-checker.sh"
-
-resolved_activities=$(adb shell pm query-activities --components -a ${ACTION_NAME})
-
-if [[ "$(echo ${resolved_activities} | wc -l)" != "1" ]]; then
- >&2 echo "Multiple VM launchers exists"
- exit 1
-fi
-
-pkg_name=$(dirname ${resolved_activities})
-current_user=$(adb shell am get-current-user)
-
-echo "Reset app & granting permission"
-adb shell pm clear --user ${current_user} ${pkg_name} > /dev/null
-adb shell pm grant --user ${current_user} ${pkg_name} android.permission.RECORD_AUDIO
-adb shell pm grant --user ${current_user} ${pkg_name} android.permission.USE_CUSTOM_VIRTUAL_MACHINE > /dev/null
-
-if [[ -z "${fecr_skip}" ]]; then
- if [[ -z "${fecr_dir}" ]]; then
- # Download fecr image archive, and extract necessary files
- # DISCLAIMER: Image is too large (1.5G+ for compressed, 6.5G+ for uncompressed), so can't submit.
- fecr_dir=$(mktemp -d)
-
- echo "Downloading & extracting ferrochrome image to ${fecr_dir}"
- curl ${FECR_GS_URL}/${fecr_version}/${fecr_image}.tar.xz | tar xfJ - -C ${fecr_dir}
- fi
-
- echo "Pushing ferrochrome image to ${FECR_DEVICE_DIR}"
- adb shell mkdir -p ${FECR_DEVICE_DIR} > /dev/null || true
- adb push ${fecr_dir}/${fecr_image}.bin ${FECR_DEVICE_DIR}/${FECR_IMAGE_VM_CONFIG_JSON}
- adb push ${fecr_script_path}/assets/vm_config.json ${FECR_CONFIG_PATH}
-fi
-
-echo "Ensure screen unlocked"
-adb shell svc power stayon true
-adb shell wm dismiss-keyguard
-
-echo "Starting ferrochrome"
-adb shell am start-activity -a ${ACTION_NAME} > /dev/null
-
-# HSUM aware log path
-log_path="/data/user/${current_user}/${pkg_name}/${FECR_CONSOLE_LOG_PATH}"
-fecr_start_time=${EPOCHSECONDS}
-
-echo "Check ${log_path} on device for console log"
-
-if [[ "${fecr_forever}" == "true" ]]; then
- echo "Ctrl+C to stop running"
- echo "To open interactive serial console, use following command:"
- echo "adb shell -t /apex/com.android.virt/bin/vm console"
-else
- adb shell mkdir -p "${fecr_screenshot_dir}"
- while [[ $((EPOCHSECONDS - fecr_start_time)) -lt ${FECR_BOOT_TIMEOUT} ]]; do
- adb shell screencap -p "${fecr_screenshot_dir}/screenshot-${EPOCHSECONDS}.png"
- adb shell grep -soF \""${fecr_boot_completed_log}"\" "${log_path}" && exit 0 || true
- sleep 10
- done
-
- >&2 echo "Ferrochrome failed to boot. Dumping console log"
- >&2 adb shell cat ${log_path}
-
- exit 1
-fi
-
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 03d7fef..fefedc9 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -93,6 +93,8 @@
private static final String APK_NAME = "MicrodroidTestApp.apk";
private static final String APK_UPDATED_NAME = "MicrodroidTestAppUpdated.apk";
private static final String PACKAGE_NAME = "com.android.microdroid.test";
+ private static final String EMPTY_AOSP_PACKAGE_NAME = "com.android.microdroid.empty_payload";
+ private static final String EMPTY_PACKAGE_NAME = "com.google.android.microdroid.empty_payload";
private static final String SHELL_PACKAGE_NAME = "com.android.shell";
private static final String VIRT_APEX = "/apex/com.android.virt/";
private static final String INSTANCE_IMG = TEST_ROOT + "instance.img";
@@ -1130,6 +1132,70 @@
assertThat(ret).contains("Payload binary name must not specify a path");
}
+ private boolean hasAppPackage(String pkgName, CommandRunner android) throws DeviceNotAvailableException {
+ String hasPackage =
+ android.run(
+ "pm list package | grep -w " + pkgName + " 1> /dev/null" + "; echo $?");
+ if (hasPackage.equals("0")) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Test
+ public void testRunEmptyPayload() throws Exception {
+ CommandRunner android = new CommandRunner(getDevice());
+
+ // Create the idsig file for the APK
+ String apkPath;
+ if (hasAppPackage(EMPTY_AOSP_PACKAGE_NAME, android))
+ apkPath = getPathForPackage(EMPTY_AOSP_PACKAGE_NAME);
+ else
+ apkPath = getPathForPackage(EMPTY_PACKAGE_NAME);
+
+ final String idSigPath = TEST_ROOT + "idsig";
+ final String instanceImgPath = TEST_ROOT + "instance.img";
+
+ android.run(VIRT_APEX + "bin/vm", "create-idsig", apkPath, idSigPath);
+
+ List<String> cmd =
+ new ArrayList<>(
+ Arrays.asList(
+ "adb",
+ "-s",
+ getDevice().getSerialNumber(),
+ "shell",
+ VIRT_APEX + "bin/vm",
+ "run-app",
+ "--debug full",
+ "--console " + CONSOLE_PATH,
+ "--payload-binary-name",
+ "MicrodroidEmptyPayloadJniLib.so",
+ apkPath,
+ idSigPath,
+ instanceImgPath));
+ if (isFeatureEnabled("com.android.kvm.LLPVM_CHANGES")) {
+ cmd.add("--instance-id-file");
+ cmd.add(TEST_ROOT + "instance_id");
+ }
+
+ PipedInputStream pis = new PipedInputStream();
+ Process process = createRunUtil().runCmdInBackground(cmd, new PipedOutputStream(pis));
+ String bufferedInput = "";
+
+ do {
+ byte[] pipeBuffer = new byte[4096];
+ pis.read(pipeBuffer, 0, 4096);
+ bufferedInput += new String(pipeBuffer);
+ } while (!bufferedInput.contains("payload is ready"));
+
+ String consoleLog = getDevice().pullFileContents(CONSOLE_PATH);
+ assertThat(consoleLog).contains("Hello Microdroid");
+
+ process.destroy();
+ }
+
@Test
@CddTest(requirements = {"9.17/C-2-2", "9.17/C-2-6"})
public void testAllVbmetaUseSHA256() throws Exception {