Merge pie-platform-release to aosp-master - DO NOT MERGE

Change-Id: I45c04d3a035af1cfa2b379cb9025b5c52eb1302f
diff --git a/Android.mk b/Android.mk
index 817bb53..8f07553 100644
--- a/Android.mk
+++ b/Android.mk
@@ -26,30 +26,31 @@
 LOCAL_JAVA_RESOURCE_DIRS := resources
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-  caliper-host \
-  caliper-gson-host \
+  caliper \
+  caliper-gson \
   guavalib \
-  junit-host \
+  junit \
   vogar-jsr305 \
   vogar-kxml-libcore-20110123
 
 LOCAL_ADDITIONAL_DEPENDENCIES := \
   $(HOST_OUT_EXECUTABLES)/dx \
   $(HOST_OUT_EXECUTABLES)/d8-compat-dx \
-  $(HOST_OUT_EXECUTABLES)/adb \
   $(HOST_OUT_JAVA_LIBRARIES)/desugar.jar
 
 # Vogar uses android.jar.
-LOCAL_CLASSPATH := prebuilts/sdk/9/android.jar
+LOCAL_CLASSPATH := $(call resolve-prebuilt-sdk-jar-path,current)
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 
 include $(BUILD_HOST_JAVA_LIBRARY)
 
 # build vogar tests jar
 # ============================================================
-# Run the tests using the following command:
-# java -cp ${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/vogar-tests.jar \
-       org.junit.runner.JUnitCore vogar.AllTests
+# Run the tests using using the following target.
+.PHONY: run-vogar-tests
+run-vogar-tests: vogar-tests
+	java -cp ./out/host/linux-x86/framework/vogar-tests.jar \
+	  org.junit.runner.JUnitCore vogar.AllTests
 
 include $(CLEAR_VARS)
 
@@ -59,9 +60,9 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, test/)
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-	junit-host \
-	mockito-host \
-	objenesis-host \
+	junit \
+	mockito \
+	objenesis \
 	vogar
 
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
diff --git a/src/vogar/Driver.java b/src/vogar/Driver.java
index cbb7365..53fcf2a 100644
--- a/src/vogar/Driver.java
+++ b/src/vogar/Driver.java
@@ -89,7 +89,8 @@
             Outcome outcome = outcomes.get(action.getName());
             if (outcome != null) {
                 addEarlyResult(outcome);
-            } else if (run.expectationStore.get(action.getName()).getResult() == Result.UNSUPPORTED) {
+            } else if (run.expectationStore.get(action.getName()).getResult()
+                    == Result.UNSUPPORTED) {
                 addEarlyResult(new Outcome(action.getName(), Result.UNSUPPORTED,
                     "Unsupported according to expectations file"));
             } else {
diff --git a/src/vogar/FileCache.java b/src/vogar/FileCache.java
index b25a63a..c54a1dd 100644
--- a/src/vogar/FileCache.java
+++ b/src/vogar/FileCache.java
@@ -24,7 +24,7 @@
 public interface FileCache {
 
     boolean existsInCache(String key);
-    
+
     void copyToCache(File source, String key);
 
     void copyFromCache(String key, File destination);
diff --git a/src/vogar/ModeId.java b/src/vogar/ModeId.java
index 54b3ce0..7a79ce1 100644
--- a/src/vogar/ModeId.java
+++ b/src/vogar/ModeId.java
@@ -42,7 +42,7 @@
             "telephony-common",
             "voip-common",
             "ims-common",
-            "org.apache.http.legacy.boot",
+            "org.apache.http.legacy.impl",
             "android.hidl.base-V1.0-java",
             "android.hidl.manager-V1.0-java"
             // TODO: get this list programatically
@@ -54,6 +54,7 @@
     private static final String[] DEVICE_JARS = new String[] {
             "core-oj",
             "core-libart",
+            "core-simple",
             "conscrypt",
             "okhttp",
             "bouncycastle",
@@ -67,6 +68,7 @@
     private static final String[] HOST_JARS = new String[] {
             "core-oj-hostdex",
             "core-libart-hostdex",
+            "core-simple-hostdex",
             "conscrypt-hostdex",
             "okhttp-hostdex",
             "bouncycastle-hostdex",
@@ -104,6 +106,12 @@
                 || ((this == HOST || this == DEVICE) && (variant == Variant.X64));
     }
 
+    /** Does this mode support chroot-based execution? */
+    public boolean supportsChroot() {
+        // We only support execution from a chroot directory in device mode for now.
+        return this == ModeId.DEVICE;
+    }
+
     public boolean supportsToolchain(Toolchain toolchain) {
         return (this == JVM && toolchain == Toolchain.JAVAC)
                 || (this != JVM && toolchain != Toolchain.JAVAC);
diff --git a/src/vogar/Run.java b/src/vogar/Run.java
index cbfab20..4a61a76 100644
--- a/src/vogar/Run.java
+++ b/src/vogar/Run.java
@@ -106,8 +106,8 @@
     public final boolean checkJni;
     public final boolean debugging;
 
-    public Run(Vogar vogar, Toolchain toolchain, Console console, Mkdir mkdir, AndroidSdk androidSdk,
-            Rm rm, Target target, File runnerDir)
+    public Run(Vogar vogar, Toolchain toolchain, Console console, Mkdir mkdir,
+            AndroidSdk androidSdk, Rm rm, Target target, File runnerDir)
             throws IOException {
         this.console = console;
 
diff --git a/src/vogar/Target.java b/src/vogar/Target.java
index 8ce93a9..30d88a5 100644
--- a/src/vogar/Target.java
+++ b/src/vogar/Target.java
@@ -30,7 +30,48 @@
  * A target runtime environment such as a remote device or the local host
  */
 public abstract class Target {
+
+    /**
+     * Return the process prefix for this target.
+     * <p>
+     * A process prefix is a list of tokens added in front of a script (VM command) to be run on
+     * this target.
+     *
+     * @see AdbTarget#targetProcessPrefix()
+     * @see AdbChrootTarget#targetProcessPrefix()
+     * @see SshTarget#targetProcessPrefix()
+     *
+     * @see Target#targetProcessWrapper()
+     * @see ScriptBuilder#commandLine()
+     */
+
     protected abstract ImmutableList<String> targetProcessPrefix();
+
+    /**
+     * Return the process wrapper for this target if there is one, or {@code null} otherwise.
+     * <p>
+     * A process wrapper is a command that is used to execute a script (VM command) to be run on
+     * this target. The script, preceded by the process prefix, is surrounded with double quotes
+     * and passed as an argument to the process wrapper:
+     * <ul>
+     *   <li>{@code <process-wrapper> "<process-prefix> <script>"}
+     * </ul>
+     * A {@code null} process wrapper means that the script will be executed as-is (but still
+     * preceded by the process prefix):
+     * <ul>
+     *   <li>{@code <process-prefix> <script>}
+     * </ul>
+     *
+     * @see AdbChrootTarget#targetProcessWrapper()
+     *
+     * @see Target#targetProcessPrefix()
+     * @see ScriptBuilder#commandLine()
+     */
+    protected String targetProcessWrapper() {
+        // By default, targets don't have a process wrapper.
+        return null;
+    };
+
     public abstract String getDeviceUserName();
 
     public abstract List<File> ls(File directory) throws FileNotFoundException;
@@ -63,7 +104,7 @@
      * Create a {@link ScriptBuilder} appropriate for this target.
      */
     public ScriptBuilder newScriptBuilder() {
-        return new ScriptBuilder(targetProcessPrefix());
+        return new ScriptBuilder(targetProcessPrefix(), targetProcessWrapper());
     }
 
     /**
@@ -108,13 +149,20 @@
         private final ImmutableList<String> commandLinePrefix;
 
         /**
+         * An optional wrapper of the command line. This can be used e.g. to wrap 'COMMAND' into
+         * 'sh -c "COMMAND"' before execution.
+         */
+        private final String commandLineWrapper;
+
+        /**
          * The list of tokens making up the script, they were escaped where necessary before they
          * were added to the list.
          */
         private final List<String> escapedTokens;
 
-        private ScriptBuilder(ImmutableList<String> commandLinePrefix) {
+        private ScriptBuilder(ImmutableList<String> commandLinePrefix, String commandLineWrapper) {
             this.commandLinePrefix = commandLinePrefix;
+            this.commandLineWrapper = commandLineWrapper;
             escapedTokens = new ArrayList<>();
         }
 
@@ -175,6 +223,10 @@
             // adb or ssh shells as they both concatenate all their arguments into one single
             // string before parsing.
             String grouped = SCRIPT_JOINER.join(escapedTokens);
+            // Honor a wrapper if there is one.
+            if (commandLineWrapper != null) {
+                grouped = commandLineWrapper + " \"" + grouped + "\"";
+            }
             return new ImmutableList.Builder<String>()
                     .addAll(commandLinePrefix)
                     .add(grouped)
diff --git a/src/vogar/Vogar.java b/src/vogar/Vogar.java
index 86d0d52..88277d3 100644
--- a/src/vogar/Vogar.java
+++ b/src/vogar/Vogar.java
@@ -27,6 +27,7 @@
 import java.util.Set;
 import java.util.LinkedHashSet;
 
+import vogar.android.AdbChrootTarget;
 import vogar.android.AdbTarget;
 import vogar.android.AndroidSdk;
 import vogar.android.DeviceFileCache;
@@ -69,6 +70,9 @@
     @Option(names = { "--ssh" })
     private String sshHost;
 
+    @Option(names = { "--chroot" })
+    private String chrootDir;
+
     @Option(names = { "--timeout" })
     int timeoutSeconds = 60; // default is one minute;
 
@@ -240,6 +244,9 @@
         System.out.println();
         System.out.println("  --ssh <host:port>: target a remote machine via SSH.");
         System.out.println();
+        System.out.println("  --chroot <dir>: target a chroot dir on device");
+        System.out.println("      Only works with --mode device.");
+        System.out.println();
         System.out.println("  --clean: synonym for --clean-before and --clean-after (default).");
         System.out.println("      Disable with --no-clean if you want no files removed.");
         System.out.println();
@@ -452,6 +459,11 @@
             return false;
         }
 
+        if (chrootDir != null && !modeId.supportsChroot()) {
+            System.out.println("Chroot-based execution not supported for mode " + modeId);
+            return false;
+        }
+
         if (xmlReportsDirectory != null && !xmlReportsDirectory.isDirectory()) {
             System.out.println("Invalid XML reports directory: " + xmlReportsDirectory);
             return false;
@@ -543,6 +555,7 @@
      */
     private enum TargetType {
         ADB(AdbTarget.defaultDeviceDir()),
+        ADB_CHROOT(AdbChrootTarget.defaultDeviceDir()),
         LOCAL(LocalTarget.defaultDeviceDir()),
         SSH(SshTarget.defaultDeviceDir());
 
@@ -580,6 +593,8 @@
             targetType = TargetType.SSH;
         } else if (modeId.isLocal()) {
             targetType = TargetType.LOCAL;
+        } else if (chrootDir != null) {
+            targetType = TargetType.ADB_CHROOT;
         } else {
             targetType = TargetType.ADB;
         }
@@ -591,12 +606,27 @@
         // Create the target.
         Target target;
         switch (targetType) {
-            case ADB:
-                DeviceFilesystem deviceFilesystem =
-                        new DeviceFilesystem(console, ImmutableList.of("adb", "shell"));
-                DeviceFileCache deviceFileCache =
-                        new DeviceFileCache(console, runnerDir, deviceFilesystem);
-                target = new AdbTarget(console, deviceFilesystem, deviceFileCache);
+            case ADB: {
+                    ImmutableList<String> targetProcessPrefix = ImmutableList.of("adb", "shell");
+                    DeviceFilesystem deviceFilesystem =
+                            new DeviceFilesystem(console, targetProcessPrefix);
+                    DeviceFileCache deviceFileCache =
+                            new DeviceFileCache(console, runnerDir, deviceFilesystem);
+                    target = new AdbTarget(console, deviceFilesystem, deviceFileCache);
+                }
+                break;
+            case ADB_CHROOT: {
+                    ImmutableList<String> targetProcessPrefix = ImmutableList.of("adb", "shell");
+                    DeviceFilesystem deviceFilesystem =
+                            new DeviceFilesystem(console, targetProcessPrefix);
+                    // Directory `runnerDir` is relative to the chroot; `runnerDirInRoot` is its
+                    // counterpart relative to the device's filesystem "absolute" root.
+                    File runnerDirInRoot = new File(chrootDir + "/" + runnerDir.getPath());
+                    DeviceFileCache deviceFileCache =
+                            new DeviceFileCache(console, runnerDirInRoot, deviceFilesystem);
+                    target =
+                        new AdbChrootTarget(console, deviceFilesystem, deviceFileCache, chrootDir);
+                }
                 break;
             case SSH:
                 target = new SshTarget(console, sshHost);
diff --git a/src/vogar/android/AdbChrootTarget.java b/src/vogar/android/AdbChrootTarget.java
new file mode 100644
index 0000000..42ed491
--- /dev/null
+++ b/src/vogar/android/AdbChrootTarget.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2018 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 vogar.android;
+
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.List;
+import java.util.ListIterator;
+import vogar.Log;
+import vogar.android.AdbTarget;
+import vogar.commands.Command;
+
+/**
+ * A class similar to AdbTarget, but working relatively to a chroot
+ * directory (instead of the whole system under the root directory on
+ * the device).
+ *
+ * All "remote" pathnames (as java.io.File objects) passed as
+ * arguments to methods below and/or returned by them are relative to
+ * the chroot directory, except when they represent "local" (host)
+ * files.
+ *
+ * @see AdbTarget
+ */
+public final class AdbChrootTarget extends AdbTarget {
+
+    private final String chrootDir;
+
+    private final ImmutableList<String> targetProcessPrefixList;
+
+    private static String TARGET_PROCESS_WRAPPER = "sh -c";
+
+    public AdbChrootTarget(Log log, DeviceFilesystem deviceFilesystem,
+            DeviceFileCache deviceFileCache, String chrootDir) {
+        super(log, deviceFilesystem, deviceFileCache);
+        this.chrootDir = chrootDir;
+        this.targetProcessPrefixList = ImmutableList.of("adb", "shell", "chroot", chrootDir);
+    }
+
+    @Override
+    public void await(File directory) {
+        super.await(chrootToRoot(directory));
+    }
+
+    @Override
+    public List<File> ls(File directory) throws FileNotFoundException {
+        // Add chroot prefix to searched directory.
+        List<File> files = super.ls(chrootToRoot(directory));
+        // Remove chroot prefix from files found in directory.
+        ListIterator<File> iterator = files.listIterator();
+        while (iterator.hasNext()){
+            File file = iterator.next();
+            try {
+                iterator.set(rootToChroot(file));
+            } catch (PathnameNotUnderChrootException e) {
+                // This should never happen, as `file` is derived from a chroot-based pathname.
+                throw new Error(e);
+            }
+        }
+        return files;
+    }
+
+    @Override
+    public void rm(File file) {
+        super.rm(chrootToRoot(file));
+    }
+
+    @Override
+    public void mkdirs(File file) {
+        super.mkdirs(chrootToRoot(file));
+    }
+
+    @Override
+    public void push(File local, File remote) {
+        super.push(local, chrootToRoot(remote));
+    }
+
+    @Override
+    public void pull(File remote, File local) {
+        super.pull(chrootToRoot(remote), local);
+    }
+
+    @Override
+    protected ImmutableList<String> targetProcessPrefix() {
+        return targetProcessPrefixList;
+    }
+
+    @Override
+    protected String targetProcessWrapper() {
+        return TARGET_PROCESS_WRAPPER;
+    }
+
+    /**
+     * Convert a file relative to the chroot dir to a file relative to
+     * the device's filesystem "absolute" root.
+     */
+    private File chrootToRoot(File file) {
+        return new File(chrootDir + "/" + file.getPath());
+    }
+
+    /**
+     * Convert a file relative to the device's filesystem "absolute"
+     * root to a file relative to the chroot dir .
+     */
+    private File rootToChroot(File file) throws PathnameNotUnderChrootException {
+        String pathname = file.getPath();
+        if (!pathname.startsWith(chrootDir)) {
+            throw new PathnameNotUnderChrootException(pathname, chrootDir);
+        }
+        return new File(pathname.substring(chrootDir.length()));
+    }
+
+    /**
+     * Exception thrown when a pathname does not represent a file
+     * under the chroot directory.
+     */
+    private class PathnameNotUnderChrootException extends Exception {
+
+        public PathnameNotUnderChrootException(String pathname, String chrootDir) {
+            super("Pathname " + pathname + " does not represent a file under chroot directory "
+                + chrootDir);
+        }
+    }
+
+}
diff --git a/src/vogar/android/AdbTarget.java b/src/vogar/android/AdbTarget.java
index 48473fd..44bf868 100644
--- a/src/vogar/android/AdbTarget.java
+++ b/src/vogar/android/AdbTarget.java
@@ -29,12 +29,12 @@
 import vogar.Target;
 import vogar.commands.Command;
 
-public final class AdbTarget extends Target {
+public class AdbTarget extends Target {
 
     private static final ImmutableList<String> TARGET_PROCESS_PREFIX =
             ImmutableList.of("adb", "shell");
 
-    private final Log log;
+    protected final Log log;
 
     private final DeviceFilesystem deviceFilesystem;
 
diff --git a/src/vogar/android/AndroidSdk.java b/src/vogar/android/AndroidSdk.java
index 01c4e07..b19f404 100644
--- a/src/vogar/android/AndroidSdk.java
+++ b/src/vogar/android/AndroidSdk.java
@@ -132,10 +132,10 @@
             }
         } else if ("bin".equals(buildToolDirString)) {
             log.verbose("Using android source build mode to find dependencies.");
-            String tmpJarPath = "prebuilts/sdk/current/android.jar";
+            String tmpJarPath = "prebuilts/sdk/current/public/android.jar";
             String androidBuildTop = System.getenv("ANDROID_BUILD_TOP");
             if (!com.google.common.base.Strings.isNullOrEmpty(androidBuildTop)) {
-                tmpJarPath = androidBuildTop + "/prebuilts/sdk/current/android.jar";
+                tmpJarPath = androidBuildTop + "/prebuilts/sdk/current/public/android.jar";
             } else {
                 log.warn("Assuming current directory is android build tree root.");
             }
diff --git a/src/vogar/android/DeviceFileCache.java b/src/vogar/android/DeviceFileCache.java
index 5d4a131..4625e93 100644
--- a/src/vogar/android/DeviceFileCache.java
+++ b/src/vogar/android/DeviceFileCache.java
@@ -38,6 +38,7 @@
         this.deviceFilesystem = deviceFilesystem;
     }
 
+    @Override
     public boolean existsInCache(String key) {
         if (cachedFiles == null) {
             try {
@@ -53,11 +54,13 @@
         return cachedFiles.contains(cachedFile);
     }
 
+    @Override
     public void copyFromCache(String key, File destination) {
         File cachedFile = new File(cacheRoot, key);
         cp(cachedFile, destination);
     }
 
+    @Override
     public void copyToCache(File source, String key) {
         File cachedFile = new File(cacheRoot, key);
         deviceFilesystem.mkdirs(cacheRoot);
diff --git a/src/vogar/android/DeviceFilesystem.java b/src/vogar/android/DeviceFilesystem.java
index 4dad4ca..65c1cbd 100644
--- a/src/vogar/android/DeviceFilesystem.java
+++ b/src/vogar/android/DeviceFilesystem.java
@@ -93,7 +93,10 @@
                 .execute();
         List<File> files = new ArrayList<File>();
         for (String fileString : rawResult) {
-            if (fileString.equals(dir.getPath() + ": No such file or directory")) {
+            // Try to match outputs like:
+            //
+            //   ls: <path>: No such file or directory
+            if (fileString.contains(dir.getPath() + ": No such file or directory")) {
                 throw new FileNotFoundException(dir + " not found.");
             }
             if (fileString.equals(dir.getPath())) {
diff --git a/src/vogar/tasks/RetrieveFilesTask.java b/src/vogar/tasks/RetrieveFilesTask.java
index de71266..aaf93e6 100644
--- a/src/vogar/tasks/RetrieveFilesTask.java
+++ b/src/vogar/tasks/RetrieveFilesTask.java
@@ -38,7 +38,7 @@
     }
 
     /**
-     * Scans {@code dir} for files to grab.
+     * Scans directory {@code source} for files to grab.
      */
     private void retrieveFiles(File destination, File source, FileFilter filenameFilter)
             throws FileNotFoundException {
diff --git a/test/vogar/android/HostRuntimeLocalTargetTest.java b/test/vogar/android/HostRuntimeLocalTargetTest.java
index 995acda..8330c6f 100644
--- a/test/vogar/android/HostRuntimeLocalTargetTest.java
+++ b/test/vogar/android/HostRuntimeLocalTargetTest.java
@@ -82,6 +82,7 @@
                         + " -Xbootclasspath"
                         + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/core-oj-hostdex.jar"
                         + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/core-libart-hostdex.jar"
+                        + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/core-simple-hostdex.jar"
                         + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/conscrypt-hostdex.jar"
                         + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/okhttp-hostdex.jar"
                         + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/bouncycastle-hostdex.jar"
@@ -137,6 +138,7 @@
                         + " -Xbootclasspath"
                         + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/core-oj-hostdex.jar"
                         + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/core-libart-hostdex.jar"
+                        + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/core-simple-hostdex.jar"
                         + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/conscrypt-hostdex.jar"
                         + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/okhttp-hostdex.jar"
                         + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/bouncycastle-hostdex.jar"