Home | History | Annotate | Download | only in intent
      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 
     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      */
     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      */
    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 
    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      */
    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 
    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 
    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 
    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 
    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      */
    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      */
    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 
    329     private List<ActivityManagerState.ActivityStack> getBaseStacks() {
    330         ActivityAndWindowManagersState amWmState = mTestBase.getAmWmState();
    331         amWmState.computeState(new ComponentName[]{});
    332         return amWmState.getAmState().getStacks();
    333     }
    334 }
    335