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