1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wm.flicker; 18 19 import static com.android.server.wm.flicker.monitor.ITransitionMonitor.OUTPUT_DIR; 20 21 import android.util.Log; 22 23 import androidx.annotation.NonNull; 24 import androidx.annotation.Nullable; 25 import androidx.annotation.VisibleForTesting; 26 import androidx.test.InstrumentationRegistry; 27 28 import com.android.server.wm.flicker.monitor.ITransitionMonitor; 29 import com.android.server.wm.flicker.monitor.LayersTraceMonitor; 30 import com.android.server.wm.flicker.monitor.ScreenRecorder; 31 import com.android.server.wm.flicker.monitor.WindowAnimationFrameStatsMonitor; 32 import com.android.server.wm.flicker.monitor.WindowManagerTraceMonitor; 33 34 import com.google.common.io.Files; 35 36 import java.io.IOException; 37 import java.nio.file.Path; 38 import java.nio.file.Paths; 39 import java.util.ArrayList; 40 import java.util.LinkedList; 41 import java.util.List; 42 import java.util.Locale; 43 44 /** 45 * Builds and runs UI transitions capturing test artifacts. 46 * 47 * <p>User can compose a transition from simpler steps, specifying setup and teardown steps. During 48 * a transition, Layers trace, WindowManager trace, screen recordings and window animation frame 49 * stats can be captured. 50 * 51 * <pre> 52 * Transition builder options: 53 * {@link TransitionBuilder#run(Runnable)} run transition under test. Monitors will be started 54 * before the transition and stopped after the transition is completed. 55 * {@link TransitionBuilder#repeat(int)} repeat transitions under test multiple times recording 56 * result for each run. 57 * {@link TransitionBuilder#withTag(String)} specify a string identifier used to prefix logs and 58 * artifacts generated. 59 * {@link TransitionBuilder#runBeforeAll(Runnable)} run setup transitions once before all other 60 * transition are run to set up an initial state on device. 61 * {@link TransitionBuilder#runBefore(Runnable)} run setup transitions before each test transition 62 * run. 63 * {@link TransitionBuilder#runAfter(Runnable)} run teardown transitions after each test 64 * transition. 65 * {@link TransitionBuilder#runAfter(Runnable)} run teardown transitions once after all 66 * other transition are run. 67 * {@link TransitionBuilder#includeJankyRuns()} disables {@link WindowAnimationFrameStatsMonitor} 68 * to monitor janky frames. If janky frames are detected, then the test run is skipped. This 69 * monitor is enabled by default. 70 * {@link TransitionBuilder#skipLayersTrace()} disables {@link LayersTraceMonitor} used to 71 * capture Layers trace during a transition. This monitor is enabled by default. 72 * {@link TransitionBuilder#skipWindowManagerTrace()} disables {@link WindowManagerTraceMonitor} 73 * used to capture WindowManager trace during a transition. This monitor is enabled by 74 * default. 75 * {@link TransitionBuilder#recordAllRuns()} records the screen contents and saves it to a file. 76 * All the runs including setup and teardown transitions are included in the recording. This 77 * monitor is used for debugging purposes. 78 * {@link TransitionBuilder#recordEachRun()} records the screen contents during test transitions 79 * and saves it to a file for each run. This monitor is used for debugging purposes. 80 * 81 * Example transition to capture WindowManager and Layers trace when opening a test app: 82 * {@code 83 * TransitionRunner.newBuilder() 84 * .withTag("OpenTestAppFast") 85 * .runBeforeAll(UiAutomationLib::wakeUp) 86 * .runBeforeAll(UiAutomationLib::UnlockDevice) 87 * .runBeforeAll(UiAutomationLib::openTestApp) 88 * .runBefore(UiAutomationLib::closeTestApp) 89 * .run(UiAutomationLib::openTestApp) 90 * .runAfterAll(UiAutomationLib::closeTestApp) 91 * .repeat(5) 92 * .build() 93 * .run(); 94 * } 95 * </pre> 96 */ 97 public class TransitionRunner { 98 private static final String TAG = "FLICKER"; 99 private final ScreenRecorder mScreenRecorder; 100 private final WindowManagerTraceMonitor mWmTraceMonitor; 101 private final LayersTraceMonitor mLayersTraceMonitor; 102 private final WindowAnimationFrameStatsMonitor mFrameStatsMonitor; 103 104 private final List<ITransitionMonitor> mAllRunsMonitors; 105 private final List<ITransitionMonitor> mPerRunMonitors; 106 private final List<Runnable> mBeforeAlls; 107 private final List<Runnable> mBefores; 108 private final List<Runnable> mTransitions; 109 private final List<Runnable> mAfters; 110 private final List<Runnable> mAfterAlls; 111 112 private final int mIterations; 113 private final String mTestTag; 114 115 @Nullable private List<TransitionResult> mResults = null; 116 TransitionRunner(TransitionBuilder builder)117 private TransitionRunner(TransitionBuilder builder) { 118 mScreenRecorder = builder.mScreenRecorder; 119 mWmTraceMonitor = builder.mWmTraceMonitor; 120 mLayersTraceMonitor = builder.mLayersTraceMonitor; 121 mFrameStatsMonitor = builder.mFrameStatsMonitor; 122 123 mAllRunsMonitors = builder.mAllRunsMonitors; 124 mPerRunMonitors = builder.mPerRunMonitors; 125 mBeforeAlls = builder.mBeforeAlls; 126 mBefores = builder.mBefores; 127 mTransitions = builder.mTransitions; 128 mAfters = builder.mAfters; 129 mAfterAlls = builder.mAfterAlls; 130 131 mIterations = builder.mIterations; 132 mTestTag = builder.mTestTag; 133 } 134 newBuilder(String outputDir)135 public static TransitionBuilder newBuilder(String outputDir) { 136 return new TransitionBuilder(Paths.get(outputDir)); 137 } 138 newBuilder()139 public static TransitionBuilder newBuilder() { 140 return new TransitionBuilder(); 141 } 142 143 /** 144 * Runs the composed transition and calls monitors at the appropriate stages. If jank monitor is 145 * enabled, transitions with jank are skipped. 146 * 147 * @return itself 148 */ run()149 public TransitionRunner run() { 150 mResults = new ArrayList<>(); 151 mAllRunsMonitors.forEach(ITransitionMonitor::start); 152 mBeforeAlls.forEach(Runnable::run); 153 for (int iteration = 0; iteration < mIterations; iteration++) { 154 mBefores.forEach(Runnable::run); 155 mPerRunMonitors.forEach(ITransitionMonitor::start); 156 mTransitions.forEach(Runnable::run); 157 mPerRunMonitors.forEach(ITransitionMonitor::stop); 158 mAfters.forEach(Runnable::run); 159 if (runJankFree() && mFrameStatsMonitor.jankyFramesDetected()) { 160 String msg = 161 String.format( 162 Locale.getDefault(), 163 "Skipping iteration %d/%d for test %s due to jank. %s", 164 iteration, 165 mIterations - 1, 166 mTestTag, 167 mFrameStatsMonitor.toString()); 168 Log.e(TAG, msg); 169 continue; 170 } 171 mResults.add(saveResult(iteration)); 172 } 173 mAfterAlls.forEach(Runnable::run); 174 mAllRunsMonitors.forEach( 175 monitor -> { 176 monitor.stop(); 177 monitor.save(mTestTag); 178 }); 179 return this; 180 } 181 182 /** 183 * Returns a list of transition results. 184 * 185 * @return list of transition results. 186 */ getResults()187 public List<TransitionResult> getResults() { 188 if (mResults == null) { 189 throw new IllegalStateException("Results do not exist!"); 190 } 191 return mResults; 192 } 193 194 /** 195 * Deletes all transition results that are not marked for saving. 196 * 197 * @return list of transition results. 198 */ deleteResults()199 public void deleteResults() { 200 if (mResults == null) { 201 return; 202 } 203 mResults.stream().filter(TransitionResult::canDelete).forEach(TransitionResult::delete); 204 mResults = null; 205 } 206 207 /** 208 * Saves monitor results to file. 209 * 210 * @return object containing paths to test artifacts 211 */ saveResult(int iteration)212 private TransitionResult saveResult(int iteration) { 213 Path windowTrace = null; 214 Path layerTrace = null; 215 Path screenCaptureVideo = null; 216 217 if (mPerRunMonitors.contains(mWmTraceMonitor)) { 218 windowTrace = mWmTraceMonitor.save(mTestTag, iteration); 219 } 220 if (mPerRunMonitors.contains(mLayersTraceMonitor)) { 221 layerTrace = mLayersTraceMonitor.save(mTestTag, iteration); 222 } 223 if (mPerRunMonitors.contains(mScreenRecorder)) { 224 screenCaptureVideo = mScreenRecorder.save(mTestTag, iteration); 225 } 226 return new TransitionResult(layerTrace, windowTrace, screenCaptureVideo); 227 } 228 runJankFree()229 private boolean runJankFree() { 230 return mPerRunMonitors.contains(mFrameStatsMonitor); 231 } 232 getTestTag()233 public String getTestTag() { 234 return mTestTag; 235 } 236 237 /** Stores paths to all test artifacts. */ 238 @VisibleForTesting 239 public static class TransitionResult { 240 @Nullable public final Path layersTrace; 241 @Nullable public final Path windowManagerTrace; 242 @Nullable public final Path screenCaptureVideo; 243 private boolean flaggedForSaving = true; 244 TransitionResult( @ullable Path layersTrace, @Nullable Path windowManagerTrace, @Nullable Path screenCaptureVideo)245 public TransitionResult( 246 @Nullable Path layersTrace, 247 @Nullable Path windowManagerTrace, 248 @Nullable Path screenCaptureVideo) { 249 this.layersTrace = layersTrace; 250 this.windowManagerTrace = windowManagerTrace; 251 this.screenCaptureVideo = screenCaptureVideo; 252 } 253 flagForSaving()254 public void flagForSaving() { 255 flaggedForSaving = true; 256 } 257 canDelete()258 public boolean canDelete() { 259 return !flaggedForSaving; 260 } 261 layersTraceExists()262 public boolean layersTraceExists() { 263 return layersTrace != null && layersTrace.toFile().exists(); 264 } 265 getLayersTrace()266 public byte[] getLayersTrace() { 267 try { 268 return Files.toByteArray(this.layersTrace.toFile()); 269 } catch (IOException e) { 270 throw new RuntimeException(e); 271 } 272 } 273 getLayersTracePath()274 public Path getLayersTracePath() { 275 return layersTrace; 276 } 277 windowManagerTraceExists()278 public boolean windowManagerTraceExists() { 279 return windowManagerTrace != null && windowManagerTrace.toFile().exists(); 280 } 281 getWindowManagerTrace()282 public byte[] getWindowManagerTrace() { 283 try { 284 return Files.toByteArray(this.windowManagerTrace.toFile()); 285 } catch (IOException e) { 286 throw new RuntimeException(e); 287 } 288 } 289 getWindowManagerTracePath()290 public Path getWindowManagerTracePath() { 291 return windowManagerTrace; 292 } 293 screenCaptureVideoExists()294 public boolean screenCaptureVideoExists() { 295 return screenCaptureVideo != null && screenCaptureVideo.toFile().exists(); 296 } 297 screenCaptureVideoPath()298 public Path screenCaptureVideoPath() { 299 return screenCaptureVideo; 300 } 301 delete()302 public void delete() { 303 if (layersTraceExists()) layersTrace.toFile().delete(); 304 if (windowManagerTraceExists()) windowManagerTrace.toFile().delete(); 305 if (screenCaptureVideoExists()) screenCaptureVideo.toFile().delete(); 306 } 307 } 308 309 /** Builds a {@link TransitionRunner} instance. */ 310 public static class TransitionBuilder { 311 private ScreenRecorder mScreenRecorder; 312 private WindowManagerTraceMonitor mWmTraceMonitor; 313 private LayersTraceMonitor mLayersTraceMonitor; 314 private WindowAnimationFrameStatsMonitor mFrameStatsMonitor; 315 316 private List<ITransitionMonitor> mAllRunsMonitors = new LinkedList<>(); 317 private List<ITransitionMonitor> mPerRunMonitors = new LinkedList<>(); 318 private List<Runnable> mBeforeAlls = new LinkedList<>(); 319 private List<Runnable> mBefores = new LinkedList<>(); 320 private List<Runnable> mTransitions = new LinkedList<>(); 321 private List<Runnable> mAfters = new LinkedList<>(); 322 private List<Runnable> mAfterAlls = new LinkedList<>(); 323 324 private boolean mRunJankFree = true; 325 private boolean mCaptureWindowManagerTrace = true; 326 private boolean mCaptureLayersTrace = true; 327 private boolean mRecordEachRun = false; 328 private int mIterations = 1; 329 private String mTestTag = ""; 330 331 private boolean mRecordAllRuns = false; 332 TransitionBuilder(@onNull Path outputDir)333 private TransitionBuilder(@NonNull Path outputDir) { 334 mScreenRecorder = new ScreenRecorder(); 335 mWmTraceMonitor = new WindowManagerTraceMonitor(outputDir); 336 mLayersTraceMonitor = new LayersTraceMonitor(outputDir); 337 mFrameStatsMonitor = 338 new WindowAnimationFrameStatsMonitor( 339 InstrumentationRegistry.getInstrumentation()); 340 } 341 TransitionBuilder()342 public TransitionBuilder() { 343 this(OUTPUT_DIR); 344 } 345 build()346 public TransitionRunner build() { 347 if (mCaptureWindowManagerTrace) { 348 mPerRunMonitors.add(mWmTraceMonitor); 349 } 350 351 if (mCaptureLayersTrace) { 352 mPerRunMonitors.add(mLayersTraceMonitor); 353 } 354 355 if (mRunJankFree) { 356 mPerRunMonitors.add(mFrameStatsMonitor); 357 } 358 359 if (mRecordAllRuns) { 360 mAllRunsMonitors.add(mScreenRecorder); 361 } 362 363 if (mRecordEachRun) { 364 mPerRunMonitors.add(mScreenRecorder); 365 } 366 367 return new TransitionRunner(this); 368 } 369 runBeforeAll(Runnable runnable)370 public TransitionBuilder runBeforeAll(Runnable runnable) { 371 mBeforeAlls.add(runnable); 372 return this; 373 } 374 runBefore(Runnable runnable)375 public TransitionBuilder runBefore(Runnable runnable) { 376 mBefores.add(runnable); 377 return this; 378 } 379 run(Runnable runnable)380 public TransitionBuilder run(Runnable runnable) { 381 mTransitions.add(runnable); 382 return this; 383 } 384 runAfter(Runnable runnable)385 public TransitionBuilder runAfter(Runnable runnable) { 386 mAfters.add(runnable); 387 return this; 388 } 389 runAfterAll(Runnable runnable)390 public TransitionBuilder runAfterAll(Runnable runnable) { 391 mAfterAlls.add(runnable); 392 return this; 393 } 394 repeat(int iterations)395 public TransitionBuilder repeat(int iterations) { 396 mIterations = iterations; 397 return this; 398 } 399 skipWindowManagerTrace()400 public TransitionBuilder skipWindowManagerTrace() { 401 mCaptureWindowManagerTrace = false; 402 return this; 403 } 404 skipLayersTrace()405 public TransitionBuilder skipLayersTrace() { 406 mCaptureLayersTrace = false; 407 return this; 408 } 409 includeJankyRuns()410 public TransitionBuilder includeJankyRuns() { 411 mRunJankFree = false; 412 return this; 413 } 414 recordEachRun()415 public TransitionBuilder recordEachRun() { 416 if (mRecordAllRuns) { 417 throw new IllegalArgumentException("Invalid option with recordAllRuns"); 418 } 419 mRecordEachRun = true; 420 return this; 421 } 422 recordAllRuns()423 public TransitionBuilder recordAllRuns() { 424 if (mRecordEachRun) { 425 throw new IllegalArgumentException("Invalid option with recordEachRun"); 426 } 427 mRecordAllRuns = true; 428 return this; 429 } 430 withTag(String testTag)431 public TransitionBuilder withTag(String testTag) { 432 if (testTag.contains(" ")) { 433 throw new IllegalArgumentException( 434 "The test tag can not contain spaces since it " 435 + "is a part of the file name"); 436 } 437 mTestTag = testTag; 438 return this; 439 } 440 } 441 } 442