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