Merge "ferrochrome-tests: Ensure VmLauncherApp activity is focused" 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/docs/custom_vm.md b/docs/custom_vm.md
index e4b374f..3d434ed 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
         }
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),
 }