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