Merge "Refactor clipboard handling into a separate class" into main
diff --git a/android/FerrochromeApp/AndroidManifest.xml b/android/FerrochromeApp/AndroidManifest.xml
index d640c4a..f6d3f6a 100644
--- a/android/FerrochromeApp/AndroidManifest.xml
+++ b/android/FerrochromeApp/AndroidManifest.xml
@@ -12,6 +12,9 @@
         <intent>
             <action android:name="android.virtualization.VM_LAUNCHER" />
         </intent>
+        <intent>
+            <action android:name="android.virtualization.FERROCHROME_DOWNLOADER" />
+        </intent>
     </queries>
     <application
         android:label="Ferrochrome">
diff --git a/android/FerrochromeApp/custom_vm_setup.sh b/android/FerrochromeApp/custom_vm_setup.sh
index a5480ff..4dce0c7 100644
--- a/android/FerrochromeApp/custom_vm_setup.sh
+++ b/android/FerrochromeApp/custom_vm_setup.sh
@@ -7,13 +7,13 @@
 }
 
 function install() {
-  user=$(cmd user get-main-user)
-  src_dir=/data/media/${user}/ferrochrome/
+  src_dir=$(getprop debug.custom_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}*
+  cp -u ${src_dir}/vm_config.json ${dst_dir}
+  chmod 666 ${dst_dir}/*
 
   # increase the size of state.img to the multiple of 4096
   num_blocks=$(du -b -K ${dst_dir}state.img | cut -f 1)
@@ -21,8 +21,8 @@
   additional_blocks=$((( ${required_num_blocks} - ${num_blocks} )))
   dd if=/dev/zero bs=512 count=${additional_blocks} >> ${dst_dir}state.img
 
-  rm ${src_dir}images.tar.gz*
-  rm ${src_dir}vm_config.json
+  rm ${src_dir}/images.tar.gz*
+  rm ${src_dir}/vm_config.json
 }
 
 setprop debug.custom_vm_setup.done false
diff --git a/android/FerrochromeApp/java/com/android/virtualization/ferrochrome/FerrochromeActivity.java b/android/FerrochromeApp/java/com/android/virtualization/ferrochrome/FerrochromeActivity.java
index 2df5cab..dba0078 100644
--- a/android/FerrochromeApp/java/com/android/virtualization/ferrochrome/FerrochromeActivity.java
+++ b/android/FerrochromeApp/java/com/android/virtualization/ferrochrome/FerrochromeActivity.java
@@ -16,14 +16,17 @@
 
 package com.android.virtualization.ferrochrome;
 
+import android.annotation.WorkerThread;
 import android.app.Activity;
 import android.app.ActivityManager;
 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;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.WindowManager;
 import android.widget.TextView;
@@ -43,12 +46,20 @@
 public class FerrochromeActivity extends Activity {
     private static final String TAG = FerrochromeActivity.class.getName();
     private static final String ACTION_VM_LAUNCHER = "android.virtualization.VM_LAUNCHER";
+    private static final String ACTION_FERROCHROME_DOWNLOAD =
+            "android.virtualization.FERROCHROME_DOWNLOADER";
+    private static final String EXTRA_FERROCHROME_DEST_DIR = "dest_dir";
+    private static final String EXTRA_FERROCHROME_UPDATE_NEEDED = "update_needed";
 
     private static final Path DEST_DIR =
             Path.of(Environment.getExternalStorageDirectory().getPath(), "ferrochrome");
+    private static final String ASSET_DIR = "ferrochrome";
     private static final Path VERSION_FILE = Path.of(DEST_DIR.toString(), "version");
 
     private static final int REQUEST_CODE_VMLAUNCHER = 1;
+    private static final int REQUEST_CODE_FERROCHROME_DOWNLOADER = 2;
+
+    private ResolvedActivity mVmLauncher;
 
     ExecutorService executorService = Executors.newSingleThreadExecutor();
 
@@ -66,25 +77,28 @@
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
 
         // Find VM Launcher
-        Intent intent = new Intent(ACTION_VM_LAUNCHER).setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
-        PackageManager pm = getPackageManager();
-        List<ResolveInfo> resolveInfos =
-                pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
-        if (resolveInfos == null || resolveInfos.size() != 1) {
+        mVmLauncher = ResolvedActivity.resolve(getPackageManager(), ACTION_VM_LAUNCHER);
+        if (mVmLauncher == null) {
             updateStatus("Failed to resolve VM Launcher");
             return;
         }
 
         // Clean up the existing vm launcher process if there is
         ActivityManager am = getSystemService(ActivityManager.class);
-        am.killBackgroundProcesses(resolveInfos.get(0).activityInfo.packageName);
+        am.killBackgroundProcesses(mVmLauncher.activityInfo.packageName);
 
         executorService.execute(
                 () -> {
-                    if (updateImageIfNeeded()) {
-                        updateStatus("Starting Ferrochrome...");
-                        runOnUiThread(
-                                () -> startActivityForResult(intent, REQUEST_CODE_VMLAUNCHER));
+                    if (hasLocalAssets()) {
+                        if (updateImageIfNeeded()) {
+                            updateStatus("Starting Ferrochrome...");
+                            runOnUiThread(
+                                    () ->
+                                            startActivityForResult(
+                                                    mVmLauncher.intent, REQUEST_CODE_VMLAUNCHER));
+                        }
+                    } else {
+                        tryLaunchDownloader();
                     }
                 });
     }
@@ -93,9 +107,55 @@
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         if (requestCode == REQUEST_CODE_VMLAUNCHER) {
             finishAndRemoveTask();
+        } else if (requestCode == REQUEST_CODE_FERROCHROME_DOWNLOADER) {
+            String destDir = data.getStringExtra(EXTRA_FERROCHROME_DEST_DIR);
+            boolean updateNeeded =
+                    data.getBooleanExtra(EXTRA_FERROCHROME_UPDATE_NEEDED, /* default= */ true);
+
+            if (resultCode != RESULT_OK || TextUtils.isEmpty(destDir)) {
+                Log.w(
+                        TAG,
+                        "Ferrochrome downloader returned error, code="
+                                + resultCode
+                                + ", dest="
+                                + destDir);
+                updateStatus("User didn't accepted ferrochrome download..");
+                return;
+            }
+
+            Log.w(TAG, "Ferrochrome downloader returned OK");
+
+            if (!updateNeeded) {
+                updateStatus("Starting Ferrochrome...");
+                startActivityForResult(mVmLauncher.intent, REQUEST_CODE_VMLAUNCHER);
+            }
+
+            executorService.execute(
+                    () -> {
+                        if (!extractImages(destDir)) {
+                            updateStatus("Images from downloader looks bad..");
+                            return;
+                        }
+                        updateStatus("Starting Ferrochrome...");
+                        runOnUiThread(
+                                () ->
+                                        startActivityForResult(
+                                                mVmLauncher.intent, REQUEST_CODE_VMLAUNCHER));
+                    });
         }
     }
 
+    @WorkerThread
+    private boolean hasLocalAssets() {
+        try {
+            String[] files = getAssets().list(ASSET_DIR);
+            return files != null && files.length > 0;
+        } catch (IOException e) {
+            return false;
+        }
+    }
+
+    @WorkerThread
     private boolean updateImageIfNeeded() {
         if (!isUpdateNeeded()) {
             Log.d(TAG, "No update needed.");
@@ -107,13 +167,8 @@
                 Files.createDirectory(DEST_DIR);
             }
 
-            String[] files = getAssets().list("ferrochrome");
-            if (files == null || files.length == 0) {
-                updateStatus("ChromeOS image not found. Please go/try-ferrochrome");
-                return false;
-            }
-
             updateStatus("Copying images...");
+            String[] files = getAssets().list("ferrochrome");
             for (String file : files) {
                 updateStatus(file);
                 Path dst = Path.of(DEST_DIR.toString(), file);
@@ -126,7 +181,38 @@
         }
         updateStatus("Done.");
 
+        return extractImages(DEST_DIR.toAbsolutePath().toString());
+    }
+
+    @WorkerThread
+    private void tryLaunchDownloader() {
+        // TODO(jaewan): Add safeguard to check whether ferrochrome downloader is valid.
+        Log.w(TAG, "No built-in assets found. Try again with ferrochrome downloader");
+
+        ResolvedActivity downloader =
+                ResolvedActivity.resolve(getPackageManager(), ACTION_FERROCHROME_DOWNLOAD);
+        if (downloader == null) {
+            Log.d(TAG, "Ferrochrome downloader doesn't exist");
+            updateStatus("ChromeOS image not found. Please go/try-ferrochrome");
+            return;
+        }
+        String pkgName = downloader.activityInfo.packageName;
+        Log.d(TAG, "Resolved Ferrochrome Downloader, pkgName=" + pkgName);
+        updateStatus("Launching Ferrochrome downloader for update");
+
+        // onActivityResult() will handle downloader result.
+        startActivityForResult(downloader.intent, REQUEST_CODE_FERROCHROME_DOWNLOADER);
+    }
+
+    @WorkerThread
+    private boolean extractImages(String destDir) {
         updateStatus("Extracting images...");
+
+        if (TextUtils.isEmpty(destDir)) {
+            throw new RuntimeException("Internal error: destDir shouldn't be null");
+        }
+
+        SystemProperties.set("debug.custom_vm_setup.path", destDir);
         SystemProperties.set("debug.custom_vm_setup.done", "false");
         SystemProperties.set("debug.custom_vm_setup.start", "true");
         while (!SystemProperties.getBoolean("debug.custom_vm_setup.done", false)) {
@@ -143,6 +229,7 @@
         return true;
     }
 
+    @WorkerThread
     private boolean isUpdateNeeded() {
         Path[] pathsToCheck = {DEST_DIR, VERSION_FILE};
         for (Path p : pathsToCheck) {
@@ -188,4 +275,33 @@
                     statusView.append(line + "\n");
                 });
     }
+
+    private static final class ResolvedActivity {
+        public final ActivityInfo activityInfo;
+        public final Intent intent;
+
+        private ResolvedActivity(ActivityInfo activityInfo, Intent intent) {
+            this.activityInfo = activityInfo;
+            this.intent = intent;
+        }
+
+        /* synthetic access */
+        static ResolvedActivity resolve(PackageManager pm, String action) {
+            Intent intent = new Intent(action).setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+            List<ResolveInfo> resolveInfos =
+                    pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+            if (resolveInfos == null || resolveInfos.size() != 1) {
+                Log.w(
+                        TAG,
+                        "Failed to resolve activity, action="
+                                + action
+                                + ", resolved="
+                                + resolveInfos);
+                return null;
+            }
+            ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
+            intent.setClassName(activityInfo.packageName, activityInfo.name);
+            return new ResolvedActivity(activityInfo, intent);
+        }
+    }
 }
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/Logger.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/Logger.java
index ccfff95..e1cb285 100644
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/Logger.java
+++ b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/Logger.java
@@ -41,7 +41,7 @@
     private Logger() {}
 
     static void setup(VirtualMachine vm, Path path, ExecutorService executor) {
-        if (vm.getConfig().getDebugLevel() == VirtualMachineConfig.DEBUG_LEVEL_FULL) {
+        if (vm.getConfig().getDebugLevel() != VirtualMachineConfig.DEBUG_LEVEL_FULL) {
             return;
         }
 
diff --git a/docs/custom_vm.md b/docs/custom_vm.md
index e4b374f..6a1cc00 100644
--- a/docs/custom_vm.md
+++ b/docs/custom_vm.md
@@ -53,7 +53,7 @@
     "name": "debian",
     "disks": [
         {
-            "image": "/data/local/tmp/debian.img
+            "image": "/data/local/tmp/debian.img",
             "partitions": [],
             "writable": true
         }
@@ -306,15 +306,15 @@
 ```
 
 To see console logs only, check
-`/data/data/com{,.google}.android.virtualization.vmlauncher/files/console.log`
+`/data/data/com{,.google}.android.virtualization.vmlauncher/files/${vm_name}.log`
 
 For HSUM enabled devices,
-`/data/user/${current_user_id}/com{,.google}.android.virtualization.vmlauncher/files/console.log`
+`/data/user/${current_user_id}/com{,.google}.android.virtualization.vmlauncher/files/${vm_name}.log`
 
 You can monitor console out as follows
 
 ```shell
-$ adb shell 'su root tail +0 -F /data/user/$(am get-current-user)/com{,.google}.android.virtualization.vmlauncher/files/console.log'
+$ adb shell 'su root tail +0 -F /data/user/$(am get-current-user)/com{,.google}.android.virtualization.vmlauncher/files/${vm_name}.log'
 ```
 
 For ChromiumOS, you can enter to the console via SSH connection. Check your IP
diff --git a/guest/pvmfw/image.ld b/guest/pvmfw/image.ld
index 18bb3ba..fb26806 100644
--- a/guest/pvmfw/image.ld
+++ b/guest/pvmfw/image.ld
@@ -18,5 +18,4 @@
 {
 	image		: ORIGIN = 0x7fc00000, LENGTH = 2M
 	writable_data	: ORIGIN = 0x7fe00000, LENGTH = 2M
-	dtb_region	: ORIGIN = 0x80000000, LENGTH = 2M
 }
diff --git a/guest/pvmfw/src/dice.rs b/guest/pvmfw/src/dice.rs
index 8be73a4..470711f 100644
--- a/guest/pvmfw/src/dice.rs
+++ b/guest/pvmfw/src/dice.rs
@@ -36,8 +36,10 @@
 #[derive(Debug)]
 pub enum Error {
     /// Error in CBOR operations
+    #[allow(dead_code)]
     CborError(ciborium::value::Error),
     /// Error in DICE operations
+    #[allow(dead_code)]
     DiceError(diced_open_dice::DiceError),
 }
 
diff --git a/guest/rialto/image.ld b/guest/rialto/image.ld
index 368acbb..95ffdf8 100644
--- a/guest/rialto/image.ld
+++ b/guest/rialto/image.ld
@@ -16,7 +16,6 @@
 
 MEMORY
 {
-	dtb_region	: ORIGIN = 0x80000000, LENGTH = 2M
 	image		: ORIGIN = 0x80200000, LENGTH = 2M
 	writable_data	: ORIGIN = 0x80400000, LENGTH = 2M
 }
diff --git a/libs/dice/open_dice/Android.bp b/libs/dice/open_dice/Android.bp
index 62504d1..efe350f 100644
--- a/libs/dice/open_dice/Android.bp
+++ b/libs/dice/open_dice/Android.bp
@@ -92,6 +92,9 @@
         "--ctypes-prefix=core::ffi",
         "--raw-line=#![no_std]",
     ],
+    dylib: {
+        enabled: false,
+    },
     no_stdlibs: true,
     prefer_rlib: true,
     stdlibs: [
diff --git a/libs/libfdt/Android.bp b/libs/libfdt/Android.bp
index 7dc9e64..b2e7b2b 100644
--- a/libs/libfdt/Android.bp
+++ b/libs/libfdt/Android.bp
@@ -16,6 +16,9 @@
         "--raw-line=#![no_std]",
         "--ctypes-prefix=core::ffi",
     ],
+    dylib: {
+        enabled: false,
+    },
     static_libs: [
         "libfdt",
     ],
diff --git a/libs/libvmbase/example/image.ld b/libs/libvmbase/example/image.ld
index 368acbb..95ffdf8 100644
--- a/libs/libvmbase/example/image.ld
+++ b/libs/libvmbase/example/image.ld
@@ -16,7 +16,6 @@
 
 MEMORY
 {
-	dtb_region	: ORIGIN = 0x80000000, LENGTH = 2M
 	image		: ORIGIN = 0x80200000, LENGTH = 2M
 	writable_data	: ORIGIN = 0x80400000, LENGTH = 2M
 }
diff --git a/libs/libvmbase/example/src/layout.rs b/libs/libvmbase/example/src/layout.rs
index fc578bc..49e4aa7 100644
--- a/libs/libvmbase/example/src/layout.rs
+++ b/libs/libvmbase/example/src/layout.rs
@@ -29,8 +29,6 @@
 }
 
 pub fn print_addresses() {
-    let dtb = layout::dtb_range();
-    info!("dtb:        {}..{} ({} bytes)", dtb.start, dtb.end, dtb.end - dtb.start);
     let text = layout::text_range();
     info!("text:       {}..{} ({} bytes)", text.start, text.end, text.end - text.start);
     let rodata = layout::rodata_range();
diff --git a/libs/libvmbase/example/src/main.rs b/libs/libvmbase/example/src/main.rs
index da82b17..a01f619 100644
--- a/libs/libvmbase/example/src/main.rs
+++ b/libs/libvmbase/example/src/main.rs
@@ -26,6 +26,7 @@
 use crate::layout::{boot_stack_range, print_addresses, DEVICE_REGION};
 use crate::pci::{check_pci, get_bar_region};
 use aarch64_paging::paging::MemoryRegion;
+use aarch64_paging::paging::VirtualAddress;
 use aarch64_paging::MapError;
 use alloc::{vec, vec::Vec};
 use core::ptr::addr_of_mut;
@@ -35,7 +36,10 @@
 use log::{debug, error, info, trace, warn, LevelFilter};
 use vmbase::{
     bionic, configure_heap,
-    layout::{dtb_range, rodata_range, scratch_range, text_range},
+    layout::{
+        crosvm::{FDT_MAX_SIZE, MEM_START},
+        rodata_range, scratch_range, text_range,
+    },
     linker, logger, main,
     memory::{PageTable, SIZE_64KB},
 };
@@ -47,7 +51,7 @@
 main!(main);
 configure_heap!(SIZE_64KB);
 
-fn init_page_table(pci_bar_range: &MemoryRegion) -> Result<(), MapError> {
+fn init_page_table(dtb: &MemoryRegion, pci_bar_range: &MemoryRegion) -> Result<(), MapError> {
     let mut page_table = PageTable::default();
 
     page_table.map_device(&DEVICE_REGION)?;
@@ -55,7 +59,7 @@
     page_table.map_rodata(&rodata_range().into())?;
     page_table.map_data(&scratch_range().into())?;
     page_table.map_data(&boot_stack_range().into())?;
-    page_table.map_rodata(&dtb_range().into())?;
+    page_table.map_rodata(dtb)?;
     page_table.map_device(pci_bar_range)?;
 
     info!("Activating IdMap...");
@@ -76,15 +80,16 @@
     info!("Hello world");
     info!("x0={:#018x}, x1={:#018x}, x2={:#018x}, x3={:#018x}", arg0, arg1, arg2, arg3);
     print_addresses();
-    assert_eq!(arg0, dtb_range().start.0 as u64);
     check_data();
     check_stack_guard();
 
     info!("Checking FDT...");
-    let fdt = dtb_range();
-    let fdt_size = fdt.end.0 - fdt.start.0;
+    let fdt_addr = usize::try_from(arg0).unwrap();
+    // We are about to access the region so check that it matches our page tables in idmap.S.
+    assert_eq!(fdt_addr, MEM_START);
     // SAFETY: The DTB range is valid, writable memory, and we don't construct any aliases to it.
-    let fdt = unsafe { core::slice::from_raw_parts_mut(fdt.start.0 as *mut u8, fdt_size) };
+    let fdt = unsafe { core::slice::from_raw_parts_mut(fdt_addr as *mut u8, FDT_MAX_SIZE) };
+    let fdt_region = (VirtualAddress(fdt_addr)..VirtualAddress(fdt_addr + fdt.len())).into();
     let fdt = Fdt::from_mut_slice(fdt).unwrap();
     info!("FDT passed verification.");
     check_fdt(fdt);
@@ -96,7 +101,7 @@
 
     check_alloc();
 
-    init_page_table(&get_bar_region(&pci_info)).unwrap();
+    init_page_table(&fdt_region, &get_bar_region(&pci_info)).unwrap();
 
     check_data();
     check_dice();
diff --git a/libs/libvmbase/sections.ld b/libs/libvmbase/sections.ld
index c7ef0ec..01b7e39 100644
--- a/libs/libvmbase/sections.ld
+++ b/libs/libvmbase/sections.ld
@@ -29,12 +29,6 @@
 
 SECTIONS
 {
-	.dtb (NOLOAD) : {
-		dtb_begin = .;
-		. += LENGTH(dtb_region);
-		dtb_end = .;
-	} >dtb_region
-
 	/*
 	 * Collect together the code. This is page aligned so it can be mapped
 	 * as executable-only.
diff --git a/libs/libvmbase/src/layout.rs b/libs/libvmbase/src/layout.rs
index 5ac435f..adcb2fa 100644
--- a/libs/libvmbase/src/layout.rs
+++ b/libs/libvmbase/src/layout.rs
@@ -60,11 +60,6 @@
     }};
 }
 
-/// Memory reserved for the DTB.
-pub fn dtb_range() -> Range<VirtualAddress> {
-    linker_region!(dtb_begin, dtb_end)
-}
-
 /// Executable code.
 pub fn text_range() -> Range<VirtualAddress> {
     linker_region!(text_begin, text_end)
diff --git a/tests/ferrochrome/AndroidTest.xml b/tests/ferrochrome/AndroidTest.xml
index 9eaaed3..6c975be 100644
--- a/tests/ferrochrome/AndroidTest.xml
+++ b/tests/ferrochrome/AndroidTest.xml
@@ -35,13 +35,19 @@
         <option name="run-command" value="mkdir /data/local/tmp" />
         <option name="teardown-command" value="pkill vmlauncher" />
         <option name="teardown-command" value="rm /data/local/tmp/chromiumos_base_image.bin" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/ferrochrome_screenshots" />
     </target_preparer>
 
-    <test class="com.android.tradefed.testtype.binary.ExecutableHostTest" >
+    <test class="com.android.tradefed.testtype.binary.ExecutableHostTest">
         <option name="binary" value="ferrochrome-tests" />
         <option name="relative-path-execution" value="true" />
         <option name="runtime-hint" value="10m" />
         <option name="per-binary-timeout" value="20m" />
     </test>
+
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys" value="/data/local/tmp/ferrochrome_screenshots" />
+        <option name="collect-on-run-ended-only" value="true" />
+    </metrics_collector>
 </configuration>
 
diff --git a/tests/ferrochrome/ferrochrome.sh b/tests/ferrochrome/ferrochrome.sh
index 683b82e..b9b9fbc 100755
--- a/tests/ferrochrome/ferrochrome.sh
+++ b/tests/ferrochrome/ferrochrome.sh
@@ -21,12 +21,13 @@
 
 FECR_GS_URL="https://storage.googleapis.com/chromiumos-image-archive/ferrochrome-public"
 FECR_DEFAULT_VERSION="R128-15958.0.0"
+FECR_DEFAULT_SCREENSHOT_DIR="/data/local/tmp/ferrochrome_screenshots"  # Hardcoded at AndroidTest.xml
 FECR_TEST_IMAGE="chromiumos_test_image"
 FECR_BASE_IMAGE="chromiumos_base_image"
 FECR_DEVICE_DIR="/data/local/tmp"
 FECR_IMAGE_VM_CONFIG_JSON="chromiumos_base_image.bin"  # hardcoded at vm_config.json
 FECR_CONFIG_PATH="/data/local/tmp/vm_config.json"  # hardcoded at VmLauncherApp
-FECR_CONSOLE_LOG_PATH="/data/data/\${pkg_name}/files/console.log"
+FECR_CONSOLE_LOG_PATH="files/cros.log" # log file name is ${vm_name}.log
 FECR_TEST_IMAGE_BOOT_COMPLETED_LOG="Have fun and send patches!"
 FECR_BASE_IMAGE_BOOT_COMPLETED_LOG="Chrome started, our work is done, exiting"
 FECR_BOOT_TIMEOUT="300" # 5 minutes (300 seconds)
@@ -74,6 +75,7 @@
 fecr_verbose=""
 fecr_image="${FECR_DEFAULT_IMAGE}"
 fecr_boot_completed_log="${FECR_DEFAULT_BOOT_COMPLETED_LOG}"
+fecr_screenshot_dir="${FECR_DEFAULT_SCREENSHOT_DIR}"
 
 # Parse parameters
 while (( "${#}" )); do
@@ -153,18 +155,20 @@
 adb shell svc power stayon true
 adb shell wm dismiss-keyguard
 
+echo "Granting runtime permissions to ensure VmLauncher is focused"
+adb shell pm grant ${pkg_name} android.permission.RECORD_AUDIO
+
 echo "Starting ferrochrome"
 adb shell am start-activity -a ${ACTION_NAME} > /dev/null
 
-if [[ $(adb shell getprop ro.fw.mu.headless_system_user) == "true" ]]; then
-  current_user=$(adb shell am get-current-user)
-  log_path="/data/user/${current_user}/${pkg_name}/files/console.log"
-else
-  log_path="/data/data/${pkg_name}/files/console.log"
-fi
+# HSUM aware log path
+current_user=$(adb shell am get-current-user)
+log_path="/data/user/${current_user}/${pkg_name}/${FECR_CONSOLE_LOG_PATH}"
 fecr_start_time=${EPOCHSECONDS}
 
+adb shell mkdir -p "${fecr_screenshot_dir}"
 while [[ $((EPOCHSECONDS - fecr_start_time)) -lt ${FECR_BOOT_TIMEOUT} ]]; do
+  adb shell screencap -a -p "${fecr_screenshot_dir}/screenshot-${EPOCHSECONDS}.png"
   adb shell grep -soF \""${fecr_boot_completed_log}"\" "${log_path}" && exit 0 || true
   sleep 10
 done