Merge "Revert "Set droid's gid 1000"" into main
diff --git a/android/LinuxInstaller/generate_assets.sh b/android/LinuxInstaller/generate_assets.sh
new file mode 100755
index 0000000..8e70cb0
--- /dev/null
+++ b/android/LinuxInstaller/generate_assets.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+set -e
+
+if [ "$#" -ne 1 ]; then
+    echo "$0 <image.raw path>"
+    exit 1
+fi
+pushd $(dirname $0) > /dev/null
+tempdir=$(mktemp -d)
+asset_dir=./assets/linux
+mkdir -p ${asset_dir}
+echo Copy files...
+pushd ${tempdir} > /dev/null
+cp "$1" ${tempdir}
+tar czvS -f images.tar.gz $(basename $1)
+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/LinuxInstaller/java/com/android/virtualization/linuxinstaller/MainActivity.java b/android/LinuxInstaller/java/com/android/virtualization/linuxinstaller/MainActivity.java
index 0351f97..9b0ec97 100644
--- a/android/LinuxInstaller/java/com/android/virtualization/linuxinstaller/MainActivity.java
+++ b/android/LinuxInstaller/java/com/android/virtualization/linuxinstaller/MainActivity.java
@@ -18,11 +18,6 @@
 
 import android.annotation.WorkerThread;
 import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.SystemProperties;
@@ -65,12 +60,6 @@
     }
 
     private void installLinuxImage() {
-        ComponentName vmTerminalComponent = resolve(getPackageManager(), ACTION_VM_TERMINAL);
-        if (vmTerminalComponent == null) {
-            updateStatus("Failed to resolve VM terminal");
-            return;
-        }
-
         if (!hasLocalAssets()) {
             updateStatus("No local assets");
             return;
@@ -81,12 +70,6 @@
             Log.e(TAG, "failed to update image", e);
             return;
         }
-        updateStatus("Enabling terminal app...");
-        getPackageManager()
-                .setComponentEnabledSetting(
-                        vmTerminalComponent,
-                        PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
-                        PackageManager.DONT_KILL_APP);
         updateStatus("Done.");
     }
 
@@ -190,18 +173,4 @@
                     statusView.append(line + "\n");
                 });
     }
-
-    private ComponentName resolve(PackageManager pm, String action) {
-        Intent intent = new Intent(action);
-        List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, PackageManager.MATCH_ALL);
-        if (resolveInfos.size() != 1) {
-            Log.w(
-                    TAG,
-                    "Failed to resolve activity, action=" + action + ", resolved=" + resolveInfos);
-            return null;
-        }
-        ActivityInfo activityInfo = resolveInfos.getFirst().activityInfo;
-        // MainActivityAlias shows in Launcher
-        return new ComponentName(activityInfo.packageName, activityInfo.name + "Alias");
-    }
 }
diff --git a/android/LinuxInstaller/linux_image_builder/commands b/android/LinuxInstaller/linux_image_builder/commands
deleted file mode 100644
index 4d27475..0000000
--- a/android/LinuxInstaller/linux_image_builder/commands
+++ /dev/null
@@ -1,11 +0,0 @@
-upload init.sh:/root
-upload vsock.py:/usr/local/bin
-upload /tmp/ttyd:/usr/local/bin
-upload ttyd.service:/etc/systemd/system
-upload vsockip.service:/etc/systemd/system
-chmod 0777:/root/init.sh
-firstboot-command "/root/init.sh"
-chmod 0644:/etc/systemd/system/vsockip.service
-chmod 0644:/etc/systemd/system/ttyd.service
-chmod 0777:/usr/local/bin/vsock.py
-chmod 0777:/usr/local/bin/ttyd
diff --git a/android/LinuxInstaller/linux_image_builder/init.sh b/android/LinuxInstaller/linux_image_builder/init.sh
deleted file mode 100644
index bec5ac5..0000000
--- a/android/LinuxInstaller/linux_image_builder/init.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/bash
-systemctl daemon-reload
-systemctl start ttyd && sudo systemctl enable ttyd
-systemctl start vsockip && sudo systemctl enable vsockip
diff --git a/android/LinuxInstaller/linux_image_builder/setup.sh b/android/LinuxInstaller/linux_image_builder/setup.sh
deleted file mode 100755
index 2883e61..0000000
--- a/android/LinuxInstaller/linux_image_builder/setup.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/bash
-
-pushd $(dirname $0) > /dev/null
-tempdir=$(mktemp -d)
-echo Get Debian image and dependencies...
-wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-nocloud-arm64.raw -O ${tempdir}/debian.img
-wget https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.aarch64 -O ${tempdir}/ttyd
-
-echo Customize the image...
-virt-customize --commands-from-file <(sed "s|/tmp|$tempdir|g" commands) -a ${tempdir}/debian.img
-
-asset_dir=../assets/linux
-mkdir -p ${asset_dir}
-
-echo Copy files...
-
-pushd ${tempdir} > /dev/null
-tar czvS -f images.tar.gz debian.img
-popd > /dev/null
-mv ${tempdir}/images.tar.gz ${asset_dir}/images.tar.gz
-cp vm_config.json ${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}
\ No newline at end of file
diff --git a/android/LinuxInstaller/linux_image_builder/setup_x86_64.sh b/android/LinuxInstaller/linux_image_builder/setup_x86_64.sh
deleted file mode 100755
index c543b2a..0000000
--- a/android/LinuxInstaller/linux_image_builder/setup_x86_64.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/bash
-
-pushd $(dirname $0) > /dev/null
-tempdir=$(mktemp -d)
-echo Get Debian image and dependencies...
-wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-nocloud-amd64.raw -O ${tempdir}/debian.img
-wget https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.x86_64 -O ${tempdir}/ttyd
-
-echo Customize the image...
-virt-customize --commands-from-file <(sed "s|/tmp|$tempdir|g" commands) -a ${tempdir}/debian.img
-
-asset_dir=../assets/linux
-mkdir -p ${asset_dir}
-
-echo Copy files...
-
-pushd ${tempdir} > /dev/null
-tar czvS -f images.tar.gz debian.img
-popd > /dev/null
-mv ${tempdir}/images.tar.gz ${asset_dir}/images.tar.gz
-cp vm_config.json ${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}
\ No newline at end of file
diff --git a/android/LinuxInstaller/linux_image_builder/ttyd.service b/android/LinuxInstaller/linux_image_builder/ttyd.service
deleted file mode 100644
index f71557d..0000000
--- a/android/LinuxInstaller/linux_image_builder/ttyd.service
+++ /dev/null
@@ -1,12 +0,0 @@
-[Unit]
-Description=TTYD
-After=syslog.target
-After=network.target
-[Service]
-ExecStart=/usr/local/bin/ttyd -W screen -aAxR -S main login
-Type=simple
-Restart=always
-User=root
-Group=root
-[Install]
-WantedBy=multi-user.target
diff --git a/android/LinuxInstaller/linux_image_builder/vsock.py b/android/LinuxInstaller/linux_image_builder/vsock.py
deleted file mode 100644
index 292d953..0000000
--- a/android/LinuxInstaller/linux_image_builder/vsock.py
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/usr/bin/env python3
-
-import socket
-
-# Constants for vsock (from linux/vm_sockets.h)
-AF_VSOCK = 40
-SOCK_STREAM = 1
-VMADDR_CID_ANY = -1
-
-def get_local_ip():
-    """Retrieves the first IPv4 address found on the system.
-
-    Returns:
-        str: The local IPv4 address, or '127.0.0.1' if no IPv4 address is found.
-    """
-
-    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
-    try:
-        s.connect(('8.8.8.8', 80))
-        ip = s.getsockname()[0]
-    except Exception:
-        ip = '127.0.0.1'
-    finally:
-        s.close()
-    return ip
-
-def main():
-    PORT = 1024
-
-    # Create a vsock socket
-    server_socket = socket.socket(AF_VSOCK, SOCK_STREAM)
-
-    # Bind the socket to the server address
-    server_address = (VMADDR_CID_ANY, PORT)
-    server_socket.bind(server_address)
-
-    # Listen for incoming connections
-    server_socket.listen(1)
-    print(f"VSOCK server listening on port {PORT}...")
-
-    while True:
-        # Accept a connection
-        connection, client_address = server_socket.accept()
-        print(f"Connection from: {client_address}")
-
-        try:
-            # Get the local IP address
-            local_ip = get_local_ip()
-
-            # Send the IP address to the client
-            connection.sendall(local_ip.encode())
-        finally:
-            # Close the connection
-            connection.close()
-
-if __name__ == "__main__":
-    main()
diff --git a/android/LinuxInstaller/linux_image_builder/vsockip.service b/android/LinuxInstaller/linux_image_builder/vsockip.service
deleted file mode 100644
index a29020b..0000000
--- a/android/LinuxInstaller/linux_image_builder/vsockip.service
+++ /dev/null
@@ -1,12 +0,0 @@
-[Unit]
-Description=vsock ip service
-After=syslog.target
-After=network.target
-[Service]
-ExecStart=/usr/bin/python3 /usr/local/bin/vsock.py
-Type=simple
-Restart=always
-User=root
-Group=root
-[Install]
-WantedBy=multi-user.target
diff --git a/android/LinuxInstaller/linux_image_builder/vm_config.json b/android/LinuxInstaller/vm_config.json
similarity index 87%
rename from android/LinuxInstaller/linux_image_builder/vm_config.json
rename to android/LinuxInstaller/vm_config.json
index 21462b8..474e9c3 100644
--- a/android/LinuxInstaller/linux_image_builder/vm_config.json
+++ b/android/LinuxInstaller/vm_config.json
@@ -1,8 +1,9 @@
+
 {
     "name": "debian",
     "disks": [
         {
-            "image": "/data/local/tmp/debian.img",
+            "image": "/data/local/tmp/image.raw",
             "partitions": [],
             "writable": true
         }
@@ -17,3 +18,4 @@
     "console_input_device": "ttyS0",
     "network": true
 }
+
diff --git a/android/TerminalApp/AndroidManifest.xml b/android/TerminalApp/AndroidManifest.xml
index f09412e..bd1395a 100644
--- a/android/TerminalApp/AndroidManifest.xml
+++ b/android/TerminalApp/AndroidManifest.xml
@@ -12,15 +12,18 @@
     <uses-feature android:name="android.software.virtualization_framework" android:required="true" />
 
     <application
-	android:label="@string/app_name"
+        android:label="@string/app_name"
         android:icon="@mipmap/ic_launcher"
         android:theme="@style/Theme.Material3.DayNight.NoActionBar"
-        android:usesCleartextTraffic="true">
+        android:usesCleartextTraffic="true"
+        android:enabled="false">
         <activity android:name=".MainActivity"
                   android:configChanges="orientation|screenSize|keyboard|keyboardHidden|navigation|uiMode|screenLayout|smallestScreenSize"
                   android:exported="true">
             <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
                 <action android:name="android.virtualization.VM_TERMINAL" />
+                <category android:name="android.intent.category.LAUNCHER" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
@@ -44,20 +47,9 @@
                 android:name="${applicationId}.SplitInitializer"
                 android:value="androidx.startup" />
         </provider>
-        <activity-alias
-            android:name=".MainActivityAlias"
-            android:targetActivity="com.android.virtualization.terminal.MainActivity"
-            android:exported="true"
-            android:enabled="false" >
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity-alias>
 
         <service
             android:name="com.android.virtualization.vmlauncher.VmLauncherService"
-            android:enabled="true"
             android:exported="false"
             android:foregroundServiceType="specialUse">
             <property
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index 018916d..1fabf8d 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -17,15 +17,21 @@
 
 import android.content.ClipData;
 import android.content.ClipboardManager;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
+import android.system.ErrnoException;
+import android.system.Os;
 import android.util.Log;
 import android.view.Menu;
 import android.view.MenuItem;
+import android.view.View;
+import android.view.accessibility.AccessibilityManager;
 import android.webkit.WebChromeClient;
+import android.webkit.WebResourceError;
+import android.webkit.WebResourceRequest;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
-import android.widget.TextView;
 import android.widget.Toast;
 
 import androidx.appcompat.app.AppCompatActivity;
@@ -34,16 +40,42 @@
 
 import com.google.android.material.appbar.MaterialToolbar;
 
-public class MainActivity extends AppCompatActivity implements
-        VmLauncherServices.VmLauncherServiceCallback {
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.UnknownHostException;
+
+public class MainActivity extends AppCompatActivity
+        implements VmLauncherServices.VmLauncherServiceCallback,
+                AccessibilityManager.TouchExplorationStateChangeListener {
+
     private static final String TAG = "VmTerminalApp";
-    private String mVmIpAddr;
+    private static final String VM_ADDR = "192.168.0.2";
+    private static final int TTYD_PORT = 7681;
     private WebView mWebView;
+    private AccessibilityManager mAccessibilityManager;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+
+        try {
+            // No resize for now.
+            long newSizeInBytes = 0;
+            diskResize(this, newSizeInBytes);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to resize disk", e);
+            Toast.makeText(this, "Error resizing disk: " + e.getMessage(), Toast.LENGTH_LONG)
+                    .show();
+        }
+
         Toast.makeText(this, R.string.vm_creation_message, Toast.LENGTH_SHORT).show();
+        android.os.Trace.beginAsyncSection("executeTerminal", 0);
         VmLauncherServices.startVmLauncherService(this, this);
 
         setContentView(R.layout.activity_headless);
@@ -55,26 +87,182 @@
         mWebView.getSettings().setDomStorageEnabled(true);
         mWebView.getSettings().setJavaScriptEnabled(true);
         mWebView.setWebChromeClient(new WebChromeClient());
+
+        mAccessibilityManager = getSystemService(AccessibilityManager.class);
+        mAccessibilityManager.addTouchExplorationStateChangeListener(this);
+
+        connectToTerminalService();
+    }
+
+    private URL getTerminalServiceUrl() {
+        boolean needsAccessibility = mAccessibilityManager.isTouchExplorationEnabled();
+        String file = "/";
+        String query = needsAccessibility ? "?screenReaderMode=true" : "";
+
+        try {
+            return new URL("http", VM_ADDR, TTYD_PORT, file + query);
+        } catch (MalformedURLException e) {
+            // this cannot happen
+            return null;
+        }
+    }
+
+    private void connectToTerminalService() {
+        Log.i(TAG, "URL=" + getTerminalServiceUrl().toString());
         mWebView.setWebViewClient(
                 new WebViewClient() {
                     @Override
-                    public boolean shouldOverrideUrlLoading(WebView view, String url) {
-                        view.loadUrl(url);
-                        return true;
+                    public boolean shouldOverrideUrlLoading(
+                            WebView view, WebResourceRequest request) {
+                        return false;
+                    }
+
+                    @Override
+                    public void onReceivedError(
+                            WebView view, WebResourceRequest request, WebResourceError error) {
+                        switch (error.getErrorCode()) {
+                            case WebViewClient.ERROR_CONNECT:
+                            case WebViewClient.ERROR_HOST_LOOKUP:
+                                view.reload();
+                                return;
+                            default:
+                                String url = request.getUrl().toString();
+                                CharSequence msg = error.getDescription();
+                                Log.e(TAG, "Failed to load " + url + ": " + msg);
+                        }
+                    }
+
+                    @Override
+                    public void onPageFinished(WebView view, String url) {
+                        URL loadedUrl = null;
+                        try {
+                            loadedUrl = new URL(url);
+                        } catch (MalformedURLException e) {
+                            // cannot happen.
+                        }
+                        Log.i(TAG, "on page finished. URL=" + loadedUrl);
+                        if (getTerminalServiceUrl().toString().equals(url)) {
+                            android.os.Trace.endAsyncSection("executeTerminal", 0);
+                            view.setVisibility(View.VISIBLE);
+                        }
                     }
                 });
+        new Thread(
+                        () -> {
+                            waitUntilVmStarts();
+                            runOnUiThread(
+                                    () -> mWebView.loadUrl(getTerminalServiceUrl().toString()));
+                        })
+                .start();
+    }
+
+    private void diskResize(Context context, long sizeInBytes) throws IOException {
+        try {
+            if (sizeInBytes == 0) {
+                return;
+            }
+            File file = getPartitionFile(context, "root_part");
+            String filePath = file.getAbsolutePath();
+            Log.d(TAG, "Disk-resize in progress for partition: " + filePath);
+
+            long currentSize = Os.stat(filePath).st_size;
+            runE2fsck(filePath);
+            if (sizeInBytes > currentSize) {
+                allocateSpace(file, sizeInBytes);
+            }
+
+            resizeFilesystem(filePath, sizeInBytes);
+        } catch (ErrnoException e) {
+            Log.e(TAG, "ErrnoException during disk resize", e);
+            throw new IOException("ErrnoException during disk resize", e);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to resize disk", e);
+            throw e;
+        }
+    }
+
+    private static File getPartitionFile(Context context, String fileName)
+            throws FileNotFoundException {
+        File file = new File(context.getFilesDir(), fileName);
+        if (!file.exists()) {
+            Log.d(TAG, fileName + " - file not found");
+            throw new FileNotFoundException("File not found: " + fileName);
+        }
+        return file;
+    }
+
+    private static void allocateSpace(File file, long sizeInBytes) throws IOException {
+        try {
+            RandomAccessFile raf = new RandomAccessFile(file, "rw");
+            FileDescriptor fd = raf.getFD();
+            Os.posix_fallocate(fd, 0, sizeInBytes);
+            raf.close();
+            Log.d(TAG, "Allocated space to: " + sizeInBytes + " bytes");
+        } catch (ErrnoException e) {
+            Log.e(TAG, "Failed to allocate space", e);
+            throw new IOException("Failed to allocate space", e);
+        }
+    }
+
+    private static void runE2fsck(String filePath) throws IOException {
+        try {
+            runCommand("/system/bin/e2fsck", "-f", filePath);
+            Log.d(TAG, "e2fsck completed: " + filePath);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to run e2fsck", e);
+            throw e;
+        }
+    }
+
+    private static void resizeFilesystem(String filePath, long sizeInBytes) throws IOException {
+        long sizeInMB = sizeInBytes / (1024 * 1024);
+        if (sizeInMB == 0) {
+            Log.e(TAG, "Invalid size: " + sizeInBytes + " bytes");
+            throw new IllegalArgumentException("Size cannot be zero MB");
+        }
+        String sizeArg = sizeInMB + "M";
+        try {
+            runCommand("/system/bin/resize2fs", filePath, sizeArg);
+            Log.d(TAG, "resize2fs completed: " + filePath + ", size: " + sizeArg);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to run resize2fs", e);
+            throw e;
+        }
+    }
+
+    private static void runCommand(String... command) throws IOException {
+        try {
+            Process process = new ProcessBuilder(command).redirectErrorStream(true).start();
+            process.waitFor();
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new IOException("Command interrupted", e);
+        }
+    }
+
+    private static void waitUntilVmStarts() {
+        InetAddress addr = null;
+        try {
+            addr = InetAddress.getByName(VM_ADDR);
+        } catch (UnknownHostException e) {
+            // this can never happen.
+        }
+        try {
+            while (!addr.isReachable(10000)) {}
+        } catch (IOException e) {
+            // give up on network error
+            throw new RuntimeException(e);
+        }
+        return;
     }
 
     @Override
     protected void onDestroy() {
+        getSystemService(AccessibilityManager.class).removeTouchExplorationStateChangeListener(this);
         VmLauncherServices.stopVmLauncherService(this);
         super.onDestroy();
     }
 
-    private void gotoURL(String url) {
-        runOnUiThread(() -> mWebView.loadUrl(url));
-    }
-
     @Override
     public void onVmStart() {
         Log.i(TAG, "onVmStart()");
@@ -96,9 +284,7 @@
 
     @Override
     public void onIpAddrAvailable(String ipAddr) {
-        mVmIpAddr = ipAddr;
-        ((TextView) findViewById(R.id.ip_addr_textview)).setText(mVmIpAddr);
-        gotoURL("http://" + mVmIpAddr + ":7681");
+        // TODO: remove this
     }
 
     @Override
@@ -113,10 +299,11 @@
         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", mVmIpAddr));
+                    .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) {
@@ -126,4 +313,9 @@
         }
         return super.onOptionsItemSelected(item);
     }
+
+    @Override
+    public void onTouchExplorationStateChanged(boolean enabled) {
+        connectToTerminalService();
+    }
 }
diff --git a/android/TerminalApp/res/layout/activity_headless.xml b/android/TerminalApp/res/layout/activity_headless.xml
index f786a0f..736b45a 100644
--- a/android/TerminalApp/res/layout/activity_headless.xml
+++ b/android/TerminalApp/res/layout/activity_headless.xml
@@ -20,6 +20,7 @@
       android:id="@+id/webview"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
-      android:layout_marginBottom="5dp" />
+      android:layout_marginBottom="5dp"
+      android:visibility="invisible"/>
 
 </LinearLayout>
diff --git a/android/TerminalApp/res/values/strings.xml b/android/TerminalApp/res/values/strings.xml
index c3a3348..6ae5b7b 100644
--- a/android/TerminalApp/res/values/strings.xml
+++ b/android/TerminalApp/res/values/strings.xml
@@ -16,25 +16,45 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name">Terminal</string>
-    <string name="vm_creation_message">Virtual machine is booting. Please wait.</string>
-    <string name="vm_stop_message">Virtual machine is stopped. Exiting.</string>
-    <string name="vm_error_message">Virtual machine crashed. Exiting.</string>
 
+    <!-- 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>
+
+    <!-- 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] -->
+    <string name="vm_stop_message">Stopping terminal</string>
+    <!-- Toast message to notify that terminal is crashed [CHAR LIMIT=none] -->
+    <string name="vm_error_message">Terminal crashed</string>
+
+    <!-- Settings memu title for resizing disk of the virtual machine. [CHAR LIMIT=none] -->
     <string name="settings_disk_resize_title">Disk Resize</string>
+    <!-- Settings memu subtitle for resizing disk of the virtual machine. [CHAR LIMIT=none] -->
     <string name="settings_disk_resize_sub_title">Resize / Rootfs</string>
+    <!-- Toast message after new disk size is set. [CHAR LIMIT=none] -->
     <string name="settings_disk_resize_resize_message">Disk size set</string>
+    <!-- Settings menu option description for the current disk size, followed by a text box with the actual number [CHAR LIMIT=none] -->
     <string name="settings_disk_resize_resize_gb_assigned">GB Assigned</string>
+    <!-- Settings menu option description for the maximum resizable disk size. [CHAR LIMIT=none] -->
     <string name="settings_disk_resize_resize_gb_total">256 GB total</string>
+    <!-- Settings menu button to cancel disk resize. [CHAR LIMIT=32] -->
     <string name="settings_disk_resize_resize_cancel">Cancel</string>
+    <!-- Settings menu button to apply change that requires to restart VM (abbrev of virtual machine). [CHAR LIMIT=64] -->
     <string name="settings_disk_resize_resize_restart_vm_to_apply">Restart VM to apply</string>
 
+    <!-- Settings menu title for 'port forwarding' [CHAR LIMIT=none] -->
     <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>
 
+    <!-- Settings menu title for recoverying image [CHAR LIMIT=none] -->
     <string name="settings_recovery_title">Recovery</string>
+    <!-- Settings menu subtitle for recoverying image [CHAR LIMIT=none] -->
     <string name="settings_recovery_sub_title">Partition Recovery options</string>
+    <!-- Settings menu title for resetting the virtual machine image [CHAR LIMIT=none] -->
     <string name="settings_recovery_reset_title">Change to Initial version</string>
+    <!-- Settings menu subtitle for resetting the virtual machine image [CHAR LIMIT=none] -->
     <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>
 </resources>
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/build.sh b/build/debian/kokoro/gcp_ubuntu_docker/build.sh
index 5023d09..c1524dd 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/build.sh
+++ b/build/debian/kokoro/gcp_ubuntu_docker/build.sh
@@ -6,8 +6,7 @@
 sudo losetup -D
 grep vmx /proc/cpuinfo || true
 sudo ./build.sh
-tar czvS -f ${KOKORO_ARTIFACTS_DIR}/image.tar.gz image.raw
+tar czvS -f ${KOKORO_ARTIFACTS_DIR}/images.tar.gz image.raw
 
 mkdir -p ${KOKORO_ARTIFACTS_DIR}/logs
-# TODO(b/372162211): Find exact location of log without breaking kokoro build.
-cp -r /var/log/fai/*/last/* ${KOKORO_ARTIFACTS_DIR}/logs || true
+sudo cp -r /var/log/fai/* ${KOKORO_ARTIFACTS_DIR}/logs || true
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg b/build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg
index ddb4ef7..a41c032 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg
+++ b/build/debian/kokoro/gcp_ubuntu_docker/continuous.cfg
@@ -8,7 +8,7 @@
 
 action {
   define_artifacts {
-    regex: "image.tar.gz"
-    regex: "logs/*"
+    regex: "images.tar.gz"
+    regex: "logs/**"
   }
 }
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/hourly.cfg b/build/debian/kokoro/gcp_ubuntu_docker/hourly.cfg
index ddb4ef7..a41c032 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/hourly.cfg
+++ b/build/debian/kokoro/gcp_ubuntu_docker/hourly.cfg
@@ -8,7 +8,7 @@
 
 action {
   define_artifacts {
-    regex: "image.tar.gz"
-    regex: "logs/*"
+    regex: "images.tar.gz"
+    regex: "logs/**"
   }
 }
diff --git a/build/debian/kokoro/gcp_ubuntu_docker/presubmit.cfg b/build/debian/kokoro/gcp_ubuntu_docker/presubmit.cfg
index ddb4ef7..a41c032 100644
--- a/build/debian/kokoro/gcp_ubuntu_docker/presubmit.cfg
+++ b/build/debian/kokoro/gcp_ubuntu_docker/presubmit.cfg
@@ -8,7 +8,7 @@
 
 action {
   define_artifacts {
-    regex: "image.tar.gz"
-    regex: "logs/*"
+    regex: "images.tar.gz"
+    regex: "logs/**"
   }
 }
diff --git a/docs/vm_remote_attestation.md b/docs/vm_remote_attestation.md
index ee20591..2ee0fae 100644
--- a/docs/vm_remote_attestation.md
+++ b/docs/vm_remote_attestation.md
@@ -126,7 +126,7 @@
 
 To support VM remote attestation, vendors must include an RKP VM marker in their
 DICE certificates. This marker should be present from the early boot stage
-within the TEE and continue through to the last DICE certificate before
+within the TEE and continue through to the leaf DICE certificate before
 [pvmfw][pvmfw] takes over.
 
 ![RKP VM DICE chain][rkpvm-dice-chain]
@@ -140,6 +140,20 @@
 server because it will lack the RKP VM marker that pvmfw would have added in a
 genuine RKP VM boot process.
 
+### Testing
+
+To ensure the correct implementation and usage of RKP VM markers, we've
+incorporated comprehensive checks into various xTS tests (e.g.,
+`VtsHalRemotelyProvisionedComponentTargetTest`).
+
+These tests validate the following conditions:
+
+- The RKP VM DICE chain must have a continuous presence of at least two RKP VM
+  markers, extending to the leaf DICE certificate.
+- Non-RKP VM DICE chains must not have a continuous presence of two or more RKP
+  VM markers, preventing non-RKP VM chains from being incorrectly identified as
+  RKP VM chains.
+
 [pvmfw]: ../guest/pvmfw/README.md
 [rkpvm-dice-chain]: img/rkpvm-dice-chain.png
 
diff --git a/guest/microdroid_manager/Android.bp b/guest/microdroid_manager/Android.bp
index 9c9a3d0..1824c20 100644
--- a/guest/microdroid_manager/Android.bp
+++ b/guest/microdroid_manager/Android.bp
@@ -43,7 +43,6 @@
         "libmicrodroid_payload_config",
         "libmicrodroid_uids",
         "libnix",
-        "libonce_cell",
         "libopenssl",
         "libprotobuf",
         "librpcbinder_rs",
diff --git a/guest/pvmfw/Android.bp b/guest/pvmfw/Android.bp
index d0d309b..bcd3e42 100644
--- a/guest/pvmfw/Android.bp
+++ b/guest/pvmfw/Android.bp
@@ -21,7 +21,6 @@
         "libfdtpci",
         "liblibfdt_nostd",
         "liblog_rust_nostd",
-        "libonce_cell_nostd",
         "libpvmfw_avb_nostd",
         "libpvmfw_embedded_key",
         "libpvmfw_fdt_template",
diff --git a/libs/dice/driver/Android.bp b/libs/dice/driver/Android.bp
index c93bd7d..baed21d 100644
--- a/libs/dice/driver/Android.bp
+++ b/libs/dice/driver/Android.bp
@@ -22,7 +22,6 @@
         "liblibc",
         "liblog_rust",
         "libnix",
-        "libonce_cell",
         "libopenssl",
         "libthiserror",
         "libserde_cbor",
diff --git a/libs/service-virtualization/src/com/android/system/virtualmachine/VirtualizationSystemService.java b/libs/service-virtualization/src/com/android/system/virtualmachine/VirtualizationSystemService.java
index 241eef4..998389b 100644
--- a/libs/service-virtualization/src/com/android/system/virtualmachine/VirtualizationSystemService.java
+++ b/libs/service-virtualization/src/com/android/system/virtualmachine/VirtualizationSystemService.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.LinkAddress;
 import android.net.TetheringManager;
 import android.net.TetheringManager.StartTetheringCallback;
 import android.net.TetheringManager.TetheringRequest;
@@ -157,8 +158,11 @@
 
         @Override
         public void enableVmTethering() {
+            LinkAddress local = new LinkAddress("192.168.0.1/24");
+            LinkAddress client = new LinkAddress("192.168.0.2/24");
             final TetheringRequest tr =
                     new TetheringRequest.Builder(TetheringManager.TETHERING_VIRTUAL)
+                            .setStaticIpv4Addresses(local, client)
                             .setConnectivityScope(TetheringManager.CONNECTIVITY_SCOPE_GLOBAL)
                             .build();
 
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 4cca110..3d5c345 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
@@ -86,7 +86,10 @@
 
         Runner runner;
         try {
+            android.os.Trace.beginSection("vmCreate");
             runner = Runner.create(this, config);
+            android.os.Trace.endSection();
+            android.os.Trace.beginAsyncSection("debianBoot", 0);
         } catch (VirtualMachineException e) {
             Log.e(TAG, "cannot create runner", e);
             stopSelf();
@@ -169,6 +172,7 @@
 
     @Override
     public void onIpAddressAvailable(String ipAddr) {
+        android.os.Trace.endAsyncSection("debianBoot", 0);
         Bundle b = new Bundle();
         b.putString(VmLauncherService.KEY_VM_IP_ADDR, ipAddr);
         mResultReceiver.send(VmLauncherService.RESULT_IPADDR, b);