Merge "Set droid's gid 1000" into main
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index 018916d..84c3eee 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -17,11 +17,15 @@
 
 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.accessibility.AccessibilityManager;
 import android.webkit.WebChromeClient;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
@@ -34,8 +38,15 @@
 
 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;
+
+public class MainActivity extends AppCompatActivity
+        implements VmLauncherServices.VmLauncherServiceCallback,
+                AccessibilityManager.TouchExplorationStateChangeListener {
     private static final String TAG = "VmTerminalApp";
     private String mVmIpAddr;
     private WebView mWebView;
@@ -43,7 +54,19 @@
     @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);
@@ -62,16 +85,121 @@
                         view.loadUrl(url);
                         return true;
                     }
+
+                    @Override
+                    public void onPageFinished(WebView view, String url) {
+                        android.os.Trace.endAsyncSection("executeTerminal", 0);
+                    }
                 });
+
+        getSystemService(AccessibilityManager.class).addTouchExplorationStateChangeListener(this);
+    }
+
+    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);
+        }
     }
 
     @Override
     protected void onDestroy() {
+        getSystemService(AccessibilityManager.class).removeTouchExplorationStateChangeListener(this);
         VmLauncherServices.stopVmLauncherService(this);
         super.onDestroy();
     }
 
-    private void gotoURL(String url) {
+    private void gotoTerminalURL() {
+        if (mVmIpAddr == null) {
+            Log.d(TAG, "ip addr is not set yet");
+            return;
+        }
+
+        boolean isTouchExplorationEnabled =
+                getSystemService(AccessibilityManager.class).isTouchExplorationEnabled();
+
+        String url =
+                "http://"
+                        + mVmIpAddr
+                        + ":7681/"
+                        + (isTouchExplorationEnabled ? "?screenReaderMode=true" : "");
         runOnUiThread(() -> mWebView.loadUrl(url));
     }
 
@@ -98,7 +226,7 @@
     public void onIpAddrAvailable(String ipAddr) {
         mVmIpAddr = ipAddr;
         ((TextView) findViewById(R.id.ip_addr_textview)).setText(mVmIpAddr);
-        gotoURL("http://" + mVmIpAddr + ":7681");
+        gotoTerminalURL();
     }
 
     @Override
@@ -126,4 +254,9 @@
         }
         return super.onOptionsItemSelected(item);
     }
+
+    @Override
+    public void onTouchExplorationStateChanged(boolean enabled) {
+        gotoTerminalURL();
+    }
 }
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/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);