/* * 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.ActivityManager.LOCK_TASK_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.server.wm.TaskFragmentOrganizerTestBase.assertEmptyTaskFragment; import static android.server.wm.TaskFragmentOrganizerTestBase.getActivityToken; import static android.server.wm.TaskFragmentOrganizerTestBase.startNewActivity; import static android.server.wm.WindowManagerState.STATE_RESUMED; import static android.server.wm.app.Components.LAUNCHING_ACTIVITY; import static android.server.wm.app30.Components.SDK_30_TEST_ACTIVITY; 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 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import android.app.Activity; import android.content.ComponentName; import android.content.Intent; import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; import android.platform.test.annotations.Presubmit; import android.server.wm.TaskFragmentOrganizerTestBase.BasicTaskFragmentOrganizer; import android.server.wm.WindowContextTests.TestActivity; import android.server.wm.WindowManagerState.Task; import android.view.SurfaceControl; import android.window.TaskAppearedInfo; import android.window.TaskFragmentCreationParams; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOrganizer; import android.window.TaskOrganizer; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import android.window.WindowContainerTransactionCallback; import androidx.annotation.NonNull; import androidx.test.runner.AndroidJUnit4; import com.android.compatibility.common.util.ApiTest; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; /** * Tests that verify the behavior of {@link TaskFragmentOrganizer} policy. * * Build/Install/Run: * atest CtsWindowManagerDeviceTestCases:TaskFragmentOrganizerPolicyTest */ @RunWith(AndroidJUnit4.class) @Presubmit @android.server.wm.annotation.Group2 public class TaskFragmentOrganizerPolicyTest extends ActivityManagerTestBase { private TaskOrganizer mTaskOrganizer; private BasicTaskFragmentOrganizer mTaskFragmentOrganizer; private final ArrayList mOrganizers = new ArrayList<>(); @Before @Override public void setUp() throws Exception { super.setUp(); mTaskFragmentOrganizer = new BasicTaskFragmentOrganizer(); mTaskFragmentOrganizer.registerOrganizer(); mOrganizers.add(mTaskFragmentOrganizer); } @After public void tearDown() { for (TaskFragmentOrganizer organizer : mOrganizers) { organizer.unregisterOrganizer(); } mOrganizers.clear(); if (mTaskOrganizer != null) { NestedShellPermission.run(() -> mTaskOrganizer.unregisterOrganizer()); } } /** * Verifies that performing {@link WindowContainerTransaction#createTaskFragment} will fail if * the fragment token is not unique. */ @Test @ApiTest(apis = { "android.window.TaskFragmentOrganizer#applyTransaction", "android.window.WindowContainerTransaction#createTaskFragment"}) public void testCreateTaskFragment_duplicatedFragmentToken_reportError() { final Activity activity = startNewActivity(); final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); final IBinder existingFragmentToken = taskFragmentInfo.getFragmentToken(); final IBinder errorCallbackToken = new Binder(); // Request to create another TaskFragment using the existing fragment token. final TaskFragmentCreationParams params = mTaskFragmentOrganizer.generateTaskFragParams( existingFragmentToken, getActivityToken(activity), new Rect(), WINDOWING_MODE_UNDEFINED); final WindowContainerTransaction wct = new WindowContainerTransaction() .setErrorCallbackToken(errorCallbackToken) .createTaskFragment(params); mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_OPEN, false /* shouldApplyIndependently */); mTaskFragmentOrganizer.waitForTaskFragmentError(); assertThat(mTaskFragmentOrganizer.getThrowable()).isInstanceOf( IllegalArgumentException.class); assertThat(mTaskFragmentOrganizer.getErrorCallbackToken()).isEqualTo(errorCallbackToken); } /** * Verifies that performing {@link WindowContainerTransaction#deleteTaskFragment} on * non-organized TaskFragment will throw {@link SecurityException}. */ @Test @ApiTest(apis = { "android.window.TaskFragmentOrganizer#applyTransaction", "android.window.WindowContainerTransaction#deleteTaskFragment"}) public void testDeleteTaskFragment_nonOrganizedTaskFragment_throwException() { final Activity activity = startNewActivity(); final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); // Create another TaskFragmentOrganizer to request operation. final TaskFragmentOrganizer anotherOrganizer = registerNewOrganizer(); final WindowContainerTransaction wct = new WindowContainerTransaction() .deleteTaskFragment(taskFragmentInfo.getFragmentToken()); assertThrows(SecurityException.class, () -> anotherOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CLOSE, false /* shouldApplyIndependently */)); } /** * Verifies that performing {@link WindowContainerTransaction#deleteTaskFragment} on organized * TaskFragment is allowed. */ @Test @ApiTest(apis = { "android.window.TaskFragmentOrganizer#applyTransaction", "android.window.WindowContainerTransaction#deleteTaskFragment"}) public void testDeleteTaskFragment_organizedTaskFragment() { final Activity activity = startNewActivity(); final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); final WindowContainerTransaction wct = new WindowContainerTransaction() .deleteTaskFragment(taskFragmentInfo.getFragmentToken()); mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CLOSE, false /* shouldApplyIndependently */); } /** * Verifies that performing {@link WindowContainerTransaction#startActivityInTaskFragment} on * non-organized TaskFragment will throw {@link SecurityException}. */ @Test @ApiTest(apis = { "android.window.TaskFragmentOrganizer#applyTransaction", "android.window.WindowContainerTransaction#startActivityInTaskFragment"}) public void testStartActivityInTaskFragment_nonOrganizedTaskFragment_throwException() { final Activity activity = startNewActivity(); final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); final IBinder fragmentToken = taskFragmentInfo.getFragmentToken(); final IBinder callerToken = getActivityToken(activity); final Intent intent = new Intent(mContext, TestActivity.class); // Create another TaskFragmentOrganizer to request operation. final TaskFragmentOrganizer anotherOrganizer = registerNewOrganizer(); final WindowContainerTransaction wct = new WindowContainerTransaction() .startActivityInTaskFragment(fragmentToken, callerToken, intent, null /* activityOptions */); assertThrows(SecurityException.class, () -> anotherOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_OPEN, false /* shouldApplyIndependently */)); } /** * Verifies that performing {@link WindowContainerTransaction#startActivityInTaskFragment} on * organized TaskFragment is allowed. */ @Test @ApiTest(apis = { "android.window.TaskFragmentOrganizer#applyTransaction", "android.window.WindowContainerTransaction#startActivityInTaskFragment"}) public void testStartActivityInTaskFragment_organizedTaskFragment() { final Activity activity = startNewActivity(); final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); final IBinder fragmentToken = taskFragmentInfo.getFragmentToken(); final IBinder callerToken = getActivityToken(activity); final Intent intent = new Intent(mContext, TestActivity.class); final WindowContainerTransaction wct = new WindowContainerTransaction() .startActivityInTaskFragment(fragmentToken, callerToken, intent, null /* activityOptions */); mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_OPEN, false /* shouldApplyIndependently */); } /** * Verifies that performing {@link WindowContainerTransaction#requestFocusOnTaskFragment} on * non-organized TaskFragment will throw {@link SecurityException}. */ @Test @ApiTest(apis = { "android.window.TaskFragmentOrganizer#applyTransaction", "android.window.WindowContainerTransaction#requestFocusOnTaskFragment"}) public void testRequestFocusOnTaskFragment_nonOrganizedTaskFragment_throwException() { final Activity activity = startNewActivity(); final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); final IBinder fragmentToken = taskFragmentInfo.getFragmentToken(); // Create another TaskFragmentOrganizer to request operation. final TaskFragmentOrganizer anotherOrganizer = registerNewOrganizer(); final WindowContainerTransaction wct = new WindowContainerTransaction() .requestFocusOnTaskFragment(fragmentToken); assertThrows(SecurityException.class, () -> anotherOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */)); } /** * Verifies that performing {@link WindowContainerTransaction#requestFocusOnTaskFragment} on * organized TaskFragment is allowed. */ @Test @ApiTest(apis = { "android.window.TaskFragmentOrganizer#applyTransaction", "android.window.WindowContainerTransaction#requestFocusOnTaskFragment"}) public void testRequestFocusOnTaskFragment_organizedTaskFragment() { final Activity activity = startNewActivity(); final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); final IBinder fragmentToken = taskFragmentInfo.getFragmentToken(); final WindowContainerTransaction wct = new WindowContainerTransaction() .requestFocusOnTaskFragment(fragmentToken); mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */); } /** * Verifies that performing {@link WindowContainerTransaction#reparentActivityToTaskFragment} on * non-organized TaskFragment will throw {@link SecurityException}. */ @Test @ApiTest(apis = { "android.window.TaskFragmentOrganizer#applyTransaction", "android.window.WindowContainerTransaction#reparentActivityToTaskFragment"}) public void testReparentActivityToTaskFragment_nonOrganizedTaskFragment_throwException() { final Activity activity = startNewActivity(); final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); final IBinder fragmentToken = taskFragmentInfo.getFragmentToken(); final IBinder activityToken = getActivityToken(activity); // Create another TaskFragmentOrganizer to request operation. final TaskFragmentOrganizer anotherOrganizer = registerNewOrganizer(); final WindowContainerTransaction wct = new WindowContainerTransaction() .reparentActivityToTaskFragment(fragmentToken, activityToken); assertThrows(SecurityException.class, () -> anotherOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */)); } /** * Verifies that performing {@link WindowContainerTransaction#reparentActivityToTaskFragment} on * organized TaskFragment is allowed. */ @Test @ApiTest(apis = { "android.window.TaskFragmentOrganizer#applyTransaction", "android.window.WindowContainerTransaction#startActivityInTaskFragment"}) public void testReparentActivityToTaskFragment_organizedTaskFragment() { final Activity activity = startNewActivity(); final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); final IBinder fragmentToken = taskFragmentInfo.getFragmentToken(); final IBinder activityToken = getActivityToken(activity); final WindowContainerTransaction wct = new WindowContainerTransaction() .reparentActivityToTaskFragment(fragmentToken, activityToken); mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */); } /** * Verifies that performing {@link WindowContainerTransaction#setAdjacentTaskFragments} on * non-organized TaskFragment will throw {@link SecurityException}. */ @Test @ApiTest(apis = { "android.window.TaskFragmentOrganizer#applyTransaction", "android.window.WindowContainerTransaction#setAdjacentTaskFragments"}) public void testSetAdjacentTaskFragments_nonOrganizedTaskFragment_throwException() { final Activity activity = startNewActivity(); final TaskFragmentInfo taskFragmentInfo0 = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); final TaskFragmentInfo taskFragmentInfo1 = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); final IBinder fragmentToken0 = taskFragmentInfo0.getFragmentToken(); final IBinder fragmentToken1 = taskFragmentInfo1.getFragmentToken(); // Create another TaskFragmentOrganizer to request operation. final TaskFragmentOrganizer anotherOrganizer = registerNewOrganizer(); final WindowContainerTransaction wct = new WindowContainerTransaction() .setAdjacentTaskFragments(fragmentToken0, fragmentToken1, null /* params */); assertThrows(SecurityException.class, () -> anotherOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */)); } /** * Verifies that performing {@link WindowContainerTransaction#setAdjacentTaskFragments} on * organized TaskFragment is allowed. */ @Test @ApiTest(apis = { "android.window.TaskFragmentOrganizer#applyTransaction", "android.window.WindowContainerTransaction#setAdjacentTaskFragments"}) public void testSetAdjacentTaskFragments_organizedTaskFragment() { final Activity activity = startNewActivity(); final TaskFragmentInfo taskFragmentInfo0 = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); final TaskFragmentInfo taskFragmentInfo1 = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); final IBinder fragmentToken0 = taskFragmentInfo0.getFragmentToken(); final IBinder fragmentToken1 = taskFragmentInfo1.getFragmentToken(); final WindowContainerTransaction wct = new WindowContainerTransaction() .setAdjacentTaskFragments(fragmentToken0, fragmentToken1, null /* params */); mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */); } /** * Verifies that changing property on non-TaskFragment window will throw * {@link SecurityException}. */ @Test @ApiTest(apis = { "android.window.TaskFragmentOrganizer#applyTransaction", "android.window.WindowContainerTransaction#setRelativeBounds", "android.window.WindowContainerTransaction#setWindowingMode", }) public void testSetProperty_nonTaskFragmentWindow_throwException() { final WindowContainerToken taskToken = getFirstTaskToken(); final WindowContainerTransaction wct0 = new WindowContainerTransaction() .setRelativeBounds(taskToken, new Rect()); assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct0, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */)); final WindowContainerTransaction wct1 = new WindowContainerTransaction() .setWindowingMode(taskToken, WINDOWING_MODE_MULTI_WINDOW); assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct1, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */)); } /** * Verifies that changing property on non-organized TaskFragment will throw * {@link SecurityException}. */ @Test @ApiTest(apis = { "android.window.TaskFragmentOrganizer#applyTransaction", "android.window.WindowContainerTransaction#setRelativeBounds", "android.window.WindowContainerTransaction#setWindowingMode", }) public void testSetProperty_nonOrganizedTaskFragment_throwException() { final Activity activity = startNewActivity(); final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken(); // Create another TaskFragmentOrganizer to request operation. final TaskFragmentOrganizer anotherOrganizer = registerNewOrganizer(); final WindowContainerTransaction wct0 = new WindowContainerTransaction() .setRelativeBounds(taskFragmentToken, new Rect()); assertThrows(SecurityException.class, () -> anotherOrganizer.applyTransaction(wct0, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */)); final WindowContainerTransaction wct1 = new WindowContainerTransaction() .setWindowingMode(taskFragmentToken, WINDOWING_MODE_MULTI_WINDOW); assertThrows(SecurityException.class, () -> anotherOrganizer.applyTransaction(wct1, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */)); } /** * Verifies that changing property on organized TaskFragment is allowed. */ @Test @ApiTest(apis = { "android.window.TaskFragmentOrganizer#applyTransaction", "android.window.WindowContainerTransaction#setRelativeBounds", "android.window.WindowContainerTransaction#setWindowingMode", }) public void testSetProperty_organizedTaskFragment() { final Activity activity = startNewActivity(); final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken(); final WindowContainerTransaction wct0 = new WindowContainerTransaction() .setRelativeBounds(taskFragmentToken, new Rect()); mTaskFragmentOrganizer.applyTransaction(wct0, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */); final WindowContainerTransaction wct1 = new WindowContainerTransaction() .setWindowingMode(taskFragmentToken, WINDOWING_MODE_MULTI_WINDOW); mTaskFragmentOrganizer.applyTransaction(wct1, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */); } /** * Verifies that the following {@link WindowContainerTransaction} operations are not allowed on * organized TaskFragment. */ @Test @ApiTest(apis = { "android.window.TaskFragmentOrganizer#applyTransaction", "android.window.WindowContainerTransaction#setBounds", "android.window.WindowContainerTransaction#setAppBounds", "android.window.WindowContainerTransaction#setScreenSizeDp", "android.window.WindowContainerTransaction#setSmallestScreenWidthDp", }) public void testSetProperty_unsupportedChange_throwException() { final Activity activity = startNewActivity(); final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken(); final WindowContainerTransaction wct0 = new WindowContainerTransaction() .setBounds(taskFragmentToken, new Rect()); assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct0, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */)); final WindowContainerTransaction wct1 = new WindowContainerTransaction() .setAppBounds(taskFragmentToken, new Rect()); assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct1, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */)); final WindowContainerTransaction wct2 = new WindowContainerTransaction() .setScreenSizeDp(taskFragmentToken, 100, 200); assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct2, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */)); final WindowContainerTransaction wct3 = new WindowContainerTransaction() .setSmallestScreenWidthDp(taskFragmentToken, 100); assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct3, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */)); } /** * Verifies that config changes with the following * {@link WindowContainerTransaction.Change#getChangeMask()} are disallowed on organized * TaskFragment. */ @Test @ApiTest(apis = {"android.window.TaskFragmentOrganizer#applyTransaction", "android.window.WindowContainerTransaction#scheduleFinishEnterPip", "android.window.WindowContainerTransaction#setBoundsChangeTransaction", "android.window.WindowContainerTransaction#setFocusable", "android.window.WindowContainerTransaction#setHidden", }) public void testApplyChange_unsupportedChangeMask_throwException() { final Activity activity = startNewActivity(); final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); final WindowContainerToken token = taskFragmentInfo.getToken(); final WindowContainerTransaction wct0 = new WindowContainerTransaction() .scheduleFinishEnterPip(token, new Rect(0, 0, 100, 100)); assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct0, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */)); final WindowContainerTransaction wct1 = new WindowContainerTransaction() .setBoundsChangeTransaction(token, new SurfaceControl.Transaction()); assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct1, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */)); final WindowContainerTransaction wct3 = new WindowContainerTransaction() .setFocusable(token, false /* focusable */); assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct3, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */)); final WindowContainerTransaction wct4 = new WindowContainerTransaction() .setHidden(token, false /* hidden */); assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct4, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */)); } /** * Verifies that performing {@link WindowContainerTransaction#reparent} from * TaskFragmentOrganizer will throw {@link SecurityException}. */ @Test @ApiTest(apis = { "android.window.TaskFragmentOrganizer#applyTransaction", "android.window.WindowContainerTransaction#reparent"}) public void testDisallowOperation_reparent() { final Activity activity = startNewActivity(); final TaskFragmentInfo taskFragmentInfo0 = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); final TaskFragmentInfo taskFragmentInfo1 = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); final WindowContainerToken taskFragmentToken0 = taskFragmentInfo0.getToken(); final WindowContainerToken taskFragmentToken1 = taskFragmentInfo1.getToken(); final WindowContainerTransaction wct = new WindowContainerTransaction() .reparent(taskFragmentToken0, taskFragmentToken1, true /* onTop */); assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */)); } /** * Verifies that performing {@link WindowContainerTransaction#reorder} from * TaskFragmentOrganizer will throw {@link SecurityException}. */ @Test @ApiTest(apis = { "android.window.TaskFragmentOrganizer#applyTransaction", "android.window.WindowContainerTransaction#reorder"}) public void testDisallowOperation_reorder() { final Activity activity = startNewActivity(); final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken(); final WindowContainerTransaction wct = new WindowContainerTransaction() .reorder(taskFragmentToken, true /* onTop */); assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */)); } /** * Verifies that performing {@link WindowContainerTransaction#setLaunchRoot} from * TaskFragmentOrganizer will throw {@link SecurityException}. */ @Test @ApiTest(apis = { "android.window.TaskFragmentOrganizer#applyTransaction", "android.window.WindowContainerTransaction#setLaunchRoot"}) public void testDisallowOperation_setLaunchRoot() { final Activity activity = startNewActivity(); final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken(); final WindowContainerTransaction wct = new WindowContainerTransaction() .setLaunchRoot(taskFragmentToken, null /* windowingModes */, null /* activityTypes */); assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */)); } /** * Verifies that performing {@link WindowContainerTransaction#setLaunchAdjacentFlagRoot} from * TaskFragmentOrganizer will throw {@link SecurityException}. */ @Test @ApiTest(apis = { "android.window.TaskFragmentOrganizer#applyTransaction", "android.window.WindowContainerTransaction#setLaunchAdjacentFlagRoot"}) public void testDisallowOperation_setLaunchAdjacentFlagRoot() { final Activity activity = startNewActivity(); final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken(); final WindowContainerTransaction wct = new WindowContainerTransaction() .setLaunchAdjacentFlagRoot(taskFragmentToken); assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */)); } /** * Verifies that performing {@link WindowContainerTransaction#clearLaunchAdjacentFlagRoot} from * TaskFragmentOrganizer will throw {@link SecurityException}. */ @Test @ApiTest(apis = { "android.window.TaskFragmentOrganizer#applyTransaction", "android.window.WindowContainerTransaction#clearLaunchAdjacentFlagRoot"}) public void testDisallowOperation_clearLaunchAdjacentFlagRoot() { final Activity activity = startNewActivity(); final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken(); final WindowContainerTransaction wct = new WindowContainerTransaction() .clearLaunchAdjacentFlagRoot(taskFragmentToken); assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */)); } /** * Verifies the behavior to start Activity in a new created Task in TaskFragment is forbidden. */ @Test public void testStartActivityFromAnotherProcessInNewTask_ThrowException() { final Activity activity = startNewActivity(); final IBinder ownerToken = getActivityToken(activity); final TaskFragmentCreationParams params = mTaskFragmentOrganizer.generateTaskFragParams( ownerToken); final IBinder taskFragToken = params.getFragmentToken(); final Intent intent = new Intent() .setComponent(LAUNCHING_ACTIVITY) .addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK); final IBinder errorCallbackToken = new Binder(); WindowContainerTransaction wct = new WindowContainerTransaction() .setErrorCallbackToken(errorCallbackToken) .createTaskFragment(params) .startActivityInTaskFragment(taskFragToken, ownerToken, intent, null /* activityOptions */); mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_OPEN, false /* shouldApplyIndependently */); mTaskFragmentOrganizer.waitForTaskFragmentError(); assertThat(mTaskFragmentOrganizer.getThrowable()).isInstanceOf(SecurityException.class); assertThat(mTaskFragmentOrganizer.getErrorCallbackToken()).isEqualTo(errorCallbackToken); // Activity must be launched on a new task instead. waitAndAssertActivityLaunchOnTask(LAUNCHING_ACTIVITY); } /** * Verifies the behavior of starting an Activity of another app in TaskFragment is not * allowed without permissions. */ @Test public void testStartAnotherAppActivityInTaskFragment() { final Activity activity = startNewActivity(); final IBinder ownerToken = getActivityToken(activity); final TaskFragmentCreationParams params = mTaskFragmentOrganizer.generateTaskFragParams(ownerToken); final IBinder taskFragToken = params.getFragmentToken(); final WindowContainerTransaction wct = new WindowContainerTransaction() .createTaskFragment(params) .startActivityInTaskFragment(taskFragToken, ownerToken, new Intent().setComponent(SDK_30_TEST_ACTIVITY), null /* activityOptions */); mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_OPEN, false /* shouldApplyIndependently */); mTaskFragmentOrganizer.waitForTaskFragmentCreated(); // Launching an activity of another app in TaskFragment should report error. mTaskFragmentOrganizer.waitForTaskFragmentError(); assertThat(mTaskFragmentOrganizer.getThrowable()).isInstanceOf(SecurityException.class); // Making sure activity is not launched on the TaskFragment TaskFragmentInfo info = mTaskFragmentOrganizer.getTaskFragmentInfo(taskFragToken); assertEmptyTaskFragment(info, taskFragToken); // Activity must be launched on a new task instead. waitAndAssertActivityLaunchOnTask(SDK_30_TEST_ACTIVITY); } private void waitAndAssertActivityLaunchOnTask(ComponentName activityName) { waitAndAssertResumedActivity(activityName, "Activity must be resumed."); Task task = mWmState.getTaskByActivity(activityName); assertWithMessage("Launching activity must be started on Task") .that(task.getActivities()).contains(mWmState.getActivity(activityName)); } /** * Verifies the behavior of starting an Activity of another app while activities of the host * app are already embedded in TaskFragment. */ @Test public void testStartAnotherAppActivityWithEmbeddedTaskFragments() { final Activity activity = startNewActivity(); final IBinder ownerToken = getActivityToken(activity); final TaskFragmentCreationParams params = mTaskFragmentOrganizer.generateTaskFragParams(ownerToken); final IBinder taskFragToken = params.getFragmentToken(); final WindowContainerTransaction wct = new WindowContainerTransaction() .createTaskFragment(params) .startActivityInTaskFragment(taskFragToken, ownerToken, new Intent(getInstrumentation().getTargetContext(), WindowMetricsActivityTests.MetricsActivity.class), null /* activityOptions */); mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_OPEN, false /* shouldApplyIndependently */); mTaskFragmentOrganizer.waitForTaskFragmentCreated(); mTaskFragmentOrganizer.waitForAndGetTaskFragmentInfo( taskFragToken, info -> info.getActivities().size() == 1, "getActivities from TaskFragment must contain 1 activities"); activity.startActivity(new Intent().setComponent(SDK_30_TEST_ACTIVITY)); waitAndAssertActivityState(SDK_30_TEST_ACTIVITY, STATE_RESUMED, "Activity should be resumed."); TaskFragmentInfo info = mTaskFragmentOrganizer.getTaskFragmentInfo(taskFragToken); assertEquals(1, info.getActivities().size()); } /** * Verifies whether creating TaskFragment with non-resizeable {@link Activity} leads to * {@link IllegalArgumentException} returned by * {@link TaskFragmentOrganizer#onTaskFragmentError(IBinder, Throwable)}. */ @Test public void testCreateTaskFragmentWithNonResizeableActivity_ThrowException() { // Pass non-resizeable Activity's token to TaskFragmentCreationParams and tries to // create a TaskFragment with the params. final Activity activity = startNewActivity(CompatChangeTests.NonResizeablePortraitActivity.class); final IBinder ownerToken = getActivityToken(activity); final TaskFragmentCreationParams params = mTaskFragmentOrganizer.generateTaskFragParams(ownerToken); final WindowContainerTransaction wct = new WindowContainerTransaction() .createTaskFragment(params); mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_OPEN, false /* shouldApplyIndependently */); mTaskFragmentOrganizer.waitForTaskFragmentError(); assertThat(mTaskFragmentOrganizer.getThrowable()) .isInstanceOf(IllegalArgumentException.class); } /** * Verifies that the TaskFragment hierarchy ops should still work while in lock task mode. */ @Test public void testApplyHierarchyOpsInLockTaskMode() { // Start an activity final Activity activity = startNewActivity(); try { // Lock the task runWithShellPermission(() -> { mAtm.startSystemLockTaskMode(activity.getTaskId()); }); waitForOrFail("Task in app pinning mode", () -> { return mAm.getLockTaskModeState() == LOCK_TASK_MODE_PINNED; }); // Create TaskFragment and reparent the activity final IBinder ownerToken = getActivityToken(activity); final TaskFragmentCreationParams params = mTaskFragmentOrganizer.generateTaskFragParams(ownerToken); final IBinder taskFragToken = params.getFragmentToken(); WindowContainerTransaction wct = new WindowContainerTransaction() .createTaskFragment(params) .reparentActivityToTaskFragment(taskFragToken, ownerToken); mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE, false /* shouldApplyIndependently */); // Verifies it works mTaskFragmentOrganizer.waitForTaskFragmentCreated(); TaskFragmentInfo info = mTaskFragmentOrganizer.getTaskFragmentInfo(taskFragToken); assertEquals(1, info.getActivities().size()); // Delete the TaskFragment wct = new WindowContainerTransaction().deleteTaskFragment(taskFragToken); mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CLOSE, false /* shouldApplyIndependently */); // Verifies the TaskFragment NOT removed because the removal would also empty the task. mTaskFragmentOrganizer.waitForTaskFragmentError(); assertThat(mTaskFragmentOrganizer.getThrowable()).isInstanceOf( IllegalStateException.class); info = mTaskFragmentOrganizer.getTaskFragmentInfo(taskFragToken); assertEquals(1, info.getActivities().size()); } finally { runWithShellPermission(() -> { mAtm.stopSystemLockTaskMode(); }); } } /** * Verifies that {@link TaskFragmentOrganizer#applySyncTransaction} is not allowed. */ @Test @ApiTest(apis = {"android.window.TaskFragmentOrganizer#applySyncTransaction"}) public void testApplySyncTransaction_disallowed() { final Activity activity = startNewActivity(); final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment( mTaskFragmentOrganizer, activity); final WindowContainerTransaction wct = new WindowContainerTransaction() .deleteTaskFragment(taskFragmentInfo.getFragmentToken()); final WindowContainerTransactionCallback callback = new WindowContainerTransactionCallback() { @Override public void onTransactionReady(int id, @NonNull SurfaceControl.Transaction t) { fail("Transaction shouldn't be executed"); } }; assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applySyncTransaction(wct, callback)); } /** * Creates and registers a {@link TaskFragmentOrganizer} that will be unregistered in * {@link #tearDown()}. */ private BasicTaskFragmentOrganizer registerNewOrganizer() { final BasicTaskFragmentOrganizer organizer = new BasicTaskFragmentOrganizer(); organizer.registerOrganizer(); mOrganizers.add(organizer); return organizer; } /** * Registers a {@link TaskOrganizer} to get the {@link WindowContainerToken} of a Task. The * organizer will be unregistered in {@link #tearDown()}. */ private WindowContainerToken getFirstTaskToken() { final List taskInfos = new ArrayList<>(); // Register TaskOrganizer to obtain Task information. NestedShellPermission.run(() -> { mTaskOrganizer = new TaskOrganizer(); taskInfos.addAll(mTaskOrganizer.registerOrganizer()); }); return taskInfos.get(0).getTaskInfo().getToken(); } /** * Creates a TaskFragment organized by the given organizer. The TaskFragment will be removed * when the organizer is unregistered. */ private static TaskFragmentInfo createOrganizedTaskFragment( BasicTaskFragmentOrganizer organizer, Activity ownerActivity) { // Create a TaskFragment with a TaskFragmentOrganizer. final TaskFragmentCreationParams params = organizer.generateTaskFragParams( getActivityToken(ownerActivity)); final WindowContainerTransaction wct = new WindowContainerTransaction() .createTaskFragment(params); organizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_OPEN, false /* shouldApplyIndependently */); // Wait for TaskFragment's creation to obtain its info. organizer.waitForTaskFragmentCreated(); organizer.resetLatch(); return organizer.getTaskFragmentInfo(params.getFragmentToken()); } }