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 androidx.test.InstrumentationRegistry.getInstrumentation; 20 21 import android.content.ComponentName; 22 import android.server.wm.intent.Persistence.LaunchFromIntent; 23 import android.server.wm.intent.Persistence.LaunchIntent; 24 25 import com.google.common.collect.Lists; 26 27 import java.util.List; 28 import java.util.Optional; 29 30 /** 31 * <pre> 32 * The main API entry point for specifying intent tests. 33 * A {@code Launch} object is an immutable command object to specify sequences of intents to 34 * launch. 35 * 36 * They can be run by a {@link LaunchRunner} 37 * Most tests using this api are defined in {@link Cases}. 38 * The two test classes actually using the tests specified there are: 39 * 40 * 1. {@link IntentGenerationTests}, which runs the cases, records the results and writes them 41 * out to device storage 42 * 2. {@link IntentTests}, which reads the recorded test files and verifies them again. 43 * 44 * 45 * Features supported by this API are: 46 * 1. Starting activities normally or for result. 47 * {@code 48 * LaunchSequence.start(intentForResult(RegularActivity.class)) // starting an intent for result. 49 * .append(intent(RegularActivity.class)); // starting an intent normally. 50 * } 51 * 2. Specifying Intent Flags 52 * {@code 53 * LaunchSequence.start(intent(RegularActivity.class).withFlags(Cases.NEW_TASK)); 54 * } 55 * 56 * 3. Launching an intent from any point earlier in the launch sequence. 57 * {@code 58 * LaunchIntent multipleTask = intent(RegularActivity.class) 59 * .withFlags(Cases.NEW_TASK,Cases.MULTIPLE_TASK); 60 * LaunchSequence firstTask = LaunchSequence.start(multipleTask); 61 * firstTask.append(multipleTask).append(intent(SingleTopActivity.class), firstTask); 62 * } 63 * 64 * The above will result in: the first task having two activities in it and the second task having 65 * one activity, instead of the normal behaviour adding only one task. 66 * 67 * It is completely immutable and can therefore be reused without hesitation. 68 * Note that making a launch object doesn't start anything yet until it is ran by a 69 * {@link LaunchRunner} 70 * </pre> 71 */ 72 public interface LaunchSequence { 73 /** 74 * @return the amount of intents that will launch before this {@link LaunchSequence} object. 75 */ depth()76 int depth(); 77 78 /** 79 * Extract all the information that has been built up in this {@link LaunchSequence} object, so 80 * that {@link LaunchRunner} can run the described sequence of intents. 81 */ fold()82 LaunchSequenceExecutionInfo fold(); 83 84 /** 85 * @return the {@link LaunchIntent} inside of this {@link LaunchSequence}. 86 */ getIntent()87 LaunchIntent getIntent(); 88 89 /** 90 * Create a {@link LaunchSequence} object this always has 91 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} set because without it we can not 92 * launch from the application context. 93 * 94 * @param intent the first intent to be Launched. 95 * @return an {@link LaunchSequence} object launching just this intent. 96 */ create(LaunchIntent intent)97 static LaunchSequence create(LaunchIntent intent) { 98 return new RootLaunch(intent.withFlags(Cases.NEW_TASK)); 99 } 100 101 /** 102 * @param intent the next intent that should be launched 103 * @return a {@link LaunchSequence} that will launch all the intents in this and then 104 * {@code intent} 105 */ append(LaunchIntent intent)106 default LaunchSequence append(LaunchIntent intent) { 107 return new ConsecutiveLaunch(this, intent, false, Optional.empty()); 108 } 109 110 /** 111 * @param intent the next intent that should be launched 112 * @param launchFrom a {@link LaunchSequence} to start the intent from, {@code launchFrom} 113 * should be a sub sequence of this. 114 * @return a {@link LaunchSequence} that will launch all the intents in this and then 115 * {@code intent} 116 */ append(LaunchIntent intent, LaunchSequence launchFrom)117 default LaunchSequence append(LaunchIntent intent, LaunchSequence launchFrom) { 118 return new ConsecutiveLaunch(this, intent, false, Optional.of(launchFrom)); 119 } 120 121 /** 122 * @param intent the intent to Launch 123 * @return a launch with the {@code intent} added to the Act stage. 124 */ act(LaunchIntent intent)125 default LaunchSequence act(LaunchIntent intent) { 126 return new ConsecutiveLaunch(this, intent, true, Optional.empty()); 127 } 128 129 /** 130 * @param intent the intent to Launch 131 * @param launchFrom a {@link LaunchSequence} to start the intent from, {@code launchFrom} 132 * should be a sub sequence of this. 133 * @return a launch with the {@code intent} added to the Act stage. 134 */ act(LaunchIntent intent, LaunchSequence launchFrom)135 default LaunchSequence act(LaunchIntent intent, LaunchSequence launchFrom) { 136 return new ConsecutiveLaunch(this, intent, true, Optional.of(launchFrom)); 137 } 138 139 /** 140 * @param activity the activity to create an intent for. 141 * @return Creates an {@link LaunchIntent} that will target the {@link android.app.activity} 142 * class passed in. see {@link LaunchIntent#withFlags} to add intent flags to the returned 143 * intent. 144 */ intent(Class<? extends android.app.Activity> activity)145 static LaunchIntent intent(Class<? extends android.app.Activity> activity) { 146 return new LaunchIntent(Lists.newArrayList(), createComponent(activity), false); 147 } 148 149 150 /** 151 * Creates an {@link LaunchIntent} that will be started with {@link 152 * android.app.Activity#startActivityForResult} 153 * 154 * @param activity the activity to create an intent for. 155 */ intentForResult(Class<? extends android.app.Activity> activity)156 static LaunchIntent intentForResult(Class<? extends android.app.Activity> activity) { 157 return new LaunchIntent(Lists.newArrayList(), createComponent(activity), true); 158 } 159 160 String packageName = getInstrumentation().getTargetContext().getPackageName(); 161 createComponent(Class<? extends android.app.Activity> activity)162 static ComponentName createComponent(Class<? extends android.app.Activity> activity) { 163 return new ComponentName(packageName, activity.getName()); 164 } 165 166 /** 167 * Allows {@link LaunchSequence} objects to form a linkedlist of intents to launch. 168 */ 169 class ConsecutiveLaunch implements LaunchSequence { 170 private final LaunchSequence mPrevious; 171 private final LaunchIntent mLaunchIntent; 172 private final boolean mAct; 173 ConsecutiveLaunch(LaunchSequence previous, LaunchIntent launchIntent, boolean act, Optional<LaunchSequence> launchFrom)174 public ConsecutiveLaunch(LaunchSequence previous, LaunchIntent launchIntent, 175 boolean act, Optional<LaunchSequence> launchFrom) { 176 mPrevious = previous; 177 mLaunchIntent = launchIntent; 178 mAct = act; 179 mLaunchFrom = launchFrom; 180 } 181 182 /** 183 * This should always be a {@link LaunchSequence} that is in mPrevious. 184 */ 185 @SuppressWarnings("OptionalUsedAsFieldOrParameterType") 186 private Optional<LaunchSequence> mLaunchFrom; 187 188 @Override depth()189 public int depth() { 190 return mPrevious.depth() + 1; 191 } 192 193 @Override fold()194 public LaunchSequenceExecutionInfo fold() { 195 LaunchSequenceExecutionInfo launchSequenceExecutionInfo = mPrevious.fold(); 196 int launchSite = mLaunchFrom.map(LaunchSequence::depth).orElse(this.depth() - 1); 197 198 LaunchFromIntent intent = new LaunchFromIntent(mLaunchIntent, launchSite); 199 if (mAct) { 200 launchSequenceExecutionInfo.acts.add(intent); 201 } else { 202 launchSequenceExecutionInfo.setup.add(intent); 203 } 204 return launchSequenceExecutionInfo; 205 } 206 207 @Override getIntent()208 public LaunchIntent getIntent() { 209 return mLaunchIntent; 210 } 211 } 212 213 /** 214 * The first intent to launch in a {@link LaunchSequence} Object. 215 */ 216 class RootLaunch implements LaunchSequence { 217 /** 218 * The intent that should be launched. 219 */ 220 private final LaunchIntent mLaunchIntent; 221 RootLaunch(LaunchIntent launchIntent)222 public RootLaunch(LaunchIntent launchIntent) { 223 mLaunchIntent = launchIntent; 224 } 225 226 @Override depth()227 public int depth() { 228 return 0; 229 } 230 231 @Override fold()232 public LaunchSequenceExecutionInfo fold() { 233 return new LaunchSequenceExecutionInfo( 234 Lists.newArrayList(new LaunchFromIntent(mLaunchIntent, -1)), 235 Lists.newArrayList()); 236 } 237 238 @Override getIntent()239 public LaunchIntent getIntent() { 240 return mLaunchIntent; 241 } 242 } 243 244 /** 245 * Information for the {@link LaunchRunner} to run the launch represented by a {@link 246 * LaunchSequence} object. The {@link LaunchSequence} object is "folded" as a list 247 * into the summary of all intents with the corresponding indexes from what context 248 * to launch them. 249 */ 250 class LaunchSequenceExecutionInfo { 251 List<LaunchFromIntent> setup; 252 List<LaunchFromIntent> acts; 253 LaunchSequenceExecutionInfo(List<LaunchFromIntent> setup, List<LaunchFromIntent> acts)254 public LaunchSequenceExecutionInfo(List<LaunchFromIntent> setup, 255 List<LaunchFromIntent> acts) { 256 this.setup = setup; 257 this.acts = acts; 258 } 259 } 260 } 261 262