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