Merge "Allow test-arg to give option to target preparer"
diff --git a/device_build_interfaces/com/android/tradefed/device/INativeDevice.java b/device_build_interfaces/com/android/tradefed/device/INativeDevice.java
index 9ec2270..808c5fd 100644
--- a/device_build_interfaces/com/android/tradefed/device/INativeDevice.java
+++ b/device_build_interfaces/com/android/tradefed/device/INativeDevice.java
@@ -125,6 +125,16 @@
     public boolean setProperty(String propKey, String propValue) throws DeviceNotAvailableException;
 
     /**
+     * Retrieve the given fastboot variable value from the device.
+     *
+     * @param variableName the variable name
+     * @return the property value or <code>null</code> if it does not exist
+     * @throws DeviceNotAvailableException, UnsupportedOperationException
+     */
+    public String getFastbootVariable(String variableName)
+            throws DeviceNotAvailableException, UnsupportedOperationException;
+
+    /**
      * Convenience method to get the bootloader version of this device.
      * <p/>
      * Will attempt to retrieve bootloader version from the device's current state. (ie if device
@@ -925,6 +935,16 @@
     public void reboot(@Nullable String reason) throws DeviceNotAvailableException;
 
     /**
+     * Reboots the device into fastbootd mode.
+     *
+     * <p>Blocks until device is in fastbootd mode.
+     *
+     * @throws DeviceNotAvailableException if connection with device is lost and cannot be
+     *     recovered.
+     */
+    public void rebootIntoFastbootd() throws DeviceNotAvailableException;
+
+    /**
      * Reboots only userspace part of device.
      *
      * <p>Blocks until device becomes available.
diff --git a/proto/test_record.proto b/proto/test_record.proto
index cbbfafc..58e7147 100644
--- a/proto/test_record.proto
+++ b/proto/test_record.proto
@@ -94,4 +94,22 @@
 
   // A stacktrace.
   string trace = 2;
+
+  // A more detailed failure status description.
+  FailureStatus failure_status = 3;
+}
+
+// A Fail TestStatus can be associated with a more granular failure status that helps understanding
+// the context.
+enum FailureStatus {
+  // The test in progress was the reason for the failure.
+  TEST_FAILURE = 0;
+  // A timeout condition on the operation in progress occurred.
+  TIMED_OUT = 1;
+  // The test in progress was cancelled.
+  CANCELLED = 2;
+  // A failure attributed to something not functioning properly.
+  INFRA_FAILURE = 10;
+  // System under test crashed and caused the test to fail.
+  SYSTEM_UNDER_TEST_CRASHED = 20;
 }
\ No newline at end of file
diff --git a/src/com/android/tradefed/device/NativeDevice.java b/src/com/android/tradefed/device/NativeDevice.java
index ee6c71d..8e2d9f4 100644
--- a/src/com/android/tradefed/device/NativeDevice.java
+++ b/src/com/android/tradefed/device/NativeDevice.java
@@ -632,7 +632,9 @@
         return prop;
     }
 
-    private String getFastbootVariable(String variableName)
+    /** {@inheritDoc} */
+    @Override
+    public String getFastbootVariable(String variableName)
             throws DeviceNotAvailableException, UnsupportedOperationException {
         CommandResult result = executeFastbootCommand("getvar", variableName);
         if (result.getStatus() == CommandStatus.SUCCESS) {
@@ -2895,21 +2897,43 @@
     @Override
     public void rebootIntoBootloader()
             throws DeviceNotAvailableException, UnsupportedOperationException {
+        rebootIntoFastbootInternal(true);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void rebootIntoFastbootd()
+            throws DeviceNotAvailableException, UnsupportedOperationException {
+        rebootIntoFastbootInternal(false);
+    }
+
+    /**
+     * Reboots the device into bootloader or fastbootd mode.
+     *
+     * @param isBootloader: true to boot the device into bootloader mode, false to boot the device
+     *     into fastbootd mode.
+     * @throws DeviceNotAvailableException if connection with device is lost and cannot be
+     *     recovered.
+     */
+    private void rebootIntoFastbootInternal(boolean isBootloader)
+            throws DeviceNotAvailableException {
+        final RebootMode mode =
+                isBootloader ? RebootMode.REBOOT_INTO_BOOTLOADER : RebootMode.REBOOT_INTO_FASTBOOT;
         if (!mFastbootEnabled) {
             throw new UnsupportedOperationException(
-                    "Fastboot is not available and cannot reboot into bootloader");
+                    String.format("Fastboot is not available and cannot reboot into %s", mode));
         }
         // If we go to bootloader, it's probably for flashing so ensure we re-check the provider
         mShouldSkipContentProviderSetup = false;
         CLog.i(
-                "Rebooting device %s in state %s into bootloader",
-                getSerialNumber(), getDeviceState());
+                "Rebooting device %s in state %s into %s",
+                getSerialNumber(), getDeviceState(), mode);
         if (TestDeviceState.FASTBOOT.equals(getDeviceState())) {
             CLog.i("device %s already in fastboot. Rebooting anyway", getSerialNumber());
-            executeFastbootCommand("reboot-bootloader");
+            executeFastbootCommand(String.format("reboot-%s", mode));
         } else {
-            CLog.i("Booting device %s into bootloader", getSerialNumber());
-            doAdbRebootBootloader();
+            CLog.i("Booting device %s into %s", getSerialNumber(), mode);
+            doAdbReboot(mode, null);
         }
         if (!mStateMonitor.waitForDeviceBootloader(mOptions.getFastbootTimeout())) {
             recoverDeviceFromBootloader();
diff --git a/src/com/android/tradefed/device/TestDevice.java b/src/com/android/tradefed/device/TestDevice.java
index 8d5c71e..d1c5b0b 100644
--- a/src/com/android/tradefed/device/TestDevice.java
+++ b/src/com/android/tradefed/device/TestDevice.java
@@ -27,6 +27,7 @@
 import com.android.tradefed.result.ByteArrayInputStreamSource;
 import com.android.tradefed.result.FileInputStreamSource;
 import com.android.tradefed.result.InputStreamSource;
+import com.android.tradefed.util.AaptParser;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
 import com.android.tradefed.util.KeyguardControllerState;
@@ -194,6 +195,9 @@
                 packageFile.getAbsolutePath(), extraArgs.toString(), getSerialNumber());
         performDeviceAction(String.format("install %s", packageFile.getAbsolutePath()),
                 installAction, MAX_RETRY_ATTEMPTS);
+        List<File> packageFiles = new ArrayList();
+        packageFiles.add(packageFile);
+        allowLegacyStorageForApps(packageFiles);
         return response[0];
     }
 
@@ -332,6 +336,9 @@
                 };
         performDeviceAction(String.format("install %s", packageFile.getAbsolutePath()),
                 installAction, MAX_RETRY_ATTEMPTS);
+        List<File> packageFiles = new ArrayList();
+        packageFiles.add(packageFile);
+        allowLegacyStorageForApps(packageFiles);
         return response[0];
     }
 
@@ -404,9 +411,45 @@
                 String.format("install %s", packageFiles.toString()),
                 installAction,
                 MAX_RETRY_ATTEMPTS);
+        allowLegacyStorageForApps(packageFiles);
         return response[0];
     }
 
+    /**
+     * Allows Legacy External Storage access for apps that request for it.
+     *
+     * <p>Apps that request for legacy external storage access are granted the access by setting
+     * MANAGE_EXTERNAL_STORAGE App Op. This gives the app File manager privileges, File managers
+     * have legacy external storage access.
+     *
+     * @param appFiles List of Files. Apk Files of the apps that are installed.
+     */
+    private void allowLegacyStorageForApps(List<File> appFiles) throws DeviceNotAvailableException {
+        for (File appFile : appFiles) {
+            AaptParser aaptParser = AaptParser.parse(appFile);
+            if (aaptParser != null && aaptParser.isRequestingLegacyStorage()) {
+                // Set the MANAGE_EXTERNAL_STORAGE App Op to MODE_ALLOWED (Code = 0)
+                // for all users.
+                ArrayList<Integer> userIds = listUsers();
+                for (int userId : userIds) {
+                    CommandResult setFileManagerAppOpResult =
+                            executeShellV2Command(
+                                    "appops set --user "
+                                            + userId
+                                            + " --uid "
+                                            + aaptParser.getPackageName()
+                                            + " MANAGE_EXTERNAL_STORAGE 0");
+                    if (!CommandStatus.SUCCESS.equals(setFileManagerAppOpResult.getStatus())) {
+                        CLog.e(
+                                "Failed to set MANAGE_EXTERNAL_STORAGE App Op to"
+                                        + " allow legacy external storage for: %s ; stderr: %s",
+                                aaptParser.getPackageName(), setFileManagerAppOpResult.getStderr());
+                    }
+                }
+            }
+        }
+    }
+
     /** {@inheritDoc} */
     @Override
     public String installPackages(
diff --git a/src/com/android/tradefed/result/ResultForwarder.java b/src/com/android/tradefed/result/ResultForwarder.java
index fa64e72..750b125 100644
--- a/src/com/android/tradefed/result/ResultForwarder.java
+++ b/src/com/android/tradefed/result/ResultForwarder.java
@@ -206,6 +206,19 @@
         }
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public void testRunFailed(FailureDescription failure) {
+        for (ITestInvocationListener listener : mListeners) {
+            try {
+                listener.testRunFailed(failure);
+            } catch (RuntimeException e) {
+                CLog.e("Exception while invoking %s#testRunFailed", listener.getClass().getName());
+                CLog.e(e);
+            }
+        }
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -268,6 +281,19 @@
 
     /** {@inheritDoc} */
     @Override
+    public void testFailed(TestDescription test, FailureDescription failure) {
+        for (ITestInvocationListener listener : mListeners) {
+            try {
+                listener.testFailed(test, failure);
+            } catch (RuntimeException e) {
+                CLog.e("Exception while invoking %s#testFailed", listener.getClass().getName());
+                CLog.e(e);
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
     public void testEnded(TestDescription test, HashMap<String, Metric> testMetrics) {
         testEnded(test, System.currentTimeMillis(), testMetrics);
     }
diff --git a/src/com/android/tradefed/util/AaptParser.java b/src/com/android/tradefed/util/AaptParser.java
index 58a8b39..78182ef 100644
--- a/src/com/android/tradefed/util/AaptParser.java
+++ b/src/com/android/tradefed/util/AaptParser.java
@@ -41,6 +41,9 @@
     private static final Pattern NATIVE_CODE_PATTERN =
             Pattern.compile("native-code: '(.*?)'( '.*?')*");
 
+    private static final Pattern REQUEST_LEGACY_STORAGE_PATTERN =
+            Pattern.compile("requestLegacyExternalStorage.*=\\(.*\\)(.*)", Pattern.MULTILINE);
+
     private static final Pattern ALT_NATIVE_CODE_PATTERN =
             Pattern.compile("alt-native-code: '(.*)'");
     private static final int AAPT_TIMEOUT_MS = 60000;
@@ -52,6 +55,7 @@
     private List<String> mNativeCode = new ArrayList<>();
     private String mLabel;
     private int mSdkVersion = INVALID_SDK;
+    private boolean mRequestLegacyStorage = false;
 
     // @VisibleForTesting
     AaptParser() {
@@ -96,6 +100,16 @@
         return false;
     }
 
+    boolean parseXmlTree(String aaptOut) {
+        Matcher m = REQUEST_LEGACY_STORAGE_PATTERN.matcher(aaptOut);
+        if (m.find()) {
+            // 0xffffffff is true and 0x0 is false
+            mRequestLegacyStorage = m.group(1).equals("0xffffffff");
+        }
+        // REQUEST_LEGACY_STORAGE_PATTERN may or may not be present
+        return true;
+    }
+
     /**
      * Parse info from the apk.
      *
@@ -118,17 +132,38 @@
         if (stderr != null && !stderr.isEmpty()) {
             CLog.e("aapt dump badging stderr: %s", stderr);
         }
-
-        if (CommandStatus.SUCCESS.equals(result.getStatus())) {
-            AaptParser p = new AaptParser();
-            if (p.parse(result.getStdout()))
-                return p;
+        AaptParser p = new AaptParser();
+        if (!CommandStatus.SUCCESS.equals(result.getStatus()) || !p.parse(result.getStdout())) {
+            CLog.e(
+                    "Failed to run aapt on %s. stdout: %s",
+                    apkFile.getAbsoluteFile(), result.getStdout());
             return null;
         }
-        CLog.e(
-                "Failed to run aapt on %s. stdout: %s",
-                apkFile.getAbsoluteFile(), result.getStdout());
-        return null;
+        result =
+                RunUtil.getDefault()
+                        .runTimedCmdRetry(
+                                AAPT_TIMEOUT_MS,
+                                0L,
+                                2,
+                                "aapt",
+                                "dump",
+                                "xmltree",
+                                apkFile.getAbsolutePath(),
+                                "AndroidManifest.xml");
+
+        stderr = result.getStderr();
+        if (stderr != null && !stderr.isEmpty()) {
+            CLog.e("aapt dump xmltree AndroidManifest.xml stderr: %s", stderr);
+        }
+
+        if (!CommandStatus.SUCCESS.equals(result.getStatus())
+                || !p.parseXmlTree(result.getStdout())) {
+            CLog.e(
+                    "Failed to run aapt on %s. stdout: %s",
+                    apkFile.getAbsoluteFile(), result.getStdout());
+            return null;
+        }
+        return p;
     }
 
     public String getPackageName() {
@@ -154,4 +189,13 @@
     public int getSdkVersion() {
         return mSdkVersion;
     }
+
+    /**
+     * Check if the app is requesting legacy storage.
+     *
+     * @return boolean return true if requestLegacyExternalStorage is true in AndroidManifest.xml
+     */
+    public boolean isRequestingLegacyStorage() {
+        return mRequestLegacyStorage;
+    }
 }
diff --git a/test_framework/com/android/tradefed/targetprep/FastbootCommandPreparer.java b/test_framework/com/android/tradefed/targetprep/FastbootCommandPreparer.java
index 62df086..51baa0f 100644
--- a/test_framework/com/android/tradefed/targetprep/FastbootCommandPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/FastbootCommandPreparer.java
@@ -30,13 +30,33 @@
  */
 @OptionClass(alias = "fastboot-command-preparer")
 public class FastbootCommandPreparer extends BaseTargetPreparer {
+
+    private enum FastbootMode {
+        BOOTLOADER,
+        FASTBOOTD,
+    }
+
+    @Option(
+            name = "fastboot-mode",
+            description = "True to boot the device into bootloader mode, false for fastbootd mode.")
+    private FastbootMode mFastbootMode = FastbootMode.BOOTLOADER;
+
+    @Option(
+            name = "stay-fastboot",
+            description = "True to keep the device in bootloader or fastbootd mode.")
+    private boolean mStayFastboot = false;
+
     @Option(name = "command", description = "Fastboot commands to run.")
     private List<String> mFastbootCommands = new ArrayList<String>();
 
     @Override
     public void setUp(TestInformation testInformation)
             throws TargetSetupError, BuildError, DeviceNotAvailableException {
-        testInformation.getDevice().rebootIntoBootloader();
+        if (mFastbootMode == FastbootMode.BOOTLOADER) {
+            testInformation.getDevice().rebootIntoBootloader();
+        } else {
+            testInformation.getDevice().rebootIntoFastbootd();
+        }
 
         for (String cmd : mFastbootCommands) {
             // Ignore reboots since we'll reboot in the end.
@@ -47,7 +67,9 @@
             testInformation.getDevice().executeFastbootCommand(cmd.split("\\s+"));
         }
 
-        testInformation.getDevice().reboot();
+        if (!mStayFastboot) {
+            testInformation.getDevice().reboot();
+        }
     }
 
     /** {@inheritDoc} */
diff --git a/test_result_interfaces/Android.bp b/test_result_interfaces/Android.bp
index 9a8cc0b..6d6d6b1 100644
--- a/test_result_interfaces/Android.bp
+++ b/test_result_interfaces/Android.bp
@@ -20,6 +20,7 @@
     ],
     libs: [
         "ddmlib-prebuilt",
+        "jsr305",
         "tradefed-common-util",
         "tradefed-protos",
     ],
diff --git a/test_result_interfaces/com/android/tradefed/result/FailureDescription.java b/test_result_interfaces/com/android/tradefed/result/FailureDescription.java
new file mode 100644
index 0000000..4018ace
--- /dev/null
+++ b/test_result_interfaces/com/android/tradefed/result/FailureDescription.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2020 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 com.android.tradefed.result;
+
+import com.android.tradefed.result.proto.TestRecordProto;
+
+import javax.annotation.Nullable;
+
+/**
+ * The class describing a failure information in Trade Federation. This class contains the debugging
+ * information and context of the failure that helps understanding the issue.
+ */
+public class FailureDescription {
+    // The error message generated from the failure
+    private String mErrorMessage;
+    // Optional: The category of the failure
+    private @Nullable TestRecordProto.FailureStatus mFailureStatus;
+
+    FailureDescription() {}
+
+    /**
+     * Set the {@link com.android.tradefed.result.proto.TestRecordProto.FailureStatus} associated
+     * with the failure.
+     */
+    public FailureDescription setFailureStatus(TestRecordProto.FailureStatus status) {
+        mFailureStatus = status;
+        return this;
+    }
+
+    /** Returns the FailureStatus associated with the failure. Can be null. */
+    public @Nullable TestRecordProto.FailureStatus getFailureStatus() {
+        return mFailureStatus;
+    }
+
+    /** Returns the error message associated with the failure. */
+    public String getErrorMessage() {
+        return mErrorMessage;
+    }
+
+    @Override
+    public String toString() {
+        // For backward compatibility of result interface, toString falls back to the simple message
+        return mErrorMessage;
+    }
+
+    /**
+     * Create a {@link FailureDescription} based on the error message generated from the failure.
+     *
+     * @param errorMessage The error message from the failure.
+     * @return the created {@link FailureDescription}
+     */
+    public static FailureDescription create(String errorMessage) {
+        return create(errorMessage, null);
+    }
+
+    /**
+     * Create a {@link FailureDescription} based on the error message generated from the failure.
+     *
+     * @param errorMessage The error message from the failure.
+     * @param status The status associated with the failure.
+     * @return the created {@link FailureDescription}
+     */
+    public static FailureDescription create(
+            String errorMessage, @Nullable TestRecordProto.FailureStatus status) {
+        FailureDescription info = new FailureDescription();
+        info.mErrorMessage = errorMessage;
+        info.mFailureStatus = status;
+        return info;
+    }
+}
diff --git a/test_result_interfaces/com/android/tradefed/result/ITestLifeCycleReceiver.java b/test_result_interfaces/com/android/tradefed/result/ITestLifeCycleReceiver.java
index a4f86cc..99f563f 100644
--- a/test_result_interfaces/com/android/tradefed/result/ITestLifeCycleReceiver.java
+++ b/test_result_interfaces/com/android/tradefed/result/ITestLifeCycleReceiver.java
@@ -88,6 +88,15 @@
     public default void testRunFailed(String errorMessage) {}
 
     /**
+     * Reports test run failed to complete due to a failure described by {@link FailureDescription}.
+     *
+     * @param failure {@link FailureDescription} describing the failure and its context.
+     */
+    public default void testRunFailed(FailureDescription failure) {
+        testRunFailed(failure.toString());
+    }
+
+    /**
      * Reports end of test run.
      *
      * @param elapsedTimeMillis device reported elapsed time, in milliseconds
@@ -145,6 +154,18 @@
     public default void testFailed(TestDescription test, String trace) {}
 
     /**
+     * Reports the failure of a individual test case.
+     *
+     * <p>Will be called between testStarted and testEnded.
+     *
+     * @param test identifies the test
+     * @param failure {@link FailureDescription} describing the failure and its context.
+     */
+    public default void testFailed(TestDescription test, FailureDescription failure) {
+        testFailed(test, failure.toString());
+    }
+
+    /**
      * Called when an atomic test flags that it assumes a condition that is false
      *
      * @param test identifies the test
diff --git a/test_result_interfaces/com/android/tradefed/result/MultiFailureDescription.java b/test_result_interfaces/com/android/tradefed/result/MultiFailureDescription.java
new file mode 100644
index 0000000..55ad8dc
--- /dev/null
+++ b/test_result_interfaces/com/android/tradefed/result/MultiFailureDescription.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2020 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 com.android.tradefed.result;
+
+import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+/**
+ * Collect multiple {@link FailureDescription} in one holder. This can be used to carry all the
+ * failures description when several attempts on the same test case or run are made, each resulting
+ * in a failure.
+ */
+public final class MultiFailureDescription extends FailureDescription {
+
+    private List<FailureDescription> mFailures = new ArrayList<>();
+
+    public MultiFailureDescription(List<FailureDescription> failures) {
+        super();
+        mFailures.addAll(failures);
+    }
+
+    public MultiFailureDescription(FailureDescription... failures) {
+        super();
+        mFailures.addAll(Arrays.asList(failures));
+    }
+
+    /**
+     * Add another failure to an existing {@link MultiFailureDescription}.
+     *
+     * @param failure The additional failure
+     * @return The current {@link MultiFailureDescription}.
+     */
+    public MultiFailureDescription addFailure(FailureDescription failure) {
+        mFailures.add(failure);
+        return this;
+    }
+
+    /**
+     * Returns the list of {@link FailureDescription} tracked by the {@link
+     * MultiFailureDescription}.
+     */
+    public List<FailureDescription> getFailures() {
+        return mFailures;
+    }
+
+    @Override
+    public @Nullable FailureStatus getFailureStatus() {
+        throw new UnsupportedOperationException(
+                "Cannot call #getFailureStatus on MultiFailureDescription");
+    }
+
+    @Override
+    public String getErrorMessage() {
+        throw new UnsupportedOperationException(
+                "Cannot call #getErrorMessage on MultiFailureDescription");
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb =
+                new StringBuilder(String.format("There were %d failures:", mFailures.size()));
+        for (FailureDescription f : mFailures) {
+            sb.append(String.format("\n  %s", f.toString()));
+        }
+        return sb.toString();
+    }
+}
diff --git a/tests/src/com/android/tradefed/UnitTests.java b/tests/src/com/android/tradefed/UnitTests.java
index ecd6680..c299b28 100644
--- a/tests/src/com/android/tradefed/UnitTests.java
+++ b/tests/src/com/android/tradefed/UnitTests.java
@@ -160,6 +160,7 @@
 import com.android.tradefed.result.DeviceFileReporterTest;
 import com.android.tradefed.result.DeviceUnavailEmailResultReporterTest;
 import com.android.tradefed.result.EmailResultReporterTest;
+import com.android.tradefed.result.FailureDescriptionTest;
 import com.android.tradefed.result.FailureEmailResultReporterTest;
 import com.android.tradefed.result.FileSystemLogSaverTest;
 import com.android.tradefed.result.InvocationFailureEmailResultReporterTest;
@@ -170,6 +171,7 @@
 import com.android.tradefed.result.LogFileSaverTest;
 import com.android.tradefed.result.LogcatCrashResultForwarderTest;
 import com.android.tradefed.result.MetricsXMLResultReporterTest;
+import com.android.tradefed.result.MultiFailureDescriptionTest;
 import com.android.tradefed.result.SnapshotInputStreamSourceTest;
 import com.android.tradefed.result.SubprocessResultsReporterTest;
 import com.android.tradefed.result.TestDescriptionTest;
@@ -589,6 +591,7 @@
     DeviceFileReporterTest.class,
     DeviceUnavailEmailResultReporterTest.class,
     EmailResultReporterTest.class,
+    FailureDescriptionTest.class,
     FailureEmailResultReporterTest.class,
     FileSystemLogSaverTest.class,
     InvocationFailureEmailResultReporterTest.class,
@@ -599,6 +602,7 @@
     LogcatCrashResultForwarderTest.class,
     LogFileSaverTest.class,
     MetricsXMLResultReporterTest.class,
+    MultiFailureDescriptionTest.class,
     SnapshotInputStreamSourceTest.class,
     SubprocessResultsReporterTest.class,
     TestDescriptionTest.class,
diff --git a/tests/src/com/android/tradefed/device/NativeDeviceTest.java b/tests/src/com/android/tradefed/device/NativeDeviceTest.java
index c45e163..40d56c0 100644
--- a/tests/src/com/android/tradefed/device/NativeDeviceTest.java
+++ b/tests/src/com/android/tradefed/device/NativeDeviceTest.java
@@ -1666,6 +1666,46 @@
         EasyMock.verify(mMockIDevice, mMockStateMonitor, mMockDvcMonitor);
     }
 
+    /** Unit test for {@link NativeDevice#rebootIntoBootloader()}}. */
+    @Test
+    public void testRebootIntoBootloader() throws Exception {
+        NativeDevice testDevice =
+                new NativeDevice(mMockIDevice, mMockStateMonitor, mMockDvcMonitor) {
+                    @Override
+                    public TestDeviceState getDeviceState() {
+                        return TestDeviceState.ONLINE;
+                    }
+                };
+        String into = "bootloader";
+        mMockIDevice.reboot(into);
+        EasyMock.expectLastCall();
+        EasyMock.expect(mMockStateMonitor.waitForDeviceBootloader(EasyMock.anyLong()))
+                .andReturn(true);
+        EasyMock.replay(mMockIDevice, mMockStateMonitor, mMockDvcMonitor);
+        testDevice.rebootIntoBootloader();
+        EasyMock.verify(mMockIDevice, mMockStateMonitor, mMockDvcMonitor);
+    }
+
+    /** Unit test for {@link NativeDevice#rebootIntoFastbootd()}}. */
+    @Test
+    public void testRebootIntoFastbootd() throws Exception {
+        NativeDevice testDevice =
+                new NativeDevice(mMockIDevice, mMockStateMonitor, mMockDvcMonitor) {
+                    @Override
+                    public TestDeviceState getDeviceState() {
+                        return TestDeviceState.ONLINE;
+                    }
+                };
+        String into = "fastboot";
+        mMockIDevice.reboot(into);
+        EasyMock.expectLastCall();
+        EasyMock.expect(mMockStateMonitor.waitForDeviceBootloader(EasyMock.anyLong()))
+                .andReturn(true);
+        EasyMock.replay(mMockIDevice, mMockStateMonitor, mMockDvcMonitor);
+        testDevice.rebootIntoFastbootd();
+        EasyMock.verify(mMockIDevice, mMockStateMonitor, mMockDvcMonitor);
+    }
+
     /** Unit test for {@link NativeDevice#unlockDevice()} already decrypted. */
     @Test
     public void testUnlockDevice_skipping() throws Exception {
diff --git a/tests/src/com/android/tradefed/device/metric/BugreportzOnFailureCollectorTest.java b/tests/src/com/android/tradefed/device/metric/BugreportzOnFailureCollectorTest.java
index fb4b2d5..330b2c5 100644
--- a/tests/src/com/android/tradefed/device/metric/BugreportzOnFailureCollectorTest.java
+++ b/tests/src/com/android/tradefed/device/metric/BugreportzOnFailureCollectorTest.java
@@ -56,7 +56,7 @@
     public void testCollect() throws Exception {
         TestDescription test = new TestDescription("class", "test");
         mMockListener.testStarted(EasyMock.eq(test), EasyMock.anyLong());
-        mMockListener.testFailed(EasyMock.eq(test), EasyMock.anyObject());
+        mMockListener.testFailed(EasyMock.eq(test), (String) EasyMock.anyObject());
         mMockListener.testEnded(
                 EasyMock.eq(test),
                 EasyMock.anyLong(),
diff --git a/tests/src/com/android/tradefed/device/metric/DebugHostLogOnFailureCollectorTest.java b/tests/src/com/android/tradefed/device/metric/DebugHostLogOnFailureCollectorTest.java
index 9d610e3..5eb295e 100644
--- a/tests/src/com/android/tradefed/device/metric/DebugHostLogOnFailureCollectorTest.java
+++ b/tests/src/com/android/tradefed/device/metric/DebugHostLogOnFailureCollectorTest.java
@@ -117,7 +117,7 @@
                 .testRunStarted(
                         Mockito.eq("runName"), Mockito.eq(1), Mockito.eq(0), Mockito.anyLong());
         Mockito.verify(mMockListener).testStarted(Mockito.eq(test), Mockito.anyLong());
-        Mockito.verify(mMockListener).testFailed(Mockito.eq(test), Mockito.any());
+        Mockito.verify(mMockListener).testFailed(Mockito.eq(test), (String) Mockito.any());
         Mockito.verify(mMockListener)
                 .testLog(
                         Mockito.eq("class#test-debug-hostlog-on-failure"),
@@ -152,7 +152,7 @@
                 .testRunStarted(
                         Mockito.eq("runName"), Mockito.eq(1), Mockito.eq(0), Mockito.anyLong());
         Mockito.verify(mMockListener).testStarted(Mockito.eq(test), Mockito.anyLong());
-        Mockito.verify(mMockListener).testFailed(Mockito.eq(test), Mockito.any());
+        Mockito.verify(mMockListener).testFailed(Mockito.eq(test), (String) Mockito.any());
         // No file is logged
         Mockito.verify(mMockListener, never())
                 .testLog(
diff --git a/tests/src/com/android/tradefed/device/metric/LogcatOnFailureCollectorTest.java b/tests/src/com/android/tradefed/device/metric/LogcatOnFailureCollectorTest.java
index ca86faa..b338ed2 100644
--- a/tests/src/com/android/tradefed/device/metric/LogcatOnFailureCollectorTest.java
+++ b/tests/src/com/android/tradefed/device/metric/LogcatOnFailureCollectorTest.java
@@ -111,7 +111,7 @@
                 EasyMock.eq("runName"), EasyMock.eq(1), EasyMock.eq(0), EasyMock.anyLong());
         TestDescription test = new TestDescription("class", "test");
         mMockListener.testStarted(EasyMock.eq(test), EasyMock.anyLong());
-        mMockListener.testFailed(EasyMock.eq(test), EasyMock.anyObject());
+        mMockListener.testFailed(EasyMock.eq(test), (String) EasyMock.anyObject());
         mMockListener.testEnded(
                 EasyMock.eq(test),
                 EasyMock.anyLong(),
@@ -152,7 +152,7 @@
                 EasyMock.eq("runName"), EasyMock.eq(1), EasyMock.eq(0), EasyMock.anyLong());
         TestDescription test = new TestDescription("class", "test");
         mMockListener.testStarted(EasyMock.eq(test), EasyMock.anyLong());
-        mMockListener.testFailed(EasyMock.eq(test), EasyMock.anyObject());
+        mMockListener.testFailed(EasyMock.eq(test), (String) EasyMock.anyObject());
         mMockListener.testEnded(
                 EasyMock.eq(test),
                 EasyMock.anyLong(),
@@ -201,7 +201,7 @@
         TestDescription test = new TestDescription("class", "test");
         TestDescription test2 = new TestDescription("class2", "test2");
         mMockListener.testStarted(EasyMock.eq(test), EasyMock.anyLong());
-        mMockListener.testFailed(EasyMock.eq(test), EasyMock.anyObject());
+        mMockListener.testFailed(EasyMock.eq(test), (String) EasyMock.anyObject());
         mMockListener.testEnded(
                 EasyMock.eq(test),
                 EasyMock.anyLong(),
@@ -221,7 +221,7 @@
         mMockListener.testRunStarted(
                 EasyMock.eq("runName2"), EasyMock.eq(1), EasyMock.eq(0), EasyMock.anyLong());
         mMockListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
-        mMockListener.testFailed(EasyMock.eq(test2), EasyMock.anyObject());
+        mMockListener.testFailed(EasyMock.eq(test2), (String) EasyMock.anyObject());
         mMockListener.testEnded(
                 EasyMock.eq(test2),
                 EasyMock.anyLong(),
diff --git a/tests/src/com/android/tradefed/device/metric/ScreenshotOnFailureCollectorTest.java b/tests/src/com/android/tradefed/device/metric/ScreenshotOnFailureCollectorTest.java
index 683a466..20776d0 100644
--- a/tests/src/com/android/tradefed/device/metric/ScreenshotOnFailureCollectorTest.java
+++ b/tests/src/com/android/tradefed/device/metric/ScreenshotOnFailureCollectorTest.java
@@ -59,7 +59,7 @@
     public void testCollect() throws Exception {
         TestDescription test = new TestDescription("class", "test");
         mMockListener.testStarted(EasyMock.eq(test), EasyMock.anyLong());
-        mMockListener.testFailed(EasyMock.eq(test), EasyMock.anyObject());
+        mMockListener.testFailed(EasyMock.eq(test), (String) EasyMock.anyObject());
         mMockListener.testEnded(
                 EasyMock.eq(test),
                 EasyMock.anyLong(),
diff --git a/tests/src/com/android/tradefed/result/FailureDescriptionTest.java b/tests/src/com/android/tradefed/result/FailureDescriptionTest.java
new file mode 100644
index 0000000..da94866
--- /dev/null
+++ b/tests/src/com/android/tradefed/result/FailureDescriptionTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 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 com.android.tradefed.result;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link FailureDescription}. */
+@RunWith(JUnit4.class)
+public class FailureDescriptionTest {
+
+    private FailureDescription mFailureDescription;
+
+    @Test
+    public void testCreation() {
+        mFailureDescription = FailureDescription.create("error message");
+        assertNull(mFailureDescription.getFailureStatus());
+        assertEquals("error message", mFailureDescription.toString());
+        mFailureDescription.setFailureStatus(FailureStatus.TEST_FAILURE);
+        assertEquals(FailureStatus.TEST_FAILURE, mFailureDescription.getFailureStatus());
+    }
+}
diff --git a/tests/src/com/android/tradefed/result/MultiFailureDescriptionTest.java b/tests/src/com/android/tradefed/result/MultiFailureDescriptionTest.java
new file mode 100644
index 0000000..3b86019
--- /dev/null
+++ b/tests/src/com/android/tradefed/result/MultiFailureDescriptionTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 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 com.android.tradefed.result;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link MultiFailureDescription}. */
+@RunWith(JUnit4.class)
+public class MultiFailureDescriptionTest {
+
+    private MultiFailureDescription mMultiFailureDesc;
+
+    @Test
+    public void testCreation() {
+        FailureDescription failure1 = FailureDescription.create("error message 1");
+        FailureDescription failure2 = FailureDescription.create("error message 2");
+        mMultiFailureDesc = new MultiFailureDescription(failure1, failure2);
+        assertEquals(2, mMultiFailureDesc.getFailures().size());
+        assertEquals(
+                "There were 2 failures:\n" + "  error message 1\n" + "  error message 2",
+                mMultiFailureDesc.toString());
+    }
+}
diff --git a/tests/src/com/android/tradefed/targetprep/InstrumentationPreparerTest.java b/tests/src/com/android/tradefed/targetprep/InstrumentationPreparerTest.java
index ac6b032..7c05067 100644
--- a/tests/src/com/android/tradefed/targetprep/InstrumentationPreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/InstrumentationPreparerTest.java
@@ -99,7 +99,7 @@
                     public void run(TestInformation testInfo, ITestInvocationListener listener) {
                         listener.testRunStarted(packageName, 1);
                         listener.testStarted(test);
-                        listener.testFailed(test, null);
+                        listener.testFailed(test, "error");
                         listener.testEnded(test, new HashMap<String, Metric>());
                         listener.testRunEnded(0, new HashMap<String, Metric>());
                     }
diff --git a/tests/src/com/android/tradefed/testtype/GoogleBenchmarkTestTest.java b/tests/src/com/android/tradefed/testtype/GoogleBenchmarkTestTest.java
index 58788c9..9fbfa13 100644
--- a/tests/src/com/android/tradefed/testtype/GoogleBenchmarkTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/GoogleBenchmarkTestTest.java
@@ -267,7 +267,7 @@
                                         "%s/test1 --benchmark_list_tests=true", nativeTestPath)))
                 .andReturn("method1\nmethod2\nmethod3");
         mMockInvocationListener.testRunStarted(test1, 3);
-        mMockInvocationListener.testRunFailed(EasyMock.anyObject());
+        mMockInvocationListener.testRunFailed((String) EasyMock.anyObject());
         // Even with exception testrunEnded is expected.
         mMockInvocationListener.testRunEnded(
                 EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
diff --git a/tests/src/com/android/tradefed/testtype/HostTestTest.java b/tests/src/com/android/tradefed/testtype/HostTestTest.java
index 544991b..c119c26 100644
--- a/tests/src/com/android/tradefed/testtype/HostTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/HostTestTest.java
@@ -1323,7 +1323,7 @@
                 EasyMock.eq(test1),
                 EasyMock.contains("MultipleFailureException: There were 2 errors:"));
         mListener.testEnded(EasyMock.eq(test1), (HashMap<String, Metric>) EasyMock.anyObject());
-        mListener.testRunFailed(EasyMock.anyObject());
+        mListener.testRunFailed((String) EasyMock.anyObject());
         mListener.testRunEnded(EasyMock.anyLong(), (HashMap<String, Metric>) EasyMock.anyObject());
         EasyMock.replay(mListener);
         try {
@@ -2186,7 +2186,7 @@
         mListener.testRunStarted(EasyMock.anyObject(), EasyMock.eq(1));
         TestDescription tid = new TestDescription(JUnit4FailedBefore.class.getName(), "test1");
         mListener.testStarted(EasyMock.eq(tid));
-        mListener.testFailed(EasyMock.eq(tid), EasyMock.anyObject());
+        mListener.testFailed(EasyMock.eq(tid), (String) EasyMock.anyObject());
         mListener.testEnded(EasyMock.eq(tid), (HashMap<String, Metric>) EasyMock.anyObject());
         mListener.testRunEnded(EasyMock.anyLong(), (HashMap<String, Metric>) EasyMock.anyObject());
 
diff --git a/tests/src/com/android/tradefed/testtype/NoisyDryRunTestTest.java b/tests/src/com/android/tradefed/testtype/NoisyDryRunTestTest.java
index a277317..2c3d663 100644
--- a/tests/src/com/android/tradefed/testtype/NoisyDryRunTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/NoisyDryRunTestTest.java
@@ -141,7 +141,7 @@
         mMockListener.testRunStarted("com.android.tradefed.testtype.NoisyDryRunTest_parseFile", 1);
         mMockListener.testStarted(anyObject());
         mMockListener.testEnded(anyObject(), EasyMock.<HashMap<String, Metric>>anyObject());
-        mMockListener.testFailed(anyObject(), anyObject());
+        mMockListener.testFailed(anyObject(), (String) anyObject());
         mMockListener.testRunEnded(EasyMock.eq(0l), EasyMock.<HashMap<String, Metric>>anyObject());
         replayMocks();
 
@@ -166,7 +166,7 @@
         mMockListener.testStarted(anyObject());
         mMockListener.testEnded(anyObject(), EasyMock.<HashMap<String, Metric>>anyObject());
         mMockListener.testStarted(anyObject());
-        mMockListener.testFailed(anyObject(), anyObject());
+        mMockListener.testFailed(anyObject(), (String) anyObject());
         mMockListener.testEnded(anyObject(), EasyMock.<HashMap<String, Metric>>anyObject());
         mMockListener.testRunEnded(EasyMock.eq(0l), EasyMock.<HashMap<String, Metric>>anyObject());
         replayMocks();
diff --git a/tests/src/com/android/tradefed/testtype/PythonUnitTestResultParserTest.java b/tests/src/com/android/tradefed/testtype/PythonUnitTestResultParserTest.java
index 366f75c..a8f4299 100644
--- a/tests/src/com/android/tradefed/testtype/PythonUnitTestResultParserTest.java
+++ b/tests/src/com/android/tradefed/testtype/PythonUnitTestResultParserTest.java
@@ -497,16 +497,16 @@
 
         mMockListener.testFailed(
                 EasyMock.eq(new TestDescription("__main__.DisconnectionTest", "test_disconnect")),
-                EasyMock.anyObject());
+                (String) EasyMock.anyObject());
         mMockListener.testFailed(
                 EasyMock.eq(new TestDescription("__main__.EmulatorTest", "test_emulator_connect")),
-                EasyMock.anyObject());
+                (String) EasyMock.anyObject());
         mMockListener.testIgnored(
                 EasyMock.eq(new TestDescription("__main__.PowerTest", "test_resume_usb_kick")));
         // Multi-line error
         mMockListener.testFailed(
                 EasyMock.eq(new TestDescription("__main__.ServerTest", "test_handle_inheritance")),
-                EasyMock.anyObject());
+                (String) EasyMock.anyObject());
 
         mMockListener.testRunEnded(10314, new HashMap<String, Metric>());
         replay(mMockListener);
@@ -550,7 +550,7 @@
 
         mMockListener.testFailed(
                 EasyMock.eq(new TestDescription("__main__.ConnectionTest", "test_reconnect")),
-                EasyMock.anyObject());
+                (String) EasyMock.anyObject());
         mMockListener.testIgnored(
                 EasyMock.eq(new TestDescription("__main__.PowerTest", "test_resume_usb_kick")));
 
diff --git a/tests/src/com/android/tradefed/testtype/binary/ExecutableHostTestTest.java b/tests/src/com/android/tradefed/testtype/binary/ExecutableHostTestTest.java
index e973b80..96e99cc 100644
--- a/tests/src/com/android/tradefed/testtype/binary/ExecutableHostTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/binary/ExecutableHostTestTest.java
@@ -120,8 +120,8 @@
             mExecutableTest.run(mTestInfo, mMockListener);
 
             verify(mMockListener, Mockito.times(1)).testRunStarted(eq(tmpBinary.getName()), eq(1));
-            verify(mMockListener, Mockito.times(0)).testRunFailed(any());
-            verify(mMockListener, Mockito.times(0)).testFailed(any(), any());
+            verify(mMockListener, Mockito.times(0)).testRunFailed((String) any());
+            verify(mMockListener, Mockito.times(0)).testFailed(any(), (String) any());
             verify(mMockListener, Mockito.times(1))
                     .testRunEnded(Mockito.anyLong(), Mockito.<HashMap<String, Metric>>any());
         } finally {
@@ -154,8 +154,8 @@
             mExecutableTest.run(mTestInfo, mMockListener);
 
             verify(mMockListener, Mockito.times(1)).testRunStarted(eq(tmpBinary.getName()), eq(1));
-            verify(mMockListener, Mockito.times(0)).testRunFailed(any());
-            verify(mMockListener, Mockito.times(0)).testFailed(any(), any());
+            verify(mMockListener, Mockito.times(0)).testRunFailed((String) any());
+            verify(mMockListener, Mockito.times(0)).testFailed(any(), (String) any());
             verify(mMockListener, Mockito.times(1))
                     .testRunEnded(Mockito.anyLong(), Mockito.<HashMap<String, Metric>>any());
         } finally {
@@ -194,7 +194,7 @@
                                     String.format(
                                             "Device became unavailable after %s.",
                                             tmpBinary.getAbsolutePath())));
-            verify(mMockListener, Mockito.times(0)).testFailed(any(), any());
+            verify(mMockListener, Mockito.times(0)).testFailed(any(), (String) any());
             verify(mMockListener, Mockito.times(1))
                     .testRunEnded(Mockito.anyLong(), Mockito.<HashMap<String, Metric>>any());
         } finally {
@@ -226,8 +226,8 @@
             mExecutableTest.run(mTestInfo, mMockListener);
 
             verify(mMockListener, Mockito.times(1)).testRunStarted(eq(tmpBinary.getName()), eq(1));
-            verify(mMockListener, Mockito.times(0)).testRunFailed(any());
-            verify(mMockListener, Mockito.times(0)).testFailed(any(), any());
+            verify(mMockListener, Mockito.times(0)).testRunFailed((String) any());
+            verify(mMockListener, Mockito.times(0)).testFailed(any(), (String) any());
             verify(mMockListener, Mockito.times(1))
                     .testRunEnded(Mockito.anyLong(), Mockito.<HashMap<String, Metric>>any());
         } finally {
@@ -300,7 +300,7 @@
             mExecutableTest.run(mTestInfo, mMockListener);
 
             verify(mMockListener, Mockito.times(1)).testRunStarted(eq(tmpBinary.getName()), eq(1));
-            verify(mMockListener, Mockito.times(0)).testRunFailed(any());
+            verify(mMockListener, Mockito.times(0)).testRunFailed((String) any());
             verify(mMockListener, Mockito.times(1)).testFailed(any(), eq("stdout\nExit Code: 5"));
             verify(mMockListener, Mockito.times(1))
                     .testRunEnded(Mockito.anyLong(), Mockito.<HashMap<String, Metric>>any());
diff --git a/tests/src/com/android/tradefed/testtype/junit4/LongevityHostRunnerTest.java b/tests/src/com/android/tradefed/testtype/junit4/LongevityHostRunnerTest.java
index 07c13b0..37b4b74 100644
--- a/tests/src/com/android/tradefed/testtype/junit4/LongevityHostRunnerTest.java
+++ b/tests/src/com/android/tradefed/testtype/junit4/LongevityHostRunnerTest.java
@@ -226,7 +226,7 @@
         mHostTest.publicSetClassName(PassingLongevitySuite.class.getName());
         mHostTest.run(mTestInfo, mMockListener);
         // Verify nothing failed, but something passed.
-        verify(mMockListener, never()).testFailed(any(), any());
+        verify(mMockListener, never()).testFailed(any(), (String) any());
         verify(mMockListener).testEnded(any(), Mockito.<HashMap<String, Metric>>any());
     }
 
diff --git a/tests/src/com/android/tradefed/testtype/python/PythonBinaryHostTestTest.java b/tests/src/com/android/tradefed/testtype/python/PythonBinaryHostTestTest.java
index b285530..5fd5c5f 100644
--- a/tests/src/com/android/tradefed/testtype/python/PythonBinaryHostTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/python/PythonBinaryHostTestTest.java
@@ -188,7 +188,7 @@
                     EasyMock.anyObject());
             // Report a failure if we cannot parse the logs
             mMockListener.testRunStarted(binary.getName(), 0);
-            mMockListener.testRunFailed(EasyMock.anyObject());
+            mMockListener.testRunFailed((String) EasyMock.anyObject());
             mMockListener.testRunEnded(
                     EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
 
diff --git a/tests/src/com/android/tradefed/testtype/rust/RustBinaryHostTestTest.java b/tests/src/com/android/tradefed/testtype/rust/RustBinaryHostTestTest.java
index 2fbef8e..09831e7 100644
--- a/tests/src/com/android/tradefed/testtype/rust/RustBinaryHostTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/rust/RustBinaryHostTestTest.java
@@ -166,7 +166,7 @@
                     EasyMock.anyObject());
             // Report a failure if we cannot parse the logs and the logs is not empty.
             mMockListener.testRunStarted(binary.getName(), 0);
-            mMockListener.testRunFailed(EasyMock.anyObject());
+            mMockListener.testRunFailed((String) EasyMock.anyObject());
             mMockListener.testRunEnded(
                     EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
 
diff --git a/tests/src/com/android/tradefed/testtype/rust/RustTestResultParserTest.java b/tests/src/com/android/tradefed/testtype/rust/RustTestResultParserTest.java
index 9e06ae9..9a261c0 100644
--- a/tests/src/com/android/tradefed/testtype/rust/RustTestResultParserTest.java
+++ b/tests/src/com/android/tradefed/testtype/rust/RustTestResultParserTest.java
@@ -134,9 +134,10 @@
                     EasyMock.anyObject(), EasyMock.<HashMap<String, Metric>>anyObject());
         }
         mMockListener.testFailed(
-                EasyMock.eq(new TestDescription("test", "idents")), EasyMock.anyObject());
+                EasyMock.eq(new TestDescription("test", "idents")), (String) EasyMock.anyObject());
         mMockListener.testFailed(
-                EasyMock.eq(new TestDescription("test", "literal_string")), EasyMock.anyObject());
+                EasyMock.eq(new TestDescription("test", "literal_string")),
+                (String) EasyMock.anyObject());
         mMockListener.testRunEnded(0, new HashMap<String, Metric>());
         replay(mMockListener);
         mParser.processNewLines(contents);
diff --git a/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java b/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
index bc6e8fc..4273924 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
@@ -456,7 +456,7 @@
                     EasyMock.anyLong(),
                     EasyMock.<HashMap<String, Metric>>anyObject());
         }
-        mMockListener.testFailed(EasyMock.anyObject(), EasyMock.anyObject());
+        mMockListener.testFailed(EasyMock.anyObject(), (String) EasyMock.anyObject());
         String aggError =
                 "unresponsive\n====Next Error====\n"
                         + "Module fakeName only ran 1 out of 4 expected tests.\n====Next "
@@ -877,8 +877,8 @@
                     EasyMock.anyLong(),
                     EasyMock.<HashMap<String, Metric>>anyObject());
         }
-        mMockListener.testFailed(EasyMock.anyObject(), EasyMock.anyObject());
-        mMockListener.testRunFailed(EasyMock.anyObject());
+        mMockListener.testFailed(EasyMock.anyObject(), (String) EasyMock.anyObject());
+        mMockListener.testRunFailed((String) EasyMock.anyObject());
         mMockListener.testRunEnded(
                 EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
         // Recovery is disabled during tearDown
@@ -939,8 +939,8 @@
                     EasyMock.anyLong(),
                     EasyMock.<HashMap<String, Metric>>anyObject());
         }
-        mMockListener.testFailed(EasyMock.anyObject(), EasyMock.anyObject());
-        mMockListener.testRunFailed(EasyMock.anyObject());
+        mMockListener.testFailed(EasyMock.anyObject(), (String) EasyMock.anyObject());
+        mMockListener.testRunFailed((String) EasyMock.anyObject());
         mMockListener.testRunEnded(
                 EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
 
@@ -1283,7 +1283,7 @@
                     EasyMock.anyLong(),
                     EasyMock.<HashMap<String, Metric>>anyObject());
         }
-        mMockListener.testFailed(EasyMock.anyObject(), EasyMock.anyObject());
+        mMockListener.testFailed(EasyMock.anyObject(), (String) EasyMock.anyObject());
         mMockListener.testRunFailed(
                 "unresponsive"
                         + TestRunResult.ERROR_DIVIDER
@@ -1531,7 +1531,7 @@
                     EasyMock.<HashMap<String, Metric>>anyObject());
             TestDescription testFail0 = new TestDescription(runName + "0class", "fail0");
             mMockListener.testStarted(EasyMock.eq(testFail0), EasyMock.anyLong());
-            mMockListener.testFailed(EasyMock.eq(testFail0), EasyMock.anyObject());
+            mMockListener.testFailed(EasyMock.eq(testFail0), (String) EasyMock.anyObject());
             mMockListener.testEnded(
                     EasyMock.eq(testFail0),
                     EasyMock.anyLong(),
@@ -1552,7 +1552,7 @@
                     EasyMock.<HashMap<String, Metric>>anyObject());
             TestDescription testFail0_1 = new TestDescription(runName + "1class", "fail0");
             mMockListener.testStarted(EasyMock.eq(testFail0_1), EasyMock.anyLong());
-            mMockListener.testFailed(EasyMock.eq(testFail0_1), EasyMock.anyObject());
+            mMockListener.testFailed(EasyMock.eq(testFail0_1), (String) EasyMock.anyObject());
             mMockListener.testEnded(
                     EasyMock.eq(testFail0_1),
                     EasyMock.anyLong(),
@@ -1637,7 +1637,7 @@
             }
             TestDescription testFail0 = new TestDescription(runName + "0class", "fail0");
             mMockListener.testStarted(EasyMock.eq(testFail0), EasyMock.anyLong());
-            mMockListener.testFailed(EasyMock.eq(testFail0), EasyMock.anyObject());
+            mMockListener.testFailed(EasyMock.eq(testFail0), (String) EasyMock.anyObject());
             mMockListener.testEnded(
                     EasyMock.eq(testFail0),
                     EasyMock.anyLong(),
@@ -1662,7 +1662,7 @@
             }
             TestDescription testFail0_1 = new TestDescription(runName + "1class", "fail0");
             mMockListener.testStarted(EasyMock.eq(testFail0_1), EasyMock.anyLong());
-            mMockListener.testFailed(EasyMock.eq(testFail0_1), EasyMock.anyObject());
+            mMockListener.testFailed(EasyMock.eq(testFail0_1), (String) EasyMock.anyObject());
             mMockListener.testEnded(
                     EasyMock.eq(testFail0_1),
                     EasyMock.anyLong(),