Merge "[3/n] Pin ActivityStack" into udc-qpr-dev
This commit is contained in:
@@ -48,7 +48,7 @@ public class WindowExtensionsImpl implements WindowExtensions {
|
||||
// TODO(b/241126279) Introduce constants to better version functionality
|
||||
@Override
|
||||
public int getVendorApiLevel() {
|
||||
return 3;
|
||||
return 4;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
||||
@@ -32,7 +32,7 @@ import androidx.window.extensions.core.util.function.Function;
|
||||
*/
|
||||
class SplitContainer {
|
||||
@NonNull
|
||||
private final TaskFragmentContainer mPrimaryContainer;
|
||||
private TaskFragmentContainer mPrimaryContainer;
|
||||
@NonNull
|
||||
private final TaskFragmentContainer mSecondaryContainer;
|
||||
@NonNull
|
||||
@@ -46,17 +46,35 @@ class SplitContainer {
|
||||
@NonNull
|
||||
private final IBinder mToken;
|
||||
|
||||
/**
|
||||
* Whether the selection of which container is primary can be changed at runtime. Runtime
|
||||
* updates is currently possible only for {@link SplitPinContainer}
|
||||
*
|
||||
* @see SplitPinContainer
|
||||
*/
|
||||
private final boolean mIsPrimaryContainerMutable;
|
||||
|
||||
SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
|
||||
@NonNull Activity primaryActivity,
|
||||
@NonNull TaskFragmentContainer secondaryContainer,
|
||||
@NonNull SplitRule splitRule,
|
||||
@NonNull SplitAttributes splitAttributes) {
|
||||
this(primaryContainer, primaryActivity, secondaryContainer, splitRule, splitAttributes,
|
||||
false /* isPrimaryContainerMutable */);
|
||||
}
|
||||
|
||||
SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
|
||||
@NonNull Activity primaryActivity,
|
||||
@NonNull TaskFragmentContainer secondaryContainer,
|
||||
@NonNull SplitRule splitRule,
|
||||
@NonNull SplitAttributes splitAttributes, boolean isPrimaryContainerMutable) {
|
||||
mPrimaryContainer = primaryContainer;
|
||||
mSecondaryContainer = secondaryContainer;
|
||||
mSplitRule = splitRule;
|
||||
mDefaultSplitAttributes = splitRule.getDefaultSplitAttributes();
|
||||
mCurrentSplitAttributes = splitAttributes;
|
||||
mToken = new Binder("SplitContainer");
|
||||
mIsPrimaryContainerMutable = isPrimaryContainerMutable;
|
||||
|
||||
if (shouldFinishPrimaryWithSecondary(splitRule)) {
|
||||
if (mPrimaryContainer.getRunningActivityCount() == 1
|
||||
@@ -74,6 +92,13 @@ class SplitContainer {
|
||||
}
|
||||
}
|
||||
|
||||
void setPrimaryContainer(@NonNull TaskFragmentContainer primaryContainer) {
|
||||
if (!mIsPrimaryContainerMutable) {
|
||||
throw new IllegalStateException("Cannot update primary TaskFragmentContainer");
|
||||
}
|
||||
mPrimaryContainer = primaryContainer;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
TaskFragmentContainer getPrimaryContainer() {
|
||||
return mPrimaryContainer;
|
||||
|
||||
@@ -212,6 +212,56 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pinTopActivityStack(int taskId, @NonNull SplitPinRule splitPinRule) {
|
||||
synchronized (mLock) {
|
||||
final TaskContainer task = getTaskContainer(taskId);
|
||||
if (task == null) {
|
||||
Log.e(TAG, "Cannot find the task for id: " + taskId);
|
||||
return false;
|
||||
}
|
||||
|
||||
final TaskFragmentContainer topContainer =
|
||||
task.getTopNonFinishingTaskFragmentContainer();
|
||||
// Cannot pin the TaskFragment if no other TaskFragment behind it.
|
||||
if (topContainer == null || task.indexOf(topContainer) <= 0) {
|
||||
Log.w(TAG, "Cannot find an ActivityStack to pin or split");
|
||||
return false;
|
||||
}
|
||||
// Abort if the top container is already pinned.
|
||||
if (task.getSplitPinContainer() != null) {
|
||||
Log.w(TAG, "There is already a pinned ActivityStack.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find a valid adjacent TaskFragmentContainer
|
||||
final TaskFragmentContainer primaryContainer =
|
||||
task.getNonFinishingTaskFragmentContainerBelow(topContainer);
|
||||
if (primaryContainer == null) {
|
||||
Log.w(TAG, "Cannot find another ActivityStack to split");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Registers a Split
|
||||
final SplitPinContainer splitPinContainer = new SplitPinContainer(primaryContainer,
|
||||
topContainer, splitPinRule, splitPinRule.getDefaultSplitAttributes());
|
||||
task.addSplitContainer(splitPinContainer);
|
||||
|
||||
// Updates the Split
|
||||
final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
|
||||
final WindowContainerTransaction wct = transactionRecord.getTransaction();
|
||||
mPresenter.updateSplitContainer(splitPinContainer, wct);
|
||||
transactionRecord.apply(false /* shouldApplyIndependently */);
|
||||
updateCallbackIfNecessary();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unpinTopActivityStack(int taskId){
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSplitAttributesCalculator(
|
||||
@NonNull Function<SplitAttributesCalculatorParams, SplitAttributes> calculator) {
|
||||
@@ -672,7 +722,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
if (targetContainer == null) {
|
||||
// When there is no embedding rule matched, try to place it in the top container
|
||||
// like a normal launch.
|
||||
targetContainer = taskContainer.getTopTaskFragmentContainer();
|
||||
targetContainer = taskContainer.getTopNonFinishingTaskFragmentContainer();
|
||||
}
|
||||
if (targetContainer == null) {
|
||||
return;
|
||||
@@ -791,7 +841,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
|
||||
final TaskFragmentContainer container = getContainerWithActivity(activity);
|
||||
if (!isOnReparent && container != null
|
||||
&& container.getTaskContainer().getTopTaskFragmentContainer() != container) {
|
||||
&& container.getTaskContainer().getTopNonFinishingTaskFragmentContainer()
|
||||
!= container) {
|
||||
// Do not resolve if the launched activity is not the top-most container in the Task.
|
||||
return true;
|
||||
}
|
||||
@@ -888,7 +939,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
if (taskContainer == null) {
|
||||
return;
|
||||
}
|
||||
final TaskFragmentContainer targetContainer = taskContainer.getTopTaskFragmentContainer();
|
||||
final TaskFragmentContainer targetContainer =
|
||||
taskContainer.getTopNonFinishingTaskFragmentContainer();
|
||||
if (targetContainer == null) {
|
||||
return;
|
||||
}
|
||||
@@ -1213,11 +1265,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
|
||||
// 3. Whether the top activity (if any) should be split with the new activity intent.
|
||||
final TaskContainer taskContainer = getTaskContainer(taskId);
|
||||
if (taskContainer == null || taskContainer.getTopTaskFragmentContainer() == null) {
|
||||
if (taskContainer == null
|
||||
|| taskContainer.getTopNonFinishingTaskFragmentContainer() == null) {
|
||||
// There is no other activity in the Task to check split with.
|
||||
return null;
|
||||
}
|
||||
final TaskFragmentContainer topContainer = taskContainer.getTopTaskFragmentContainer();
|
||||
final TaskFragmentContainer topContainer =
|
||||
taskContainer.getTopNonFinishingTaskFragmentContainer();
|
||||
final Activity topActivity = topContainer.getTopNonFinishingActivity();
|
||||
if (topActivity != null && topActivity != launchingActivity) {
|
||||
final TaskFragmentContainer container = getSecondaryContainerForSplitIfAny(wct,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 androidx.window.extensions.embedding;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Client-side descriptor of a split that holds two containers while the secondary
|
||||
* container is pinned on top of the Task and the primary container is the container that is
|
||||
* currently below the secondary container. The primary container could be updated to
|
||||
* another container whenever the existing primary container is removed or no longer
|
||||
* be the container that's right behind the secondary container.
|
||||
*/
|
||||
class SplitPinContainer extends SplitContainer {
|
||||
|
||||
SplitPinContainer(@NonNull TaskFragmentContainer primaryContainer,
|
||||
@NonNull TaskFragmentContainer secondaryContainer,
|
||||
@NonNull SplitPinRule splitPinRule,
|
||||
@NonNull SplitAttributes splitAttributes) {
|
||||
super(primaryContainer, primaryContainer.getTopNonFinishingActivity(), secondaryContainer,
|
||||
splitPinRule, splitAttributes, true /* isPrimaryContainerMutable */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SplitPinContainer{"
|
||||
+ " primaryContainer=" + getPrimaryContainer()
|
||||
+ " secondaryContainer=" + getSecondaryContainer()
|
||||
+ " splitPinRule=" + getSplitRule()
|
||||
+ " splitAttributes" + getCurrentSplitAttributes()
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
@@ -336,10 +336,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
// value.
|
||||
final SplitRule rule = splitContainer.getSplitRule();
|
||||
final TaskFragmentContainer primaryContainer = splitContainer.getPrimaryContainer();
|
||||
final Activity activity = primaryContainer.getTopNonFinishingActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
final TaskContainer taskContainer = splitContainer.getTaskContainer();
|
||||
final TaskProperties taskProperties = taskContainer.getTaskProperties();
|
||||
final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes();
|
||||
|
||||
@@ -57,6 +57,10 @@ class TaskContainer {
|
||||
@NonNull
|
||||
private final List<SplitContainer> mSplitContainers = new ArrayList<>();
|
||||
|
||||
/** Active pin split pair in this Task. */
|
||||
@Nullable
|
||||
private SplitPinContainer mSplitPinContainer;
|
||||
|
||||
@NonNull
|
||||
private final Configuration mConfiguration;
|
||||
|
||||
@@ -174,11 +178,28 @@ class TaskContainer {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
TaskFragmentContainer getTopTaskFragmentContainer() {
|
||||
if (mContainers.isEmpty()) {
|
||||
return null;
|
||||
TaskFragmentContainer getTopNonFinishingTaskFragmentContainer() {
|
||||
for (int i = mContainers.size() - 1; i >= 0; i--) {
|
||||
final TaskFragmentContainer container = mContainers.get(i);
|
||||
if (!container.isFinished()) {
|
||||
return container;
|
||||
}
|
||||
}
|
||||
return mContainers.get(mContainers.size() - 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Gets a non-finishing container below the given one. */
|
||||
@Nullable
|
||||
TaskFragmentContainer getNonFinishingTaskFragmentContainerBelow(
|
||||
@NonNull TaskFragmentContainer current) {
|
||||
final int index = mContainers.indexOf(current);
|
||||
for (int i = index - 1; i >= 0; i--) {
|
||||
final TaskFragmentContainer container = mContainers.get(i);
|
||||
if (!container.isFinished()) {
|
||||
return container;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -217,31 +238,57 @@ class TaskContainer {
|
||||
}
|
||||
|
||||
void addSplitContainer(@NonNull SplitContainer splitContainer) {
|
||||
if (splitContainer instanceof SplitPinContainer) {
|
||||
mSplitPinContainer = (SplitPinContainer) splitContainer;
|
||||
mSplitContainers.add(splitContainer);
|
||||
return;
|
||||
}
|
||||
|
||||
// Keeps the SplitPinContainer on the top of the list.
|
||||
mSplitContainers.remove(mSplitPinContainer);
|
||||
mSplitContainers.add(splitContainer);
|
||||
if (mSplitPinContainer != null) {
|
||||
mSplitContainers.add(mSplitPinContainer);
|
||||
}
|
||||
}
|
||||
|
||||
void removeSplitContainers(@NonNull List<SplitContainer> containers) {
|
||||
mSplitContainers.removeAll(containers);
|
||||
}
|
||||
|
||||
void removeSplitPinContainer() {
|
||||
mSplitContainers.remove(mSplitPinContainer);
|
||||
mSplitPinContainer = null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
SplitPinContainer getSplitPinContainer() {
|
||||
return mSplitPinContainer;
|
||||
}
|
||||
|
||||
void addTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) {
|
||||
mContainers.add(taskFragmentContainer);
|
||||
onTaskFragmentContainerUpdated();
|
||||
}
|
||||
|
||||
void addTaskFragmentContainer(int index, @NonNull TaskFragmentContainer taskFragmentContainer) {
|
||||
mContainers.add(index, taskFragmentContainer);
|
||||
onTaskFragmentContainerUpdated();
|
||||
}
|
||||
|
||||
void removeTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) {
|
||||
mContainers.remove(taskFragmentContainer);
|
||||
onTaskFragmentContainerUpdated();
|
||||
}
|
||||
|
||||
void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainer) {
|
||||
mContainers.removeAll(taskFragmentContainer);
|
||||
onTaskFragmentContainerUpdated();
|
||||
}
|
||||
|
||||
void clearTaskFragmentContainer() {
|
||||
mContainers.clear();
|
||||
onTaskFragmentContainerUpdated();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,6 +301,34 @@ class TaskContainer {
|
||||
return mContainers;
|
||||
}
|
||||
|
||||
private void onTaskFragmentContainerUpdated() {
|
||||
if (mSplitPinContainer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final TaskFragmentContainer pinnedContainer = mSplitPinContainer.getSecondaryContainer();
|
||||
final int index = mContainers.indexOf(pinnedContainer);
|
||||
if (index <= 0) {
|
||||
removeSplitPinContainer();
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure the pinned container is top-most.
|
||||
if (index != mContainers.size() - 1) {
|
||||
mContainers.remove(pinnedContainer);
|
||||
mContainers.add(pinnedContainer);
|
||||
}
|
||||
|
||||
// Update the primary container adjacent to the pinned container if needed.
|
||||
final TaskFragmentContainer adjacentContainer =
|
||||
getNonFinishingTaskFragmentContainerBelow(pinnedContainer);
|
||||
if (adjacentContainer == null) {
|
||||
removeSplitPinContainer();
|
||||
} else if (mSplitPinContainer.getPrimaryContainer() != adjacentContainer) {
|
||||
mSplitPinContainer.setPrimaryContainer(adjacentContainer);
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds the descriptors of split states in this Task to {@code outSplitStates}. */
|
||||
void getSplitStates(@NonNull List<SplitInfo> outSplitStates) {
|
||||
for (SplitContainer container : mSplitContainers) {
|
||||
|
||||
@@ -1462,6 +1462,51 @@ public class SplitControllerTest {
|
||||
verify(testRecord).apply(eq(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPinTopActivityStack() {
|
||||
// Create two activities.
|
||||
final Activity primaryActivity = createMockActivity();
|
||||
final Activity secondaryActivity = createMockActivity();
|
||||
|
||||
// Unable to pin if not being embedded.
|
||||
SplitPinRule splitPinRule = new SplitPinRule.Builder(new SplitAttributes.Builder().build(),
|
||||
parentWindowMetrics -> true /* parentWindowMetricsPredicate */).build();
|
||||
assertFalse(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule));
|
||||
|
||||
// Split the two activities.
|
||||
addSplitTaskFragments(primaryActivity, secondaryActivity);
|
||||
final TaskFragmentContainer primaryContainer =
|
||||
mSplitController.getContainerWithActivity(primaryActivity);
|
||||
spyOn(primaryContainer);
|
||||
|
||||
// Unable to pin if no valid TaskFragment.
|
||||
doReturn(true).when(primaryContainer).isFinished();
|
||||
assertFalse(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule));
|
||||
|
||||
// Otherwise, should pin successfully.
|
||||
doReturn(false).when(primaryContainer).isFinished();
|
||||
assertTrue(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule));
|
||||
|
||||
// Unable to pin if there is already a pinned TaskFragment
|
||||
assertFalse(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule));
|
||||
|
||||
// Unable to pin on an unknown Task.
|
||||
assertFalse(mSplitController.pinTopActivityStack(TASK_ID + 1, splitPinRule));
|
||||
|
||||
// Gets the current size of all the SplitContainers.
|
||||
final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
|
||||
final int splitContainerCount = taskContainer.getSplitContainers().size();
|
||||
|
||||
// Create another activity and split with primary activity.
|
||||
final Activity thirdActivity = createMockActivity();
|
||||
addSplitTaskFragments(primaryActivity, thirdActivity);
|
||||
|
||||
// Ensure another SplitContainer is added and the pinned TaskFragment still on top
|
||||
assertTrue(taskContainer.getSplitContainers().size() == splitContainerCount + +1);
|
||||
assertTrue(mSplitController.getTopActiveContainer(TASK_ID).getTopNonFinishingActivity()
|
||||
== secondaryActivity);
|
||||
}
|
||||
|
||||
/** Creates a mock activity in the organizer process. */
|
||||
private Activity createMockActivity() {
|
||||
return createMockActivity(TASK_ID);
|
||||
|
||||
@@ -135,15 +135,15 @@ public class TaskContainerTest {
|
||||
@Test
|
||||
public void testGetTopTaskFragmentContainer() {
|
||||
final TaskContainer taskContainer = createTestTaskContainer();
|
||||
assertNull(taskContainer.getTopTaskFragmentContainer());
|
||||
assertNull(taskContainer.getTopNonFinishingTaskFragmentContainer());
|
||||
|
||||
final TaskFragmentContainer tf0 = new TaskFragmentContainer(null /* activity */,
|
||||
new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
|
||||
assertEquals(tf0, taskContainer.getTopTaskFragmentContainer());
|
||||
assertEquals(tf0, taskContainer.getTopNonFinishingTaskFragmentContainer());
|
||||
|
||||
final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
|
||||
new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
|
||||
assertEquals(tf1, taskContainer.getTopTaskFragmentContainer());
|
||||
assertEquals(tf1, taskContainer.getTopNonFinishingTaskFragmentContainer());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user