Merge "Moved profile to RunListener interface."
diff --git a/libraries/longevity/platform/src/android/platform/test/longevity/Profile.java b/libraries/longevity/platform/src/android/platform/test/longevity/Profile.java
index 39526ca..7ce759e 100644
--- a/libraries/longevity/platform/src/android/platform/test/longevity/Profile.java
+++ b/libraries/longevity/platform/src/android/platform/test/longevity/Profile.java
@@ -16,8 +16,8 @@
 package android.platform.test.longevity;
 
 import android.content.res.AssetManager;
-import android.host.test.composer.Compose;
 import android.os.Bundle;
+import android.os.SystemClock;
 import android.platform.test.longevity.proto.Configuration;
 import android.platform.test.longevity.proto.Configuration.Scenario;
 import android.platform.test.longevity.proto.Configuration.Schedule;
@@ -27,6 +27,7 @@
 
 import org.junit.runner.Description;
 import org.junit.runner.Runner;
+import org.junit.runner.notification.RunListener;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -43,10 +44,8 @@
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
-/**
- * A profile composer for device-side testing.
- */
-public class Profile implements Compose<Bundle, Runner> {
+/** A profile composer for device-side testing. */
+public class Profile extends RunListener {
     @VisibleForTesting static final String PROFILE_OPTION_NAME = "profile";
 
     protected static final String PROFILE_EXTENSION = ".pb";
@@ -56,13 +55,13 @@
     // Parser for parsing "at" timestamps in profiles.
     private static final SimpleDateFormat TIMESTAMP_FORMATTER = new SimpleDateFormat("HH:mm:ss");
 
-    // Keeps track of the next scenario to run.
+    // Keeps track of the current scenario being run; updated at the end of a scenario.
     private int mScenarioIndex = 0;
     // A list of scenarios in the order that they will be run.
     private List<Scenario> mOrderedScenariosList;
     // Timestamp when the test run starts, defaults to time when the ProfileBase object is
     // constructed. Can be overridden by {@link setTestRunStartTimeMs}.
-    private long mRunStartTimeMs = System.currentTimeMillis();
+    private long mRunStartTimeMs = SystemClock.elapsedRealtime();
     // The profile configuration.
     private Configuration mConfiguration;
     // The timestamp of the first scenario in milliseconds. All scenarios will be scheduled relative
@@ -112,13 +111,11 @@
         }
     }
 
-    @Override
-    public List<Runner> apply(Bundle args, List<Runner> input) {
-        Configuration config = getConfigurationArgument(args);
-        if (config == null) {
+    public List<Runner> getRunnerSequence(List<Runner> input) {
+        if (mConfiguration == null) {
             return input;
         }
-        return getTestSequenceFromConfiguration(config, input);
+        return getTestSequenceFromConfiguration(mConfiguration, input);
     }
 
     protected List<Runner> getTestSequenceFromConfiguration(
@@ -155,13 +152,13 @@
         return result;
     }
 
-    /** Enables classes using the profile composer to set the test run start time. */
-    public void setTestRunStartTimeMs(long timestamp) {
-        mRunStartTimeMs = timestamp;
+    @Override
+    public void testRunStarted(Description description) {
+        mRunStartTimeMs = SystemClock.elapsedRealtime();
     }
 
-    /** Called by suite runners to signal that a scenario/test has started. */
-    public void scenarioStarted() {
+    @Override
+    public void testFinished(Description description) {
         // Increments the index to move onto the next scenario.
         mScenarioIndex += 1;
     }
@@ -171,12 +168,13 @@
      * false.
      */
     public boolean hasNextScheduledScenario() {
-        return (mOrderedScenariosList != null) && (mScenarioIndex < mOrderedScenariosList.size());
+        return (mOrderedScenariosList != null)
+                && (mScenarioIndex < mOrderedScenariosList.size() - 1);
     }
 
     /** Returns time in milliseconds until the next scenario. */
     public long getTimeUntilNextScenarioMs() {
-        Scenario nextScenario = mOrderedScenariosList.get(mScenarioIndex);
+        Scenario nextScenario = mOrderedScenariosList.get(mScenarioIndex + 1);
         if (nextScenario.hasAt()) {
             try {
                 // Calibrate the start time against the first scenario's timestamp.
@@ -203,14 +201,12 @@
 
     /** Return time in milliseconds since the test run started. */
     public long getTimeSinceRunStartedMs() {
-        return System.currentTimeMillis() - mRunStartTimeMs;
+        return SystemClock.elapsedRealtime() - mRunStartTimeMs;
     }
 
     /** Returns the Scenario object for the current scenario. */
     public Scenario getCurrentScenario() {
-        // mScenarioIndex points to the next scenario, so the index for the current one is
-        // mScenarioIndex - 1.
-        return mOrderedScenariosList.get(mScenarioIndex - 1);
+        return mOrderedScenariosList.get(mScenarioIndex);
     }
 
     /** Returns the profile configuration. */
diff --git a/libraries/longevity/platform/src/android/platform/test/longevity/ProfileSuite.java b/libraries/longevity/platform/src/android/platform/test/longevity/ProfileSuite.java
index 4248a86..76746bb 100644
--- a/libraries/longevity/platform/src/android/platform/test/longevity/ProfileSuite.java
+++ b/libraries/longevity/platform/src/android/platform/test/longevity/ProfileSuite.java
@@ -19,12 +19,10 @@
 import android.app.Instrumentation;
 import android.content.Context;
 import android.os.Bundle;
-import android.os.SystemClock;
 import android.platform.test.longevity.proto.Configuration.Scenario;
 import androidx.annotation.VisibleForTesting;
 import androidx.test.InstrumentationRegistry;
 
-import java.util.function.BiFunction;
 import java.util.List;
 
 import org.junit.runner.Runner;
@@ -112,19 +110,14 @@
             }
         }
         // Construct and store custom runners for the full suite.
-        BiFunction<Bundle, List<Runner>, List<Runner>> modifier = new Profile(args);
-        return modifier.apply(args, builder.runners(suite, annotation.value()));
+        return new Profile(args).getRunnerSequence(builder.runners(suite, annotation.value()));
     }
 
     /** {@inheritDoc} */
     @Override
     public void run(final RunNotifier notifier) {
-        // Set the test run start time in the profile composer and sleep until the first scheduled
-        // test starts. When no profile is supplied, hasNextScheduledScenario() returns false and
-        // no sleep is performed.
-        if (mProfile.hasNextScheduledScenario()) {
-            mProfile.setTestRunStartTimeMs(System.currentTimeMillis());
-        }
+        // Add the profile listener.
+        notifier.addListener(mProfile);
         // Register other listeners and continue with standard longevity run.
         super.run(notifier);
     }
@@ -132,7 +125,6 @@
     /** {@inheritDoc} */
     @Override
     protected void runChild(Runner runner, final RunNotifier notifier) {
-        mProfile.scenarioStarted();
         super.runChild(runner, notifier);
     }
 
diff --git a/libraries/longevity/platform/src/android/platform/test/longevity/listener/TimeoutTerminator.java b/libraries/longevity/platform/src/android/platform/test/longevity/listener/TimeoutTerminator.java
index 8090659..4773ce8 100644
--- a/libraries/longevity/platform/src/android/platform/test/longevity/listener/TimeoutTerminator.java
+++ b/libraries/longevity/platform/src/android/platform/test/longevity/listener/TimeoutTerminator.java
@@ -36,7 +36,7 @@
      */
     @Override
     protected long getCurrentTimestamp() {
-        return SystemClock.uptimeMillis();
+        return SystemClock.elapsedRealtime();
     }
 
     /**
diff --git a/libraries/longevity/platform/tests/src/android/platform/test/longevity/ProfileSuiteTest.java b/libraries/longevity/platform/tests/src/android/platform/test/longevity/ProfileSuiteTest.java
index 76ccf1d..5f00b04 100644
--- a/libraries/longevity/platform/tests/src/android/platform/test/longevity/ProfileSuiteTest.java
+++ b/libraries/longevity/platform/tests/src/android/platform/test/longevity/ProfileSuiteTest.java
@@ -28,9 +28,10 @@
 import android.app.Instrumentation;
 import android.content.Context;
 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.scenario.annotation.Scenario;
-import android.os.Bundle;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -63,8 +64,8 @@
 
     @Mock private Instrumentation mInstrumentation;
     @Mock private Context mContext;
-    @Mock private RunNotifier mRunNotifier;
     @Mock private Profile mProfile;
+    private RunNotifier mRunNotifier;
 
     // Threshold above which missing a schedule is considered a failure.
     private static final long SCHEDULE_LEEWAY_MS = 500;
@@ -72,6 +73,7 @@
     @Before
     public void setUpSuite() throws InitializationError {
         initMocks(this);
+        mRunNotifier = spy(new RunNotifier());
     }
 
     /** Test that profile suites with classes that aren't scenarios are rejected. */
@@ -158,7 +160,7 @@
         // 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
         // same reference but altering the value.
-        final AtomicLong runStartTimeMs = new AtomicLong(System.currentTimeMillis());
+        final AtomicLong runStartTimeMs = new AtomicLong(SystemClock.elapsedRealtime());
         ProfileSuite suite =
                 spy(
                         new ProfileSuite(
@@ -170,7 +172,7 @@
         // Stub the lifecycle calls to verify that tests are run on schedule.
         doAnswer(
                         invocation -> {
-                            runStartTimeMs.set(System.currentTimeMillis());
+                            runStartTimeMs.set(SystemClock.elapsedRealtime());
                             invocation.callRealMethod();
                             return null;
                         })
@@ -180,7 +182,7 @@
                         invocation -> {
                             // The first scenario should start immediately.
                             Assert.assertTrue(
-                                    abs(System.currentTimeMillis() - runStartTimeMs.longValue())
+                                    abs(SystemClock.elapsedRealtime() - runStartTimeMs.longValue())
                                             <= SCHEDULE_LEEWAY_MS);
                             invocation.callRealMethod();
                             return null;
@@ -198,7 +200,7 @@
                             // The second scenario should begin at 00:00:10 - 00:00:01 = 9 seconds.
                             Assert.assertTrue(
                                     abs(
-                                                    System.currentTimeMillis()
+                                                    SystemClock.elapsedRealtime()
                                                             - runStartTimeMs.longValue()
                                                             - TimeUnit.SECONDS.toMillis(9))
                                             <= SCHEDULE_LEEWAY_MS);
diff --git a/libraries/longevity/platform/tests/src/android/platform/test/longevity/ProfileTest.java b/libraries/longevity/platform/tests/src/android/platform/test/longevity/ProfileTest.java
index 077481d..aacee7c 100644
--- a/libraries/longevity/platform/tests/src/android/platform/test/longevity/ProfileTest.java
+++ b/libraries/longevity/platform/tests/src/android/platform/test/longevity/ProfileTest.java
@@ -120,12 +120,11 @@
             "android.platform.test.scenario.calendar.FlingWeekPage",
             "android.platform.test.scenario.calendar.FlingDayPage");
 
-        List<Runner> output = getProfile(getArguments(VALID_CONFIG_KEY))
-                .apply(getArguments(VALID_CONFIG_KEY), mMockInput);
+        List<Runner> output =
+                getProfile(getArguments(VALID_CONFIG_KEY)).getRunnerSequence(mMockInput);
         List<String> outputDescriptions = output.stream().map(r ->
                 r.getDescription().getDisplayName()).collect(Collectors.toList());
         boolean respected = outputDescriptions.equals(expectedJourneyOrder);
-        System.out.println(outputDescriptions);
         assertThat(respected).isTrue();
     }
 
@@ -140,8 +139,9 @@
         exceptionThrown.expectMessage("invalid");
 
         // Attempt to apply a profile with invalid CUJ; the above exception should be thrown.
-        List<Runner> output = getProfile(getArguments(CONFIG_WITH_INVALID_JOURNEY_KEY))
-                .apply(getArguments(CONFIG_WITH_INVALID_JOURNEY_KEY), mMockInput);
+        List<Runner> output =
+                getProfile(getArguments(CONFIG_WITH_INVALID_JOURNEY_KEY))
+                        .getRunnerSequence(mMockInput);
     }
 
     protected class TestableProfile extends Profile {