blob: 9a5640ac8b7dd8b65b73b68ca66d2931a716cbe0 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package android.server.wm.lifecycle;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
import static android.server.wm.UiDeviceUtils.pressHomeButton;
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_CREATE;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_DESTROY;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_NEW_INTENT;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_PAUSE;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_POST_CREATE;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESTART;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_START;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
import static android.server.wm.lifecycle.LifecycleVerifier.transition;
import static android.view.Display.DEFAULT_DISPLAY;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
import android.app.Activity;
import android.app.ActivityOptions;
import android.content.ComponentName;
import android.content.Intent;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.server.wm.WindowManagerState;
import android.server.wm.WindowManagerState.Task;
import android.server.wm.lifecycle.LifecycleLog.ActivityCallback;
import android.util.Pair;
import androidx.test.filters.MediumTest;
import org.junit.Before;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
/**
* Tests for {@link Activity#onTopResumedActivityChanged}.
*
* Build/Install/Run:
* atest CtsWindowManagerDeviceTestCases:ActivityLifecycleTopResumedStateTests
*/
@MediumTest
@Presubmit
@android.server.wm.annotation.Group3
public class ActivityLifecycleTopResumedStateTests extends ActivityLifecycleClientTestBase {
@Before
public void setUp() throws Exception {
super.setUp();
}
@Test
public void testTopPositionAfterLaunch() throws Exception {
launchActivityAndWait(CallbackTrackingActivity.class);
LifecycleVerifier.assertLaunchSequence(CallbackTrackingActivity.class, getLifecycleLog());
}
@Test
public void testTopPositionLostOnFinish() throws Exception {
final Activity activity = launchActivityAndWait(CallbackTrackingActivity.class);
getLifecycleLog().clear();
activity.finish();
mWmState.waitForActivityRemoved(getComponentName(CallbackTrackingActivity.class));
LifecycleVerifier.assertResumeToDestroySequence(CallbackTrackingActivity.class,
getLifecycleLog());
}
@Test
public void testTopPositionSwitchToActivityOnTop() throws Exception {
final Activity activity = launchActivityAndWait(CallbackTrackingActivity.class);
getLifecycleLog().clear();
final Activity topActivity = launchActivityAndWait(SingleTopActivity.class);
waitAndAssertActivityStates(state(activity, ON_STOP));
LifecycleVerifier.assertLaunchSequence(SingleTopActivity.class,
CallbackTrackingActivity.class, getLifecycleLog(),
false /* launchingIsTranslucent */);
}
@Test
public void testTopPositionSwitchToActivityOnTopSlowDifferentProcess() throws Exception {
// Launch first activity, which will be slow to release top position
final Intent slowTopReleaseIntent = new Intent();
slowTopReleaseIntent.putExtra(SlowActivity.EXTRA_CONTROL_FLAGS,
SlowActivity.FLAG_SLOW_TOP_RESUME_RELEASE);
final Activity firstActivity =
mSlowActivityTestRule.launchActivity(slowTopReleaseIntent);
waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED));
// Launch second activity on top
getLifecycleLog().clear();
final Class<? extends Activity> secondActivityClass =
SecondProcessCallbackTrackingActivity.class;
launchActivity(new ComponentName(firstActivity, secondActivityClass));
// Wait and assert top position switch
waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED),
state(firstActivity, ON_STOP));
LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
transition(firstActivity.getClass(), ON_TOP_POSITION_LOST),
transition(secondActivityClass, ON_TOP_POSITION_GAINED)),
"launchOnTop");
}
@Test
public void testTopPositionSwitchToActivityOnTopTimeoutDifferentProcess() throws Exception {
// Launch first activity, which will be slow to release top position
final Intent slowTopReleaseIntent = new Intent();
slowTopReleaseIntent.putExtra(SlowActivity.EXTRA_CONTROL_FLAGS,
SlowActivity.FLAG_TIMEOUT_TOP_RESUME_RELEASE);
final Activity firstActivity =
mSlowActivityTestRule.launchActivity(slowTopReleaseIntent);
waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED));
final Class<? extends Activity> firstActivityClass = firstActivity.getClass();
// Launch second activity on top
getLifecycleLog().clear();
final Class<? extends Activity> secondActivityClass =
SecondProcessCallbackTrackingActivity.class;
launchActivity(new ComponentName(firstActivity, secondActivityClass));
// Wait and assert top position switch,
waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED),
state(firstActivity, ON_STOP));
LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
transition(secondActivityClass, ON_TOP_POSITION_GAINED),
transition(firstActivityClass, ON_TOP_POSITION_LOST)),
"launchOnTop");
// Wait 5 seconds more to make sure that no new messages received after top resumed state
// released by the slow activity
getLifecycleLog().clear();
Thread.sleep(5000);
LifecycleVerifier.assertEmptySequence(firstActivityClass, getLifecycleLog(),
"topStateLossTimeout");
LifecycleVerifier.assertEmptySequence(secondActivityClass, getLifecycleLog(),
"topStateLossTimeout");
}
@Test
public void testTopPositionSwitchToTranslucentActivityOnTop() throws Exception {
final Activity activity = launchActivityAndWait(CallbackTrackingActivity.class);
getLifecycleLog().clear();
final Activity topActivity = launchActivityAndWait(
TranslucentCallbackTrackingActivity.class);
waitAndAssertActivityStates(state(activity, ON_PAUSE));
LifecycleVerifier.assertLaunchSequence(TranslucentCallbackTrackingActivity.class,
CallbackTrackingActivity.class, getLifecycleLog(),
true /* launchingIsTranslucent */);
}
@Test
public void testTopPositionSwitchOnDoubleLaunch() throws Exception {
final Activity baseActivity = launchActivityAndWait(CallbackTrackingActivity.class);
getLifecycleLog().clear();
new Launcher(LaunchForResultActivity.class)
.setExpectedState(ON_STOP)
.setNoInstance()
.launch();
waitAndAssertActivityStates(state(baseActivity, ON_STOP));
final List<ActivityCallback> expectedTopActivitySequence = Arrays.asList(
ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED);
waitForActivityTransitions(ResultActivity.class, expectedTopActivitySequence);
final List<Pair<String, ActivityCallback>> observedTransitions =
getLifecycleLog().getLog();
final List<Pair<String, ActivityCallback>> expectedTransitions = Arrays.asList(
transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
transition(CallbackTrackingActivity.class, ON_PAUSE),
transition(LaunchForResultActivity.class, ON_CREATE),
transition(LaunchForResultActivity.class, ON_START),
transition(LaunchForResultActivity.class, ON_POST_CREATE),
transition(LaunchForResultActivity.class, ON_RESUME),
transition(LaunchForResultActivity.class, ON_TOP_POSITION_GAINED),
transition(LaunchForResultActivity.class, ON_TOP_POSITION_LOST),
transition(LaunchForResultActivity.class, ON_PAUSE),
transition(ResultActivity.class, ON_CREATE),
transition(ResultActivity.class, ON_START),
transition(ResultActivity.class, ON_POST_CREATE),
transition(ResultActivity.class, ON_RESUME),
transition(ResultActivity.class, ON_TOP_POSITION_GAINED),
transition(LaunchForResultActivity.class, ON_STOP),
transition(CallbackTrackingActivity.class, ON_STOP));
assertEquals("Double launch sequence must match", expectedTransitions, observedTransitions);
}
@Test
public void testTopPositionSwitchOnDoubleLaunchAndTopFinish() throws Exception {
final Activity baseActivity = launchActivityAndWait(CallbackTrackingActivity.class);
getLifecycleLog().clear();
final Activity launchForResultActivity = new Launcher(LaunchForResultActivity.class)
.customizeIntent(LaunchForResultActivity.forwardFlag(EXTRA_FINISH_IN_ON_RESUME,
EXTRA_SKIP_TOP_RESUMED_STATE))
// Start the TranslucentResultActivity to avoid activity below stopped sometimes
// and resulted in different lifecycle events.
.setExtraFlags(LaunchForResultActivity.EXTRA_USE_TRANSLUCENT_RESULT)
.launch();
waitAndAssertActivityStates(state(baseActivity, ON_STOP));
final List<ActivityCallback> expectedLaunchingSequence =
Arrays.asList(ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME,
ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST, ON_PAUSE,
ON_ACTIVITY_RESULT, ON_RESUME, ON_TOP_POSITION_GAINED);
waitForActivityTransitions(LaunchForResultActivity.class, expectedLaunchingSequence);
final List<ActivityCallback> expectedTopActivitySequence = Arrays.asList(ON_CREATE,
ON_START, ON_POST_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED);
waitForActivityTransitions(TranslucentResultActivity.class, expectedTopActivitySequence);
LifecycleVerifier.assertEntireSequence(Arrays.asList(
transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
transition(CallbackTrackingActivity.class, ON_PAUSE),
transition(LaunchForResultActivity.class, ON_CREATE),
transition(LaunchForResultActivity.class, ON_START),
transition(LaunchForResultActivity.class, ON_POST_CREATE),
transition(LaunchForResultActivity.class, ON_RESUME),
transition(LaunchForResultActivity.class, ON_TOP_POSITION_GAINED),
transition(LaunchForResultActivity.class, ON_TOP_POSITION_LOST),
transition(LaunchForResultActivity.class, ON_PAUSE),
transition(TranslucentResultActivity.class, ON_CREATE),
transition(TranslucentResultActivity.class, ON_START),
transition(TranslucentResultActivity.class, ON_POST_CREATE),
transition(TranslucentResultActivity.class, ON_RESUME),
transition(TranslucentResultActivity.class, ON_PAUSE),
transition(LaunchForResultActivity.class, ON_ACTIVITY_RESULT),
transition(LaunchForResultActivity.class, ON_RESUME),
transition(LaunchForResultActivity.class, ON_TOP_POSITION_GAINED),
transition(TranslucentResultActivity.class, ON_STOP),
transition(TranslucentResultActivity.class, ON_DESTROY),
transition(CallbackTrackingActivity.class, ON_STOP)),
getLifecycleLog(), "Double launch sequence must match");
}
@Test
public void testTopPositionLostWhenDocked() throws Exception {
assumeTrue(supportsSplitScreenMultiWindow());
// Launch an activity that will be moved to split-screen secondary
final Activity sideActivity = launchActivityAndWait(SideActivity.class);
// Launch first activity
final Activity firstActivity = launchActivityAndWait(CallbackTrackingActivity.class);
// Enter split screen
moveTaskToPrimarySplitScreenAndVerify(firstActivity, sideActivity);
}
@Test
public void testTopPositionSwitchToAnotherVisibleActivity() throws Exception {
assumeTrue(supportsSplitScreenMultiWindow());
// Launch side activity
final Activity sideActivity = launchActivityAndWait(SideActivity.class);
// Launch first activity
final Activity firstActivity = launchActivityAndWait(CallbackTrackingActivity.class);
// Enter split screen
moveTaskToPrimarySplitScreenAndVerify(firstActivity, sideActivity);
// Launch second activity to side
getLifecycleLog().clear();
new Launcher(SingleTopActivity.class)
.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
.launch();
// Second activity must be on top now
LifecycleVerifier.assertLaunchSequence(SingleTopActivity.class, getLifecycleLog());
}
@Test
public void testTopPositionSwitchBetweenVisibleActivities() throws Exception {
assumeTrue(supportsSplitScreenMultiWindow());
// Launch side activity
final Activity sideActivity = launchActivityAndWait(SideActivity.class);
// Launch first activity
final Activity firstActivity = launchActivityAndWait(CallbackTrackingActivity.class);
// Enter split screen
moveTaskToPrimarySplitScreenAndVerify(firstActivity, sideActivity);
// Launch second activity to side
getLifecycleLog().clear();
mTaskOrganizer.setLaunchRoot(mTaskOrganizer.getSecondarySplitTaskId());
final Activity secondActivity = new Launcher(SingleTopActivity.class)
.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
.launch();
// Switch top between two activities
getLifecycleLog().clear();
mTaskOrganizer.setLaunchRoot(mTaskOrganizer.getPrimarySplitTaskId());
new Launcher(CallbackTrackingActivity.class)
.setFlags(FLAG_ACTIVITY_NEW_TASK)
.setNoInstance()
.launch();
waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED),
state(secondActivity, ON_TOP_POSITION_LOST));
LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
Arrays.asList(ON_TOP_POSITION_GAINED), "switchTop");
LifecycleVerifier.assertSequence(SingleTopActivity.class, getLifecycleLog(),
Arrays.asList(ON_TOP_POSITION_LOST), "switchTop");
// Switch top again
getLifecycleLog().clear();
mTaskOrganizer.setLaunchRoot(mTaskOrganizer.getSecondarySplitTaskId());
new Launcher(SingleTopActivity.class)
.setFlags(FLAG_ACTIVITY_NEW_TASK)
.setNoInstance()
.launch();
waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_LOST),
state(secondActivity, ON_TOP_POSITION_GAINED));
LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
Arrays.asList(ON_TOP_POSITION_LOST), "switchTop");
LifecycleVerifier.assertSequence(SingleTopActivity.class, getLifecycleLog(),
Arrays.asList(ON_PAUSE, ON_NEW_INTENT, ON_RESUME, ON_TOP_POSITION_GAINED),
"switchTop");
}
@Test
public void testTopPositionNewIntent() throws Exception {
// Launch single top activity
launchActivityAndWait(SingleTopActivity.class);
// Launch the activity again to observe new intent
getLifecycleLog().clear();
new Launcher(SingleTopActivity.class)
.setFlags(FLAG_ACTIVITY_NEW_TASK)
.setNoInstance()
.launch();
waitAndAssertActivityTransitions(SingleTopActivity.class,
Arrays.asList(
ON_TOP_POSITION_LOST, ON_PAUSE, ON_NEW_INTENT, ON_RESUME,
ON_TOP_POSITION_GAINED), "newIntent");
}
@Test
public void testTopPositionNewIntentForStopped() throws Exception {
// Launch single top activity
final Activity singleTopActivity = launchActivityAndWait(SingleTopActivity.class);
// Launch another activity on top
final Activity topActivity = launchActivityAndWait(CallbackTrackingActivity.class);
waitAndAssertActivityStates(state(singleTopActivity, ON_STOP));
// Launch the single top activity again to observe new intent
getLifecycleLog().clear();
new Launcher(SingleTopActivity.class)
.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP)
.setNoInstance()
.launch();
waitAndAssertActivityStates(state(topActivity, ON_DESTROY));
LifecycleVerifier.assertEntireSequence(Arrays.asList(
transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
transition(CallbackTrackingActivity.class, ON_PAUSE),
transition(SingleTopActivity.class, ON_RESTART),
transition(SingleTopActivity.class, ON_START),
transition(SingleTopActivity.class, ON_NEW_INTENT),
transition(SingleTopActivity.class, ON_RESUME),
transition(SingleTopActivity.class, ON_TOP_POSITION_GAINED),
transition(CallbackTrackingActivity.class, ON_STOP),
transition(CallbackTrackingActivity.class, ON_DESTROY)),
getLifecycleLog(), "Single top resolution sequence must match");
}
@Test
public void testTopPositionNewIntentForPaused() throws Exception {
// Launch single top activity
final Activity singleTopActivity = launchActivityAndWait(SingleTopActivity.class);
// Launch a translucent activity on top
final Activity topActivity =
launchActivityAndWait(TranslucentCallbackTrackingActivity.class);
waitAndAssertActivityStates(state(singleTopActivity, ON_PAUSE));
// Launch the single top activity again to observe new intent
getLifecycleLog().clear();
new Launcher(SingleTopActivity.class)
.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP)
.setNoInstance()
.launch();
waitAndAssertActivityStates(state(topActivity, ON_DESTROY));
LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
transition(TranslucentCallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
transition(TranslucentCallbackTrackingActivity.class, ON_PAUSE),
transition(SingleTopActivity.class, ON_NEW_INTENT),
transition(SingleTopActivity.class, ON_RESUME),
transition(SingleTopActivity.class, ON_TOP_POSITION_GAINED)),
"Single top resolution sequence must match");
}
@Test
public void testTopPositionSwitchWhenGoingHome() throws Exception {
final Activity topActivity = launchActivityAndWait(CallbackTrackingActivity.class);
// Press HOME and verify the lifecycle
getLifecycleLog().clear();
pressHomeButton();
waitAndAssertActivityStates(state(topActivity, ON_STOP));
LifecycleVerifier.assertResumeToStopSequence(CallbackTrackingActivity.class,
getLifecycleLog());
}
@Test
public void testTopPositionSwitchOnTap() throws Exception {
assumeTrue(supportsSplitScreenMultiWindow());
// Launch side activity
final Activity sideActivity = launchActivityAndWait(SideActivity.class);
// Launch first activity
final Activity firstActivity = launchActivityAndWait(CallbackTrackingActivity.class);
// Enter split screen
moveTaskToPrimarySplitScreenAndVerify(firstActivity, sideActivity);
// Launch second activity to side
getLifecycleLog().clear();
mTaskOrganizer.setLaunchRoot(mTaskOrganizer.getSecondarySplitTaskId());
final Activity secondActivity = new Launcher(SingleTopActivity.class)
.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
.launch();
// Tap on first activity to switch the focus
getLifecycleLog().clear();
final Task dockedTask = getRootTaskForLeafTaskId(firstActivity.getTaskId());
tapOnTaskCenter(dockedTask);
// Wait and assert focus switch
waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED),
state(secondActivity, ON_TOP_POSITION_LOST));
LifecycleVerifier.assertEntireSequence(Arrays.asList(
transition(SingleTopActivity.class, ON_TOP_POSITION_LOST),
transition(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED)),
getLifecycleLog(), "Single top resolution sequence must match");
// Tap on second activity to switch the focus again
getLifecycleLog().clear();
final Task sideTask = getRootTaskForLeafTaskId(secondActivity.getTaskId());
tapOnTaskCenter(sideTask);
// Wait and assert focus switch
waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_LOST),
state(secondActivity, ON_TOP_POSITION_GAINED));
LifecycleVerifier.assertEntireSequence(Arrays.asList(
transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
transition(SingleTopActivity.class, ON_TOP_POSITION_GAINED)),
getLifecycleLog(), "Single top resolution sequence must match");
}
@Test
public void testTopPositionSwitchOnTapSlowDifferentProcess() throws Exception {
assumeTrue(supportsSplitScreenMultiWindow());
// Launch side activity
final Activity sideActivity = launchActivityAndWait(SideActivity.class);
// Launch first activity
final Intent slowTopReleaseIntent = new Intent();
slowTopReleaseIntent.putExtra(SlowActivity.EXTRA_CONTROL_FLAGS,
SlowActivity.FLAG_SLOW_TOP_RESUME_RELEASE);
final Activity firstActivity =
mSlowActivityTestRule.launchActivity(slowTopReleaseIntent);
waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED));
final Class<? extends Activity> firstActivityClass = firstActivity.getClass();
// Enter split screen
moveTaskToPrimarySplitScreenAndVerify(firstActivity, sideActivity);
// Launch second activity to side
getLifecycleLog().clear();
mTaskOrganizer.setLaunchRoot(mTaskOrganizer.getSecondarySplitTaskId());
final Class<? extends Activity> secondActivityClass =
SecondProcessCallbackTrackingActivity.class;
final ComponentName secondActivityComponent =
new ComponentName(firstActivity, secondActivityClass);
getLaunchActivityBuilder()
.setTargetActivity(secondActivityComponent)
.setUseInstrumentation()
.setNewTask(true)
.setMultipleTask(true)
.execute();
// Wait and assert top position switch
waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED));
// Tap on first activity to switch the top resumed one
getLifecycleLog().clear();
final Task dockedTask = getRootTaskForLeafTaskId(firstActivity.getTaskId());
tapOnTaskCenter(dockedTask);
// Wait and assert top resumed position switch
waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_LOST),
state(firstActivityClass, ON_TOP_POSITION_GAINED));
LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
transition(secondActivityClass, ON_TOP_POSITION_LOST),
transition(firstActivityClass, ON_TOP_POSITION_GAINED)),
"tapOnTask");
// Tap on second activity to switch the top resumed activity again
getLifecycleLog().clear();
final Task sideTask = mWmState.getTaskByActivity(secondActivityComponent);
tapOnTaskCenter(sideTask);
// Wait and assert top resumed position switch
waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED),
state(firstActivityClass, ON_TOP_POSITION_LOST));
LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
transition(firstActivityClass, ON_TOP_POSITION_LOST),
transition(secondActivityClass, ON_TOP_POSITION_GAINED)),
"tapOnTask");
}
@Test
public void testTopPositionSwitchOnTapTimeoutDifferentProcess() throws Exception {
assumeTrue(supportsSplitScreenMultiWindow());
// Launch side activity
final Activity sideActivity = launchActivityAndWait(SideActivity.class);
// Launch first activity
final Intent slowTopReleaseIntent = new Intent();
slowTopReleaseIntent.putExtra(SlowActivity.EXTRA_CONTROL_FLAGS,
SlowActivity.FLAG_TIMEOUT_TOP_RESUME_RELEASE);
final Activity slowActivity =
mSlowActivityTestRule.launchActivity(slowTopReleaseIntent);
waitAndAssertActivityStates(state(slowActivity, ON_TOP_POSITION_GAINED));
final Class<? extends Activity> slowActivityClass = slowActivity.getClass();
// Enter split screen
moveTaskToPrimarySplitScreenAndVerify(slowActivity, sideActivity);
// Launch second activity to side
getLifecycleLog().clear();
mTaskOrganizer.setLaunchRoot(mTaskOrganizer.getSecondarySplitTaskId());
final Class<? extends Activity> secondActivityClass =
SecondProcessCallbackTrackingActivity.class;
final ComponentName secondActivityComponent =
new ComponentName(slowActivity, secondActivityClass);
getLaunchActivityBuilder()
.setTargetActivity(secondActivityComponent)
.setUseInstrumentation()
.setNewTask(true)
.setMultipleTask(true)
.execute();
// Wait and assert top position switch
waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED));
// Tap on first activity to switch the top resumed one
getLifecycleLog().clear();
final Task dockedTask = getRootTaskForLeafTaskId(slowActivity.getTaskId());
tapOnTaskCenter(dockedTask);
// Wait and assert top resumed position switch.
waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_LOST),
state(slowActivityClass, ON_TOP_POSITION_GAINED));
LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
transition(secondActivityClass, ON_TOP_POSITION_LOST),
transition(slowActivityClass, ON_TOP_POSITION_GAINED)),
"tapOnTask");
// Tap on second activity to switch the top resumed activity again
getLifecycleLog().clear();
final Task sideTask = mWmState
.getTaskByActivity(secondActivityComponent);
tapOnTaskCenter(sideTask);
// Wait and assert top resumed position switch. Because of timeout the new top position will
// be reported to the first activity before second will finish handling it.
waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED),
state(slowActivityClass, ON_TOP_POSITION_LOST));
LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
transition(secondActivityClass, ON_TOP_POSITION_GAINED),
transition(slowActivityClass, ON_TOP_POSITION_LOST)),
"tapOnTask");
// Wait 5 seconds more to make sure that no new messages received after top resumed state
// released by the slow activity
getLifecycleLog().clear();
Thread.sleep(5000);
LifecycleVerifier.assertEmptySequence(slowActivityClass, getLifecycleLog(),
"topStateLossTimeout");
LifecycleVerifier.assertEmptySequence(secondActivityClass, getLifecycleLog(),
"topStateLossTimeout");
}
@Test
public void testTopPositionPreservedOnLocalRelaunch() throws Exception {
final Activity activity = launchActivityAndWait(CallbackTrackingActivity.class);
getLifecycleLog().clear();
getInstrumentation().runOnMainSync(activity::recreate);
waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
LifecycleVerifier.assertRelaunchSequence(CallbackTrackingActivity.class, getLifecycleLog(),
ON_TOP_POSITION_GAINED);
}
@Test
public void testTopPositionLaunchedBehindLockScreen() throws Exception {
assumeTrue(supportsSecureLock());
try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
lockScreenSession.setLockCredential().gotoKeyguard();
new Launcher(CallbackTrackingActivity.class)
.setExpectedState(ON_STOP)
.setNoInstance()
.launch();
LifecycleVerifier.assertLaunchAndStopSequence(CallbackTrackingActivity.class,
getLifecycleLog(), true /* onTop */);
getLifecycleLog().clear();
}
// Lock screen removed - activity should be on top now
if (isCar()) {
LifecycleVerifier.assertStopToResumeSubSequence(CallbackTrackingActivity.class,
getLifecycleLog());
waitAndAssertActivityCurrentState(CallbackTrackingActivity.class,
ON_TOP_POSITION_GAINED);
} else {
waitAndAssertActivityStates(
state(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED));
LifecycleVerifier.assertStopToResumeSequence(CallbackTrackingActivity.class,
getLifecycleLog());
}
}
@Test
public void testTopPositionRemovedBehindLockScreen() throws Exception {
assumeTrue(supportsSecureLock());
final Activity activity = launchActivityAndWait(CallbackTrackingActivity.class);
getLifecycleLog().clear();
try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
lockScreenSession.setLockCredential().gotoKeyguard();
waitAndAssertActivityStates(state(activity, ON_STOP));
LifecycleVerifier.assertResumeToStopSequence(CallbackTrackingActivity.class,
getLifecycleLog());
getLifecycleLog().clear();
}
// Lock screen removed - activity should be on top now
if (isCar()) {
LifecycleVerifier.assertStopToResumeSubSequence(CallbackTrackingActivity.class,
getLifecycleLog());
waitAndAssertActivityCurrentState(activity.getClass(), ON_TOP_POSITION_GAINED);
} else {
waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
LifecycleVerifier.assertStopToResumeSequence(CallbackTrackingActivity.class,
getLifecycleLog());
}
}
@Test
public void testTopPositionLaunchedOnTopOfLockScreen() throws Exception {
assumeTrue(supportsSecureLock());
final Activity showWhenLockedActivity;
try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
lockScreenSession.setLockCredential().gotoKeyguard();
ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
showWhenLockedActivity = new Launcher(ShowWhenLockedCallbackTrackingActivity.class)
.setOptions(options)
.launch();
// TODO(b/123432490): Fix extra pause/resume
LifecycleVerifier.assertSequence(ShowWhenLockedCallbackTrackingActivity.class,
getLifecycleLog(),
Arrays.asList(ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME,
ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST, ON_PAUSE, ON_RESUME,
ON_TOP_POSITION_GAINED),
"launchAboveKeyguard");
getLifecycleLog().clear();
}
// When the lock screen is removed, the ShowWhenLocked activity will be dismissed using the
// back button, which should finish the activity.
waitAndAssertActivityStates(state(showWhenLockedActivity, ON_DESTROY));
LifecycleVerifier.assertResumeToDestroySequence(
ShowWhenLockedCallbackTrackingActivity.class, getLifecycleLog());
}
@Test
public void testTopPositionSwitchAcrossDisplays() throws Exception {
assumeTrue(supportsMultiDisplay());
// Launch activity on default display.
final ActivityOptions launchOptions = ActivityOptions.makeBasic();
launchOptions.setLaunchDisplayId(DEFAULT_DISPLAY);
new Launcher(CallbackTrackingActivity.class)
.setFlags(FLAG_ACTIVITY_NEW_TASK)
.setOptions(launchOptions)
.launch();
waitAndAssertTopResumedActivity(getComponentName(CallbackTrackingActivity.class),
DEFAULT_DISPLAY, "Activity launched on default display must be focused");
waitAndAssertActivityTransitions(CallbackTrackingActivity.class,
LifecycleVerifier.getLaunchSequence(CallbackTrackingActivity.class), "launch");
try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
// Create new simulated display
final WindowManagerState.DisplayContent newDisplay
= virtualDisplaySession.setSimulateDisplay(true).createDisplay();
// Launch another activity on new secondary display.
getLifecycleLog().clear();
launchOptions.setLaunchDisplayId(newDisplay.mId);
new Launcher(SingleTopActivity.class)
.setFlags(FLAG_ACTIVITY_NEW_TASK)
.setOptions(launchOptions)
.launch();
waitAndAssertTopResumedActivity(getComponentName(SingleTopActivity.class),
newDisplay.mId, "Activity launched on secondary display must be focused");
waitAndAssertActivityTransitions(SingleTopActivity.class,
LifecycleVerifier.getLaunchSequence(SingleTopActivity.class), "launch");
LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
transition(SingleTopActivity.class, ON_TOP_POSITION_GAINED)),
"launchOnOtherDisplay");
getLifecycleLog().clear();
}
// Secondary display was removed - activity will be moved to the default display
waitForActivityTransitions(SingleTopActivity.class,
LifecycleVerifier.getRelaunchSequence(
ON_TOP_POSITION_GAINED));
LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
transition(SingleTopActivity.class, ON_TOP_POSITION_LOST),
transition(SingleTopActivity.class, ON_TOP_POSITION_GAINED)),
"hostingDisplayRemoved");
}
@Test
public void testTopPositionSwitchAcrossDisplaysOnTap() throws Exception {
assumeTrue(supportsMultiDisplay());
// Launch activity on default display.
final ActivityOptions launchOptions = ActivityOptions.makeBasic();
launchOptions.setLaunchDisplayId(DEFAULT_DISPLAY);
new Launcher(CallbackTrackingActivity.class)
.setFlags(FLAG_ACTIVITY_NEW_TASK)
.setOptions(launchOptions)
.launch();
waitAndAssertTopResumedActivity(getComponentName(CallbackTrackingActivity.class),
DEFAULT_DISPLAY, "Activity launched on default display must be focused");
// Create new simulated display
final WindowManagerState.DisplayContent newDisplay = createManagedVirtualDisplaySession()
.setSimulateDisplay(true)
.createDisplay();
// Launch another activity on new secondary display.
getLifecycleLog().clear();
launchOptions.setLaunchDisplayId(newDisplay.mId);
new Launcher(SingleTopActivity.class)
.setFlags(FLAG_ACTIVITY_NEW_TASK)
.setOptions(launchOptions)
.launch();
waitAndAssertTopResumedActivity(getComponentName(SingleTopActivity.class),
newDisplay.mId, "Activity launched on secondary display must be focused");
getLifecycleLog().clear();
// Tap on task center to switch the top activity.
final Task callbackTrackingTask = mWmState
.getTaskByActivity(getComponentName(CallbackTrackingActivity.class));
tapOnTaskCenter(callbackTrackingTask);
// Wait and assert focus switch
waitAndAssertActivityTransitions(SingleTopActivity.class,
Arrays.asList(ON_TOP_POSITION_LOST), "tapOnFocusSwitch");
waitAndAssertActivityTransitions(CallbackTrackingActivity.class,
Arrays.asList(ON_TOP_POSITION_GAINED), "tapOnFocusSwitch");
LifecycleVerifier.assertEntireSequence(Arrays.asList(
transition(SingleTopActivity.class, ON_TOP_POSITION_LOST),
transition(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED)),
getLifecycleLog(), "Top activity must be switched on tap");
getLifecycleLog().clear();
// Tap on task center to switch the top activity.
final Task singleTopActivityTask = mWmState
.getTaskByActivity(getComponentName(SingleTopActivity.class));
tapOnTaskCenter(singleTopActivityTask);
// Wait and assert focus switch
waitAndAssertActivityTransitions(CallbackTrackingActivity.class,
Arrays.asList(ON_TOP_POSITION_LOST), "tapOnFocusSwitch");
waitAndAssertActivityTransitions(SingleTopActivity.class,
Arrays.asList(ON_TOP_POSITION_GAINED), "tapOnFocusSwitch");
LifecycleVerifier.assertEntireSequence(Arrays.asList(
transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
transition(SingleTopActivity.class, ON_TOP_POSITION_GAINED)),
getLifecycleLog(), "Top activity must be switched on tap");
}
@Test
public void testTopPositionSwitchAcrossDisplaysOnTapSlowDifferentProcess() {
assumeTrue(supportsMultiDisplay());
// Create new simulated display.
final WindowManagerState.DisplayContent newDisplay = createManagedVirtualDisplaySession()
.setSimulateDisplay(true)
.createDisplay();
// Launch an activity on new secondary display.
final Class<? extends Activity> secondActivityClass =
SecondProcessCallbackTrackingActivity.class;
final ComponentName secondActivityComponent =
new ComponentName(mTargetContext, secondActivityClass);
getLaunchActivityBuilder()
.setTargetActivity(secondActivityComponent)
.setUseInstrumentation()
.setDisplayId(newDisplay.mId)
.execute();
waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED));
// Launch activity on default display, which will be slow to release top position.
getLifecycleLog().clear();
final ActivityOptions launchOptions = ActivityOptions.makeBasic();
launchOptions.setLaunchDisplayId(DEFAULT_DISPLAY);
final Class<? extends Activity> defaultActivityClass = SlowActivity.class;
final Intent defaultDisplaySlowIntent = new Intent(mContext, defaultActivityClass);
defaultDisplaySlowIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
defaultDisplaySlowIntent.putExtra(SlowActivity.EXTRA_CONTROL_FLAGS,
SlowActivity.FLAG_SLOW_TOP_RESUME_RELEASE);
mTargetContext.startActivity(defaultDisplaySlowIntent, launchOptions.toBundle());
waitAndAssertTopResumedActivity(getComponentName(SlowActivity.class),
DEFAULT_DISPLAY, "Activity launched on default display must be focused");
// Wait and assert focus switch
waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_LOST),
state(defaultActivityClass, ON_TOP_POSITION_GAINED));
LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
transition(secondActivityClass, ON_TOP_POSITION_LOST),
transition(defaultActivityClass, ON_TOP_POSITION_GAINED)),
"launchOnDifferentDisplay");
// Tap on task center to switch the top activity.
getLifecycleLog().clear();
final Task secondActivityTask = mWmState
.getTaskByActivity(getComponentName(SecondProcessCallbackTrackingActivity.class));
tapOnTaskCenter(secondActivityTask);
// Wait and assert top resumed position switch.
waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED),
state(defaultActivityClass, ON_TOP_POSITION_LOST));
LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
transition(defaultActivityClass, ON_TOP_POSITION_LOST),
transition(secondActivityClass, ON_TOP_POSITION_GAINED)),
"tapOnDifferentDisplay");
getLifecycleLog().clear();
// Tap on task center to switch the top activity.
final Task defaultActivityTask = mWmState
.getTaskByActivity(getComponentName(defaultActivityClass));
tapOnTaskCenter(defaultActivityTask);
// Wait and assert top resumed position switch.
waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_LOST),
state(defaultActivityClass, ON_TOP_POSITION_GAINED));
LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
transition(secondActivityClass, ON_TOP_POSITION_LOST),
transition(defaultActivityClass, ON_TOP_POSITION_GAINED)),
"tapOnDifferentDisplay");
}
@Test
public void testTopPositionSwitchAcrossDisplaysOnTapTimeoutDifferentProcess() {
assumeTrue(supportsMultiDisplay());
// Create new simulated display.
final WindowManagerState.DisplayContent newDisplay = createManagedVirtualDisplaySession()
.setSimulateDisplay(true)
.createDisplay();
// Launch an activity on new secondary display.
final Class<? extends Activity> secondActivityClass =
SecondProcessCallbackTrackingActivity.class;
final ComponentName secondActivityComponent =
new ComponentName(mTargetContext, secondActivityClass);
getLaunchActivityBuilder()
.setTargetActivity(secondActivityComponent)
.setUseInstrumentation()
.setDisplayId(newDisplay.mId)
.execute();
waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED));
// Launch activity on default display, which will be slow to release top position.
getLifecycleLog().clear();
final ActivityOptions launchOptions = ActivityOptions.makeBasic();
launchOptions.setLaunchDisplayId(DEFAULT_DISPLAY);
final Class<? extends Activity> defaultActivityClass = SlowActivity.class;
final Intent defaultDisplaySlowIntent = new Intent(mContext, defaultActivityClass);
defaultDisplaySlowIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
defaultDisplaySlowIntent.putExtra(SlowActivity.EXTRA_CONTROL_FLAGS,
SlowActivity.FLAG_TIMEOUT_TOP_RESUME_RELEASE);
mTargetContext.startActivity(defaultDisplaySlowIntent, launchOptions.toBundle());
waitAndAssertTopResumedActivity(getComponentName(SlowActivity.class),
DEFAULT_DISPLAY, "Activity launched on default display must be focused");
// Wait and assert focus switch.
waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_LOST),
state(defaultActivityClass, ON_TOP_POSITION_GAINED));
LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
transition(secondActivityClass, ON_TOP_POSITION_LOST),
transition(defaultActivityClass, ON_TOP_POSITION_GAINED)),
"launchOnDifferentDisplay");
// Tap on secondary display to switch the top activity.
getLifecycleLog().clear();
tapOnDisplayCenter(newDisplay.mId);
// Wait and assert top resumed position switch. Because of timeout top position gain
// will appear before top position loss handling is finished.
waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED),
state(defaultActivityClass, ON_TOP_POSITION_LOST));
LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
transition(secondActivityClass, ON_TOP_POSITION_GAINED),
transition(defaultActivityClass, ON_TOP_POSITION_LOST)),
"tapOnDifferentDisplay");
// Wait 5 seconds more to make sure that no new messages received after top resumed state
// released by the slow activity
getLifecycleLog().clear();
SystemClock.sleep(5000);
LifecycleVerifier.assertEmptySequence(defaultActivityClass, getLifecycleLog(),
"topStateLossTimeout");
LifecycleVerifier.assertEmptySequence(secondActivityClass, getLifecycleLog(),
"topStateLossTimeout");
}
@Test
public void testFinishOnDifferentDisplay_nonFocused() throws Exception {
assumeTrue(supportsMultiDisplay());
// In some platforms, Launcher can invoke another embedded Activity and it can affect
// the test. We'll place a bottom Activity to eliminate the side effects by that Launcher.
final Activity bottomActivity = launchActivityAndWait(SecondActivity.class);
// Launch activity on some display.
final Activity callbackTrackingActivity =
launchActivityAndWait(CallbackTrackingActivity.class);
waitAndAssertTopResumedActivity(getComponentName(CallbackTrackingActivity.class),
DEFAULT_DISPLAY, "Activity launched on default display must be focused");
// Create new simulated display.
final WindowManagerState.DisplayContent newDisplay = createManagedVirtualDisplaySession()
.setSimulateDisplay(true)
.createDisplay();
// Launch another activity on new secondary display.
getLifecycleLog().clear();
final ActivityOptions launchOptions = ActivityOptions.makeBasic();
launchOptions.setLaunchDisplayId(newDisplay.mId);
new Launcher(SingleTopActivity.class)
.setFlags(FLAG_ACTIVITY_NEW_TASK)
.setOptions(launchOptions)
.launch();
waitAndAssertTopResumedActivity(getComponentName(SingleTopActivity.class),
newDisplay.mId, "Activity launched on secondary display must be focused");
// An activity is launched on the new display, so the activity on default display should
// lose the top state.
LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
Arrays.asList(ON_TOP_POSITION_LOST), "launchFocusSwitch");
// Finish the activity on the default display.
getLifecycleLog().clear();
callbackTrackingActivity.finish();
// Verify that activity was actually destroyed.
waitAndAssertActivityStates(state(CallbackTrackingActivity.class, ON_DESTROY));
// Verify that the original focused display is not affected by the finished activity on
// non-focused display.
LifecycleVerifier.assertEmptySequence(SingleTopActivity.class, getLifecycleLog(),
"destructionOnDifferentDisplay");
}
@Test
public void testFinishOnDifferentDisplay_focused() throws Exception {
assumeTrue(supportsMultiDisplay());
// Launch activity on some display.
final Activity bottomActivity = launchActivityAndWait(SecondActivity.class);
final Activity callbackTrackingActivity =
launchActivityAndWait(CallbackTrackingActivity.class);
waitAndAssertTopResumedActivity(getComponentName(CallbackTrackingActivity.class),
DEFAULT_DISPLAY, "Activity launched on default display must be focused");
// Create new simulated display.
final WindowManagerState.DisplayContent newDisplay = createManagedVirtualDisplaySession()
.setSimulateDisplay(true)
.createDisplay();
// Launch another activity on new secondary display.
getLifecycleLog().clear();
final ActivityOptions launchOptions = ActivityOptions.makeBasic();
launchOptions.setLaunchDisplayId(newDisplay.mId);
new Launcher(SingleTopActivity.class)
.setFlags(FLAG_ACTIVITY_NEW_TASK)
.setOptions(launchOptions)
.launch();
waitAndAssertTopResumedActivity(getComponentName(SingleTopActivity.class),
newDisplay.mId, "Activity launched on secondary display must be focused");
// Bring the focus back.
final Intent sameInstanceIntent = new Intent(mContext, CallbackTrackingActivity.class);
sameInstanceIntent.setFlags(FLAG_ACTIVITY_REORDER_TO_FRONT);
bottomActivity.startActivity(sameInstanceIntent, null);
waitAndAssertActivityStates(state(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED));
// Finish the focused activity
getLifecycleLog().clear();
callbackTrackingActivity.finish();
// Verify that lifecycle of the activity on a different display did not change.
// Top resumed state will be given to home activity on that display.
waitAndAssertActivityStates(state(CallbackTrackingActivity.class, ON_DESTROY),
state(SecondActivity.class, ON_RESUME));
LifecycleVerifier.assertEmptySequence(SingleTopActivity.class, getLifecycleLog(),
"destructionOnDifferentDisplay");
}
@Test
public void testTopPositionNotSwitchedToPip() throws Exception {
assumeTrue(supportsPip());
// Launch first activity
final Activity activity = launchActivityAndWait(CallbackTrackingActivity.class);
// Clear the log before launching to Pip
getLifecycleLog().clear();
// Launch Pip-capable activity and enter Pip immediately
final Activity pipActivity = new Launcher(PipActivity.class)
.setExtraFlags(EXTRA_ENTER_PIP)
.setExpectedState(ON_PAUSE)
.launch();
// The PipMenuActivity could start anytime after moving pipActivity to pinned stack,
// however, we cannot control when would it start or finish, so this test could fail when
// PipMenuActivity just start and pipActivity call finish almost at the same time.
// So the strategy here is to wait the PipMenuActivity start and finish after pipActivity
// moved to pinned stack and paused, because pipActivity is not focusable but the
// PipMenuActivity is focusable, when the pinned stack gain top focus means the
// PipMenuActivity is launched and resumed, then when pinned stack lost top focus means the
// PipMenuActivity is finished.
mWmState.waitWindowingModeTopFocus(WINDOWING_MODE_PINNED, true /* topFocus */
, "wait PipMenuActivity get top focus");
mWmState.waitWindowingModeTopFocus(WINDOWING_MODE_PINNED, false /* topFocus */
, "wait PipMenuActivity lost top focus");
waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
transition(CallbackTrackingActivity.class, ON_PAUSE),
transition(CallbackTrackingActivity.class, ON_RESUME),
transition(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED)), "startPIP");
// Exit PiP
getLifecycleLog().clear();
pipActivity.finish();
waitAndAssertActivityStates(state(pipActivity, ON_DESTROY));
LifecycleVerifier.assertSequence(PipActivity.class, getLifecycleLog(),
Arrays.asList(
ON_STOP, ON_DESTROY), "finishPip");
LifecycleVerifier.assertEmptySequence(CallbackTrackingActivity.class, getLifecycleLog(),
"finishPip");
}
@Test
public void testTopPositionForAlwaysFocusableActivityInPip() throws Exception {
assumeTrue(supportsPip());
// Launch first activity
final Activity activity = launchActivityAndWait(CallbackTrackingActivity.class);
// Clear the log before launching to Pip
getLifecycleLog().clear();
// Launch Pip-capable activity and enter Pip immediately
final Activity pipActivity = new Launcher(PipActivity.class)
.setExtraFlags(EXTRA_ENTER_PIP)
.setExpectedState(ON_PAUSE)
.launch();
// Launch always focusable activity into PiP
// Notice that do not clear the lifecycle log here, because it may clear the event
// ON_TOP_POSITION_LOST of CallbackTrackingActivity if PipMenuActivity is started earlier.
final Activity alwaysFocusableActivity =
launchActivityAndWait(AlwaysFocusablePipActivity.class);
waitAndAssertActivityStates(state(pipActivity, ON_STOP),
state(activity, ON_TOP_POSITION_LOST));
LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
transition(AlwaysFocusablePipActivity.class, ON_TOP_POSITION_GAINED)),
"launchAlwaysFocusablePip");
// Finish always focusable activity - top position should go back to fullscreen activity
getLifecycleLog().clear();
alwaysFocusableActivity.finish();
waitAndAssertActivityStates(state(alwaysFocusableActivity, ON_DESTROY),
state(activity, ON_TOP_POSITION_GAINED), state(pipActivity, ON_PAUSE));
LifecycleVerifier.assertResumeToDestroySequence(AlwaysFocusablePipActivity.class,
getLifecycleLog());
LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
transition(AlwaysFocusablePipActivity.class, ON_TOP_POSITION_LOST),
transition(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED)),
"finishAlwaysFocusablePip");
}
}