• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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