/* * 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_MULTI_WINDOW; import static android.server.wm.WindowManagerState.STATE_RESUMED; import static android.server.wm.WindowManagerState.STATE_STOPPED; import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE; import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CLOSE; 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 android.app.Activity; import android.content.ComponentName; import android.content.Intent; import android.graphics.Rect; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.platform.test.annotations.Presubmit; import android.server.wm.WindowManagerState.Task; import android.server.wm.WindowManagerState.TaskFragment; import android.window.TaskFragmentCreationParams; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOrganizer; import android.window.WindowContainerTransaction; import com.android.compatibility.common.util.ApiTest; import org.junit.Test; /** * Tests that verify the behavior of {@link TaskFragmentOrganizer}. * * Build/Install/Run: * atest CtsWindowManagerDeviceTestCases:TaskFragmentOrganizerTest */ @Presubmit @android.server.wm.annotation.Group2 public class TaskFragmentOrganizerTest extends TaskFragmentOrganizerTestBase { private final ComponentName mLaunchingActivity = new ComponentName(mContext, WindowMetricsActivityTests.MetricsActivity.class); /** * Verifies the behavior of * {@link WindowContainerTransaction#createTaskFragment(TaskFragmentCreationParams)} to create * TaskFragment. */ @Test public void testCreateTaskFragment() { mWmState.computeState(mOwnerActivityName); Task parentTask = mWmState.getRootTask(mOwnerActivity.getTaskId()); final int originalTaskFragCount = parentTask.getTaskFragments().size(); final IBinder taskFragToken = new Binder(); final Rect bounds = new Rect(0, 0, 1000, 1000); final int windowingMode = WINDOWING_MODE_MULTI_WINDOW; final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder( mTaskFragmentOrganizer.getOrganizerToken(), taskFragToken, mOwnerToken) .setInitialRelativeBounds(bounds) .setWindowingMode(windowingMode) .build(); final WindowContainerTransaction wct = new WindowContainerTransaction() .createTaskFragment(params); mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_OPEN, false /* shouldApplyIndependently */); mTaskFragmentOrganizer.waitForTaskFragmentCreated(); final TaskFragmentInfo info = mTaskFragmentOrganizer.getTaskFragmentInfo(taskFragToken); assertEmptyTaskFragment(info, taskFragToken); assertThat(info.getConfiguration().windowConfiguration.getBounds()).isEqualTo(bounds); assertThat(info.getWindowingMode()).isEqualTo(windowingMode); mWmState.computeState(mOwnerActivityName); parentTask = mWmState.getRootTask(mOwnerActivity.getTaskId()); final int curTaskFragCount = parentTask.getTaskFragments().size(); assertWithMessage("There must be a TaskFragment created under Task#" + mOwnerTaskId).that(curTaskFragCount - originalTaskFragCount) .isEqualTo(1); } /** * Verifies the behavior of * {@link WindowContainerTransaction#reparentActivityToTaskFragment(IBinder, IBinder)} to * reparent {@link Activity} to TaskFragment. */ @Test public void testReparentActivity() { mWmState.computeState(mOwnerActivityName); final TaskFragmentCreationParams params = generateTaskFragCreationParams(); final IBinder taskFragToken = params.getFragmentToken(); final WindowContainerTransaction wct = new WindowContainerTransaction() .createTaskFragment(params) .reparentActivityToTaskFragment(taskFragToken, mOwnerToken); mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */); mTaskFragmentOrganizer.waitForTaskFragmentCreated(); assertNotEmptyTaskFragment(mTaskFragmentOrganizer.getTaskFragmentInfo(taskFragToken), taskFragToken, mOwnerToken); mWmState.waitForActivityState(mOwnerActivityName, STATE_RESUMED); final Task parentTask = mWmState.getTaskByActivity(mOwnerActivityName); final TaskFragment taskFragment = mWmState.getTaskFragmentByActivity(mOwnerActivityName); // Assert window hierarchy must be as follows // - owner Activity's Task (parentTask) // - taskFragment // - owner Activity assertWindowHierarchy(parentTask, taskFragment, mWmState.getActivity(mOwnerActivityName)); } /** * Verifies the behavior of * {@link WindowContainerTransaction#startActivityInTaskFragment(IBinder, IBinder, Intent, * Bundle)} to start Activity in TaskFragment without creating new Task. */ @Test public void testStartActivityInTaskFragment_reuseTask() { final TaskFragmentCreationParams params = generateTaskFragCreationParams(); final IBinder taskFragToken = params.getFragmentToken(); final WindowContainerTransaction wct = new WindowContainerTransaction() .createTaskFragment(params) .startActivityInTaskFragment(taskFragToken, mOwnerToken, new Intent().setComponent(mLaunchingActivity), null /* activityOptions */); mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_OPEN, false /* shouldApplyIndependently */); mTaskFragmentOrganizer.waitForTaskFragmentCreated(); TaskFragmentInfo info = mTaskFragmentOrganizer.getTaskFragmentInfo(taskFragToken); assertNotEmptyTaskFragment(info, taskFragToken); mWmState.waitForActivityState(mLaunchingActivity, STATE_RESUMED); Task parentTask = mWmState.getTaskByActivity(mOwnerActivityName); TaskFragment taskFragment = mWmState.getTaskFragmentByActivity(mLaunchingActivity); // Assert window hierarchy must be as follows // - owner Activity's Task (parentTask) // - taskFragment // - LAUNCHING_ACTIVITY // - owner Activity assertWindowHierarchy(parentTask, taskFragment, mWmState.getActivity(mLaunchingActivity)); assertWindowHierarchy(parentTask, mWmState.getActivity(mOwnerActivityName)); assertWithMessage("The owner Activity's Task must be reused as" + " the launching Activity's Task.").that(parentTask) .isEqualTo(mWmState.getTaskByActivity(mLaunchingActivity)); } /** * Verifies the behavior of {@link WindowContainerTransaction#deleteTaskFragment} to remove the * organized TaskFragment. */ @Test @ApiTest(apis = { "android.window.WindowContainerTransaction#deleteTaskFragment"}) public void testDeleteTaskFragment() { final TaskFragmentInfo taskFragmentInfo = createTaskFragment(null); final IBinder taskFragToken = taskFragmentInfo.getFragmentToken(); assertEmptyTaskFragment(taskFragmentInfo, taskFragmentInfo.getFragmentToken()); mWmState.computeState(mOwnerActivityName); final int originalTaskFragCount = mWmState.getTaskByActivity(mOwnerActivityName) .getTaskFragments().size(); WindowContainerTransaction wct = new WindowContainerTransaction() .deleteTaskFragment(taskFragToken); mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CLOSE, false /* shouldApplyIndependently */); mTaskFragmentOrganizer.waitForTaskFragmentRemoved(); assertEmptyTaskFragment(mTaskFragmentOrganizer.getRemovedTaskFragmentInfo(taskFragToken), taskFragToken); mWmState.computeState(mOwnerActivityName); final int currTaskFragCount = mWmState.getTaskByActivity(mOwnerActivityName) .getTaskFragments().size(); assertWithMessage("TaskFragment with token " + taskFragToken + " must be" + " removed.").that(originalTaskFragCount - currTaskFragCount).isEqualTo(1); } /** * Verifies the behavior of {@link WindowContainerTransaction#deleteTaskFragment} to remove the * organized TaskFragment with activity embedded. */ @Test @ApiTest(apis = { "android.window.WindowContainerTransaction#deleteTaskFragment", "android.window.TaskFragmentOrganizer#onTransactionReady"}) public void testDeleteTaskFragmentWithActivity() { final TaskFragmentInfo taskFragmentInfo = createTaskFragment(mLaunchingActivity); final IBinder taskFragToken = taskFragmentInfo.getFragmentToken(); assertNotEmptyTaskFragment(taskFragmentInfo, taskFragmentInfo.getFragmentToken()); mWmState.computeState(mOwnerActivityName); final int originalTaskFragCount = mWmState.getTaskByActivity(mOwnerActivityName) .getTaskFragments().size(); WindowContainerTransaction wct = new WindowContainerTransaction() .deleteTaskFragment(taskFragToken); mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CLOSE, false /* shouldApplyIndependently */); mTaskFragmentOrganizer.waitForTaskFragmentRemoved(); assertEmptyTaskFragment(mTaskFragmentOrganizer.getRemovedTaskFragmentInfo(taskFragToken), taskFragToken); mWmState.computeState(mOwnerActivityName); final int currTaskFragCount = mWmState.getTaskByActivity(mOwnerActivityName) .getTaskFragments().size(); assertWithMessage("TaskFragment with token " + taskFragToken + " must be" + " removed.").that(originalTaskFragCount - currTaskFragCount).isEqualTo(1); } /** * Verifies the behavior of {@link WindowContainerTransaction#finishActivity(IBinder)} to finish * an Activity. */ @Test @ApiTest(apis = { "android.window.TaskFragmentOrganizer#applyTransaction", "android.window.WindowContainerTransaction#finishActivity"}) public void testFinishActivity() { final Activity activity = startNewActivity( WindowMetricsActivityTests.MetricsActivity.class); // Make sure mLaunchingActivity is mapping to the correct component that is started. mWmState.waitAndAssertActivityState(mLaunchingActivity, STATE_RESUMED); final WindowContainerTransaction wct = new WindowContainerTransaction() .finishActivity(getActivityToken(activity)); mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CLOSE, false /* shouldApplyIndependently */); mWmState.waitAndAssertActivityRemoved(mLaunchingActivity); } /** * Verifies the visibility of an activity behind a TaskFragment that has the same * bounds of the host Task. */ @Test public void testActivityVisibilityBehindTaskFragment() { // Start an activity and reparent it to a TaskFragment. final Activity embeddedActivity = startActivity(WindowMetricsActivityTests.MetricsActivity.class); final IBinder embeddedActivityToken = getActivityToken(embeddedActivity); final TaskFragmentCreationParams params = generateTaskFragCreationParams(); final IBinder taskFragToken = params.getFragmentToken(); final WindowContainerTransaction wct = new WindowContainerTransaction() .createTaskFragment(params) .reparentActivityToTaskFragment(taskFragToken, embeddedActivityToken); mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */); mTaskFragmentOrganizer.waitForTaskFragmentCreated(); // The activity below must be occluded and stopped. waitAndAssertActivityState(mOwnerActivityName, STATE_STOPPED, "Activity must be stopped"); // Finishing the top activity and remain the TaskFragment on top. The next top activity // must be resumed. embeddedActivity.finish(); waitAndAssertResumedActivity(mOwnerActivityName, "Activity must be resumed"); } }