Merge "Do not generate the report in case of OOM"
diff --git a/.classpath b/.classpath
index 95c2966..6c19af3 100644
--- a/.classpath
+++ b/.classpath
@@ -13,7 +13,6 @@
 	<classpathentry kind="src" path="global_configuration"/>
 	<classpathentry excluding="Android.bp" kind="src" path="device_build_interfaces"/>
 	<classpathentry combineaccessrules="false" exported="true" kind="src" path="/tf-remote-client"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/LongevityHostRunner"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/loganalysis"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/platform-annotations"/>
@@ -41,5 +40,6 @@
 	<classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/tools/tradefederation/core/tradefed-protos/linux_glibc_common/combined/tradefed-protos.jar"/>
 	<classpathentry kind="var" path="TRADEFED_ROOT/prebuilts/tools/common/m2/repository/com/google/code/gson/gson/2.8.0/gson-2.8.0.jar"/>
 	<classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/prebuilts/tools/common/m2/protobuf-java-util-prebuilt-jar/linux_glibc_common/combined/protobuf-java-util-prebuilt-jar.jar"/>
+	<classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/platform_testing/libraries/health/runners/longevity/host/longevity-base-lib/linux_glibc_common/javac/longevity-base-lib.jar"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/Android.bp b/Android.bp
index 4b6d838..8bbb8f3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -114,10 +114,8 @@
         "junit-params",
         "kxml2-2.3.0",
         "libprotobuf-java-full",
-        "longevity-host-lib",
         "perfetto_config-full",
         "platform-test-annotations",
-        "test-composers",
         "tf-remote-client",
         "tradefed-protos",
     ],
diff --git a/atest/atest.py b/atest/atest.py
index 6baa28e..ba22e37 100755
--- a/atest/atest.py
+++ b/atest/atest.py
@@ -35,7 +35,6 @@
 import atest_arg_parser
 import atest_error
 import atest_execution_info
-import atest_metrics
 import atest_utils
 import bug_detector
 import cli_translator
@@ -528,7 +527,6 @@
     args = _parse_args(argv)
     _configure_logging(args.verbose)
     _validate_args(args)
-    atest_metrics.log_start_event()
     metrics_utils.get_start_time()
     metrics.AtestStartEvent(
         command_line=' '.join(argv),
diff --git a/res/config/instrumentations.xml b/res/config/instrumentations.xml
index 88c7d50..7df6186 100644
--- a/res/config/instrumentations.xml
+++ b/res/config/instrumentations.xml
@@ -14,11 +14,16 @@
      limitations under the License.
 -->
 <configuration description="Runs all the Android instrumentation tests on an existing device">
+    <option name="compress-files" value="false" />
+
+    <logger class="com.android.tradefed.log.FileLogger">
+        <option name="log-level" value="verbose" />
+        <option name="log-level-display" value="debug" />
+    </logger>
 
     <target_preparer class="com.android.tradefed.targetprep.InstallApkSetup">
         <!-- Use "apk-path" option to specify which apk to install -->
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.InstalledInstrumentationsTest" />
-
 </configuration>
diff --git a/src/com/android/tradefed/command/CommandOptions.java b/src/com/android/tradefed/command/CommandOptions.java
index 64ef014..ffeb6e3 100644
--- a/src/com/android/tradefed/command/CommandOptions.java
+++ b/src/com/android/tradefed/command/CommandOptions.java
@@ -201,6 +201,7 @@
     private String mHostLogSuffix = null;
 
     // [Options related to auto-retry]
+    @Deprecated
     @Option(
         name = "max-testcase-run-count",
         description =
@@ -209,6 +210,7 @@
     )
     private int mMaxRunLimit = 1;
 
+    @Deprecated
     @Option(
         name = "retry-strategy",
         description =
@@ -217,6 +219,7 @@
     )
     private RetryStrategy mRetryStrategy = RetryStrategy.NO_RETRY;
 
+    @Deprecated
     @Option(
         name = "auto-retry",
         description =
@@ -558,28 +561,4 @@
     public boolean shouldReportModuleProgression() {
         return mReportModuleProgression;
     }
-
-    /** {@inheritDoc} */
-    @Override
-    public int getMaxRetryCount() {
-        return mMaxRunLimit;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void setMaxRetryCount(int maxRetryCount) {
-        mMaxRunLimit = maxRetryCount;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public RetryStrategy getRetryStrategy() {
-        return mRetryStrategy;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean isAutoRetryEnabled() {
-        return mEnableAutoRetry;
-    }
 }
diff --git a/src/com/android/tradefed/command/ICommandOptions.java b/src/com/android/tradefed/command/ICommandOptions.java
index 9e5061d..a6682ae 100644
--- a/src/com/android/tradefed/command/ICommandOptions.java
+++ b/src/com/android/tradefed/command/ICommandOptions.java
@@ -17,7 +17,6 @@
 package com.android.tradefed.command;
 
 import com.android.tradefed.device.metric.AutoLogCollector;
-import com.android.tradefed.testtype.retry.RetryStrategy;
 import com.android.tradefed.util.UniqueMultiMap;
 
 import java.util.Set;
@@ -200,16 +199,4 @@
 
     /** Whether or not to report progression of remote invocation at module level. */
     public boolean shouldReportModuleProgression();
-
-    /** The maximum number of attempts during auto-retry. */
-    public int getMaxRetryCount();
-
-    /** Set the max retry count allowed during auto-retry. */
-    public void setMaxRetryCount(int maxRetryCount);
-
-    /** The {@link RetryStrategy} used during auto-retry. */
-    public RetryStrategy getRetryStrategy();
-
-    /** Whether or not to enable auto-retry. */
-    public boolean isAutoRetryEnabled();
 }
diff --git a/src/com/android/tradefed/config/Configuration.java b/src/com/android/tradefed/config/Configuration.java
index 242c566..5859b0d 100644
--- a/src/com/android/tradefed/config/Configuration.java
+++ b/src/com/android/tradefed/config/Configuration.java
@@ -40,10 +40,13 @@
 import com.android.tradefed.targetprep.multi.IMultiTargetPreparer;
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.StubTest;
+import com.android.tradefed.testtype.retry.BaseRetryDecision;
+import com.android.tradefed.testtype.retry.IRetryDecision;
 import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.IDisableable;
 import com.android.tradefed.util.MultiMap;
 import com.android.tradefed.util.QuotationAwareTokenizer;
+import com.android.tradefed.util.SystemUtil;
 import com.android.tradefed.util.keystore.IKeyStoreClient;
 
 import com.google.common.base.Joiner;
@@ -98,6 +101,7 @@
     public static final String METRIC_POST_PROCESSOR_TYPE_NAME = "metric_post_processor";
     public static final String SANDBOX_TYPE_NAME = "sandbox";
     public static final String SANBOX_OPTIONS_TYPE_NAME = "sandbox_options";
+    public static final String RETRY_DECISION_TYPE_NAME = "retry_decision";
 
     private static Map<String, ObjTypeInfo> sObjTypeMap = null;
     private static Set<String> sMultiDeviceSupportedTag = null;
@@ -185,6 +189,7 @@
                     METRIC_POST_PROCESSOR_TYPE_NAME,
                     new ObjTypeInfo(BasePostProcessor.class, true));
             sObjTypeMap.put(SANBOX_OPTIONS_TYPE_NAME, new ObjTypeInfo(SandboxOptions.class, false));
+            sObjTypeMap.put(RETRY_DECISION_TYPE_NAME, new ObjTypeInfo(IRetryDecision.class, false));
         }
         return sObjTypeMap;
     }
@@ -240,6 +245,7 @@
         setDeviceMetricCollectors(new ArrayList<>());
         setPostProcessors(new ArrayList<>());
         setConfigurationObjectNoThrow(SANBOX_OPTIONS_TYPE_NAME, new SandboxOptions());
+        setConfigurationObjectNoThrow(RETRY_DECISION_TYPE_NAME, new BaseRetryDecision());
     }
 
     /**
@@ -349,6 +355,12 @@
         return (ILogSaver) getConfigurationObject(LOG_SAVER_TYPE_NAME);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public IRetryDecision getRetryDecision() {
+        return (IRetryDecision) getConfigurationObject(RETRY_DECISION_TYPE_NAME);
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -1299,10 +1311,14 @@
     /** {@inheritDoc} */
     @Override
     public void resolveDynamicOptions() throws ConfigurationException {
-        ICommandOptions options = getCommandOptions();
-        if (options.getShardCount() != null && options.getShardIndex() == null) {
-            CLog.w("Skipping download due to local sharding detected.");
-            return;
+        // Resolve regardless of sharding if we are in remote environment because we know that's
+        // where the execution will occur.
+        if (!SystemUtil.isRemoteEnvironment()) {
+            ICommandOptions options = getCommandOptions();
+            if (options.getShardCount() != null && options.getShardIndex() == null) {
+                CLog.w("Skipping download due to local sharding detected.");
+                return;
+            }
         }
 
         ArgsOptionParser argsParser = new ArgsOptionParser(getAllConfigurationObjects());
@@ -1528,6 +1544,13 @@
                 excludeFilters,
                 printDeprecatedOptions,
                 printUnchangedOptions);
+        ConfigurationUtil.dumpClassToXml(
+                serializer,
+                RETRY_DECISION_TYPE_NAME,
+                getRetryDecision(),
+                excludeFilters,
+                printDeprecatedOptions,
+                printUnchangedOptions);
 
         serializer.endTag(null, ConfigurationUtil.CONFIGURATION_NAME);
         serializer.endDocument();
diff --git a/src/com/android/tradefed/config/IConfiguration.java b/src/com/android/tradefed/config/IConfiguration.java
index 53751cc..1da3daf 100644
--- a/src/com/android/tradefed/config/IConfiguration.java
+++ b/src/com/android/tradefed/config/IConfiguration.java
@@ -31,6 +31,7 @@
 import com.android.tradefed.targetprep.ITargetPreparer;
 import com.android.tradefed.targetprep.multi.IMultiTargetPreparer;
 import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.testtype.retry.IRetryDecision;
 import com.android.tradefed.util.keystore.IKeyStoreClient;
 
 import org.json.JSONArray;
@@ -111,6 +112,9 @@
      */
     public ILogSaver getLogSaver();
 
+    /** Returns the {@link IRetryDecision} used for the invocation. */
+    public IRetryDecision getRetryDecision();
+
     /**
      * Gets the {@link IMultiTargetPreparer}s from the configuration.
      *
diff --git a/src/com/android/tradefed/device/ManagedTestDeviceFactory.java b/src/com/android/tradefed/device/ManagedTestDeviceFactory.java
index 284c53e..faaf079 100644
--- a/src/com/android/tradefed/device/ManagedTestDeviceFactory.java
+++ b/src/com/android/tradefed/device/ManagedTestDeviceFactory.java
@@ -27,10 +27,10 @@
 import com.android.tradefed.device.cloud.NestedRemoteDevice;
 import com.android.tradefed.device.cloud.RemoteAndroidVirtualDevice;
 import com.android.tradefed.device.cloud.VmRemoteDevice;
-import com.android.tradefed.invoker.RemoteInvocationExecution;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.util.IRunUtil;
 import com.android.tradefed.util.RunUtil;
+import com.android.tradefed.util.SystemUtil;
 
 import java.io.IOException;
 import java.util.concurrent.TimeUnit;
@@ -190,10 +190,7 @@
      */
     @VisibleForTesting
     protected boolean isRemoteEnvironment() {
-        if ("1".equals(System.getenv(RemoteInvocationExecution.REMOTE_VM_VARIABLE))) {
-            return true;
-        }
-        return false;
+        return SystemUtil.isRemoteEnvironment();
     }
 
     /** Create a {@link CollectingOutputReceiver}. */
diff --git a/src/com/android/tradefed/device/cloud/GceManager.java b/src/com/android/tradefed/device/cloud/GceManager.java
index 508170f..1b3ec87 100644
--- a/src/com/android/tradefed/device/cloud/GceManager.java
+++ b/src/com/android/tradefed/device/cloud/GceManager.java
@@ -210,8 +210,18 @@
 
     /** Build and return the command to launch GCE. Exposed for testing. */
     protected List<String> buildGceCmd(File reportFile, IBuildInfo b) {
-        List<String> gceArgs =
-                ArrayUtil.list(getTestDeviceOptions().getAvdDriverBinary().getAbsolutePath());
+        File avdDriverFile = getTestDeviceOptions().getAvdDriverBinary();
+        if (!avdDriverFile.exists()) {
+            throw new RuntimeException(
+                    String.format(
+                            "Could not find the Acloud driver at %s",
+                            avdDriverFile.getAbsolutePath()));
+        }
+        if (!avdDriverFile.canExecute()) {
+            // Set the executable bit if needed
+            FileUtil.chmodGroupRWX(avdDriverFile);
+        }
+        List<String> gceArgs = ArrayUtil.list(avdDriverFile.getAbsolutePath());
         gceArgs.add(
                 TestDeviceOptions.getCreateCommandByInstanceType(
                         getTestDeviceOptions().getInstanceType()));
diff --git a/src/com/android/tradefed/device/cloud/ManagedRemoteDevice.java b/src/com/android/tradefed/device/cloud/ManagedRemoteDevice.java
index 8e2b8f0..288f567 100644
--- a/src/com/android/tradefed/device/cloud/ManagedRemoteDevice.java
+++ b/src/com/android/tradefed/device/cloud/ManagedRemoteDevice.java
@@ -131,8 +131,11 @@
                 getGceHandler().cleanUp();
             }
         } finally {
+            // Reset the internal variable
+            mCopiedOptions = null;
             if (mValidationConfig != null) {
                 mValidationConfig.cleanDynamicOptionFiles();
+                mValidationConfig = null;
             }
             // Ensure parent postInvocationTearDown is always called.
             super.postInvocationTearDown(exception);
diff --git a/src/com/android/tradefed/device/metric/LogcatOnFailureCollector.java b/src/com/android/tradefed/device/metric/LogcatOnFailureCollector.java
index 066b425..4f01ffa 100644
--- a/src/com/android/tradefed/device/metric/LogcatOnFailureCollector.java
+++ b/src/com/android/tradefed/device/metric/LogcatOnFailureCollector.java
@@ -16,10 +16,14 @@
 package com.android.tradefed.device.metric;
 
 import com.android.annotations.VisibleForTesting;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ILogcatReceiver;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.device.LogcatReceiver;
+import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
+import com.android.tradefed.result.ByteArrayInputStreamSource;
 import com.android.tradefed.result.InputStreamSource;
 import com.android.tradefed.result.LogDataType;
 import com.android.tradefed.result.TestDescription;
@@ -34,16 +38,24 @@
 
     private static final int MAX_LOGAT_SIZE_BYTES = 4 * 1024 * 1024;
     /** Always include a bit of prior data to capture what happened before */
-    private static final int OFFSET_CORRECTION = 20000;
+    private static final int OFFSET_CORRECTION = 10000;
 
     private static final String NAME_FORMAT = "%s-%s-logcat-on-failure";
 
+    private static final String LOGCAT_COLLECT_CMD = "logcat -T 150";
+    // -t implies -d (dump) so it's a one time collection
+    private static final String LOGCAT_COLLECT_CMD_LEGACY = "logcat -t 5000";
+    private static final int API_LIMIT = 20;
+
     private Map<ITestDevice, ILogcatReceiver> mLogcatReceivers = new HashMap<>();
     private Map<ITestDevice, Integer> mOffset = new HashMap<>();
 
     @Override
     public void onTestRunStart(DeviceMetricData runData) {
         for (ITestDevice device : getRealDevices()) {
+            if (getApiLevelNoThrow(device) < API_LIMIT) {
+                continue;
+            }
             // In case of multiple runs for the same test runner, re-init the receiver.
             initReceiver(device);
             // Get the current offset of the buffer to be able to query later
@@ -62,17 +74,9 @@
 
     @Override
     public void onTestFail(DeviceMetricData testData, TestDescription test) {
-        for (ITestDevice device : getRealDevices()) {
-            // Delay slightly for the error to get in the logcat
-            getRunUtil().sleep(100);
-            try (InputStreamSource logcatSource =
-                    mLogcatReceivers
-                            .get(device)
-                            .getLogcatData(MAX_LOGAT_SIZE_BYTES, mOffset.get(device))) {
-                String name = String.format(NAME_FORMAT, test.toString(), device.getSerialNumber());
-                super.testLog(name, LogDataType.LOGCAT, logcatSource);
-            }
-        }
+        // Delay slightly for the error to get in the logcat
+        getRunUtil().sleep(100);
+        collectAndLog(test);
     }
 
     @Override
@@ -84,7 +88,7 @@
     ILogcatReceiver createLogcatReceiver(ITestDevice device) {
         // Use logcat -T 'count' to only print a few line before we start and not the full buffer
         return new LogcatReceiver(
-                device, "logcat -T 150", device.getOptions().getMaxLogcatDataSize(), 0);
+                device, LOGCAT_COLLECT_CMD, device.getOptions().getMaxLogcatDataSize(), 0);
     }
 
     @VisibleForTesting
@@ -92,6 +96,31 @@
         return RunUtil.getDefault();
     }
 
+    private void collectAndLog(TestDescription test) {
+        for (ITestDevice device : getRealDevices()) {
+            ILogcatReceiver receiver = mLogcatReceivers.get(device);
+            // Receiver is only initialized above API 19, if not supported, we use a legacy command
+            if (receiver == null) {
+                CollectingByteOutputReceiver outputReceiver = new CollectingByteOutputReceiver();
+                try {
+                    device.executeShellCommand(LOGCAT_COLLECT_CMD_LEGACY, outputReceiver);
+                    saveLogcatSource(
+                            test,
+                            new ByteArrayInputStreamSource(outputReceiver.getOutput()),
+                            device.getSerialNumber());
+                } catch (DeviceNotAvailableException e) {
+                    CLog.e(e);
+                }
+                continue;
+            }
+            // If supported get the logcat buffer
+            saveLogcatSource(
+                    test,
+                    receiver.getLogcatData(MAX_LOGAT_SIZE_BYTES, mOffset.get(device)),
+                    device.getSerialNumber());
+        }
+    }
+
     private void initReceiver(ITestDevice device) {
         if (mLogcatReceivers.get(device) == null) {
             ILogcatReceiver receiver = createLogcatReceiver(device);
@@ -108,4 +137,19 @@
         mLogcatReceivers.clear();
         mOffset.clear();
     }
+
+    private int getApiLevelNoThrow(ITestDevice device) {
+        try {
+            return device.getApiLevel();
+        } catch (DeviceNotAvailableException e) {
+            return 1;
+        }
+    }
+
+    private void saveLogcatSource(TestDescription test, InputStreamSource source, String serial) {
+        try (InputStreamSource logcatSource = source) {
+            String name = String.format(NAME_FORMAT, test.toString(), serial);
+            super.testLog(name, LogDataType.LOGCAT, logcatSource);
+        }
+    }
 }
diff --git a/src/com/android/tradefed/invoker/RemoteInvocationExecution.java b/src/com/android/tradefed/invoker/RemoteInvocationExecution.java
index f82442c..f0c3685 100644
--- a/src/com/android/tradefed/invoker/RemoteInvocationExecution.java
+++ b/src/com/android/tradefed/invoker/RemoteInvocationExecution.java
@@ -50,6 +50,7 @@
 import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.IRunUtil;
 import com.android.tradefed.util.RunUtil;
+import com.android.tradefed.util.SystemUtil;
 import com.android.tradefed.util.TimeUtil;
 import com.android.tradefed.util.proto.TestRecordProtoUtil;
 
@@ -74,7 +75,6 @@
     public static final long REMOTE_PROCESS_RUNNING_WAIT = 15000L;
     public static final long LAUNCH_EXTRA_DEVICE = 10 * 60 * 1000L;
     public static final long NEW_USER_TIMEOUT = 5 * 60 * 1000L;
-    public static final String REMOTE_VM_VARIABLE = "REMOTE_VM_ENV";
 
     public static final String REMOTE_USER_DIR = "/home/{$USER}/";
     public static final String PROTO_RESULT_NAME = "output.pb";
@@ -329,7 +329,7 @@
         StringBuilder tfCmdBuilder =
                 new StringBuilder("TF_GLOBAL_CONFIG=" + globalConfig.getName());
         // Set an env variable to notify that this a remote environment.
-        tfCmdBuilder.append(" " + REMOTE_VM_VARIABLE + "=1");
+        tfCmdBuilder.append(" " + SystemUtil.REMOTE_VM_VARIABLE + "=1");
         // Disable clearcut in the remote
         tfCmdBuilder.append(" " + ClearcutClient.DISABLE_CLEARCUT_KEY + "=1");
         tfCmdBuilder.append(" ENTRY_CLASS=" + CommandRunner.class.getCanonicalName());
@@ -501,6 +501,13 @@
                     parser.processFileProto(resultFile);
                 }
             } while (resultFile != null);
+
+            if (!parser.invocationEndedReached()) {
+                currentInvocationListener.invocationFailed(
+                        new RuntimeException(
+                                "Parsing of results protos might be incomplete: invocation ended "
+                                        + "of remote execution was not found."));
+            }
         }
         return stillRunning;
     }
diff --git a/src/com/android/tradefed/invoker/TestInvocation.java b/src/com/android/tradefed/invoker/TestInvocation.java
index 8f18c26..278dc00 100644
--- a/src/com/android/tradefed/invoker/TestInvocation.java
+++ b/src/com/android/tradefed/invoker/TestInvocation.java
@@ -56,6 +56,7 @@
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.IResumableTest;
 import com.android.tradefed.testtype.IRetriableTest;
+import com.android.tradefed.testtype.retry.IRetryDecision;
 import com.android.tradefed.testtype.retry.ResultAggregator;
 import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.IRunUtil;
@@ -635,12 +636,11 @@
         ITestInvocationListener listener = null;
 
         // Auto retry feature
-        if (config.getCommandOptions().isAutoRetryEnabled()
-                && config.getCommandOptions().getMaxRetryCount() > 1) {
+        IRetryDecision decision = config.getRetryDecision();
+        if (decision.isAutoRetryEnabled() && decision.getMaxRetryCount() > 1) {
             CLog.d("Auto-retry enabled, using the ResultAggregator to handle multiple retries.");
             ResultAggregator aggregator =
-                    new ResultAggregator(
-                            allListeners, config.getCommandOptions().getRetryStrategy());
+                    new ResultAggregator(allListeners, decision.getRetryStrategy());
             allListeners = Arrays.asList(aggregator);
         }
 
diff --git a/src/com/android/tradefed/result/InvocationToJUnitResultForwarder.java b/src/com/android/tradefed/result/InvocationToJUnitResultForwarder.java
index 41776e9..acddd3b 100644
--- a/src/com/android/tradefed/result/InvocationToJUnitResultForwarder.java
+++ b/src/com/android/tradefed/result/InvocationToJUnitResultForwarder.java
@@ -59,6 +59,7 @@
         Test test = new TestIdentifierResult(testId);
         // TODO: is it accurate to represent the trace as AssertionFailedError?
         mJUnitListener.addFailure(test, new AssertionFailedError(trace));
+        Log.i(LOG_TAG, String.format("Test %s failed with:\n %s", testId.toString(), trace));
     }
 
     @Override
@@ -82,7 +83,7 @@
     @Override
     public void testRunFailed(String errorMessage) {
         // TODO: no run failed method on TestListener - would be good to propagate this up
-        Log.e(LOG_TAG, String.format("run failed: %s", errorMessage));
+        Log.e(LOG_TAG, String.format("Run failed: %s", errorMessage));
     }
 
     /**
@@ -107,7 +108,7 @@
     /** {@inheritDoc} */
     @Override
     public void testStarted(TestDescription test) {
-        Log.d(LOG_TAG, test.toString());
+        Log.d(LOG_TAG, String.format("Starting test: %s", test.toString()));
         mJUnitListener.startTest(new TestIdentifierResult(test));
     }
 
diff --git a/src/com/android/tradefed/result/LogcatCrashResultForwarder.java b/src/com/android/tradefed/result/LogcatCrashResultForwarder.java
index 18e8dde..4a0b738 100644
--- a/src/com/android/tradefed/result/LogcatCrashResultForwarder.java
+++ b/src/com/android/tradefed/result/LogcatCrashResultForwarder.java
@@ -38,6 +38,7 @@
 
     /** Special error message from the instrumentation when something goes wrong on device side. */
     public static final String ERROR_MESSAGE = "Process crashed.";
+    public static final String SYSTEM_CRASH_MESSAGE = "System has crashed.";
 
     public static final int MAX_NUMBER_CRASH = 3;
 
@@ -94,7 +95,8 @@
 
     /** Attempt to extract the crash from the logcat if the test was seen as started. */
     private String extractCrashAndAddToMessage(String errorMessage, Long startTime) {
-        if (errorMessage.contains(ERROR_MESSAGE) && startTime != null) {
+        if ((errorMessage.contains(ERROR_MESSAGE) || errorMessage.contains(SYSTEM_CRASH_MESSAGE))
+                && startTime != null) {
             mLogcatItem = extractLogcat(mDevice, startTime);
             errorMessage = addJavaCrashToString(mLogcatItem, errorMessage);
         }
diff --git a/src/com/android/tradefed/result/proto/ProtoResultParser.java b/src/com/android/tradefed/result/proto/ProtoResultParser.java
index c715a99..047375d 100644
--- a/src/com/android/tradefed/result/proto/ProtoResultParser.java
+++ b/src/com/android/tradefed/result/proto/ProtoResultParser.java
@@ -65,6 +65,7 @@
     private boolean mQuietParsing = true;
 
     private boolean mInvocationStarted = false;
+    private boolean mInvocationEnded = false;
 
     /** Ctor. */
     public ProtoResultParser(
@@ -163,6 +164,11 @@
         }
     }
 
+    /** Returns whether or not the parsing reached an invocation ended. */
+    public boolean invocationEndedReached() {
+        return mInvocationEnded;
+    }
+
     private void evalChildrenProto(List<ChildReference> children, boolean isInRun) {
         for (ChildReference child : children) {
             TestRecord childProto = child.getInlineTestRecord();
@@ -255,6 +261,7 @@
         }
 
         log("Invocation ended proto");
+        mInvocationEnded = true;
         if (!mReportInvocation) {
             CLog.d("Skipping invocation ended reporting.");
             return;
diff --git a/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java b/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java
index 9be70dd..8e66961 100644
--- a/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java
+++ b/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java
@@ -171,7 +171,7 @@
             installTrain(device, buildInfo, testAppFileNames, new String[] {"--staged"});
             return;
         }
-        installTrain(device, buildInfo, testAppFileNames, null);
+        installTrain(device, buildInfo, testAppFileNames, new String[] {});
     }
 
     /**
@@ -185,9 +185,21 @@
     protected void installTrain(
             ITestDevice device,
             IBuildInfo buildInfo,
-            Collection<String> moduleFilenames,
+            List<String> moduleFilenames,
             final String[] extraArgs)
             throws TargetSetupError, DeviceNotAvailableException {
+        // TODO(b/137883918):remove after new adb is released, which supports installing
+        // single apk/apex using 'install-multi-package'
+        if (moduleFilenames.size() == 1) {
+            String moduleFileName = moduleFilenames.get(0);
+            File module = getLocalPathForFilename(buildInfo, moduleFileName, device);
+            device.installPackage(module, true, extraArgs);
+            if (moduleFileName.endsWith(APK_SUFFIX)) {
+                String packageName = parsePackageName(module, device.getDeviceDescriptor());
+                mApkInstalled.add(packageName);
+            }
+            return;
+        }
 
         List<String> apkPackageNames = new ArrayList<>();
         List<String> trainInstallCmd = new ArrayList<>();
@@ -498,8 +510,7 @@
             List<String> testAppFileNames, ITestDevice device, IBuildInfo buildInfo)
             throws TargetSetupError, DeviceNotAvailableException {
         for (String moduleFileName : testAppFileNames) {
-            if (moduleFileName.endsWith(APK_SUFFIX) &&
-                isPersistentApk(moduleFileName, device, buildInfo)) {
+            if (isPersistentApk(moduleFileName, device, buildInfo)) {
                 return true;
             }
         }
@@ -516,6 +527,9 @@
      */
     protected boolean isPersistentApk(String filename, ITestDevice device, IBuildInfo buildInfo)
             throws TargetSetupError, DeviceNotAvailableException {
+        if (!filename.endsWith(APK_SUFFIX)) {
+            return false;
+        }
         File moduleFile = getLocalPathForFilename(buildInfo, filename, device);
         PackageInfo pkgInfo =
             device.getAppPackageInfo(parsePackageName(moduleFile, device.getDeviceDescriptor()));
diff --git a/src/com/android/tradefed/testtype/retry/BaseRetryDecision.java b/src/com/android/tradefed/testtype/retry/BaseRetryDecision.java
index 862dd36..d254c98 100644
--- a/src/com/android/tradefed/testtype/retry/BaseRetryDecision.java
+++ b/src/com/android/tradefed/testtype/retry/BaseRetryDecision.java
@@ -15,15 +15,22 @@
  */
 package com.android.tradefed.testtype.retry;
 
+import com.android.tradefed.config.Option;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.StubDevice;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.testtype.IDeviceTest;
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.ITestFilterReceiver;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Base implementation of {@link IRetryDecision}. Base implementation only take local signals into
@@ -31,17 +38,60 @@
  */
 public class BaseRetryDecision implements IRetryDecision {
 
-    private RetryStrategy mRetryStrategy;
+    @Option(
+        name = "reboot-at-last-retry",
+        description = "Reboot the device at the last retry attempt."
+    )
+    private boolean mRebootAtLastRetry = false;
+
+    @Option(
+        name = "max-testcase-run-count",
+        description =
+                "If the IRemoteTest can have its testcases run multiple times, "
+                        + "the max number of runs for each testcase."
+    )
+    private int mMaxRetryAttempts = 1;
+
+    @Option(
+        name = "retry-strategy",
+        description =
+                "The retry strategy to be used when re-running some tests with "
+                        + "--max-testcase-run-count"
+    )
+    private RetryStrategy mRetryStrategy = RetryStrategy.NO_RETRY;
+
+    @Option(
+        name = "auto-retry",
+        description =
+                "Whether or not to enable the new auto-retry. This is a feature flag for testing."
+    )
+    private boolean mEnableAutoRetry = false;
+
     private IRemoteTest mCurrentlyConsideredTest;
     private RetryStatsHelper mStatistics;
 
-    /** Constructor for the retry decision, always based on the {@link RetryStrategy}. */
-    public BaseRetryDecision(RetryStrategy strategy) {
-        mRetryStrategy = strategy;
+    /** Constructor for the retry decision */
+    public BaseRetryDecision() {}
+
+    @Override
+    public boolean isAutoRetryEnabled() {
+        return mEnableAutoRetry;
     }
 
     @Override
-    public boolean shouldRetry(IRemoteTest test, List<TestRunResult> previousResults) {
+    public RetryStrategy getRetryStrategy() {
+        return mRetryStrategy;
+    }
+
+    @Override
+    public int getMaxRetryCount() {
+        return mMaxRetryAttempts;
+    }
+
+    @Override
+    public boolean shouldRetry(
+            IRemoteTest test, int attemptJustExecuted, List<TestRunResult> previousResults)
+            throws DeviceNotAvailableException {
         // Keep track of some results for the test in progress for statistics purpose.
         if (test != mCurrentlyConsideredTest) {
             mCurrentlyConsideredTest = test;
@@ -74,7 +124,12 @@
         // TODO(b/77548917): Right now we only support ITestFilterReceiver. We should expect to
         // support ITestFile*Filter*Receiver in the future.
         ITestFilterReceiver filterableTest = (ITestFilterReceiver) test;
-        return handleRetryFailures(filterableTest, previousResults);
+        boolean shouldRetry = handleRetryFailures(filterableTest, previousResults);
+        if (shouldRetry) {
+            // In case of retry, go through the recovery routine
+            recoverStateOfDevices(getDevices(test), attemptJustExecuted);
+        }
+        return shouldRetry;
     }
 
     @Override
@@ -146,4 +201,31 @@
             test.addIncludeFilter(filter);
         }
     }
+
+    /** Returns all the non-stub device associated with the {@link IRemoteTest}. */
+    private List<ITestDevice> getDevices(IRemoteTest test) {
+        List<ITestDevice> listDevices = new ArrayList<>();
+        if (test instanceof IDeviceTest) {
+            ITestDevice device = ((IDeviceTest) test).getDevice();
+            if (device != null) {
+                listDevices.add(device);
+            }
+        }
+        // Return all the non-stub device (the one we can actually do some recovery against)
+        return listDevices
+                .stream()
+                .filter(d -> (!(d.getIDevice() instanceof StubDevice)))
+                .collect(Collectors.toList());
+    }
+
+    /** Recovery attempt on the device to get it a better state before next retry. */
+    private void recoverStateOfDevices(List<ITestDevice> devices, int lastAttempt)
+            throws DeviceNotAvailableException {
+        // TODO: Support multi-devices recovery
+        for (ITestDevice device : devices) {
+            if (mRebootAtLastRetry && (lastAttempt == (mMaxRetryAttempts - 2))) {
+                device.reboot();
+            }
+        }
+    }
 }
diff --git a/src/com/android/tradefed/testtype/retry/IRetryDecision.java b/src/com/android/tradefed/testtype/retry/IRetryDecision.java
index 808520f..249eab6 100644
--- a/src/com/android/tradefed/testtype/retry/IRetryDecision.java
+++ b/src/com/android/tradefed/testtype/retry/IRetryDecision.java
@@ -15,6 +15,7 @@
  */
 package com.android.tradefed.testtype.retry;
 
+import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.result.TestRunResult;
 import com.android.tradefed.testtype.IRemoteTest;
 
@@ -26,18 +27,31 @@
  */
 public interface IRetryDecision {
 
+    /** Whether or not to enable auto-retry. */
+    public boolean isAutoRetryEnabled();
+
+    /** The {@link RetryStrategy} used during auto-retry. */
+    public RetryStrategy getRetryStrategy();
+
+    /** The maximum number of attempts during auto-retry. */
+    public int getMaxRetryCount();
+
     /**
      * Decide whether or not retry should be attempted. Also make any necessary changes to the
      * {@link IRemoteTest} to be retried (Applying filters, etc.).
      *
      * @param test The {@link IRemoteTest} that just ran.
+     * @param attemptJustExecuted The number of the attempt that we just ran.
      * @param previousResults The list of {@link TestRunResult} of the test that just ran.
      * @return True if we should retry, False otherwise.
+     * @throws DeviceNotAvailableException Can be thrown during device recovery
      */
-    public boolean shouldRetry(IRemoteTest test, List<TestRunResult> previousResults);
+    public boolean shouldRetry(
+            IRemoteTest test, int attemptJustExecuted, List<TestRunResult> previousResults)
+            throws DeviceNotAvailableException;
 
     /**
-     * {@link #shouldRetry(IRemoteTest, List)} will most likely be called before the last retry
+     * {@link #shouldRetry(IRemoteTest, int, List)} will most likely be called before the last retry
      * attempt, so we might be missing the very last attempt results for statistics purpose. This
      * method allows those results to be provided for proper statistics calculations.
      *
diff --git a/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java b/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
index 172ac97..f4d52ec 100644
--- a/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
+++ b/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
@@ -19,8 +19,6 @@
 import com.android.tradefed.config.IConfiguration;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.DeviceUnresponsiveException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.device.StubDevice;
 import com.android.tradefed.device.metric.CollectorHelper;
 import com.android.tradefed.device.metric.IMetricCollector;
 import com.android.tradefed.device.metric.IMetricCollectorReceiver;
@@ -33,11 +31,9 @@
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.ITestCollector;
 import com.android.tradefed.testtype.ITestFilterReceiver;
-import com.android.tradefed.testtype.retry.BaseRetryDecision;
 import com.android.tradefed.testtype.retry.IRetryDecision;
 import com.android.tradefed.testtype.retry.MergeStrategy;
 import com.android.tradefed.testtype.retry.RetryStatistics;
-import com.android.tradefed.testtype.retry.RetryStrategy;
 import com.android.tradefed.util.StreamUtil;
 
 import com.google.common.annotations.VisibleForTesting;
@@ -70,6 +66,7 @@
  */
 public class GranularRetriableTestWrapper implements IRemoteTest, ITestCollector {
 
+    private IRetryDecision mRetryDecision;
     private IRemoteTest mTest;
     private List<IMetricCollector> mRunMetricCollectors;
     private TestFailureListener mFailureListener;
@@ -87,9 +84,6 @@
     // Tracking of the metrics
     private RetryStatistics mRetryStats = null;
 
-    private RetryStrategy mRetryStrategy = RetryStrategy.NO_RETRY;
-    private boolean mRebootAtLastRetry = false;
-
     public GranularRetriableTestWrapper(
             IRemoteTest test,
             ITestInvocationListener mainListener,
@@ -103,6 +97,11 @@
         mMaxRunLimit = maxRunLimit;
     }
 
+    /** Sets the {@link IRetryDecision} to be used. */
+    public void setRetryDecision(IRetryDecision decision) {
+        mRetryDecision = decision;
+    }
+
     /**
      * Set the {@link ModuleDefinition} name as a {@link GranularRetriableTestWrapper} attribute.
      *
@@ -161,16 +160,6 @@
         mLogSaver = logSaver;
     }
 
-    /** Sets the {@link RetryStrategy} to be used when retrying. */
-    public final void setRetryStrategy(RetryStrategy retryStrategy) {
-        mRetryStrategy = retryStrategy;
-    }
-
-    /** Sets the flag to reboot devices at the last intra-module retry. */
-    public final void setRebootAtLastRetry(boolean rebootAtLastRetry) {
-        mRebootAtLastRetry = rebootAtLastRetry;
-    }
-
     /**
      * Initialize a new {@link ModuleListener} for each test run.
      *
@@ -225,9 +214,14 @@
             return;
         }
 
+        if (mRetryDecision == null) {
+            CLog.e("RetryDecision is null. Something is misconfigured this shouldn't happen");
+            return;
+        }
+
         // Bail out early if there is no need to retry at all.
-        IRetryDecision retryDecision = new BaseRetryDecision(mRetryStrategy);
-        if (!retryDecision.shouldRetry(mTest, mMainGranularRunListener.getTestRunForAttempts(0))) {
+        if (!mRetryDecision.shouldRetry(
+                mTest, 0, mMainGranularRunListener.getTestRunForAttempts(0))) {
             return;
         }
 
@@ -237,30 +231,21 @@
             CLog.d("Starting intra-module retry.");
             for (int attemptNumber = 1; attemptNumber < mMaxRunLimit; attemptNumber++) {
                 boolean retry =
-                        retryDecision.shouldRetry(
+                        mRetryDecision.shouldRetry(
                                 mTest,
+                                attemptNumber - 1,
                                 mMainGranularRunListener.getTestRunForAttempts(attemptNumber - 1));
                 if (!retry) {
                     return;
                 }
-                // Reboot device at the last intra-module retry if reboot-at-last-retry is set.
-                if (mRebootAtLastRetry && (attemptNumber == (mMaxRunLimit-1))) {
-                    for (ITestDevice device : mModuleInvocationContext.getDevices()) {
-                        if (!(device.getIDevice() instanceof StubDevice)) {
-                            CLog.i("Rebooting device: %s at the last intra-module retry.",
-                                    device.getSerialNumber());
-                            device.reboot();
-                        }
-                    }
-                }
                 // Run the tests again
                 intraModuleRun(allListeners);
             }
             // Feed the last attempt if we reached here.
-            retryDecision.addLastAttempt(
+            mRetryDecision.addLastAttempt(
                     mMainGranularRunListener.getTestRunForAttempts(mMaxRunLimit - 1));
         } finally {
-            mRetryStats = retryDecision.getRetryStats();
+            mRetryStats = mRetryDecision.getRetryStats();
             // Track how long we spend in retry
             mRetryStats.mRetryTime = System.currentTimeMillis() - startTime;
         }
@@ -315,7 +300,7 @@
 
     /** Get the merged TestRunResults from each {@link IRemoteTest} run. */
     public final List<TestRunResult> getFinalTestRunResults() {
-        MergeStrategy strategy = MergeStrategy.getMergeStrategy(mRetryStrategy);
+        MergeStrategy strategy = MergeStrategy.getMergeStrategy(mRetryDecision.getRetryStrategy());
         mMainGranularRunListener.setMergeStrategy(strategy);
         return mMainGranularRunListener.getMergedTestRunResults();
     }
diff --git a/src/com/android/tradefed/testtype/suite/ITestSuite.java b/src/com/android/tradefed/testtype/suite/ITestSuite.java
index 370d6e5..7ae4b78 100644
--- a/src/com/android/tradefed/testtype/suite/ITestSuite.java
+++ b/src/com/android/tradefed/testtype/suite/ITestSuite.java
@@ -164,11 +164,6 @@
     @Option(name = "reboot-per-module", description = "Reboot the device before every module run.")
     private boolean mRebootPerModule = false;
 
-    @Deprecated
-    @Option(name = "reboot-at-last-retry",
-        description = "Reboot the device at the last intra-module retry")
-    private boolean mRebootAtLastRetry = false;
-
     @Option(
         name = REBOOT_BEFORE_TEST,
         description = "Reboot the device before the test suite starts."
@@ -735,17 +730,16 @@
         // Pass the main invocation logSaver
         module.setLogSaver(mMainConfiguration.getLogSaver());
         // Pass the retry strategy to the module
-        module.setRetryStrategy(
-                getConfiguration().getCommandOptions().getRetryStrategy(), mMergeAttempts);
-        // Pass the reboot strategy at the last intra-module retry to the module
-        module.setRebootAtLastRetry(mRebootAtLastRetry);
+        module.setMergeAttemps(mMergeAttempts);
+        // Pass the retry decision to be used.
+        module.setRetryDecision(mMainConfiguration.getRetryDecision());
 
         // Actually run the module
         module.run(
                 listener,
                 moduleListeners,
                 failureListener,
-                getConfiguration().getCommandOptions().getMaxRetryCount());
+                getConfiguration().getRetryDecision().getMaxRetryCount());
 
         if (!mSkipAllSystemStatusCheck) {
             runPostModuleCheck(module.getId(), mSystemStatusCheckers, mDevice, listener);
diff --git a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
index 665ec6f..098862d 100644
--- a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
+++ b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
@@ -56,8 +56,8 @@
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.IRuntimeHintProvider;
 import com.android.tradefed.testtype.ITestCollector;
+import com.android.tradefed.testtype.retry.IRetryDecision;
 import com.android.tradefed.testtype.retry.RetryStatistics;
-import com.android.tradefed.testtype.retry.RetryStrategy;
 import com.android.tradefed.testtype.suite.module.BaseModuleController;
 import com.android.tradefed.testtype.suite.module.IModuleController.RunStrategy;
 import com.android.tradefed.util.StreamUtil;
@@ -136,9 +136,8 @@
     // Tracking of retry performance
     private List<RetryStatistics> mRetryStats = new ArrayList<>();
 
-    private RetryStrategy mRetryStrategy = RetryStrategy.NO_RETRY;
     private boolean mMergeAttempts = true;
-    private boolean mRebootAtLastRetry = false;
+    private IRetryDecision mRetryDecision;
 
     // Token during sharding
     private Set<TokenProperty> mRequiredTokens = new HashSet<>();
@@ -584,8 +583,7 @@
         retriableTest.setModuleConfig(mModuleConfiguration);
         retriableTest.setInvocationContext(mModuleInvocationContext);
         retriableTest.setLogSaver(mLogSaver);
-        retriableTest.setRetryStrategy(mRetryStrategy);
-        retriableTest.setRebootAtLastRetry(mRebootAtLastRetry);
+        retriableTest.setRetryDecision(mRetryDecision);
         return retriableTest;
     }
 
@@ -866,15 +864,14 @@
         mCollectTestsOnly = collectTestsOnly;
     }
 
-    /** Sets the {@link RetryStrategy} to be used when retrying. */
-    public final void setRetryStrategy(RetryStrategy retryStrategy, boolean mergeAttempts) {
-        mRetryStrategy = retryStrategy;
+    /** Sets whether or not we should merge results. */
+    public final void setMergeAttemps(boolean mergeAttempts) {
         mMergeAttempts = mergeAttempts;
     }
 
-    /** Sets the flag to reboot devices at the last intra-module retry. */
-    public final void setRebootAtLastRetry(boolean rebootAtLastRetry) {
-        mRebootAtLastRetry = rebootAtLastRetry;
+    /** Sets the {@link IRetryDecision} to be used for intra-module retry. */
+    public final void setRetryDecision(IRetryDecision decision) {
+        mRetryDecision = decision;
     }
 
     /** Returns a list of tests that ran in this module. */
diff --git a/src/com/android/tradefed/util/SystemUtil.java b/src/com/android/tradefed/util/SystemUtil.java
index 6a66c89..a38bc0b 100644
--- a/src/com/android/tradefed/util/SystemUtil.java
+++ b/src/com/android/tradefed/util/SystemUtil.java
@@ -42,6 +42,8 @@
         ANDROID_HOST_OUT_TESTCASES,
     }
 
+    public static final String REMOTE_VM_VARIABLE = "REMOTE_VM_ENV";
+
     private static final String HOST_TESTCASES = "host/testcases";
     private static final String TARGET_TESTCASES = "target/testcases";
 
@@ -163,4 +165,12 @@
             return new File(path);
         }
     }
+
+    /** Return true if we are currently running in a remote environment. */
+    public static boolean isRemoteEnvironment() {
+        if ("1".equals(System.getenv(REMOTE_VM_VARIABLE))) {
+            return true;
+        }
+        return false;
+    }
 }
diff --git a/test_framework/Android.bp b/test_framework/Android.bp
index 561d9a8..3178c3d 100644
--- a/test_framework/Android.bp
+++ b/test_framework/Android.bp
@@ -18,6 +18,10 @@
     srcs: [
         "com/**/*.java",
     ],
+    static_libs: [
+        "longevity-host-lib",
+        "test-composers",
+    ],
     libs: [
         "tradefed-lib-core",
     ],
diff --git a/test_framework/OWNERS b/test_framework/OWNERS
new file mode 100644
index 0000000..1a475dc
--- /dev/null
+++ b/test_framework/OWNERS
@@ -0,0 +1,5 @@
+# Base Owners + extra folks that can review the test_framework layer
+allenhair@google.com
+gelanchezhian@google.com
+mrosenfeld@google.com
+
diff --git a/test_framework/README.md b/test_framework/README.md
new file mode 100644
index 0000000..cec8088
--- /dev/null
+++ b/test_framework/README.md
@@ -0,0 +1,10 @@
+# Trade Federation Test Framework
+
+This is the top-layer provided to write and run tests against.
+
+The goal of this layer is to provide a simple framework to
+write and execute tests.
+
+This directory should contain classes that are:
+* Related to tests (IRemoteTest types)
+* Related to tests setup (ITargetPreparer types)
diff --git a/src/com/android/tradefed/result/JUnit4ResultForwarder.java b/test_framework/com/android/tradefed/result/JUnit4ResultForwarder.java
similarity index 100%
rename from src/com/android/tradefed/result/JUnit4ResultForwarder.java
rename to test_framework/com/android/tradefed/result/JUnit4ResultForwarder.java
diff --git a/src/com/android/tradefed/targetprep/InstrumentationPreparer.java b/test_framework/com/android/tradefed/targetprep/InstrumentationPreparer.java
similarity index 100%
rename from src/com/android/tradefed/targetprep/InstrumentationPreparer.java
rename to test_framework/com/android/tradefed/targetprep/InstrumentationPreparer.java
diff --git a/src/com/android/tradefed/testtype/AndroidJUnitTest.java b/test_framework/com/android/tradefed/testtype/AndroidJUnitTest.java
similarity index 100%
rename from src/com/android/tradefed/testtype/AndroidJUnitTest.java
rename to test_framework/com/android/tradefed/testtype/AndroidJUnitTest.java
diff --git a/src/com/android/tradefed/testtype/CodeCoverageReportFormat.java b/test_framework/com/android/tradefed/testtype/CodeCoverageReportFormat.java
similarity index 100%
rename from src/com/android/tradefed/testtype/CodeCoverageReportFormat.java
rename to test_framework/com/android/tradefed/testtype/CodeCoverageReportFormat.java
diff --git a/src/com/android/tradefed/testtype/CodeCoverageTest.java b/test_framework/com/android/tradefed/testtype/CodeCoverageTest.java
similarity index 100%
rename from src/com/android/tradefed/testtype/CodeCoverageTest.java
rename to test_framework/com/android/tradefed/testtype/CodeCoverageTest.java
diff --git a/src/com/android/tradefed/testtype/CompanionAwareTest.java b/test_framework/com/android/tradefed/testtype/CompanionAwareTest.java
similarity index 100%
rename from src/com/android/tradefed/testtype/CompanionAwareTest.java
rename to test_framework/com/android/tradefed/testtype/CompanionAwareTest.java
diff --git a/src/com/android/tradefed/testtype/DeviceJUnit4ClassRunner.java b/test_framework/com/android/tradefed/testtype/DeviceJUnit4ClassRunner.java
similarity index 100%
rename from src/com/android/tradefed/testtype/DeviceJUnit4ClassRunner.java
rename to test_framework/com/android/tradefed/testtype/DeviceJUnit4ClassRunner.java
diff --git a/src/com/android/tradefed/testtype/DeviceSuite.java b/test_framework/com/android/tradefed/testtype/DeviceSuite.java
similarity index 100%
rename from src/com/android/tradefed/testtype/DeviceSuite.java
rename to test_framework/com/android/tradefed/testtype/DeviceSuite.java
diff --git a/src/com/android/tradefed/testtype/DeviceTestCase.java b/test_framework/com/android/tradefed/testtype/DeviceTestCase.java
similarity index 100%
rename from src/com/android/tradefed/testtype/DeviceTestCase.java
rename to test_framework/com/android/tradefed/testtype/DeviceTestCase.java
diff --git a/src/com/android/tradefed/testtype/DeviceTestResult.java b/test_framework/com/android/tradefed/testtype/DeviceTestResult.java
similarity index 100%
rename from src/com/android/tradefed/testtype/DeviceTestResult.java
rename to test_framework/com/android/tradefed/testtype/DeviceTestResult.java
diff --git a/src/com/android/tradefed/testtype/DeviceTestSuite.java b/test_framework/com/android/tradefed/testtype/DeviceTestSuite.java
similarity index 100%
rename from src/com/android/tradefed/testtype/DeviceTestSuite.java
rename to test_framework/com/android/tradefed/testtype/DeviceTestSuite.java
diff --git a/src/com/android/tradefed/testtype/GTest.java b/test_framework/com/android/tradefed/testtype/GTest.java
similarity index 100%
rename from src/com/android/tradefed/testtype/GTest.java
rename to test_framework/com/android/tradefed/testtype/GTest.java
diff --git a/src/com/android/tradefed/testtype/GTestBase.java b/test_framework/com/android/tradefed/testtype/GTestBase.java
similarity index 100%
rename from src/com/android/tradefed/testtype/GTestBase.java
rename to test_framework/com/android/tradefed/testtype/GTestBase.java
diff --git a/src/com/android/tradefed/testtype/GTestListTestParser.java b/test_framework/com/android/tradefed/testtype/GTestListTestParser.java
similarity index 100%
rename from src/com/android/tradefed/testtype/GTestListTestParser.java
rename to test_framework/com/android/tradefed/testtype/GTestListTestParser.java
diff --git a/src/com/android/tradefed/testtype/GTestResultParser.java b/test_framework/com/android/tradefed/testtype/GTestResultParser.java
similarity index 100%
rename from src/com/android/tradefed/testtype/GTestResultParser.java
rename to test_framework/com/android/tradefed/testtype/GTestResultParser.java
diff --git a/src/com/android/tradefed/testtype/GTestXmlResultParser.java b/test_framework/com/android/tradefed/testtype/GTestXmlResultParser.java
similarity index 100%
rename from src/com/android/tradefed/testtype/GTestXmlResultParser.java
rename to test_framework/com/android/tradefed/testtype/GTestXmlResultParser.java
diff --git a/src/com/android/tradefed/testtype/GoogleBenchmarkResultParser.java b/test_framework/com/android/tradefed/testtype/GoogleBenchmarkResultParser.java
similarity index 100%
rename from src/com/android/tradefed/testtype/GoogleBenchmarkResultParser.java
rename to test_framework/com/android/tradefed/testtype/GoogleBenchmarkResultParser.java
diff --git a/src/com/android/tradefed/testtype/GoogleBenchmarkTest.java b/test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java
similarity index 100%
rename from src/com/android/tradefed/testtype/GoogleBenchmarkTest.java
rename to test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java
diff --git a/src/com/android/tradefed/testtype/HostGTest.java b/test_framework/com/android/tradefed/testtype/HostGTest.java
similarity index 99%
rename from src/com/android/tradefed/testtype/HostGTest.java
rename to test_framework/com/android/tradefed/testtype/HostGTest.java
index bfb4f54..8619236 100644
--- a/src/com/android/tradefed/testtype/HostGTest.java
+++ b/test_framework/com/android/tradefed/testtype/HostGTest.java
@@ -180,6 +180,8 @@
                             String.format("Command run timed out after %d ms", maxTestTimeMs));
                 case EXCEPTION:
                     throw new RuntimeException("Command run failed with exception");
+                default:
+                    break;
             }
         } finally {
             resultParser.flush();
diff --git a/src/com/android/tradefed/testtype/HostTest.java b/test_framework/com/android/tradefed/testtype/HostTest.java
similarity index 100%
rename from src/com/android/tradefed/testtype/HostTest.java
rename to test_framework/com/android/tradefed/testtype/HostTest.java
diff --git a/src/com/android/tradefed/testtype/InstalledInstrumentationsTest.java b/test_framework/com/android/tradefed/testtype/InstalledInstrumentationsTest.java
similarity index 100%
rename from src/com/android/tradefed/testtype/InstalledInstrumentationsTest.java
rename to test_framework/com/android/tradefed/testtype/InstalledInstrumentationsTest.java
diff --git a/src/com/android/tradefed/testtype/InstrumentationFileTest.java b/test_framework/com/android/tradefed/testtype/InstrumentationFileTest.java
similarity index 100%
rename from src/com/android/tradefed/testtype/InstrumentationFileTest.java
rename to test_framework/com/android/tradefed/testtype/InstrumentationFileTest.java
diff --git a/src/com/android/tradefed/testtype/InstrumentationSerialTest.java b/test_framework/com/android/tradefed/testtype/InstrumentationSerialTest.java
similarity index 100%
rename from src/com/android/tradefed/testtype/InstrumentationSerialTest.java
rename to test_framework/com/android/tradefed/testtype/InstrumentationSerialTest.java
diff --git a/src/com/android/tradefed/testtype/InstrumentationTest.java b/test_framework/com/android/tradefed/testtype/InstrumentationTest.java
similarity index 99%
rename from src/com/android/tradefed/testtype/InstrumentationTest.java
rename to test_framework/com/android/tradefed/testtype/InstrumentationTest.java
index cba5774..aaab43c 100644
--- a/src/com/android/tradefed/testtype/InstrumentationTest.java
+++ b/test_framework/com/android/tradefed/testtype/InstrumentationTest.java
@@ -1002,6 +1002,9 @@
         }
         if (mRebootBeforeReRun) {
             mDevice.reboot();
+        } else {
+            // Ensure device is online and responsive before retrying.
+            mDevice.waitForDeviceAvailable();
         }
 
         IRemoteTest testReRunner = null;
diff --git a/src/com/android/tradefed/testtype/JUnitRunUtil.java b/test_framework/com/android/tradefed/testtype/JUnitRunUtil.java
similarity index 100%
rename from src/com/android/tradefed/testtype/JUnitRunUtil.java
rename to test_framework/com/android/tradefed/testtype/JUnitRunUtil.java
diff --git a/src/com/android/tradefed/testtype/JavaCodeCoverageListener.java b/test_framework/com/android/tradefed/testtype/JavaCodeCoverageListener.java
similarity index 100%
rename from src/com/android/tradefed/testtype/JavaCodeCoverageListener.java
rename to test_framework/com/android/tradefed/testtype/JavaCodeCoverageListener.java
diff --git a/src/com/android/tradefed/testtype/MetricTestCase.java b/test_framework/com/android/tradefed/testtype/MetricTestCase.java
similarity index 100%
rename from src/com/android/tradefed/testtype/MetricTestCase.java
rename to test_framework/com/android/tradefed/testtype/MetricTestCase.java
diff --git a/src/com/android/tradefed/testtype/NativeBenchmarkTest.java b/test_framework/com/android/tradefed/testtype/NativeBenchmarkTest.java
similarity index 100%
rename from src/com/android/tradefed/testtype/NativeBenchmarkTest.java
rename to test_framework/com/android/tradefed/testtype/NativeBenchmarkTest.java
diff --git a/src/com/android/tradefed/testtype/NativeBenchmarkTestParser.java b/test_framework/com/android/tradefed/testtype/NativeBenchmarkTestParser.java
similarity index 100%
rename from src/com/android/tradefed/testtype/NativeBenchmarkTestParser.java
rename to test_framework/com/android/tradefed/testtype/NativeBenchmarkTestParser.java
diff --git a/src/com/android/tradefed/testtype/NativeCodeCoverageListener.java b/test_framework/com/android/tradefed/testtype/NativeCodeCoverageListener.java
similarity index 98%
rename from src/com/android/tradefed/testtype/NativeCodeCoverageListener.java
rename to test_framework/com/android/tradefed/testtype/NativeCodeCoverageListener.java
index dca83db..80a7d31 100644
--- a/src/com/android/tradefed/testtype/NativeCodeCoverageListener.java
+++ b/test_framework/com/android/tradefed/testtype/NativeCodeCoverageListener.java
@@ -44,7 +44,7 @@
  */
 public final class NativeCodeCoverageListener extends ResultForwarder {
 
-    private static final String NATIVE_COVERAGE_DEVICE_PATH = "/data/misc/trace/proc/self/cwd/out";
+    private static final String NATIVE_COVERAGE_DEVICE_PATH = "/data/misc/trace";
     private static final String COVERAGE_FILE_LIST_COMMAND =
             String.format("find %s -name '*.gcda'", NATIVE_COVERAGE_DEVICE_PATH);
 
diff --git a/src/com/android/tradefed/testtype/NativeStressTest.java b/test_framework/com/android/tradefed/testtype/NativeStressTest.java
similarity index 100%
rename from src/com/android/tradefed/testtype/NativeStressTest.java
rename to test_framework/com/android/tradefed/testtype/NativeStressTest.java
diff --git a/src/com/android/tradefed/testtype/NativeStressTestParser.java b/test_framework/com/android/tradefed/testtype/NativeStressTestParser.java
similarity index 100%
rename from src/com/android/tradefed/testtype/NativeStressTestParser.java
rename to test_framework/com/android/tradefed/testtype/NativeStressTestParser.java
diff --git a/src/com/android/tradefed/testtype/UiAutomatorRunner.java b/test_framework/com/android/tradefed/testtype/UiAutomatorRunner.java
similarity index 100%
rename from src/com/android/tradefed/testtype/UiAutomatorRunner.java
rename to test_framework/com/android/tradefed/testtype/UiAutomatorRunner.java
diff --git a/src/com/android/tradefed/testtype/UiAutomatorTest.java b/test_framework/com/android/tradefed/testtype/UiAutomatorTest.java
similarity index 100%
rename from src/com/android/tradefed/testtype/UiAutomatorTest.java
rename to test_framework/com/android/tradefed/testtype/UiAutomatorTest.java
diff --git a/src/com/android/tradefed/testtype/host/PrettyTestEventLogger.java b/test_framework/com/android/tradefed/testtype/host/PrettyTestEventLogger.java
similarity index 100%
rename from src/com/android/tradefed/testtype/host/PrettyTestEventLogger.java
rename to test_framework/com/android/tradefed/testtype/host/PrettyTestEventLogger.java
diff --git a/src/com/android/tradefed/testtype/junit4/BaseHostJUnit4Test.java b/test_framework/com/android/tradefed/testtype/junit4/BaseHostJUnit4Test.java
similarity index 100%
rename from src/com/android/tradefed/testtype/junit4/BaseHostJUnit4Test.java
rename to test_framework/com/android/tradefed/testtype/junit4/BaseHostJUnit4Test.java
diff --git a/src/com/android/tradefed/testtype/junit4/CarryDnaeError.java b/test_framework/com/android/tradefed/testtype/junit4/CarryDnaeError.java
similarity index 100%
rename from src/com/android/tradefed/testtype/junit4/CarryDnaeError.java
rename to test_framework/com/android/tradefed/testtype/junit4/CarryDnaeError.java
diff --git a/src/com/android/tradefed/testtype/junit4/DeviceParameterizedRunner.java b/test_framework/com/android/tradefed/testtype/junit4/DeviceParameterizedRunner.java
similarity index 100%
rename from src/com/android/tradefed/testtype/junit4/DeviceParameterizedRunner.java
rename to test_framework/com/android/tradefed/testtype/junit4/DeviceParameterizedRunner.java
diff --git a/src/com/android/tradefed/testtype/junit4/DeviceTestRunOptions.java b/test_framework/com/android/tradefed/testtype/junit4/DeviceTestRunOptions.java
similarity index 100%
rename from src/com/android/tradefed/testtype/junit4/DeviceTestRunOptions.java
rename to test_framework/com/android/tradefed/testtype/junit4/DeviceTestRunOptions.java
diff --git a/src/com/android/tradefed/testtype/junit4/LongevityHostRunner.java b/test_framework/com/android/tradefed/testtype/junit4/LongevityHostRunner.java
similarity index 100%
rename from src/com/android/tradefed/testtype/junit4/LongevityHostRunner.java
rename to test_framework/com/android/tradefed/testtype/junit4/LongevityHostRunner.java
diff --git a/src/com/android/tradefed/testtype/junit4/RunNotifierWrapper.java b/test_framework/com/android/tradefed/testtype/junit4/RunNotifierWrapper.java
similarity index 100%
rename from src/com/android/tradefed/testtype/junit4/RunNotifierWrapper.java
rename to test_framework/com/android/tradefed/testtype/junit4/RunNotifierWrapper.java
diff --git a/src/com/android/tradefed/testtype/junit4/builder/DeviceJUnit4ClassRunnerBuilder.java b/test_framework/com/android/tradefed/testtype/junit4/builder/DeviceJUnit4ClassRunnerBuilder.java
similarity index 100%
rename from src/com/android/tradefed/testtype/junit4/builder/DeviceJUnit4ClassRunnerBuilder.java
rename to test_framework/com/android/tradefed/testtype/junit4/builder/DeviceJUnit4ClassRunnerBuilder.java
diff --git a/src/com/android/tradefed/testtype/suite/AtestRunner.java b/test_framework/com/android/tradefed/testtype/suite/AtestRunner.java
similarity index 100%
rename from src/com/android/tradefed/testtype/suite/AtestRunner.java
rename to test_framework/com/android/tradefed/testtype/suite/AtestRunner.java
diff --git a/tests/res/config/tf/unit-runner-tests.xml b/tests/res/config/tf/unit-runner-tests.xml
new file mode 100644
index 0000000..75c86cf
--- /dev/null
+++ b/tests/res/config/tf/unit-runner-tests.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+<configuration description="Executes the TF unit tests">
+    <!-- Use HostTest runner to have JUnit3 and JUnit4 Suite support -->
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="class" value="com.android.loganalysis.UnitTests" />
+        <option name="class" value="com.android.tradefed.UnitTests" />
+        <option name="class" value="com.android.tradefed.prodtests.UnitTests" />
+    </test>
+</configuration>
diff --git a/tests/res/config/tf/unit-runner.xml b/tests/res/config/tf/unit-runner.xml
index 8c11a96..ddc527c 100644
--- a/tests/res/config/tf/unit-runner.xml
+++ b/tests/res/config/tf/unit-runner.xml
@@ -17,12 +17,9 @@
     <option name="loop" value="false" />
     <option name="null-device" value="true" />
     <build_provider class="com.android.tradefed.build.StubBuildProvider" />
-    <!-- Use HostTest runner to have JUnit3 and JUnit4 Suite support -->
-    <test class="com.android.tradefed.testtype.HostTest" >
-        <option name="class" value="com.android.loganalysis.UnitTests" />
-        <option name="class" value="com.android.tradefed.UnitTests" />
-        <option name="class" value="com.android.tradefed.prodtests.UnitTests" />
-    </test>
+
+    <include name="tf/unit-runner-tests" />
+
     <logger class="com.android.tradefed.log.FileLogger" />
     <result_reporter class="com.android.tradefed.result.SubprocessResultsReporter">
         <option name="output-test-log" value="true" />
diff --git a/tests/src/com/android/tradefed/device/cloud/ManagedRemoteDeviceTest.java b/tests/src/com/android/tradefed/device/cloud/ManagedRemoteDeviceTest.java
index 7bf87e6..38d7254 100644
--- a/tests/src/com/android/tradefed/device/cloud/ManagedRemoteDeviceTest.java
+++ b/tests/src/com/android/tradefed/device/cloud/ManagedRemoteDeviceTest.java
@@ -15,14 +15,16 @@
  */
 package com.android.tradefed.device.cloud;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNotEquals;
 
 import com.android.ddmlib.IDevice;
 import com.android.tradefed.config.GlobalConfiguration;
 import com.android.tradefed.device.IDeviceMonitor;
 import com.android.tradefed.device.IDeviceStateMonitor;
 import com.android.tradefed.device.TestDeviceOptions;
+import com.android.tradefed.log.ITestLogger;
 
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -38,6 +40,7 @@
     private IDevice mIDevice;
     private IDeviceStateMonitor mStateMonitor;
     private IDeviceMonitor mDeviceMonitor;
+    private ITestLogger mMockLogger;
 
     @BeforeClass
     public static void setUpClass() throws Exception {
@@ -53,7 +56,9 @@
         mIDevice = Mockito.mock(IDevice.class);
         mStateMonitor = Mockito.mock(IDeviceStateMonitor.class);
         mDeviceMonitor = Mockito.mock(IDeviceMonitor.class);
+        mMockLogger = Mockito.mock(ITestLogger.class);
         mDevice = new ManagedRemoteDevice(mIDevice, mStateMonitor, mDeviceMonitor);
+        mDevice.setTestLogger(mMockLogger);
     }
 
     @Test
@@ -63,6 +68,11 @@
         TestDeviceOptions get = mDevice.getOptions();
         assertFalse(get.equals(originalOptions));
         TestDeviceOptions get2 = mDevice.getOptions();
-        assertTrue(get2.equals(get));
+        // Same during the same session
+        assertEquals(get2, get);
+
+        mDevice.postInvocationTearDown(null);
+        TestDeviceOptions get3 = mDevice.getOptions();
+        assertNotEquals(get2, get3);
     }
 }
diff --git a/tests/src/com/android/tradefed/device/metric/LogcatOnFailureCollectorTest.java b/tests/src/com/android/tradefed/device/metric/LogcatOnFailureCollectorTest.java
index d213104..61945dc 100644
--- a/tests/src/com/android/tradefed/device/metric/LogcatOnFailureCollectorTest.java
+++ b/tests/src/com/android/tradefed/device/metric/LogcatOnFailureCollectorTest.java
@@ -82,7 +82,7 @@
     }
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         mMockDevice = EasyMock.createMock(ITestDevice.class);
         mNullMockDevice = EasyMock.createMock(ITestDevice.class);
         mMockListener = EasyMock.createMock(ITestInvocationListener.class);
@@ -101,6 +101,7 @@
 
     @Test
     public void testCollect() throws Exception {
+        EasyMock.expect(mMockDevice.getApiLevel()).andReturn(20);
         mMockReceiver.start();
         mMockReceiver.clear();
         mMockReceiver.stop();
@@ -137,6 +138,41 @@
         assertTrue(mCollector.mOnTestFailCalled);
     }
 
+    /**
+     * If the API level support of the device is lower than a threshold we fall back to a different
+     * collection for the logcat.
+     */
+    @Test
+    public void testCollect_legacy() throws Exception {
+        EasyMock.expect(mMockDevice.getApiLevel()).andReturn(18);
+        mMockListener.testRunStarted("runName", 1);
+        TestDescription test = new TestDescription("class", "test");
+        mMockListener.testStarted(EasyMock.eq(test), EasyMock.anyLong());
+        mMockListener.testFailed(EasyMock.eq(test), EasyMock.anyObject());
+        mMockListener.testEnded(
+                EasyMock.eq(test),
+                EasyMock.anyLong(),
+                EasyMock.<HashMap<String, Metric>>anyObject());
+        mMockListener.testRunEnded(0L, new HashMap<String, Metric>());
+        mMockDevice.executeShellCommand(EasyMock.eq("logcat -t 5000"), EasyMock.anyObject());
+        mMockListener.testLog(
+                EasyMock.eq("class#test-serial-logcat-on-failure"),
+                EasyMock.eq(LogDataType.LOGCAT),
+                EasyMock.anyObject());
+
+        EasyMock.replay(mMockListener, mMockDevice, mMockReceiver, mNullMockDevice);
+        mTestListener = mCollector.init(mContext, mMockListener);
+        mTestListener.testRunStarted("runName", 1);
+        mTestListener.testStarted(test);
+        mTestListener.testFailed(test, "I failed");
+        mTestListener.testEnded(test, new HashMap<String, Metric>());
+        mTestListener.testRunEnded(0L, new HashMap<String, Metric>());
+        EasyMock.verify(mMockListener, mMockDevice, mMockReceiver, mNullMockDevice);
+        // Ensure the callback went through
+        assertTrue(mCollector.mOnTestStartCalled);
+        assertTrue(mCollector.mOnTestFailCalled);
+    }
+
     @Test
     public void testCollect_noRuns() throws Exception {
         // If there was no runs, nothing should be done.
@@ -149,6 +185,7 @@
 
     @Test
     public void testCollect_multiRun() throws Exception {
+        EasyMock.expect(mMockDevice.getApiLevel()).andStubReturn(20);
         mMockReceiver.start();
         EasyMock.expectLastCall().times(2);
         mMockReceiver.clear();
diff --git a/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java b/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java
index c9a22e5..cf2bc60 100644
--- a/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java
+++ b/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java
@@ -39,6 +39,7 @@
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.result.LogDataType;
 import com.android.tradefed.result.LogFile;
+import com.android.tradefed.testtype.retry.BaseRetryDecision;
 
 import org.easymock.Capture;
 import org.easymock.EasyMock;
@@ -77,6 +78,7 @@
 
         mMockConfig = EasyMock.createMock(IConfiguration.class);
         EasyMock.expect(mMockConfig.getPostProcessors()).andReturn(mPostProcessors);
+        EasyMock.expect(mMockConfig.getRetryDecision()).andReturn(new BaseRetryDecision());
         mMockRescheduler = EasyMock.createMock(IRescheduler.class);
         mMockTestListener = EasyMock.createMock(ITestInvocationListener.class);
         mMockLogSaver = EasyMock.createMock(ILogSaver.class);
diff --git a/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java
index 4cde810..59c8926 100644
--- a/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java
@@ -353,11 +353,15 @@
         EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)).andReturn(res);
         mMockDevice.reboot();
         EasyMock.expectLastCall();
-        List<String> trainInstallCmd = new ArrayList<>();
-        trainInstallCmd.add("install-multi-package");
-        trainInstallCmd.add(mFakeApk.getAbsolutePath());
-        EasyMock.expect(mMockDevice.executeAdbCommand(trainInstallCmd.toArray(new String[0])))
-                .andReturn("Success")
+        //TODO:add back once new adb is deployed to the lab
+        // List<String> trainInstallCmd = new ArrayList<>();
+        // trainInstallCmd.add("install-multi-package");
+        // trainInstallCmd.add(mFakeApk.getAbsolutePath());
+        // EasyMock.expect(mMockDevice.executeAdbCommand(trainInstallCmd.toArray(new String[0])))
+        //         .andReturn("Success")
+        //         .once();
+        EasyMock.expect(mMockDevice.installPackage((File) EasyMock.anyObject(), EasyMock.eq(true)))
+                .andReturn(null)
                 .once();
         EasyMock.expect(mMockDevice.uninstallPackage(APK_PACKAGE_NAME)).andReturn(null).once();
 
@@ -723,11 +727,15 @@
     }
 
     private void mockSuccessfulInstallPackageAndReboot(File f) throws Exception {
-        List<String> trainInstallCmd = new ArrayList<>();
-        trainInstallCmd.add("install-multi-package");
-        trainInstallCmd.add(f.getAbsolutePath());
-        EasyMock.expect(mMockDevice.executeAdbCommand(trainInstallCmd.toArray(new String[0])))
-                .andReturn("Success")
+        //TODO:add back once new adb is deployed to the lab
+        // List<String> trainInstallCmd = new ArrayList<>();
+        // trainInstallCmd.add("install-multi-package");
+        // trainInstallCmd.add(f.getAbsolutePath());
+        // EasyMock.expect(mMockDevice.executeAdbCommand(trainInstallCmd.toArray(new String[0])))
+        //         .andReturn("Success")
+        //         .once();
+        EasyMock.expect(mMockDevice.installPackage((File) EasyMock.anyObject(), EasyMock.eq(true)))
+                .andReturn(null)
                 .once();
         mMockDevice.reboot();
         EasyMock.expectLastCall().once();
diff --git a/tests/src/com/android/tradefed/testtype/InstrumentationFileTestTest.java b/tests/src/com/android/tradefed/testtype/InstrumentationFileTestTest.java
index f00cab1..e91fb88 100644
--- a/tests/src/com/android/tradefed/testtype/InstrumentationFileTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/InstrumentationFileTestTest.java
@@ -307,6 +307,7 @@
                     }
                 };
         setRunTestExpectations(secdondSerialRunAnswer);
+        mMockTestDevice.waitForDeviceAvailable();
 
         mInstrumentationFileTest = new InstrumentationFileTest(mMockITest, testsList, true, -1) {
             @Override
diff --git a/tests/src/com/android/tradefed/testtype/NativeCodeCoverageListenerTest.java b/tests/src/com/android/tradefed/testtype/NativeCodeCoverageListenerTest.java
index 98d88eb..88454a5 100644
--- a/tests/src/com/android/tradefed/testtype/NativeCodeCoverageListenerTest.java
+++ b/tests/src/com/android/tradefed/testtype/NativeCodeCoverageListenerTest.java
@@ -94,9 +94,8 @@
         doReturn(true).when(mMockDevice).enableAdbRoot();
         doReturn(
                         new StringJoiner("\n")
-                                .add("/data/misc/trace/proc/self/cwd/out/path/to/coverage.gcda")
-                                .add(
-                                        "/data/misc/trace/proc/self/cwd/out/path/to/.hidden/coverage2.gcda")
+                                .add("/data/misc/trace/path/to/coverage.gcda")
+                                .add("/data/misc/trace/path/to/.hidden/coverage2.gcda")
                                 .toString())
                 .when(mMockDevice)
                 .executeShellCommand(anyString());
@@ -163,7 +162,7 @@
     public void testFailure_unableToPullFile() throws DeviceNotAvailableException {
         // Setup mocks.
         doReturn(true).when(mMockDevice).enableAdbRoot();
-        doReturn("/data/misc/trace/proc/self/cwd/out/some/path/to/coverage.gcda\n")
+        doReturn("/data/misc/trace/some/path/to/coverage.gcda\n")
                 .when(mMockDevice)
                 .executeShellCommand(anyString());
         doReturn(false).when(mMockDevice).pullFile(anyString(), any());
diff --git a/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java b/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java
index b64498b..39cbb64 100644
--- a/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java
@@ -15,12 +15,17 @@
  */
 package com.android.tradefed.testtype.suite;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import com.android.ddmlib.IDevice;
 import com.android.ddmlib.testrunner.TestResult.TestStatus;
 import com.android.tradefed.config.IConfiguration;
 import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionSetter;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.DeviceUnresponsiveException;
 import com.android.tradefed.device.ITestDevice;
@@ -36,10 +41,12 @@
 import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.result.TestResult;
 import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.testtype.IDeviceTest;
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.ITestFilterReceiver;
+import com.android.tradefed.testtype.retry.BaseRetryDecision;
+import com.android.tradefed.testtype.retry.IRetryDecision;
 import com.android.tradefed.testtype.retry.RetryStatistics;
-import com.android.tradefed.testtype.retry.RetryStrategy;
 
 import org.easymock.EasyMock;
 import org.junit.Before;
@@ -139,7 +146,9 @@
         }
     }
 
-    private class FakeTest extends BasicFakeTest implements ITestFilterReceiver {
+    private class FakeTest extends BasicFakeTest implements ITestFilterReceiver, IDeviceTest {
+
+        private ITestDevice mDevice;
 
         public FakeTest(ArrayList<TestDescription> testCases) {
             super(testCases);
@@ -180,6 +189,16 @@
 
         @Override
         public void clearExcludeFilters() {}
+
+        @Override
+        public void setDevice(ITestDevice device) {
+            mDevice = device;
+        }
+
+        @Override
+        public ITestDevice getDevice() {
+            return mDevice;
+        }
     }
 
     private class MultiTestOneRunFakeTest extends FakeTest {
@@ -238,12 +257,12 @@
     }
 
     private GranularRetriableTestWrapper createGranularTestWrapper(
-            IRemoteTest test, int maxRunCount) {
+            IRemoteTest test, int maxRunCount) throws Exception {
         return createGranularTestWrapper(test, maxRunCount, new ArrayList<>());
     }
 
     private GranularRetriableTestWrapper createGranularTestWrapper(
-            IRemoteTest test, int maxRunCount, List<IMetricCollector> collectors) {
+            IRemoteTest test, int maxRunCount, List<IMetricCollector> collectors) throws Exception {
         GranularRetriableTestWrapper granularTestWrapper =
                 new GranularRetriableTestWrapper(test, null, null, null, maxRunCount);
         granularTestWrapper.setModuleId("test module");
@@ -255,6 +274,11 @@
         granularTestWrapper.setLogSaver(new FileSystemLogSaver());
         IConfiguration mockModuleConfiguration = Mockito.mock(IConfiguration.class);
         granularTestWrapper.setModuleConfig(mockModuleConfiguration);
+        IRetryDecision decision = new BaseRetryDecision();
+        OptionSetter setter = new OptionSetter(decision);
+        setter.setOptionValue("retry-strategy", "RETRY_ANY_FAILURE");
+        setter.setOptionValue("max-testcase-run-count", Integer.toString(maxRunCount));
+        granularTestWrapper.setRetryDecision(decision);
         return granularTestWrapper;
     }
 
@@ -275,7 +299,6 @@
                 .run(Mockito.any(ITestInvocationListener.class));
 
         GranularRetriableTestWrapper granularTestWrapper = createGranularTestWrapper(mockTest, 1);
-        granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
         try {
             granularTestWrapper.run(new CollectingTestListener());
             fail("Should have thrown an exception.");
@@ -327,7 +350,6 @@
         int maxRunCount = 5;
         GranularRetriableTestWrapper granularTestWrapper =
                 createGranularTestWrapper(test, maxRunCount);
-        granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
         granularTestWrapper.run(new CollectingTestListener());
         // Verify the test runs several times but under the same run name
         assertEquals(1, granularTestWrapper.getTestRunResultCollected().size());
@@ -387,7 +409,6 @@
         int maxRunCount = 5;
         GranularRetriableTestWrapper granularTestWrapper =
                 createGranularTestWrapper(test, maxRunCount);
-        granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
         granularTestWrapper.run(new CollectingTestListener());
         // Verify the test runs several times but under the same run name
         assertEquals(1, granularTestWrapper.getTestRunResultCollected().size());
@@ -448,7 +469,6 @@
         int maxRunCount = 5;
         GranularRetriableTestWrapper granularTestWrapper =
                 createGranularTestWrapper(test, maxRunCount);
-        granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
         granularTestWrapper.run(new CollectingTestListener());
         // Verify the test runs several times but under the same run name
         assertEquals(1, granularTestWrapper.getTestRunResultCollected().size());
@@ -535,7 +555,6 @@
         int maxRunCount = 3;
         GranularRetriableTestWrapper granularTestWrapper =
                 createGranularTestWrapper(test, maxRunCount);
-        granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
         granularTestWrapper.run(new CollectingTestListener());
 
         assertEquals(1, granularTestWrapper.getTestRunResultCollected().size());
@@ -575,7 +594,6 @@
 
         GranularRetriableTestWrapper granularTestWrapper =
                 createGranularTestWrapper(test, maxRunCount);
-        granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
         granularTestWrapper.run(new CollectingTestListener());
         // Two runs.
         assertEquals(2, granularTestWrapper.getTestRunResultCollected().size());
@@ -616,7 +634,6 @@
         FakeTest test = new FakeTest(testCases);
         test.setRunFailure("I failed!");
         GranularRetriableTestWrapper granularTestWrapper = createGranularTestWrapper(test, 3);
-        granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
         granularTestWrapper.run(new CollectingTestListener());
 
         assertEquals(1, granularTestWrapper.getTestRunResultCollected().size());
@@ -652,7 +669,6 @@
         test.setRunFailure("I failed!");
         test.setClearRunFailure(3);
         GranularRetriableTestWrapper granularTestWrapper = createGranularTestWrapper(test, 7);
-        granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
         granularTestWrapper.run(new CollectingTestListener());
 
         assertEquals(1, granularTestWrapper.getTestRunResultCollected().size());
@@ -687,7 +703,11 @@
         testCases.add(fakeTestCase2);
         FakeTest test = new FakeTest(testCases);
         GranularRetriableTestWrapper granularTestWrapper = createGranularTestWrapper(test, 3);
-        granularTestWrapper.setRetryStrategy(RetryStrategy.ITERATIONS);
+        IRetryDecision decision = new BaseRetryDecision();
+        OptionSetter setter = new OptionSetter(decision);
+        setter.setOptionValue("retry-strategy", "ITERATIONS");
+        setter.setOptionValue("max-testcase-run-count", Integer.toString(3));
+        granularTestWrapper.setRetryDecision(decision);
         granularTestWrapper.run(new CollectingTestListener());
 
         assertEquals(1, granularTestWrapper.getTestRunResultCollected().size());
@@ -719,7 +739,11 @@
         FakeTest test = new FakeTest(testCases);
         test.addFailedTestCase(fakeTestCase2);
         GranularRetriableTestWrapper granularTestWrapper = createGranularTestWrapper(test, 3);
-        granularTestWrapper.setRetryStrategy(RetryStrategy.RERUN_UNTIL_FAILURE);
+        IRetryDecision decision = new BaseRetryDecision();
+        OptionSetter setter = new OptionSetter(decision);
+        setter.setOptionValue("retry-strategy", "RERUN_UNTIL_FAILURE");
+        setter.setOptionValue("max-testcase-run-count", Integer.toString(3));
+        granularTestWrapper.setRetryDecision(decision);
         granularTestWrapper.run(new CollectingTestListener());
 
         assertEquals(1, granularTestWrapper.getTestRunResultCollected().size());
@@ -768,7 +792,6 @@
         test.addTestBecomePass(fakeTestCase1, 5);
         GranularRetriableTestWrapper granularTestWrapper =
                 createGranularTestWrapper(test, 7, collectors);
-        granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
         granularTestWrapper.run(new CollectingTestListener());
 
         assertEquals(1, granularTestWrapper.getTestRunResultCollected().size());
@@ -819,15 +842,19 @@
      */
     @Test
     public void testIntraModuleRun_rebootAtLastIntraModuleRetry() throws Exception {
+        IRetryDecision decision = new BaseRetryDecision();
+        OptionSetter setter = new OptionSetter(decision);
+        setter.setOptionValue("reboot-at-last-retry", "true");
+        setter.setOptionValue("retry-strategy", "RETRY_ANY_FAILURE");
+        setter.setOptionValue("max-testcase-run-count", Integer.toString(3));
         FakeTest test = new FakeTest();
         test.setRunFailure("I failed!");
         ITestDevice mMockDevice = EasyMock.createMock(ITestDevice.class);
+        test.setDevice(mMockDevice);
         mModuleInvocationContext.addAllocatedDevice("default-device1", mMockDevice);
         GranularRetriableTestWrapper granularTestWrapper = createGranularTestWrapper(test, 3);
-        granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
-        granularTestWrapper.setRebootAtLastRetry(true);
+        granularTestWrapper.setRetryDecision(decision);
         EasyMock.expect(mMockDevice.getIDevice()).andStubReturn(EasyMock.createMock(IDevice.class));
-        EasyMock.expect(mMockDevice.getSerialNumber()).andReturn("SERIAL");
         mMockDevice.reboot();
         EasyMock.replay(mMockDevice);
         granularTestWrapper.run(new CollectingTestListener());
@@ -839,26 +866,28 @@
      */
     @Test
     public void testIntraModuleRun_rebootMultiDevicesAtLastIntraModuleRetry() throws Exception {
+        IRetryDecision decision = new BaseRetryDecision();
+        OptionSetter setter = new OptionSetter(decision);
+        setter.setOptionValue("reboot-at-last-retry", "true");
+        setter.setOptionValue("retry-strategy", "RETRY_ANY_FAILURE");
+        setter.setOptionValue("max-testcase-run-count", Integer.toString(3));
         FakeTest test = new FakeTest();
         test.setRunFailure("I failed!");
         ITestDevice mMockDevice = EasyMock.createMock(ITestDevice.class);
         ITestDevice mMockDevice2 = EasyMock.createMock(ITestDevice.class);
+        test.setDevice(mMockDevice);
         mModuleInvocationContext.addAllocatedDevice("default-device1", mMockDevice);
         mModuleInvocationContext.addAllocatedDevice("default-device2", mMockDevice2);
         GranularRetriableTestWrapper granularTestWrapper = createGranularTestWrapper(test, 3);
-        granularTestWrapper.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE);
-        granularTestWrapper.setRebootAtLastRetry(true);
+        granularTestWrapper.setRetryDecision(decision);
         EasyMock.expect(mMockDevice.getIDevice()).andStubReturn(EasyMock.createMock(IDevice.class));
-        EasyMock.expect(mMockDevice.getSerialNumber()).andReturn("SERIAL");
         EasyMock.expect(mMockDevice2.getIDevice()).andStubReturn(EasyMock.createMock(IDevice.class));
-        EasyMock.expect(mMockDevice2.getSerialNumber()).andReturn("SERIAL-2");
         mMockDevice.reboot();
-        mMockDevice2.reboot();
-        EasyMock.replay(mMockDevice);
-        EasyMock.replay(mMockDevice2);
+        // FIXME(b/137769473): add the support for resetting multi-devices.
+        // mMockDevice2.reboot();
+        EasyMock.replay(mMockDevice, mMockDevice2);
         granularTestWrapper.run(new CollectingTestListener());
-        EasyMock.verify(mMockDevice);
-        EasyMock.verify(mMockDevice2);
+        EasyMock.verify(mMockDevice, mMockDevice2);
     }
 
     /** Collector that track if it was called or not */
diff --git a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
index 62b3512..3488f1a 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
@@ -66,6 +66,8 @@
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.ITestFilterReceiver;
 import com.android.tradefed.testtype.StubTest;
+import com.android.tradefed.testtype.retry.BaseRetryDecision;
+import com.android.tradefed.testtype.retry.IRetryDecision;
 import com.android.tradefed.util.AbiUtils;
 import com.android.tradefed.util.MultiMap;
 
@@ -1492,9 +1494,14 @@
         mTestSuite.setDevice(mMockDevice);
         mTestSuite.setBuild(mMockBuildInfo);
         mTestSuite.setConfiguration(mStubMainConfiguration);
-        mStubMainConfiguration.getCommandOptions().setMaxRetryCount(maxRunLimit);
         OptionSetter cmdSetter = new OptionSetter(mStubMainConfiguration.getCommandOptions());
         cmdSetter.setOptionValue("retry-strategy", "RETRY_ANY_FAILURE");
+        IRetryDecision decision = new BaseRetryDecision();
+        OptionSetter setter = new OptionSetter(decision);
+        setter.setOptionValue("retry-strategy", "RETRY_ANY_FAILURE");
+        setter.setOptionValue("max-testcase-run-count", Integer.toString(maxRunLimit));
+        mStubMainConfiguration.setConfigurationObject(
+                Configuration.RETRY_DECISION_TYPE_NAME, decision);
         mContext = new InvocationContext();
         mTestSuite.setInvocationContext(mContext);
         mContext.addAllocatedDevice(ConfigurationDef.DEFAULT_DEVICE_NAME, mMockDevice);
diff --git a/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java b/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
index 6bb0d10..df744b0 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
@@ -57,7 +57,8 @@
 import com.android.tradefed.testtype.IDeviceTest;
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.ITestFilterReceiver;
-import com.android.tradefed.testtype.retry.RetryStrategy;
+import com.android.tradefed.testtype.retry.BaseRetryDecision;
+import com.android.tradefed.testtype.retry.IRetryDecision;
 import com.android.tradefed.testtype.suite.module.BaseModuleController;
 import com.android.tradefed.testtype.suite.module.IModuleController;
 import com.android.tradefed.testtype.suite.module.TestFailureModuleController;
@@ -100,6 +101,8 @@
     private ILogSaver mMockLogSaver;
     private ILogSaverListener mMockLogSaverListener;
 
+    private IRetryDecision mDecision = new BaseRetryDecision();
+
     private interface ITestInterface extends IRemoteTest, IBuildReceiver, IDeviceTest {}
 
     /** Test implementation that allows us to exercise different use cases * */
@@ -289,7 +292,7 @@
                         mMapDeviceTargetPreparer,
                         mMultiTargetPrepList,
                         new Configuration("", ""));
-
+        mModule.setRetryDecision(mDecision);
         mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
         mModule.getModuleInvocationContext()
                 .addDeviceBuildInfo(DEFAULT_DEVICE_NAME, mMockBuildInfo);
@@ -398,6 +401,7 @@
                         mMapDeviceTargetPreparer,
                         mMultiTargetPrepList,
                         new Configuration("", ""));
+        mModule.setRetryDecision(mDecision);
         mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
         mModule.getModuleInvocationContext()
                 .addDeviceBuildInfo(DEFAULT_DEVICE_NAME, mMockBuildInfo);
@@ -751,6 +755,7 @@
                         mMapDeviceTargetPreparer,
                         mMultiTargetPrepList,
                         new Configuration("", ""));
+        mModule.setRetryDecision(mDecision);
         mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
         mModule.getModuleInvocationContext()
                 .addDeviceBuildInfo(DEFAULT_DEVICE_NAME, mMockBuildInfo);
@@ -799,6 +804,7 @@
                         mMapDeviceTargetPreparer,
                         mMultiTargetPrepList,
                         new Configuration("", ""));
+        mModule.setRetryDecision(mDecision);
         mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
         mModule.getModuleInvocationContext()
                 .addDeviceBuildInfo(DEFAULT_DEVICE_NAME, mMockBuildInfo);
@@ -861,6 +867,7 @@
                         mMapDeviceTargetPreparer,
                         mMultiTargetPrepList,
                         new Configuration("", ""));
+        mModule.setRetryDecision(mDecision);
         mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
         mModule.getModuleInvocationContext()
                 .addDeviceBuildInfo(DEFAULT_DEVICE_NAME, mMockBuildInfo);
@@ -1020,6 +1027,7 @@
                         mMapDeviceTargetPreparer,
                         mMultiTargetPrepList,
                         config);
+        mModule.setRetryDecision(mDecision);
         mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
         mModule.getModuleInvocationContext()
                 .addDeviceBuildInfo(DEFAULT_DEVICE_NAME, mMockBuildInfo);
@@ -1031,9 +1039,9 @@
         mMockListener.testEnded(
                 EasyMock.anyObject(),
                 EasyMock.anyLong(),
-                (HashMap<String, Metric>) EasyMock.anyObject());
+                EasyMock.<HashMap<String, Metric>>anyObject());
         mMockListener.testRunEnded(
-                EasyMock.anyLong(), (HashMap<String, Metric>) EasyMock.anyObject());
+                EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
         replayMocks();
         mModule.run(mMockListener, null, null);
         verifyMocks();
@@ -1081,6 +1089,7 @@
                         mMapDeviceTargetPreparer,
                         mMultiTargetPrepList,
                         new Configuration("", ""));
+        mModule.setRetryDecision(mDecision);
         mModule.setLogSaver(mMockLogSaver);
         mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
         mModule.getModuleInvocationContext()
@@ -1164,11 +1173,12 @@
                         mMapDeviceTargetPreparer,
                         mMultiTargetPrepList,
                         config);
+        mModule.setRetryDecision(mDecision);
         mModule.setLogSaver(mMockLogSaver);
         mMockListener.testRunStarted(
                 EasyMock.eq("fakeName"), EasyMock.eq(0), EasyMock.eq(0), EasyMock.anyLong());
         mMockListener.testRunEnded(
-                EasyMock.anyLong(), (HashMap<String, Metric>) EasyMock.anyObject());
+                EasyMock.anyLong(), EasyMock.<HashMap<String, Metric>>anyObject());
         replayMocks();
         mModule.run(mMockListener, null, failureListener);
         verifyMocks();
@@ -1187,6 +1197,7 @@
                         mMapDeviceTargetPreparer,
                         mMultiTargetPrepList,
                         new Configuration("", ""));
+        mModule.setRetryDecision(mDecision);
         mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
         mModule.getModuleInvocationContext()
                 .addDeviceBuildInfo(DEFAULT_DEVICE_NAME, mMockBuildInfo);
@@ -1255,6 +1266,7 @@
                         mMapDeviceTargetPreparer,
                         mMultiTargetPrepList,
                         new Configuration("", ""));
+        mModule.setRetryDecision(mDecision);
         mModule.setLogSaver(mMockLogSaver);
         mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
         mModule.getModuleInvocationContext()
@@ -1324,7 +1336,7 @@
                         mMapDeviceTargetPreparer,
                         mMultiTargetPrepList,
                         new Configuration("", ""));
-
+        mModule.setRetryDecision(mDecision);
         mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
         mModule.getModuleInvocationContext()
                 .addDeviceBuildInfo(DEFAULT_DEVICE_NAME, mMockBuildInfo);
@@ -1377,6 +1389,7 @@
                         mMapDeviceTargetPreparer,
                         mMultiTargetPrepList,
                         new Configuration("", ""));
+        mModule.setRetryDecision(mDecision);
         mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
         mModule.getModuleInvocationContext()
                 .addDeviceBuildInfo(DEFAULT_DEVICE_NAME, mMockBuildInfo);
@@ -1418,7 +1431,12 @@
                         mMapDeviceTargetPreparer,
                         mMultiTargetPrepList,
                         new Configuration("", ""));
-        mModule.setRetryStrategy(RetryStrategy.ITERATIONS, false);
+        IRetryDecision decision = new BaseRetryDecision();
+        OptionSetter setter = new OptionSetter(decision);
+        setter.setOptionValue("retry-strategy", "ITERATIONS");
+        setter.setOptionValue("max-testcase-run-count", Integer.toString(3));
+        mModule.setRetryDecision(decision);
+        mModule.setMergeAttemps(false);
         mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
         mModule.getModuleInvocationContext()
                 .addDeviceBuildInfo(DEFAULT_DEVICE_NAME, mMockBuildInfo);
@@ -1503,7 +1521,12 @@
                         mMapDeviceTargetPreparer,
                         mMultiTargetPrepList,
                         new Configuration("", ""));
-        mModule.setRetryStrategy(RetryStrategy.RETRY_ANY_FAILURE, false);
+        IRetryDecision decision = new BaseRetryDecision();
+        OptionSetter setter = new OptionSetter(decision);
+        setter.setOptionValue("retry-strategy", "RETRY_ANY_FAILURE");
+        setter.setOptionValue("max-testcase-run-count", Integer.toString(3));
+        mModule.setRetryDecision(decision);
+        mModule.setMergeAttemps(false);
 
         mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
         mModule.getModuleInvocationContext()