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.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; 20 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; 21 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; 22 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; 23 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 24 import static android.content.Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP; 25 import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT; 26 import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED; 27 import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP; 28 import static android.server.wm.intent.LaunchSequence.intent; 29 import static android.server.wm.intent.LaunchSequence.intentForResult; 30 import static android.server.wm.intent.Persistence.flag; 31 32 import android.content.Intent; 33 import android.server.wm.intent.Activities.RegularActivity; 34 import android.server.wm.intent.Persistence.IntentFlag; 35 import android.server.wm.intent.Persistence.LaunchIntent; 36 37 import com.google.common.collect.Lists; 38 39 import java.util.HashMap; 40 import java.util.List; 41 import java.util.Map; 42 43 /** 44 * Contains all information to create and reuse intent test launches. 45 * It enumerates all the flags so they can easily be used. 46 * 47 * It also stores commonly used {@link LaunchSequence} objects for reuse. 48 */ 49 public class Cases { 50 51 public static final IntentFlag CLEAR_TASK = flag(FLAG_ACTIVITY_CLEAR_TASK, 52 "FLAG_ACTIVITY_CLEAR_TASK"); 53 public static final IntentFlag CLEAR_TOP = flag(FLAG_ACTIVITY_CLEAR_TOP, 54 "FLAG_ACTIVITY_CLEAR_TOP"); 55 private static final IntentFlag SINGLE_TOP = flag(FLAG_ACTIVITY_SINGLE_TOP, 56 "FLAG_ACTIVITY_SINGLE_TOP"); 57 public static final IntentFlag NEW_TASK = flag(FLAG_ACTIVITY_NEW_TASK, 58 "FLAG_ACTIVITY_NEW_TASK"); 59 public static final IntentFlag NEW_DOCUMENT = flag(FLAG_ACTIVITY_NEW_DOCUMENT, 60 "FLAG_ACTIVITY_NEW_DOCUMENT"); 61 private static final IntentFlag MULTIPLE_TASK = flag(FLAG_ACTIVITY_MULTIPLE_TASK, 62 "FLAG_ACTIVITY_MULTIPLE_TASK"); 63 public static final IntentFlag RESET_TASK_IF_NEEDED = flag( 64 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED, 65 "FLAG_ACTIVITY_RESET_TASK_IF_NEEDED"); 66 public static final IntentFlag PREVIOUS_IS_TOP = flag(FLAG_ACTIVITY_PREVIOUS_IS_TOP, 67 "FLAG_ACTIVITY_PREVIOUS_IS_TOP"); 68 public static final IntentFlag REORDER_TO_FRONT = flag(FLAG_ACTIVITY_REORDER_TO_FRONT, 69 "FLAG_ACTIVITY_REORDER_TO_FRONT"); 70 71 // Flag only used for parsing intents that contain no flags. 72 private static final IntentFlag NONE = flag(0, ""); 73 74 public final List<IntentFlag> flags = Lists.newArrayList( 75 CLEAR_TASK, 76 CLEAR_TOP, 77 SINGLE_TOP, 78 NEW_TASK, 79 NEW_DOCUMENT, 80 MULTIPLE_TASK, 81 RESET_TASK_IF_NEEDED, 82 PREVIOUS_IS_TOP, 83 REORDER_TO_FRONT 84 ); 85 86 // Definition of intents used across multiple test cases. 87 private final LaunchIntent mRegularIntent = intent(RegularActivity.class); 88 private final LaunchIntent mSingleTopIntent = intent(Activities.SingleTopActivity.class); 89 private final LaunchIntent mAff1Intent = intent(Activities.TaskAffinity1Activity.class); 90 private final LaunchIntent mSecondAff1Intent = intent(Activities.TaskAffinity1Activity2.class); 91 private final LaunchIntent mSingleInstanceIntent = intent( 92 Activities.SingleInstanceActivity.class); 93 private final LaunchIntent mSingleTaskIntent = intent(Activities.SingleTaskActivity.class); 94 private final LaunchIntent mRegularForResultIntent = intentForResult(RegularActivity.class); 95 96 // LaunchSequences used across multiple test cases. 97 private final LaunchSequence mRegularSequence = LaunchSequence.create(mRegularIntent); 98 99 // To show that the most recent task get's resumed by new task 100 private final LaunchSequence mTwoAffinitiesSequence = 101 LaunchSequence.create(mAff1Intent) 102 .append(mRegularIntent) 103 .append(mAff1Intent.withFlags(NEW_TASK, MULTIPLE_TASK)); 104 105 // Used to show that the task affinity is determined by the activity that started it, 106 // Not the affinity of the current root activity. 107 private final LaunchSequence mRearrangedRootSequence = LaunchSequence.create(mRegularIntent) 108 .append(mAff1Intent.withFlags(NEW_TASK)) 109 .append(mRegularIntent) 110 .append(mAff1Intent.withFlags(REORDER_TO_FRONT)); 111 112 113 private final LaunchSequence mSingleInstanceActivitySequence = 114 LaunchSequence.create(mSingleInstanceIntent); 115 116 private final LaunchSequence mSingleTaskActivitySequence = LaunchSequence.create( 117 mSingleTaskIntent); 118 119 public List<LaunchSequence> newTaskCases() { 120 LaunchIntent aff1NewTask = mAff1Intent.withFlags(NEW_TASK); 121 122 return Lists.newArrayList( 123 // 1. Single instance will start a new task even without new task set 124 mRegularSequence.act(mSingleInstanceIntent), 125 // 2. With new task it will still end up in a new task 126 mRegularSequence.act(mSingleInstanceIntent.withFlags(NEW_TASK)), 127 // 3. Starting an activity with a different affinity without new task will put it in 128 // the existing task 129 mRegularSequence.act(mAff1Intent), 130 // 4. Starting a task with a different affinity and new task will start a new task 131 mRegularSequence.act(aff1NewTask), 132 // 5. Starting the intent with new task will not add a new activity to the task 133 mRegularSequence.act(mRegularIntent.withFlags(NEW_TASK)), 134 // 6. A different activity with the same affinity will start a new activity in 135 // the sam task 136 mRegularSequence.act(mSingleTopIntent.withFlags(NEW_TASK)), 137 // 7. To show that the most recent task get's resumed by new task, this can't 138 // be observed without differences in the same activity / task 139 mTwoAffinitiesSequence.act(aff1NewTask), 140 // 8. To show that new task respects the root as a single even 141 // if it is not at the bottom 142 mRearrangedRootSequence.act(mAff1Intent.withFlags(NEW_TASK)), 143 // 9. To show that new task with non root does start a new activity. 144 mRearrangedRootSequence.act(mSecondAff1Intent.withFlags(NEW_TASK)), 145 // 10. Multiple task will allow starting activities of the same affinity in 146 // different tasks 147 mRegularSequence.act(mRegularIntent.withFlags(NEW_TASK, MULTIPLE_TASK)), 148 // 11. Single instance will not start a new task even with multiple task on 149 mSingleInstanceActivitySequence.act( 150 mSingleInstanceIntent.withFlags(NEW_TASK, MULTIPLE_TASK)), 151 // 12. The same should happen for single task. 152 mSingleTaskActivitySequence.act( 153 mSingleTaskIntent.withFlags(NEW_TASK, MULTIPLE_TASK)), 154 // 13. This starts a regular in a new task 155 mSingleInstanceActivitySequence.act(mRegularIntent), 156 // 14. This adds regular in the same task 157 mSingleTaskActivitySequence.act(mRegularIntent), 158 // 15. Starting the activity for result keeps it in the same task 159 mSingleInstanceActivitySequence.act(mRegularForResultIntent), 160 // 16. Restarts the previous task with regular activity. 161 mRegularSequence.append(mSingleInstanceIntent).act(mRegularIntent) 162 ); 163 } 164 165 /** 166 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT }test cases are the same as 167 * {@link Cases#newTaskCases()}, we should check them for differences. 168 */ 169 public List<LaunchSequence> newDocumentCases() { 170 LaunchIntent aff1NewDocument = mAff1Intent.withFlags(NEW_DOCUMENT); 171 172 return Lists.newArrayList( 173 // 1. Single instance will start a new task even without new task set 174 mRegularSequence.act(mSingleInstanceIntent), 175 // 2. With new task it will still end up in a new task 176 mRegularSequence.act(mSingleInstanceIntent.withFlags(NEW_DOCUMENT)), 177 // 3. Starting an activity with a different affinity without new task will put it 178 // in the existing task 179 mRegularSequence.act(mAff1Intent), 180 // 4. With new document it will start it's own task 181 mRegularSequence.act(aff1NewDocument), 182 // 5. Starting the intent with new task will not add a new activity to the task 183 mRegularSequence.act(mRegularIntent.withFlags(NEW_DOCUMENT)), 184 // 6. Unlike the case with NEW_TASK, with new Document 185 mRegularSequence.act(mSingleTopIntent.withFlags(NEW_DOCUMENT)), 186 // 7. To show that the most recent task get's resumed by new task 187 mTwoAffinitiesSequence.act(aff1NewDocument), 188 // 8. To show that new task respects the root as a single 189 mRearrangedRootSequence.act(mAff1Intent.withFlags(NEW_DOCUMENT)), 190 // 9. New document starts a third task here, because there was no task for the 191 // document yet 192 mRearrangedRootSequence.act(mSecondAff1Intent.withFlags(NEW_DOCUMENT)), 193 // 10. Multiple task wil allow starting activities of the same affinity in different 194 // tasks 195 mRegularSequence.act(mRegularIntent.withFlags(NEW_DOCUMENT, MULTIPLE_TASK)), 196 // 11. Single instance will not start a new task even with multiple task on 197 mSingleInstanceActivitySequence 198 .act(mSingleInstanceIntent.withFlags(NEW_DOCUMENT, MULTIPLE_TASK)), 199 // 12. The same should happen for single task. 200 mSingleTaskActivitySequence.act( 201 mSingleTaskIntent.withFlags(NEW_DOCUMENT, MULTIPLE_TASK)) 202 ); 203 } 204 205 public List<LaunchSequence> clearCases() { 206 LaunchSequence doubleRegularActivity = mRegularSequence.append(mRegularIntent); 207 208 return Lists.newArrayList( 209 // 1. This will clear the bottom and end up with just one activity 210 mRegularSequence.act(mRegularIntent.withFlags(CLEAR_TOP)), 211 // 2. This will result in still two regulars 212 doubleRegularActivity.act(mRegularIntent.withFlags(CLEAR_TOP)), 213 // 3. This will result in a single regular it clears the top regular 214 // activity and then fails to start a new regular activity because it is already 215 // at the root of the task 216 doubleRegularActivity.act(mRegularIntent.withFlags(CLEAR_TOP, NEW_TASK)), 217 // 3. This will also result in two regulars, showing the first difference between 218 // new document and new task. 219 doubleRegularActivity.act(mRegularIntent.withFlags(CLEAR_TOP, NEW_DOCUMENT)), 220 // 4. This is here to show that previous is top has no effect on clear top or single 221 // top 222 doubleRegularActivity.act(mRegularIntent.withFlags(CLEAR_TOP, PREVIOUS_IS_TOP)), 223 // 5. Clear top finds the same activity in the task and clears from there 224 mRegularSequence.append(mAff1Intent).append(mRegularIntent).act( 225 mAff1Intent.withFlags(CLEAR_TOP)) 226 ); 227 } 228 229 /** 230 * Tests for {@link android.app.Activity#startActivityForResult(Intent, int)} 231 */ 232 //TODO: If b/122968776 is fixed these tests need to be updated 233 public List<LaunchSequence> forResultCases() { 234 LaunchIntent singleTopForResult = intentForResult(Activities.SingleTopActivity.class); 235 236 return Lists.newArrayList( 237 // 1. Start just a single regular activity 238 mRegularSequence.act(mRegularIntent.withFlags(SINGLE_TOP)), 239 // 2. For result will start a second activity 240 mRegularSequence.act(mRegularForResultIntent.withFlags(SINGLE_TOP)), 241 // 3. The same but for SINGLE_TOP as a launch mode 242 LaunchSequence.create(mSingleTopIntent).act(mSingleTopIntent), 243 // 4. Launch mode SINGLE_TOP when launched for result also starts a second activity. 244 LaunchSequence.create(mSingleTopIntent).act(singleTopForResult), 245 // 5. CLEAR_TOP results in a single regular activity 246 mRegularSequence.act(mRegularIntent.withFlags(CLEAR_TOP)), 247 // 6. Clear will still kill the for result 248 mRegularSequence.act(mRegularForResultIntent.withFlags(CLEAR_TOP)), 249 // 7. An activity started for result can go to a different task 250 mRegularSequence.act(mRegularForResultIntent.withFlags(NEW_TASK, MULTIPLE_TASK)), 251 // 8. Reorder to front with for result 252 mRegularSequence.append(mAff1Intent).act( 253 mRegularForResultIntent.withFlags(REORDER_TO_FRONT)), 254 // 9. Reorder can move an activity above one that it started for result 255 mRegularSequence.append(intentForResult(Activities.TaskAffinity1Activity.class)) 256 .act(mRegularIntent.withFlags(REORDER_TO_FRONT)) 257 ); 258 } 259 260 /** 261 * Reset task if needed will trigger when it is delivered with new task set 262 * and there are activities in the task that have a different affinity. 263 * 264 * @return the test cases 265 */ 266 //TODO: If b/122324373 is fixed these test need to be updated. 267 public List<LaunchSequence> resetTaskIfNeeded() { 268 // If a task with a different affinity like this get's reset 269 // it will create another task in the same stack with the now orphaned activity. 270 LaunchIntent resetRegularTask = mRegularIntent.withFlags(NEW_TASK, 271 RESET_TASK_IF_NEEDED); 272 LaunchSequence resetIfNeeded = mRegularSequence.append(mAff1Intent) 273 .act(resetRegularTask); 274 275 // If you try to reset a task with an activity that was started for result 276 // it will not move task. 277 LaunchSequence resetWontMoveResult = mRegularSequence.append( 278 intentForResult(Activities.TaskAffinity1Activity.class)) 279 .act(resetRegularTask); 280 281 // Reset will not move activities with to a task with that affinity, 282 // instead it will always create a new task in that stack. 283 LaunchSequence resetToExistingTask2 = mRegularSequence 284 .append(mAff1Intent) 285 .append(mAff1Intent.withFlags(NEW_TASK)) 286 .act(resetRegularTask); 287 288 // If a reset occurs the activities that move retain the same order 289 // in the new task as they had in the old task. 290 LaunchSequence resetOrdering = mRegularSequence 291 .append(mAff1Intent) 292 .append(mSecondAff1Intent) 293 .act(resetRegularTask); 294 295 return Lists.newArrayList(resetIfNeeded, resetWontMoveResult, resetToExistingTask2, 296 resetOrdering); 297 } 298 299 /** 300 * The human readable flags in the JSON files need to be converted back to the corresponding 301 * IntentFlag object when reading the file. This creates a map from the flags to their 302 * corresponding object. 303 * 304 * @return lookup table for the parsing of intent flags in the json files. 305 */ 306 public Map<String, IntentFlag> createFlagParsingTable() { 307 HashMap<String, IntentFlag> flags = new HashMap<>(); 308 for (IntentFlag flag : this.flags) { 309 flags.put(flag.name, flag); 310 } 311 312 flags.put(NONE.getName(), NONE); 313 return flags; 314 } 315 } 316