• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 android.server.wm.intent;
18 
19 import static android.server.wm.intent.Persistence.LaunchFromIntent.prepareSerialisation;
20 import static android.server.wm.intent.StateComparisonException.assertEndStatesEqual;
21 import static android.server.wm.intent.StateComparisonException.assertInitialStateEqual;
22 
23 import static androidx.test.InstrumentationRegistry.getInstrumentation;
24 
25 import static com.google.common.collect.Iterables.getLast;
26 
27 import static org.junit.Assert.assertNotNull;
28 
29 import android.app.Activity;
30 import android.app.Instrumentation;
31 import android.content.ComponentName;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.os.SystemClock;
35 import android.server.wm.ActivityAndWindowManagersState;
36 import android.server.wm.ActivityManagerState;
37 import android.server.wm.intent.LaunchSequence.LaunchSequenceExecutionInfo;
38 import android.server.wm.intent.Persistence.GenerationIntent;
39 import android.server.wm.intent.Persistence.LaunchFromIntent;
40 import android.server.wm.intent.Persistence.StateDump;
41 import android.view.Display;
42 
43 import com.google.common.collect.Lists;
44 
45 import java.util.List;
46 
47 /**
48  * Launch runner is an interpreter for a {@link LaunchSequence} command object.
49  * It supports three main modes of operation.
50  *
51  * 1. The {@link LaunchRunner#runAndWrite} method to run a launch object and write out the
52  * resulting {@link Persistence.TestCase} to device storage
53  *
54  * 2. The {@link LaunchRunner#verify} method to rerun a previously recorded
55  * {@link Persistence.TestCase} and verify that the recorded states match the states resulting from
56  * the rerun.
57  *
58  * 3. The {@link LaunchRunner#run} method to run a launch object and return an {@link LaunchRecord}
59  * that can be used to do assertions directly in the same test.
60  */
61 public class LaunchRunner {
62     private static final int ACTIVITY_LAUNCH_TIMEOUT = 10000;
63     private static final int BEFORE_DUMP_TIMEOUT = 3000;
64 
65     /**
66      * Used for the waiting utilities.
67      */
68     private IntentTestBase mTestBase;
69 
70     /**
71      * The activities that were already present in the system when the test started.
72      * So they can be removed form the outputs, otherwise our tests would be system dependent.
73      */
74     private List<ActivityManagerState.ActivityStack> mBaseStacks;
75 
LaunchRunner(IntentTestBase testBase)76     public LaunchRunner(IntentTestBase testBase) {
77         mTestBase = testBase;
78         mBaseStacks = getBaseStacks();
79     }
80 
81     /**
82      * Re-run a previously recorded {@link Persistence.TestCase} and verify that the recorded
83      * states match the states resulting from the rerun.
84      *
85      * @param initialContext the context to launch the first Activity from.
86      * @param testCase       the {@link Persistence.TestCase} we are verifying.
87      */
verify(Context initialContext, Persistence.TestCase testCase)88     void verify(Context initialContext, Persistence.TestCase testCase) {
89         List<GenerationIntent> initialState = testCase.getSetup().getInitialIntents();
90         List<GenerationIntent> act = testCase.getSetup().getAct();
91 
92         List<Activity> activityLog = Lists.newArrayList();
93 
94         // Launch the first activity from the start context
95         GenerationIntent firstIntent = initialState.get(0);
96         activityLog.add(launchFromContext(initialContext, firstIntent.getActualIntent()));
97 
98         // launch the rest from the initial intents
99         for (int i = 1; i < initialState.size(); i++) {
100             GenerationIntent generationIntent = initialState.get(i);
101             Activity activityToLaunchFrom = activityLog.get(generationIntent.getLaunchFromIndex(i));
102             Activity result = launch(activityToLaunchFrom, generationIntent.getActualIntent(),
103                     generationIntent.startForResult());
104             activityLog.add(result);
105         }
106 
107         // assert that the state after setup is the same this time as the recorded state.
108         StateDump setupStateDump = waitDumpAndTrimForVerification(getLast(activityLog),
109                 testCase.getEndState());
110         assertInitialStateEqual(testCase.getInitialState(), setupStateDump);
111 
112         // apply all the intents in the act stage
113         for (int i = 0; i < act.size(); i++) {
114             GenerationIntent generationIntent = act.get(i);
115             Activity activityToLaunchFrom = activityLog.get(
116                     generationIntent.getLaunchFromIndex(initialState.size() + i));
117             Activity result = launch(activityToLaunchFrom, generationIntent.getActualIntent(),
118                     generationIntent.startForResult());
119             activityLog.add(result);
120         }
121 
122         // assert that the endStates are the same.
123         StateDump endStateDump = waitDumpAndTrimForVerification(getLast(activityLog),
124                 testCase.getEndState());
125         assertEndStatesEqual(testCase.getEndState(), endStateDump);
126     }
127 
128     /**
129      * Runs a launch object and writes out the resulting {@link Persistence.TestCase} to
130      * device storage
131      *
132      * @param startContext the context to launch the first Activity from.
133      * @param name         the name of the directory to store the json files in.
134      * @param launches     a list of launches to run and record.
135      */
runAndWrite(Context startContext, String name, List<LaunchSequence> launches)136     public void runAndWrite(Context startContext, String name, List<LaunchSequence> launches)
137             throws Exception {
138         for (int i = 0; i < launches.size(); i++) {
139             Persistence.TestCase testCase = this.runAndSerialize(launches.get(i), startContext,
140                     Integer.toString(i));
141             IntentTests.writeToDocumentsStorage(testCase, i + 1, name);
142             // Cleanup all the activities of this testCase before going to the next
143             // to preserve isolation across test cases.
144             mTestBase.cleanUp(testCase.getSetup().componentsInCase());
145         }
146     }
147 
runAndSerialize(LaunchSequence launchSequence, Context startContext, String name)148     private Persistence.TestCase runAndSerialize(LaunchSequence launchSequence,
149             Context startContext, String name) {
150         LaunchRecord launchRecord = run(launchSequence, startContext);
151 
152         LaunchSequenceExecutionInfo executionInfo = launchSequence.fold();
153         List<GenerationIntent> setupIntents = prepareSerialisation(executionInfo.setup);
154         List<GenerationIntent> actIntents = prepareSerialisation(executionInfo.acts,
155                 setupIntents.size());
156 
157         Persistence.Setup setup = new Persistence.Setup(setupIntents, actIntents);
158 
159         return new Persistence.TestCase(setup, launchRecord.initialDump, launchRecord.endDump,
160                 name);
161     }
162 
163     /**
164      * Runs a launch object and returns a {@link LaunchRecord} that can be used to do assertions
165      * directly in the same test.
166      *
167      * @param launch       the {@link LaunchSequence}we want to run
168      * @param startContext the {@link android.content.Context} to launch the first Activity from.
169      * @return {@link LaunchRecord} that can be used to do assertions.
170      */
run(LaunchSequence launch, Context startContext)171     LaunchRecord run(LaunchSequence launch, Context startContext) {
172         LaunchSequence.LaunchSequenceExecutionInfo work = launch.fold();
173         List<Activity> activityLog = Lists.newArrayList();
174 
175         if (work.setup.isEmpty() || work.acts.isEmpty()) {
176             throw new IllegalArgumentException("no intents to start");
177         }
178 
179         // Launch the first activity from the start context.
180         LaunchFromIntent firstIntent = work.setup.get(0);
181         Activity firstActivity = this.launchFromContext(startContext,
182                 firstIntent.getActualIntent());
183 
184         activityLog.add(firstActivity);
185 
186         // launch the rest from the initial intents.
187         for (int i = 1; i < work.setup.size(); i++) {
188             LaunchFromIntent launchFromIntent = work.setup.get(i);
189             Intent actualIntent = launchFromIntent.getActualIntent();
190             Activity activity = launch(activityLog.get(launchFromIntent.getLaunchFrom()),
191                     actualIntent, launchFromIntent.startForResult());
192             activityLog.add(activity);
193         }
194 
195         // record the state after the initial intents.
196         StateDump initialDump = waitDumpAndTrim(getLast(activityLog));
197 
198         // apply all the intents in the act stage
199         for (LaunchFromIntent launchFromIntent : work.acts) {
200             Intent actualIntent = launchFromIntent.getActualIntent();
201             Activity activity = launch(activityLog.get(launchFromIntent.getLaunchFrom()),
202                     actualIntent, launchFromIntent.startForResult());
203 
204             activityLog.add(activity);
205         }
206 
207         //record the end state after all intents are launched.
208         StateDump endDump = waitDumpAndTrim(getLast(activityLog));
209 
210         return new LaunchRecord(initialDump, endDump, activityLog);
211     }
212 
213     /**
214      * Results from the running of an {@link LaunchSequence} so the user can assert on the results
215      * directly.
216      */
217     class LaunchRecord {
218 
219         /**
220          * The end state after the setup intents.
221          */
222         public final StateDump initialDump;
223 
224         /**
225          * The end state after the setup and act intents.
226          */
227         public final StateDump endDump;
228 
229         /**
230          * The activities that were started by every intent in the {@link LaunchSequence}.
231          */
232         public final List<Activity> mActivitiesLog;
233 
LaunchRecord(StateDump initialDump, StateDump endDump, List<Activity> activitiesLog)234         public LaunchRecord(StateDump initialDump, StateDump endDump,
235                 List<Activity> activitiesLog) {
236             this.initialDump = initialDump;
237             this.endDump = endDump;
238             mActivitiesLog = activitiesLog;
239         }
240     }
241 
242 
launchFromContext(Context context, Intent intent)243     public Activity launchFromContext(Context context, Intent intent) {
244         Instrumentation.ActivityMonitor monitor = getInstrumentation()
245                 .addMonitor((String) null, null, false);
246 
247         context.startActivity(intent);
248         Activity activity = monitor.waitForActivityWithTimeout(ACTIVITY_LAUNCH_TIMEOUT);
249         waitAndAssertActivityLaunched(activity, intent);
250 
251         return activity;
252     }
253 
launch(Activity activityContext, Intent intent, boolean startForResult)254     public Activity launch(Activity activityContext, Intent intent, boolean startForResult) {
255         Instrumentation.ActivityMonitor monitor = getInstrumentation()
256                 .addMonitor((String) null, null, false);
257 
258         if (startForResult) {
259             activityContext.startActivityForResult(intent, 1);
260         } else {
261             activityContext.startActivity(intent);
262         }
263         Activity activity = monitor.waitForActivityWithTimeout(ACTIVITY_LAUNCH_TIMEOUT);
264 
265         if (activity == null) {
266             return activityContext;
267         } else {
268             waitAndAssertActivityLaunched(activity, intent);
269         }
270 
271         return activity;
272     }
273 
waitAndAssertActivityLaunched(Activity activity, Intent intent)274     private void waitAndAssertActivityLaunched(Activity activity, Intent intent) {
275         assertNotNull("Intent: " + intent.toString(), activity);
276 
277         final ComponentName testActivityName = activity.getComponentName();
278         mTestBase.waitAndAssertTopResumedActivity(testActivityName,
279                 Display.DEFAULT_DISPLAY, "Activity must be resumed");
280     }
281 
282     /**
283      * After the last activity has been launched we wait for a valid state + an extra three seconds
284      * so have a stable state of the system. Also all previously known stacks in
285      * {@link LaunchRunner#mBaseStacks} is excluded from the output.
286      *
287      * @param activity The last activity to be launched before dumping the state.
288      * @return A stable {@link StateDump}, meaning no more {@link android.app.Activity} is in a
289      * life cycle transition.
290      */
waitDumpAndTrim(Activity activity)291     public StateDump waitDumpAndTrim(Activity activity) {
292         mTestBase.getAmWmState().waitForValidState(activity.getComponentName());
293         // The last activity that was launched before the dump could still be in an intermediate
294         // lifecycle state. wait an extra 3 seconds for it to settle
295         SystemClock.sleep(BEFORE_DUMP_TIMEOUT);
296         mTestBase.getAmWmState().computeState(activity.getComponentName());
297         List<ActivityManagerState.ActivityStack> endStateStacks =
298                 mTestBase.getAmWmState().getAmState().getStacks();
299         return StateDump.fromStacks(endStateStacks, mBaseStacks);
300     }
301 
302     /**
303      * Like {@link LaunchRunner#waitDumpAndTrim(Activity)} but also waits until the state becomes
304      * equal to the state we expect. It is therefore only used when verifying a recorded testcase.
305      *
306      * If we take a dump of an unstable state we allow it to settle into the expected state.
307      *
308      * @param activity The last activity to be launched before dumping the state.
309      * @param expected The state that was previously recorded for this testCase.
310      * @return A stable {@link StateDump}, meaning no more {@link android.app.Activity} is in a
311      * life cycle transition.
312      */
waitDumpAndTrimForVerification(Activity activity, StateDump expected)313     public StateDump waitDumpAndTrimForVerification(Activity activity, StateDump expected) {
314         mTestBase.getAmWmState().waitForValidState(activity.getComponentName());
315         // The last activity that was launched before the dump could still be in an intermediate
316         // lifecycle state. wait an extra 3 seconds for it to settle
317         SystemClock.sleep(BEFORE_DUMP_TIMEOUT);
318         mTestBase.getAmWmState().waitForWithAmState(
319                 am -> StateDump.fromStacks(am.getStacks(), mBaseStacks).equals(expected),
320                 "Wait until the activity states match up with what we recorded");
321         mTestBase.getAmWmState().computeState(activity.getComponentName());
322 
323         List<ActivityManagerState.ActivityStack> endStateStacks =
324                 mTestBase.getAmWmState().getAmState().getStacks();
325 
326         return StateDump.fromStacks(endStateStacks, mBaseStacks);
327     }
328 
getBaseStacks()329     private List<ActivityManagerState.ActivityStack> getBaseStacks() {
330         ActivityAndWindowManagersState amWmState = mTestBase.getAmWmState();
331         amWmState.computeState(new ComponentName[]{});
332         return amWmState.getAmState().getStacks();
333     }
334 }
335