1 /*
2  * Copyright (C) 2018 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.lifecycle;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
20 import static android.content.Intent.FLAG_ACTIVITY_FORWARD_RESULT;
21 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
22 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
23 import static android.server.wm.StateLogger.log;
24 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP;
25 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
26 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_CREATE;
27 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_DESTROY;
28 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED;
29 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_NEW_INTENT;
30 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_PAUSE;
31 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_POST_CREATE;
32 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESTART;
33 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
34 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_START;
35 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
36 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
37 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
38 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_USER_LEAVE_HINT;
39 
40 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
41 
42 import static org.hamcrest.Matchers.lessThan;
43 import static org.junit.Assert.fail;
44 
45 import android.app.Activity;
46 import android.app.ActivityOptions;
47 import android.app.PictureInPictureParams;
48 import android.content.ComponentName;
49 import android.content.Context;
50 import android.content.Intent;
51 import android.content.pm.ActivityInfo;
52 import android.content.res.Configuration;
53 import android.os.Bundle;
54 import android.os.Looper;
55 import android.server.wm.MultiDisplayTestBase;
56 import android.server.wm.ObjectTracker;
57 import android.server.wm.lifecycle.LifecycleLog.ActivityCallback;
58 import android.transition.Transition;
59 import android.transition.TransitionListenerAdapter;
60 import android.util.Pair;
61 
62 import android.server.wm.cts.R;
63 
64 import androidx.annotation.NonNull;
65 import androidx.test.rule.ActivityTestRule;
66 
67 import com.android.compatibility.common.util.SystemUtil;
68 
69 import org.junit.Assert;
70 import org.junit.Before;
71 
72 import java.util.ArrayList;
73 import java.util.Arrays;
74 import java.util.Collections;
75 import java.util.List;
76 import java.util.function.Consumer;
77 
78 /** Base class for device-side tests that verify correct activity lifecycle transitions. */
79 public class ActivityLifecycleClientTestBase extends MultiDisplayTestBase {
80 
81     /**
82      * Activity launch time is evaluated. It is expected to be less than 5 seconds. Otherwise, it's
83      * likely there is a timeout.
84      */
85     private static final long ACTIVITY_LAUNCH_TIMEOUT = 5 * 1000;
86 
87     static final String EXTRA_RECREATE = "recreate";
88     static final String EXTRA_FINISH_IN_ON_CREATE = "finish_in_on_create";
89     static final String EXTRA_FINISH_IN_ON_START = "finish_in_on_start";
90     static final String EXTRA_FINISH_IN_ON_RESUME = "finish_in_on_resume";
91     static final String EXTRA_FINISH_IN_ON_PAUSE = "finish_in_on_pause";
92     static final String EXTRA_FINISH_IN_ON_STOP = "finish_in_on_stop";
93     static final String EXTRA_START_ACTIVITY_IN_ON_CREATE = "start_activity_in_on_create";
94     static final String EXTRA_START_ACTIVITY_WHEN_IDLE = "start_activity_when_idle";
95     static final String EXTRA_ACTIVITY_ON_USER_LEAVE_HINT = "activity_on_user_leave_hint";
96 
97     static final ComponentName CALLBACK_TRACKING_ACTIVITY =
98             getComponentName(CallbackTrackingActivity.class);
99 
100     static final ComponentName CONFIG_CHANGE_HANDLING_ACTIVITY =
101             getComponentName(ConfigChangeHandlingActivity.class);
102 
103     final ActivityTestRule mSlowActivityTestRule = new ActivityTestRule<>(
104             SlowActivity.class, true /* initialTouchMode */, false /* launchActivity */);
105 
106     private static LifecycleLog mLifecycleLog;
107 
108     protected Context mTargetContext;
109     private LifecycleTracker mLifecycleTracker;
110 
111     @Before
112     @Override
setUp()113     public void setUp() throws Exception {
114         super.setUp();
115 
116         mTargetContext = getInstrumentation().getTargetContext();
117         // Log transitions for all activities that belong to this app.
118         mLifecycleLog = new LifecycleLog();
119         mLifecycleLog.clear();
120 
121         // Track transitions and allow waiting for pending activity states.
122         mLifecycleTracker = new LifecycleTracker(mLifecycleLog);
123     }
124 
125     /** Activity launch builder for lifecycle tests. */
126     class Launcher implements ObjectTracker.Consumable {
127         private int mFlags;
128         private ActivityCallback mExpectedState;
129         private List<String> mExtraFlags = new ArrayList<>();
130         private Consumer<Intent> mPostIntentSetup;
131         private ActivityOptions mOptions;
132         private boolean mNoInstance;
133         private final Class<? extends Activity> mActivityClass;
134         private boolean mSkipLaunchTimeCheck;
135 
136         private boolean mLaunchCalled = false;
137 
138         /**
139          * @param activityClass Class of the activity to launch.
140          */
Launcher(@onNull Class<? extends Activity> activityClass)141         Launcher(@NonNull Class<? extends Activity> activityClass) {
142             mActivityClass = activityClass;
143             mObjectTracker.track(this);
144         }
145 
146         /**
147          * Perform the activity launch. Will wait for an instance of the activity if needed and will
148          * verify the launch time.
149          */
launch()150         Activity launch() throws Exception {
151             mLaunchCalled = true;
152 
153             // Prepare the intent
154             final Intent intent = new Intent(mTargetContext, mActivityClass);
155             if (mFlags != 0) {
156                 intent.setFlags(mFlags);
157             } else {
158                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
159             }
160             for (String flag : mExtraFlags) {
161                 intent.putExtra(flag, true);
162             }
163             if (mPostIntentSetup != null) {
164                 mPostIntentSetup.accept(intent);
165             }
166             final Bundle optionsBundle = mOptions != null ? mOptions.toBundle() : null;
167 
168             // Start measuring time spent on starting the activity
169             final long startTime = System.currentTimeMillis();
170             final Activity activity = SystemUtil.callWithShellPermissionIdentity(() -> {
171                 if (mNoInstance) {
172                     mTargetContext.startActivity(intent, optionsBundle);
173                     return null;
174                 }
175                 return getInstrumentation().startActivitySync(
176                         intent, optionsBundle);
177             });
178             if (!mNoInstance && activity == null) {
179                 fail("Must have returned an instance of Activity after launch.");
180             }
181             // Wait for activity to reach the desired state and verify launch time.
182             if (mExpectedState == null) {
183                 mExpectedState = CallbackTrackingActivity.class.isAssignableFrom(mActivityClass)
184                         ? ON_TOP_POSITION_GAINED : ON_RESUME;
185             }
186             waitAndAssertActivityStates(state(mActivityClass, mExpectedState));
187             if (!mSkipLaunchTimeCheck) {
188                 Assert.assertThat(System.currentTimeMillis() - startTime,
189                         lessThan(ACTIVITY_LAUNCH_TIMEOUT));
190             }
191 
192             return activity;
193         }
194 
195         /** Set intent flags for launch. */
setFlags(int flags)196         public Launcher setFlags(int flags) {
197             mFlags = flags;
198             return this;
199         }
200 
201         /**
202          * Set the expected lifecycle state to verify. Will be inferred automatically if not set.
203          */
setExpectedState(ActivityCallback expectedState)204         public Launcher setExpectedState(ActivityCallback expectedState) {
205             mExpectedState = expectedState;
206             return this;
207         }
208 
209         /** Allow the caller to customize the intent right before starting activity. */
customizeIntent(Consumer<Intent> intentSetup)210         public Launcher customizeIntent(Consumer<Intent> intentSetup) {
211             mPostIntentSetup = intentSetup;
212             return this;
213         }
214 
215         /** Set extra flags to pass as boolean values through the intent. */
setExtraFlags(String... extraFlags)216         public Launcher setExtraFlags(String... extraFlags) {
217             mExtraFlags.addAll(Arrays.asList(extraFlags));
218             return this;
219         }
220 
221         /** Set the activity options to use for the launch. */
setOptions(ActivityOptions options)222         public Launcher setOptions(ActivityOptions options) {
223             mOptions = options;
224             return this;
225         }
226 
227         /**
228          * Indicate that no instance should be returned. Usually used for activity launches that are
229          * expected to end up in not-active state and when the synchronous instrumentation launch
230          * can timeout.
231          */
setNoInstance()232         Launcher setNoInstance() {
233             mNoInstance = true;
234             return this;
235         }
236 
237         /** Indicate that launch time verification should not be performed. */
setSkipLaunchTimeCheck()238         Launcher setSkipLaunchTimeCheck() {
239             mSkipLaunchTimeCheck = true;
240             return this;
241         }
242 
243         @Override
isConsumed()244         public boolean isConsumed() {
245             return mLaunchCalled;
246         }
247     }
248 
249     /**
250      * Launch an activity given a class. Will wait for the launch to finish and verify the launch
251      * time.
252      * @return The launched Activity instance.
253      */
254     @SuppressWarnings("unchecked")
launchActivityAndWait(Class<? extends Activity> activityClass)255     <T extends Activity> T launchActivityAndWait(Class<? extends Activity> activityClass)
256             throws Exception {
257         return (T) new Launcher(activityClass).launch();
258     }
259 
260     /**
261      * Blocking call that will wait for activities to reach expected states with timeout.
262      */
263     @SafeVarargs
waitAndAssertActivityStates( Pair<Class<? extends Activity>, ActivityCallback>.... activityCallbacks)264     final void waitAndAssertActivityStates(
265             Pair<Class<? extends Activity>, ActivityCallback>... activityCallbacks) {
266         log("Start waitAndAssertActivityCallbacks");
267         mLifecycleTracker.waitAndAssertActivityStates(activityCallbacks);
268     }
269 
270     /**
271      * Blocking call that will wait and verify that the activity transition settles with the
272      * expected state.
273      */
waitAndAssertActivityCurrentState( Class<? extends Activity> activityClass, ActivityCallback expectedState)274     final void waitAndAssertActivityCurrentState(
275             Class<? extends Activity> activityClass, ActivityCallback expectedState) {
276         log("Start waitAndAssertActivityCurrentState");
277         mLifecycleTracker.waitAndAssertActivityCurrentState(activityClass, expectedState);
278     }
279 
280     /**
281      * Blocking call that will wait for activities to perform the expected sequence of transitions.
282      * @see LifecycleTracker#waitForActivityTransitions(Class, List)
283      */
waitForActivityTransitions(Class<? extends Activity> activityClass, List<ActivityCallback> expectedTransitions)284     final void waitForActivityTransitions(Class<? extends Activity> activityClass,
285             List<ActivityCallback> expectedTransitions) {
286         log("Start waitForActivityTransitions");
287         mLifecycleTracker.waitForActivityTransitions(activityClass, expectedTransitions);
288     }
289 
290     /**
291      * Blocking call that will wait for activities to perform the expected sequence of transitions.
292      * After waiting it asserts that the sequence matches the expected.
293      * @see LifecycleTracker#waitForActivityTransitions(Class, List)
294      */
waitAndAssertActivityTransitions(Class<? extends Activity> activityClass, List<ActivityCallback> expectedTransitions, String message)295     final void waitAndAssertActivityTransitions(Class<? extends Activity> activityClass,
296             List<ActivityCallback> expectedTransitions, String message) {
297         log("Start waitAndAssertActivityTransition");
298         mLifecycleTracker.waitForActivityTransitions(activityClass, expectedTransitions);
299 
300         LifecycleVerifier.assertSequence(activityClass, getLifecycleLog(), expectedTransitions,
301                 message);
302     }
303 
getLifecycleLog()304     LifecycleLog getLifecycleLog() {
305         return mLifecycleLog;
306     }
307 
state(Activity activity, ActivityCallback stage)308     static Pair<Class<? extends Activity>, ActivityCallback> state(Activity activity,
309             ActivityCallback stage) {
310         return state(activity.getClass(), stage);
311     }
312 
state( Class<? extends Activity> activityClass, ActivityCallback stage)313     static Pair<Class<? extends Activity>, ActivityCallback> state(
314             Class<? extends Activity> activityClass, ActivityCallback stage) {
315         return new Pair<>(activityClass, stage);
316     }
317 
318     /**
319      * Returns a pair of the activity and the state it should be in based on the configuration of
320      * occludingActivity.
321      */
occludedActivityState( Activity activity, Activity occludingActivity)322     static Pair<Class<? extends Activity>, ActivityCallback> occludedActivityState(
323             Activity activity, Activity occludingActivity) {
324         return occludedActivityState(activity, isTranslucent(occludingActivity));
325     }
326 
327     /**
328      * Returns a pair of the activity and the state it should be in based on
329      * occludingActivityIsTranslucent.
330      */
occludedActivityState( Activity activity, boolean occludingActivityIsTranslucent)331     static Pair<Class<? extends Activity>, ActivityCallback> occludedActivityState(
332             Activity activity, boolean occludingActivityIsTranslucent) {
333         // Activities behind a translucent activity should be in the paused state since they are
334         // still visible. Otherwise, they should be in the stopped state.
335         return state(activity, occludedActivityState(occludingActivityIsTranslucent));
336     }
337 
occludedActivityState(boolean occludingActivityIsTranslucent)338     static ActivityCallback occludedActivityState(boolean occludingActivityIsTranslucent) {
339         return occludingActivityIsTranslucent ? ON_PAUSE : ON_STOP;
340     }
341 
342     /** Returns true if the input activity is translucent. */
isTranslucent(Activity activity)343     static boolean isTranslucent(Activity activity) {
344         return ActivityInfo.isTranslucentOrFloating(activity.getWindow().getWindowStyle());
345     }
346 
347     /** Base activity that only tracks fundamental activity lifecycle states. */
348     public static class LifecycleTrackingActivity extends Activity {
349         LifecycleLog.LifecycleLogClient mLifecycleLogClient;
350 
351         @Override
onCreate(Bundle savedInstanceState)352         protected void onCreate(Bundle savedInstanceState) {
353             super.onCreate(savedInstanceState);
354             mLifecycleLogClient = LifecycleLog.LifecycleLogClient.create(this);
355             mLifecycleLogClient.onActivityCallback(ON_CREATE);
356 
357             final Intent intent = getIntent();
358             final Intent startOnCreate =
359                     intent.getParcelableExtra(EXTRA_START_ACTIVITY_IN_ON_CREATE);
360             if (startOnCreate != null) {
361                 startActivity(startOnCreate);
362             }
363 
364             final Intent startOnIdle = intent.getParcelableExtra(EXTRA_START_ACTIVITY_WHEN_IDLE);
365             if (startOnIdle != null) {
366                 Looper.getMainLooper().getQueue().addIdleHandler(() -> {
367                     startActivity(startOnIdle);
368                     return false;
369                 });
370             }
371 
372             if (intent.getBooleanExtra(EXTRA_FINISH_IN_ON_CREATE, false)) {
373                 finish();
374             }
375         }
376 
377         @Override
onStart()378         protected void onStart() {
379             super.onStart();
380             mLifecycleLogClient.onActivityCallback(ON_START);
381 
382             if (getIntent().getBooleanExtra(EXTRA_FINISH_IN_ON_START, false)) {
383                 finish();
384             }
385         }
386 
387         @Override
onResume()388         protected void onResume() {
389             super.onResume();
390             mLifecycleLogClient.onActivityCallback(ON_RESUME);
391 
392             final Intent intent = getIntent();
393             if (intent.getBooleanExtra(EXTRA_FINISH_IN_ON_RESUME, false)) {
394                 finish();
395             }
396         }
397 
398         @Override
onPause()399         protected void onPause() {
400             super.onPause();
401             mLifecycleLogClient.onActivityCallback(ON_PAUSE);
402 
403             if (getIntent().getBooleanExtra(EXTRA_FINISH_IN_ON_PAUSE, false)) {
404                 finish();
405             }
406         }
407 
408         @Override
onStop()409         protected void onStop() {
410             super.onStop();
411             mLifecycleLogClient.onActivityCallback(ON_STOP);
412 
413             if (getIntent().getBooleanExtra(EXTRA_FINISH_IN_ON_STOP, false)) {
414                 finish();
415             }
416         }
417 
418         @Override
onDestroy()419         protected void onDestroy() {
420             super.onDestroy();
421             mLifecycleLogClient.onActivityCallback(ON_DESTROY);
422             mLifecycleLogClient.close();
423         }
424 
425         @Override
onRestart()426         protected void onRestart() {
427             super.onRestart();
428             mLifecycleLogClient.onActivityCallback(ON_RESTART);
429         }
430 
431         @Override
onUserLeaveHint()432         protected void onUserLeaveHint() {
433             super.onUserLeaveHint();
434 
435             if (getIntent().getBooleanExtra(EXTRA_ACTIVITY_ON_USER_LEAVE_HINT, false)) {
436                 mLifecycleLogClient.onActivityCallback(ON_USER_LEAVE_HINT);
437             }
438         }
439     }
440 
441     // Test activity
442     public static class FirstActivity extends LifecycleTrackingActivity {
443     }
444 
445     // Test activity
446     public static class SecondActivity extends LifecycleTrackingActivity {
447     }
448 
449     // Test activity
450     public static class ThirdActivity extends LifecycleTrackingActivity {
451     }
452 
453     // Test activity
454     public static class SideActivity extends LifecycleTrackingActivity {
455     }
456 
457     // Translucent test activity
458     public static class TranslucentActivity extends LifecycleTrackingActivity {
459     }
460 
461     // Translucent test activity
462     public static class SecondTranslucentActivity extends LifecycleTrackingActivity {
463     }
464 
465     /**
466      * Base activity that records callbacks in addition to main lifecycle transitions.
467      */
468     public static class CallbackTrackingActivity extends LifecycleTrackingActivity {
469 
470         @Override
onActivityResult(int requestCode, int resultCode, Intent data)471         protected void onActivityResult(int requestCode, int resultCode, Intent data) {
472             super.onActivityResult(requestCode, resultCode, data);
473             mLifecycleLogClient.onActivityCallback(ON_ACTIVITY_RESULT);
474         }
475 
476         @Override
onPostCreate(Bundle savedInstanceState)477         protected void onPostCreate(Bundle savedInstanceState) {
478             super.onPostCreate(savedInstanceState);
479             mLifecycleLogClient.onActivityCallback(ON_POST_CREATE);
480         }
481 
482         @Override
onNewIntent(Intent intent)483         protected void onNewIntent(Intent intent) {
484             super.onNewIntent(intent);
485             mLifecycleLogClient.onActivityCallback(ON_NEW_INTENT);
486         }
487 
488         @Override
onTopResumedActivityChanged(boolean isTopResumedActivity)489         public void onTopResumedActivityChanged(boolean isTopResumedActivity) {
490             mLifecycleLogClient.onActivityCallback(
491                     isTopResumedActivity ? ON_TOP_POSITION_GAINED : ON_TOP_POSITION_LOST);
492         }
493 
494         @Override
onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig)495         public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {
496             mLifecycleLogClient.onActivityCallback(ON_MULTI_WINDOW_MODE_CHANGED);
497         }
498     }
499 
500     // Just another callback tracking activity, nothing special.
501     public static class SecondCallbackTrackingActivity extends CallbackTrackingActivity {
502     }
503 
504     // Translucent callback tracking test activity
505     public static class TranslucentCallbackTrackingActivity extends CallbackTrackingActivity {
506     }
507 
508     // Callback tracking activity that supports being shown on top of lock screen
509     public static class ShowWhenLockedCallbackTrackingActivity extends CallbackTrackingActivity {
510         @Override
onCreate(Bundle savedInstanceState)511         protected void onCreate(Bundle savedInstanceState) {
512             super.onCreate(savedInstanceState);
513             setShowWhenLocked(true);
514         }
515     }
516 
517     /**
518      * Test activity that launches {@link TrampolineActivity} for result.
519      */
520     public static class LaunchForwardResultActivity extends CallbackTrackingActivity {
521         @Override
onCreate(Bundle savedInstanceState)522         protected void onCreate(Bundle savedInstanceState) {
523             super.onCreate(savedInstanceState);
524             final Intent intent = new Intent(this, TrampolineActivity.class);
525             startActivityForResult(intent, 1 /* requestCode */);
526         }
527     }
528 
529     public static class TrampolineActivity extends CallbackTrackingActivity {
530         @Override
onCreate(Bundle savedInstanceState)531         protected void onCreate(Bundle savedInstanceState) {
532             super.onCreate(savedInstanceState);
533             final Intent intent = new Intent(this, ResultActivity.class);
534             intent.setFlags(FLAG_ACTIVITY_FORWARD_RESULT);
535             startActivity(intent);
536             finish();
537         }
538     }
539 
540     /**
541      * Test activity that launches {@link ResultActivity} for result.
542      */
543     public static class LaunchForResultActivity extends CallbackTrackingActivity {
544         private static final String EXTRA_FORWARD_EXTRAS = "FORWARD_EXTRAS";
545         public static final String EXTRA_LAUNCH_ON_RESULT = "LAUNCH_ON_RESULT";
546         public static final String EXTRA_LAUNCH_ON_RESUME_AFTER_RESULT =
547                 "LAUNCH_ON_RESUME_AFTER_RESULT";
548         public static final String EXTRA_USE_TRANSLUCENT_RESULT =
549                 "USE_TRANSLUCENT_RESULT";
550 
551         boolean mReceivedResultOk;
552 
553         /** Adds the flag to the extra of intent which will forward to {@link ResultActivity}. */
forwardFlag(String flag)554         static Consumer<Intent> forwardFlag(String flag) {
555             return intent -> {
556                 final Bundle data = new Bundle();
557                 data.putBoolean(flag, true);
558                 intent.putExtra(EXTRA_FORWARD_EXTRAS, data);
559             };
560         }
561 
562         @Override
onCreate(Bundle savedInstanceState)563         protected void onCreate(Bundle savedInstanceState) {
564             super.onCreate(savedInstanceState);
565 
566             final Intent intent;
567             if (getIntent().hasExtra(EXTRA_USE_TRANSLUCENT_RESULT)) {
568                 intent = new Intent(this, TranslucentResultActivity.class);
569             } else {
570                 intent = new Intent(this, ResultActivity.class);
571             }
572 
573             final Bundle forwardExtras = getIntent().getBundleExtra(EXTRA_FORWARD_EXTRAS);
574             if (forwardExtras != null) {
575                 intent.putExtras(forwardExtras);
576             }
577             startActivityForResult(intent, 1 /* requestCode */);
578         }
579 
580         @Override
onResume()581         protected void onResume() {
582             super.onResume();
583             if (mReceivedResultOk
584                     && getIntent().getBooleanExtra(EXTRA_LAUNCH_ON_RESUME_AFTER_RESULT, false)) {
585                 startActivity(new Intent(this, CallbackTrackingActivity.class));
586             }
587         }
588 
589         @Override
onActivityResult(int requestCode, int resultCode, Intent data)590         protected void onActivityResult(int requestCode, int resultCode, Intent data) {
591             super.onActivityResult(requestCode, resultCode, data);
592             mReceivedResultOk = resultCode == RESULT_OK;
593             if (mReceivedResultOk && getIntent().getBooleanExtra(EXTRA_LAUNCH_ON_RESULT, false)) {
594                 startActivity(new Intent(this, CallbackTrackingActivity.class));
595             }
596         }
597     }
598 
599     /** Translucent activity that is started for result. */
600     public static class TranslucentResultActivity extends ResultActivity {
601     }
602 
603     /** Test activity that is started for result. */
604     public static class ResultActivity extends CallbackTrackingActivity {
605         @Override
onCreate(Bundle savedInstanceState)606         protected void onCreate(Bundle savedInstanceState) {
607             setResult(RESULT_OK);
608             super.onCreate(savedInstanceState);
609         }
610     }
611 
612     /** Test activity with NoDisplay theme that can finish itself. */
613     public static class NoDisplayActivity extends ResultActivity {
614         static final String EXTRA_LAUNCH_ACTIVITY = "extra_launch_activity";
615         static final String EXTRA_NEW_TASK = "extra_new_task";
616 
617         @Override
onCreate(Bundle savedInstanceState)618         protected void onCreate(Bundle savedInstanceState) {
619             super.onCreate(savedInstanceState);
620 
621             if (getIntent().getBooleanExtra(EXTRA_LAUNCH_ACTIVITY, false)) {
622                 final Intent intent = new Intent(this, CallbackTrackingActivity.class);
623                 if (getIntent().getBooleanExtra(EXTRA_NEW_TASK, false)) {
624                     intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
625                 }
626                 startActivity(intent);
627             }
628         }
629     }
630 
631     /** Test activity that can call {@link Activity#recreate()} if requested in a new intent. */
632     public static class SingleTopActivity extends CallbackTrackingActivity {
633         static final String EXTRA_LAUNCH_ACTIVITY = "extra_launch_activity";
634         static final String EXTRA_NEW_TASK = "extra_new_task";
635         @Override
onNewIntent(Intent intent)636         protected void onNewIntent(Intent intent) {
637             super.onNewIntent(intent);
638             if (intent != null && intent.getBooleanExtra(EXTRA_RECREATE, false)) {
639                 recreate();
640             }
641         }
642 
643         @Override
onCreate(Bundle savedInstanceState)644         protected void onCreate(Bundle savedInstanceState) {
645             super.onCreate(savedInstanceState);
646 
647             if (getIntent().getBooleanExtra(EXTRA_LAUNCH_ACTIVITY, false)) {
648                 final Intent intent = new Intent(this, SingleTopActivity.class);
649                 if (getIntent().getBooleanExtra(EXTRA_NEW_TASK, false)) {
650                     intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
651                 }
652                 startActivityForResult(intent, 1 /* requestCode */);
653             }
654         }
655     }
656 
657     // Config change handling activity
658     public static class ConfigChangeHandlingActivity extends CallbackTrackingActivity {
659     }
660 
661     // Callback tracking activity that runs in a separate process
662     public static class SecondProcessCallbackTrackingActivity extends CallbackTrackingActivity {
663     }
664 
665     // Pip-capable activity
666     // TODO(b/123013403): Disabled onMultiWindowMode changed callbacks to make the tests pass, so
667     // that they can verify other lifecycle transitions. This should be fixed and switched to
668     // extend CallbackTrackingActivity.
669     public static class PipActivity extends LifecycleTrackingActivity {
670         @Override
onCreate(Bundle savedInstanceState)671         protected void onCreate(Bundle savedInstanceState) {
672             super.onCreate(savedInstanceState);
673 
674             // Enter picture in picture with the given aspect ratio if provided
675             if (getIntent().hasExtra(EXTRA_ENTER_PIP)) {
676                 enterPip();
677             }
678         }
679 
enterPip()680         void enterPip() {
681             enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
682         }
683     }
684 
685     public static class AlwaysFocusablePipActivity extends CallbackTrackingActivity {
686     }
687 
688     public static class SlowActivity extends CallbackTrackingActivity {
689 
690         static final String EXTRA_CONTROL_FLAGS = "extra_control_flags";
691         static final int FLAG_SLOW_TOP_RESUME_RELEASE = 0x00000001;
692         static final int FLAG_TIMEOUT_TOP_RESUME_RELEASE = 0x00000002;
693 
694         private int mFlags;
695 
696         @Override
onCreate(Bundle savedInstanceState)697         protected void onCreate(Bundle savedInstanceState) {
698             super.onCreate(savedInstanceState);
699             mFlags = getIntent().getIntExtra(EXTRA_CONTROL_FLAGS, 0);
700         }
701 
702         @Override
onNewIntent(Intent intent)703         protected void onNewIntent(Intent intent) {
704             super.onNewIntent(intent);
705             mFlags = getIntent().getIntExtra(EXTRA_CONTROL_FLAGS, 0);
706         }
707 
708         @Override
onTopResumedActivityChanged(boolean isTopResumedActivity)709         public void onTopResumedActivityChanged(boolean isTopResumedActivity) {
710             if (!isTopResumedActivity && (mFlags & FLAG_SLOW_TOP_RESUME_RELEASE) != 0) {
711                 sleep(200);
712             } else if (!isTopResumedActivity && (mFlags & FLAG_TIMEOUT_TOP_RESUME_RELEASE) != 0) {
713                 sleep(2000);
714             }
715             // Intentionally moving the logging of the state change to after sleep to facilitate
716             // race condition with other activity getting top state before this releases its.
717             super.onTopResumedActivityChanged(isTopResumedActivity);
718         }
719 
sleep(long millis)720         private void sleep(long millis) {
721             try {
722                 Thread.sleep(millis);
723             } catch (InterruptedException e) {
724                 e.printStackTrace();
725             }
726         }
727     }
728 
729     public static class DifferentAffinityActivity extends LifecycleTrackingActivity {
730     }
731 
732     public static class TransitionSourceActivity extends LifecycleTrackingActivity {
733         @Override
onCreate(Bundle savedInstanceState)734         protected void onCreate(Bundle savedInstanceState) {
735             super.onCreate(savedInstanceState);
736             setContentView(R.layout.transition_source_layout);
737         }
738 
launchActivityWithTransition()739         void launchActivityWithTransition() {
740             final ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this,
741                     findViewById(R.id.transitionView), "sharedTransition");
742             final Intent intent = new Intent(this, TransitionDestinationActivity.class);
743             startActivity(intent, options.toBundle());
744         }
745     }
746 
747     public static class TransitionDestinationActivity extends LifecycleTrackingActivity {
748         @Override
onCreate(Bundle savedInstanceState)749         protected void onCreate(Bundle savedInstanceState) {
750             super.onCreate(savedInstanceState);
751             setContentView(R.layout.transition_destination_layout);
752             final Transition sharedElementEnterTransition =
753                     getWindow().getSharedElementEnterTransition();
754             sharedElementEnterTransition.addListener(new TransitionListenerAdapter() {
755                 @Override
756                 public void onTransitionEnd(Transition transition) {
757                     super.onTransitionEnd(transition);
758                     finishAfterTransition();
759                 }
760             });
761         }
762     }
763 
getComponentName(Class<? extends Activity> activity)764     static ComponentName getComponentName(Class<? extends Activity> activity) {
765         return new ComponentName(getInstrumentation().getContext(), activity);
766     }
767 
moveTaskToPrimarySplitScreenAndVerify(Activity primaryActivity, Activity secondaryActivity)768     void moveTaskToPrimarySplitScreenAndVerify(Activity primaryActivity,
769             Activity secondaryActivity) throws Exception {
770         getLifecycleLog().clear();
771 
772         mWmState.computeState(secondaryActivity.getComponentName());
773         moveActivitiesToSplitScreen(primaryActivity.getComponentName(),
774                 secondaryActivity.getComponentName());
775 
776         final Class<? extends Activity> activityClass = primaryActivity.getClass();
777 
778         final List<LifecycleLog.ActivityCallback> expectedTransitions =
779                 new ArrayList<LifecycleLog.ActivityCallback>(
780                         LifecycleVerifier.getSplitScreenTransitionSequence(activityClass));
781         final List<LifecycleLog.ActivityCallback> expectedTransitionForMinimizedDock =
782                 LifecycleVerifier.appendMinimizedDockTransitionTrail(expectedTransitions);
783 
784         final int displayWindowingMode =
785                 getDisplayWindowingModeByActivity(getComponentName(activityClass));
786         if (displayWindowingMode != WINDOWING_MODE_FULLSCREEN) {
787             // For non-fullscreen display mode, there won't be a multi-window callback.
788             expectedTransitions.removeAll(Collections.singleton(ON_MULTI_WINDOW_MODE_CHANGED));
789             expectedTransitionForMinimizedDock.removeAll(
790                     Collections.singleton(ON_MULTI_WINDOW_MODE_CHANGED));
791         }
792 
793         mLifecycleTracker.waitForActivityTransitions(activityClass, expectedTransitions);
794         LifecycleVerifier.assertSequenceMatchesOneOf(
795                 activityClass,
796                 getLifecycleLog(),
797                 Arrays.asList(expectedTransitions, expectedTransitionForMinimizedDock),
798                 "enterSplitScreen");
799     }
800 
getLaunchOptionsForFullscreen()801     final ActivityOptions getLaunchOptionsForFullscreen() {
802         final ActivityOptions launchOptions = ActivityOptions.makeBasic();
803         launchOptions.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
804         return launchOptions;
805     }
806 }
807