| /* |
| * 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.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.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 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import static org.junit.Assert.assertEquals; |
| |
| import android.app.Activity; |
| import android.app.Instrumentation; |
| 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.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 androidx.test.InstrumentationRegistry; |
| import androidx.test.runner.AndroidJUnit4; |
| |
| 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 |
| public class TaskFragmentOrganizerPolicyTest extends ActivityManagerTestBase { |
| private TaskOrganizer mTaskOrganizer; |
| private BasicTaskFragmentOrganizer mTaskFragmentOrganizer; |
| |
| @Before |
| @Override |
| public void setUp() throws Exception { |
| super.setUp(); |
| mTaskFragmentOrganizer = new BasicTaskFragmentOrganizer(); |
| mTaskFragmentOrganizer.registerOrganizer(); |
| } |
| |
| @After |
| public void tearDown() { |
| if (mTaskFragmentOrganizer != null) { |
| mTaskFragmentOrganizer.unregisterOrganizer(); |
| } |
| } |
| |
| /** |
| * Verifies whether performing non-TaskFragment |
| * {@link android.window.WindowContainerTransaction.HierarchyOp operations} on |
| * {@link TaskFragmentOrganizer} without permission throws {@link SecurityException}. |
| */ |
| @Test(expected = SecurityException.class) |
| public void testPerformNonTaskFragmentHierarchyOperation_ThrowException() { |
| final List<TaskAppearedInfo> taskInfos = new ArrayList<>(); |
| try { |
| // Register TaskOrganizer to obtain Task information. |
| NestedShellPermission.run(() -> { |
| mTaskOrganizer = new TaskOrganizer(); |
| taskInfos.addAll(mTaskOrganizer.registerOrganizer()); |
| }); |
| |
| // It is expected to throw Security exception when TaskFragmentOrganizer performs a |
| // non-TaskFragment hierarchy operation. |
| final WindowContainerToken taskToken = taskInfos.get(0).getTaskInfo().getToken(); |
| final WindowContainerTransaction wct = new WindowContainerTransaction() |
| .reorder(taskToken, true /* opTop */); |
| mTaskFragmentOrganizer.applyTransaction(wct); |
| } finally { |
| if (mTaskOrganizer != null) { |
| NestedShellPermission.run(() -> mTaskOrganizer.unregisterOrganizer()); |
| } |
| } |
| } |
| |
| /** |
| * Verifies whether changing property on non-TaskFragment window container without permission |
| * throws {@link SecurityException}. |
| */ |
| @Test(expected = SecurityException.class) |
| public void testSetPropertyOnNonTaskFragment_ThrowException() { |
| final List<TaskAppearedInfo> taskInfos = new ArrayList<>(); |
| try { |
| // Register TaskOrganizer to obtain Task information. |
| NestedShellPermission.run(() -> { |
| mTaskOrganizer = new TaskOrganizer(); |
| taskInfos.addAll(mTaskOrganizer.registerOrganizer()); |
| }); |
| |
| // It is expected to throw SecurityException when TaskFragmentOrganizer attempts to |
| // change the property on non-TaskFragment container. |
| final WindowContainerToken taskToken = taskInfos.get(0).getTaskInfo().getToken(); |
| final WindowContainerTransaction wct = new WindowContainerTransaction() |
| .setBounds(taskToken, new Rect()); |
| mTaskFragmentOrganizer.applyTransaction(wct); |
| } finally { |
| if (mTaskOrganizer != null) { |
| NestedShellPermission.run(() -> mTaskOrganizer.unregisterOrganizer()); |
| } |
| } |
| } |
| |
| /** |
| * Verifies whether performing TaskFragment |
| * {@link android.window.WindowContainerTransaction.HierarchyOp operations} on the TaskFragment |
| * which is not organized by given {@link TaskFragmentOrganizer} throws |
| * {@link SecurityException}. |
| */ |
| @Test(expected = SecurityException.class) |
| public void testPerformOperationsOnNonOrganizedTaskFragment_ThrowException() { |
| final Activity activity = startNewActivity(); |
| |
| // Create a TaskFragment with a TaskFragmentOrganizer. |
| final TaskFragmentCreationParams params = mTaskFragmentOrganizer.generateTaskFragParams( |
| getActivityToken(activity)); |
| final IBinder taskFragToken = params.getFragmentToken(); |
| WindowContainerTransaction wct = new WindowContainerTransaction() |
| .createTaskFragment(params); |
| mTaskFragmentOrganizer.applyTransaction(wct); |
| // Wait for TaskFragment's creation to obtain its WindowContainerToken. |
| mTaskFragmentOrganizer.waitForTaskFragmentCreated(); |
| |
| // Create another TaskFragmentOrganizer |
| final TaskFragmentOrganizer anotherOrganizer = new TaskFragmentOrganizer(Runnable::run); |
| anotherOrganizer.registerOrganizer(); |
| // Try to perform an operation on the TaskFragment when is organized by the previous |
| // TaskFragmentOrganizer. |
| wct = new WindowContainerTransaction() |
| .deleteTaskFragment(mTaskFragmentOrganizer.getTaskFragmentInfo(taskFragToken) |
| .getToken()); |
| |
| // It is expected to throw SecurityException when performing operations on the TaskFragment |
| // which is not organized by the same TaskFragmentOrganizer. |
| anotherOrganizer.applyTransaction(wct); |
| } |
| |
| /** |
| * 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); |
| |
| mTaskFragmentOrganizer.waitForTaskFragmentCreated(); |
| |
| TaskFragmentInfo info = mTaskFragmentOrganizer.getTaskFragmentInfo(taskFragToken); |
| |
| // TaskFragment must remain empty because embedding activities in a new task is not allowed. |
| assertEmptyTaskFragment(info, taskFragToken); |
| |
| mTaskFragmentOrganizer.waitForTaskFragmentError(); |
| |
| assertThat(mTaskFragmentOrganizer.getThrowable()).isInstanceOf(SecurityException.class); |
| assertThat(mTaskFragmentOrganizer.getErrorCallbackToken()).isEqualTo(errorCallbackToken); |
| } |
| |
| /** |
| * 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); |
| mTaskFragmentOrganizer.waitForTaskFragmentCreated(); |
| |
| // Launching an activity of another app in TaskFragment should report error. |
| mTaskFragmentOrganizer.waitForTaskFragmentError(); |
| assertThat(mTaskFragmentOrganizer.getThrowable()).isInstanceOf(SecurityException.class); |
| |
| // Making sure no activity launched |
| TaskFragmentInfo info = mTaskFragmentOrganizer.getTaskFragmentInfo(taskFragToken); |
| assertEmptyTaskFragment(info, taskFragToken); |
| } |
| |
| /** |
| * 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); |
| 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); |
| |
| 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()); |
| }); |
| |
| // 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); |
| |
| // Verifies it works |
| mTaskFragmentOrganizer.waitForTaskFragmentCreated(); |
| TaskFragmentInfo info = mTaskFragmentOrganizer.getTaskFragmentInfo(taskFragToken); |
| assertEquals(1, info.getActivities().size()); |
| |
| // Delete the TaskFragment |
| wct = new WindowContainerTransaction().deleteTaskFragment( |
| mTaskFragmentOrganizer.getTaskFragmentInfo(taskFragToken).getToken()); |
| mTaskFragmentOrganizer.applyTransaction(wct); |
| |
| // 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(); |
| }); |
| } |
| } |
| |
| private static Activity startNewActivity() { |
| return startNewActivity(TestActivity.class); |
| } |
| |
| private static Activity startNewActivity(Class<?> className) { |
| final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); |
| final Intent intent = new Intent(instrumentation.getTargetContext(), className) |
| .addFlags(FLAG_ACTIVITY_NEW_TASK); |
| return instrumentation.startActivitySync(intent); |
| } |
| } |