1 /*
2  * Copyright (C) 2023 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.activity.lifecycle;
18 
19 import static android.server.wm.StateLogger.log;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertFalse;
23 import static org.junit.Assert.assertTrue;
24 import static org.junit.Assert.fail;
25 
26 import android.app.Activity;
27 import android.util.Pair;
28 
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.List;
32 
33 /** Util class that verifies correct event and state transition sequences. */
34 public class TransitionVerifier {
35 
36     private static final Class CALLBACK_TRACKING_CLASS = CallbackTrackingActivity.class;
37     private static final Class CONFIG_CHANGE_HANDLING_CLASS =
38             LifecycleConfigChangeHandlingActivity.class;
39 
assertLaunchSequence( Class<? extends Activity> activityClass, EventLog eventLog, String... expectedSubsequentEvents)40     static void assertLaunchSequence(
41             Class<? extends Activity> activityClass,
42             EventLog eventLog,
43             String... expectedSubsequentEvents) {
44         final List<String> observedTransitions = eventLog.getActivityLog(activityClass);
45         log("Observed sequence: " + observedTransitions);
46         final String errorMessage = errorDuringTransition(activityClass, "launch");
47 
48         final List<String> launchSequence = getLaunchSequence(activityClass);
49         final List<String> expectedTransitions;
50         expectedTransitions =
51                 new ArrayList<>(launchSequence.size() + expectedSubsequentEvents.length);
52         expectedTransitions.addAll(launchSequence);
53         expectedTransitions.addAll(Arrays.asList(expectedSubsequentEvents));
54         assertEquals(errorMessage, expectedTransitions, observedTransitions);
55     }
56 
getLaunchSequence(Class<? extends Activity> activityClass)57     public static List<String> getLaunchSequence(Class<? extends Activity> activityClass) {
58         return CALLBACK_TRACKING_CLASS.isAssignableFrom(activityClass)
59                 ? Arrays.asList(
60                         LifecycleConstants.ON_CREATE,
61                         LifecycleConstants.ON_START,
62                         LifecycleConstants.ON_POST_CREATE,
63                         LifecycleConstants.ON_RESUME,
64                         LifecycleConstants.ON_TOP_POSITION_GAINED)
65                 : Arrays.asList(
66                         LifecycleConstants.ON_CREATE,
67                         LifecycleConstants.ON_START,
68                         LifecycleConstants.ON_RESUME);
69     }
70 
getLaunchAndDestroySequence(Class<? extends Activity> activityClass)71     static List<String> getLaunchAndDestroySequence(Class<? extends Activity> activityClass) {
72         final List<String> expectedTransitions = new ArrayList<>();
73         expectedTransitions.addAll(getLaunchSequence(activityClass));
74         expectedTransitions.addAll(getResumeToDestroySequence(activityClass));
75         return expectedTransitions;
76     }
77 
assertLaunchSequence( Class<? extends Activity> launchingActivity, Class<? extends Activity> existingActivity, EventLog eventLog, boolean launchingIsTranslucent)78     static void assertLaunchSequence(
79             Class<? extends Activity> launchingActivity,
80             Class<? extends Activity> existingActivity,
81             EventLog eventLog,
82             boolean launchingIsTranslucent) {
83         final boolean includingCallbacks;
84         if (CALLBACK_TRACKING_CLASS.isAssignableFrom(launchingActivity)
85                 && CALLBACK_TRACKING_CLASS.isAssignableFrom(existingActivity)) {
86             includingCallbacks = true;
87         } else if (!CALLBACK_TRACKING_CLASS.isAssignableFrom(launchingActivity)
88                 && !CALLBACK_TRACKING_CLASS.isAssignableFrom(existingActivity)) {
89             includingCallbacks = false;
90         } else {
91             throw new IllegalArgumentException(
92                     "Mixed types of callback tracking not supported. "
93                             + "Both activities must support or not support callback tracking "
94                             + "simultaneously");
95         }
96 
97         final List<Pair<String, String>> observedTransitions = eventLog.getLog();
98         log("Observed sequence: " + observedTransitions);
99         final String errorMessage = errorDuringTransition(launchingActivity, "launch");
100 
101         final List<Pair<String, String>> expectedTransitions = new ArrayList<>();
102         // First top position will be lost
103         if (includingCallbacks) {
104             expectedTransitions.add(
105                     transition(existingActivity, LifecycleConstants.ON_TOP_POSITION_LOST));
106         }
107         // Next the existing activity is paused and the next one is launched
108         expectedTransitions.add(transition(existingActivity, LifecycleConstants.ON_PAUSE));
109         expectedTransitions.add(transition(launchingActivity, LifecycleConstants.ON_CREATE));
110         expectedTransitions.add(transition(launchingActivity, LifecycleConstants.ON_START));
111         if (includingCallbacks) {
112             expectedTransitions.add(
113                     transition(launchingActivity, LifecycleConstants.ON_POST_CREATE));
114         }
115         expectedTransitions.add(transition(launchingActivity, LifecycleConstants.ON_RESUME));
116         if (includingCallbacks) {
117             expectedTransitions.add(
118                     transition(launchingActivity, LifecycleConstants.ON_TOP_POSITION_GAINED));
119         }
120         if (!launchingIsTranslucent) {
121             expectedTransitions.add(transition(existingActivity, LifecycleConstants.ON_STOP));
122         }
123 
124         assertEquals(errorMessage, expectedTransitions, observedTransitions);
125     }
126 
assertLaunchAndStopSequence( Class<? extends Activity> activityClass, EventLog eventLog)127     static void assertLaunchAndStopSequence(
128             Class<? extends Activity> activityClass, EventLog eventLog) {
129         assertLaunchAndStopSequence(activityClass, eventLog, false /* onTop */);
130     }
131 
assertLaunchAndStopSequence( Class<? extends Activity> activityClass, EventLog eventLog, boolean onTop)132     static void assertLaunchAndStopSequence(
133             Class<? extends Activity> activityClass, EventLog eventLog, boolean onTop) {
134         final List<String> observedTransitions = eventLog.getActivityLog(activityClass);
135         log("Observed sequence: " + observedTransitions);
136         final String errorMessage = errorDuringTransition(activityClass, "launch and stop");
137 
138         final boolean includeCallbacks = CALLBACK_TRACKING_CLASS.isAssignableFrom(activityClass);
139 
140         final List<String> expectedTransitions =
141                 new ArrayList<>(
142                         Arrays.asList(LifecycleConstants.ON_CREATE, LifecycleConstants.ON_START));
143         if (includeCallbacks) {
144             expectedTransitions.add(LifecycleConstants.ON_POST_CREATE);
145         }
146         expectedTransitions.add(LifecycleConstants.ON_RESUME);
147         if (includeCallbacks && onTop) {
148             expectedTransitions.addAll(
149                     Arrays.asList(
150                             LifecycleConstants.ON_TOP_POSITION_GAINED,
151                             LifecycleConstants.ON_TOP_POSITION_LOST));
152         }
153         expectedTransitions.addAll(
154                 Arrays.asList(LifecycleConstants.ON_PAUSE, LifecycleConstants.ON_STOP));
155         assertEquals(errorMessage, expectedTransitions, observedTransitions);
156     }
157 
assertLaunchAndPauseSequence( Class<? extends Activity> activityClass, EventLog eventLog)158     static void assertLaunchAndPauseSequence(
159             Class<? extends Activity> activityClass, EventLog eventLog) {
160         final List<String> observedTransitions = eventLog.getActivityLog(activityClass);
161         log("Observed sequence: " + observedTransitions);
162         final String errorMessage = errorDuringTransition(activityClass, "launch and pause");
163 
164         final List<String> expectedTransitions =
165                 Arrays.asList(
166                         LifecycleConstants.ON_CREATE,
167                         LifecycleConstants.ON_START,
168                         LifecycleConstants.ON_RESUME,
169                         LifecycleConstants.ON_PAUSE);
170         assertEquals(errorMessage, expectedTransitions, observedTransitions);
171     }
172 
assertRestartSequence(Class<? extends Activity> activityClass, EventLog eventLog)173     static void assertRestartSequence(Class<? extends Activity> activityClass, EventLog eventLog) {
174         final List<String> observedTransitions = eventLog.getActivityLog(activityClass);
175         log("Observed sequence: " + observedTransitions);
176         final String errorMessage = errorDuringTransition(activityClass, "restart");
177 
178         final List<String> expectedTransitions =
179                 Arrays.asList(LifecycleConstants.ON_RESTART, LifecycleConstants.ON_START);
180         assertEquals(errorMessage, expectedTransitions, observedTransitions);
181     }
182 
assertRestartAndResumeSequence( Class<? extends Activity> activityClass, EventLog eventLog)183     static void assertRestartAndResumeSequence(
184             Class<? extends Activity> activityClass, EventLog eventLog) {
185         final List<String> observedTransitions = eventLog.getActivityLog(activityClass);
186         log("Observed sequence: " + observedTransitions);
187         final String errorMessage = errorDuringTransition(activityClass, "restart and pause");
188 
189         final List<String> expectedTransitions =
190                 Arrays.asList(
191                         LifecycleConstants.ON_RESTART,
192                         LifecycleConstants.ON_START,
193                         LifecycleConstants.ON_RESUME);
194         assertEquals(errorMessage, expectedTransitions, observedTransitions);
195     }
196 
197     /**
198      * TODO(b/192274045): In Automotive, we tolerate superfluous lifecycle events between the first
199      * lifecycle events and the last one until any discrepancy between ActivityManager and Keyguard
200      * state is resolved.
201      */
assertRestartAndResumeSubSequence( Class<? extends Activity> activityClass, EventLog eventLog)202     static void assertRestartAndResumeSubSequence(
203             Class<? extends Activity> activityClass, EventLog eventLog) {
204         final List<String> observedTransitions = eventLog.getActivityLog(activityClass);
205         log("Observed sequence: " + observedTransitions);
206 
207         final List<Pair<String, String>> expectedTransitions =
208                 Arrays.asList(
209                         transition(activityClass, LifecycleConstants.ON_RESTART),
210                         transition(activityClass, LifecycleConstants.ON_START),
211                         transition(activityClass, LifecycleConstants.ON_RESUME));
212 
213         assertOrder(eventLog, expectedTransitions, "restart and resume");
214     }
215 
assertRecreateAndResumeSequence( Class<? extends Activity> activityClass, EventLog eventLog)216     static void assertRecreateAndResumeSequence(
217             Class<? extends Activity> activityClass, EventLog eventLog) {
218         final List<String> observedTransitions = eventLog.getActivityLog(activityClass);
219         log("Observed sequence: " + observedTransitions);
220         final String errorMessage = errorDuringTransition(activityClass, "recreateA  and pause");
221 
222         final List<String> expectedTransitions =
223                 Arrays.asList(
224                         LifecycleConstants.ON_DESTROY,
225                         LifecycleConstants.ON_CREATE,
226                         LifecycleConstants.ON_START,
227                         LifecycleConstants.ON_RESUME);
228         assertEquals(errorMessage, expectedTransitions, observedTransitions);
229     }
230 
assertLaunchAndDestroySequence( Class<? extends Activity> activityClass, EventLog eventLog)231     static void assertLaunchAndDestroySequence(
232             Class<? extends Activity> activityClass, EventLog eventLog) {
233         final List<String> observedTransitions = eventLog.getActivityLog(activityClass);
234         log("Observed sequence: " + observedTransitions);
235         final String errorMessage = errorDuringTransition(activityClass, "launch and destroy");
236 
237         final List<String> expectedTransitions =
238                 Arrays.asList(
239                         LifecycleConstants.ON_CREATE,
240                         LifecycleConstants.ON_START,
241                         LifecycleConstants.ON_RESUME,
242                         LifecycleConstants.ON_PAUSE,
243                         LifecycleConstants.ON_STOP,
244                         LifecycleConstants.ON_DESTROY);
245         assertEquals(errorMessage, expectedTransitions, observedTransitions);
246     }
247 
assertResumeToDestroySequence( Class<? extends Activity> activityClass, EventLog eventLog)248     static void assertResumeToDestroySequence(
249             Class<? extends Activity> activityClass, EventLog eventLog) {
250         final List<String> observedTransitions = eventLog.getActivityLog(activityClass);
251         log("Observed sequence: " + observedTransitions);
252         final String errorMessage = errorDuringTransition(activityClass, "launch and destroy");
253 
254         final List<String> expectedTransitions = getResumeToDestroySequence(activityClass);
255         assertEquals(errorMessage, expectedTransitions, observedTransitions);
256     }
257 
getResumeToDestroySequence(Class<? extends Activity> activityClass)258     static List<String> getResumeToDestroySequence(Class<? extends Activity> activityClass) {
259         return CALLBACK_TRACKING_CLASS.isAssignableFrom(activityClass)
260                 ? Arrays.asList(
261                         LifecycleConstants.ON_TOP_POSITION_LOST,
262                         LifecycleConstants.ON_PAUSE,
263                         LifecycleConstants.ON_STOP,
264                         LifecycleConstants.ON_DESTROY)
265                 : Arrays.asList(
266                         LifecycleConstants.ON_PAUSE,
267                         LifecycleConstants.ON_STOP,
268                         LifecycleConstants.ON_DESTROY);
269     }
270 
assertResumeToStopSequence( Class<? extends Activity> activityClass, EventLog eventLog)271     static void assertResumeToStopSequence(
272             Class<? extends Activity> activityClass, EventLog eventLog) {
273         final List<String> observedTransitions = eventLog.getActivityLog(activityClass);
274         log("Observed sequence: " + observedTransitions);
275         final String errorMessage = errorDuringTransition(activityClass, "resumed to stopped");
276         final boolean includeCallbacks = CALLBACK_TRACKING_CLASS.isAssignableFrom(activityClass);
277 
278         final List<String> expectedTransitions = new ArrayList<>();
279         if (includeCallbacks) {
280             expectedTransitions.add(LifecycleConstants.ON_TOP_POSITION_LOST);
281         }
282         expectedTransitions.add(LifecycleConstants.ON_PAUSE);
283         expectedTransitions.add(LifecycleConstants.ON_STOP);
284 
285         assertEquals(errorMessage, expectedTransitions, observedTransitions);
286     }
287 
assertStopToResumeSequence( Class<? extends Activity> activityClass, EventLog eventLog)288     static void assertStopToResumeSequence(
289             Class<? extends Activity> activityClass, EventLog eventLog) {
290         final List<String> observedTransitions = eventLog.getActivityLog(activityClass);
291         log("Observed sequence: " + observedTransitions);
292         final String errorMessage = errorDuringTransition(activityClass, "stopped to resumed");
293         final boolean includeCallbacks = CALLBACK_TRACKING_CLASS.isAssignableFrom(activityClass);
294 
295         final List<String> expectedTransitions =
296                 new ArrayList<>(
297                         Arrays.asList(
298                                 LifecycleConstants.ON_RESTART,
299                                 LifecycleConstants.ON_START,
300                                 LifecycleConstants.ON_RESUME));
301         if (includeCallbacks) {
302             expectedTransitions.add(LifecycleConstants.ON_TOP_POSITION_GAINED);
303         }
304 
305         assertEquals(errorMessage, expectedTransitions, observedTransitions);
306     }
307 
308     /**
309      * TODO(b/192274045): In Automotive, we tolerate superfluous lifecycle events between the first
310      * lifecycle events and the last one until any discrepancy between ActivityManager and Keyguard
311      * state is resolved.
312      */
assertStopToResumeSubSequence( Class<? extends Activity> activityClass, EventLog eventLog)313     static void assertStopToResumeSubSequence(
314             Class<? extends Activity> activityClass, EventLog eventLog) {
315         final List<String> observedTransitions = eventLog.getActivityLog(activityClass);
316         log("Observed sequence: " + observedTransitions);
317         final boolean includeCallbacks = CALLBACK_TRACKING_CLASS.isAssignableFrom(activityClass);
318 
319         final List<Pair<String, String>> expectedTransitions =
320                 new ArrayList<>(
321                         Arrays.asList(
322                                 transition(activityClass, LifecycleConstants.ON_RESTART),
323                                 transition(activityClass, LifecycleConstants.ON_START),
324                                 transition(activityClass, LifecycleConstants.ON_RESUME)));
325         if (includeCallbacks) {
326             expectedTransitions.add(
327                     transition(activityClass, LifecycleConstants.ON_TOP_POSITION_GAINED));
328         }
329 
330         assertOrder(eventLog, expectedTransitions, "stop and resume");
331     }
332 
assertRelaunchSequence( Class<? extends Activity> activityClass, EventLog eventLog, String startState)333     static void assertRelaunchSequence(
334             Class<? extends Activity> activityClass, EventLog eventLog, String startState) {
335         final List<String> expectedTransitions = getRelaunchSequence(startState);
336         assertSequence(activityClass, eventLog, expectedTransitions, "relaunch");
337     }
338 
getRelaunchSequence(String startState)339     static List<String> getRelaunchSequence(String startState) {
340         final List<String> expectedTransitions;
341         if (startState.equals(LifecycleConstants.ON_PAUSE)) {
342             expectedTransitions =
343                     Arrays.asList(
344                             LifecycleConstants.ON_STOP,
345                             LifecycleConstants.ON_DESTROY,
346                             LifecycleConstants.ON_CREATE,
347                             LifecycleConstants.ON_START,
348                             LifecycleConstants.ON_RESUME,
349                             LifecycleConstants.ON_PAUSE);
350         } else if (startState.equals(LifecycleConstants.ON_STOP)) {
351             expectedTransitions =
352                     Arrays.asList(
353                             LifecycleConstants.ON_DESTROY,
354                             LifecycleConstants.ON_CREATE,
355                             LifecycleConstants.ON_START,
356                             LifecycleConstants.ON_RESUME,
357                             LifecycleConstants.ON_PAUSE,
358                             LifecycleConstants.ON_STOP);
359         } else if (startState.equals(LifecycleConstants.ON_RESUME)) {
360             expectedTransitions =
361                     Arrays.asList(
362                             LifecycleConstants.ON_PAUSE,
363                             LifecycleConstants.ON_STOP,
364                             LifecycleConstants.ON_DESTROY,
365                             LifecycleConstants.ON_CREATE,
366                             LifecycleConstants.ON_START,
367                             LifecycleConstants.ON_RESUME);
368         } else if (startState.equals(LifecycleConstants.ON_TOP_POSITION_GAINED)) {
369             // Looks like we're tracking the callbacks here
370             expectedTransitions =
371                     Arrays.asList(
372                             LifecycleConstants.ON_TOP_POSITION_LOST,
373                             LifecycleConstants.ON_PAUSE,
374                             LifecycleConstants.ON_STOP,
375                             LifecycleConstants.ON_DESTROY,
376                             LifecycleConstants.ON_CREATE,
377                             LifecycleConstants.ON_START,
378                             LifecycleConstants.ON_POST_CREATE,
379                             LifecycleConstants.ON_RESUME,
380                             LifecycleConstants.ON_TOP_POSITION_GAINED);
381         } else {
382             throw new IllegalArgumentException("Start state not supported: " + startState);
383         }
384         return expectedTransitions;
385     }
386 
getSplitScreenTransitionSequence(Class<? extends Activity> activityClass)387     static List<String> getSplitScreenTransitionSequence(Class<? extends Activity> activityClass) {
388         // Minimized-dock is not a policy requirement and but SysUI-specific concept, so we here
389         // don't expect a trailing ON_PAUSE.
390         return CALLBACK_TRACKING_CLASS.isAssignableFrom(activityClass)
391                 ? CONFIG_CHANGE_HANDLING_CLASS.isAssignableFrom(activityClass)
392                         ? Arrays.asList(
393                                 LifecycleConstants.ON_MULTI_WINDOW_MODE_CHANGED,
394                                 LifecycleConstants.ON_TOP_POSITION_LOST)
395                         : Arrays.asList(
396                                 LifecycleConstants.ON_TOP_POSITION_LOST,
397                                 LifecycleConstants.ON_PAUSE,
398                                 LifecycleConstants.ON_STOP,
399                                 LifecycleConstants.ON_DESTROY,
400                                 LifecycleConstants.ON_CREATE,
401                                 LifecycleConstants.ON_START,
402                                 LifecycleConstants.ON_POST_CREATE,
403                                 LifecycleConstants.ON_RESUME,
404                                 LifecycleConstants.ON_TOP_POSITION_GAINED,
405                                 LifecycleConstants.ON_TOP_POSITION_LOST)
406                 : Arrays.asList(
407                         LifecycleConstants.ON_PAUSE,
408                         LifecycleConstants.ON_STOP,
409                         LifecycleConstants.ON_DESTROY,
410                         LifecycleConstants.ON_CREATE,
411                         LifecycleConstants.ON_START,
412                         LifecycleConstants.ON_RESUME);
413     }
414 
415     // TODO(b/149338177): Remove this workaround once test passes with TestTaskOrganizer not to
416     // depend on minimized dock feature which is not policy requirement, but SysUI-specific.
417     /**
418      * Returns the result of appending "leave from minimized dock" transitions to given transitions
419      * to "consume" these activity callbacks.
420      */
appendMinimizedDockTransitionTrail(List<String> transitions)421     static List<String> appendMinimizedDockTransitionTrail(List<String> transitions) {
422         final List<String> newTransitions = new ArrayList<>(transitions);
423         newTransitions.addAll(
424                 Arrays.asList(LifecycleConstants.ON_PAUSE, LifecycleConstants.ON_RESUME));
425 
426         return newTransitions;
427     }
428 
assertSequence( Class<? extends Activity> activityClass, EventLog eventLog, List<String> expectedTransitions, String transition)429     static void assertSequence(
430             Class<? extends Activity> activityClass,
431             EventLog eventLog,
432             List<String> expectedTransitions,
433             String transition) {
434         final List<String> observedTransitions = eventLog.getActivityLog(activityClass);
435         log("Observed sequence: " + observedTransitions);
436         final String errorMessage = errorDuringTransition(activityClass, transition);
437 
438         assertEquals(errorMessage, expectedTransitions, observedTransitions);
439     }
440 
441     /**
442      * Assert that the observed transitions of a particular activity happened in expected order.
443      * There may be more observed transitions than in the expected array, only their order matters.
444      *
445      * <p>Use this method when there is no need to verify the entire sequence, only that some
446      * transitions happened after another.
447      */
assertOrder( EventLog eventLog, Class<? extends Activity> activityClass, List<String> expectedTransitionsOrder, String transition)448     static void assertOrder(
449             EventLog eventLog,
450             Class<? extends Activity> activityClass,
451             List<String> expectedTransitionsOrder,
452             String transition) {
453         List<Pair<String, String>> expectedTransitions = new ArrayList<>();
454         for (String callback : expectedTransitionsOrder) {
455             expectedTransitions.add(transition(activityClass, callback));
456         }
457         assertOrder(eventLog, expectedTransitions, transition);
458     }
459 
460     /**
461      * Assert that the observed transitions happened in expected order. There may be more observed
462      * transitions than in the expected array, only their order matters.
463      *
464      * <p>Use this method when there is no need to verify the entire sequence, only that some
465      * transitions happened after another.
466      */
assertOrder( EventLog eventLog, List<Pair<String, String>> expectedTransitionsOrder, String transition)467     public static void assertOrder(
468             EventLog eventLog,
469             List<Pair<String, String>> expectedTransitionsOrder,
470             String transition) {
471         String result = checkOrderAndReturnError(eventLog, expectedTransitionsOrder, transition);
472         if (result != null) {
473             fail(result);
474         }
475     }
476 
477     /**
478      * Same as {@link #assertOrder(EventLog, List, String)}, but returns the String with error if it
479      * occurs. Otherwise returns {@code null}
480      */
checkOrderAndReturnError( EventLog eventLog, List<Pair<String, String>> expectedTransitionsOrder, String transition)481     public static String checkOrderAndReturnError(
482             EventLog eventLog,
483             List<Pair<String, String>> expectedTransitionsOrder,
484             String transition) {
485         final List<Pair<String, String>> observedTransitions = eventLog.getLog();
486         int nextObservedPosition = 0;
487         for (Pair<String, String> expectedTransition : expectedTransitionsOrder) {
488             while (nextObservedPosition < observedTransitions.size()
489                     && !observedTransitions.get(nextObservedPosition).equals(expectedTransition)) {
490                 nextObservedPosition++;
491             }
492             if (nextObservedPosition == observedTransitions.size()) {
493                 return "Transition wasn't observed in the expected position: "
494                         + expectedTransition
495                         + " during transition: "
496                         + transition;
497             }
498         }
499         return null;
500     }
501 
502     /** Assert that a transition was observer, no particular order. */
assertTransitionObserved( EventLog eventLog, Pair<String, String> expectedTransition, String transition)503     public static void assertTransitionObserved(
504             EventLog eventLog, Pair<String, String> expectedTransition, String transition) {
505         assertTrue(
506                 "Transition " + expectedTransition + " must be observed during " + transition,
507                 eventLog.getLog().contains(expectedTransition));
508     }
509 
510     /**
511      * Same as {@link #checkOrderAndReturnError(EventLog, List, String)}, but returns {@code false}
512      * if the order does not match. Otherwise returns {@code true}
513      */
checkOrder( EventLog eventLog, List<Pair<String, String>> expectedTransitionsOrder)514     public static boolean checkOrder(
515             EventLog eventLog, List<Pair<String, String>> expectedTransitionsOrder) {
516         String result = checkOrderAndReturnError(eventLog, expectedTransitionsOrder, null);
517         return result == null;
518     }
519 
520     /** Assert that a transition was not observer, no particular order. */
assertTransitionNotObserved( EventLog eventLog, Pair<String, String> expectedTransition, String transition)521     static void assertTransitionNotObserved(
522             EventLog eventLog, Pair<String, String> expectedTransition, String transition) {
523         assertFalse(
524                 "Transition " + expectedTransition + " must not be observed during " + transition,
525                 eventLog.getLog().contains(expectedTransition));
526     }
527 
assertEmptySequence( Class<? extends Activity> activityClass, EventLog eventLog, String transition)528     static void assertEmptySequence(
529             Class<? extends Activity> activityClass, EventLog eventLog, String transition) {
530         assertSequence(activityClass, eventLog, new ArrayList<>(), transition);
531     }
532 
533     /** Assert that a lifecycle sequence matches one of the possible variants. */
assertSequenceMatchesOneOf( Class<? extends Activity> activityClass, EventLog eventLog, List<List<String>> expectedTransitions, String transition)534     static void assertSequenceMatchesOneOf(
535             Class<? extends Activity> activityClass,
536             EventLog eventLog,
537             List<List<String>> expectedTransitions,
538             String transition) {
539         final List<String> observedTransitions = eventLog.getActivityLog(activityClass);
540         log("Observed sequence: " + observedTransitions);
541         final String errorMessage = errorDuringTransition(activityClass, transition);
542 
543         boolean oneOfExpectedSequencesObserved = false;
544         for (List<String> transitionVariant : expectedTransitions) {
545             if (transitionVariant.equals(observedTransitions)) {
546                 oneOfExpectedSequencesObserved = true;
547                 break;
548             }
549         }
550         assertTrue(
551                 errorMessage
552                         + "\nObserved transitions: "
553                         + observedTransitions
554                         + "\nExpected one of: "
555                         + expectedTransitions,
556                 oneOfExpectedSequencesObserved);
557     }
558 
559     /** Assert the entire sequence for all involved activities. */
assertEntireSequence( List<Pair<String, String>> expectedTransitions, EventLog eventLog, String message)560     static void assertEntireSequence(
561             List<Pair<String, String>> expectedTransitions, EventLog eventLog, String message) {
562         final List<Pair<String, String>> observedTransitions = eventLog.getLog();
563         assertEquals(message, expectedTransitions, observedTransitions);
564     }
565 
transition( Class<? extends Activity> activityClass, String state)566     public static Pair<String, String> transition(
567             Class<? extends Activity> activityClass, String state) {
568         return new Pair<>(activityClass.getCanonicalName(), state);
569     }
570 
transition(String owner, String state)571     public static Pair<String, String> transition(String owner, String state) {
572         return new Pair<>(owner, state);
573     }
574 
errorDuringTransition( Class<? extends Activity> activityClass, String transition)575     private static String errorDuringTransition(
576             Class<? extends Activity> activityClass, String transition) {
577         return "Failed verification during moving activity: "
578                 + activityClass.getCanonicalName()
579                 + " through transition: "
580                 + transition;
581     }
582 }
583