summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Louis Chang <louischang@google.com> 2023-04-17 10:03:33 +0000
committer Louis Chang <louischang@google.com> 2023-07-07 04:12:08 +0000
commit4944e8e0717dfaa5d85b31bd93353d80d7f287f2 (patch)
tree4364feeb5cf0285b97a4afa0747d1d676ce95266
parentb5485855d9b601adbd9c10bf49d33466844224e6 (diff)
[3/n] Pin ActivityStack
Implementing pinTopActivityStack API, which required in extensions vendor API level 4. This is the initial change to make the top-most TF container to split with another TF container below. Also adding a SplitPinContainer to hold the two TF containers. The primary container is set to mutable which could be changed and updated in runtime (in latter CLs). The pinned TF container and SplitPinContainer is ensured to be on top on the client side. The server side change will be done in the following CLs. Bug: 208573140 Test: atest SplitControllerTest Change-Id: Ic932756f0adb4997b3729f0ed53dd9542dd4b7ae
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java2
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java27
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java64
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPinContainer.java47
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java4
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java83
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java45
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java6
8 files changed, 260 insertions, 18 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index 76e0e1eb7a95..55eabb039c01 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -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
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index 18497ad249ee..381e9d472f0f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -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;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 4cedd41e2d9a..47bd6adb11e2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -213,6 +213,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) {
synchronized (mLock) {
@@ -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,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPinContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPinContainer.java
new file mode 100644
index 000000000000..03c77a089012
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPinContainer.java
@@ -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()
+ + "}";
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 53d39d9fa28e..6630e05a8b0a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -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();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 4580c9836168..822adbd84454 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -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 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 mContainers.get(mContainers.size() - 1);
+ 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) {
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 9e264726a65a..9af1fe916279 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -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);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index 11af1d1f20e1..000c65a75c81 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -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