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