• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.LifecycleConstants.ACTIVITY_LAUNCH_TIMEOUT;
26 import static android.server.wm.lifecycle.LifecycleConstants.EXTRA_RECREATE;
27 import static android.server.wm.lifecycle.LifecycleConstants.EXTRA_SKIP_TOP_RESUMED_STATE;
28 import static android.server.wm.lifecycle.LifecycleConstants.ON_MULTI_WINDOW_MODE_CHANGED;
29 import static android.server.wm.lifecycle.LifecycleConstants.ON_PAUSE;
30 import static android.server.wm.lifecycle.LifecycleConstants.ON_RESUME;
31 import static android.server.wm.lifecycle.LifecycleConstants.ON_STOP;
32 import static android.server.wm.lifecycle.LifecycleConstants.ON_TOP_POSITION_GAINED;
33 import static android.server.wm.lifecycle.LifecycleConstants.getComponentName;
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 activities to perform the expected sequence of transitions.
271      * @see EventTracker#waitForActivityTransitions(Class, List)
272      */
waitForActivityTransitions(Class<? extends Activity> activityClass, List<String> expectedTransitions)273     final void waitForActivityTransitions(Class<? extends Activity> activityClass,
274             List<String> expectedTransitions) {
275         log("Start waitForActivityTransitions");
276         mTransitionTracker.waitForActivityTransitions(activityClass, expectedTransitions);
277     }
278 
279     /**
280      * Blocking call that will wait for activities to perform the expected sequence of transitions.
281      * After waiting it asserts that the sequence matches the expected.
282      * @see EventTracker#waitForActivityTransitions(Class, List)
283      */
waitAndAssertActivityTransitions(Class<? extends Activity> activityClass, List<String> expectedTransitions, String message)284     final void waitAndAssertActivityTransitions(Class<? extends Activity> activityClass,
285             List<String> expectedTransitions, String message) {
286         log("Start waitAndAssertActivityTransition");
287         mTransitionTracker.waitForActivityTransitions(activityClass, expectedTransitions);
288 
289         TransitionVerifier.assertSequence(activityClass, getTransitionLog(), expectedTransitions,
290                 message);
291     }
292 
getTransitionLog()293     EventLog getTransitionLog() {
294         return sEventLog;
295     }
296 
state(Activity activity, String stage)297     static Pair<Class<? extends Activity>, String> state(Activity activity,
298             String stage) {
299         return state(activity.getClass(), stage);
300     }
301 
state( Class<? extends Activity> activityClass, String stage)302     static Pair<Class<? extends Activity>, String> state(
303             Class<? extends Activity> activityClass, String stage) {
304         return new Pair<>(activityClass, stage);
305     }
306 
307     /**
308      * Returns a pair of the activity and the state it should be in based on the configuration of
309      * occludingActivity.
310      */
occludedActivityState( Activity activity, Activity occludingActivity)311     static Pair<Class<? extends Activity>, String> occludedActivityState(
312             Activity activity, Activity occludingActivity) {
313         return occludedActivityState(activity, isTranslucent(occludingActivity));
314     }
315 
316     /**
317      * Returns a pair of the activity and the state it should be in based on
318      * occludingActivityIsTranslucent.
319      */
occludedActivityState( Activity activity, boolean occludingActivityIsTranslucent)320     static Pair<Class<? extends Activity>, String> occludedActivityState(
321             Activity activity, boolean occludingActivityIsTranslucent) {
322         // Activities behind a translucent activity should be in the paused state since they are
323         // still visible. Otherwise, they should be in the stopped state.
324         return state(activity, occludedActivityState(occludingActivityIsTranslucent));
325     }
326 
occludedActivityState(boolean occludingActivityIsTranslucent)327     static String occludedActivityState(boolean occludingActivityIsTranslucent) {
328         return occludingActivityIsTranslucent ? ON_PAUSE : ON_STOP;
329     }
330 
331     /** Returns true if the input activity is translucent. */
isTranslucent(Activity activity)332     static boolean isTranslucent(Activity activity) {
333         return ActivityInfo.isTranslucentOrFloating(activity.getWindow().getWindowStyle());
334     }
335 
336     // Test activity
337     public static class FirstActivity extends LifecycleTrackingActivity {
338     }
339 
340     // Test activity
341     public static class SecondActivity extends LifecycleTrackingActivity {
342     }
343 
344     // Test activity
345     public static class ThirdActivity extends LifecycleTrackingActivity {
346     }
347 
348     // Test activity
349     public static class SideActivity extends LifecycleTrackingActivity {
350     }
351 
352     // Translucent test activity
353     public static class TranslucentActivity extends LifecycleTrackingActivity {
354     }
355 
356     // Translucent test activity
357     public static class SecondTranslucentActivity extends LifecycleTrackingActivity {
358     }
359 
360     // Just another callback tracking activity, nothing special.
361     public static class SecondCallbackTrackingActivity extends CallbackTrackingActivity {
362     }
363 
364     // Translucent callback tracking test activity
365     public static class TranslucentCallbackTrackingActivity extends CallbackTrackingActivity {
366     }
367 
368     // Callback tracking activity that supports being shown on top of lock screen
369     public static class ShowWhenLockedCallbackTrackingActivity extends CallbackTrackingActivity {
370         @Override
onCreate(Bundle savedInstanceState)371         protected void onCreate(Bundle savedInstanceState) {
372             super.onCreate(savedInstanceState);
373             setShowWhenLocked(true);
374         }
375     }
376 
377     /**
378      * Test activity that launches {@link TrampolineActivity} for result.
379      */
380     public static class LaunchForwardResultActivity extends CallbackTrackingActivity {
381         @Override
onCreate(Bundle savedInstanceState)382         protected void onCreate(Bundle savedInstanceState) {
383             super.onCreate(savedInstanceState);
384             final Intent intent = new Intent(this, TrampolineActivity.class);
385             startActivityForResult(intent, 1 /* requestCode */);
386         }
387     }
388 
389     public static class TrampolineActivity extends CallbackTrackingActivity {
390         @Override
onCreate(Bundle savedInstanceState)391         protected void onCreate(Bundle savedInstanceState) {
392             super.onCreate(savedInstanceState);
393             final Intent intent = new Intent(this, ResultActivity.class);
394             intent.setFlags(FLAG_ACTIVITY_FORWARD_RESULT);
395             startActivity(intent);
396             finish();
397         }
398     }
399 
400     /**
401      * Test activity that launches {@link ResultActivity} for result.
402      */
403     public static class LaunchForResultActivity extends CallbackTrackingActivity {
404         private static final String EXTRA_FORWARD_EXTRAS = "FORWARD_EXTRAS";
405         public static final String EXTRA_LAUNCH_ON_RESULT = "LAUNCH_ON_RESULT";
406         public static final String EXTRA_LAUNCH_ON_RESUME_AFTER_RESULT =
407                 "LAUNCH_ON_RESUME_AFTER_RESULT";
408         public static final String EXTRA_USE_TRANSLUCENT_RESULT =
409                 "USE_TRANSLUCENT_RESULT";
410 
411         boolean mReceivedResultOk;
412 
413         /** Adds the flag to the extra of intent which will forward to {@link ResultActivity}. */
forwardFlag(String... flags)414         static Consumer<Intent> forwardFlag(String... flags) {
415             return intent -> {
416                 final Bundle data = new Bundle();
417                 for (String f : flags) {
418                     data.putBoolean(f, true);
419                 }
420                 intent.putExtra(EXTRA_FORWARD_EXTRAS, data);
421             };
422         }
423 
424         @Override
onCreate(Bundle savedInstanceState)425         protected void onCreate(Bundle savedInstanceState) {
426             super.onCreate(savedInstanceState);
427 
428             final Intent intent;
429             if (getIntent().hasExtra(EXTRA_USE_TRANSLUCENT_RESULT)) {
430                 intent = new Intent(this, TranslucentResultActivity.class);
431             } else {
432                 intent = new Intent(this, ResultActivity.class);
433             }
434 
435             final Bundle forwardExtras = getIntent().getBundleExtra(EXTRA_FORWARD_EXTRAS);
436             if (forwardExtras != null) {
437                 intent.putExtras(forwardExtras);
438             }
439             startActivityForResult(intent, 1 /* requestCode */);
440         }
441 
442         @Override
onResume()443         protected void onResume() {
444             super.onResume();
445             if (mReceivedResultOk
446                     && getIntent().getBooleanExtra(EXTRA_LAUNCH_ON_RESUME_AFTER_RESULT, false)) {
447                 startActivity(new Intent(this, CallbackTrackingActivity.class));
448             }
449         }
450 
451         @Override
onActivityResult(int requestCode, int resultCode, Intent data)452         protected void onActivityResult(int requestCode, int resultCode, Intent data) {
453             super.onActivityResult(requestCode, resultCode, data);
454             mReceivedResultOk = resultCode == RESULT_OK;
455             if (mReceivedResultOk && getIntent().getBooleanExtra(EXTRA_LAUNCH_ON_RESULT, false)) {
456                 startActivity(new Intent(this, CallbackTrackingActivity.class));
457             }
458         }
459     }
460 
461     /** Translucent activity that is started for result. */
462     public static class TranslucentResultActivity extends ResultActivity {
463     }
464 
465     /** Test activity that is started for result. */
466     public static class ResultActivity extends CallbackTrackingActivity {
467         @Override
onCreate(Bundle savedInstanceState)468         protected void onCreate(Bundle savedInstanceState) {
469             setResult(RESULT_OK);
470             super.onCreate(savedInstanceState);
471         }
472     }
473 
474     /** Test activity with NoDisplay theme that can finish itself. */
475     public static class NoDisplayActivity extends ResultActivity {
476         static final String EXTRA_LAUNCH_ACTIVITY = "extra_launch_activity";
477         static final String EXTRA_NEW_TASK = "extra_new_task";
478 
479         @Override
onCreate(Bundle savedInstanceState)480         protected void onCreate(Bundle savedInstanceState) {
481             super.onCreate(savedInstanceState);
482 
483             if (getIntent().getBooleanExtra(EXTRA_LAUNCH_ACTIVITY, false)) {
484                 final Intent intent = new Intent(this, CallbackTrackingActivity.class);
485                 if (getIntent().getBooleanExtra(EXTRA_NEW_TASK, false)) {
486                     intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
487                 }
488                 startActivity(intent);
489             }
490         }
491     }
492 
493     /** Test activity that can call {@link Activity#recreate()} if requested in a new intent. */
494     public static class SingleTopActivity extends CallbackTrackingActivity {
495         static final String EXTRA_LAUNCH_ACTIVITY = "extra_launch_activity";
496         static final String EXTRA_NEW_TASK = "extra_new_task";
497         @Override
onNewIntent(Intent intent)498         protected void onNewIntent(Intent intent) {
499             super.onNewIntent(intent);
500             if (intent != null && intent.getBooleanExtra(EXTRA_RECREATE, false)) {
501                 recreate();
502             }
503         }
504 
505         @Override
onCreate(Bundle savedInstanceState)506         protected void onCreate(Bundle savedInstanceState) {
507             super.onCreate(savedInstanceState);
508 
509             if (getIntent().getBooleanExtra(EXTRA_LAUNCH_ACTIVITY, false)) {
510                 final Intent intent = new Intent(this, SingleTopActivity.class);
511                 if (getIntent().getBooleanExtra(EXTRA_NEW_TASK, false)) {
512                     intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
513                 }
514                 startActivityForResult(intent, 1 /* requestCode */);
515             }
516         }
517     }
518 
519     // Callback tracking activity that runs in a separate process
520     public static class SecondProcessCallbackTrackingActivity extends CallbackTrackingActivity {
521     }
522 
523     // Pip-capable activity
524     // TODO(b/123013403): Disabled onMultiWindowMode changed callbacks to make the tests pass, so
525     // that they can verify other lifecycle transitions. This should be fixed and switched to
526     // extend CallbackTrackingActivity.
527     public static class PipActivity extends LifecycleTrackingActivity {
528         @Override
onCreate(Bundle savedInstanceState)529         protected void onCreate(Bundle savedInstanceState) {
530             super.onCreate(savedInstanceState);
531 
532             // Enter picture in picture with the given aspect ratio if provided
533             if (getIntent().hasExtra(EXTRA_ENTER_PIP)) {
534                 enterPip();
535             }
536         }
537 
enterPip()538         void enterPip() {
539             enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
540         }
541     }
542 
543     public static class AlwaysFocusablePipActivity extends CallbackTrackingActivity {
544     }
545 
546     public static class SlowActivity extends CallbackTrackingActivity {
547 
548         static final String EXTRA_CONTROL_FLAGS = "extra_control_flags";
549         static final int FLAG_SLOW_TOP_RESUME_RELEASE = 0x00000001;
550         static final int FLAG_TIMEOUT_TOP_RESUME_RELEASE = 0x00000002;
551 
552         private int mFlags;
553 
554         @Override
onCreate(Bundle savedInstanceState)555         protected void onCreate(Bundle savedInstanceState) {
556             super.onCreate(savedInstanceState);
557             mFlags = getIntent().getIntExtra(EXTRA_CONTROL_FLAGS, 0);
558         }
559 
560         @Override
onNewIntent(Intent intent)561         protected void onNewIntent(Intent intent) {
562             super.onNewIntent(intent);
563             mFlags = getIntent().getIntExtra(EXTRA_CONTROL_FLAGS, 0);
564         }
565 
566         @Override
onTopResumedActivityChanged(boolean isTopResumedActivity)567         public void onTopResumedActivityChanged(boolean isTopResumedActivity) {
568             if (!isTopResumedActivity && (mFlags & FLAG_SLOW_TOP_RESUME_RELEASE) != 0) {
569                 sleep(200);
570             } else if (!isTopResumedActivity && (mFlags & FLAG_TIMEOUT_TOP_RESUME_RELEASE) != 0) {
571                 sleep(2000);
572             }
573             // Intentionally moving the logging of the state change to after sleep to facilitate
574             // race condition with other activity getting top state before this releases its.
575             super.onTopResumedActivityChanged(isTopResumedActivity);
576         }
577 
sleep(long millis)578         private void sleep(long millis) {
579             try {
580                 Thread.sleep(millis);
581             } catch (InterruptedException e) {
582                 e.printStackTrace();
583             }
584         }
585     }
586 
587     public static class DifferentAffinityActivity extends LifecycleTrackingActivity {
588     }
589 
590     public static class TransitionSourceActivity extends LifecycleTrackingActivity {
591         @Override
onCreate(Bundle savedInstanceState)592         protected void onCreate(Bundle savedInstanceState) {
593             super.onCreate(savedInstanceState);
594             setContentView(R.layout.transition_source_layout);
595         }
596 
launchActivityWithTransition()597         void launchActivityWithTransition() {
598             final ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this,
599                     findViewById(R.id.transitionView), "sharedTransition");
600             final Intent intent = new Intent(this, TransitionDestinationActivity.class);
601             startActivity(intent, options.toBundle());
602         }
603     }
604 
605     public static class TransitionDestinationActivity extends LifecycleTrackingActivity {
606         @Override
onCreate(Bundle savedInstanceState)607         protected void onCreate(Bundle savedInstanceState) {
608             super.onCreate(savedInstanceState);
609             setContentView(R.layout.transition_destination_layout);
610             final Transition sharedElementEnterTransition =
611                     getWindow().getSharedElementEnterTransition();
612             sharedElementEnterTransition.addListener(new TransitionListenerAdapter() {
613                 @Override
614                 public void onTransitionEnd(Transition transition) {
615                     super.onTransitionEnd(transition);
616                     finishAfterTransition();
617                 }
618             });
619         }
620     }
621 
moveTaskToPrimarySplitScreenAndVerify(Activity primaryActivity, Activity secondaryActivity)622     void moveTaskToPrimarySplitScreenAndVerify(Activity primaryActivity,
623             Activity secondaryActivity) throws Exception {
624         getTransitionLog().clear();
625 
626         final int screenwidth = mWm.getDefaultDisplay().getWidth();
627         final int screenheight = mWm.getDefaultDisplay().getHeight();
628 
629         mTaskOrganizer.registerOrganizerIfNeeded();
630         Rect primaryTaskBounds = mTaskOrganizer.getPrimaryTaskBounds();
631         if (screenheight > screenwidth) {
632             primaryTaskBounds.bottom = primaryTaskBounds.width() / 2;
633         } else {
634             primaryTaskBounds.right = primaryTaskBounds.height() / 2;
635         }
636         mTaskOrganizer.setRootPrimaryTaskBounds(primaryTaskBounds);
637 
638         mWmState.computeState(secondaryActivity.getComponentName());
639         moveActivitiesToSplitScreen(primaryActivity.getComponentName(),
640                 secondaryActivity.getComponentName());
641 
642         final Class<? extends Activity> activityClass = primaryActivity.getClass();
643 
644         final List<String> expectedTransitions =
645                 new ArrayList<>(TransitionVerifier.getSplitScreenTransitionSequence(activityClass));
646         final List<String> expectedTransitionForMinimizedDock =
647                 TransitionVerifier.appendMinimizedDockTransitionTrail(expectedTransitions);
648 
649         final int displayWindowingMode =
650                 getDisplayWindowingModeByActivity(getComponentName(activityClass));
651         if (displayWindowingMode != WINDOWING_MODE_FULLSCREEN) {
652             // For non-fullscreen display mode, there won't be a multi-window callback.
653             expectedTransitions.removeAll(Collections.singleton(ON_MULTI_WINDOW_MODE_CHANGED));
654             expectedTransitionForMinimizedDock.removeAll(
655                     Collections.singleton(ON_MULTI_WINDOW_MODE_CHANGED));
656         }
657 
658         mTransitionTracker.waitForActivityTransitions(activityClass, expectedTransitions);
659         TransitionVerifier.assertSequenceMatchesOneOf(
660                 activityClass,
661                 getTransitionLog(),
662                 Arrays.asList(expectedTransitions, expectedTransitionForMinimizedDock),
663                 "enterSplitScreen");
664     }
665 
getLaunchOptionsForFullscreen()666     final ActivityOptions getLaunchOptionsForFullscreen() {
667         final ActivityOptions launchOptions = ActivityOptions.makeBasic();
668         launchOptions.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
669         return launchOptions;
670     }
671 }
672