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