Add support for indexed profiles.

Bug: 143773722
Test: included
Change-Id: I2ccaaf21ae9c1b9c517bf3c5773ca36841c63a2f
diff --git a/libraries/health/runners/longevity/platform/samples/assets/sample_indexed_profile.textpb b/libraries/health/runners/longevity/platform/samples/assets/sample_indexed_profile.textpb
new file mode 100644
index 0000000..4a4ade4
--- /dev/null
+++ b/libraries/health/runners/longevity/platform/samples/assets/sample_indexed_profile.textpb
@@ -0,0 +1,17 @@
+schedule: INDEXED
+scenarios [{
+    index: 1
+    journey: "android.platform.test.longevity.samples.SimpleSuite$PassingTest"
+}, {
+    index: 2
+    journey: "android.platform.test.longevity.samples.SimpleSuite$FailingTest"
+}, {
+    index: 3
+    journey: "android.platform.test.longevity.samples.SimpleSuite$PassingTest"
+}, {
+    index: 4
+    journey: "android.platform.test.longevity.samples.SimpleSuite$PassingTest"
+}, {
+    index: 5
+    journey: "android.platform.test.longevity.samples.SimpleSuite$FailingTest"
+}]
diff --git a/libraries/health/runners/longevity/platform/samples/assets/sample_profile.textpb b/libraries/health/runners/longevity/platform/samples/assets/sample_scheduled_profile.textpb
similarity index 100%
rename from libraries/health/runners/longevity/platform/samples/assets/sample_profile.textpb
rename to libraries/health/runners/longevity/platform/samples/assets/sample_scheduled_profile.textpb
diff --git a/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/Profile.java b/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/Profile.java
index 4cb3933..b981276 100644
--- a/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/Profile.java
+++ b/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/Profile.java
@@ -79,6 +79,17 @@
         }
     }
 
+    // Comparator for sorting indexed CUJs.
+    private static class ScenarioIndexedComparator implements Comparator<Scenario> {
+        public int compare(Scenario s1, Scenario s2) {
+            if (!(s1.hasIndex() && s2.hasIndex())) {
+                throw new IllegalArgumentException(
+                        "Scenarios in indexed profiles must have indexes.");
+            }
+            return Integer.compare(s1.getIndex(), s2.getIndex());
+        }
+    }
+
     public Profile(Bundle args) {
         super();
         // Set the timestamp parser to UTC to get test timstamps as "time elapsed since zero".
@@ -105,6 +116,8 @@
                 throw new IllegalArgumentException(
                         "Cannot parse the timestamp of the first scenario.", e);
             }
+        } else if (mConfiguration.getSchedule().equals(Schedule.INDEXED)) {
+            Collections.sort(mOrderedScenariosList, new ScenarioIndexedComparator());
         } else {
             throw new UnsupportedOperationException(
                     "Only scheduled profiles are currently supported.");
diff --git a/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/ProfileSuite.java b/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/ProfileSuite.java
index 2baa274..d557780 100644
--- a/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/ProfileSuite.java
+++ b/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/ProfileSuite.java
@@ -147,6 +147,11 @@
                         mProfile.getCurrentScenario(),
                         timeout,
                         mProfile.hasNextScheduledScenario());
+
+            case INDEXED:
+                return getIndexedRunner(
+                        (BlockJUnit4ClassRunner) runner, mProfile.getCurrentScenario());
+
             default:
                 throw new RuntimeException(
                         String.format(
@@ -173,4 +178,19 @@
                     e);
         }
     }
+
+    /** Replace a runner with {@link ScenarioRunner} for features specific to indexed profiles. */
+    @VisibleForTesting
+    protected ScenarioRunner getIndexedRunner(BlockJUnit4ClassRunner runner, Scenario scenario) {
+        Class<?> testClass = runner.getTestClass().getJavaClass();
+        try {
+            return new ScenarioRunner(testClass, scenario);
+        } catch (InitializationError e) {
+            throw new RuntimeException(
+                    String.format(
+                            "Unable to run scenario %s with an indexed runner.",
+                            runner.getDescription().getDisplayName()),
+                    e);
+        }
+    }
 }
diff --git a/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/ScenarioRunner.java b/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/ScenarioRunner.java
new file mode 100644
index 0000000..33e4056
--- /dev/null
+++ b/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/ScenarioRunner.java
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+package android.platform.test.longevity;
+
+import android.os.Bundle;
+import android.platform.test.longevity.proto.Configuration.Scenario;
+import android.platform.test.longevity.proto.Configuration.Scenario.ExtraArg;
+import androidx.annotation.VisibleForTesting;
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+
+/** A {@link BlockJUnit4ClassRunner} that runs a test class with profile-specified options. */
+public class ScenarioRunner extends LongevityClassRunner {
+    private final Scenario mScenario;
+    private final Bundle mArguments;
+
+    public ScenarioRunner(Class<?> klass, Scenario scenario) throws InitializationError {
+        this(klass, scenario, InstrumentationRegistry.getArguments());
+    }
+
+    @VisibleForTesting
+    ScenarioRunner(Class<?> klass, Scenario scenario, Bundle arguments) throws InitializationError {
+        super(klass, arguments);
+        mScenario = scenario;
+        mArguments = arguments;
+    }
+
+    @Override
+    protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
+        // Keep a copy of the bundle arguments for restoring later.
+        Bundle modifiedArguments = mArguments.deepCopy();
+        for (ExtraArg argPair : mScenario.getExtrasList()) {
+            if (argPair.getKey() == null || argPair.getValue() == null) {
+                throw new IllegalArgumentException(
+                        String.format(
+                                "Each extra arg entry in scenario must have both a key and a value,"
+                                        + " but scenario is %s.",
+                                mScenario.toString()));
+            }
+            modifiedArguments.putString(argPair.getKey(), argPair.getValue());
+        }
+        // Swap the arguments, run the scenario, and then restore arguments.
+        InstrumentationRegistry.registerInstance(
+                InstrumentationRegistry.getInstrumentation(), modifiedArguments);
+        super.runChild(method, notifier);
+        InstrumentationRegistry.registerInstance(
+                InstrumentationRegistry.getInstrumentation(), mArguments);
+    }
+}
diff --git a/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/ScheduledScenarioRunner.java b/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/ScheduledScenarioRunner.java
index 1c4043e..c5a9ced 100644
--- a/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/ScheduledScenarioRunner.java
+++ b/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/ScheduledScenarioRunner.java
@@ -38,6 +38,8 @@
 /**
  * A {@link BlockJUnit4ClassRunner} that runs a test class with a specified timeout and optionally
  * performs an idle before teardown (staying inside the app for Android CUJs).
+ *
+ * <p>TODO(b/146215435): Refactor to extends the index-based {@link ScenarioRunner}.
  */
 public class ScheduledScenarioRunner extends LongevityClassRunner {
     // A leeway to ensure that the teardown steps in @After and @AfterClass has time to finish.
diff --git a/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/profile.proto b/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/profile.proto
index 37bed59..d05fd5a 100644
--- a/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/profile.proto
+++ b/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/profile.proto
@@ -21,17 +21,17 @@
 
 message Configuration {
     // Schedule used to run the profile.
-    // TODO(b/122323704): Implement ordered profile.
     enum Schedule {
         TIMESTAMPED = 1;
+        INDEXED = 2;
     }
     optional Schedule schedule = 1 [default = TIMESTAMPED];
 
     // Information for each scenario.
     message Scenario {
         oneof schedule {
-            // Timestamp to run the scenario in HH:MM:SS.
-            string at = 1;
+            string at = 1; // A timestamp (HH:MM:SS) for when to run the scenario.
+            int32 index = 2; // An index for the relative order of the scenario.
         }
         // Reference to the CUJ (<package>.<class>).
         optional string journey = 3;
diff --git a/libraries/health/runners/longevity/platform/tests/assets/testIndexedScheduling_respectsSchedule.textpb b/libraries/health/runners/longevity/platform/tests/assets/testIndexedScheduling_respectsSchedule.textpb
new file mode 100644
index 0000000..6f67f1e
--- /dev/null
+++ b/libraries/health/runners/longevity/platform/tests/assets/testIndexedScheduling_respectsSchedule.textpb
@@ -0,0 +1,11 @@
+schedule: INDEXED
+scenarios [{
+    index: 1
+    journey: "android.platform.test.longevity.samples.testing.SampleBasicProfileSuite$PassingTest1"
+}, {
+    index: 2
+    journey: "android.platform.test.longevity.samples.testing.SampleBasicProfileSuite$PassingTest2"
+}, {
+    index: 3
+    journey: "android.platform.test.longevity.samples.testing.SampleBasicProfileSuite$PassingTest1"
+}]
diff --git a/libraries/health/runners/longevity/platform/tests/assets/testScheduling_respectsSchedule.textpb b/libraries/health/runners/longevity/platform/tests/assets/testTimestampScheduling_respectsSchedule.textpb
similarity index 78%
rename from libraries/health/runners/longevity/platform/tests/assets/testScheduling_respectsSchedule.textpb
rename to libraries/health/runners/longevity/platform/tests/assets/testTimestampScheduling_respectsSchedule.textpb
index 4c02f42..f68cc2f 100644
--- a/libraries/health/runners/longevity/platform/tests/assets/testScheduling_respectsSchedule.textpb
+++ b/libraries/health/runners/longevity/platform/tests/assets/testTimestampScheduling_respectsSchedule.textpb
@@ -1,10 +1,10 @@
 schedule: TIMESTAMPED
 scenarios [{
     at: "00:00:01"
-    journey: "android.platform.test.longevity.samples.testing.SampleProfileSuite$LongIdleTest"
+    journey: "android.platform.test.longevity.samples.testing.SampleTimedProfileSuite$LongIdleTest"
     after_test: STAY_IN_APP
 }, {
     at: "00:00:10"
-    journey: "android.platform.test.longevity.samples.testing.SampleProfileSuite$PassingTest"
+    journey: "android.platform.test.longevity.samples.testing.SampleTimedProfileSuite$PassingTest"
     after_test: STAY_IN_APP
 }]
diff --git a/libraries/health/runners/longevity/platform/tests/assets/testScheduling_respectsSuiteTimeout.textpb b/libraries/health/runners/longevity/platform/tests/assets/testTimestampScheduling_respectsSuiteTimeout.textpb
similarity index 77%
rename from libraries/health/runners/longevity/platform/tests/assets/testScheduling_respectsSuiteTimeout.textpb
rename to libraries/health/runners/longevity/platform/tests/assets/testTimestampScheduling_respectsSuiteTimeout.textpb
index 7c32fa7..06af14f 100644
--- a/libraries/health/runners/longevity/platform/tests/assets/testScheduling_respectsSuiteTimeout.textpb
+++ b/libraries/health/runners/longevity/platform/tests/assets/testTimestampScheduling_respectsSuiteTimeout.textpb
@@ -1,9 +1,9 @@
 schedule: TIMESTAMPED
 scenarios [{
     at: "00:00:01"
-    journey: "android.platform.test.longevity.samples.testing.SampleProfileSuite$PassingTest"
+    journey: "android.platform.test.longevity.samples.testing.SampleTimedProfileSuite$PassingTest"
 }, {
     at: "00:00:05"
-    journey: "android.platform.test.longevity.samples.testing.SampleProfileSuite$LongIdleTest"
+    journey: "android.platform.test.longevity.samples.testing.SampleTimedProfileSuite$LongIdleTest"
     after_test: STAY_IN_APP
 }]
diff --git a/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ProfileSuiteTest.java b/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ProfileSuiteTest.java
index d42ccd7..14d66de 100644
--- a/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ProfileSuiteTest.java
+++ b/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ProfileSuiteTest.java
@@ -30,7 +30,8 @@
 import android.host.test.longevity.listener.TimeoutTerminator;
 import android.os.Bundle;
 import android.os.SystemClock;
-import android.platform.test.longevity.samples.testing.SampleProfileSuite;
+import android.platform.test.longevity.samples.testing.SampleBasicProfileSuite;
+import android.platform.test.longevity.samples.testing.SampleTimedProfileSuite;
 import android.platform.test.scenario.annotation.Scenario;
 
 import org.junit.Assert;
@@ -143,19 +144,21 @@
     @RunWith(Parameterized.class)
     public static class NotSupportedRunner extends BasicScenario {}
 
-    /** Test that a profile's scheduling is followed. */
+    /** Test that a timestamped profile's scheduling is followed. */
     @Test
-    public void testScheduling_respectsSchedule() throws InitializationError {
+    public void testTimestampScheduling_respectsSchedule() throws InitializationError {
         // TODO(harrytczhang@): Find a way to run this without relying on actual idles.
 
         // Arguments with the profile under test.
         Bundle args = new Bundle();
-        args.putString(Profile.PROFILE_OPTION_NAME, "testScheduling_respectsSchedule");
+        args.putString(Profile.PROFILE_OPTION_NAME, "testTimestampScheduling_respectsSchedule");
         // Scenario names from the profile.
         final String firstScenarioName =
-                "android.platform.test.longevity.samples.testing.SampleProfileSuite$LongIdleTest";
+                "android.platform.test.longevity.samples.testing."
+                        + "SampleTimedProfileSuite$LongIdleTest";
         final String secondScenarioName =
-                "android.platform.test.longevity.samples.testing.SampleProfileSuite$PassingTest";
+                "android.platform.test.longevity.samples.testing."
+                        + "SampleTimedProfileSuite$PassingTest";
         // Stores the start time of the test run for the suite. Using AtomicLong here as the time
         // should be initialized when run() is called on the suite, but Java does not want
         // assignment to local varaible in lambda expressions. AtomicLong allows for using the
@@ -164,7 +167,7 @@
         ProfileSuite suite =
                 spy(
                         new ProfileSuite(
-                                SampleProfileSuite.class,
+                                SampleTimedProfileSuite.class,
                                 new AllDefaultPossibilitiesBuilder(true),
                                 mInstrumentation,
                                 mContext,
@@ -242,22 +245,22 @@
                         argThat(notifier -> notifier.equals(mRunNotifier)));
     }
 
-    /** Test that a profile's last scenario is bounded by the suite timeout. */
+    /** Test that a timestamp profile's last scenario is bounded by the suite timeout. */
     @Test
-    public void testScheduling_respectsSuiteTimeout() throws InitializationError {
+    public void testTimestampScheduling_respectsSuiteTimeout() throws InitializationError {
         long suiteTimeoutMsecs = TimeUnit.SECONDS.toMillis(10);
         ArgumentCaptor<Failure> failureCaptor = ArgumentCaptor.forClass(Failure.class);
 
         // Arguments with the profile under test and suite timeout.
         Bundle args = new Bundle();
-        args.putString(Profile.PROFILE_OPTION_NAME, "testScheduling_respectsSuiteTimeout");
+        args.putString(Profile.PROFILE_OPTION_NAME, "testTimestampScheduling_respectsSuiteTimeout");
         args.putString(TimeoutTerminator.OPTION, String.valueOf(suiteTimeoutMsecs));
 
         // Construct and run the profile suite.
         ProfileSuite suite =
                 spy(
                         new ProfileSuite(
-                                SampleProfileSuite.class,
+                                SampleTimedProfileSuite.class,
                                 new AllDefaultPossibilitiesBuilder(true),
                                 mInstrumentation,
                                 mContext,
@@ -294,4 +297,64 @@
                                 });
         Assert.assertTrue(correctTestTimedOutExceptionFired);
     }
+
+    /** Test that an indexed profile's scheduling is followed. */
+    @Test
+    public void testIndexedScheduling_respectsSchedule() throws InitializationError {
+        // Arguments with the profile under test.
+        Bundle args = new Bundle();
+        args.putString(Profile.PROFILE_OPTION_NAME, "testIndexedScheduling_respectsSchedule");
+        // Scenario names from the profile.
+        final String firstScenarioName =
+                "android.platform.test.longevity.samples.testing."
+                        + "SampleBasicProfileSuite$PassingTest1";
+        final String secondScenarioName =
+                "android.platform.test.longevity.samples.testing."
+                        + "SampleBasicProfileSuite$PassingTest2";
+        final String thirdScenarioName =
+                "android.platform.test.longevity.samples.testing."
+                        + "SampleBasicProfileSuite$PassingTest1";
+        ProfileSuite suite =
+                spy(
+                        new ProfileSuite(
+                                SampleBasicProfileSuite.class,
+                                new AllDefaultPossibilitiesBuilder(true),
+                                mInstrumentation,
+                                mContext,
+                                args));
+
+        InOrder inOrderVerifier = inOrder(suite);
+
+        suite.run(mRunNotifier);
+        // Verify that the first scenario is started.
+        inOrderVerifier
+                .verify(suite)
+                .runChild(
+                        argThat(
+                                runner ->
+                                        runner.getDescription()
+                                                .getDisplayName()
+                                                .equals(firstScenarioName)),
+                        argThat(notifier -> notifier.equals(mRunNotifier)));
+        // Verify that the second scenario is started.
+        inOrderVerifier
+                .verify(suite)
+                .runChild(
+                        argThat(
+                                runner ->
+                                        runner.getDescription()
+                                                .getDisplayName()
+                                                .equals(secondScenarioName)),
+                        argThat(notifier -> notifier.equals(mRunNotifier)));
+        // Verify that the third scenario is started.
+        inOrderVerifier
+                .verify(suite)
+                .runChild(
+                        argThat(
+                                runner ->
+                                        runner.getDescription()
+                                                .getDisplayName()
+                                                .equals(thirdScenarioName)),
+                        argThat(notifier -> notifier.equals(mRunNotifier)));
+    }
 }
diff --git a/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ScenarioRunnerTest.java b/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ScenarioRunnerTest.java
new file mode 100644
index 0000000..a6f3c50
--- /dev/null
+++ b/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ScenarioRunnerTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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.
+ */
+package android.platform.test.longevity;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.os.Bundle;
+import android.platform.test.longevity.proto.Configuration.Scenario;
+import android.platform.test.longevity.proto.Configuration.Scenario.ExtraArg;
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Assert;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.JUnit4;
+import org.junit.runners.model.InitializationError;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.exceptions.base.MockitoAssertionError;
+
+import java.util.HashSet;
+import java.util.List;
+
+/** Unit tests for the {@link ScenarioRunner} runner. */
+@RunWith(JUnit4.class)
+public class ScenarioRunnerTest {
+
+    @Mock private RunNotifier mRunNotifier;
+
+    private static final String ASSERTION_FAILURE_MESSAGE = "Test assertion failed";
+
+    public static class ArgumentTest {
+        public static final String TEST_ARG = "test-arg-test-only";
+        public static final String TEST_ARG_DEFAULT = "default";
+        public static final String TEST_ARG_OVERRIDE = "not default";
+
+        @Before
+        public void setUp() {
+            // The actual argument testing happens here as this is where instrumentation args are
+            // parsed in the CUJs.
+            String argValue =
+                    InstrumentationRegistry.getArguments().getString(TEST_ARG, TEST_ARG_DEFAULT);
+            Assert.assertEquals(ASSERTION_FAILURE_MESSAGE, argValue, TEST_ARG_OVERRIDE);
+        }
+
+        @Test
+        public void dummyTest() {
+            // Does nothing; always passes.
+        }
+    }
+
+    // Holds the state of the instrumentation args before each test for restoring after, as one test
+    // might affect the state of another otherwise.
+    // TODO(b/124239142): Avoid manipulating the instrumentation args here.
+    private Bundle mArgumentsBeforeTest;
+
+    @Before
+    public void setUpSuite() throws InitializationError {
+        initMocks(this);
+        mArgumentsBeforeTest = InstrumentationRegistry.getArguments();
+    }
+
+    @After
+    public void restoreSuite() {
+        InstrumentationRegistry.registerInstance(
+                InstrumentationRegistry.getInstrumentation(), mArgumentsBeforeTest);
+    }
+
+    /** Test that the "extras" in a scenario is properly registered before the test. */
+    @Test
+    public void testExtraArgs_registeredBeforeTest() throws Throwable {
+        Scenario testScenario =
+                Scenario.newBuilder()
+                        .setIndex(1)
+                        .setJourney(ArgumentTest.class.getName())
+                        .addExtras(
+                                ExtraArg.newBuilder()
+                                        .setKey(ArgumentTest.TEST_ARG)
+                                        .setValue(ArgumentTest.TEST_ARG_OVERRIDE))
+                        .build();
+        ScenarioRunner runner = spy(new ScenarioRunner(ArgumentTest.class, testScenario));
+        runner.run(mRunNotifier);
+        verifyForAssertionFailures(mRunNotifier);
+    }
+
+    /** Test that the "extras" in a scenario is properly un-registered after the test. */
+    @Test
+    public void testExtraArgs_unregisteredAfterTest() throws Throwable {
+        Bundle argsBeforeTest = InstrumentationRegistry.getArguments();
+        Scenario testScenario =
+                Scenario.newBuilder()
+                        .setIndex(1)
+                        .setJourney(ArgumentTest.class.getName())
+                        .addExtras(
+                                ExtraArg.newBuilder()
+                                        .setKey(ArgumentTest.TEST_ARG)
+                                        .setValue(ArgumentTest.TEST_ARG_OVERRIDE))
+                        .build();
+        ScenarioRunner runner = spy(new ScenarioRunner(ArgumentTest.class, testScenario));
+        runner.run(mRunNotifier);
+        Bundle argsAfterTest = InstrumentationRegistry.getArguments();
+        Assert.assertTrue(bundlesContainSameStringKeyValuePairs(argsBeforeTest, argsAfterTest));
+    }
+
+    /**
+     * Verify that no test failure is fired because of an assertion failure in the stubbed methods.
+     * If the verification fails, check whether it's due the injected assertions failing. If yes,
+     * throw that exception out; otherwise, throw the first exception.
+     */
+    private void verifyForAssertionFailures(final RunNotifier notifier) throws Throwable {
+        try {
+            verify(notifier, never()).fireTestFailure(any());
+        } catch (MockitoAssertionError e) {
+            ArgumentCaptor<Failure> failureCaptor = ArgumentCaptor.forClass(Failure.class);
+            verify(notifier, atLeastOnce()).fireTestFailure(failureCaptor.capture());
+            List<Failure> failures = failureCaptor.getAllValues();
+            // Go through the failures, look for an known failure case from the above exceptions
+            // and throw the exception in the first one out if any.
+            for (Failure failure : failures) {
+                if (failure.getException().getMessage().contains(ASSERTION_FAILURE_MESSAGE)) {
+                    throw failure.getException();
+                }
+            }
+            // Otherwise, throw the exception from the first failure reported.
+            throw failures.get(0).getException();
+        }
+    }
+
+    /**
+     * Helper method to check whether two {@link Bundle}s are equal since the built-in {@code
+     * equals} is not properly overridden.
+     */
+    private boolean bundlesContainSameStringKeyValuePairs(Bundle b1, Bundle b2) {
+        if (b1.size() != b2.size()) {
+            return false;
+        }
+        HashSet<String> allKeys = new HashSet<String>(b1.keySet());
+        allKeys.addAll(b2.keySet());
+        for (String key : allKeys) {
+            if (b1.getString(key) != null) {
+                // If key is in b1 and corresponds to a string, check whether this key corresponds
+                // to the same value in b2.
+                if (!b1.getString(key).equals(b2.getString(key))) {
+                    return false;
+                }
+            } else if (b2.getString(key) != null) {
+                // Otherwise if b2 has a string at this key, return false since we know that b1 does
+                // not have a string at this key.
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ScheduledScenarioRunnerTest.java b/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ScheduledScenarioRunnerTest.java
index 7afec46..e8d1218 100644
--- a/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ScheduledScenarioRunnerTest.java
+++ b/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ScheduledScenarioRunnerTest.java
@@ -31,7 +31,7 @@
 import android.platform.test.longevity.proto.Configuration.Scenario;
 import android.platform.test.longevity.proto.Configuration.Scenario.AfterTest;
 import android.platform.test.longevity.proto.Configuration.Scenario.ExtraArg;
-import android.platform.test.longevity.samples.testing.SampleProfileSuite;
+import android.platform.test.longevity.samples.testing.SampleTimedProfileSuite;
 import androidx.test.InstrumentationRegistry;
 
 import org.junit.Assert;
@@ -114,13 +114,13 @@
         Scenario testScenario =
                 Scenario.newBuilder()
                         .setAt("00:00:00")
-                        .setJourney(SampleProfileSuite.LongIdleTest.class.getName())
+                        .setJourney(SampleTimedProfileSuite.LongIdleTest.class.getName())
                         .setAfterTest(AfterTest.STAY_IN_APP)
                         .build();
         ScheduledScenarioRunner runner =
                 spy(
                         new ScheduledScenarioRunner(
-                                SampleProfileSuite.LongIdleTest.class,
+                                SampleTimedProfileSuite.LongIdleTest.class,
                                 testScenario,
                                 timeoutMs,
                                 true));
@@ -157,13 +157,13 @@
         Scenario testScenario =
                 Scenario.newBuilder()
                         .setAt("00:00:00")
-                        .setJourney(SampleProfileSuite.LongIdleTest.class.getName())
+                        .setJourney(SampleTimedProfileSuite.LongIdleTest.class.getName())
                         .setAfterTest(AfterTest.STAY_IN_APP)
                         .build();
         ScheduledScenarioRunner runner =
                 spy(
                         new ScheduledScenarioRunner(
-                                SampleProfileSuite.LongIdleTest.class,
+                                SampleTimedProfileSuite.LongIdleTest.class,
                                 testScenario,
                                 TimeUnit.SECONDS.toMillis(6),
                                 true));
@@ -181,13 +181,13 @@
         Scenario testScenario =
                 Scenario.newBuilder()
                         .setAt("00:00:00")
-                        .setJourney(SampleProfileSuite.LongIdleTest.class.getName())
+                        .setJourney(SampleTimedProfileSuite.LongIdleTest.class.getName())
                         .setAfterTest(AfterTest.STAY_IN_APP)
                         .build();
         ScheduledScenarioRunner runner =
                 spy(
                         new ScheduledScenarioRunner(
-                                SampleProfileSuite.LongIdleTest.class,
+                                SampleTimedProfileSuite.LongIdleTest.class,
                                 testScenario,
                                 TimeUnit.SECONDS.toMillis(6),
                                 true));
@@ -210,13 +210,13 @@
         Scenario testScenario =
                 Scenario.newBuilder()
                         .setAt("00:00:00")
-                        .setJourney(SampleProfileSuite.PassingTest.class.getName())
+                        .setJourney(SampleTimedProfileSuite.PassingTest.class.getName())
                         .setAfterTest(AfterTest.STAY_IN_APP)
                         .build();
         ScheduledScenarioRunner runner =
                 spy(
                         new ScheduledScenarioRunner(
-                                SampleProfileSuite.PassingTest.class,
+                                SampleTimedProfileSuite.PassingTest.class,
                                 testScenario,
                                 timeoutMs,
                                 true));
@@ -242,13 +242,13 @@
         Scenario testScenario =
                 Scenario.newBuilder()
                         .setAt("00:00:00")
-                        .setJourney(SampleProfileSuite.PassingTest.class.getName())
+                        .setJourney(SampleTimedProfileSuite.PassingTest.class.getName())
                         .setAfterTest(AfterTest.EXIT)
                         .build();
         ScheduledScenarioRunner runner =
                 spy(
                         new ScheduledScenarioRunner(
-                                SampleProfileSuite.PassingTest.class,
+                                SampleTimedProfileSuite.PassingTest.class,
                                 testScenario,
                                 timeoutMs,
                                 true));
@@ -270,17 +270,17 @@
         Scenario testScenario =
                 Scenario.newBuilder()
                         .setAt("00:00:00")
-                        .setJourney(SampleProfileSuite.PassingTest.class.getName())
+                        .setJourney(SampleTimedProfileSuite.PassingTest.class.getName())
                         .setAfterTest(AfterTest.EXIT)
                         .build();
         Bundle ignores = new Bundle();
         ignores.putString(
                 LongevityClassRunner.FILTER_OPTION,
-                SampleProfileSuite.PassingTest.class.getCanonicalName());
+                SampleTimedProfileSuite.PassingTest.class.getCanonicalName());
         ScheduledScenarioRunner runner =
                 spy(
                         new ScheduledScenarioRunner(
-                                SampleProfileSuite.PassingTest.class,
+                                SampleTimedProfileSuite.PassingTest.class,
                                 testScenario,
                                 timeoutMs,
                                 true,
@@ -307,13 +307,13 @@
         Scenario testScenario =
                 Scenario.newBuilder()
                         .setAt("00:00:00")
-                        .setJourney(SampleProfileSuite.PassingTest.class.getName())
+                        .setJourney(SampleTimedProfileSuite.PassingTest.class.getName())
                         .setAfterTest(AfterTest.STAY_IN_APP)
                         .build();
         ScheduledScenarioRunner runner =
                 spy(
                         new ScheduledScenarioRunner(
-                                SampleProfileSuite.PassingTest.class,
+                                SampleTimedProfileSuite.PassingTest.class,
                                 testScenario,
                                 TimeUnit.SECONDS.toMillis(6),
                                 false));
@@ -380,7 +380,7 @@
 
     /**
      * Verify that no test failure is fired because of an assertion failure in the stubbed methods.
-     * If the verfication fails, check whether it's due the injected assertions failing. If yes,
+     * If the verification fails, check whether it's due the injected assertions failing. If yes,
      * throw that exception out; otherwise, throw the first exception.
      */
     private void verifyForAssertionFailures(final RunNotifier notifier) throws Throwable {
@@ -404,7 +404,7 @@
 
     /**
      * Helper method to check whether two {@link Bundle}s are equal since the built-in {@code
-     * equals} is not properly overriden.
+     * equals} is not properly overridden.
      */
     private boolean bundlesContainSameStringKeyValuePairs(Bundle b1, Bundle b2) {
         if (b1.size() != b2.size()) {
diff --git a/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/samples/testing/SampleProfileSuite.java b/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/samples/testing/SampleBasicProfileSuite.java
similarity index 64%
copy from libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/samples/testing/SampleProfileSuite.java
copy to libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/samples/testing/SampleBasicProfileSuite.java
index 2b1bf10..2c0cd5f 100644
--- a/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/samples/testing/SampleProfileSuite.java
+++ b/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/samples/testing/SampleBasicProfileSuite.java
@@ -16,53 +16,38 @@
 
 package android.platform.test.longevity.samples.testing;
 
-import android.os.SystemClock;
 import android.platform.test.longevity.ProfileSuite;
 import android.platform.test.scenario.annotation.Scenario;
 
-import org.junit.AfterClass;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import org.junit.runners.Suite.SuiteClasses;
 
-import java.util.concurrent.TimeUnit;
-
 @RunWith(ProfileSuite.class)
 @SuiteClasses({
-    SampleProfileSuite.LongIdleTest.class,
-    SampleProfileSuite.PassingTest.class,
+    SampleBasicProfileSuite.PassingTest1.class,
+    SampleBasicProfileSuite.PassingTest2.class,
 })
 /** Sample device-side test cases using a profile. */
-public class SampleProfileSuite {
+public class SampleBasicProfileSuite {
+
     @Scenario
     @RunWith(JUnit4.class)
-    public static class LongIdleTest {
+    public static class PassingTest1 {
         @Test
-        public void testLongIdle() {
-            SystemClock.sleep(TimeUnit.SECONDS.toMillis(10));
-        }
-
-        // Simulates a quick teardown step.
-        @AfterClass
-        public static void dummyTearDown() {
-            SystemClock.sleep(100);
+        public void testPassing() {
+            Assert.assertEquals(1, 1);
         }
     }
 
     @Scenario
     @RunWith(JUnit4.class)
-    public static class PassingTest {
+    public static class PassingTest2 {
         @Test
         public void testPassing() {
-            Assert.assertEquals(1, 1);
-        }
-
-        // Simulates a quick teardown step.
-        @AfterClass
-        public static void dummyTearDown() {
-            SystemClock.sleep(100);
+            Assert.assertEquals(2, 2);
         }
     }
 }
diff --git a/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/samples/testing/SampleProfileSuite.java b/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/samples/testing/SampleTimedProfileSuite.java
similarity index 93%
rename from libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/samples/testing/SampleProfileSuite.java
rename to libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/samples/testing/SampleTimedProfileSuite.java
index 2b1bf10..16686ee 100644
--- a/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/samples/testing/SampleProfileSuite.java
+++ b/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/samples/testing/SampleTimedProfileSuite.java
@@ -31,11 +31,11 @@
 
 @RunWith(ProfileSuite.class)
 @SuiteClasses({
-    SampleProfileSuite.LongIdleTest.class,
-    SampleProfileSuite.PassingTest.class,
+    SampleTimedProfileSuite.LongIdleTest.class,
+    SampleTimedProfileSuite.PassingTest.class,
 })
 /** Sample device-side test cases using a profile. */
-public class SampleProfileSuite {
+public class SampleTimedProfileSuite {
     @Scenario
     @RunWith(JUnit4.class)
     public static class LongIdleTest {