Convert the flickerlib transition runner mechanism to kotlin

Test: atest FlickerLibTest
Change-Id: I9472cff3d3fd8eb9375e0c154960b1f5425391eb
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/LayersTraceSubject.kt b/libraries/flicker/src/com/android/server/wm/flicker/LayersTraceSubject.kt
index 618a04d..7fbe80c 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/LayersTraceSubject.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/LayersTraceSubject.kt
@@ -19,7 +19,6 @@
 import android.graphics.Rect
 import android.graphics.Region
 import android.util.Log
-import com.android.server.wm.flicker.TransitionRunner.TransitionResult
 import com.google.common.truth.FailureMetadata
 import com.google.common.truth.Subject
 import com.google.common.truth.Subject.Factory
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/TransitionResult.kt b/libraries/flicker/src/com/android/server/wm/flicker/TransitionResult.kt
new file mode 100644
index 0000000..4566f12
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/TransitionResult.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker
+
+import androidx.annotation.VisibleForTesting
+import java.nio.file.Files
+import java.nio.file.Path
+
+/** Stores paths to all test artifacts.  */
+class TransitionResult @VisibleForTesting internal constructor(
+    val layersTracePath: Path?,
+    val layersTraceChecksum: String,
+    val windowManagerTracePath: Path?,
+    val windowManagerTraceChecksum: String,
+    val screenCaptureVideo: Path?,
+    val screenCaptureVideoChecksum: String
+) {
+    private var flaggedForSaving = true
+
+    val layersTrace: ByteArray
+        get() {
+            return try {
+                Files.readAllBytes(layersTracePath)
+            } catch (e: Exception) {
+                throw RuntimeException(e)
+            }
+        }
+
+    val windowManagerTrace: ByteArray
+        get() {
+            return try {
+                Files.readAllBytes(windowManagerTracePath)
+            } catch (e: Exception) {
+                throw RuntimeException(e)
+            }
+        }
+
+    fun flagForSaving() {
+        flaggedForSaving = true
+    }
+
+    fun canDelete(): Boolean {
+        return !flaggedForSaving
+    }
+
+    fun layersTraceExists(): Boolean {
+        return layersTracePath != null && Files.exists(layersTracePath)
+    }
+
+    fun windowManagerTraceExists(): Boolean {
+        return windowManagerTracePath != null && Files.exists(windowManagerTracePath)
+    }
+
+    fun screenCaptureVideoExists(): Boolean {
+        return screenCaptureVideo != null && Files.exists(screenCaptureVideo)
+    }
+
+    fun screenCaptureVideoPath(): Path? {
+        return screenCaptureVideo
+    }
+
+    fun delete() {
+        if (layersTraceExists()) {
+            layersTracePath?.toFile()?.delete()
+        }
+        if (windowManagerTraceExists()) {
+            windowManagerTracePath?.toFile()?.delete()
+        }
+        if (screenCaptureVideoExists()) {
+            screenCaptureVideo?.toFile()?.delete()
+        }
+    }
+}
\ No newline at end of file
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/TransitionRunner.java b/libraries/flicker/src/com/android/server/wm/flicker/TransitionRunner.java
deleted file mode 100644
index da2771b..0000000
--- a/libraries/flicker/src/com/android/server/wm/flicker/TransitionRunner.java
+++ /dev/null
@@ -1,475 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker;
-
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-import androidx.test.InstrumentationRegistry;
-
-import com.android.server.wm.flicker.monitor.ITransitionMonitor;
-import com.android.server.wm.flicker.monitor.LayersTraceMonitor;
-import com.android.server.wm.flicker.monitor.ScreenRecorder;
-import com.android.server.wm.flicker.monitor.WindowAnimationFrameStatsMonitor;
-import com.android.server.wm.flicker.monitor.WindowManagerTraceMonitor;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Builds and runs UI transitions capturing test artifacts.
- *
- * <p>User can compose a transition from simpler steps, specifying setup and teardown steps. During
- * a transition, Layers trace, WindowManager trace, screen recordings and window animation frame
- * stats can be captured.
- *
- * <pre>
- * Transition builder options:
- *  {@link TransitionBuilder#run(Runnable)} run transition under test. Monitors will be started
- *  before the transition and stopped after the transition is completed.
- *  {@link TransitionBuilder#repeat(int)} repeat transitions under test multiple times recording
- *  result for each run.
- *  {@link TransitionBuilder#withTag(String)} specify a string identifier used to prefix logs and
- *  artifacts generated.
- *  {@link TransitionBuilder#runBeforeAll(Runnable)} run setup transitions once before all other
- *  transition are run to set up an initial state on device.
- *  {@link TransitionBuilder#runBefore(Runnable)} run setup transitions before each test transition
- *  run.
- *  {@link TransitionBuilder#runAfter(Runnable)} run teardown transitions after each test
- *  transition.
- *  {@link TransitionBuilder#runAfter(Runnable)} run teardown transitions once after all
- *  other transition  are run.
- *  {@link TransitionBuilder#includeJankyRuns()} disables {@link WindowAnimationFrameStatsMonitor}
- *  to monitor janky frames. If janky frames are detected, then the test run is skipped. This
- *  monitor is enabled by default.
- *  {@link TransitionBuilder#skipLayersTrace()} disables {@link LayersTraceMonitor} used to
- *  capture Layers trace during a transition. This monitor is enabled by default.
- *  {@link TransitionBuilder#skipWindowManagerTrace()} disables {@link WindowManagerTraceMonitor}
- *  used to capture WindowManager trace during a transition. This monitor is enabled by
- *  default.
- *  {@link TransitionBuilder#recordAllRuns()} records the screen contents and saves it to a file.
- *  All the runs including setup and teardown transitions are included in the recording. This
- *  monitor is used for debugging purposes.
- *  {@link TransitionBuilder#recordEachRun()} records the screen contents during test transitions
- *  and saves it to a file for each run. This monitor is used for debugging purposes.
- *
- * Example transition to capture WindowManager and Layers trace when opening a test app:
- * {@code
- * TransitionRunner.newBuilder()
- *      .withTag("OpenTestAppFast")
- *      .runBeforeAll(UiAutomationLib::wakeUp)
- *      .runBeforeAll(UiAutomationLib::UnlockDevice)
- *      .runBeforeAll(UiAutomationLib::openTestApp)
- *      .runBefore(UiAutomationLib::closeTestApp)
- *      .run(UiAutomationLib::openTestApp)
- *      .runAfterAll(UiAutomationLib::closeTestApp)
- *      .repeat(5)
- *      .build()
- *      .run();
- * }
- * </pre>
- */
-public class TransitionRunner {
-    private static final String TAG = "FLICKER";
-    private final ScreenRecorder mScreenRecorder;
-    private final WindowManagerTraceMonitor mWmTraceMonitor;
-    private final LayersTraceMonitor mLayersTraceMonitor;
-    private final WindowAnimationFrameStatsMonitor mFrameStatsMonitor;
-
-    private final List<ITransitionMonitor> mAllRunsMonitors;
-    private final List<ITransitionMonitor> mPerRunMonitors;
-    private final List<Runnable> mBeforeAlls;
-    private final List<Runnable> mBefores;
-    private final List<Runnable> mTransitions;
-    private final List<Runnable> mAfters;
-    private final List<Runnable> mAfterAlls;
-
-    private final int mIterations;
-    private final String mTestTag;
-
-    @Nullable private List<TransitionResult> mResults = null;
-
-    private TransitionRunner(TransitionBuilder builder) {
-        mScreenRecorder = builder.mScreenRecorder;
-        mWmTraceMonitor = builder.mWmTraceMonitor;
-        mLayersTraceMonitor = builder.mLayersTraceMonitor;
-        mFrameStatsMonitor = builder.mFrameStatsMonitor;
-
-        mAllRunsMonitors = builder.mAllRunsMonitors;
-        mPerRunMonitors = builder.mPerRunMonitors;
-        mBeforeAlls = builder.mBeforeAlls;
-        mBefores = builder.mBefores;
-        mTransitions = builder.mTransitions;
-        mAfters = builder.mAfters;
-        mAfterAlls = builder.mAfterAlls;
-
-        mIterations = builder.mIterations;
-        mTestTag = builder.mTestTag;
-    }
-
-    public static TransitionBuilder newBuilder(String outputDir) {
-        return new TransitionBuilder(Paths.get(outputDir));
-    }
-
-    public static TransitionBuilder newBuilder() {
-        return new TransitionBuilder();
-    }
-
-    /**
-     * Runs the composed transition and calls monitors at the appropriate stages. If jank monitor is
-     * enabled, transitions with jank are skipped.
-     *
-     * @return itself
-     */
-    public TransitionRunner run() {
-        mResults = new ArrayList<>();
-        mAllRunsMonitors.forEach(ITransitionMonitor::start);
-        mBeforeAlls.forEach(Runnable::run);
-        for (int iteration = 0; iteration < mIterations; iteration++) {
-            mBefores.forEach(Runnable::run);
-            mPerRunMonitors.forEach(ITransitionMonitor::start);
-            mTransitions.forEach(Runnable::run);
-            mPerRunMonitors.forEach(ITransitionMonitor::stop);
-            mAfters.forEach(Runnable::run);
-            if (runJankFree() && mFrameStatsMonitor.jankyFramesDetected()) {
-                String msg =
-                        String.format(
-                                Locale.getDefault(),
-                                "Skipping iteration %d/%d for test %s due to jank. %s",
-                                iteration,
-                                mIterations - 1,
-                                mTestTag,
-                                mFrameStatsMonitor.toString());
-                Log.e(TAG, msg);
-                continue;
-            }
-            mResults.add(saveResult(iteration));
-        }
-        mAfterAlls.forEach(Runnable::run);
-        mAllRunsMonitors.forEach(
-                monitor -> {
-                    monitor.stop();
-                    monitor.save(mTestTag);
-                });
-        return this;
-    }
-
-    /**
-     * Returns a list of transition results.
-     *
-     * @return list of transition results.
-     */
-    public List<TransitionResult> getResults() {
-        if (mResults == null) {
-            throw new IllegalStateException("Results do not exist!");
-        }
-        return mResults;
-    }
-
-    /**
-     * Deletes all transition results that are not marked for saving.
-     *
-     * @return list of transition results.
-     */
-    public void deleteResults() {
-        if (mResults == null) {
-            return;
-        }
-        mResults.stream().filter(TransitionResult::canDelete).forEach(TransitionResult::delete);
-        mResults = null;
-    }
-
-    /**
-     * Saves monitor results to file.
-     *
-     * @return object containing paths to test artifacts
-     */
-    private TransitionResult saveResult(int iteration) {
-        Path windowTrace = null;
-        String windowTraceChecksum = "";
-        Path layerTrace = null;
-        String layerTraceChecksum = "";
-        Path screenCaptureVideo = null;
-        String screenCaptureVideoChecksum = "";
-
-        if (mPerRunMonitors.contains(mWmTraceMonitor)) {
-            windowTrace = mWmTraceMonitor.save(mTestTag, iteration);
-            windowTraceChecksum = mWmTraceMonitor.getChecksum();
-        }
-        if (mPerRunMonitors.contains(mLayersTraceMonitor)) {
-            layerTrace = mLayersTraceMonitor.save(mTestTag, iteration);
-            layerTraceChecksum = mLayersTraceMonitor.getChecksum();
-        }
-        if (mPerRunMonitors.contains(mScreenRecorder)) {
-            screenCaptureVideo = mScreenRecorder.save(mTestTag, iteration);
-            screenCaptureVideoChecksum = mScreenRecorder.getChecksum();
-        }
-        return new TransitionResult(
-                layerTrace,
-                layerTraceChecksum,
-                windowTrace,
-                windowTraceChecksum,
-                screenCaptureVideo,
-                screenCaptureVideoChecksum);
-    }
-
-    private boolean runJankFree() {
-        return mPerRunMonitors.contains(mFrameStatsMonitor);
-    }
-
-    public String getTestTag() {
-        return mTestTag;
-    }
-
-    /** Stores paths to all test artifacts. */
-    @VisibleForTesting
-    public static class TransitionResult {
-        @Nullable private final Path layersTrace;
-        private final String layersTraceChecksum;
-        @Nullable private final Path windowManagerTrace;
-        private final String windowManagerTraceChecksum;
-        @Nullable private final Path screenCaptureVideo;
-        private final String screenCaptureVideoChecksum;
-        private boolean flaggedForSaving = true;
-
-        public TransitionResult(
-                @Nullable Path layersTrace,
-                String layersTraceChecksum,
-                @Nullable Path windowManagerTrace,
-                String windowManagerTraceChecksum,
-                @Nullable Path screenCaptureVideo,
-                String screenCaptureVideoChecksum) {
-            this.layersTrace = layersTrace;
-            this.layersTraceChecksum = layersTraceChecksum;
-            this.windowManagerTrace = windowManagerTrace;
-            this.windowManagerTraceChecksum = windowManagerTraceChecksum;
-            this.screenCaptureVideo = screenCaptureVideo;
-            this.screenCaptureVideoChecksum = screenCaptureVideoChecksum;
-        }
-
-        public void flagForSaving() {
-            flaggedForSaving = true;
-        }
-
-        public boolean canDelete() {
-            return !flaggedForSaving;
-        }
-
-        public boolean layersTraceExists() {
-            return layersTrace != null && layersTrace.toFile().exists();
-        }
-
-        public byte[] getLayersTrace() {
-            try {
-                return Files.readAllBytes(this.layersTrace);
-            } catch (Exception e) {
-                throw new RuntimeException(e);
-            }
-        }
-
-        public Path getLayersTracePath() {
-            return layersTrace;
-        }
-
-        public String getLayersTraceChecksum() {
-            return layersTraceChecksum;
-        }
-
-        public boolean windowManagerTraceExists() {
-            return windowManagerTrace != null && windowManagerTrace.toFile().exists();
-        }
-
-        public byte[] getWindowManagerTrace() {
-            try {
-                return Files.readAllBytes(this.windowManagerTrace);
-            } catch (Exception e) {
-                throw new RuntimeException(e);
-            }
-        }
-
-        public Path getWindowManagerTracePath() {
-            return windowManagerTrace;
-        }
-
-        public String getWindowManagerTraceChecksum() {
-            return windowManagerTraceChecksum;
-        }
-
-        public boolean screenCaptureVideoExists() {
-            return screenCaptureVideo != null && screenCaptureVideo.toFile().exists();
-        }
-
-        public Path screenCaptureVideoPath() {
-            return screenCaptureVideo;
-        }
-
-        public String getScreenCaptureVideoChecksum() {
-            return screenCaptureVideoChecksum;
-        }
-
-        public void delete() {
-            if (layersTraceExists()) layersTrace.toFile().delete();
-            if (windowManagerTraceExists()) windowManagerTrace.toFile().delete();
-            if (screenCaptureVideoExists()) screenCaptureVideo.toFile().delete();
-        }
-    }
-
-    /** Builds a {@link TransitionRunner} instance. */
-    public static class TransitionBuilder {
-        private ScreenRecorder mScreenRecorder;
-        private WindowManagerTraceMonitor mWmTraceMonitor;
-        private LayersTraceMonitor mLayersTraceMonitor;
-        private WindowAnimationFrameStatsMonitor mFrameStatsMonitor;
-
-        private List<ITransitionMonitor> mAllRunsMonitors = new LinkedList<>();
-        private List<ITransitionMonitor> mPerRunMonitors = new LinkedList<>();
-        private List<Runnable> mBeforeAlls = new LinkedList<>();
-        private List<Runnable> mBefores = new LinkedList<>();
-        private List<Runnable> mTransitions = new LinkedList<>();
-        private List<Runnable> mAfters = new LinkedList<>();
-        private List<Runnable> mAfterAlls = new LinkedList<>();
-
-        private boolean mRunJankFree = true;
-        private boolean mCaptureWindowManagerTrace = true;
-        private boolean mCaptureLayersTrace = true;
-        private boolean mRecordEachRun = false;
-        private int mIterations = 1;
-        private String mTestTag = "";
-
-        private boolean mRecordAllRuns = false;
-
-        private TransitionBuilder(@NonNull Path outputDir) {
-            mScreenRecorder = new ScreenRecorder();
-            mWmTraceMonitor = new WindowManagerTraceMonitor(outputDir);
-            mLayersTraceMonitor = new LayersTraceMonitor(outputDir);
-            mFrameStatsMonitor =
-                    new WindowAnimationFrameStatsMonitor(
-                            InstrumentationRegistry.getInstrumentation());
-        }
-
-        public TransitionBuilder() {
-            mScreenRecorder = new ScreenRecorder();
-            mWmTraceMonitor = new WindowManagerTraceMonitor();
-            mLayersTraceMonitor = new LayersTraceMonitor();
-            mFrameStatsMonitor =
-                    new WindowAnimationFrameStatsMonitor(
-                            InstrumentationRegistry.getInstrumentation());
-        }
-
-        public TransitionRunner build() {
-            if (mCaptureWindowManagerTrace) {
-                mPerRunMonitors.add(mWmTraceMonitor);
-            }
-
-            if (mCaptureLayersTrace) {
-                mPerRunMonitors.add(mLayersTraceMonitor);
-            }
-
-            if (mRunJankFree) {
-                mPerRunMonitors.add(mFrameStatsMonitor);
-            }
-
-            if (mRecordAllRuns) {
-                mAllRunsMonitors.add(mScreenRecorder);
-            }
-
-            if (mRecordEachRun) {
-                mPerRunMonitors.add(mScreenRecorder);
-            }
-
-            return new TransitionRunner(this);
-        }
-
-        public TransitionBuilder runBeforeAll(Runnable runnable) {
-            mBeforeAlls.add(runnable);
-            return this;
-        }
-
-        public TransitionBuilder runBefore(Runnable runnable) {
-            mBefores.add(runnable);
-            return this;
-        }
-
-        public TransitionBuilder run(Runnable runnable) {
-            mTransitions.add(runnable);
-            return this;
-        }
-
-        public TransitionBuilder runAfter(Runnable runnable) {
-            mAfters.add(runnable);
-            return this;
-        }
-
-        public TransitionBuilder runAfterAll(Runnable runnable) {
-            mAfterAlls.add(runnable);
-            return this;
-        }
-
-        public TransitionBuilder repeat(int iterations) {
-            mIterations = iterations;
-            return this;
-        }
-
-        public TransitionBuilder skipWindowManagerTrace() {
-            mCaptureWindowManagerTrace = false;
-            return this;
-        }
-
-        public TransitionBuilder skipLayersTrace() {
-            mCaptureLayersTrace = false;
-            return this;
-        }
-
-        public TransitionBuilder includeJankyRuns() {
-            mRunJankFree = false;
-            return this;
-        }
-
-        public TransitionBuilder recordEachRun() {
-            if (mRecordAllRuns) {
-                throw new IllegalArgumentException("Invalid option with recordAllRuns");
-            }
-            mRecordEachRun = true;
-            return this;
-        }
-
-        public TransitionBuilder recordAllRuns() {
-            if (mRecordEachRun) {
-                throw new IllegalArgumentException("Invalid option with recordEachRun");
-            }
-            mRecordAllRuns = true;
-            return this;
-        }
-
-        public TransitionBuilder withTag(String testTag) {
-            if (testTag.contains(" ")) {
-                throw new IllegalArgumentException(
-                        "The test tag can not contain spaces since it "
-                                + "is a part of the file name");
-            }
-            mTestTag = testTag;
-            return this;
-        }
-    }
-}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/TransitionRunner.kt b/libraries/flicker/src/com/android/server/wm/flicker/TransitionRunner.kt
new file mode 100644
index 0000000..70367d2
--- /dev/null
+++ b/libraries/flicker/src/com/android/server/wm/flicker/TransitionRunner.kt
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker
+
+import android.app.Instrumentation
+import android.util.Log
+import com.android.server.wm.flicker.monitor.ITransitionMonitor
+import com.android.server.wm.flicker.monitor.LayersTraceMonitor
+import com.android.server.wm.flicker.monitor.ScreenRecorder
+import com.android.server.wm.flicker.monitor.WindowAnimationFrameStatsMonitor
+import com.android.server.wm.flicker.monitor.WindowManagerTraceMonitor
+import java.nio.file.Path
+
+/**
+ * Builds and runs UI transitions capturing test artifacts.
+ *
+ *
+ * User can compose a transition from simpler steps, specifying setup and teardown steps. During
+ * a transition, Layers trace, WindowManager trace, screen recordings and window animation frame
+ * stats can be captured.
+ *
+ * <pre>
+ * Transition builder options:
+ * [TransitionBuilder.run] run transition under test. Monitors will be started
+ * before the transition and stopped after the transition is completed.
+ * [TransitionBuilder.repeat] repeat transitions under test multiple times recording
+ * result for each run.
+ * [TransitionBuilder.withTag] specify a string identifier used to prefix logs and
+ * artifacts generated.
+ * [TransitionBuilder.runBeforeAll] run setup transitions once before all other
+ * transition are run to set up an initial state on device.
+ * [TransitionBuilder.runBefore] run setup transitions before each test transition
+ * run.
+ * [TransitionBuilder.runAfter] run teardown transitions after each test
+ * transition.
+ * [TransitionBuilder.runAfter] run teardown transitions once after all
+ * other transition  are run.
+ * [TransitionBuilder.includeJankyRuns] disables [WindowAnimationFrameStatsMonitor]
+ * to monitor janky frames. If janky frames are detected, then the test run is skipped. This
+ * monitor is enabled by default.
+ * [TransitionBuilder.skipLayersTrace] disables [LayersTraceMonitor] used to
+ * capture Layers trace during a transition. This monitor is enabled by default.
+ * [TransitionBuilder.skipWindowManagerTrace] disables [WindowManagerTraceMonitor]
+ * used to capture WindowManager trace during a transition. This monitor is enabled by
+ * default.
+ * [TransitionBuilder.recordAllRuns] records the screen contents and saves it to a file.
+ * All the runs including setup and teardown transitions are included in the recording. This
+ * monitor is used for debugging purposes.
+ * [TransitionBuilder.recordEachRun] records the screen contents during test transitions
+ * and saves it to a file for each run. This monitor is used for debugging purposes.
+ *
+ * Example transition to capture WindowManager and Layers trace when opening a test app:
+ * `TransitionRunner.newBuilder()
+ * .withTag("OpenTestAppFast")
+ * .runBeforeAll(UiAutomationLib::wakeUp)
+ * .runBeforeAll(UiAutomationLib::UnlockDevice)
+ * .runBeforeAll(UiAutomationLib::openTestApp)
+ * .runBefore(UiAutomationLib::closeTestApp)
+ * .run(UiAutomationLib::openTestApp)
+ * .runAfterAll(UiAutomationLib::closeTestApp)
+ * .repeat(5)
+ * .build()
+ * .run();
+` *
+</pre> *
+ */
+class TransitionRunner private constructor(
+    private val screenRecorder: ScreenRecorder,
+    private val wmTraceMonitor: WindowManagerTraceMonitor,
+    private val layersTraceMonitor: LayersTraceMonitor,
+    private val frameStatsMonitor: WindowAnimationFrameStatsMonitor,
+    private val allRunsMonitors: List<ITransitionMonitor>,
+    private val perRunMonitors: List<ITransitionMonitor>,
+    private val beforeAlls: List<() -> Any>,
+    private val befores: List<() -> Any>,
+    private val transitions: List<() -> Any>,
+    private val afters: List<() -> Any>,
+    private val afterAlls: List<() -> Any>,
+    private val iterations: Int,
+    val testTag: String
+) {
+    private val _results = mutableListOf<TransitionResult>()
+
+    /**
+     * Returns a list of transition results.
+     *
+     * @return list of transition results.
+     */
+    val results: List<TransitionResult>
+        get() {
+            check(_results.isNotEmpty()) { "Results do not exist!" }
+            return _results
+        }
+
+    /**
+     * Runs the composed transition and calls monitors at the appropriate stages. If jank monitor is
+     * enabled, transitions with jank are skipped.
+     *
+     * @return itself
+     */
+    fun run(): TransitionRunner {
+        allRunsMonitors.forEach { it.start() }
+        beforeAlls.forEach { it.invoke() }
+        for (iteration in 0 until iterations) {
+            befores.forEach { it.invoke() }
+            perRunMonitors.forEach { it.start() }
+            transitions.forEach { it.invoke() }
+            perRunMonitors.forEach { it.stop() }
+            afters.forEach { it.invoke() }
+            if (runJankFree() && frameStatsMonitor.jankyFramesDetected()) {
+                Log.e(FLICKER_TAG, "Skipping iteration ${iteration}/${iterations - 1} " +
+                        "for test $testTag due to jank. $frameStatsMonitor")
+                continue
+            }
+            _results.add(saveResult(iteration))
+        }
+        afterAlls.forEach { it.invoke() }
+        allRunsMonitors.forEach {
+                    it.stop()
+                    it.save(testTag)
+                }
+        return this
+    }
+
+    /**
+     * Deletes all transition results that are not marked for saving.
+     *
+     * @return list of transition results.
+     */
+    fun deleteResults() {
+        if (_results.isEmpty()) {
+            return
+        }
+        _results.filter { it.canDelete() }.forEach { it.delete() }
+        _results.clear()
+    }
+
+    /**
+     * Saves monitor results to file.
+     *
+     * @return object containing paths to test artifacts
+     */
+    private fun saveResult(iteration: Int): TransitionResult {
+        var windowTrace: Path? = null
+        var windowTraceChecksum = ""
+        var layerTrace: Path? = null
+        var layerTraceChecksum = ""
+        var screenCaptureVideo: Path? = null
+        var screenCaptureVideoChecksum = ""
+
+        if (perRunMonitors.contains(wmTraceMonitor)) {
+            windowTrace = wmTraceMonitor.save(testTag, iteration)
+            windowTraceChecksum = wmTraceMonitor.checksum
+        }
+        if (perRunMonitors.contains(layersTraceMonitor)) {
+            layerTrace = layersTraceMonitor.save(testTag, iteration)
+            layerTraceChecksum = layersTraceMonitor.checksum
+        }
+        if (perRunMonitors.contains(screenRecorder)) {
+            screenCaptureVideo = screenRecorder.save(testTag, iteration)
+            screenCaptureVideoChecksum = screenRecorder.checksum
+        }
+        return TransitionResult(
+                layerTrace,
+                layerTraceChecksum,
+                windowTrace,
+                windowTraceChecksum,
+                screenCaptureVideo,
+                screenCaptureVideoChecksum)
+    }
+
+    private fun runJankFree(): Boolean {
+        return perRunMonitors.contains(frameStatsMonitor)
+    }
+
+    /** Builds a [TransitionRunner] instance.  */
+    data class TransitionBuilder @JvmOverloads constructor(
+        private val instrumentation: Instrumentation,
+        private val outputDir: Path? = null
+    ) {
+        private var runJankFree = true
+        private var captureWindowManagerTrace = true
+        private var captureLayersTrace = true
+        private var recordEachRun = false
+        private var iterations = 1
+        private var testTag = ""
+        private var recordAllRuns = false
+
+        private val allRunsMonitors= mutableListOf<ITransitionMonitor>()
+        private val perRunMonitors= mutableListOf<ITransitionMonitor>()
+        private val beforeAlls= mutableListOf<() -> Any>()
+        private val befores= mutableListOf<() -> Any>()
+        private val transitions= mutableListOf<() -> Any>()
+        private val afters= mutableListOf<() -> Any>()
+        private val afterAlls= mutableListOf<() -> Any>()
+
+        fun build(): TransitionRunner {
+            val wmTraceMonitor = WindowManagerTraceMonitor(outputDir)
+            val layersTraceMonitor = LayersTraceMonitor(outputDir)
+            val frameStatsMonitor = WindowAnimationFrameStatsMonitor(instrumentation)
+            val screenRecorder = ScreenRecorder()
+            if (captureWindowManagerTrace) {
+                perRunMonitors.add(wmTraceMonitor)
+            }
+            if (captureLayersTrace) {
+                perRunMonitors.add(layersTraceMonitor)
+            }
+            if (runJankFree) {
+                perRunMonitors.add(frameStatsMonitor)
+            }
+            if (recordAllRuns) {
+                allRunsMonitors.add(screenRecorder)
+            }
+            if (recordEachRun) {
+                perRunMonitors.add(screenRecorder)
+            }
+            return TransitionRunner(
+                    screenRecorder,
+                    wmTraceMonitor,
+                    layersTraceMonitor,
+                    frameStatsMonitor,
+                    allRunsMonitors,
+                    perRunMonitors,
+                    beforeAlls,
+                    befores,
+                    transitions,
+                    afters,
+                    afterAlls,
+                    iterations,
+                    testTag
+            )
+        }
+
+        /**
+         * Execute [runnable] before activating the winscope tracing.
+         *
+         * Repeats this execution for each iteration.
+         */
+        fun runBeforeAll(runnable: Runnable) = apply{ runBeforeAll { runnable.run() } }
+
+        /**
+         * Execute [command] before activating the winscope tracing.
+         *
+         * Repeats this execution for each iteration.
+         */
+        fun runBeforeAll(command: () -> Any) = apply { beforeAlls.add(command) }
+
+        /**
+         * Execute [runnable] before activating the winscope tracing.
+         *
+         * Repeats this execution once, irrespectively of the number of iterations
+         */
+        fun runBefore(runnable: Runnable) = apply { runBefore { runnable.run() } }
+
+        /**
+         * Execute [command] before activating the winscope tracing.
+         *
+         * Repeats this execution once, irrespectively of the number of iterations
+         */
+        fun runBefore(command: () -> Any) = apply { befores.add(command) }
+
+        /**
+         * Execute [runnable] while tracing is active.
+         */
+        fun run(runnable: Runnable) = apply { run { runnable.run() } }
+
+        /**
+         * Execute [command] while tracing is active.
+         */
+        fun run(command: () -> Any) = apply { transitions.add(command) }
+
+        /**
+         * Execute [runnable] after deactivating the winscope tracing.
+         *
+         * Repeats this execution once, irrespectively of the number of iterations
+         */
+        fun runAfter(runnable: Runnable) = apply {runAfter { runnable.run() } }
+
+        /**
+         * Execute [command] after deactivating the winscope tracing.
+         *
+         * Repeats this execution once, irrespectively of the number of iterations
+         */
+        fun runAfter(command: () -> Any) = apply { afters.add(command) }
+
+        /**
+         * Execute [runnable] after deactivating the winscope tracing.
+         *
+         * Repeats this execution for each iteration.
+         */
+        fun runAfterAll(runnable: Runnable) = apply { runAfterAll { runnable } }
+
+        /**
+         * Execute [command] after deactivating the winscope tracing.
+         *
+         * Repeats this execution for each iteration.
+         */
+        fun runAfterAll(command: () -> Any) = apply { afterAlls.add(command) }
+
+        /**
+         * Run the test [iterations] times
+         */
+        fun repeat(iterations: Int) = apply { this.iterations = iterations }
+
+        fun skipWindowManagerTrace() = apply { captureWindowManagerTrace = false }
+
+        fun skipLayersTrace() = apply { captureLayersTrace = false }
+
+        fun includeJankyRuns() = apply { runJankFree = false }
+
+        /**
+         * Record a video of each run
+         */
+        fun recordEachRun() = apply {
+            require(!recordAllRuns) { "Invalid option with recordAllRuns" }
+            recordEachRun = true
+        }
+
+        /**
+         * Record a single video for all runs
+         */
+        fun recordAllRuns() = apply {
+            require(!recordEachRun) { "Invalid option with recordEachRun" }
+            recordAllRuns = true
+        }
+
+        fun withTag(testTag: String) = apply {
+            require(!testTag.contains(" ")) {
+                "The test tag can not contain spaces since it is a part of the file name"
+            }
+            this.testTag = testTag
+        }
+    }
+}
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/WmTraceSubject.kt b/libraries/flicker/src/com/android/server/wm/flicker/WmTraceSubject.kt
index d4d5396..b31300f 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/WmTraceSubject.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/WmTraceSubject.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker
 
-import com.android.server.wm.flicker.TransitionRunner.TransitionResult
 import com.google.common.truth.FailureMetadata
 import com.google.common.truth.Subject
 import com.google.common.truth.Subject.Factory
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.kt b/libraries/flicker/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.kt
index fdb29a8..4494d04 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.kt
@@ -22,8 +22,8 @@
 
 /** Captures Layers trace from SurfaceFlinger.  */
 open class LayersTraceMonitor @JvmOverloads constructor(
-    outputDir: Path = OUTPUT_DIR
-) : TraceMonitor(outputDir, "layers_trace.pb") {
+    outputDir: Path? = OUTPUT_DIR
+) : TraceMonitor(outputDir ?: OUTPUT_DIR, "layers_trace.pb") {
     private val windowManager= WindowManagerGlobal.getWindowManagerService()
 
     override fun start() {
diff --git a/libraries/flicker/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.kt b/libraries/flicker/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.kt
index a2fb367..4d03cb3 100644
--- a/libraries/flicker/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.kt
+++ b/libraries/flicker/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.kt
@@ -22,8 +22,8 @@
 
 /** Captures WindowManager trace from WindowManager.  */
 open class WindowManagerTraceMonitor @JvmOverloads constructor(
-    outputDir: Path = OUTPUT_DIR
-) : TraceMonitor(outputDir, "wm_trace.pb") {
+    outputDir: Path? = OUTPUT_DIR
+) : TraceMonitor(outputDir ?: OUTPUT_DIR, "wm_trace.pb") {
     private val windowManager= WindowManagerGlobal.getWindowManagerService()
 
     override fun start() {
diff --git a/libraries/flicker/test/src/com/android/server/wm/flicker/TransitionRunnerTest.java b/libraries/flicker/test/src/com/android/server/wm/flicker/TransitionRunnerTest.java
index 10562d9..928b278 100644
--- a/libraries/flicker/test/src/com/android/server/wm/flicker/TransitionRunnerTest.java
+++ b/libraries/flicker/test/src/com/android/server/wm/flicker/TransitionRunnerTest.java
@@ -26,10 +26,9 @@
 
 import android.os.Environment;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.server.wm.flicker.TransitionRunner.TransitionBuilder;
-import com.android.server.wm.flicker.TransitionRunner.TransitionResult;
 import com.android.server.wm.flicker.monitor.LayersTraceMonitor;
 import com.android.server.wm.flicker.monitor.ScreenRecorder;
 import com.android.server.wm.flicker.monitor.WindowAnimationFrameStatsMonitor;
@@ -57,7 +56,8 @@
     @Mock private WindowManagerTraceMonitor mWindowManagerTraceMonitorMock;
     @Mock private LayersTraceMonitor mLayersTraceMonitorMock;
     @Mock private WindowAnimationFrameStatsMonitor mWindowAnimationFrameStatsMonitor;
-    @InjectMocks private TransitionBuilder mTransitionBuilder = TransitionRunner.newBuilder();
+    @InjectMocks private TransitionRunner.TransitionBuilder mTransitionBuilder =
+            new TransitionRunner.TransitionBuilder(InstrumentationRegistry.getInstrumentation());
 
     @Before
     public void init() {
@@ -66,7 +66,7 @@
 
     @Test
     public void transitionsRunInOrder() {
-        TransitionRunner.newBuilder()
+        mTransitionBuilder
                 .runBeforeAll(mTransitionsMock::turnOnDevice)
                 .runBefore(mTransitionsMock::openApp)
                 .run(mTransitionsMock::performMagic)
@@ -87,7 +87,7 @@
 
     @Test
     public void canCombineTransitions() {
-        TransitionRunner.newBuilder()
+        mTransitionBuilder
                 .runBeforeAll(mTransitionsMock::turnOnDevice)
                 .runBeforeAll(mTransitionsMock::turnOnDevice)
                 .runBefore(mTransitionsMock::openApp)
@@ -114,7 +114,7 @@
     @Test
     public void emptyTransitionPasses() {
         List<TransitionResult> results =
-                TransitionRunner.newBuilder()
+                mTransitionBuilder
                         .skipLayersTrace()
                         .skipWindowManagerTrace()
                         .build()
@@ -129,7 +129,7 @@
     @Test
     public void canRepeatTransitions() {
         final int wantedNumberOfInvocations = 10;
-        TransitionRunner.newBuilder()
+        mTransitionBuilder
                 .runBeforeAll(mTransitionsMock::turnOnDevice)
                 .runBefore(mTransitionsMock::openApp)
                 .run(mTransitionsMock::performMagic)