Merge "vmbase: uart: Move asm block to crate::arch" into main
diff --git a/android/TerminalApp/Android.bp b/android/TerminalApp/Android.bp
index e5e8b0a..932ca76 100644
--- a/android/TerminalApp/Android.bp
+++ b/android/TerminalApp/Android.bp
@@ -26,15 +26,3 @@
"com.android.virt",
],
}
-
-filegroup {
- name: "linux_vm_setup.rc",
- srcs: ["linux_vm_setup.rc"],
-}
-
-sh_binary {
- name: "linux_vm_setup",
- src: "linux_vm_setup.sh",
- init_rc: [":linux_vm_setup.rc"],
- host_supported: false,
-}
diff --git a/android/TerminalApp/AndroidManifest.xml b/android/TerminalApp/AndroidManifest.xml
index 105e454..28b5436 100644
--- a/android/TerminalApp/AndroidManifest.xml
+++ b/android/TerminalApp/AndroidManifest.xml
@@ -9,6 +9,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-feature android:name="android.software.virtualization_framework" android:required="true" />
diff --git a/android/TerminalApp/generate_assets.sh b/android/TerminalApp/generate_assets.sh
deleted file mode 100755
index 4001bfd..0000000
--- a/android/TerminalApp/generate_assets.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-set -e
-
-if [ "$#" -ne 1 ]; then
- echo "$0 <image.raw path>"
- echo "image.raw can be built with packages/modules/Virtualization/build/debian/build.sh"
- exit 1
-fi
-image_raw_path=$(realpath $1)
-pushd $(dirname $0) > /dev/null
-tempdir=$(mktemp -d)
-asset_dir=./assets/linux
-mkdir -p ${asset_dir}
-echo Copy files...
-pushd ${tempdir} > /dev/null
-cp "${image_raw_path}" ${tempdir}
-tar czvS -f images.tar.gz $(basename ${image_raw_path})
-popd > /dev/null
-cp vm_config.json ${asset_dir}
-mv ${tempdir}/images.tar.gz ${asset_dir}
-echo Calculating hash...
-hash=$(cat ${asset_dir}/images.tar.gz ${asset_dir}/vm_config.json | sha1sum | cut -d' ' -f 1)
-echo ${hash} > ${asset_dir}/hash
-popd > /dev/null
-echo Cleaning up...
-rm -rf ${tempdir}
-
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
index 1c739e2..a49ea72 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
@@ -16,37 +16,20 @@
package com.android.virtualization.terminal;
-import android.annotation.WorkerThread;
import android.app.Activity;
+import android.os.Build;
import android.os.Bundle;
-import android.os.Environment;
-import android.os.SystemProperties;
-import android.text.TextUtils;
import android.util.Log;
import android.widget.TextView;
-import libcore.io.Streams;
+import com.android.virtualization.vmlauncher.InstallUtils;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class InstallerActivity extends Activity {
private static final String TAG = "LinuxInstaller";
- private static final Path DEST_DIR =
- Path.of(Environment.getExternalStorageDirectory().getPath(), "linux");
-
- private static final String ASSET_DIR = "linux";
- private static final String HASH_FILE_NAME = "hash";
- private static final Path HASH_FILE = Path.of(DEST_DIR.toString(), HASH_FILE_NAME);
-
ExecutorService executorService = Executors.newSingleThreadExecutor();
@Override
@@ -60,115 +43,24 @@
}
private void installLinuxImage() {
- if (!hasLocalAssets()) {
- updateStatus("No local assets");
- setResult(RESULT_CANCELED, null);
- return;
+ Log.d(TAG, "installLinuxImage");
+ // Installing from sdcard is supported only in debuggable build.
+ if (Build.isDebuggable()) {
+ updateStatus("try /sdcard/linux/images.tar.gz");
+ if (InstallUtils.installImageFromExternalStorage(this)) {
+ Log.d(TAG, "success / sdcard");
+ updateStatus("image is installed from /sdcard/linux/images.tar.gz");
+ setResult(RESULT_OK);
+ finish();
+ return;
+ }
+ Log.d(TAG, "fail / sdcard");
+ updateStatus("There is no /sdcard/linux/images.tar.gz");
}
- try {
- updateImageIfNeeded();
- } catch (IOException e) {
- Log.e(TAG, "failed to update image", e);
- return;
- }
- updateStatus("Done.");
- setResult(RESULT_OK);
+ setResult(RESULT_CANCELED, null);
finish();
}
- @WorkerThread
- private boolean hasLocalAssets() {
- try {
- String[] files = getAssets().list(ASSET_DIR);
- return files != null && files.length > 0;
- } catch (IOException e) {
- Log.e(TAG, "there is an error during listing up assets", e);
- return false;
- }
- }
-
- @WorkerThread
- private void updateImageIfNeeded() throws IOException {
- if (!isUpdateNeeded()) {
- Log.d(TAG, "No update needed.");
- return;
- }
-
- try {
- if (Files.notExists(DEST_DIR)) {
- Files.createDirectory(DEST_DIR);
- }
-
- updateStatus("Copying images...");
- String[] files = getAssets().list(ASSET_DIR);
- for (String file : files) {
- updateStatus(file);
- Path dst = Path.of(DEST_DIR.toString(), file);
- updateFile(getAssets().open(ASSET_DIR + "/" + file), dst);
- }
- } catch (IOException e) {
- Log.e(TAG, "Error while updating image: " + e);
- updateStatus("Failed to update image.");
- throw e;
- }
- extractImages(DEST_DIR.toAbsolutePath().toString());
- }
-
- @WorkerThread
- private void extractImages(String destDir) throws IOException {
- updateStatus("Extracting images...");
-
- if (TextUtils.isEmpty(destDir)) {
- throw new RuntimeException("Internal error: destDir shouldn't be null");
- }
-
- SystemProperties.set("debug.linux_vm_setup.path", destDir);
- SystemProperties.set("debug.linux_vm_setup.done", "false");
- SystemProperties.set("debug.linux_vm_setup.start", "true");
- while (!SystemProperties.getBoolean("debug.linux_vm_setup.done", false)) {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- Log.e(TAG, "Error while extracting image: " + e);
- updateStatus("Failed to extract image.");
- throw new IOException("extracting image is interrupted", e);
- }
- }
- }
-
- @WorkerThread
- private boolean isUpdateNeeded() {
- Path[] pathsToCheck = {DEST_DIR, HASH_FILE};
- for (Path p : pathsToCheck) {
- if (Files.notExists(p)) {
- Log.d(TAG, p.toString() + " does not exist.");
- return true;
- }
- }
-
- try {
- String installedHash = readAll(new FileInputStream(HASH_FILE.toFile()));
- String updatedHash = readAll(getAssets().open(ASSET_DIR + "/" + HASH_FILE_NAME));
- if (installedHash.equals(updatedHash)) {
- return false;
- }
- Log.d(TAG, "Hash mismatch. Installed: " + installedHash + " Updated: " + updatedHash);
- } catch (IOException e) {
- Log.e(TAG, "Error while checking hash: " + e);
- }
- return true;
- }
-
- private static String readAll(InputStream input) throws IOException {
- return Streams.readFully(new InputStreamReader(input)).strip();
- }
-
- private static void updateFile(InputStream input, Path path) throws IOException {
- try (input) {
- Files.copy(input, path, StandardCopyOption.REPLACE_EXISTING);
- }
- }
-
private void updateStatus(String line) {
runOnUiThread(
() -> {
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index 612da12..d71a17c 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -15,11 +15,17 @@
*/
package com.android.virtualization.terminal;
-import android.content.ClipData;
-import android.content.ClipboardManager;
+import android.Manifest;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
import android.net.http.SslError;
+import android.os.Build;
import android.os.Bundle;
import android.system.ErrnoException;
import android.system.Os;
@@ -39,6 +45,7 @@
import androidx.appcompat.app.AppCompatActivity;
+import com.android.virtualization.vmlauncher.InstallUtils;
import com.android.virtualization.vmlauncher.VmLauncherServices;
import com.google.android.material.appbar.MaterialToolbar;
@@ -72,12 +79,13 @@
private PrivateKey mPrivateKey;
private WebView mWebView;
private AccessibilityManager mAccessibilityManager;
+ private static final int POST_NOTIFICATIONS_PERMISSION_REQUEST_CODE = 101;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- checkForUpdate();
+ boolean launchInstaller = installIfNecessary();
try {
// No resize for now.
long newSizeInBytes = 0;
@@ -88,6 +96,14 @@
.show();
}
+ checkAndRequestPostNotificationsPermission();
+
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ NotificationChannel notificationChannel =
+ new NotificationChannel(TAG, TAG, NotificationManager.IMPORTANCE_LOW);
+ assert notificationManager != null;
+ notificationManager.createNotificationChannel(notificationChannel);
+
setContentView(R.layout.activity_headless);
MaterialToolbar toolbar = (MaterialToolbar) findViewById(R.id.toolbar);
@@ -103,6 +119,11 @@
connectToTerminalService();
readClientCertificate();
+
+ // if installer is launched, it will be handled in onActivityResult
+ if (!launchInstaller) {
+ startVm();
+ }
}
private URL getTerminalServiceUrl() {
@@ -306,6 +327,15 @@
return;
}
+ private void checkAndRequestPostNotificationsPermission() {
+ if (getApplicationContext().checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS)
+ != PackageManager.PERMISSION_GRANTED) {
+ requestPermissions(
+ new String[]{Manifest.permission.POST_NOTIFICATIONS},
+ POST_NOTIFICATIONS_PERMISSION_REQUEST_CODE);
+ }
+ }
+
@Override
protected void onDestroy() {
getSystemService(AccessibilityManager.class).removeTouchExplorationStateChangeListener(this);
@@ -346,17 +376,7 @@
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
- if (id == R.id.copy_ip_addr) {
- // TODO(b/340126051): remove this menu item when port forwarding is supported.
- getSystemService(ClipboardManager.class)
- .setPrimaryClip(ClipData.newPlainText("A VM's IP address", VM_ADDR));
- return true;
- } else if (id == R.id.stop_vm) {
- VmLauncherServices.stopVmLauncherService(this);
- mWebView.setVisibility(View.INVISIBLE);
- return true;
-
- } else if (id == R.id.menu_item_settings) {
+ if (id == R.id.menu_item_settings) {
Intent intent = new Intent(this, SettingsActivity.class);
this.startActivity(intent);
return true;
@@ -382,13 +402,45 @@
}
}
- private void checkForUpdate() {
- Intent intent = new Intent(this, InstallerActivity.class);
- startActivityForResult(intent, REQUEST_CODE_INSTALLER);
+ private boolean installIfNecessary() {
+ // If payload from external storage exists(only for debuggable build) or there is no
+ // installed image, launch installer activity.
+ if ((Build.isDebuggable() && InstallUtils.payloadFromExternalStorageExists())
+ || !InstallUtils.isImageInstalled(this)) {
+ Intent intent = new Intent(this, InstallerActivity.class);
+ startActivityForResult(intent, REQUEST_CODE_INSTALLER);
+ return true;
+ }
+ return false;
}
private void startVm() {
+ if (!InstallUtils.isImageInstalled(this)) {
+ return;
+ }
+ // TODO: implement intent for setting, close and tap to the notification
+ // Currently mock a PendingIntent for notification.
+ Intent intent = new Intent();
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+
+ Icon icon = Icon.createWithResource(getResources(), R.drawable.ic_launcher_foreground);
+ Notification notification = new Notification.Builder(this, TAG)
+ .setChannelId(TAG)
+ .setSmallIcon(R.drawable.ic_launcher_foreground)
+ .setContentTitle(getResources().getString(R.string.service_notification_title))
+ .setContentText(getResources().getString(R.string.service_notification_content))
+ .setContentIntent(pendingIntent)
+ .setOngoing(true)
+ .addAction(new Notification.Action.Builder(icon,
+ getResources().getString(R.string.service_notification_settings),
+ pendingIntent).build())
+ .addAction(new Notification.Action.Builder(icon,
+ getResources().getString(R.string.service_notification_quit_action),
+ pendingIntent).build())
+ .build();
+
android.os.Trace.beginAsyncSection("executeTerminal", 0);
- VmLauncherServices.startVmLauncherService(this, this);
+ VmLauncherServices.startVmLauncherService(this, this, notification);
}
}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt
index 6c36cc8..7119225 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsPortForwardingActivity.kt
@@ -15,12 +15,21 @@
*/
package com.android.virtualization.terminal
+import android.Manifest
+import android.app.Notification
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.drawable.Icon
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.app.ActivityCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class SettingsPortForwardingActivity : AppCompatActivity() {
+ val TAG: String = "VmTerminalApp"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.settings_port_forwarding)
@@ -37,5 +46,43 @@
val recyclerView: RecyclerView = findViewById(R.id.settings_port_forwarding_recycler_view)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = settingsPortForwardingAdapter
+
+ // TODO: implement intent for accept, deny and tap to the notification
+ // Currently show a mock notification of a port opening
+ val terminalIntent = Intent()
+ val pendingIntent = PendingIntent.getActivity(
+ this, 0, terminalIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ val notification =
+ Notification.Builder(this, TAG)
+ .setChannelId(TAG)
+ .setSmallIcon(R.drawable.ic_launcher_foreground)
+ .setContentTitle(resources.getString(R.string.settings_port_forwarding_notification_title))
+ .setContentText(resources.getString(R.string.settings_port_forwarding_notification_content, settingsPortForwardingItems[0].port))
+ .addAction(
+ Notification.Action.Builder(
+ Icon.createWithResource(resources, R.drawable.ic_launcher_foreground),
+ resources.getString(R.string.settings_port_forwarding_notification_accept),
+ pendingIntent
+ ).build()
+ )
+ .addAction(
+ Notification.Action.Builder(
+ Icon.createWithResource(resources, R.drawable.ic_launcher_foreground),
+ resources.getString(R.string.settings_port_forwarding_notification_deny),
+ pendingIntent
+ ).build()
+ )
+ .build()
+
+ with(NotificationManager.from(this)) {
+ if (ActivityCompat.checkSelfPermission(
+ this@SettingsPortForwardingActivity, Manifest.permission.POST_NOTIFICATIONS
+ ) == PackageManager.PERMISSION_GRANTED
+ ) {
+ notify(0, notification)
+ }
+ }
}
}
\ No newline at end of file
diff --git a/android/TerminalApp/linux_vm_setup.rc b/android/TerminalApp/linux_vm_setup.rc
deleted file mode 100644
index ac91532..0000000
--- a/android/TerminalApp/linux_vm_setup.rc
+++ /dev/null
@@ -1,23 +0,0 @@
-# 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.
-
-service linux_vm_setup /system/bin/linux_vm_setup
- user shell
- group shell media_rw
- disabled
- oneshot
- seclabel u:r:shell:s0
-
-on property:debug.linux_vm_setup.start=true
- start linux_vm_setup
diff --git a/android/TerminalApp/linux_vm_setup.sh b/android/TerminalApp/linux_vm_setup.sh
deleted file mode 100644
index 6a93f6f..0000000
--- a/android/TerminalApp/linux_vm_setup.sh
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/system/bin/sh
-
-function round_up() {
- num=$1
- div=$2
- echo $((( (( ${num} / ${div} ) + 1) * ${div} )))
-}
-
-function install() {
- src_dir=$(getprop debug.linux_vm_setup.path)
- src_dir=${src_dir/#\/storage\/emulated\//\/data\/media\/}
- dst_dir=/data/local/tmp/
-
- cat $(find ${src_dir} -name "images.tar.gz*" | sort) | tar xz -C ${dst_dir}
- cp -u ${src_dir}/vm_config.json ${dst_dir}
- chmod 666 ${dst_dir}/*
-
- if [ -f ${dst_dir}state.img ]; then
- # increase the size of state.img to the multiple of 4096
- num_blocks=$(du -b -K ${dst_dir}state.img | cut -f 1)
- required_num_blocks=$(round_up ${num_blocks} 4)
- additional_blocks=$((( ${required_num_blocks} - ${num_blocks} )))
- dd if=/dev/zero bs=512 count=${additional_blocks} >> ${dst_dir}state.img
- fi
- rm ${src_dir}/images.tar.gz*
- rm ${src_dir}/vm_config.json
-}
-
-setprop debug.linux_vm_setup.done false
-install
-setprop debug.linux_vm_setup.start false
-setprop debug.linux_vm_setup.done true
diff --git a/android/TerminalApp/res/drawable/ic_settings.xml b/android/TerminalApp/res/drawable/ic_settings.xml
new file mode 100644
index 0000000..4bcd4aa
--- /dev/null
+++ b/android/TerminalApp/res/drawable/ic_settings.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M370,880L354,752Q341,747 329.5,740Q318,733 307,725L188,775L78,585L181,507Q180,500 180,493.5Q180,487 180,480Q180,473 180,466.5Q180,460 181,453L78,375L188,185L307,235Q318,227 330,220Q342,213 354,208L370,80L590,80L606,208Q619,213 630.5,220Q642,227 653,235L772,185L882,375L779,453Q780,460 780,466.5Q780,473 780,480Q780,487 780,493.5Q780,500 778,507L881,585L771,775L653,725Q642,733 630,740Q618,747 606,752L590,880L370,880ZM440,800L519,800L533,694Q564,686 590.5,670.5Q617,655 639,633L738,674L777,606L691,541Q696,527 698,511.5Q700,496 700,480Q700,464 698,448.5Q696,433 691,419L777,354L738,286L639,328Q617,305 590.5,289.5Q564,274 533,266L520,160L441,160L427,266Q396,274 369.5,289.5Q343,305 321,327L222,286L183,354L269,418Q264,433 262,448Q260,463 260,480Q260,496 262,511Q264,526 269,541L183,606L222,674L321,632Q343,655 369.5,670.5Q396,686 427,694L440,800ZM482,620Q540,620 581,579Q622,538 622,480Q622,422 581,381Q540,340 482,340Q423,340 382.5,381Q342,422 342,480Q342,538 382.5,579Q423,620 482,620ZM480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Z"/>
+</vector>
diff --git a/android/TerminalApp/res/layout/activity_headless.xml b/android/TerminalApp/res/layout/activity_headless.xml
index 9211799..7baaf5c 100644
--- a/android/TerminalApp/res/layout/activity_headless.xml
+++ b/android/TerminalApp/res/layout/activity_headless.xml
@@ -21,12 +21,13 @@
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
- <TextView
+ <com.google.android.material.textview.MaterialTextView
android:text="@string/vm_creation_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"/>
- <ProgressBar
+ <com.google.android.material.progressindicator.CircularProgressIndicator
+ android:indeterminate="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
diff --git a/android/TerminalApp/res/layout/settings_activity.xml b/android/TerminalApp/res/layout/settings_activity.xml
index b1acf23..4fc6229 100644
--- a/android/TerminalApp/res/layout/settings_activity.xml
+++ b/android/TerminalApp/res/layout/settings_activity.xml
@@ -5,10 +5,6 @@
android:orientation="vertical"
android:fitsSystemWindows="true">
- <com.google.android.material.search.SearchBar
- android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
-
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/settings_list_recycler_view"
android:layout_marginHorizontal="16dp"
diff --git a/android/TerminalApp/res/menu/main_menu.xml b/android/TerminalApp/res/menu/main_menu.xml
index 9c83923..0fee90e 100644
--- a/android/TerminalApp/res/menu/main_menu.xml
+++ b/android/TerminalApp/res/menu/main_menu.xml
@@ -1,9 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:id="@+id/copy_ip_addr"
- android:title="Copy the IP address"/>
- <item android:id="@+id/stop_vm"
- android:title="Stop the existing VM instance"/>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/menu_item_settings"
- android:title="Settings"/>
-</menu>
\ No newline at end of file
+ android:icon="@drawable/ic_settings"
+ android:title="@string/action_settings"
+ app:showAsAction="always"/>
+</menu>
diff --git a/android/TerminalApp/res/values/strings.xml b/android/TerminalApp/res/values/strings.xml
index d3670d2..0cdb939 100644
--- a/android/TerminalApp/res/values/strings.xml
+++ b/android/TerminalApp/res/values/strings.xml
@@ -20,6 +20,9 @@
<!-- Application name of this terminal app shown in the launcher. This app provides computer terminal to connect to virtual machine. [CHAR LIMIT=16] -->
<string name="app_name">Terminal</string>
+ <!-- Action bar icon name for the settings view CHAR LIMIT=none] -->
+ <string name="action_settings">Settings</string>
+
<!-- Toast message to notify that preparing terminal to start [CHAR LIMIT=none] -->
<string name="vm_creation_message">Preparing terminal</string>
<!-- Toast message to notify that terminal is stopping [CHAR LIMIT=none] -->
@@ -46,6 +49,14 @@
<string name="settings_port_forwarding_title">Port Forwarding</string>
<!-- Settings menu subtitle for 'port forwarding' [CHAR LIMIT=none] -->
<string name="settings_port_forwarding_sub_title">Configure port forwarding</string>
+ <!-- Notification title for new port forwarding [CHAR LIMIT=none] -->
+ <string name="settings_port_forwarding_notification_title">Terminal is trying to open a new port</string>
+ <!-- Notification content for new port forwarding [CHAR LIMIT=none] -->
+ <string name="settings_port_forwarding_notification_content">Port requested to be open: <xliff:g id="port_number" example="8080">%d</xliff:g></string>
+ <!-- Notification action accept [CHAR LIMIT=none] -->
+ <string name="settings_port_forwarding_notification_accept">Accept</string>
+ <!-- Notification action deny [CHAR LIMIT=none] -->
+ <string name="settings_port_forwarding_notification_deny">Deny</string>
<!-- Settings menu title for recoverying image [CHAR LIMIT=none] -->
<string name="settings_recovery_title">Recovery</string>
@@ -57,4 +68,13 @@
<string name="settings_recovery_reset_sub_title">Remove all</string>
<!-- Toast message for reset is completed [CHAR LIMIT=none] -->
<string name="settings_recovery_reset_message">VM reset</string>
+
+ <!-- Notification action button for settings [CHAR LIMIT=none] -->
+ <string name="service_notification_settings">Settings</string>
+ <!-- Notification title for foreground service notification [CHAR LIMIT=none] -->
+ <string name="service_notification_title">Terminal is running</string>
+ <!-- Notification content for foreground service notification [CHAR LIMIT=none] -->
+ <string name="service_notification_content">Click to open the terminal.</string>
+ <!-- Notification action button for closing the virtual machine [CHAR LIMIT=none] -->
+ <string name="service_notification_quit_action">Close</string>
</resources>
diff --git a/android/virtmgr/src/aidl.rs b/android/virtmgr/src/aidl.rs
index 23652d2..52bfd87 100644
--- a/android/virtmgr/src/aidl.rs
+++ b/android/virtmgr/src/aidl.rs
@@ -67,6 +67,7 @@
};
use cstr::cstr;
use glob::glob;
+use libc::{AF_VSOCK, sa_family_t, sockaddr_vm};
use log::{debug, error, info, warn};
use microdroid_payload_config::{ApkConfig, Task, TaskType, VmPayloadConfig};
use nix::unistd::pipe;
@@ -89,7 +90,7 @@
use std::sync::{Arc, Mutex, Weak, LazyLock};
use vbmeta::VbMetaImage;
use vmconfig::{VmConfig, get_debug_level};
-use vsock::{VsockAddr, VsockStream};
+use vsock::VsockStream;
use zip::ZipArchive;
/// The unique ID of a VM used (together with a port number) for vsock communication.
@@ -1434,8 +1435,14 @@
));
}
let cid = self.instance.cid;
- let get_connection_info =
- move |_instance: &str| Some(ConnectionInfo::Vsock(VsockAddr::new(cid, port)));
+ let addr = sockaddr_vm {
+ svm_family: AF_VSOCK as sa_family_t,
+ svm_reserved1: 0,
+ svm_port: port,
+ svm_cid: cid,
+ svm_zero: [0u8; 4],
+ };
+ let get_connection_info = move |_instance: &str| Some(ConnectionInfo::Vsock(addr));
let accessor = Accessor::new(name, get_connection_info);
accessor
.as_binder()
diff --git a/build/apex/Android.bp b/build/apex/Android.bp
index 4b69660..f794239 100644
--- a/build/apex/Android.bp
+++ b/build/apex/Android.bp
@@ -175,10 +175,6 @@
"true": ["virtualizationservice.xml"],
default: unset,
}),
- required: select(release_flag("RELEASE_AVF_SUPPORT_CUSTOM_VM_WITH_PARAVIRTUALIZED_DEVICES"), {
- true: ["linux_vm_setup"],
- default: [],
- }),
}
apex_defaults {
diff --git a/build/debian/build.sh b/build/debian/build.sh
index 71a9d3b..b4e8b2f 100755
--- a/build/debian/build.sh
+++ b/build/debian/build.sh
@@ -82,6 +82,13 @@
qemu-system
)
fi
+
+ # TODO(b/365955006): remove these lines when uboot supports x86_64 EFI application
+ if [[ "$arch" == "x86_64" ]]; then
+ packages+=(
+ libguestfs-tools
+ )
+ fi
DEBIAN_FRONTEND=noninteractive \
apt install --no-install-recommends --assume-yes "${packages[@]}"
@@ -160,3 +167,18 @@
copy_android_config
run_fai
fdisk -l image.raw
+images=(image.raw)
+# TODO(b/365955006): remove these lines when uboot supports x86_64 EFI application
+if [[ "$arch" == "x86_64" ]]; then
+ virt-get-kernel -a image.raw
+ mv vmlinuz* vmlinuz
+ mv initrd.img* initrd.img
+ images+=(
+ vmlinuz
+ initrd.img
+ )
+fi
+
+cp $(dirname $0)/vm_config.json.${arch} vm_config.json
+# --sparse option isn't supported in apache-commons-compress
+tar czv -f images.tar.gz ${images[@]} vm_config.json
\ No newline at end of file
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh b/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh
index c1524dd..7a1523a 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh
+++ b/build/debian/kokoro/gcp_ubuntu_docker/aarch64/build.sh
@@ -6,7 +6,7 @@
sudo losetup -D
grep vmx /proc/cpuinfo || true
sudo ./build.sh
-tar czvS -f ${KOKORO_ARTIFACTS_DIR}/images.tar.gz image.raw
+sudo mv images.tar.gz ${KOKORO_ARTIFACTS_DIR} || true
mkdir -p ${KOKORO_ARTIFACTS_DIR}/logs
sudo cp -r /var/log/fai/* ${KOKORO_ARTIFACTS_DIR}/logs || true
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh b/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh
index ab675f0..66e3d64 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh
+++ b/build/debian/kokoro/gcp_ubuntu_docker/x86_64/build.sh
@@ -6,7 +6,7 @@
sudo losetup -D
grep vmx /proc/cpuinfo || true
sudo ./build.sh -a x86_64
-tar czvS -f ${KOKORO_ARTIFACTS_DIR}/images.tar.gz image.raw
+sudo mv images.tar.gz ${KOKORO_ARTIFACTS_DIR} || true
mkdir -p ${KOKORO_ARTIFACTS_DIR}/logs
sudo cp -r /var/log/fai/* ${KOKORO_ARTIFACTS_DIR}/logs || true
diff --git a/android/TerminalApp/vm_config.json b/build/debian/vm_config.json.aarch64
similarity index 86%
rename from android/TerminalApp/vm_config.json
rename to build/debian/vm_config.json.aarch64
index 474e9c3..9f9295c 100644
--- a/android/TerminalApp/vm_config.json
+++ b/build/debian/vm_config.json.aarch64
@@ -1,9 +1,8 @@
-
{
"name": "debian",
"disks": [
{
- "image": "/data/local/tmp/image.raw",
+ "image": "$PAYLOAD_DIR/image.raw",
"partitions": [],
"writable": true
}
@@ -18,4 +17,3 @@
"console_input_device": "ttyS0",
"network": true
}
-
diff --git a/android/TerminalApp/vm_config.json b/build/debian/vm_config.json.x86_64
similarity index 69%
copy from android/TerminalApp/vm_config.json
copy to build/debian/vm_config.json.x86_64
index 474e9c3..2fb9faa 100644
--- a/android/TerminalApp/vm_config.json
+++ b/build/debian/vm_config.json.x86_64
@@ -1,13 +1,15 @@
-
{
"name": "debian",
"disks": [
{
- "image": "/data/local/tmp/image.raw",
+ "image": "$PAYLOAD_DIR/image.raw",
"partitions": [],
"writable": true
}
],
+ "kernel": "$PAYLOAD_DIR/vmlinuz",
+ "initrd": "$PAYLOAD_DIR/initrd.img",
+ "params": "root=/dev/vda1",
"protected": false,
"cpu_topology": "match_host",
"platform_version": "~1.0",
@@ -18,4 +20,3 @@
"console_input_device": "ttyS0",
"network": true
}
-
diff --git a/guest/pvmfw/Android.bp b/guest/pvmfw/Android.bp
index bcd3e42..4586cca 100644
--- a/guest/pvmfw/Android.bp
+++ b/guest/pvmfw/Android.bp
@@ -18,7 +18,6 @@
"libciborium_io_nostd",
"libcstr",
"libdiced_open_dice_nostd",
- "libfdtpci",
"liblibfdt_nostd",
"liblog_rust_nostd",
"libpvmfw_avb_nostd",
diff --git a/guest/pvmfw/src/fdt.rs b/guest/pvmfw/src/fdt.rs
index 953fdae..0d934a6 100644
--- a/guest/pvmfw/src/fdt.rs
+++ b/guest/pvmfw/src/fdt.rs
@@ -30,8 +30,6 @@
use core::mem::size_of;
use core::ops::Range;
use cstr::cstr;
-use fdtpci::PciMemoryFlags;
-use fdtpci::PciRangeType;
use libfdt::AddressRange;
use libfdt::CellIterator;
use libfdt::Fdt;
@@ -45,6 +43,8 @@
use log::warn;
use static_assertions::const_assert;
use tinyvec::ArrayVec;
+use vmbase::fdt::pci::PciMemoryFlags;
+use vmbase::fdt::pci::PciRangeType;
use vmbase::fdt::SwiotlbInfo;
use vmbase::hyp;
use vmbase::layout::{crosvm::MEM_START, MAX_VIRT_ADDR};
diff --git a/guest/pvmfw/src/main.rs b/guest/pvmfw/src/main.rs
index 10f8549..1e88c4b 100644
--- a/guest/pvmfw/src/main.rs
+++ b/guest/pvmfw/src/main.rs
@@ -46,13 +46,13 @@
use core::ops::Range;
use cstr::cstr;
use diced_open_dice::{bcc_handover_parse, DiceArtifacts, Hidden};
-use fdtpci::{PciError, PciInfo};
use libfdt::{Fdt, FdtNode};
use log::{debug, error, info, trace, warn};
use pvmfw_avb::verify_payload;
use pvmfw_avb::Capability;
use pvmfw_avb::DebugLevel;
use pvmfw_embedded_key::PUBLIC_KEY;
+use vmbase::fdt::pci::{PciError, PciInfo};
use vmbase::heap;
use vmbase::memory::flush;
use vmbase::memory::MEMORY;
diff --git a/guest/rialto/Android.bp b/guest/rialto/Android.bp
index eeb5b2d..7bcfd54 100644
--- a/guest/rialto/Android.bp
+++ b/guest/rialto/Android.bp
@@ -14,7 +14,6 @@
"libciborium_nostd",
"libcstr",
"libdiced_open_dice_nostd",
- "libfdtpci",
"liblibfdt_nostd",
"liblog_rust_nostd",
"libservice_vm_comm_nostd",
diff --git a/guest/rialto/src/error.rs b/guest/rialto/src/error.rs
index 033159b..ba5f4b0 100644
--- a/guest/rialto/src/error.rs
+++ b/guest/rialto/src/error.rs
@@ -17,10 +17,11 @@
use aarch64_paging::MapError;
use core::{fmt, result};
use diced_open_dice::DiceError;
-use fdtpci::PciError;
use libfdt::FdtError;
use service_vm_comm::RequestProcessingError;
-use vmbase::{hyp::Error as HypervisorError, memory::MemoryTrackerError, virtio::pci};
+use vmbase::{
+ fdt::pci::PciError, hyp::Error as HypervisorError, memory::MemoryTrackerError, virtio::pci,
+};
pub type Result<T> = result::Result<T, Error>;
diff --git a/guest/rialto/src/main.rs b/guest/rialto/src/main.rs
index 9265775..f09cbd2 100644
--- a/guest/rialto/src/main.rs
+++ b/guest/rialto/src/main.rs
@@ -32,7 +32,6 @@
use core::num::NonZeroUsize;
use core::slice;
use diced_open_dice::{bcc_handover_parse, DiceArtifacts};
-use fdtpci::PciInfo;
use libfdt::FdtError;
use log::{debug, error, info};
use service_vm_comm::{ServiceVmRequest, VmType};
@@ -45,6 +44,7 @@
};
use vmbase::{
configure_heap,
+ fdt::pci::PciInfo,
fdt::SwiotlbInfo,
generate_image_header,
hyp::{get_mem_sharer, get_mmio_guard},
diff --git a/guest/rialto/tests/test.rs b/guest/rialto/tests/test.rs
index 7ec5647..09f9cc3 100644
--- a/guest/rialto/tests/test.rs
+++ b/guest/rialto/tests/test.rs
@@ -25,7 +25,10 @@
use bssl_avf::{rand_bytes, sha256, EcKey, PKey};
use client_vm_csr::generate_attestation_key_and_csr;
use coset::{CborSerializable, CoseMac0, CoseSign};
-use hwtrust::{rkp, session::Session};
+use hwtrust::{
+ rkp,
+ session::{RkpInstance, Session},
+};
use log::{info, warn};
use service_vm_comm::{
ClientVmAttestationParams, Csr, CsrPayload, EcdsaP256KeyPair, GenerateCertificateRequestParams,
@@ -206,7 +209,8 @@
let vm_component = vm_component.decode_as::<asn1::SequenceOf<asn1::Any, 4>>().unwrap();
assert_eq!(4, vm_component.len());
let name = vm_component.get(0).unwrap().decode_as::<asn1::Utf8StringRef>().unwrap();
- assert_eq!(expected_component.name, name.as_ref());
+ let name_str: &str = name.as_ref();
+ assert_eq!(expected_component.name, name_str);
let version = vm_component.get(1).unwrap().decode_as::<u64>().unwrap();
assert_eq!(expected_component.version, version);
let code_hash = vm_component.get(2).unwrap().decode_as::<asn1::OctetString>().unwrap();
@@ -285,6 +289,7 @@
fn check_csr(csr: Vec<u8>) -> Result<()> {
let mut session = Session::default();
session.set_allow_any_mode(true);
+ session.set_rkp_instance(RkpInstance::Avf);
let _csr = rkp::Csr::from_cbor(&session, &csr[..]).context("Failed to parse CSR")?;
Ok(())
}
diff --git a/guest/vmbase_example/Android.bp b/guest/vmbase_example/Android.bp
index 49a6d69..09bd77c 100644
--- a/guest/vmbase_example/Android.bp
+++ b/guest/vmbase_example/Android.bp
@@ -11,7 +11,6 @@
"libaarch64_paging",
"libcstr",
"libdiced_open_dice_nostd",
- "libfdtpci",
"liblibfdt_nostd",
"liblog_rust_nostd",
"libvirtio_drivers",
diff --git a/guest/vmbase_example/src/main.rs b/guest/vmbase_example/src/main.rs
index 7a3f427..1466d1e 100644
--- a/guest/vmbase_example/src/main.rs
+++ b/guest/vmbase_example/src/main.rs
@@ -31,11 +31,12 @@
use core::mem;
use core::ptr::addr_of_mut;
use cstr::cstr;
-use fdtpci::PciInfo;
use libfdt::Fdt;
use log::{debug, error, info, trace, warn, LevelFilter};
use vmbase::{
- bionic, configure_heap, generate_image_header,
+ bionic, configure_heap,
+ fdt::pci::PciInfo,
+ generate_image_header,
layout::{crosvm::FDT_MAX_SIZE, rodata_range, scratch_range, text_range},
linker, logger, main,
memory::{PageTable, SIZE_64KB},
diff --git a/guest/vmbase_example/src/pci.rs b/guest/vmbase_example/src/pci.rs
index b838539..563f24a 100644
--- a/guest/vmbase_example/src/pci.rs
+++ b/guest/vmbase_example/src/pci.rs
@@ -17,7 +17,6 @@
use aarch64_paging::paging::MemoryRegion;
use alloc::alloc::{alloc_zeroed, dealloc, handle_alloc_error, Layout};
use core::{mem::size_of, ptr::NonNull};
-use fdtpci::PciInfo;
use log::{debug, info};
use virtio_drivers::{
device::console::VirtIOConsole,
@@ -27,7 +26,10 @@
},
BufferDirection, Error, Hal, PhysAddr, PAGE_SIZE,
};
-use vmbase::virtio::pci::{self, PciTransportIterator};
+use vmbase::{
+ fdt::pci::PciInfo,
+ virtio::pci::{self, PciTransportIterator},
+};
/// The standard sector size of a VirtIO block device, in bytes.
const SECTOR_SIZE_BYTES: usize = 512;
diff --git a/libs/fdtpci/Android.bp b/libs/fdtpci/Android.bp
deleted file mode 100644
index d7a5da3..0000000
--- a/libs/fdtpci/Android.bp
+++ /dev/null
@@ -1,19 +0,0 @@
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-rust_library_rlib {
- name: "libfdtpci",
- edition: "2021",
- no_stdlibs: true,
- host_supported: false,
- crate_name: "fdtpci",
- defaults: ["avf_build_flags_rust"],
- srcs: ["src/lib.rs"],
- rustlibs: [
- "liblibfdt_nostd",
- "liblog_rust_nostd",
- "libvirtio_drivers",
- ],
- apex_available: ["com.android.virt"],
-}
diff --git a/libs/fdtpci/TEST_MAPPING b/libs/fdtpci/TEST_MAPPING
deleted file mode 100644
index 192a7c6..0000000
--- a/libs/fdtpci/TEST_MAPPING
+++ /dev/null
@@ -1,9 +0,0 @@
-// When adding or removing tests here, don't forget to amend _all_modules list in
-// wireless/android/busytown/ath_config/configs/prod/avf/tests.gcl
-{
- "avf-presubmit": [
- {
- "name": "vmbase_example.integration_test"
- }
- ]
-}
diff --git a/libs/libservice_vm_fake_chain/src/service_vm.rs b/libs/libservice_vm_fake_chain/src/service_vm.rs
index 9bd831d..86fd3ea 100644
--- a/libs/libservice_vm_fake_chain/src/service_vm.rs
+++ b/libs/libservice_vm_fake_chain/src/service_vm.rs
@@ -116,6 +116,7 @@
component_name: Some(cstr!("Protected VM firmware")),
component_version: Some(1),
resettable: true,
+ rkp_vm_marker: true,
..Default::default()
};
let config_descriptor = retry_bcc_format_config_descriptor(&config_values)?;
@@ -158,6 +159,7 @@
component_name: Some(cstr!("vm_entry")),
component_version: Some(12),
resettable: true,
+ rkp_vm_marker: true,
..Default::default()
};
let config_descriptor = retry_bcc_format_config_descriptor(&config_values)?;
diff --git a/libs/libvmbase/Android.bp b/libs/libvmbase/Android.bp
index e634c18..206c4cb 100644
--- a/libs/libvmbase/Android.bp
+++ b/libs/libvmbase/Android.bp
@@ -80,7 +80,6 @@
"libaarch64_paging",
"libbuddy_system_allocator",
"libcstr",
- "libfdtpci",
"liblibfdt_nostd",
"liblog_rust_nostd",
"libonce_cell_nostd",
diff --git a/libs/libvmbase/src/fdt.rs b/libs/libvmbase/src/fdt.rs
index 4101f7e..ff0eaf0 100644
--- a/libs/libvmbase/src/fdt.rs
+++ b/libs/libvmbase/src/fdt.rs
@@ -14,6 +14,8 @@
//! High-level FDT functions.
+pub mod pci;
+
use core::ops::Range;
use cstr::cstr;
use libfdt::{self, Fdt, FdtError};
diff --git a/libs/fdtpci/src/lib.rs b/libs/libvmbase/src/fdt/pci.rs
similarity index 99%
rename from libs/fdtpci/src/lib.rs
rename to libs/libvmbase/src/fdt/pci.rs
index bdd904f..ebaa671 100644
--- a/libs/fdtpci/src/lib.rs
+++ b/libs/libvmbase/src/fdt/pci.rs
@@ -14,8 +14,6 @@
//! Library for working with (VirtIO) PCI devices discovered from a device tree.
-#![no_std]
-
use core::{
ffi::CStr,
fmt::{self, Display, Formatter},
diff --git a/libs/libvmbase/src/virtio/pci.rs b/libs/libvmbase/src/virtio/pci.rs
index 1d05c18..72e648b 100644
--- a/libs/libvmbase/src/virtio/pci.rs
+++ b/libs/libvmbase/src/virtio/pci.rs
@@ -14,11 +14,13 @@
//! Functions to scan the PCI bus for VirtIO devices.
-use crate::memory::{MemoryTracker, MemoryTrackerError};
+use crate::{
+ fdt::pci::PciInfo,
+ memory::{MemoryTracker, MemoryTrackerError},
+};
use alloc::boxed::Box;
use core::fmt;
use core::marker::PhantomData;
-use fdtpci::PciInfo;
use log::debug;
use once_cell::race::OnceBox;
use virtio_drivers::{
diff --git a/libs/vm_launcher_lib/Android.bp b/libs/vm_launcher_lib/Android.bp
index 5267508..f47f6b6 100644
--- a/libs/vm_launcher_lib/Android.bp
+++ b/libs/vm_launcher_lib/Android.bp
@@ -13,6 +13,7 @@
static_libs: [
"gson",
"debian-service-grpclib-lite",
+ "apache-commons-compress",
],
libs: [
"framework-virtualization.impl",
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/InstallUtils.java b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/InstallUtils.java
new file mode 100644
index 0000000..eb6dd77
--- /dev/null
+++ b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/InstallUtils.java
@@ -0,0 +1,141 @@
+/*
+ * 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.os.Environment;
+import android.util.Log;
+
+import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+public class InstallUtils {
+ private static final String TAG = InstallUtils.class.getSimpleName();
+
+ private static final String VM_CONFIG_FILENAME = "vm_config.json";
+ private static final String COMPRESSED_PAYLOAD_FILENAME = "images.tar.gz";
+ private static final String PAYLOAD_DIR = "linux";
+
+ public static String getVmConfigPath(Context context) {
+ return new File(context.getFilesDir(), PAYLOAD_DIR)
+ .toPath()
+ .resolve(VM_CONFIG_FILENAME)
+ .toString();
+ }
+
+ public static boolean isImageInstalled(Context context) {
+ return Files.exists(Path.of(getVmConfigPath(context)));
+ }
+
+ private static Path getPayloadPath() {
+ File payloadDir = Environment.getExternalStoragePublicDirectory(PAYLOAD_DIR);
+ if (payloadDir == null) {
+ Log.d(TAG, "no payload dir: " + payloadDir);
+ return null;
+ }
+ Path payloadPath = payloadDir.toPath().resolve(COMPRESSED_PAYLOAD_FILENAME);
+ return payloadPath;
+ }
+
+ public static boolean payloadFromExternalStorageExists() {
+ return Files.exists(getPayloadPath());
+ }
+
+ public static boolean installImageFromExternalStorage(Context context) {
+ if (!payloadFromExternalStorageExists()) {
+ Log.d(TAG, "no artifact file from external storage");
+ }
+ Path payloadPath = getPayloadPath();
+ try (BufferedInputStream inputStream =
+ new BufferedInputStream(Files.newInputStream(payloadPath));
+ TarArchiveInputStream tar =
+ new TarArchiveInputStream(new GzipCompressorInputStream(inputStream))) {
+ ArchiveEntry entry;
+ Path baseDir = new File(context.getFilesDir(), PAYLOAD_DIR).toPath();
+ Files.createDirectories(baseDir);
+ while ((entry = tar.getNextEntry()) != null) {
+ Path extractTo = baseDir.resolve(entry.getName());
+ if (entry.isDirectory()) {
+ Files.createDirectories(extractTo);
+ } else {
+ Files.copy(tar, extractTo, StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "installation failed", e);
+ return false;
+ }
+ if (!isImageInstalled(context)) {
+ return false;
+ }
+
+ if (!resolvePathInVmConfig(context)) {
+ Log.d(TAG, "resolving path failed");
+ try {
+ Files.deleteIfExists(Path.of(getVmConfigPath(context)));
+ } catch (IOException e) {
+ return false;
+ }
+ return false;
+ }
+
+ // remove payload if installation is done.
+ try {
+ Files.deleteIfExists(payloadPath);
+ } catch (IOException e) {
+ Log.d(TAG, "failed to remove installed payload", e);
+ }
+
+ return true;
+ }
+
+ private static Function<String, String> getReplacer(Context context) {
+ Map<String, String> rules = new HashMap<>();
+ rules.put("\\$PAYLOAD_DIR", new File(context.getFilesDir(), PAYLOAD_DIR).toString());
+ return (s) -> {
+ for (Map.Entry<String, String> rule : rules.entrySet()) {
+ s = s.replaceAll(rule.getKey(), rule.getValue());
+ }
+ return s;
+ };
+ }
+
+ private static boolean resolvePathInVmConfig(Context context) {
+ try {
+ String replacedVmConfig =
+ String.join(
+ "\n",
+ Files.readAllLines(Path.of(getVmConfigPath(context))).stream()
+ .map(getReplacer(context))
+ .toList());
+ Files.write(Path.of(getVmConfigPath(context)), replacedVmConfig.getBytes());
+ return true;
+ } catch (IOException e) {
+ return false;
+ }
+ }
+}
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java
index 3d5c345..849cc24 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java
+++ b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java
@@ -17,8 +17,6 @@
package com.android.virtualization.vmlauncher;
import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
@@ -39,9 +37,8 @@
import java.util.concurrent.Executors;
public class VmLauncherService extends Service implements DebianServiceImpl.DebianServiceCallback {
+ public static final String EXTRA_NOTIFICATION = "EXTRA_NOTIFICATION";
private static final String TAG = "VmLauncherService";
- // TODO: this path should be from outside of this service
- private static final String VM_CONFIG_PATH = "/data/local/tmp/vm_config.json";
private static final int RESULT_START = 0;
private static final int RESULT_STOP = 1;
@@ -59,18 +56,8 @@
return null;
}
- private void startForeground() {
- NotificationManager notificationManager = getSystemService(NotificationManager.class);
- NotificationChannel notificationChannel =
- new NotificationChannel(TAG, TAG, NotificationManager.IMPORTANCE_LOW);
- notificationManager.createNotificationChannel(notificationChannel);
- startForeground(
- this.hashCode(),
- new Notification.Builder(this, TAG)
- .setChannelId(TAG)
- .setSmallIcon(android.R.drawable.ic_dialog_info)
- .setContentText("A VM " + mVirtualMachine.getName() + " is running")
- .build());
+ private void startForeground(Notification notification) {
+ startForeground(this.hashCode(), notification);
}
@Override
@@ -81,7 +68,7 @@
}
mExecutorService = Executors.newCachedThreadPool();
- ConfigJson json = ConfigJson.from(VM_CONFIG_PATH);
+ ConfigJson json = ConfigJson.from(InstallUtils.getVmConfigPath(this));
VirtualMachineConfig config = json.toConfig(this);
Runner runner;
@@ -112,7 +99,10 @@
Path logPath = getFileStreamPath(mVirtualMachine.getName() + ".log").toPath();
Logger.setup(mVirtualMachine, logPath, mExecutorService);
- startForeground();
+ Notification notification = intent.getParcelableExtra(EXTRA_NOTIFICATION,
+ Notification.class);
+
+ startForeground(notification);
mResultReceiver.send(RESULT_START, null);
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherServices.java b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherServices.java
index 565b793..2fa0b32 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherServices.java
+++ b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherServices.java
@@ -16,6 +16,7 @@
package com.android.virtualization.vmlauncher;
+import android.app.Notification;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -64,7 +65,8 @@
context.stopService(i);
}
- public static void startVmLauncherService(Context context, VmLauncherServiceCallback callback) {
+ public static void startVmLauncherService(Context context, VmLauncherServiceCallback callback,
+ Notification notification) {
Intent i = buildVmLauncherServiceIntent(context);
if (i == null) {
return;
@@ -93,6 +95,7 @@
}
};
i.putExtra(Intent.EXTRA_RESULT_RECEIVER, getResultReceiverForIntent(resultReceiver));
+ i.putExtra(VmLauncherService.EXTRA_NOTIFICATION, notification);
context.startForegroundService(i);
}