/* * Copyright (C) 2021 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; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.server.wm.SplitActivityLifecycleTest.SplitTestActivity.EXTRA_SET_RESULT_AND_FINISH; import static android.server.wm.SplitActivityLifecycleTest.SplitTestActivity.EXTRA_SHOW_WHEN_LOCKED; import static android.server.wm.WindowManagerState.STATE_STARTED; import static android.server.wm.WindowManagerState.STATE_STOPPED; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_90; import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE; import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assume.assumeTrue; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; import android.os.SystemProperties; import android.platform.test.annotations.Presubmit; import android.server.wm.WindowManagerState.TaskFragment; import android.view.WindowManager; import android.window.TaskFragmentCreationParams; import android.window.TaskFragmentInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import org.junit.Test; /** * Tests that verify the behavior of split Activity. *
* At the beginning of test, two Activities are launched side-by-side in two adjacent TaskFragments. * Then another Activity will be launched with different scenarios. The purpose of this test is to * verify the CUJ of split Activity. *
* * Build/Install/Run: * atest CtsWindowManagerDeviceTestCases:SplitActivityLifecycleTest */ @Presubmit @android.server.wm.annotation.Group2 public class SplitActivityLifecycleTest extends TaskFragmentOrganizerTestBase { /** The bounds should only be updated through {@link #updateSplitBounds(Rect)}. */ private final Rect mPrimaryBounds = new Rect(); private final Rect mPrimaryRelativeBounds = new Rect(); private final Rect mSideBounds = new Rect(); private final Rect mSideRelativeBounds = new Rect(); private TaskFragmentRecord mTaskFragA; private TaskFragmentRecord mTaskFragB; private final ComponentName mActivityA = new ComponentName(mContext, ActivityA.class); private final ComponentName mActivityB = new ComponentName(mContext, ActivityB.class); private final ComponentName mActivityC = new ComponentName(mContext, ActivityC.class); private final Intent mIntent = new Intent().setComponent(mActivityC); @Override public void setUp() throws Exception { super.setUp(); } @Override Activity setUpOwnerActivity() { // Launch activities in fullscreen, otherwise, some tests fail on devices which use freeform // as the default windowing mode, because tests' prerequisite are that activity A, B, and C // need to overlay completely, but they can be partially overlay as freeform windows. return startActivityInWindowingModeFullScreen(ActivityA.class); } /** Launch two Activities in two adjacent TaskFragments side-by-side. */ private void initializeSplitActivities() { initializeSplitActivities(false /* showWhenLocked */); } /** * Launch two Activities in two adjacent TaskFragments side-by-side and support to set the * showWhenLocked attribute to Activity B. */ private void initializeSplitActivities(boolean showWhenLocked) { final Rect activityBounds = mOwnerActivity.getWindowManager().getCurrentWindowMetrics() .getBounds(); updateSplitBounds(activityBounds); final TaskFragmentCreationParams paramsA = generatePrimaryTaskFragParams(); final TaskFragmentCreationParams paramsB = generateSideTaskFragParams(); IBinder taskFragTokenA = paramsA.getFragmentToken(); IBinder taskFragTokenB = paramsB.getFragmentToken(); final WindowContainerTransaction wct = new WindowContainerTransaction() .createTaskFragment(paramsA) .reparentActivityToTaskFragment(taskFragTokenA, mOwnerToken) .createTaskFragment(paramsB) .setAdjacentTaskFragments(taskFragTokenA, taskFragTokenB, null /* params */); final Intent intent = new Intent().setComponent(mActivityB); if (showWhenLocked) { intent.putExtra(EXTRA_SHOW_WHEN_LOCKED, true); } wct.startActivityInTaskFragment(taskFragTokenB, mOwnerToken, intent, null /* activityOptions */); mTaskFragmentOrganizer.setAppearedCount(2); mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */); mTaskFragmentOrganizer.waitForTaskFragmentCreated(); final TaskFragmentInfo infoA = mTaskFragmentOrganizer.getTaskFragmentInfo( taskFragTokenA); final TaskFragmentInfo infoB = mTaskFragmentOrganizer.getTaskFragmentInfo( taskFragTokenB); assertNotEmptyTaskFragment(infoA, taskFragTokenA, mOwnerToken); assertNotEmptyTaskFragment(infoB, taskFragTokenB); mTaskFragA = new TaskFragmentRecord(infoA); mTaskFragB = new TaskFragmentRecord(infoB); waitAndAssertResumedActivity(mActivityA, "Activity A must still be resumed."); waitAndAssertResumedActivity(mActivityB, "Activity B must still be resumed."); mTaskFragmentOrganizer.resetLatch(); } /** * Splits the {@code parentBounds} vertically to {@link #mPrimaryBounds} and * {@link #mSideBounds}. {@link #mPrimaryRelativeBounds} and {@link #mSideRelativeBounds} will * also be updated to the corresponding relative bounds in parent coordinate. */ private void updateSplitBounds(@NonNull Rect parentBounds) { parentBounds.splitVertically(mPrimaryBounds, mSideBounds); mPrimaryRelativeBounds.set(mPrimaryBounds); mPrimaryRelativeBounds.offsetTo(0, 0); mSideRelativeBounds.set(mSideBounds); mSideRelativeBounds.offsetTo(mSideBounds.left - mPrimaryBounds.left, mSideBounds.top - mPrimaryBounds.top); } /** * Verifies the behavior to launch Activity in the same TaskFragment as the owner Activity. ** For example, given that Activity A and B are showed side-by-side, this test verifies * the behavior to launch Activity C in the same TaskFragment as Activity A: *
* |A|B| -> |C|B|
*
*/
@Test
public void testActivityLaunchInSameSplitTaskFragment() {
// Initialize test environment by launching Activity A and B side-by-side.
initializeSplitActivities();
final IBinder taskFragTokenA = mTaskFragA.getTaskFragToken();
final WindowContainerTransaction wct = new WindowContainerTransaction()
.startActivityInTaskFragment(taskFragTokenA, mOwnerToken, mIntent,
null /* activityOptions */);
mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_OPEN,
false /* shouldApplyIndependently */);
final TaskFragmentInfo infoA = mTaskFragmentOrganizer.waitForAndGetTaskFragmentInfo(
taskFragTokenA, info -> info.getActivities().size() == 2,
"getActivities from TaskFragment A must contain 2 activities");
assertNotEmptyTaskFragment(infoA, taskFragTokenA, mOwnerToken);
waitAndAssertResumedActivity(mActivityC, "Activity C must be resumed.");
waitAndAssertActivityState(mActivityA, STATE_STOPPED,
"Activity A is occluded by Activity C, so it must be stopped.");
waitAndAssertResumedActivity(mActivityB, "Activity B must be resumed.");
final TaskFragment taskFragmentA = mWmState.getTaskFragmentByActivity(mActivityA);
assertWithMessage("TaskFragmentA must contain Activity A and C")
.that(taskFragmentA.mActivities).containsExactly(mWmState.getActivity(mActivityA),
mWmState.getActivity(mActivityC));
}
/**
* Verifies the behavior to launch Activity in the adjacent TaskFragment.
* * For example, given that Activity A and B are showed side-by-side, this test verifies * the behavior to launch Activity C in the same TaskFragment as Activity B: *
* |A|B| -> |A|C|
*
*/
@Test
public void testActivityLaunchInAdjacentSplitTaskFragment() {
// Initialize test environment by launching Activity A and B side-by-side.
initializeSplitActivities();
final IBinder taskFragTokenB = mTaskFragB.getTaskFragToken();
final WindowContainerTransaction wct = new WindowContainerTransaction()
.startActivityInTaskFragment(taskFragTokenB, mOwnerToken, mIntent,
null /* activityOptions */);
mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_OPEN,
false /* shouldApplyIndependently */);
final TaskFragmentInfo infoB = mTaskFragmentOrganizer.waitForAndGetTaskFragmentInfo(
taskFragTokenB, info -> info.getActivities().size() == 2,
"getActivities from TaskFragment A must contain 2 activities");
assertNotEmptyTaskFragment(infoB, taskFragTokenB);
waitAndAssertResumedActivity(mActivityC, "Activity C must be resumed.");
waitAndAssertResumedActivity(mActivityA, "Activity A must be resumed.");
waitAndAssertActivityState(mActivityB, STATE_STOPPED,
"Activity B is occluded by Activity C, so it must be stopped.");
final TaskFragment taskFragmentB = mWmState.getTaskFragmentByActivity(mActivityB);
assertWithMessage("TaskFragmentB must contain Activity B and C")
.that(taskFragmentB.mActivities).containsExactly(mWmState.getActivity(mActivityB),
mWmState.getActivity(mActivityC));
}
/**
* Verifies the behavior that the Activity instance in bottom TaskFragment calls
* {@link Context#startActivity(Intent)} to launch another Activity.
* * For example, given that Activity A and B are showed side-by-side, Activity A calls * {@link Context#startActivity(Intent)} to launch Activity C. The expected behavior is that * Activity C will be launch on top of Activity B as below: *
* |A|B| -> |A|C|
*
* The reason is that TaskFragment B has higher z-order than TaskFragment A because we create
* TaskFragment B later than TaskFragment A.
*
*/
@Test
public void testActivityLaunchFromBottomTaskFragment() {
// Initialize test environment by launching Activity A and B side-by-side.
initializeSplitActivities();
mOwnerActivity.startActivity(mIntent);
final IBinder taskFragTokenB = mTaskFragB.getTaskFragToken();
final TaskFragmentInfo infoB = mTaskFragmentOrganizer.waitForAndGetTaskFragmentInfo(
taskFragTokenB, info -> info.getActivities().size() == 2,
"getActivities from TaskFragment A must contain 2 activities");
assertNotEmptyTaskFragment(infoB, taskFragTokenB);
waitAndAssertResumedActivity(mActivityC, "Activity C must be resumed.");
waitAndAssertResumedActivity(mActivityA, "Activity A must be resumed.");
waitAndAssertActivityState(mActivityB, STATE_STOPPED,
"Activity B is occluded by Activity C, so it must be stopped.");
final TaskFragment taskFragmentB = mWmState.getTaskFragmentByActivity(mActivityB);
assertWithMessage("TaskFragmentB must contain Activity B and C")
.that(taskFragmentB.mActivities).containsExactly(mWmState.getActivity(mActivityB),
mWmState.getActivity(mActivityC));
}
/**
* Verifies the behavior of the activities in a TaskFragment that is sandwiched in adjacent
* TaskFragments.
*/
@Test
public void testSandwichTaskFragmentInAdjacent() {
// Initialize test environment by launching Activity A and B side-by-side.
initializeSplitActivities();
final IBinder taskFragTokenA = mTaskFragA.getTaskFragToken();
final TaskFragmentCreationParams paramsC = generateSideTaskFragParams();
final IBinder taskFragTokenC = paramsC.getFragmentToken();
final WindowContainerTransaction wct = new WindowContainerTransaction()
// Create the side TaskFragment for C and launch
.createTaskFragment(paramsC)
.startActivityInTaskFragment(taskFragTokenC, mOwnerToken, mIntent,
null /* activityOptions */)
.setAdjacentTaskFragments(taskFragTokenA, taskFragTokenC, null /* options */);
mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_OPEN,
false /* shouldApplyIndependently */);
// Wait for the TaskFragment of Activity C to be created.
mTaskFragmentOrganizer.waitForTaskFragmentCreated();
waitAndAssertResumedActivity(mActivityC, "Activity C must be resumed.");
waitAndAssertActivityState(mActivityB, STATE_STOPPED,
"Activity B is occluded by Activity C, so it must be stopped.");
waitAndAssertResumedActivity(mActivityA, "Activity A must be resumed.");
}
/**
* Verifies the behavior of the activities in a TaskFragment that is sandwiched in adjacent
* TaskFragments. It should be hidden even if part of it is not cover by the adjacent
* TaskFragment above.
*/
@Test
public void testSandwichTaskFragmentInAdjacent_partialOccluding() {
// Initialize test environment by launching Activity A and B side-by-side.
initializeSplitActivities();
final IBinder taskFragTokenA = mTaskFragA.getTaskFragToken();
// TaskFragment C is not fully occluding TaskFragment B.
final Rect partialOccludingRelativeSideBounds = new Rect(mSideRelativeBounds);
partialOccludingRelativeSideBounds.left += 50;
final TaskFragmentCreationParams paramsC = mTaskFragmentOrganizer.generateTaskFragParams(
mOwnerToken, partialOccludingRelativeSideBounds, WINDOWING_MODE_MULTI_WINDOW);
final IBinder taskFragTokenC = paramsC.getFragmentToken();
final WindowContainerTransaction wct = new WindowContainerTransaction()
// Create the side TaskFragment for C and launch
.createTaskFragment(paramsC)
.startActivityInTaskFragment(taskFragTokenC, mOwnerToken, mIntent,
null /* activityOptions */)
.setAdjacentTaskFragments(taskFragTokenA, taskFragTokenC, null /* options */);
mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_OPEN,
false /* shouldApplyIndependently */);
// Wait for the TaskFragment of Activity C to be created.
mTaskFragmentOrganizer.waitForTaskFragmentCreated();
waitAndAssertResumedActivity(mActivityC, "Activity C must be resumed.");
waitAndAssertActivityState(mActivityB, STATE_STOPPED,
"Activity B is occluded by Activity C, so it must be stopped.");
waitAndAssertResumedActivity(mActivityA, "Activity A must be resumed.");
}
/**
* Verifies the behavior to launch adjacent Activity to the adjacent TaskFragment.
* * For example, given that Activity A and B are showed side-by-side, this test verifies * the behavior to launch the Activity C to the adjacent TaskFragment of the secondary * TaskFragment, which Activity B is attached to. Then the secondary TaskFragment is shifted to * occlude the primary TaskFragment, which Activity A is attached to, and the adjacent * TaskFragment, which Activity C is attached to, is occupied the region where the secondary * TaskFragment is located. This test is to verify the "shopping mode" scenario. *
* |A|B| -> |B|C|
*
*/
@Test
public void testAdjacentActivityLaunchFromSecondarySplitTaskFragment() {
// Initialize test environment by launching Activity A and B side-by-side.
initializeSplitActivities();
final IBinder taskFragTokenB = mTaskFragB.getTaskFragToken();
final TaskFragmentCreationParams paramsC = generateSideTaskFragParams();
final IBinder taskFragTokenC = paramsC.getFragmentToken();
final WindowContainerTransaction wct = new WindowContainerTransaction()
// Move TaskFragment B to the primaryBounds
.setRelativeBounds(mTaskFragB.getToken(), mPrimaryRelativeBounds)
// Create the side TaskFragment for C and launch
.createTaskFragment(paramsC)
.startActivityInTaskFragment(taskFragTokenC, mOwnerToken, mIntent,
null /* activityOptions */)
.setAdjacentTaskFragments(taskFragTokenB, taskFragTokenC, null /* options */);
mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE,
false /* shouldApplyIndependently */);
// Wait for the TaskFragment of Activity C to be created.
mTaskFragmentOrganizer.waitForTaskFragmentCreated();
// Wait for the TaskFragment of Activity B to be changed.
mTaskFragmentOrganizer.waitForTaskFragmentInfoChanged();
final TaskFragmentInfo infoB = mTaskFragmentOrganizer.getTaskFragmentInfo(taskFragTokenB);
final TaskFragmentInfo infoC = mTaskFragmentOrganizer.getTaskFragmentInfo(taskFragTokenC);
assertNotEmptyTaskFragment(infoB, taskFragTokenB);
assertNotEmptyTaskFragment(infoC, taskFragTokenC);
mTaskFragB = new TaskFragmentRecord(infoB);
final TaskFragmentRecord taskFragC = new TaskFragmentRecord(infoC);
assertThat(mTaskFragB.getBounds()).isEqualTo(mPrimaryBounds);
assertThat(taskFragC.getBounds()).isEqualTo(mSideBounds);
waitAndAssertResumedActivity(mActivityC, "Activity C must be resumed.");
waitAndAssertActivityState(mActivityA, STATE_STOPPED,
"Activity A is occluded by Activity C, so it must be stopped.");
waitAndAssertResumedActivity(mActivityB, "Activity B must be resumed.");
}
/**
* Verifies the behavior to launch Activity in expanded TaskFragment.
* * For example, given that Activity A and B are showed side-by-side, this test verifies * the behavior to launch Activity C in the TaskFragment which fills the Task bounds of owner * Activity: *
* |A|B| -> |C|
*
*/
@Test
public void testActivityLaunchInExpandedTaskFragment() {
// Initialize test environment by launching Activity A and B side-by-side.
initializeSplitActivities();
testActivityLaunchInExpandedTaskFragmentInternal();
}
private void testActivityLaunchInExpandedTaskFragmentInternal() {
final TaskFragmentCreationParams fullScreenParamsC = mTaskFragmentOrganizer
.generateTaskFragParams(mOwnerToken, new Rect(), WINDOWING_MODE_FULLSCREEN);
final IBinder taskFragTokenC = fullScreenParamsC.getFragmentToken();
final WindowContainerTransaction wct = new WindowContainerTransaction()
.createTaskFragment(fullScreenParamsC)
.startActivityInTaskFragment(taskFragTokenC, mOwnerToken, mIntent,
null /* activityOptions */);
mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_OPEN,
false /* shouldApplyIndependently */);
mTaskFragmentOrganizer.waitForTaskFragmentCreated();
assertNotEmptyTaskFragment(mTaskFragmentOrganizer.getTaskFragmentInfo(taskFragTokenC),
taskFragTokenC);
waitAndAssertResumedActivity(mActivityC, "Activity C must be resumed.");
waitAndAssertActivityState(mActivityA, STATE_STOPPED,
"Activity A is occluded by Activity C, so it must be stopped.");
waitAndAssertActivityState(mActivityB, STATE_STOPPED,
"Activity B is occluded by Activity C, so it must be stopped.");
}
/**
* Verifies the show-when-locked behavior while launch embedded activities. Don't show the
* embedded activities even if one of Activity has showWhenLocked flag.
*/
@Test
public void testLaunchEmbeddedActivityWithShowWhenLocked() {
assumeTrue(supportsLockScreen());
final LockScreenSession lockScreenSession = createManagedLockScreenSession();
// Initialize test environment by launching Activity A and B (with showWhenLocked)
// side-by-side.
initializeSplitActivities(true /* showWhenLocked */);
lockScreenSession.sleepDevice();
lockScreenSession.wakeUpDevice();
waitAndAssertActivityState(mActivityA, STATE_STOPPED,"Activity A must be stopped");
waitAndAssertActivityState(mActivityB, STATE_STOPPED,"Activity B must be stopped");
}
/**
* Verifies the show-when-locked behavior while launch embedded activities. Don't show the
* embedded activities if the activities don't have showWhenLocked flag.
*/
@Test
public void testLaunchEmbeddedActivitiesWithoutShowWhenLocked() {
assumeTrue(supportsLockScreen());
final LockScreenSession lockScreenSession = createManagedLockScreenSession();
// Initialize test environment by launching Activity A and B side-by-side.
initializeSplitActivities();
lockScreenSession.sleepDevice();
lockScreenSession.wakeUpDevice();
waitAndAssertActivityState(mActivityA, STATE_STOPPED,"Activity A must be stopped");
waitAndAssertActivityState(mActivityB, STATE_STOPPED,"Activity B must be stopped");
}
/**
* Verifies the show-when-locked behavior while launch embedded activities. The embedded
* activities should be shown on top of the lock screen since they have the showWhenLocked flag.
* Don't show the embedded activities even if one of Activity has showWhenLocked flag.
*/
@Test
public void testLaunchEmbeddedActivitiesWithShowWhenLocked() {
assumeTrue(supportsLockScreen());
final LockScreenSession lockScreenSession = createManagedLockScreenSession();
// Initialize test environment by launching Activity A and B side-by-side.
mOwnerActivity.setShowWhenLocked(true);
initializeSplitActivities(true /* showWhenLocked */);
lockScreenSession.sleepDevice();
lockScreenSession.wakeUpDevice();
waitAndAssertResumedActivity(mActivityA, "Activity A must be resumed.");
waitAndAssertResumedActivity(mActivityB, "Activity B must be resumed.");
// Launch Activity C without show-when-lock and verifies that both activities are stopped.
mOwnerActivity.startActivity(mIntent);
waitAndAssertActivityState(mActivityA, STATE_STOPPED, "Activity A must be stopped");
waitAndAssertActivityState(mActivityC, STATE_STOPPED, "Activity C must be stopped");
}
/**
* Verifies the Activity in primary TaskFragment is no longer focused after clear adjacent
* TaskFragments.
*/
@Test
public void testResetFocusedAppAfterClearAdjacentTaskFragment() {
// Initialize test environment by launching Activity A and B side-by-side.
initializeSplitActivities();
// Request the focus on the primary TaskFragment
WindowContainerTransaction wct = new WindowContainerTransaction()
.requestFocusOnTaskFragment(mTaskFragA.getTaskFragToken());
mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE,
false /* shouldApplyIndependently */);
waitForActivityFocused(5000, mActivityA);
assertThat(mWmState.getFocusedApp()).isEqualTo(mActivityA.flattenToShortString());
// Expand top TaskFragment and clear the adjacent TaskFragments to have the two
// TaskFragment stacked.
wct = new WindowContainerTransaction()
.setRelativeBounds(mTaskFragB.getToken(), new Rect())
.setWindowingMode(mTaskFragB.getToken(), WINDOWING_MODE_UNDEFINED)
.clearAdjacentTaskFragments(mTaskFragA.getTaskFragToken());
mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE,
false /* shouldApplyIndependently */);
// Ensure the Activity on primary TaskFragment is stopped and no longer focused.
waitAndAssertActivityState(mActivityA, STATE_STOPPED, "Activity A must be stopped");
assertThat(mWmState.getFocusedApp()).isNotEqualTo(mActivityA.flattenToShortString());
assertThat(mWmState.getFocusedWindow()).isEqualTo(mActivityB.flattenToShortString());
}
/**
* Verifies an Activity below adjacent translucent TaskFragments is visible.
*/
@Test
public void testTranslucentAdjacentTaskFragment() {
// Create ActivityB on top of ActivityA.
// Make sure ActivityB is launched into the same task as ActivityA so that we can reparent
// it to TaskFragment in the same task later.
Activity activityB = startActivity(ActivityB.class, DEFAULT_DISPLAY, true /* hasFocus */,
WINDOWING_MODE_FULLSCREEN, mOwnerActivity.getTaskId());
waitAndAssertResumedActivity(mActivityB, "Activity B must be resumed.");
waitAndAssertActivityState(mActivityA, STATE_STOPPED,
"Activity A is occluded by Activity B, so it must be stopped.");
// Create two adjacent TaskFragments, making ActivityB and TranslucentActivity
// displayed side-by-side (ActivityB|TranslucentActivity).
updateSplitBounds(mOwnerActivity.getWindowManager().getCurrentWindowMetrics().getBounds());
final TaskFragmentCreationParams primaryParams = generatePrimaryTaskFragParams();
final TaskFragmentCreationParams secondaryParams = generateSideTaskFragParams();
IBinder primaryToken = primaryParams.getFragmentToken();
IBinder secondaryToken = secondaryParams.getFragmentToken();
final ComponentName translucentActivity = new ComponentName(mContext,
TranslucentActivity.class);
final Intent intent = new Intent().setComponent(translucentActivity);
WindowContainerTransaction wct = new WindowContainerTransaction()
.createTaskFragment(primaryParams)
.reparentActivityToTaskFragment(primaryToken, getActivityToken(activityB))
.createTaskFragment(secondaryParams)
.setAdjacentTaskFragments(primaryToken, secondaryToken, null /* params */)
.startActivityInTaskFragment(secondaryToken, mOwnerToken, intent,
null /* activityOptions */);
mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE,
false /* shouldApplyIndependently */);
waitAndAssertResumedActivity(translucentActivity, "TranslucentActivity must be resumed.");
waitAndAssertResumedActivity(mActivityB, "Activity B must be resumed.");
waitAndAssertActivityState(mActivityA, STATE_STARTED,
"Activity A is not fully occluded and must be visible and started");
}
@Test
public void testIgnoreOrientationRequestForActivityEmbeddingSplits() {
// Skip the test on devices without WM extensions.
assumeTrue(SystemProperties.getBoolean("persist.wm.extensions.enabled", false));
// Skip the test if this is not a large screen device
assumeTrue(getDisplayConfiguration().smallestScreenWidthDp
>= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP);
// Rotate the device to landscape
final RotationSession rotationSession = createManagedRotationSession();
final int[] rotations = { ROTATION_0, ROTATION_90 };
for (final int rotation : rotations) {
if (getDisplayConfiguration().orientation == ORIENTATION_LANDSCAPE) {
break;
}
rotationSession.set(rotation);
}
assumeTrue(getDisplayConfiguration().orientation == ORIENTATION_LANDSCAPE);
// Launch a fixed-portrait activity
Activity activity = startActivityInWindowingModeFullScreen(PortraitActivity.class);
// The activity should be displayed in portrait while the display is remained in landscape.
assertWithMessage("The activity should be displayed in portrait")
.that(activity.getResources().getConfiguration().orientation)
.isEqualTo(ORIENTATION_PORTRAIT);
assertWithMessage("The display should be remained in landscape")
.that(getDisplayConfiguration().orientation)
.isEqualTo(ORIENTATION_LANDSCAPE);
}
private Configuration getDisplayConfiguration() {
mWmState.computeState();
WindowManagerState.DisplayContent display = mWmState.getDisplay(DEFAULT_DISPLAY);
return display.mFullConfiguration;
}
/**
* Verifies starting an Activity on the adjacent TaskFragment and able to get the result.
*/
@Test
public void testStartActivityForResultInAdjacentTaskFragment() {
// Initialize test environment by launching Activity A and B side-by-side.
initializeSplitActivities();
// Start an Activity on the adjacent TaskFragment for result.
final Intent intent = new Intent();
intent.setComponent(mActivityC);
intent.putExtra(EXTRA_SET_RESULT_AND_FINISH, true);
mOwnerActivity.startActivityForResult(intent, 1 /* requestCode */);
// Waits for the result
waitForOrFail("Wait for the result",
() -> ((SplitTestActivity) mOwnerActivity).getResultCode() == 100);
}
private TaskFragmentCreationParams generatePrimaryTaskFragParams() {
return mTaskFragmentOrganizer.generateTaskFragParams(mOwnerToken, mPrimaryRelativeBounds,
WINDOWING_MODE_MULTI_WINDOW);
}
private TaskFragmentCreationParams generateSideTaskFragParams() {
return mTaskFragmentOrganizer.generateTaskFragParams(mOwnerToken, mSideRelativeBounds,
WINDOWING_MODE_MULTI_WINDOW);
}
private static class TaskFragmentRecord {
private final IBinder mTaskFragToken;
private final Rect mBounds = new Rect();
private final WindowContainerToken mContainerToken;
private TaskFragmentRecord(TaskFragmentInfo info) {
mTaskFragToken = info.getFragmentToken();
mBounds.set(info.getConfiguration().windowConfiguration.getBounds());
mContainerToken = info.getToken();
}
private IBinder getTaskFragToken() {
return mTaskFragToken;
}
private Rect getBounds() {
return mBounds;
}
private WindowContainerToken getToken() {
return mContainerToken;
}
}
public static class ActivityA extends SplitTestActivity {}
public static class ActivityB extends SplitTestActivity {}
public static class ActivityC extends SplitTestActivity {}
public static class PortraitActivity extends SplitTestActivity {}
public static class TranslucentActivity extends SplitTestActivity {}
public static class SplitTestActivity extends FocusableActivity {
public static final String EXTRA_SHOW_WHEN_LOCKED = "showWhenLocked";
public static final String EXTRA_SET_RESULT_AND_FINISH = "setResultAndFinish";
private int mResultCode = -1;
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
if (getIntent().getBooleanExtra(EXTRA_SHOW_WHEN_LOCKED, false)) {
setShowWhenLocked(true);
}
}
@Override
protected void onResume() {
super.onResume();
if (getIntent().getBooleanExtra(EXTRA_SET_RESULT_AND_FINISH, false)) {
setResult(100);
finish();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
mResultCode = resultCode;
}
public int getResultCode() {
return mResultCode;
}
}
}