blob: d849fc46fb4ec3dcebb5a223daeca71381bf1586 [file] [log] [blame]
/*
* Copyright (C) 2023 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.taskfragment;
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.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.server.wm.taskfragment.TaskFragmentOrganizerTestBase.assertEmptyTaskFragment;
import static android.server.wm.taskfragment.TaskFragmentOrganizerTestBase.getActivityToken;
import static android.server.wm.taskfragment.TaskFragmentOrganizerTestBase.startNewActivity;
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 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.ActivityManagerTestBase;
import android.server.wm.HelperActivities;
import android.server.wm.MetricsActivity;
import android.server.wm.NestedShellPermission;
import android.server.wm.WindowContextTestActivity;
import android.server.wm.WindowManagerState.Task;
import android.server.wm.taskfragment.TaskFragmentOrganizerTestBase.BasicTaskFragmentOrganizer;
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.
*
* <p>Build/Install/Run: atest CtsWindowManagerDeviceTaskFragment:TaskFragmentOrganizerPolicyTest
*/
@RunWith(AndroidJUnit4.class)
@Presubmit
@android.server.wm.annotation.Group2
public class TaskFragmentOrganizerPolicyTest extends ActivityManagerTestBase {
private TaskOrganizer mTaskOrganizer;
private BasicTaskFragmentOrganizer mTaskFragmentOrganizer;
private final ArrayList<BasicTaskFragmentOrganizer> 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, WindowContextTestActivity.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, MetricsActivity.class);
final WindowContainerTransaction wct =
new WindowContainerTransaction()
.startActivityInTaskFragment(
fragmentToken, callerToken, intent, null /* activityOptions */);
mTaskFragmentOrganizer.applyTransaction(
wct, TASK_FRAGMENT_TRANSIT_OPEN, false /* shouldApplyIndependently */);
waitAndAssertResumedActivity(intent.getComponent());
mTaskFragmentOrganizer.waitForAndGetTaskFragmentInfo(
fragmentToken, info -> info.getActivities().size() == 1,
"getActivities from TaskFragment must contain 1 activities");
}
/**
* 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 Intent intent = new Intent(mContext, MetricsActivity.class);
final WindowContainerTransaction wct =
new WindowContainerTransaction()
.createTaskFragment(params)
.startActivityInTaskFragment(
taskFragToken,
ownerToken,
intent,
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");
waitAndAssertResumedActivity(intent.getComponent());
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(HelperActivities.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<TaskAppearedInfo> 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());
}
}