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