diff options
Diffstat (limited to 'libs')
205 files changed, 5485 insertions, 1439 deletions
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 89f4890c254e..4cedd41e2d9a 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -308,7 +308,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen forAllTaskContainers(taskContainer -> { synchronized (mLock) { - final List<TaskFragmentContainer> containers = taskContainer.mContainers; + final List<TaskFragmentContainer> containers = + taskContainer.getTaskFragmentContainers(); // Clean up the TaskFragmentContainers by the z-order from the lowest. for (int i = 0; i < containers.size(); i++) { final TaskFragmentContainer container = containers.get(i); @@ -611,8 +612,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @NonNull TaskContainer taskContainer) { // Update all TaskFragments in the Task. Make a copy of the list since some may be // removed on updating. - final List<TaskFragmentContainer> containers = - new ArrayList<>(taskContainer.mContainers); + final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers(); for (int i = containers.size() - 1; i >= 0; i--) { final TaskFragmentContainer container = containers.get(i); // Wait until onTaskFragmentAppeared to update new container. @@ -1331,7 +1331,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Check pending appeared activity first because there can be a delay for the server // update. for (int i = mTaskContainers.size() - 1; i >= 0; i--) { - final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers; + final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i) + .getTaskFragmentContainers(); for (int j = containers.size() - 1; j >= 0; j--) { final TaskFragmentContainer container = containers.get(j); if (container.hasPendingAppearedActivity(activityToken)) { @@ -1342,7 +1343,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Check appeared activity if there is no such pending appeared activity. for (int i = mTaskContainers.size() - 1; i >= 0; i--) { - final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers; + final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i) + .getTaskFragmentContainers(); for (int j = containers.size() - 1; j >= 0; j--) { final TaskFragmentContainer container = containers.get(j); if (container.hasAppearedActivity(activityToken)) { @@ -1418,7 +1420,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) { removeExistingSecondaryContainers(wct, primaryContainer); } - primaryContainer.getTaskContainer().mSplitContainers.add(splitContainer); + primaryContainer.getTaskContainer().addSplitContainer(splitContainer); } /** Cleanups all the dependencies when the TaskFragment is entering PIP. */ @@ -1430,8 +1432,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return; } final List<SplitContainer> splitsToRemove = new ArrayList<>(); + final List<SplitContainer> splitContainers = taskContainer.getSplitContainers(); final Set<TaskFragmentContainer> containersToUpdate = new ArraySet<>(); - for (SplitContainer splitContainer : taskContainer.mSplitContainers) { + for (SplitContainer splitContainer : splitContainers) { if (splitContainer.getPrimaryContainer() != container && splitContainer.getSecondaryContainer() != container) { continue; @@ -1449,7 +1452,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } container.resetDependencies(); - taskContainer.mSplitContainers.removeAll(splitsToRemove); + taskContainer.removeSplitContainers(splitsToRemove); // If there is any TaskFragment split with the PIP TaskFragment, update their presentations // since the split is dismissed. // We don't want to close any of them even if they are dependencies of the PIP TaskFragment. @@ -1471,7 +1474,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen void removeContainers(@NonNull TaskContainer taskContainer, @NonNull List<TaskFragmentContainer> containers) { // Remove all split containers that included this one - taskContainer.mContainers.removeAll(containers); + taskContainer.removeTaskFragmentContainers(containers); // Marked as a pending removal which will be removed after it is actually removed on the // server side (#onTaskFragmentVanished). // In this way, we can keep track of the Task bounds until we no longer have any @@ -1481,7 +1484,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Cleanup any split references. final List<SplitContainer> containersToRemove = new ArrayList<>(); - for (SplitContainer splitContainer : taskContainer.mSplitContainers) { + final List<SplitContainer> splitContainers = taskContainer.getSplitContainers(); + for (SplitContainer splitContainer : splitContainers) { if (containersToRemove.contains(splitContainer)) { // Don't need to check because it has been in the remove list. continue; @@ -1492,10 +1496,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen containersToRemove.add(splitContainer); } } - taskContainer.mSplitContainers.removeAll(containersToRemove); + taskContainer.removeSplitContainers(containersToRemove); // Cleanup any dependent references. - for (TaskFragmentContainer containerToUpdate : taskContainer.mContainers) { + final List<TaskFragmentContainer> taskFragmentContainers = + taskContainer.getTaskFragmentContainers(); + for (TaskFragmentContainer containerToUpdate : taskFragmentContainers) { containerToUpdate.removeContainersToFinishOnExit(containers); } } @@ -1534,8 +1540,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (taskContainer == null) { return null; } - for (int i = taskContainer.mContainers.size() - 1; i >= 0; i--) { - final TaskFragmentContainer container = taskContainer.mContainers.get(i); + final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers(); + for (int i = containers.size() - 1; i >= 0; i--) { + final TaskFragmentContainer container = containers.get(i); if (!container.isFinished() && (container.getRunningActivityCount() > 0 // We may be waiting for the top TaskFragment to become non-empty after // creation. In that case, we don't want to treat the TaskFragment below it as @@ -1629,7 +1636,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** Whether the given split is the topmost split in the Task. */ private boolean isTopMostSplit(@NonNull SplitContainer splitContainer) { final List<SplitContainer> splitContainers = splitContainer.getPrimaryContainer() - .getTaskContainer().mSplitContainers; + .getTaskContainer().getSplitContainers(); return splitContainer == splitContainers.get(splitContainers.size() - 1); } @@ -1641,7 +1648,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (container == null) { return null; } - final List<SplitContainer> splitContainers = container.getTaskContainer().mSplitContainers; + final List<SplitContainer> splitContainers = + container.getTaskContainer().getSplitContainers(); if (splitContainers.isEmpty()) { return null; } @@ -1665,7 +1673,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @NonNull TaskFragmentContainer firstContainer, @NonNull TaskFragmentContainer secondContainer) { final List<SplitContainer> splitContainers = firstContainer.getTaskContainer() - .mSplitContainers; + .getSplitContainers(); for (int i = splitContainers.size() - 1; i >= 0; i--) { final SplitContainer splitContainer = splitContainers.get(i); final TaskFragmentContainer primary = splitContainer.getPrimaryContainer(); @@ -1930,7 +1938,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) { for (int i = mTaskContainers.size() - 1; i >= 0; i--) { - final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers; + final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i) + .getTaskFragmentContainers(); for (TaskFragmentContainer container : containers) { if (container.getTaskFragmentToken().equals(fragmentToken)) { return container; @@ -1945,7 +1954,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") SplitContainer getSplitContainer(@NonNull IBinder token) { for (int i = mTaskContainers.size() - 1; i >= 0; i--) { - final List<SplitContainer> containers = mTaskContainers.valueAt(i).mSplitContainers; + final List<SplitContainer> containers = mTaskContainers.valueAt(i).getSplitContainers(); for (SplitContainer container : containers) { if (container.getToken().equals(token)) { return container; @@ -2091,7 +2100,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } for (int i = mTaskContainers.size() - 1; i >= 0; i--) { final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i) - .mContainers; + .getTaskFragmentContainers(); for (int j = containers.size() - 1; j >= 0; j--) { final TaskFragmentContainer container = containers.get(j); if (!container.hasActivity(activityToken) 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 4b15bb187035..4580c9836168 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -51,11 +51,11 @@ class TaskContainer { /** Active TaskFragments in this Task. */ @NonNull - final List<TaskFragmentContainer> mContainers = new ArrayList<>(); + private final List<TaskFragmentContainer> mContainers = new ArrayList<>(); /** Active split pairs in this Task. */ @NonNull - final List<SplitContainer> mSplitContainers = new ArrayList<>(); + private final List<SplitContainer> mSplitContainers = new ArrayList<>(); @NonNull private final Configuration mConfiguration; @@ -207,6 +207,53 @@ class TaskContainer { return false; } + /** + * Returns a list of {@link SplitContainer}. Do not modify the containers directly on the + * returned list. Use {@link #addSplitContainer} or {@link #removeSplitContainers} instead. + */ + @NonNull + List<SplitContainer> getSplitContainers() { + return mSplitContainers; + } + + void addSplitContainer(@NonNull SplitContainer splitContainer) { + mSplitContainers.add(splitContainer); + } + + void removeSplitContainers(@NonNull List<SplitContainer> containers) { + mSplitContainers.removeAll(containers); + } + + void addTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) { + mContainers.add(taskFragmentContainer); + } + + void addTaskFragmentContainer(int index, @NonNull TaskFragmentContainer taskFragmentContainer) { + mContainers.add(index, taskFragmentContainer); + } + + void removeTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) { + mContainers.remove(taskFragmentContainer); + } + + void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainer) { + mContainers.removeAll(taskFragmentContainer); + } + + void clearTaskFragmentContainer() { + mContainers.clear(); + } + + /** + * Returns a list of {@link TaskFragmentContainer}. Do not modify the containers directly on + * the returned list. Use {@link #addTaskFragmentContainer}, + * {@link #removeTaskFragmentContainer} or other related methods instead. + */ + @NonNull + List<TaskFragmentContainer> getTaskFragmentContainers() { + return mContainers; + } + /** 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/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index 60be9d16d749..61df335515b8 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -180,23 +180,25 @@ class TaskFragmentContainer { throw new IllegalArgumentException( "pairedPrimaryContainer must be in the same Task"); } - final int primaryIndex = taskContainer.mContainers.indexOf(pairedPrimaryContainer); - taskContainer.mContainers.add(primaryIndex + 1, this); + final int primaryIndex = taskContainer.indexOf(pairedPrimaryContainer); + taskContainer.addTaskFragmentContainer(primaryIndex + 1, this); } else if (pendingAppearedActivity != null) { // The TaskFragment will be positioned right above the pending appeared Activity. If any // existing TaskFragment is empty with pending Intent, it is likely that the Activity of // the pending Intent hasn't been created yet, so the new Activity should be below the // empty TaskFragment. - int i = taskContainer.mContainers.size() - 1; + final List<TaskFragmentContainer> containers = + taskContainer.getTaskFragmentContainers(); + int i = containers.size() - 1; for (; i >= 0; i--) { - final TaskFragmentContainer container = taskContainer.mContainers.get(i); + final TaskFragmentContainer container = containers.get(i); if (!container.isEmpty() || container.getPendingAppearedIntent() == null) { break; } } - taskContainer.mContainers.add(i + 1, this); + taskContainer.addTaskFragmentContainer(i + 1, this); } else { - taskContainer.mContainers.add(this); + taskContainer.addTaskFragmentContainer(this); } if (pendingAppearedActivity != null) { addPendingAppearedActivity(pendingAppearedActivity); 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 ff08782e8cd8..9e264726a65a 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 @@ -183,23 +183,23 @@ public class SplitControllerTest { // tf2 has running activity so is active. final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class); doReturn(1).when(tf2).getRunningActivityCount(); - taskContainer.mContainers.add(tf2); + taskContainer.addTaskFragmentContainer(tf2); // tf3 is finished so is not active. final TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class); doReturn(true).when(tf3).isFinished(); doReturn(false).when(tf3).isWaitingActivityAppear(); - taskContainer.mContainers.add(tf3); + taskContainer.addTaskFragmentContainer(tf3); mSplitController.mTaskContainers.put(TASK_ID, taskContainer); assertWithMessage("Must return tf2 because tf3 is not active.") .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2); - taskContainer.mContainers.remove(tf3); + taskContainer.removeTaskFragmentContainer(tf3); assertWithMessage("Must return tf2 because tf2 has running activity.") .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2); - taskContainer.mContainers.remove(tf2); + taskContainer.removeTaskFragmentContainer(tf2); assertWithMessage("Must return tf because we are waiting for tf1 to appear.") .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1); @@ -320,11 +320,11 @@ public class SplitControllerTest { doReturn(tf).when(splitContainer).getSecondaryContainer(); doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer(); doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule(); - final List<SplitContainer> splitContainers = - mSplitController.getTaskContainer(TASK_ID).mSplitContainers; - splitContainers.add(splitContainer); + final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID); + taskContainer.addSplitContainer(splitContainer); // Add a mock SplitContainer on top of splitContainer - splitContainers.add(1, mock(SplitContainer.class)); + final SplitContainer splitContainer2 = mock(SplitContainer.class); + taskContainer.addSplitContainer(splitContainer2); mSplitController.updateContainer(mTransaction, tf); @@ -332,7 +332,9 @@ public class SplitControllerTest { // Verify if one or both containers in the top SplitContainer are finished, // dismissPlaceholder() won't be called. - splitContainers.remove(1); + final ArrayList<SplitContainer> splitContainersToRemove = new ArrayList<>(); + splitContainersToRemove.add(splitContainer2); + taskContainer.removeSplitContainers(splitContainersToRemove); doReturn(true).when(tf).isFinished(); mSplitController.updateContainer(mTransaction, tf); @@ -363,7 +365,8 @@ public class SplitControllerTest { final Activity r1 = createMockActivity(); addSplitTaskFragments(r0, r1); final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID); - final TaskFragmentContainer taskFragmentContainer = taskContainer.mContainers.get(0); + final TaskFragmentContainer taskFragmentContainer = + taskContainer.getTaskFragmentContainers().get(0); spyOn(taskContainer); // No update when the Task is invisible. @@ -377,7 +380,7 @@ public class SplitControllerTest { doReturn(true).when(taskContainer).isVisible(); mSplitController.updateContainer(mTransaction, taskFragmentContainer); - verify(mSplitPresenter).updateSplitContainer(taskContainer.mSplitContainers.get(0), + verify(mSplitPresenter).updateSplitContainer(taskContainer.getSplitContainers().get(0), mTransaction); } @@ -1090,8 +1093,8 @@ public class SplitControllerTest { verify(mTransaction).finishActivity(mActivity.getActivityToken()); verify(mTransaction).finishActivity(secondaryActivity0.getActivityToken()); verify(mTransaction).finishActivity(secondaryActivity1.getActivityToken()); - assertTrue(taskContainer.mContainers.isEmpty()); - assertTrue(taskContainer.mSplitContainers.isEmpty()); + assertTrue(taskContainer.getTaskFragmentContainers().isEmpty()); + assertTrue(taskContainer.getSplitContainers().isEmpty()); } @Test @@ -1363,15 +1366,13 @@ public class SplitControllerTest { TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); tf.setInfo(mTransaction, createMockTaskFragmentInfo(tf, mActivity)); - List<TaskFragmentContainer> containers = mSplitController.mTaskContainers.get(TASK_ID) - .mContainers; - - assertEquals(containers.get(0), tf); + final TaskContainer taskContainer = mSplitController.mTaskContainers.get(TASK_ID); + assertEquals(taskContainer.getTaskFragmentContainers().get(0), tf); mSplitController.finishActivityStacks(Collections.singleton(tf.getTaskFragmentToken())); verify(mSplitPresenter).deleteTaskFragment(any(), eq(tf.getTaskFragmentToken())); - assertTrue(containers.isEmpty()); + assertTrue(taskContainer.getTaskFragmentContainers().isEmpty()); } @Test @@ -1381,10 +1382,8 @@ public class SplitControllerTest { bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity)); topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity())); - List<TaskFragmentContainer> containers = mSplitController.mTaskContainers.get(TASK_ID) - .mContainers; - - assertEquals(containers.size(), 2); + final TaskContainer taskContainer = mSplitController.mTaskContainers.get(TASK_ID); + assertEquals(taskContainer.getTaskFragmentContainers().size(), 2); Set<IBinder> activityStackTokens = new ArraySet<>(new IBinder[]{ topTf.getTaskFragmentToken(), bottomTf.getTaskFragmentToken()}); @@ -1403,7 +1402,7 @@ public class SplitControllerTest { + "regardless of the order in ActivityStack set", topTf.getTaskFragmentToken(), fragmentTokens.get(1)); - assertTrue(containers.isEmpty()); + assertTrue(taskContainer.getTaskFragmentContainers().isEmpty()); } @Test 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 13e709271221..11af1d1f20e1 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 @@ -127,7 +127,7 @@ public class TaskContainerTest { assertFalse(taskContainer.isEmpty()); taskContainer.mFinishedContainer.add(tf.getTaskFragmentToken()); - taskContainer.mContainers.clear(); + taskContainer.clearTaskFragmentContainer(); assertFalse(taskContainer.isEmpty()); } @@ -152,13 +152,13 @@ public class TaskContainerTest { assertNull(taskContainer.getTopNonFinishingActivity()); final TaskFragmentContainer tf0 = mock(TaskFragmentContainer.class); - taskContainer.mContainers.add(tf0); + taskContainer.addTaskFragmentContainer(tf0); final Activity activity0 = mock(Activity.class); doReturn(activity0).when(tf0).getTopNonFinishingActivity(); assertEquals(activity0, taskContainer.getTopNonFinishingActivity()); final TaskFragmentContainer tf1 = mock(TaskFragmentContainer.class); - taskContainer.mContainers.add(tf1); + taskContainer.addTaskFragmentContainer(tf1); assertEquals(activity0, taskContainer.getTopNonFinishingActivity()); final Activity activity1 = mock(Activity.class); diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 54978bd4496d..71598938f42f 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -42,16 +42,19 @@ filegroup { filegroup { name: "wm_shell_util-sources", srcs: [ - "src/com/android/wm/shell/util/**/*.java", + "src/com/android/wm/shell/animation/Interpolators.java", + "src/com/android/wm/shell/animation/PhysicsAnimator.kt", + "src/com/android/wm/shell/common/bubbles/*.kt", + "src/com/android/wm/shell/common/bubbles/*.java", + "src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt", "src/com/android/wm/shell/common/split/SplitScreenConstants.java", - "src/com/android/wm/shell/sysui/ShellSharedConstants.java", "src/com/android/wm/shell/common/TransactionPool.java", - "src/com/android/wm/shell/common/bubbles/*.java", "src/com/android/wm/shell/common/TriangleShape.java", - "src/com/android/wm/shell/animation/Interpolators.java", + "src/com/android/wm/shell/draganddrop/DragAndDropConstants.java", "src/com/android/wm/shell/pip/PipContentOverlay.java", "src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java", - "src/com/android/wm/shell/draganddrop/DragAndDropConstants.java", + "src/com/android/wm/shell/sysui/ShellSharedConstants.java", + "src/com/android/wm/shell/util/**/*.java", ], path: "src", } diff --git a/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_section.xml b/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_section.xml new file mode 100644 index 000000000000..d99d64d8da20 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_section.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 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. + --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true"> + <ripple android:color="#99999999"> + <item android:drawable="@drawable/bubble_manage_menu_bg" /> + </ripple> + </item> + <item android:drawable="@drawable/bubble_manage_menu_bg" /> +</selector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml index 5d7771366bec..ce242751c172 100644 --- a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml +++ b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml @@ -13,13 +13,20 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - <group android:translateY="8.0"> +<vector + xmlns:android="http://schemas.android.com/apk/res/android" + android:width="128dp" + android:height="4dp" + android:viewportWidth="128" + android:viewportHeight="4" + > + <group> + <clip-path + android:pathData="M2 0H126C127.105 0 128 0.895431 128 2C128 3.10457 127.105 4 126 4H2C0.895431 4 0 3.10457 0 2C0 0.895431 0.895431 0 2 0Z" + /> <path - android:fillColor="@android:color/black" android:pathData="M3,5V3H21V5Z"/> + android:pathData="M0 0V4H128V0" + android:fillColor="@android:color/black" + /> </group> </vector> diff --git a/libs/WindowManager/Shell/res/drawable/ic_expand_less.xml b/libs/WindowManager/Shell/res/drawable/ic_expand_less.xml new file mode 100644 index 000000000000..f4508464883d --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/ic_expand_less.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M18.59,16.41L20,15L12,7L4,15L5.41,16.41L12,9.83" + android:fillColor="#5F6368"/> +</vector> diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml new file mode 100644 index 000000000000..ddcd5c60d9c8 --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> +<com.android.wm.shell.bubbles.bar.BubbleBarMenuItemView + xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="@dimen/bubble_bar_manage_menu_item_height" + android:gravity="center_vertical" + android:paddingStart="@dimen/bubble_menu_padding" + android:paddingEnd="@dimen/bubble_menu_padding" + android:background="@drawable/bubble_manage_menu_row"> + + <ImageView + android:id="@+id/bubble_bar_menu_item_icon" + android:layout_width="@dimen/bubble_bar_manage_menu_item_icon_size" + android:layout_height="@dimen/bubble_bar_manage_menu_item_icon_size" + android:contentDescription="@null"/> + + <TextView + android:id="@+id/bubble_bar_menu_item_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:textColor="?android:attr/textColorPrimary" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault" /> + +</com.android.wm.shell.bubbles.bar.BubbleBarMenuItemView>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml new file mode 100644 index 000000000000..82e5aee41ff2 --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> +<com.android.wm.shell.bubbles.bar.BubbleBarMenuView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:minWidth="@dimen/bubble_bar_manage_menu_min_width" + android:orientation="vertical" + android:elevation="@dimen/bubble_manage_menu_elevation" + android:paddingTop="@dimen/bubble_bar_manage_menu_padding_top" + android:paddingHorizontal="@dimen/bubble_bar_manage_menu_padding" + android:paddingBottom="@dimen/bubble_bar_manage_menu_padding" + android:clipToPadding="false"> + + <LinearLayout + android:id="@+id/bubble_bar_manage_menu_bubble_section" + android:layout_width="match_parent" + android:layout_height="@dimen/bubble_bar_manage_menu_item_height" + android:orientation="horizontal" + android:gravity="center_vertical" + android:paddingStart="14dp" + android:paddingEnd="12dp" + android:background="@drawable/bubble_manage_menu_section" + android:elevation="@dimen/bubble_manage_menu_elevation"> + + <ImageView + android:id="@+id/bubble_bar_manage_menu_bubble_icon" + android:layout_width="@dimen/bubble_menu_icon_size" + android:layout_height="@dimen/bubble_menu_icon_size" + android:contentDescription="@null" /> + + <TextView + android:id="@+id/bubble_bar_manage_menu_bubble_title" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_weight="1" + android:textColor="?android:attr/textColorPrimary" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault" /> + + <ImageView + android:id="@+id/bubble_bar_manage_menu_dismiss_icon" + android:layout_width="@dimen/bubble_bar_manage_menu_dismiss_icon_size" + android:layout_height="@dimen/bubble_bar_manage_menu_dismiss_icon_size" + android:layout_marginStart="8dp" + android:contentDescription="@null" + android:src="@drawable/ic_expand_less" + app:tint="?android:attr/textColorPrimary" /> + + </LinearLayout> + + <LinearLayout + android:id="@+id/bubble_bar_manage_menu_actions_section" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_marginTop="@dimen/bubble_bar_manage_menu_section_spacing" + android:background="@drawable/bubble_manage_menu_bg" + android:elevation="@dimen/bubble_manage_menu_elevation" /> + +</com.android.wm.shell.bubbles.bar.BubbleBarMenuView>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml index 1d6864c152c2..0ca912e20527 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml @@ -28,6 +28,7 @@ android:layout_width="176dp" android:layout_height="42dp" android:paddingHorizontal="24dp" + android:paddingVertical="19dp" android:contentDescription="@string/handle_text" android:src="@drawable/decor_handle_dark" tools:tint="@color/desktop_mode_caption_handle_bar_dark" diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml index 171a6b2fe5fb..f2a07857cd4a 100644 --- a/libs/WindowManager/Shell/res/values/colors.xml +++ b/libs/WindowManager/Shell/res/values/colors.xml @@ -30,6 +30,9 @@ <color name="bubbles_light">#FFFFFF</color> <color name="bubbles_dark">@color/GM2_grey_800</color> <color name="bubbles_icon_tint">@color/GM2_grey_700</color> + <color name="bubble_bar_expanded_view_handle_light">#EBffffff</color> + <color name="bubble_bar_expanded_view_handle_dark">#99000000</color> + <color name="bubble_bar_expanded_view_menu_close">#DC362E</color> <!-- PiP --> <color name="pip_custom_close_bg">#D93025</color> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 2be34c90a661..14e82539a6a5 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -229,7 +229,25 @@ <!-- Size of the bubble bar (height), should match transient_taskbar_size in Launcher. --> <dimen name="bubblebar_size">72dp</dimen> <!-- The size of the drag handle / menu shown along with a bubble bar expanded view. --> - <dimen name="bubblebar_expanded_view_menu_size">16dp</dimen> + <dimen name="bubble_bar_expanded_view_handle_size">40dp</dimen> + <!-- The width of the drag handle shown along with a bubble bar expanded view. --> + <dimen name="bubble_bar_expanded_view_handle_width">128dp</dimen> + <!-- The height of the drag handle shown along with a bubble bar expanded view. --> + <dimen name="bubble_bar_expanded_view_handle_height">4dp</dimen> + <!-- Minimum width of the bubble bar manage menu. --> + <dimen name="bubble_bar_manage_menu_min_width">200dp</dimen> + <!-- Size of the dismiss icon in the bubble bar manage menu. --> + <dimen name="bubble_bar_manage_menu_dismiss_icon_size">16dp</dimen> + <!-- Padding of the bubble bar manage menu, provides space for menu shadows --> + <dimen name="bubble_bar_manage_menu_padding">8dp</dimen> + <!-- Top padding of the bubble bar manage menu --> + <dimen name="bubble_bar_manage_menu_padding_top">2dp</dimen> + <!-- Spacing between sections of the bubble bar manage menu --> + <dimen name="bubble_bar_manage_menu_section_spacing">2dp</dimen> + <!-- Height of an item in the bubble bar manage menu. --> + <dimen name="bubble_bar_manage_menu_item_height">52dp</dimen> + <!-- Size of the icons in the bubble bar manage menu. --> + <dimen name="bubble_bar_manage_menu_item_icon_size">20dp</dimen> <!-- Bottom and end margin for compat buttons. --> <dimen name="compat_button_margin">24dp</dimen> @@ -398,7 +416,11 @@ <!-- The radius of the caption menu shadow. --> <dimen name="desktop_mode_handle_menu_shadow_radius">2dp</dimen> - <dimen name="freeform_resize_handle">30dp</dimen> + <dimen name="freeform_resize_handle">15dp</dimen> <dimen name="freeform_resize_corner">44dp</dimen> + + <!-- The height of the area at the top of the screen where a freeform task will transition to + fullscreen if dragged until the top bound of the task is within the area. --> + <dimen name="desktop_mode_transition_area_height">16dp</dimen> </resources> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 102f2cb4b8d0..7e09c989e1b3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -422,6 +422,7 @@ public class Bubble implements BubbleViewProvider { } if (mBubbleBarExpandedView != null) { mBubbleBarExpandedView.cleanUpExpandedState(); + mBubbleBarExpandedView = null; } if (mIntent != null) { mIntent.unregisterCancelListener(mIntentCancelListener); @@ -549,10 +550,10 @@ public class Bubble implements BubbleViewProvider { /** * Set visibility of bubble in the expanded state. * - * @param visibility {@code true} if the expanded bubble should be visible on the screen. - * - * Note that this contents visibility doesn't affect visibility at {@link android.view.View}, + * <p>Note that this contents visibility doesn't affect visibility at {@link android.view.View}, * and setting {@code false} actually means rendering the expanded view in transparent. + * + * @param visibility {@code true} if the expanded bubble should be visible on the screen. */ @Override public void setTaskViewVisibility(boolean visibility) { @@ -855,7 +856,8 @@ public class Bubble implements BubbleViewProvider { return mIsAppBubble; } - Intent getSettingsIntent(final Context context) { + /** Creates open app settings intent */ + public Intent getSettingsIntent(final Context context) { final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS); intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName()); final int uid = getUid(context); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 0fdfbb8c0c61..688023737074 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -64,7 +64,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.service.notification.NotificationListenerService; @@ -92,6 +91,7 @@ import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; +import com.android.wm.shell.bubbles.properties.BubbleProperties; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.FloatingContentCoordinator; @@ -143,16 +143,6 @@ public class BubbleController implements ConfigurationChangeListener, private static final String SYSTEM_DIALOG_REASON_KEY = "reason"; private static final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav"; - // TODO(b/256873975) Should use proper flag when available to shell/launcher - /** - * Whether bubbles are showing in the bubble bar from launcher. This is only available - * on large screens and {@link BubbleController#isShowingAsBubbleBar()} should be used - * to check all conditions that indicate if the bubble bar is in use. - */ - private static final boolean BUBBLE_BAR_ENABLED = - SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false); - - /** * Common interface to send updates to bubble views. */ @@ -195,6 +185,7 @@ public class BubbleController implements ConfigurationChangeListener, private final ShellController mShellController; private final ShellCommandHandler mShellCommandHandler; private final IWindowManager mWmService; + private final BubbleProperties mBubbleProperties; // Used to post to main UI thread private final ShellExecutor mMainExecutor; @@ -291,7 +282,8 @@ public class BubbleController implements ConfigurationChangeListener, @ShellBackgroundThread ShellExecutor bgExecutor, TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue, - IWindowManager wmService) { + IWindowManager wmService, + BubbleProperties bubbleProperties) { mContext = context; mShellCommandHandler = shellCommandHandler; mShellController = shellController; @@ -328,6 +320,7 @@ public class BubbleController implements ConfigurationChangeListener, mDragAndDropController = dragAndDropController; mSyncQueue = syncQueue; mWmService = wmService; + mBubbleProperties = bubbleProperties; shellInit.addInitCallback(this::onInit, this); } @@ -518,11 +511,14 @@ public class BubbleController implements ConfigurationChangeListener, /** * Sets a listener to be notified of bubble updates. This is used by launcher so that * it may render bubbles in itself. Only one listener is supported. + * + * <p>If bubble bar is supported, bubble views will be updated to switch to bar mode. */ public void registerBubbleStateListener(Bubbles.BubbleStateListener listener) { - if (isShowingAsBubbleBar()) { - // Only set the listener if bubble bar is showing. + if (canShowAsBubbleBar() && listener != null) { + // Only set the listener if we can show the bubble bar. mBubbleStateListener = listener; + setUpBubbleViewsForMode(); sendInitialListenerUpdate(); } else { mBubbleStateListener = null; @@ -531,9 +527,15 @@ public class BubbleController implements ConfigurationChangeListener, /** * Unregisters the {@link Bubbles.BubbleStateListener}. + * + * <p>If there's an existing listener, then we're switching back to stack mode and bubble views + * will be updated accordingly. */ public void unregisterBubbleStateListener() { - mBubbleStateListener = null; + if (mBubbleStateListener != null) { + mBubbleStateListener = null; + setUpBubbleViewsForMode(); + } } /** @@ -645,8 +647,12 @@ public class BubbleController implements ConfigurationChangeListener, /** Whether bubbles are showing in the bubble bar. */ public boolean isShowingAsBubbleBar() { - // TODO(b/269670598): should also check that we're in gesture nav - return BUBBLE_BAR_ENABLED && mBubblePositioner.isLargeScreen(); + return canShowAsBubbleBar() && mBubbleStateListener != null; + } + + /** Whether the current configuration supports showing as bubble bar. */ + private boolean canShowAsBubbleBar() { + return mBubbleProperties.isBubbleBarEnabled() && mBubblePositioner.isLargeScreen(); } /** Whether this userId belongs to the current user. */ @@ -719,6 +725,7 @@ public class BubbleController implements ConfigurationChangeListener, // TODO(b/273312602): consider foldables where we do need a stack view when folded if (mLayerView == null) { mLayerView = new BubbleBarLayerView(mContext, this); + mLayerView.setUnBubbleConversationCallback(mSysuiProxy::onUnbubbleConversation); } } else { if (mStackView == null) { @@ -773,12 +780,12 @@ public class BubbleController implements ConfigurationChangeListener, try { mAddedToWindowManager = true; registerBroadcastReceiver(); - mBubbleData.getOverflow().initialize(this); + mBubbleData.getOverflow().initialize(this, isShowingAsBubbleBar()); // (TODO: b/273314541) some duplication in the inset listener if (isShowingAsBubbleBar()) { mWindowManager.addView(mLayerView, mWmLayoutParams); mLayerView.setOnApplyWindowInsetsListener((view, windowInsets) -> { - if (!windowInsets.equals(mWindowInsets)) { + if (!windowInsets.equals(mWindowInsets) && mLayerView != null) { mWindowInsets = windowInsets; mBubblePositioner.update(); mLayerView.onDisplaySizeChanged(); @@ -788,7 +795,7 @@ public class BubbleController implements ConfigurationChangeListener, } else { mWindowManager.addView(mStackView, mWmLayoutParams); mStackView.setOnApplyWindowInsetsListener((view, windowInsets) -> { - if (!windowInsets.equals(mWindowInsets)) { + if (!windowInsets.equals(mWindowInsets) && mStackView != null) { mWindowInsets = windowInsets; mBubblePositioner.update(); mStackView.onDisplaySizeChanged(); @@ -1065,9 +1072,18 @@ public class BubbleController implements ConfigurationChangeListener, * Expands and selects the provided bubble as long as it already exists in the stack or the * overflow. * - * This is used by external callers (launcher). + * <p>This is used by external callers (launcher). */ - public void expandStackAndSelectBubbleFromLauncher(String key) { + @VisibleForTesting + public void expandStackAndSelectBubbleFromLauncher(String key, boolean onLauncherHome) { + mBubblePositioner.setShowingInBubbleBar(onLauncherHome); + + if (BubbleOverflow.KEY.equals(key)) { + mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow()); + mLayerView.showExpandedView(mBubbleData.getOverflow()); + return; + } + Bubble b = mBubbleData.getAnyBubbleWithkey(key); if (b == null) { return; @@ -1220,6 +1236,13 @@ public class BubbleController implements ConfigurationChangeListener, } /** + * Dismiss bubble if it exists and remove it from the stack + */ + public void dismissBubble(Bubble bubble, @Bubbles.DismissReason int reason) { + mBubbleData.dismissBubbleWithKey(bubble.getKey(), reason); + } + + /** * Performs a screenshot that may exclude the bubble layer, if one is present. The screenshot * can be access via the supplied {@link SynchronousScreenCaptureListener#getBuffer()} * asynchronously. @@ -1259,7 +1282,9 @@ public class BubbleController implements ConfigurationChangeListener, return; } mOverflowDataLoadNeeded = false; - mDataRepository.loadBubbles(mCurrentUserId, (bubbles) -> { + List<UserInfo> users = mUserManager.getAliveUsers(); + List<Integer> userIds = users.stream().map(userInfo -> userInfo.id).toList(); + mDataRepository.loadBubbles(mCurrentUserId, userIds, (bubbles) -> { bubbles.forEach(bubble -> { if (mBubbleData.hasAnyBubbleWithKey(bubble.getKey())) { // if the bubble is already active, there's no need to push it to overflow @@ -1278,6 +1303,50 @@ public class BubbleController implements ConfigurationChangeListener, }); } + void setUpBubbleViewsForMode() { + mBubbleViewCallback = isShowingAsBubbleBar() + ? mBubbleBarViewCallback + : mBubbleStackViewCallback; + + // reset the overflow so that it can be re-added later if needed. + if (mStackView != null) { + mStackView.resetOverflowView(); + mStackView.removeAllViews(); + } + // cleanup existing bubble views so they can be recreated later if needed. + mBubbleData.getBubbles().forEach(Bubble::cleanupViews); + + // remove the current bubble container from window manager, null it out, and create a new + // container based on the current mode. + removeFromWindowManagerMaybe(); + mLayerView = null; + mStackView = null; + ensureBubbleViewsAndWindowCreated(); + + // inflate bubble views + BubbleViewInfoTask.Callback callback = null; + if (!isShowingAsBubbleBar()) { + callback = b -> { + if (mStackView != null) { + mStackView.addBubble(b); + mStackView.setSelectedBubble(b); + } else { + Log.w(TAG, "Tried to add a bubble to the stack but the stack is null"); + } + }; + } + for (int i = mBubbleData.getBubbles().size() - 1; i >= 0; i--) { + Bubble bubble = mBubbleData.getBubbles().get(i); + bubble.inflate(callback, + mContext, + this, + mStackView, + mLayerView, + mBubbleIconFactory, + false /* skipInflation */); + } + } + /** * Adds or updates a bubble associated with the provided notification entry. * @@ -1738,7 +1807,7 @@ public class BubbleController implements ConfigurationChangeListener, // Update the cached state for queries from SysUI mImpl.mCachedState.update(update); - if (isShowingAsBubbleBar() && mBubbleStateListener != null) { + if (isShowingAsBubbleBar()) { BubbleBarUpdate bubbleBarUpdate = update.toBubbleBarUpdate(); // Some updates aren't relevant to the bubble bar so check first. if (bubbleBarUpdate.anythingChanged()) { @@ -1846,7 +1915,7 @@ public class BubbleController implements ConfigurationChangeListener, if (mStackView != null) { mStackView.setVisibility(VISIBLE); } - if (mLayerView != null && isStackExpanded()) { + if (mLayerView != null) { mLayerView.setVisibility(VISIBLE); } } @@ -1860,10 +1929,17 @@ public class BubbleController implements ConfigurationChangeListener, } @VisibleForTesting + @Nullable public BubbleStackView getStackView() { return mStackView; } + @VisibleForTesting + @Nullable + public BubbleBarLayerView getLayerView() { + return mLayerView; + } + /** * Check if notification panel is in an expanded state. * Makes a call to System UI process and delivers the result via {@code callback} on the @@ -2002,22 +2078,18 @@ public class BubbleController implements ConfigurationChangeListener, @Override public void registerBubbleListener(IBubblesListener listener) { - mMainExecutor.execute(() -> { - mListener.register(listener); - }); + mMainExecutor.execute(() -> mListener.register(listener)); } @Override public void unregisterBubbleListener(IBubblesListener listener) { - mMainExecutor.execute(() -> mListener.unregister()); + mMainExecutor.execute(mListener::unregister); } @Override public void showBubble(String key, boolean onLauncherHome) { - mMainExecutor.execute(() -> { - mBubblePositioner.setShowingInBubbleBar(onLauncherHome); - mController.expandStackAndSelectBubbleFromLauncher(key); - }); + mMainExecutor.execute( + () -> mController.expandStackAndSelectBubbleFromLauncher(key, onLauncherHome)); } @Override @@ -2029,11 +2101,6 @@ public class BubbleController implements ConfigurationChangeListener, public void collapseBubbles() { mMainExecutor.execute(() -> mController.collapseStack()); } - - @Override - public void onTaskbarStateChanged(int newState) { - // TODO (b/269670598) - } } private class BubblesImpl implements Bubbles { @@ -2144,7 +2211,7 @@ public class BubbleController implements ConfigurationChangeListener, pw.println(" suppressing: " + key); } - pw.print("mAppBubbleTaskIds: " + mAppBubbleTaskIds.values()); + pw.println("mAppBubbleTaskIds: " + mAppBubbleTaskIds.values()); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt index e37c785f15f5..896a33449185 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.bubbles import android.annotation.SuppressLint import android.annotation.UserIdInt -import android.content.Context import android.content.pm.LauncherApps import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC @@ -25,6 +24,8 @@ import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LA import android.content.pm.UserInfo import android.os.UserHandle import android.util.Log +import android.util.SparseArray +import com.android.internal.annotations.VisibleForTesting import com.android.wm.shell.bubbles.Bubbles.BubbleMetadataFlagListener import com.android.wm.shell.bubbles.storage.BubbleEntity import com.android.wm.shell.bubbles.storage.BubblePersistentRepository @@ -33,19 +34,19 @@ import com.android.wm.shell.common.ShellExecutor import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.launch import kotlinx.coroutines.yield -internal class BubbleDataRepository( - context: Context, +class BubbleDataRepository( private val launcherApps: LauncherApps, - private val mainExecutor: ShellExecutor + private val mainExecutor: ShellExecutor, + private val persistentRepository: BubblePersistentRepository, ) { private val volatileRepository = BubbleVolatileRepository(launcherApps) - private val persistentRepository = BubblePersistentRepository(context) - private val ioScope = CoroutineScope(Dispatchers.IO) + private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) private var job: Job? = null // For use in Bubble construction. @@ -98,6 +99,43 @@ internal class BubbleDataRepository( if (volatileRepository.sanitizeBubbles(userIds)) persistToDisk() } + /** + * Removes all entities that don't have a user in the activeUsers list, if any entities were + * removed it persists the new list to disk. + */ + private fun filterForActiveUsersAndPersist( + activeUsers: List<Int>, + entitiesByUser: SparseArray<List<BubbleEntity>> + ): SparseArray<List<BubbleEntity>> { + val validEntitiesByUser = SparseArray<List<BubbleEntity>>() + var entitiesChanged = false + for (i in 0 until entitiesByUser.size()) { + val parentUserId = entitiesByUser.keyAt(i) + if (activeUsers.contains(parentUserId)) { + val validEntities = mutableListOf<BubbleEntity>() + // Check if each of the bubbles in the top-level user still has a valid user + // as it could belong to a profile and have a different id from the parent. + for (entity in entitiesByUser.get(parentUserId)) { + if (activeUsers.contains(entity.userId)) { + validEntities.add(entity) + } else { + entitiesChanged = true + } + } + if (validEntities.isNotEmpty()) { + validEntitiesByUser.put(parentUserId, validEntities) + } + } else { + entitiesChanged = true + } + } + if (entitiesChanged) { + persistToDisk(validEntitiesByUser) + return validEntitiesByUser + } + return entitiesByUser + } + private fun transform(bubbles: List<Bubble>): List<BubbleEntity> { return bubbles.mapNotNull { b -> BubbleEntity( @@ -129,15 +167,17 @@ internal class BubbleDataRepository( * Job C resumes and reaches yield() and is then cancelled * Job D resumes and performs another blocking I/O */ - private fun persistToDisk() { + private fun persistToDisk( + entitiesByUser: SparseArray<List<BubbleEntity>> = volatileRepository.bubbles + ) { val prev = job - job = ioScope.launch { + job = coroutineScope.launch { // if there was an ongoing disk I/O operation, they can be cancelled prev?.cancelAndJoin() // check for cancellation before disk I/O yield() // save to disk - persistentRepository.persistsToDisk(volatileRepository.bubbles) + persistentRepository.persistsToDisk(entitiesByUser) } } @@ -148,7 +188,12 @@ internal class BubbleDataRepository( * bubbles. */ @SuppressLint("WrongConstant") - fun loadBubbles(userId: Int, cb: (List<Bubble>) -> Unit) = ioScope.launch { + @VisibleForTesting + fun loadBubbles( + userId: Int, + currentUsers: List<Int>, + cb: (List<Bubble>) -> Unit + ) = coroutineScope.launch { /** * Load BubbleEntity from disk. * e.g. @@ -159,7 +204,12 @@ internal class BubbleDataRepository( * ] */ val entitiesByUser = persistentRepository.readFromDisk() - val entities = entitiesByUser.get(userId) ?: return@launch + + // Before doing anything, validate that the entities we loaded are valid & have an existing + // user. + val validEntitiesByUser = filterForActiveUsersAndPersist(currentUsers, entitiesByUser) + + val entities = validEntitiesByUser.get(userId) ?: return@launch volatileRepository.addBubbles(userId, entities) /** * Extract userId/packageName from these entities. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index e1a3f3a1ac5d..67185655f2d2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -948,9 +948,9 @@ public class BubbleExpandedView extends LinearLayout { mTaskView.onLocationChanged(); } if (mIsOverflow) { - post(() -> { - mOverflowView.show(); - }); + // post this to the looper so that the view has a chance to be laid out before it can + // calculate row and column sizes correctly. + post(() -> mOverflowView.show()); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt index df7f5b4f0150..df19757203eb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt @@ -44,6 +44,7 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl private val inflater: LayoutInflater = LayoutInflater.from(context) private var expandedView: BubbleExpandedView? + private var bubbleBarExpandedView: BubbleBarExpandedView? = null private var overflowBtn: BadgedImageView? init { @@ -53,19 +54,26 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl } /** Call before use and again if cleanUpExpandedState was called. */ - fun initialize(controller: BubbleController) { - createExpandedView() - getExpandedView()?.initialize(controller, controller.stackView, true /* isOverflow */) + fun initialize(controller: BubbleController, forBubbleBar: Boolean) { + if (forBubbleBar) { + createBubbleBarExpandedView().initialize(controller, true /* isOverflow */) + } else { + createExpandedView() + .initialize(controller, controller.stackView, true /* isOverflow */) + } } fun cleanUpExpandedState() { expandedView?.cleanUpExpandedState() expandedView = null + bubbleBarExpandedView?.cleanUpExpandedState() + bubbleBarExpandedView = null } fun update() { updateResources() getExpandedView()?.applyThemeAttrs() + getBubbleBarExpandedView()?.applyThemeAttrs() // Apply inset and new style to fresh icon drawable. getIconView()?.setIconImageResource(R.drawable.bubble_ic_overflow_button) updateBtnTheme() @@ -151,26 +159,39 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl overflowBtn?.updateDotVisibility(true /* animate */) } - fun createExpandedView(): BubbleExpandedView? { - expandedView = + /** Creates the expanded view for bubbles showing in the stack view. */ + private fun createExpandedView(): BubbleExpandedView { + val view = inflater.inflate( R.layout.bubble_expanded_view, null /* root */, false /* attachToRoot */ ) as BubbleExpandedView - expandedView?.applyThemeAttrs() + view.applyThemeAttrs() + expandedView = view updateResources() - return expandedView + return view } override fun getExpandedView(): BubbleExpandedView? { return expandedView } - override fun getBubbleBarExpandedView(): BubbleBarExpandedView? { - return null + /** Creates the expanded view for bubbles showing in the bubble bar. */ + private fun createBubbleBarExpandedView(): BubbleBarExpandedView { + val view = + inflater.inflate( + R.layout.bubble_bar_expanded_view, + null, /* root */ + false /* attachToRoot*/ + ) as BubbleBarExpandedView + view.applyThemeAttrs() + bubbleBarExpandedView = view + return view } + override fun getBubbleBarExpandedView(): BubbleBarExpandedView? = bubbleBarExpandedView + override fun getDotColor(): Int { return dotColor } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index d101b0c4d7e8..cb08f93a9efa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -732,17 +732,25 @@ public class BubblePositioner { /** * How wide the expanded view should be when showing from the bubble bar. */ - public int getExpandedViewWidthForBubbleBar() { - return mExpandedViewLargeScreenWidth; + public int getExpandedViewWidthForBubbleBar(boolean isOverflow) { + return isOverflow ? mOverflowWidth : mExpandedViewLargeScreenWidth; } /** * How tall the expanded view should be when showing from the bubble bar. */ - public int getExpandedViewHeightForBubbleBar() { + public int getExpandedViewHeightForBubbleBar(boolean isOverflow) { + return isOverflow + ? mOverflowHeight + : getExpandedViewBottomForBubbleBar() - mInsets.top - mExpandedViewPadding; + } + + /** The bottom position of the expanded view when showing above the bubble bar. */ + public int getExpandedViewBottomForBubbleBar() { return getAvailableRect().height() + + mInsets.top - mBubbleBarSize - - mExpandedViewPadding * 2 + - mExpandedViewPadding - getBubbleBarHomeAdjustment(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 68fea41e134e..282db9e18d81 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -88,6 +88,8 @@ import com.android.wm.shell.bubbles.animation.PhysicsAnimationLayout; import com.android.wm.shell.bubbles.animation.StackAnimationController; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.bubbles.DismissView; +import com.android.wm.shell.common.bubbles.RelativeTouchListener; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; import java.io.PrintWriter; @@ -307,6 +309,7 @@ public class BubbleStackView extends FrameLayout String bubblesOnScreen = BubbleDebugConfig.formatBubblesString( getBubblesOnScreen(), getExpandedBubble()); + pw.print(" stack visibility : "); pw.println(getVisibility()); pw.print(" bubbles on screen: "); pw.println(bubblesOnScreen); pw.print(" gestureInProgress: "); pw.println(mIsGestureInProgress); pw.print(" showingDismiss: "); pw.println(mDismissView.isShowing()); @@ -968,6 +971,8 @@ public class BubbleStackView extends FrameLayout mBubbleContainer.bringToFront(); mBubbleOverflow = mBubbleData.getOverflow(); + + resetOverflowView(); mBubbleContainer.addView(mBubbleOverflow.getIconView(), mBubbleContainer.getChildCount() /* index */, new FrameLayout.LayoutParams(mPositioner.getBubbleSize(), @@ -1179,6 +1184,7 @@ public class BubbleStackView extends FrameLayout removeView(mDismissView); } mDismissView = new DismissView(getContext()); + DismissViewUtils.setup(mDismissView); int elevation = getResources().getDimensionPixelSize(R.dimen.bubble_elevation); addView(mDismissView); @@ -1494,9 +1500,6 @@ public class BubbleStackView extends FrameLayout getViewTreeObserver().removeOnPreDrawListener(mViewUpdater); getViewTreeObserver().removeOnDrawListener(mSystemGestureExcludeUpdater); getViewTreeObserver().removeOnComputeInternalInsetsListener(this); - if (mBubbleOverflow != null) { - mBubbleOverflow.cleanUpExpandedState(); - } } @Override @@ -3411,6 +3414,19 @@ public class BubbleStackView extends FrameLayout } /** + * Removes the overflow view from the stack. This allows for re-adding it later to a new stack. + */ + void resetOverflowView() { + BadgedImageView overflowIcon = mBubbleOverflow.getIconView(); + if (overflowIcon != null) { + PhysicsAnimationLayout parent = (PhysicsAnimationLayout) overflowIcon.getParent(); + if (parent != null) { + parent.removeViewNoAnimation(overflowIcon); + } + } + } + + /** * Holds some commonly queried information about the stack. */ public static class StackViewState { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java index 7a5815994dd0..da4a9898a44c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java @@ -15,6 +15,7 @@ */ package com.android.wm.shell.bubbles; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; @@ -110,6 +111,9 @@ public class BubbleTaskViewHelper { try { options.setTaskAlwaysOnTop(true); options.setLaunchedFromBubble(true); + options.setPendingIntentBackgroundActivityStartMode( + MODE_BACKGROUND_ACTIVITY_START_ALLOWED); + options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true); Intent fillInIntent = new Intent(); // Apply flags to make behaviour match documentLaunchMode=always. @@ -117,11 +121,19 @@ public class BubbleTaskViewHelper { fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); if (mBubble.isAppBubble()) { - PendingIntent pi = PendingIntent.getActivity(mContext, 0, - mBubble.getAppBubbleIntent(), - PendingIntent.FLAG_MUTABLE, - null); - mTaskView.startActivity(pi, fillInIntent, options, launchBounds); + Context context = + mContext.createContextAsUser( + mBubble.getUser(), Context.CONTEXT_RESTRICTED); + PendingIntent pi = PendingIntent.getActivity( + context, + /* requestCode= */ 0, + mBubble.getAppBubbleIntent() + .addFlags(FLAG_ACTIVITY_NEW_DOCUMENT) + .addFlags(FLAG_ACTIVITY_MULTIPLE_TASK), + PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT, + /* options= */ null); + mTaskView.startActivity(pi, /* fillInIntent= */ null, options, + launchBounds); } else if (mBubble.hasMetadataShortcutId()) { options.setApplyActivityFlagsForBubbles(true); mTaskView.startShortcutActivity(mBubble.getShortcutInfo(), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java index 8ab9841ff0c2..66e69300f45f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java @@ -104,7 +104,11 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask @Override protected BubbleViewInfo doInBackground(Void... voids) { - if (mController.get().isShowingAsBubbleBar()) { + if (!verifyState()) { + // If we're in an inconsistent state, then switched modes and should just bail now. + return null; + } + if (mLayerView.get() != null) { return BubbleViewInfo.populateForBubbleBar(mContext.get(), mController.get(), mLayerView.get(), mIconFactory, mBubble, mSkipInflation); } else { @@ -118,7 +122,11 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask if (isCancelled() || viewInfo == null) { return; } + mMainExecutor.execute(() -> { + if (!verifyState()) { + return; + } mBubble.setViewInfo(viewInfo); if (mCallback != null) { mCallback.onBubbleViewsReady(mBubble); @@ -126,6 +134,14 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask }); } + private boolean verifyState() { + if (mController.get().isShowingAsBubbleBar()) { + return mLayerView.get() != null; + } else { + return mStackView.get() != null; + } + } + /** * Info necessary to render a bubble. */ @@ -160,39 +176,14 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask LayoutInflater inflater = LayoutInflater.from(c); info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate( R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */); - info.bubbleBarExpandedView.initialize(controller); - } - - if (b.getShortcutInfo() != null) { - info.shortcutInfo = b.getShortcutInfo(); + info.bubbleBarExpandedView.initialize(controller, false /* isOverflow */); } - // App name & app icon - PackageManager pm = BubbleController.getPackageManagerForUser(c, - b.getUser().getIdentifier()); - ApplicationInfo appInfo; - Drawable badgedIcon; - Drawable appIcon; - try { - appInfo = pm.getApplicationInfo( - b.getPackageName(), - PackageManager.MATCH_UNINSTALLED_PACKAGES - | PackageManager.MATCH_DISABLED_COMPONENTS - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.MATCH_DIRECT_BOOT_AWARE); - if (appInfo != null) { - info.appName = String.valueOf(pm.getApplicationLabel(appInfo)); - } - appIcon = pm.getApplicationIcon(b.getPackageName()); - badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser()); - } catch (PackageManager.NameNotFoundException exception) { - // If we can't find package... don't think we should show the bubble. - Log.w(TAG, "Unable to find package: " + b.getPackageName()); + if (!populateCommonInfo(info, c, b, iconFactory)) { + // if we failed to update common fields return null return null; } - info.rawBadgeBitmap = iconFactory.getBadgeBitmap(badgedIcon, false).icon; - return info; } @@ -215,66 +206,11 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask info.expandedView.initialize(controller, stackView, false /* isOverflow */); } - if (b.getShortcutInfo() != null) { - info.shortcutInfo = b.getShortcutInfo(); - } - - // App name & app icon - PackageManager pm = BubbleController.getPackageManagerForUser(c, - b.getUser().getIdentifier()); - ApplicationInfo appInfo; - Drawable badgedIcon; - Drawable appIcon; - try { - appInfo = pm.getApplicationInfo( - b.getPackageName(), - PackageManager.MATCH_UNINSTALLED_PACKAGES - | PackageManager.MATCH_DISABLED_COMPONENTS - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.MATCH_DIRECT_BOOT_AWARE); - if (appInfo != null) { - info.appName = String.valueOf(pm.getApplicationLabel(appInfo)); - } - appIcon = pm.getApplicationIcon(b.getPackageName()); - badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser()); - } catch (PackageManager.NameNotFoundException exception) { - // If we can't find package... don't think we should show the bubble. - Log.w(TAG, "Unable to find package: " + b.getPackageName()); + if (!populateCommonInfo(info, c, b, iconFactory)) { + // if we failed to update common fields return null return null; } - // Badged bubble image - Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo, - b.getIcon()); - if (bubbleDrawable == null) { - // Default to app icon - bubbleDrawable = appIcon; - } - - BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon, - b.isImportantConversation()); - info.badgeBitmap = badgeBitmapInfo.icon; - // Raw badge bitmap never includes the important conversation ring - info.rawBadgeBitmap = b.isImportantConversation() - ? iconFactory.getBadgeBitmap(badgedIcon, false).icon - : badgeBitmapInfo.icon; - - float[] bubbleBitmapScale = new float[1]; - info.bubbleBitmap = iconFactory.getBubbleBitmap(bubbleDrawable, bubbleBitmapScale); - - // Dot color & placement - Path iconPath = PathParser.createPathFromPathData( - c.getResources().getString(com.android.internal.R.string.config_icon_mask)); - Matrix matrix = new Matrix(); - float scale = bubbleBitmapScale[0]; - float radius = DEFAULT_PATH_SIZE / 2f; - matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */, - radius /* pivot y */); - iconPath.transform(matrix); - info.dotPath = iconPath; - info.dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color, - Color.WHITE, WHITE_SCRIM_ALPHA); - // Flyout info.flyoutMessage = b.getFlyoutMessage(); if (info.flyoutMessage != null) { @@ -285,6 +221,75 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask } } + /** + * Modifies the given {@code info} object and populates common fields in it. + * + * <p>This method returns {@code true} if the update was successful and {@code false} otherwise. + * Callers should assume that the info object is unusable if the update was unsuccessful. + */ + private static boolean populateCommonInfo( + BubbleViewInfo info, Context c, Bubble b, BubbleIconFactory iconFactory) { + if (b.getShortcutInfo() != null) { + info.shortcutInfo = b.getShortcutInfo(); + } + + // App name & app icon + PackageManager pm = BubbleController.getPackageManagerForUser(c, + b.getUser().getIdentifier()); + ApplicationInfo appInfo; + Drawable badgedIcon; + Drawable appIcon; + try { + appInfo = pm.getApplicationInfo( + b.getPackageName(), + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_DIRECT_BOOT_AWARE); + if (appInfo != null) { + info.appName = String.valueOf(pm.getApplicationLabel(appInfo)); + } + appIcon = pm.getApplicationIcon(b.getPackageName()); + badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser()); + } catch (PackageManager.NameNotFoundException exception) { + // If we can't find package... don't think we should show the bubble. + Log.w(TAG, "Unable to find package: " + b.getPackageName()); + return false; + } + + // Badged bubble image + Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo, b.getIcon()); + if (bubbleDrawable == null) { + // Default to app icon + bubbleDrawable = appIcon; + } + + BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon, + b.isImportantConversation()); + info.badgeBitmap = badgeBitmapInfo.icon; + // Raw badge bitmap never includes the important conversation ring + info.rawBadgeBitmap = b.isImportantConversation() // is this needed for bar? + ? iconFactory.getBadgeBitmap(badgedIcon, false).icon + : badgeBitmapInfo.icon; + + float[] bubbleBitmapScale = new float[1]; + info.bubbleBitmap = iconFactory.getBubbleBitmap(bubbleDrawable, bubbleBitmapScale); + + // Dot color & placement + Path iconPath = PathParser.createPathFromPathData( + c.getResources().getString(com.android.internal.R.string.config_icon_mask)); + Matrix matrix = new Matrix(); + float scale = bubbleBitmapScale[0]; + float radius = DEFAULT_PATH_SIZE / 2f; + matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */, + radius /* pivot y */); + iconPath.transform(matrix); + info.dotPath = iconPath; + info.dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color, + Color.WHITE, WHITE_SCRIM_ALPHA); + return true; + } + @Nullable static Drawable loadSenderAvatar(@NonNull final Context context, @Nullable final Icon icon) { Objects.requireNonNull(context); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 4d329dd5d446..759246eb285d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -60,9 +60,11 @@ public interface Bubbles { DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE, DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT, DISMISS_OVERFLOW_MAX_REACHED, DISMISS_SHORTCUT_REMOVED, DISMISS_PACKAGE_REMOVED, - DISMISS_NO_BUBBLE_UP, DISMISS_RELOAD_FROM_DISK, DISMISS_USER_REMOVED}) + DISMISS_NO_BUBBLE_UP, DISMISS_RELOAD_FROM_DISK, DISMISS_USER_REMOVED, + DISMISS_SWITCH_TO_STACK}) @Target({FIELD, LOCAL_VARIABLE, PARAMETER}) - @interface DismissReason {} + @interface DismissReason { + } int DISMISS_USER_GESTURE = 1; int DISMISS_AGED = 2; @@ -80,6 +82,7 @@ public interface Bubbles { int DISMISS_NO_BUBBLE_UP = 14; int DISMISS_RELOAD_FROM_DISK = 15; int DISMISS_USER_REMOVED = 16; + int DISMISS_SWITCH_TO_STACK = 17; /** Returns a binder that can be passed to an external process to manipulate Bubbles. */ default IBubbles createExternalInterface() { @@ -120,8 +123,8 @@ public interface Bubbles { /** * This method has different behavior depending on: - * - if an app bubble exists - * - if an app bubble is expanded + * - if an app bubble exists + * - if an app bubble is expanded * * If no app bubble exists, this will add and expand a bubble with the provided intent. The * intent must be explicit (i.e. include a package name or fully qualified component class name) @@ -135,13 +138,13 @@ public interface Bubbles { * the bubble or bubble stack. * * Some notes: - * - Only one app bubble is supported at a time, regardless of users. Multi-users support is - * tracked in b/273533235. - * - Calling this method with a different intent than the existing app bubble will do nothing + * - Only one app bubble is supported at a time, regardless of users. Multi-users support is + * tracked in b/273533235. + * - Calling this method with a different intent than the existing app bubble will do nothing * * @param intent the intent to display in the bubble expanded view. - * @param user the {@link UserHandle} of the user to start this activity for. - * @param icon the {@link Icon} to use for the bubble view. + * @param user the {@link UserHandle} of the user to start this activity for. + * @param icon the {@link Icon} to use for the bubble view. */ void showOrHideAppBubble(Intent intent, UserHandle user, @Nullable Icon icon); @@ -172,13 +175,12 @@ public interface Bubbles { * {@link Bubble#setSuppressNotification}. For the case of suppressed summaries, we also add * {@link BubbleData#addSummaryToSuppress}. * - * @param entry the notification of the BubbleEntry should be removed. - * @param children the list of child notification of the BubbleEntry from 1st param entry, - * this will be null if entry does have no children. + * @param entry the notification of the BubbleEntry should be removed. + * @param children the list of child notification of the BubbleEntry from 1st param entry, + * this will be null if entry does have no children. * @param removeCallback the remove callback for SystemUI side to remove notification, the int * number should be list position of children list and use -1 for * removing the parent notification. - * * @return true if we want to intercept the dismissal of the entry, else false. */ boolean handleDismissalInterception(BubbleEntry entry, @Nullable List<BubbleEntry> children, @@ -200,9 +202,9 @@ public interface Bubbles { /** * Called when new notification entry updated. * - * @param entry the {@link BubbleEntry} by the notification. + * @param entry the {@link BubbleEntry} by the notification. * @param shouldBubbleUp {@code true} if this notification should bubble up. - * @param fromSystem {@code true} if this update is from NotificationManagerService. + * @param fromSystem {@code true} if this update is from NotificationManagerService. */ void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem); @@ -218,7 +220,7 @@ public interface Bubbles { * filtering and sorting. This is used to dismiss or create bubbles based on changes in * permissions on the notification channel or the global setting. * - * @param rankingMap the updated ranking map from NotificationListenerService + * @param rankingMap the updated ranking map from NotificationListenerService * @param entryDataByKey a map of ranking key to bubble entry and whether the entry should * bubble up */ @@ -230,9 +232,9 @@ public interface Bubbles { * Called when a notification channel is modified, in response to * {@link NotificationListenerService#onNotificationChannelModified}. * - * @param pkg the package the notification channel belongs to. - * @param user the user the notification channel belongs to. - * @param channel the channel being modified. + * @param pkg the package the notification channel belongs to. + * @param user the user the notification channel belongs to. + * @param channel the channel being modified. * @param modificationType the type of modification that occurred to the channel. */ void onNotificationChannelModified( @@ -300,7 +302,7 @@ public interface Bubbles { * Called when the expansion state of the bubble stack changes. * * @param isExpanding whether it's expanding or collapsing - * @param key the notification key associated with bubble being expanded + * @param key the notification key associated with bubble being expanded */ void onBubbleExpandChanged(boolean isExpanding, String key); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt new file mode 100644 index 000000000000..ed3624035757 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt @@ -0,0 +1,33 @@ +/* + * 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. + */ +@file:JvmName("DismissViewUtils") + +package com.android.wm.shell.bubbles + +import com.android.wm.shell.R +import com.android.wm.shell.common.bubbles.DismissView + +fun DismissView.setup() { + setup(DismissView.Config( + targetSizeResId = R.dimen.dismiss_circle_size, + iconSizeResId = R.dimen.dismiss_target_x_size, + bottomMarginResId = R.dimen.floating_dismiss_bottom_margin, + floatingGradientHeightResId = R.dimen.floating_dismiss_gradient_height, + floatingGradientColorResId = android.R.color.system_neutral1_900, + backgroundResId = R.drawable.dismiss_circle_background, + iconResId = R.drawable.pip_ic_close_white + )) +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl index 862e818a998b..20ae8469f431 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl @@ -35,6 +35,4 @@ interface IBubbles { oneway void collapseBubbles() = 5; - oneway void onTaskbarStateChanged(in int newState) = 6; - }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java index beb1c5fa6c10..f3cc514d2972 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java @@ -327,6 +327,12 @@ public class PhysicsAnimationLayout extends FrameLayout { addViewInternal(child, index, params, false /* isReorder */); } + /** Removes the child view immediately. */ + public void removeViewNoAnimation(View view) { + super.removeView(view); + view.setTag(R.id.physics_animator_tag, null); + } + @Override public void removeView(View view) { if (mController != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java index 23f65f943aa4..f729d029a818 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java @@ -27,6 +27,7 @@ import android.widget.FrameLayout; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.animation.PhysicsAnimator; +import com.android.wm.shell.bubbles.BubbleOverflow; import com.android.wm.shell.bubbles.BubblePositioner; import com.android.wm.shell.bubbles.BubbleViewProvider; import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix; @@ -215,9 +216,10 @@ public class BubbleBarAnimationHelper { } BubbleBarExpandedView bbev = mExpandedBubble.getBubbleBarExpandedView(); + boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY); final int padding = mPositioner.getBubbleBarExpandedViewPadding(); - final int width = mPositioner.getExpandedViewWidthForBubbleBar(); - final int height = mPositioner.getExpandedViewHeightForBubbleBar(); + final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded); + final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded); FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) bbev.getLayoutParams(); lp.width = width; lp.height = height; @@ -227,7 +229,8 @@ public class BubbleBarAnimationHelper { } else { bbev.setX(mPositioner.getAvailableRect().width() - width - padding); } - bbev.setY(mPositioner.getInsets().top + padding); + bbev.setY(mPositioner.getExpandedViewBottomForBubbleBar() - height); bbev.updateLocation(); + bbev.maybeShowOverflow(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java index b8f049becb6f..396aa0e56cf6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java @@ -16,12 +16,16 @@ package com.android.wm.shell.bubbles.bar; +import android.annotation.ColorInt; +import android.annotation.Nullable; import android.app.ActivityManager; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Outline; +import android.graphics.Rect; import android.util.AttributeSet; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewOutlineProvider; import android.widget.FrameLayout; @@ -30,9 +34,14 @@ import com.android.internal.policy.ScreenDecorationsUtils; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.Bubble; import com.android.wm.shell.bubbles.BubbleController; +import com.android.wm.shell.bubbles.BubbleOverflowContainerView; import com.android.wm.shell.bubbles.BubbleTaskViewHelper; +import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.taskview.TaskView; +import java.util.function.Consumer; +import java.util.function.Supplier; + /** * Expanded view of a bubble when it's part of the bubble bar. * @@ -44,12 +53,17 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView private static final int INVALID_TASK_ID = -1; private BubbleController mController; + private boolean mIsOverflow; private BubbleTaskViewHelper mBubbleTaskViewHelper; + private BubbleBarMenuViewController mMenuViewController; + private @Nullable Supplier<Rect> mLayerBoundsSupplier; + private @Nullable Consumer<String> mUnBubbleConversationCallback; - private HandleView mMenuView; - private TaskView mTaskView; + private BubbleBarHandleView mHandleView = new BubbleBarHandleView(getContext()); + private @Nullable TaskView mTaskView; + private @Nullable BubbleOverflowContainerView mOverflowView; - private int mMenuHeight; + private int mHandleHeight; private int mBackgroundColor; private float mCornerRadius = 0f; @@ -83,11 +97,9 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView super.onFinishInflate(); Context context = getContext(); setElevation(getResources().getDimensionPixelSize(R.dimen.bubble_elevation)); - mMenuHeight = context.getResources().getDimensionPixelSize( - R.dimen.bubblebar_expanded_view_menu_size); - mMenuView = new HandleView(context); - addView(mMenuView); - + mHandleHeight = context.getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_handle_size); + addView(mHandleView); applyThemeAttrs(); setClipToOutline(true); setOutlineProvider(new ViewOutlineProvider() { @@ -98,23 +110,71 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView }); } + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + // Hide manage menu when view disappears + mMenuViewController.hideMenu(false /* animated */); + } + /** Set the BubbleController on the view, must be called before doing anything else. */ - public void initialize(BubbleController controller) { + public void initialize(BubbleController controller, boolean isOverflow) { mController = controller; - mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, mController, - /* listener= */ this, - /* viewParent= */ this); - mTaskView = mBubbleTaskViewHelper.getTaskView(); - addView(mTaskView); - mTaskView.setEnableSurfaceClipping(true); - mTaskView.setCornerRadius(mCornerRadius); + mIsOverflow = isOverflow; + + if (mIsOverflow) { + mOverflowView = (BubbleOverflowContainerView) LayoutInflater.from(getContext()).inflate( + R.layout.bubble_overflow_container, null /* root */); + mOverflowView.setBubbleController(mController); + addView(mOverflowView); + } else { + + mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, mController, + /* listener= */ this, + /* viewParent= */ this); + mTaskView = mBubbleTaskViewHelper.getTaskView(); + addView(mTaskView); + mTaskView.setEnableSurfaceClipping(true); + mTaskView.setCornerRadius(mCornerRadius); + } + mMenuViewController = new BubbleBarMenuViewController(mContext, this); + mMenuViewController.setListener(new BubbleBarMenuViewController.Listener() { + @Override + public void onMenuVisibilityChanged(boolean visible) { + if (mTaskView == null || mLayerBoundsSupplier == null) return; + // Updates the obscured touchable region for the task surface. + mTaskView.setObscuredTouchRect(visible ? mLayerBoundsSupplier.get() : null); + } + + @Override + public void onUnBubbleConversation(Bubble bubble) { + if (mUnBubbleConversationCallback != null) { + mUnBubbleConversationCallback.accept(bubble.getKey()); + } + } + + @Override + public void onOpenAppSettings(Bubble bubble) { + mController.collapseStack(); + mContext.startActivityAsUser(bubble.getSettingsIntent(mContext), bubble.getUser()); + } + + @Override + public void onDismissBubble(Bubble bubble) { + mController.dismissBubble(bubble, Bubbles.DISMISS_USER_REMOVED); + } + }); + mHandleView.setOnClickListener(view -> { + mMenuViewController.showMenu(true /* animated */); + }); } // TODO (b/275087636): call this when theme/config changes - void applyThemeAttrs() { + /** Updates the view based on the current theme. */ + public void applyThemeAttrs() { boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows( mContext.getResources()); - final TypedArray ta = mContext.obtainStyledAttributes(new int[] { + final TypedArray ta = mContext.obtainStyledAttributes(new int[]{ android.R.attr.dialogCornerRadius, android.R.attr.colorBackgroundFloating}); mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0; @@ -123,14 +183,12 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView ta.recycle(); - mMenuView.setCornerRadius(mCornerRadius); - mMenuHeight = getResources().getDimensionPixelSize( - R.dimen.bubblebar_expanded_view_menu_size); + mHandleHeight = getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_handle_size); if (mTaskView != null) { mTaskView.setCornerRadius(mCornerRadius); - mTaskView.setElevation(150); - updateMenuColor(); + updateHandleAndBackgroundColor(true /* animated */); } } @@ -138,10 +196,8 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); - - // Add corner radius here so that the menu extends behind the rounded corners of TaskView. - int menuViewHeight = Math.min((int) (mMenuHeight + mCornerRadius), height); - measureChild(mMenuView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight, + int menuViewHeight = Math.min(mHandleHeight, height); + measureChild(mHandleView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight, MeasureSpec.getMode(heightMeasureSpec))); if (mTaskView != null) { @@ -153,12 +209,12 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); // Drag handle above - final int dragHandleBottom = t + mMenuView.getMeasuredHeight(); - mMenuView.layout(l, t, r, dragHandleBottom); + final int dragHandleBottom = t + mHandleView.getMeasuredHeight(); + mHandleView.layout(l, t, r, dragHandleBottom); if (mTaskView != null) { - // Subtract radius so that the menu extends behind the rounded corners of TaskView. - mTaskView.layout(l, (int) (dragHandleBottom - mCornerRadius), r, + mTaskView.layout(l, dragHandleBottom, r, dragHandleBottom + mTaskView.getMeasuredHeight()); } } @@ -166,7 +222,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView @Override public void onTaskCreated() { setContentVisibility(true); - updateMenuColor(); + updateHandleAndBackgroundColor(false /* animated */); } @Override @@ -187,11 +243,13 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView } mBubbleTaskViewHelper.cleanUpTaskView(); } + mMenuViewController.hideMenu(false /* animated */); } - /** Updates the bubble shown in this task view. */ + /** Updates the bubble shown in the expanded view. */ public void update(Bubble bubble) { mBubbleTaskViewHelper.update(bubble); + mMenuViewController.updateMenu(bubble); } /** The task id of the activity shown in the task view, if it exists. */ @@ -199,12 +257,33 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView return mBubbleTaskViewHelper != null ? mBubbleTaskViewHelper.getTaskId() : INVALID_TASK_ID; } + /** Sets layer bounds supplier used for obscured touchable region of task view */ + void setLayerBoundsSupplier(@Nullable Supplier<Rect> supplier) { + mLayerBoundsSupplier = supplier; + } + + /** Sets the function to call to un-bubble the given conversation. */ + public void setUnBubbleConversationCallback( + @Nullable Consumer<String> unBubbleConversationCallback) { + mUnBubbleConversationCallback = unBubbleConversationCallback; + } + /** * Call when the location or size of the view has changed to update TaskView. */ public void updateLocation() { - if (mTaskView == null) return; - mTaskView.onLocationChanged(); + if (mTaskView != null) { + mTaskView.onLocationChanged(); + } + } + + /** Shows the expanded view for the overflow if it exists. */ + void maybeShowOverflow() { + if (mOverflowView != null) { + // post this to the looper so that the view has a chance to be laid out before it can + // calculate row and column sizes correctly. + post(() -> mOverflowView.show()); + } } /** Sets the alpha of the task view. */ @@ -218,16 +297,33 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView } } - /** Updates the menu bar to be the status bar color specified by the app. */ - private void updateMenuColor() { + /** + * Updates the background color to match with task view status/bg color, and sets handle color + * to contrast with the background + */ + private void updateHandleAndBackgroundColor(boolean animated) { if (mTaskView == null) return; - ActivityManager.RunningTaskInfo info = mTaskView.getTaskInfo(); - final int taskBgColor = info.taskDescription.getStatusBarColor(); - final int color = Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb(); - if (color != -1) { - mMenuView.setBackgroundColor(color); + final int color = getTaskViewColor(); + final boolean isRegionDark = Color.luminance(color) <= 0.5; + mHandleView.updateHandleColor(isRegionDark, animated); + setBackgroundColor(color); + } + + /** + * Retrieves task view status/nav bar color or background if available + * + * TODO (b/283075226): Update with color sampling when + * RegionSamplingHelper or alternative is available + */ + private @ColorInt int getTaskViewColor() { + if (mTaskView == null || mTaskView.getTaskInfo() == null) return mBackgroundColor; + ActivityManager.TaskDescription taskDescription = mTaskView.getTaskInfo().taskDescription; + if (taskDescription.getStatusBarColor() != Color.TRANSPARENT) { + return taskDescription.getStatusBarColor(); + } else if (taskDescription.getBackgroundColor() != Color.TRANSPARENT) { + return taskDescription.getBackgroundColor(); } else { - mMenuView.setBackgroundColor(mBackgroundColor); + return mBackgroundColor; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java new file mode 100644 index 000000000000..ce26bc0322b3 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java @@ -0,0 +1,114 @@ +/* + * 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 com.android.wm.shell.bubbles.bar; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.Outline; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewOutlineProvider; + +import androidx.annotation.ColorInt; +import androidx.core.content.ContextCompat; + +import com.android.wm.shell.R; + +/** + * Handle view to show at the top of a bubble bar expanded view. + */ +public class BubbleBarHandleView extends View { + private static final long COLOR_CHANGE_DURATION = 120; + + private int mHandleWidth; + private int mHandleHeight; + private @ColorInt int mHandleLightColor; + private @ColorInt int mHandleDarkColor; + private @Nullable ObjectAnimator mColorChangeAnim; + + public BubbleBarHandleView(Context context) { + this(context, null /* attrs */); + } + + public BubbleBarHandleView(Context context, AttributeSet attrs) { + this(context, attrs, 0 /* defStyleAttr */); + } + + public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0 /* defStyleRes */); + } + + public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + mHandleWidth = getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_handle_width); + mHandleHeight = getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_handle_height); + mHandleLightColor = ContextCompat.getColor(getContext(), + R.color.bubble_bar_expanded_view_handle_light); + mHandleDarkColor = ContextCompat.getColor(getContext(), + R.color.bubble_bar_expanded_view_handle_dark); + + setClipToOutline(true); + setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + final int handleCenterX = view.getWidth() / 2; + final int handleCenterY = view.getHeight() / 2; + final float handleRadius = mHandleHeight / 2f; + Rect handleBounds = new Rect( + handleCenterX - mHandleWidth / 2, + handleCenterY - mHandleHeight / 2, + handleCenterX + mHandleWidth / 2, + handleCenterY + mHandleHeight / 2); + outline.setRoundRect(handleBounds, handleRadius); + } + }); + } + + /** + * Updates the handle color. + * + * @param isRegionDark Whether the background behind the handle is dark, and thus the handle + * should be light (and vice versa). + * @param animated Whether to animate the change, or apply it immediately. + */ + public void updateHandleColor(boolean isRegionDark, boolean animated) { + int newColor = isRegionDark ? mHandleLightColor : mHandleDarkColor; + if (mColorChangeAnim != null) { + mColorChangeAnim.cancel(); + } + if (animated) { + mColorChangeAnim = ObjectAnimator.ofArgb(this, "backgroundColor", newColor); + mColorChangeAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mColorChangeAnim = null; + } + }); + mColorChangeAnim.setDuration(COLOR_CHANGE_DURATION); + mColorChangeAnim.start(); + } else { + setBackgroundColor(newColor); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index b1a725b6e5c4..d20b33edf2c9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -29,9 +29,12 @@ import android.view.ViewTreeObserver; import android.widget.FrameLayout; import com.android.wm.shell.bubbles.BubbleController; +import com.android.wm.shell.bubbles.BubbleOverflow; import com.android.wm.shell.bubbles.BubblePositioner; import com.android.wm.shell.bubbles.BubbleViewProvider; +import java.util.function.Consumer; + /** * Similar to {@link com.android.wm.shell.bubbles.BubbleStackView}, this view is added to window * manager to display bubbles. However, it is only used when bubbles are being displayed in @@ -53,6 +56,7 @@ public class BubbleBarLayerView extends FrameLayout @Nullable private BubbleViewProvider mExpandedBubble; private BubbleBarExpandedView mExpandedView; + private @Nullable Consumer<String> mUnBubbleConversationCallback; // TODO(b/273310265) - currently the view is always on the right, need to update for RTL. /** Whether the expanded view is displaying on the left of the screen or not. */ @@ -143,9 +147,18 @@ public class BubbleBarLayerView extends FrameLayout if (mExpandedView == null) { mExpandedBubble = b; mExpandedView = expandedView; - final int width = mPositioner.getExpandedViewWidthForBubbleBar(); - final int height = mPositioner.getExpandedViewHeightForBubbleBar(); + boolean isOverflowExpanded = b.getKey().equals(BubbleOverflow.KEY); + final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded); + final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded); mExpandedView.setVisibility(GONE); + mExpandedView.setUnBubbleConversationCallback(mUnBubbleConversationCallback); + mExpandedView.setLayerBoundsSupplier(() -> new Rect(0, 0, getWidth(), getHeight())); + mExpandedView.setUnBubbleConversationCallback(bubbleKey -> { + if (mUnBubbleConversationCallback != null) { + mUnBubbleConversationCallback.accept(bubbleKey); + } + }); + mExpandedView.setY(mPositioner.getExpandedViewBottomForBubbleBar() - height); addView(mExpandedView, new FrameLayout.LayoutParams(width, height)); } @@ -165,12 +178,19 @@ public class BubbleBarLayerView extends FrameLayout showScrim(false); } + /** Sets the function to call to un-bubble the given conversation. */ + public void setUnBubbleConversationCallback( + @Nullable Consumer<String> unBubbleConversationCallback) { + mUnBubbleConversationCallback = unBubbleConversationCallback; + } + /** Updates the expanded view size and position. */ private void updateExpandedView() { if (mExpandedView == null) return; + boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY); final int padding = mPositioner.getBubbleBarExpandedViewPadding(); - final int width = mPositioner.getExpandedViewWidthForBubbleBar(); - final int height = mPositioner.getExpandedViewHeightForBubbleBar(); + final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded); + final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded); FrameLayout.LayoutParams lp = (LayoutParams) mExpandedView.getLayoutParams(); lp.width = width; lp.height = height; @@ -180,7 +200,7 @@ public class BubbleBarLayerView extends FrameLayout } else { mExpandedView.setX(mPositioner.getAvailableRect().width() - width - padding); } - mExpandedView.setY(mPositioner.getInsets().top + padding); + mExpandedView.setY(mPositioner.getExpandedViewBottomForBubbleBar() - height); mExpandedView.updateLocation(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java new file mode 100644 index 000000000000..00b977721bea --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java @@ -0,0 +1,77 @@ +/* + * 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 com.android.wm.shell.bubbles.bar; + +import android.annotation.ColorInt; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.drawable.Icon; +import android.util.AttributeSet; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.wm.shell.R; + +/** + * Bubble bar expanded view menu item view to display menu action details + */ +public class BubbleBarMenuItemView extends LinearLayout { + private ImageView mImageView; + private TextView mTextView; + + public BubbleBarMenuItemView(Context context) { + this(context, null /* attrs */); + } + + public BubbleBarMenuItemView(Context context, AttributeSet attrs) { + this(context, attrs, 0 /* defStyleAttr */); + } + + public BubbleBarMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0 /* defStyleRes */); + } + + public BubbleBarMenuItemView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mImageView = findViewById(R.id.bubble_bar_menu_item_icon); + mTextView = findViewById(R.id.bubble_bar_menu_item_title); + } + + /** + * Update menu item with the details and tint color + */ + void update(Icon icon, String title, @ColorInt int tint) { + if (tint == Color.TRANSPARENT) { + final TypedArray typedArray = getContext().obtainStyledAttributes( + new int[]{android.R.attr.textColorPrimary}); + mTextView.setTextColor(typedArray.getColor(0, Color.BLACK)); + } else { + icon.setTint(tint); + mTextView.setTextColor(tint); + } + + mImageView.setImageIcon(icon); + mTextView.setText(title); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java new file mode 100644 index 000000000000..211fe0d48e43 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java @@ -0,0 +1,145 @@ +/* + * 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 com.android.wm.shell.bubbles.bar; + +import android.annotation.ColorInt; +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.Icon; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.wm.shell.R; +import com.android.wm.shell.bubbles.Bubble; + +import java.util.ArrayList; + +/** + * Bubble bar expanded view menu + */ +public class BubbleBarMenuView extends LinearLayout { + private ViewGroup mBubbleSectionView; + private ViewGroup mActionsSectionView; + private ImageView mBubbleIconView; + private TextView mBubbleTitleView; + + public BubbleBarMenuView(Context context) { + this(context, null /* attrs */); + } + + public BubbleBarMenuView(Context context, AttributeSet attrs) { + this(context, attrs, 0 /* defStyleAttr */); + } + + public BubbleBarMenuView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0 /* defStyleRes */); + } + + public BubbleBarMenuView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mBubbleSectionView = findViewById(R.id.bubble_bar_manage_menu_bubble_section); + mActionsSectionView = findViewById(R.id.bubble_bar_manage_menu_actions_section); + mBubbleIconView = findViewById(R.id.bubble_bar_manage_menu_bubble_icon); + mBubbleTitleView = findViewById(R.id.bubble_bar_manage_menu_bubble_title); + } + + /** Update menu details with bubble info */ + void updateInfo(Bubble bubble) { + if (bubble.getIcon() != null) { + mBubbleIconView.setImageIcon(bubble.getIcon()); + } else { + mBubbleIconView.setImageBitmap(bubble.getBubbleIcon()); + } + mBubbleTitleView.setText(bubble.getTitle()); + } + + /** + * Update menu action items views + * @param actions used to populate menu item views + */ + void updateActions(ArrayList<MenuAction> actions) { + mActionsSectionView.removeAllViews(); + LayoutInflater inflater = LayoutInflater.from(mContext); + + for (MenuAction action : actions) { + BubbleBarMenuItemView itemView = (BubbleBarMenuItemView) inflater.inflate( + R.layout.bubble_bar_menu_item, mActionsSectionView, false); + itemView.update(action.mIcon, action.mTitle, action.mTint); + itemView.setOnClickListener(action.mOnClick); + mActionsSectionView.addView(itemView); + } + } + + /** Sets on close menu listener */ + void setOnCloseListener(Runnable onClose) { + mBubbleSectionView.setOnClickListener(view -> { + onClose.run(); + }); + } + + /** + * Overridden to proxy to section views alpha. + * @implNote + * If animate alpha on the parent (menu container) view, section view shadows get distorted. + * To prevent distortion and artifacts alpha changes applied directly on the section views. + */ + @Override + public void setAlpha(float alpha) { + mBubbleSectionView.setAlpha(alpha); + mActionsSectionView.setAlpha(alpha); + } + + /** + * Overridden to proxy section view alpha value. + * @implNote + * The assumption is that both section views have the same alpha value + */ + @Override + public float getAlpha() { + return mBubbleSectionView.getAlpha(); + } + + /** + * Menu action details used to create menu items + */ + static class MenuAction { + private Icon mIcon; + private @ColorInt int mTint; + private String mTitle; + private OnClickListener mOnClick; + + MenuAction(Icon icon, String title, OnClickListener onClick) { + this(icon, title, Color.TRANSPARENT, onClick); + } + + MenuAction(Icon icon, String title, @ColorInt int tint, OnClickListener onClick) { + this.mIcon = icon; + this.mTitle = title; + this.mTint = tint; + this.mOnClick = onClick; + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java new file mode 100644 index 000000000000..8be140c16435 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java @@ -0,0 +1,242 @@ +/* + * 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 com.android.wm.shell.bubbles.bar; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Icon; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.core.content.ContextCompat; +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.wm.shell.R; +import com.android.wm.shell.animation.PhysicsAnimator; +import com.android.wm.shell.bubbles.Bubble; + +import java.util.ArrayList; + +/** + * Manages bubble bar expanded view menu presentation and animations + */ +class BubbleBarMenuViewController { + private static final float MENU_INITIAL_SCALE = 0.5f; + private final Context mContext; + private final ViewGroup mRootView; + private @Nullable Listener mListener; + private @Nullable Bubble mBubble; + private @Nullable BubbleBarMenuView mMenuView; + /** A transparent view used to intercept touches to collapse menu when presented */ + private @Nullable View mScrimView; + private @Nullable PhysicsAnimator<BubbleBarMenuView> mMenuAnimator; + private PhysicsAnimator.SpringConfig mMenuSpringConfig; + + BubbleBarMenuViewController(Context context, ViewGroup rootView) { + mContext = context; + mRootView = rootView; + mMenuSpringConfig = new PhysicsAnimator.SpringConfig( + SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY); + } + + /** Sets menu actions listener */ + void setListener(@Nullable Listener listener) { + mListener = listener; + } + + /** Update menu with bubble */ + void updateMenu(@NonNull Bubble bubble) { + mBubble = bubble; + } + + /** + * Show bubble bar expanded view menu + * @param animated if should animate transition + */ + void showMenu(boolean animated) { + if (mMenuView == null || mScrimView == null) { + setupMenu(); + } + cancelAnimations(); + mMenuView.setVisibility(View.VISIBLE); + mScrimView.setVisibility(View.VISIBLE); + Runnable endActions = () -> { + mMenuView.getChildAt(0).requestAccessibilityFocus(); + if (mListener != null) { + mListener.onMenuVisibilityChanged(true /* isShown */); + } + }; + if (animated) { + animateTransition(true /* show */, endActions); + } else { + endActions.run(); + } + } + + /** + * Hide bubble bar expanded view menu + * @param animated if should animate transition + */ + void hideMenu(boolean animated) { + if (mMenuView == null || mScrimView == null) return; + cancelAnimations(); + Runnable endActions = () -> { + mMenuView.setVisibility(View.GONE); + mScrimView.setVisibility(View.GONE); + if (mListener != null) { + mListener.onMenuVisibilityChanged(false /* isShown */); + } + }; + if (animated) { + animateTransition(false /* show */, endActions); + } else { + endActions.run(); + } + } + + /** + * Animate show/hide menu transition + * @param show if should show or hide the menu + * @param endActions will be called when animation ends + */ + private void animateTransition(boolean show, Runnable endActions) { + if (mMenuView == null) return; + mMenuAnimator = PhysicsAnimator.getInstance(mMenuView); + mMenuAnimator.setDefaultSpringConfig(mMenuSpringConfig); + mMenuAnimator + .spring(DynamicAnimation.ALPHA, show ? 1f : 0f) + .spring(DynamicAnimation.SCALE_Y, show ? 1f : MENU_INITIAL_SCALE) + .withEndActions(() -> { + mMenuAnimator = null; + endActions.run(); + }) + .start(); + } + + /** Cancel running animations */ + private void cancelAnimations() { + if (mMenuAnimator != null) { + mMenuAnimator.cancel(); + mMenuAnimator = null; + } + } + + /** Sets up and inflate menu views */ + private void setupMenu() { + // Menu view setup + mMenuView = (BubbleBarMenuView) LayoutInflater.from(mContext).inflate( + R.layout.bubble_bar_menu_view, mRootView, false); + mMenuView.setAlpha(0f); + mMenuView.setPivotY(0f); + mMenuView.setScaleY(MENU_INITIAL_SCALE); + mMenuView.setOnCloseListener(() -> hideMenu(true /* animated */)); + if (mBubble != null) { + mMenuView.updateInfo(mBubble); + mMenuView.updateActions(createMenuActions(mBubble)); + } + // Scrim view setup + mScrimView = new View(mContext); + mScrimView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + mScrimView.setOnClickListener(view -> hideMenu(true /* animated */)); + // Attach to root view + mRootView.addView(mScrimView); + mRootView.addView(mMenuView); + } + + /** + * Creates menu actions to populate menu view + * @param bubble used to create actions depending on bubble type + */ + private ArrayList<BubbleBarMenuView.MenuAction> createMenuActions(Bubble bubble) { + ArrayList<BubbleBarMenuView.MenuAction> menuActions = new ArrayList<>(); + Resources resources = mContext.getResources(); + + if (bubble.isConversation()) { + // Don't bubble conversation action + menuActions.add(new BubbleBarMenuView.MenuAction( + Icon.createWithResource(mContext, R.drawable.bubble_ic_stop_bubble), + resources.getString(R.string.bubbles_dont_bubble_conversation), + view -> { + hideMenu(true /* animated */); + if (mListener != null) { + mListener.onUnBubbleConversation(bubble); + } + } + )); + // Open settings action + Icon appIcon = bubble.getRawAppBadge() != null ? Icon.createWithBitmap( + bubble.getRawAppBadge()) : null; + menuActions.add(new BubbleBarMenuView.MenuAction( + appIcon, + resources.getString(R.string.bubbles_app_settings, bubble.getAppName()), + view -> { + hideMenu(true /* animated */); + if (mListener != null) { + mListener.onOpenAppSettings(bubble); + } + } + )); + } + + // Dismiss bubble action + menuActions.add(new BubbleBarMenuView.MenuAction( + Icon.createWithResource(resources, R.drawable.ic_remove_no_shadow), + resources.getString(R.string.bubble_dismiss_text), + ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_menu_close), + view -> { + hideMenu(true /* animated */); + if (mListener != null) { + mListener.onDismissBubble(bubble); + } + } + )); + + return menuActions; + } + + /** + * Bubble bar expanded view menu actions listener + */ + interface Listener { + /** + * Called when manage menu is shown/hidden + * If animated will be called when animation ends + */ + void onMenuVisibilityChanged(boolean visible); + + /** + * Un-bubbles conversation and removes the bubble from the stack + * This conversation will not be bubbled with new messages + * @see com.android.wm.shell.bubbles.BubbleController + */ + void onUnBubbleConversation(Bubble bubble); + + /** + * Launches app notification bubble settings for the bubble with intent created in: + * {@code Bubble.getSettingsIntent} + */ + void onOpenAppSettings(Bubble bubble); + + /** + * Dismiss bubble and remove it from the bubble stack + */ + void onDismissBubble(Bubble bubble); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java deleted file mode 100644 index 9ee8a9d98aa1..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 com.android.wm.shell.bubbles.bar; - -import android.content.Context; -import android.view.Gravity; -import android.widget.LinearLayout; - -/** - * Handle / menu view to show at the top of a bubble bar expanded view. - */ -public class HandleView extends LinearLayout { - - // TODO(b/273307221): implement the manage menu in this view. - public HandleView(Context context) { - super(context); - setOrientation(LinearLayout.HORIZONTAL); - setGravity(Gravity.CENTER); - } - - /** - * The menu extends past the top of the TaskView because of the rounded corners. This means - * to center content in the menu we must subtract the radius (i.e. the amount of space covered - * by TaskView). - */ - public void setCornerRadius(float radius) { - setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), (int) radius); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt new file mode 100644 index 000000000000..85aaa8ef585c --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt @@ -0,0 +1,32 @@ +/* + * 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 com.android.wm.shell.bubbles.properties + +/** + * An interface for exposing bubble properties via flags which can be controlled easily in tests. + */ +interface BubbleProperties { + /** + * Whether bubble bar is enabled. + * + * When this is `true`, depending on additional factors, such as screen size and taskbar state, + * bubbles will be displayed in the bubble bar instead of floating. + * + * When this is `false`, bubbles will be floating. + */ + val isBubbleBarEnabled: Boolean +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt new file mode 100644 index 000000000000..9d8b9a6f3260 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt @@ -0,0 +1,27 @@ +/* + * 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 com.android.wm.shell.bubbles.properties + +import android.os.SystemProperties + +/** Provides bubble properties in production. */ +object ProdBubbleProperties : BubbleProperties { + + // TODO(b/256873975) Should use proper flag when available to shell/launcher + override val isBubbleBarEnabled = + SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false) +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt new file mode 100644 index 000000000000..81592c35e4ac --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt @@ -0,0 +1,90 @@ +/* + * 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 com.android.wm.shell.common + +import android.window.WindowContainerToken +import android.window.WindowContainerTransaction +import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG +import com.android.wm.shell.util.KtProtoLog + +/** + * Controller to manage behavior of activities launched with + * [android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT]. + */ +class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) { + + /** Allows to temporarily disable launch adjacent handling */ + var launchAdjacentEnabled: Boolean = true + set(value) { + if (field != value) { + KtProtoLog.d(WM_SHELL_TASK_ORG, "set launch adjacent flag root enabled=%b", value) + field = value + container?.let { c -> + if (value) { + enableContainer(c) + } else { + disableContainer((c)) + } + } + } + } + private var container: WindowContainerToken? = null + + /** + * Set [container] as the new launch adjacent flag root container. + * + * If launch adjacent handling is disabled through [setLaunchAdjacentEnabled], won't set the + * container until after it is enabled again. + * + * @see WindowContainerTransaction.setLaunchAdjacentFlagRoot + */ + fun setLaunchAdjacentRoot(container: WindowContainerToken) { + KtProtoLog.d(WM_SHELL_TASK_ORG, "set new launch adjacent flag root container") + this.container = container + if (launchAdjacentEnabled) { + enableContainer(container) + } + } + + /** + * Clear a container previously set through [setLaunchAdjacentRoot]. + * + * Always clears the container, regardless of [launchAdjacentEnabled] value. + * + * @see WindowContainerTransaction.clearLaunchAdjacentFlagRoot + */ + fun clearLaunchAdjacentRoot() { + KtProtoLog.d(WM_SHELL_TASK_ORG, "clear launch adjacent flag root container") + container?.let { + disableContainer(it) + container = null + } + } + + private fun enableContainer(container: WindowContainerToken) { + KtProtoLog.v(WM_SHELL_TASK_ORG, "enable launch adjacent flag root container") + val wct = WindowContainerTransaction() + wct.setLaunchAdjacentFlagRoot(container) + syncQueue.queue(wct) + } + + private fun disableContainer(container: WindowContainerToken) { + KtProtoLog.v(WM_SHELL_TASK_ORG, "disable launch adjacent flag root container") + val wct = WindowContainerTransaction() + wct.clearLaunchAdjacentFlagRoot(container) + syncQueue.queue(wct) + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS new file mode 100644 index 000000000000..7af038999797 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS @@ -0,0 +1 @@ +madym@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissCircleView.java index e0c782d1675b..7c5bb211a4cc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissCircleView.java @@ -14,16 +14,17 @@ * limitations under the License. */ -package com.android.wm.shell.common; +package com.android.wm.shell.common.bubbles; import android.content.Context; import android.content.res.Configuration; -import android.content.res.Resources; import android.view.Gravity; import android.widget.FrameLayout; import android.widget.ImageView; -import com.android.wm.shell.R; +import androidx.annotation.DimenRes; +import androidx.annotation.DrawableRes; +import androidx.core.content.ContextCompat; /** * Circular view with a semitransparent, circular background with an 'X' inside it. @@ -31,33 +32,44 @@ import com.android.wm.shell.R; * This is used by both Bubbles and PIP as the dismiss target. */ public class DismissCircleView extends FrameLayout { + @DrawableRes int mBackgroundResId; + @DimenRes int mIconSizeResId; private final ImageView mIconView = new ImageView(getContext()); public DismissCircleView(Context context) { super(context); - final Resources res = getResources(); - - setBackground(res.getDrawable(R.drawable.dismiss_circle_background)); - - mIconView.setImageDrawable(res.getDrawable(R.drawable.pip_ic_close_white)); addView(mIconView); - - setViewSizes(); } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - final Resources res = getResources(); - setBackground(res.getDrawable(R.drawable.dismiss_circle_background)); + setBackground(ContextCompat.getDrawable(getContext(), mBackgroundResId)); + setViewSizes(); + } + + /** + * Sets up view with the provided resource ids. + * Decouples resource dependency in order to be used externally (e.g. Launcher) + * + * @param backgroundResId drawable resource id of the circle background + * @param iconResId drawable resource id of the icon for the dismiss view + * @param iconSizeResId dimen resource id of the icon size + */ + public void setup(@DrawableRes int backgroundResId, @DrawableRes int iconResId, + @DimenRes int iconSizeResId) { + mBackgroundResId = backgroundResId; + mIconSizeResId = iconSizeResId; + + setBackground(ContextCompat.getDrawable(getContext(), backgroundResId)); + mIconView.setImageDrawable(ContextCompat.getDrawable(getContext(), iconResId)); setViewSizes(); } /** Retrieves the current dimensions for the icon and circle and applies them. */ private void setViewSizes() { - final Resources res = getResources(); - final int iconSize = res.getDimensionPixelSize(R.dimen.dismiss_target_x_size); + final int iconSize = getResources().getDimensionPixelSize(mIconSizeResId); mIconView.setLayoutParams( new FrameLayout.LayoutParams(iconSize, iconSize, Gravity.CENTER)); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt index 67ecb915e098..d275a0be8e93 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt @@ -14,41 +14,73 @@ * limitations under the License. */ -package com.android.wm.shell.bubbles +package com.android.wm.shell.common.bubbles import android.animation.ObjectAnimator import android.content.Context import android.graphics.Color import android.graphics.drawable.GradientDrawable import android.util.IntProperty +import android.util.Log import android.view.Gravity import android.view.View import android.view.ViewGroup import android.view.WindowInsets import android.view.WindowManager import android.widget.FrameLayout +import androidx.annotation.ColorRes +import androidx.annotation.DimenRes +import androidx.annotation.DrawableRes +import androidx.core.content.ContextCompat import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW -import com.android.wm.shell.R import com.android.wm.shell.animation.PhysicsAnimator -import com.android.wm.shell.common.DismissCircleView -/* +/** * View that handles interactions between DismissCircleView and BubbleStackView. + * + * @note [setup] method should be called after initialisation */ class DismissView(context: Context) : FrameLayout(context) { + /** + * The configuration is used to provide module specific resource ids + * + * @see [setup] method + */ + data class Config( + /** dimen resource id of the dismiss target circle view size */ + @DimenRes val targetSizeResId: Int, + /** dimen resource id of the icon size in the dismiss target */ + @DimenRes val iconSizeResId: Int, + /** dimen resource id of the bottom margin for the dismiss target */ + @DimenRes var bottomMarginResId: Int, + /** dimen resource id of the height for dismiss area gradient */ + @DimenRes val floatingGradientHeightResId: Int, + /** color resource id of the dismiss area gradient color */ + @ColorRes val floatingGradientColorResId: Int, + /** drawable resource id of the dismiss target background */ + @DrawableRes val backgroundResId: Int, + /** drawable resource id of the icon for the dismiss target */ + @DrawableRes val iconResId: Int + ) + + companion object { + private const val SHOULD_SETUP = + "The view isn't ready. Should be called after `setup`" + private val TAG = DismissView::class.simpleName + } var circle = DismissCircleView(context) var isShowing = false - var targetSizeResId: Int + var config: Config? = null private val animator = PhysicsAnimator.getInstance(circle) private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY) private val DISMISS_SCRIM_FADE_MS = 200L private var wm: WindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager - private var gradientDrawable = createGradient() + private var gradientDrawable: GradientDrawable? = null private val GRADIENT_ALPHA: IntProperty<GradientDrawable> = object : IntProperty<GradientDrawable>("alpha") { @@ -61,23 +93,41 @@ class DismissView(context: Context) : FrameLayout(context) { } init { - setLayoutParams(LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - resources.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height), - Gravity.BOTTOM)) - updatePadding() setClipToPadding(false) setClipChildren(false) setVisibility(View.INVISIBLE) + addView(circle) + } + + /** + * Sets up view with the provided resource ids. + * + * Decouples resource dependency in order to be used externally (e.g. Launcher). Usually called + * with default params in module specific extension: + * @see [DismissView.setup] in DismissViewExt.kt + */ + fun setup(config: Config) { + this.config = config + + // Setup layout + layoutParams = LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + resources.getDimensionPixelSize(config.floatingGradientHeightResId), + Gravity.BOTTOM) + updatePadding() + + // Setup gradient + gradientDrawable = createGradient(color = config.floatingGradientColorResId) setBackgroundDrawable(gradientDrawable) - targetSizeResId = R.dimen.dismiss_circle_size - val targetSize: Int = resources.getDimensionPixelSize(targetSizeResId) - addView(circle, LayoutParams(targetSize, targetSize, - Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)) - // start with circle offscreen so it's animated up - circle.setTranslationY(resources.getDimensionPixelSize( - R.dimen.floating_dismiss_gradient_height).toFloat()) + // Setup DismissCircleView + circle.setup(config.backgroundResId, config.iconResId, config.iconSizeResId) + val targetSize: Int = resources.getDimensionPixelSize(config.targetSizeResId) + circle.layoutParams = LayoutParams(targetSize, targetSize, + Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL) + // Initial position with circle offscreen so it's animated up + circle.translationY = resources.getDimensionPixelSize(config.floatingGradientHeightResId) + .toFloat() } /** @@ -85,6 +135,7 @@ class DismissView(context: Context) : FrameLayout(context) { */ fun show() { if (isShowing) return + val gradientDrawable = checkExists(gradientDrawable) ?: return isShowing = true setVisibility(View.VISIBLE) val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, @@ -104,6 +155,7 @@ class DismissView(context: Context) : FrameLayout(context) { */ fun hide() { if (!isShowing) return + val gradientDrawable = checkExists(gradientDrawable) ?: return isShowing = false val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, gradientDrawable.alpha, 0) @@ -124,18 +176,17 @@ class DismissView(context: Context) : FrameLayout(context) { } fun updateResources() { + val config = checkExists(config) ?: return updatePadding() - layoutParams.height = resources.getDimensionPixelSize( - R.dimen.floating_dismiss_gradient_height) - - val targetSize = resources.getDimensionPixelSize(targetSizeResId) + layoutParams.height = resources.getDimensionPixelSize(config.floatingGradientHeightResId) + val targetSize = resources.getDimensionPixelSize(config.targetSizeResId) circle.layoutParams.width = targetSize circle.layoutParams.height = targetSize circle.requestLayout() } - private fun createGradient(): GradientDrawable { - val gradientColor = context.resources.getColor(android.R.color.system_neutral1_900) + private fun createGradient(@ColorRes color: Int): GradientDrawable { + val gradientColor = ContextCompat.getColor(context, color) val alpha = 0.7f * 255 val gradientColorWithAlpha = Color.argb(alpha.toInt(), Color.red(gradientColor), @@ -150,10 +201,22 @@ class DismissView(context: Context) : FrameLayout(context) { } private fun updatePadding() { + val config = checkExists(config) ?: return val insets: WindowInsets = wm.getCurrentWindowMetrics().getWindowInsets() val navInset = insets.getInsetsIgnoringVisibility( WindowInsets.Type.navigationBars()) setPadding(0, 0, 0, navInset.bottom + - resources.getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin)) + resources.getDimensionPixelSize(config.bottomMarginResId)) + } + + /** + * Checks if the value is set up and exists, if not logs an exception. + * Used for convenient logging in case `setup` wasn't called before + * + * @return value provided as argument + */ + private fun <T>checkExists(value: T?): T? { + if (value == null) Log.e(TAG, SHOULD_SETUP) + return value } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt index ea9d065d5f53..cc37bd3a4589 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.bubbles +package com.android.wm.shell.common.bubbles import android.graphics.PointF import android.view.MotionEvent diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java index c76937de6669..ec2680085fb5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java @@ -76,6 +76,9 @@ public class DividerHandleView extends View { private int mCurrentHeight; private AnimatorSet mAnimator; private boolean mTouching; + private boolean mHovering; + private final int mHoveringWidth; + private final int mHoveringHeight; public DividerHandleView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); @@ -87,6 +90,8 @@ public class DividerHandleView extends View { mCurrentHeight = mHeight; mTouchingWidth = mWidth > mHeight ? mWidth / 2 : mWidth; mTouchingHeight = mHeight > mWidth ? mHeight / 2 : mHeight; + mHoveringWidth = mWidth > mHeight ? ((int) (mWidth * 1.5f)) : mWidth; + mHoveringHeight = mHeight > mWidth ? ((int) (mHeight * 1.5f)) : mHeight; } /** Sets touching state for this handle view. */ @@ -94,24 +99,32 @@ public class DividerHandleView extends View { if (touching == mTouching) { return; } + setInputState(touching, animate, mTouchingWidth, mTouchingHeight); + mTouching = touching; + } + + /** Sets hovering state for this handle view. */ + public void setHovering(boolean hovering, boolean animate) { + if (hovering == mHovering) { + return; + } + setInputState(hovering, animate, mHoveringWidth, mHoveringHeight); + mHovering = hovering; + } + + private void setInputState(boolean stateOn, boolean animate, int stateWidth, int stateHeight) { if (mAnimator != null) { mAnimator.cancel(); mAnimator = null; } if (!animate) { - if (touching) { - mCurrentWidth = mTouchingWidth; - mCurrentHeight = mTouchingHeight; - } else { - mCurrentWidth = mWidth; - mCurrentHeight = mHeight; - } + mCurrentWidth = stateOn ? stateWidth : mWidth; + mCurrentHeight = stateOn ? stateHeight : mHeight; invalidate(); } else { - animateToTarget(touching ? mTouchingWidth : mWidth, - touching ? mTouchingHeight : mHeight, touching); + animateToTarget(stateOn ? stateWidth : mWidth, + stateOn ? stateHeight : mHeight, stateOn); } - mTouching = touching; } private void animateToTarget(int targetWidth, int targetHeight, boolean touching) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java index 69f0bad4fb45..262d487b1d1b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java @@ -17,14 +17,19 @@ package com.android.wm.shell.common.split; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW; +import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW; import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY; +import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CURSOR_HOVER_STATES_ENABLED; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Rect; import android.os.Bundle; +import android.provider.DeviceConfig; import android.util.AttributeSet; import android.util.Property; import android.view.GestureDetector; @@ -32,6 +37,7 @@ import android.view.InsetsController; import android.view.InsetsSource; import android.view.InsetsState; import android.view.MotionEvent; +import android.view.PointerIcon; import android.view.SurfaceControlViewHost; import android.view.VelocityTracker; import android.view.View; @@ -46,6 +52,7 @@ import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.DividerSnapAlgorithm; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; @@ -222,7 +229,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { for (int i = insetsState.sourceSize() - 1; i >= 0; i--) { final InsetsSource source = insetsState.sourceAt(i); if (source.getType() == WindowInsets.Type.navigationBars() - && source.insetsRoundedCornerFrame()) { + && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) { mTempRect.inset(source.calculateVisibleInsets(mTempRect)); } } @@ -270,6 +277,12 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { } @Override + public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) { + return PointerIcon.getSystemIcon(getContext(), + isLandscape() ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW); + } + + @Override public boolean onTouch(View v, MotionEvent event) { if (mSplitLayout == null || !mInteractive) { return false; @@ -371,6 +384,43 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { mViewHost.relayout(lp); } + @Override + public boolean onHoverEvent(MotionEvent event) { + if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, CURSOR_HOVER_STATES_ENABLED, + /* defaultValue = */ false)) { + return false; + } + + if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) { + setHovering(); + return true; + } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) { + releaseHovering(); + return true; + } + return false; + } + + @VisibleForTesting + void setHovering() { + mHandle.setHovering(true, true); + mHandle.animate() + .setInterpolator(Interpolators.TOUCH_RESPONSE) + .setDuration(TOUCH_ANIMATION_DURATION) + .translationZ(mTouchElevation) + .start(); + } + + @VisibleForTesting + void releaseHovering() { + mHandle.setHovering(false, true); + mHandle.animate() + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .setDuration(TOUCH_RELEASE_ANIMATION_DURATION) + .translationZ(0) + .start(); + } + /** * Set divider should interactive to user or not. * diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java index 12d51f54a09c..47d58afb6aba 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java @@ -25,6 +25,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; @@ -86,13 +87,14 @@ public class TvWMShellModule { TransactionPool transactionPool, IconProvider iconProvider, Optional<RecentTasksController> recentTasks, + LaunchAdjacentController launchAdjacentController, @ShellMainThread ShellExecutor mainExecutor, Handler mainHandler, SystemWindows systemWindows) { return new TvSplitScreenController(context, shellInit, shellCommandHandler, shellController, shellTaskOrganizer, syncQueue, rootTDAOrganizer, displayController, displayImeController, displayInsetsController, dragAndDropController, transitions, - transactionPool, iconProvider, recentTasks, mainExecutor, mainHandler, - systemWindows); + transactionPool, iconProvider, recentTasks, launchAdjacentController, mainExecutor, + mainHandler, systemWindows); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index c491fed5d896..34a6e0ae406c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -46,6 +46,7 @@ import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.DockStateReader; import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; @@ -275,6 +276,13 @@ public abstract class WMShellBaseModule { return new WindowManagerShellWrapper(mainExecutor); } + @WMSingleton + @Provides + static LaunchAdjacentController provideLaunchAdjacentController( + SyncTransactionQueue syncQueue) { + return new LaunchAdjacentController(syncQueue); + } + // // Back animation // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index cff317259f1e..20c3bd2dccc4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -36,17 +36,21 @@ import com.android.wm.shell.bubbles.BubbleData; import com.android.wm.shell.bubbles.BubbleDataRepository; import com.android.wm.shell.bubbles.BubbleLogger; import com.android.wm.shell.bubbles.BubblePositioner; +import com.android.wm.shell.bubbles.properties.ProdBubbleProperties; +import com.android.wm.shell.bubbles.storage.BubblePersistentRepository; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TabletopModeController; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.common.annotations.ShellAnimationThread; import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.desktopmode.DesktopModeController; @@ -55,6 +59,7 @@ import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler; import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler; +import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformComponents; import com.android.wm.shell.freeform.FreeformTaskListener; @@ -180,11 +185,12 @@ public abstract class WMShellModule { IWindowManager wmService) { return new BubbleController(context, shellInit, shellCommandHandler, shellController, data, null /* synchronizer */, floatingContentCoordinator, - new BubbleDataRepository(context, launcherApps, mainExecutor), + new BubbleDataRepository(launcherApps, mainExecutor, + new BubblePersistentRepository(context)), statusBarService, windowManager, windowManagerShellWrapper, userManager, launcherApps, logger, taskStackListener, organizer, positioner, displayController, oneHandedOptional, dragAndDropController, mainExecutor, mainHandler, bgExecutor, - taskViewTransitions, syncQueue, wmService); + taskViewTransitions, syncQueue, wmService, ProdBubbleProperties.INSTANCE); } // @@ -202,8 +208,7 @@ public abstract class WMShellModule { SyncTransactionQueue syncQueue, Transitions transitions, Optional<DesktopModeController> desktopModeController, - Optional<DesktopTasksController> desktopTasksController, - Optional<SplitScreenController> splitScreenController) { + Optional<DesktopTasksController> desktopTasksController) { if (DesktopModeStatus.isAnyEnabled()) { return new DesktopModeWindowDecorViewModel( context, @@ -214,16 +219,15 @@ public abstract class WMShellModule { syncQueue, transitions, desktopModeController, - desktopTasksController, - splitScreenController); + desktopTasksController); } return new CaptionWindowDecorViewModel( - context, - mainHandler, - mainChoreographer, - taskOrganizer, - displayController, - syncQueue); + context, + mainHandler, + mainChoreographer, + taskOrganizer, + displayController, + syncQueue); } // @@ -263,8 +267,13 @@ public abstract class WMShellModule { static FreeformTaskTransitionHandler provideFreeformTaskTransitionHandler( ShellInit shellInit, Transitions transitions, - WindowDecorViewModel windowDecorViewModel) { - return new FreeformTaskTransitionHandler(shellInit, transitions, windowDecorViewModel); + Context context, + WindowDecorViewModel windowDecorViewModel, + DisplayController displayController, + @ShellMainThread ShellExecutor mainExecutor, + @ShellAnimationThread ShellExecutor animExecutor) { + return new FreeformTaskTransitionHandler(shellInit, transitions, context, + windowDecorViewModel, displayController, mainExecutor, animExecutor); } @WMSingleton @@ -327,11 +336,14 @@ public abstract class WMShellModule { TransactionPool transactionPool, IconProvider iconProvider, Optional<RecentTasksController> recentTasks, + LaunchAdjacentController launchAdjacentController, + Optional<WindowDecorViewModel> windowDecorViewModel, @ShellMainThread ShellExecutor mainExecutor) { return new SplitScreenController(context, shellInit, shellCommandHandler, shellController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, displayController, displayImeController, displayInsetsController, dragAndDropController, transitions, - transactionPool, iconProvider, recentTasks, mainExecutor); + transactionPool, iconProvider, recentTasks, launchAdjacentController, + windowDecorViewModel, mainExecutor); } // @@ -535,11 +547,13 @@ public abstract class WMShellModule { Optional<PipTouchHandler> pipTouchHandlerOptional, Optional<RecentsTransitionHandler> recentsTransitionHandler, KeyguardTransitionHandler keyguardTransitionHandler, + Optional<DesktopModeController> desktopModeController, + Optional<DesktopTasksController> desktopTasksController, Optional<UnfoldTransitionHandler> unfoldHandler, Transitions transitions) { return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional, pipTouchHandlerOptional, recentsTransitionHandler, keyguardTransitionHandler, - unfoldHandler); + desktopModeController, desktopTasksController, unfoldHandler); } @WMSingleton @@ -573,13 +587,13 @@ public abstract class WMShellModule { animators.add(fullscreenAnimator); return new UnfoldAnimationController( - shellInit, - transactionPool, - progressProvider.get(), - animators, - unfoldTransitionHandler, - mainExecutor - ); + shellInit, + transactionPool, + progressProvider.get(), + animators, + unfoldTransitionHandler, + mainExecutor + ); } @Provides @@ -671,6 +685,7 @@ public abstract class WMShellModule { static DesktopTasksController provideDesktopTasksController( Context context, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ShellController shellController, DisplayController displayController, ShellTaskOrganizer shellTaskOrganizer, @@ -679,13 +694,16 @@ public abstract class WMShellModule { Transitions transitions, EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler, ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler, + ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository, + LaunchAdjacentController launchAdjacentController, @ShellMainThread ShellExecutor mainExecutor ) { - return new DesktopTasksController(context, shellInit, shellController, displayController, - shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, transitions, - enterDesktopTransitionHandler, exitDesktopTransitionHandler, - desktopModeTaskRepository, mainExecutor); + return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController, + displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, + transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler, + toggleResizeDesktopTaskTransitionHandler, desktopModeTaskRepository, + launchAdjacentController, mainExecutor); } @WMSingleton @@ -697,6 +715,13 @@ public abstract class WMShellModule { @WMSingleton @Provides + static ToggleResizeDesktopTaskTransitionHandler provideToggleResizeDesktopTaskTransitionHandler( + Transitions transitions) { + return new ToggleResizeDesktopTaskTransitionHandler(transitions); + } + + @WMSingleton + @Provides static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler( Transitions transitions, Context context diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java index b9d2be280efb..db6c258e84c2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java @@ -33,6 +33,7 @@ import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DE import android.app.ActivityManager.RunningTaskInfo; import android.app.WindowConfiguration; import android.content.Context; +import android.content.res.TypedArray; import android.database.ContentObserver; import android.graphics.Region; import android.net.Uri; @@ -414,6 +415,25 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll } /** + * Applies the proper surface states (rounded corners) to tasks when desktop mode is active. + * This is intended to be used when desktop mode is part of another animation but isn't, itself, + * animating. + */ + public void syncSurfaceState(@NonNull TransitionInfo info, + SurfaceControl.Transaction finishTransaction) { + // Add rounded corners to freeform windows + final TypedArray ta = mContext.obtainStyledAttributes( + new int[]{android.R.attr.dialogCornerRadius}); + final int cornerRadius = ta.getDimensionPixelSize(0, 0); + ta.recycle(); + for (TransitionInfo.Change change: info.getChanges()) { + if (change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FREEFORM) { + finishTransaction.setCornerRadius(change.getLeash(), cornerRadius); + } + } + } + + /** * A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE} */ private final class SettingsObserver extends ContentObserver { @@ -500,6 +520,11 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll } @Override + public void showDesktopApp(int taskId) throws RemoteException { + // TODO + } + + @Override public int getVisibleTaskCount(int displayId) throws RemoteException { int[] result = new int[1]; executeRemoteCallWithTaskPermission(mController, "getVisibleTaskCount", @@ -508,5 +533,20 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll ); return result[0]; } + + @Override + public void stashDesktopApps(int displayId) throws RemoteException { + // Stashing of desktop apps not needed. Apps always launch on desktop + } + + @Override + public void hideStashedDesktopApps(int displayId) throws RemoteException { + // Stashing of desktop apps not needed. Apps always launch on desktop + } + + @Override + public void setTaskListener(IDesktopTaskListener listener) throws RemoteException { + // TODO(b/261234402): move visibility from sysui state to listener + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt index 3ab175d3b68a..711df0d89936 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt @@ -25,6 +25,7 @@ import androidx.core.util.keyIterator import androidx.core.util.valueIterator import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.util.KtProtoLog +import java.io.PrintWriter import java.util.concurrent.Executor import java.util.function.Consumer @@ -43,6 +44,7 @@ class DesktopModeTaskRepository { */ val activeTasks: ArraySet<Int> = ArraySet(), val visibleTasks: ArraySet<Int> = ArraySet(), + var stashed: Boolean = false ) // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0). @@ -85,8 +87,10 @@ class DesktopModeTaskRepository { visibleTasksListeners[visibleTasksListener] = executor displayData.keyIterator().forEach { displayId -> val visibleTasks = getVisibleTaskCount(displayId) + val stashed = isStashed(displayId) executor.execute { visibleTasksListener.onVisibilityChanged(displayId, visibleTasks > 0) + visibleTasksListener.onStashedChanged(displayId, stashed) } } } @@ -312,6 +316,52 @@ class DesktopModeTaskRepository { } /** + * Update stashed status on display with id [displayId] + */ + fun setStashed(displayId: Int, stashed: Boolean) { + val data = displayData.getOrCreate(displayId) + val oldValue = data.stashed + data.stashed = stashed + if (oldValue != stashed) { + KtProtoLog.d( + WM_SHELL_DESKTOP_MODE, + "DesktopTaskRepo: mark stashed=%b displayId=%d", + stashed, + displayId + ) + visibleTasksListeners.forEach { (listener, executor) -> + executor.execute { listener.onStashedChanged(displayId, stashed) } + } + } + } + + /** + * Check if display with id [displayId] has desktop tasks stashed + */ + fun isStashed(displayId: Int): Boolean { + return displayData[displayId]?.stashed ?: false + } + + internal fun dump(pw: PrintWriter, prefix: String) { + val innerPrefix = "$prefix " + pw.println("${prefix}DesktopModeTaskRepository") + dumpDisplayData(pw, innerPrefix) + pw.println("${innerPrefix}freeformTasksInZOrder=${freeformTasksInZOrder.toDumpString()}") + pw.println("${innerPrefix}activeTasksListeners=${activeTasksListeners.size}") + pw.println("${innerPrefix}visibleTasksListeners=${visibleTasksListeners.size}") + } + + private fun dumpDisplayData(pw: PrintWriter, prefix: String) { + val innerPrefix = "$prefix " + displayData.forEach { displayId, data -> + pw.println("${prefix}Display $displayId:") + pw.println("${innerPrefix}activeTasks=${data.activeTasks.toDumpString()}") + pw.println("${innerPrefix}visibleTasks=${data.visibleTasks.toDumpString()}") + pw.println("${innerPrefix}stashed=${data.stashed}") + } + } + + /** * Defines interface for classes that can listen to changes for active tasks in desktop mode. */ interface ActiveTasksListener { @@ -331,5 +381,15 @@ class DesktopModeTaskRepository { */ @JvmDefault fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {} + + /** + * Called when the desktop stashed status changes. + */ + @JvmDefault + fun onStashedChanged(displayId: Int, stashed: Boolean) {} } } + +private fun <T> Iterable<T>.toDumpString(): String { + return joinToString(separator = ", ", prefix = "[", postfix = "]") +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 91bb155d9d01..4fda4b7896c2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.desktopmode +import android.R import android.app.ActivityManager.RunningTaskInfo import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD @@ -24,19 +25,21 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.app.WindowConfiguration.WindowingMode import android.content.Context +import android.content.res.TypedArray import android.graphics.Point import android.graphics.Rect import android.graphics.Region import android.os.IBinder import android.os.SystemProperties +import android.util.DisplayMetrics.DENSITY_DEFAULT import android.view.SurfaceControl +import android.view.SurfaceControl.Transaction import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_NONE import android.view.WindowManager.TRANSIT_OPEN import android.view.WindowManager.TRANSIT_TO_FRONT import android.window.TransitionInfo import android.window.TransitionRequestInfo -import android.window.WindowContainerToken import android.window.WindowContainerTransaction import androidx.annotation.BinderThread import com.android.wm.shell.RootTaskDisplayAreaOrganizer @@ -44,18 +47,23 @@ import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.ExecutorUtils import com.android.wm.shell.common.ExternalInterfaceBinder +import com.android.wm.shell.common.LaunchAdjacentController import com.android.wm.shell.common.RemoteCallable import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.common.SingleInstanceRemoteListener import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.common.annotations.ExternalThread import com.android.wm.shell.common.annotations.ShellMainThread import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.sysui.ShellSharedConstants import com.android.wm.shell.transition.Transitions import com.android.wm.shell.util.KtProtoLog +import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration +import java.io.PrintWriter import java.util.concurrent.Executor import java.util.function.Consumer @@ -63,6 +71,7 @@ import java.util.function.Consumer class DesktopTasksController( private val context: Context, shellInit: ShellInit, + private val shellCommandHandler: ShellCommandHandler, private val shellController: ShellController, private val displayController: DisplayController, private val shellTaskOrganizer: ShellTaskOrganizer, @@ -71,7 +80,10 @@ class DesktopTasksController( private val transitions: Transitions, private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler, private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler, + private val toggleResizeDesktopTaskTransitionHandler: + ToggleResizeDesktopTaskTransitionHandler, private val desktopModeTaskRepository: DesktopModeTaskRepository, + private val launchAdjacentController: LaunchAdjacentController, @ShellMainThread private val mainExecutor: ShellExecutor ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler { @@ -82,6 +94,15 @@ class DesktopTasksController( visualIndicator?.releaseVisualIndicator(t) visualIndicator = null } + private val taskVisibilityListener = object : VisibleTasksListener { + override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) { + launchAdjacentController.launchAdjacentEnabled = !hasVisibleFreeformTasks + } + } + + private val transitionAreaHeight + get() = context.resources.getDimensionPixelSize( + com.android.wm.shell.R.dimen.desktop_mode_transition_area_height) init { desktopMode = DesktopModeImpl() @@ -92,12 +113,14 @@ class DesktopTasksController( private fun onInit() { KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController") + shellCommandHandler.addDumpCallback(this::dump, this) shellController.addExternalInterface( ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE, { createExternalInterface() }, this ) transitions.addHandler(this) + desktopModeTaskRepository.addVisibleTasksListener(taskVisibilityListener, mainExecutor) } /** Show all tasks, that are part of the desktop, on top of launcher */ @@ -118,27 +141,58 @@ class DesktopTasksController( } } + /** + * Stash desktop tasks on display with id [displayId]. + * + * When desktop tasks are stashed, launcher home screen icons are fully visible. New apps + * launched in this state will be added to the desktop. Existing desktop tasks will be brought + * back to front during the launch. + */ + fun stashDesktopApps(displayId: Int) { + KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: stashDesktopApps") + desktopModeTaskRepository.setStashed(displayId, true) + } + + /** + * Clear the stashed state for the given display + */ + fun hideStashedDesktopApps(displayId: Int) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: hideStashedApps displayId=%d", + displayId + ) + desktopModeTaskRepository.setStashed(displayId, false) + } + /** Get number of tasks that are marked as visible */ fun getVisibleTaskCount(displayId: Int): Int { return desktopModeTaskRepository.getVisibleTaskCount(displayId) } /** Move a task with given `taskId` to desktop */ - fun moveToDesktop(taskId: Int) { - shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToDesktop(task) } + fun moveToDesktop(taskId: Int, wct: WindowContainerTransaction = WindowContainerTransaction()) { + shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { + task -> moveToDesktop(task, wct) + } } - /** Move a task to desktop */ - fun moveToDesktop(task: RunningTaskInfo) { + /** + * Move a task to desktop + */ + fun moveToDesktop( + task: RunningTaskInfo, + wct: WindowContainerTransaction = WindowContainerTransaction() + ) { KtProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: moveToDesktop taskId=%d", task.taskId ) - val wct = WindowContainerTransaction() // Bring other apps to front first bringDesktopAppsToFront(task.displayId, wct) - addMoveToDesktopChanges(wct, task.token) + addMoveToDesktopChanges(wct, task) + if (Transitions.ENABLE_SHELL_TRANSITIONS) { transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) } else { @@ -158,7 +212,7 @@ class DesktopTasksController( ) val wct = WindowContainerTransaction() moveHomeTaskToFront(wct) - addMoveToDesktopChanges(wct, taskInfo.getToken()) + addMoveToDesktopChanges(wct, taskInfo) wct.setBounds(taskInfo.token, startBounds) if (Transitions.ENABLE_SHELL_TRANSITIONS) { @@ -178,7 +232,7 @@ class DesktopTasksController( ) val wct = WindowContainerTransaction() bringDesktopAppsToFront(taskInfo.displayId, wct) - addMoveToDesktopChanges(wct, taskInfo.getToken()) + addMoveToDesktopChanges(wct, taskInfo) wct.setBounds(taskInfo.token, freeformBounds) if (Transitions.ENABLE_SHELL_TRANSITIONS) { @@ -204,7 +258,7 @@ class DesktopTasksController( ) val wct = WindowContainerTransaction() - addMoveToFullscreenChanges(wct, task.token) + addMoveToFullscreenChanges(wct, task) if (Transitions.ENABLE_SHELL_TRANSITIONS) { transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) } else { @@ -218,16 +272,24 @@ class DesktopTasksController( */ fun cancelMoveToFreeform(task: RunningTaskInfo, position: Point) { KtProtoLog.v( - WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: cancelMoveToFreeform taskId=%d", - task.taskId + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: cancelMoveToFreeform taskId=%d", + task.taskId ) val wct = WindowContainerTransaction() - addMoveToFullscreenChanges(wct, task.token) + wct.setBounds(task.token, null) + if (Transitions.ENABLE_SHELL_TRANSITIONS) { - enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, position, - mOnAnimationFinishedCallback) + enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode( + wct, position) { t -> + val callbackWCT = WindowContainerTransaction() + visualIndicator?.releaseVisualIndicator(t) + visualIndicator = null + addMoveToFullscreenChanges(callbackWCT, task) + shellTaskOrganizer.applyTransaction(callbackWCT) + } } else { + addMoveToFullscreenChanges(wct, task) shellTaskOrganizer.applyTransaction(wct) releaseVisualIndicator() } @@ -240,7 +302,7 @@ class DesktopTasksController( task.taskId ) val wct = WindowContainerTransaction() - addMoveToFullscreenChanges(wct, task.token) + addMoveToFullscreenChanges(wct, task) if (Transitions.ENABLE_SHELL_TRANSITIONS) { exitDesktopTaskTransitionHandler.startTransition( @@ -252,6 +314,11 @@ class DesktopTasksController( } /** Move a task to the front */ + fun moveTaskToFront(taskId: Int) { + shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveTaskToFront(task) } + } + + /** Move a task to the front */ fun moveTaskToFront(taskInfo: RunningTaskInfo) { KtProtoLog.v( WM_SHELL_DESKTOP_MODE, @@ -331,6 +398,49 @@ class DesktopTasksController( } } + /** Quick-resizes a desktop task, toggling between the stable bounds and the default bounds. */ + fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo, windowDecor: DesktopModeWindowDecoration) { + val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return + + val stableBounds = Rect() + displayLayout.getStableBounds(stableBounds) + val destinationBounds = Rect() + if (taskInfo.configuration.windowConfiguration.bounds == stableBounds) { + // The desktop task is currently occupying the whole stable bounds, toggle to the + // default bounds. + getDefaultDesktopTaskBounds( + density = taskInfo.configuration.densityDpi.toFloat() / DENSITY_DEFAULT, + stableBounds = stableBounds, + outBounds = destinationBounds + ) + } else { + // Toggle to the stable bounds. + destinationBounds.set(stableBounds) + } + + val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds) + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + toggleResizeDesktopTaskTransitionHandler.startTransition( + wct, + taskInfo.taskId, + windowDecor + ) + } else { + shellTaskOrganizer.applyTransaction(wct) + } + } + + private fun getDefaultDesktopTaskBounds(density: Float, stableBounds: Rect, outBounds: Rect) { + val width = (DESKTOP_MODE_DEFAULT_WIDTH_DP * density + 0.5f).toInt() + val height = (DESKTOP_MODE_DEFAULT_HEIGHT_DP * density + 0.5f).toInt() + outBounds.set(0, 0, width, height) + // Center the task in stable bounds + outBounds.offset( + stableBounds.centerX() - outBounds.centerX(), + stableBounds.centerY() - outBounds.centerY() + ) + } + /** * Get windowing move for a given `taskId` * @@ -397,83 +507,169 @@ class DesktopTasksController( transition: IBinder, request: TransitionRequestInfo ): WindowContainerTransaction? { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: handleRequest request=%s", + request + ) // Check if we should skip handling this transition + var reason = "" val shouldHandleRequest = when { // Only handle open or to front transitions - request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> false + request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> { + reason = "transition type not handled (${request.type})" + false + } // Only handle when it is a task transition - request.triggerTask == null -> false + request.triggerTask == null -> { + reason = "triggerTask is null" + false + } // Only handle standard type tasks - request.triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> false + request.triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> { + reason = "activityType not handled (${request.triggerTask.activityType})" + false + } // Only handle fullscreen or freeform tasks request.triggerTask.windowingMode != WINDOWING_MODE_FULLSCREEN && - request.triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> false + request.triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> { + reason = "windowingMode not handled (${request.triggerTask.windowingMode})" + false + } // Otherwise process it else -> true } if (!shouldHandleRequest) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: skipping handleRequest reason=%s", + reason + ) return null } val task: RunningTaskInfo = request.triggerTask - val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId) - // Check if we should switch a fullscreen task to freeform - if (task.windowingMode == WINDOWING_MODE_FULLSCREEN) { - // If there are any visible desktop tasks, switch the task to freeform - if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) { - KtProtoLog.d( + val result = when { + // If display has tasks stashed, handle as stashed launch + desktopModeTaskRepository.isStashed(task.displayId) -> handleStashedTaskLaunch(task) + // Check if fullscreen task should be updated + task.windowingMode == WINDOWING_MODE_FULLSCREEN -> handleFullscreenTaskLaunch(task) + // Check if freeform task should be updated + task.windowingMode == WINDOWING_MODE_FREEFORM -> handleFreeformTaskLaunch(task) + else -> { + null + } + } + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: handleRequest result=%s", + result ?: "null" + ) + return result + } + + /** + * Applies the proper surface states (rounded corners) to tasks when desktop mode is active. + * This is intended to be used when desktop mode is part of another animation but isn't, itself, + * animating. + */ + fun syncSurfaceState( + info: TransitionInfo, + finishTransaction: SurfaceControl.Transaction + ) { + // Add rounded corners to freeform windows + val ta: TypedArray = context.obtainStyledAttributes( + intArrayOf(R.attr.dialogCornerRadius)) + val cornerRadius = ta.getDimensionPixelSize(0, 0).toFloat() + ta.recycle() + info.changes + .filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM } + .forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) } + } + + private fun handleFreeformTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? { + KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch") + val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId) + if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) { + KtProtoLog.d( WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: switch fullscreen task to freeform on transition" + - " taskId=%d", + "DesktopTasksController: switch freeform task to fullscreen oon transition" + + " taskId=%d", task.taskId - ) - return WindowContainerTransaction().also { wct -> - addMoveToDesktopChanges(wct, task.token) - } + ) + return WindowContainerTransaction().also { wct -> + addMoveToFullscreenChanges(wct, task) } } + return null + } - // CHeck if we should switch a freeform task to fullscreen - if (task.windowingMode == WINDOWING_MODE_FREEFORM) { - // If no visible desktop tasks, switch this task to freeform as the transition came - // outside of this controller - if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) { - KtProtoLog.d( + private fun handleFullscreenTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? { + KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFullscreenTaskLaunch") + val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId) + if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) { + KtProtoLog.d( WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: switch freeform task to fullscreen oon transition" + - " taskId=%d", + "DesktopTasksController: switch fullscreen task to freeform on transition" + + " taskId=%d", task.taskId - ) - return WindowContainerTransaction().also { wct -> - addMoveToFullscreenChanges(wct, task.token) - } + ) + return WindowContainerTransaction().also { wct -> + addMoveToDesktopChanges(wct, task) } } return null } + private fun handleStashedTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction { + KtProtoLog.d( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: launch apps with stashed on transition taskId=%d", + task.taskId + ) + val wct = WindowContainerTransaction() + bringDesktopAppsToFront(task.displayId, wct) + addMoveToDesktopChanges(wct, task) + desktopModeTaskRepository.setStashed(task.displayId, false) + return wct + } + private fun addMoveToDesktopChanges( wct: WindowContainerTransaction, - token: WindowContainerToken + taskInfo: RunningTaskInfo ) { - wct.setWindowingMode(token, WINDOWING_MODE_FREEFORM) - wct.reorder(token, true /* onTop */) + val displayWindowingMode = taskInfo.configuration.windowConfiguration.displayWindowingMode + val targetWindowingMode = if (displayWindowingMode == WINDOWING_MODE_FREEFORM) { + // Display windowing is freeform, set to undefined and inherit it + WINDOWING_MODE_UNDEFINED + } else { + WINDOWING_MODE_FREEFORM + } + wct.setWindowingMode(taskInfo.token, targetWindowingMode) + wct.reorder(taskInfo.token, true /* onTop */) if (isDesktopDensityOverrideSet()) { - wct.setDensityDpi(token, getDesktopDensityDpi()) + wct.setDensityDpi(taskInfo.token, getDesktopDensityDpi()) } } private fun addMoveToFullscreenChanges( wct: WindowContainerTransaction, - token: WindowContainerToken + taskInfo: RunningTaskInfo ) { - wct.setWindowingMode(token, WINDOWING_MODE_FULLSCREEN) - wct.setBounds(token, null) + val displayWindowingMode = taskInfo.configuration.windowConfiguration.displayWindowingMode + val targetWindowingMode = if (displayWindowingMode == WINDOWING_MODE_FULLSCREEN) { + // Display windowing is fullscreen, set to undefined and inherit it + WINDOWING_MODE_UNDEFINED + } else { + WINDOWING_MODE_FULLSCREEN + } + wct.setWindowingMode(taskInfo.token, targetWindowingMode) + wct.setBounds(taskInfo.token, null) if (isDesktopDensityOverrideSet()) { - wct.setDensityDpi(token, getFullscreenDensityDpi()) + wct.setDensityDpi(taskInfo.token, getFullscreenDensityDpi()) } } @@ -508,13 +704,12 @@ class DesktopTasksController( y: Float ) { if (taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) { - val statusBarHeight = getStatusBarHeight(taskInfo) - if (y <= statusBarHeight && visualIndicator == null) { + if (y <= transitionAreaHeight && visualIndicator == null) { visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo, displayController, context, taskSurface, shellTaskOrganizer, rootTaskDisplayAreaOrganizer) visualIndicator?.createFullscreenIndicatorWithAnimatedBounds() - } else if (y > statusBarHeight && visualIndicator != null) { + } else if (y > transitionAreaHeight && visualIndicator != null) { releaseVisualIndicator() } } @@ -524,14 +719,18 @@ class DesktopTasksController( * Perform checks required on drag end. Move to fullscreen if drag ends in status bar area. * * @param taskInfo the task being dragged. - * @param position position of surface when drag ends + * @param position position of surface when drag ends. + * @param y the Y position of the motion event. + * @param windowDecor the window decoration for the task being dragged */ fun onDragPositioningEnd( taskInfo: RunningTaskInfo, - position: Point + position: Point, + y: Float, + windowDecor: DesktopModeWindowDecoration ) { - val statusBarHeight = getStatusBarHeight(taskInfo) - if (position.y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) { + if (y <= transitionAreaHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) { + windowDecor.incrementRelayoutBlock() moveToFullscreenWithAnimation(taskInfo, position) } } @@ -549,9 +748,9 @@ class DesktopTasksController( taskSurface: SurfaceControl, y: Float ) { - // If the motion event is above the status bar, return since we do not need to show the - // visual indicator at this point. - if (y < getStatusBarHeight(taskInfo)) { + // If the motion event is above the status bar and the visual indicator is not yet visible, + // return since we do not need to show the visual indicator at this point. + if (y < getStatusBarHeight(taskInfo) && visualIndicator == null) { return } if (visualIndicator == null) { @@ -632,6 +831,12 @@ class DesktopTasksController( desktopModeTaskRepository.setTaskCornerListener(listener, callbackExecutor) } + private fun dump(pw: PrintWriter, prefix: String) { + val innerPrefix = "$prefix " + pw.println("${prefix}DesktopTasksController") + desktopModeTaskRepository.dump(pw, innerPrefix) + } + /** The interface for calls from outside the shell, within the host process. */ @ExternalThread private inner class DesktopModeImpl : DesktopMode { @@ -658,8 +863,51 @@ class DesktopTasksController( @BinderThread private class IDesktopModeImpl(private var controller: DesktopTasksController?) : IDesktopMode.Stub(), ExternalInterfaceBinder { + + private lateinit var remoteListener: + SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener> + + private val listener: VisibleTasksListener = object : VisibleTasksListener { + override fun onVisibilityChanged(displayId: Int, visible: Boolean) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "IDesktopModeImpl: onVisibilityChanged display=%d visible=%b", + displayId, + visible + ) + remoteListener.call { l -> l.onVisibilityChanged(displayId, visible) } + } + + override fun onStashedChanged(displayId: Int, stashed: Boolean) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "IDesktopModeImpl: onStashedChanged display=%d stashed=%b", + displayId, + stashed + ) + remoteListener.call { l -> l.onStashedChanged(displayId, stashed) } + } + } + + init { + remoteListener = + SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>( + controller, + { c -> + c.desktopModeTaskRepository.addVisibleTasksListener( + listener, + c.mainExecutor + ) + }, + { c -> + c.desktopModeTaskRepository.removeVisibleTasksListener(listener) + } + ) + } + /** Invalidates this instance, preventing future calls from updating the controller. */ override fun invalidate() { + remoteListener.unregister() controller = null } @@ -670,6 +918,27 @@ class DesktopTasksController( ) { c -> c.showDesktopApps(displayId) } } + override fun stashDesktopApps(displayId: Int) { + ExecutorUtils.executeRemoteCallWithTaskPermission( + controller, + "stashDesktopApps" + ) { c -> c.stashDesktopApps(displayId) } + } + + override fun hideStashedDesktopApps(displayId: Int) { + ExecutorUtils.executeRemoteCallWithTaskPermission( + controller, + "hideStashedDesktopApps" + ) { c -> c.hideStashedDesktopApps(displayId) } + } + + override fun showDesktopApp(taskId: Int) { + ExecutorUtils.executeRemoteCallWithTaskPermission( + controller, + "showDesktopApp" + ) { c -> c.moveTaskToFront(taskId) } + } + override fun getVisibleTaskCount(displayId: Int): Int { val result = IntArray(1) ExecutorUtils.executeRemoteCallWithTaskPermission( @@ -680,13 +949,33 @@ class DesktopTasksController( ) return result[0] } + + override fun setTaskListener(listener: IDesktopTaskListener?) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "IDesktopModeImpl: set task listener=%s", + listener ?: "null" + ) + ExecutorUtils.executeRemoteCallWithTaskPermission( + controller, + "setTaskListener" + ) { _ -> listener?.let { remoteListener.register(it) } ?: remoteListener.unregister() } + } } companion object { private val DESKTOP_DENSITY_OVERRIDE = - SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 0) + SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 284) private val DESKTOP_DENSITY_ALLOWED_RANGE = (100..1000) + // Override default freeform task width when desktop mode is enabled. In dips. + private val DESKTOP_MODE_DEFAULT_WIDTH_DP = + SystemProperties.getInt("persist.wm.debug.desktop_mode.default_width", 840) + + // Override default freeform task height when desktop mode is enabled. In dips. + private val DESKTOP_MODE_DEFAULT_HEIGHT_DP = + SystemProperties.getInt("persist.wm.debug.desktop_mode.default_height", 630) + /** * Check if desktop density override is enabled */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java index 3733b919e366..3e175f3a9ae2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java @@ -17,7 +17,6 @@ package com.android.wm.shell.desktopmode; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -200,7 +199,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition } if (type == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE - && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN + && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM && mPosition != null) { // This Transition animates a task to fullscreen after being dragged from the status // bar and then released back into the status bar area diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl index 899d67267e69..ee3a080e7318 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl @@ -16,6 +16,8 @@ package com.android.wm.shell.desktopmode; +import com.android.wm.shell.desktopmode.IDesktopTaskListener; + /** * Interface that is exposed to remote callers to manipulate desktop mode features. */ @@ -24,6 +26,18 @@ interface IDesktopMode { /** Show apps on the desktop on the given display */ void showDesktopApps(int displayId); + /** Stash apps on the desktop to allow launching another app from home screen */ + void stashDesktopApps(int displayId); + + /** Hide apps that may be stashed */ + void hideStashedDesktopApps(int displayId); + + /** Bring task with the given id to front */ + oneway void showDesktopApp(int taskId); + /** Get count of visible desktop tasks on the given display */ int getVisibleTaskCount(int displayId); + + /** Set listener that will receive callbacks about updates to desktop tasks */ + oneway void setTaskListener(IDesktopTaskListener listener); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl new file mode 100644 index 000000000000..39128a863ec9 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl @@ -0,0 +1,30 @@ +/* + * 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 com.android.wm.shell.desktopmode; + +/** + * Allows external processes to register a listener in WMShell to get updates about desktop task + * state. + */ +interface IDesktopTaskListener { + + /** Desktop task visibility has change. Visible if at least 1 task is visible. */ + oneway void onVisibilityChanged(int displayId, boolean visible); + + /** Desktop task stashed status has changed. */ + oneway void onStashedChanged(int displayId, boolean stashed); +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt new file mode 100644 index 000000000000..94788e45e2b6 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt @@ -0,0 +1,154 @@ +/* + * 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 com.android.wm.shell.desktopmode + +import android.animation.Animator +import android.animation.RectEvaluator +import android.animation.ValueAnimator +import android.graphics.Rect +import android.os.IBinder +import android.util.SparseArray +import android.view.SurfaceControl +import android.view.WindowManager.TRANSIT_CHANGE +import android.window.TransitionInfo +import android.window.TransitionRequestInfo +import android.window.WindowContainerTransaction +import androidx.core.animation.addListener +import com.android.wm.shell.transition.Transitions +import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE +import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration +import java.util.function.Supplier + +/** Handles the animation of quick resizing of desktop tasks. */ +class ToggleResizeDesktopTaskTransitionHandler( + private val transitions: Transitions, + private val transactionSupplier: Supplier<SurfaceControl.Transaction> +) : Transitions.TransitionHandler { + + private val rectEvaluator = RectEvaluator(Rect()) + private val taskToDecorationMap = SparseArray<DesktopModeWindowDecoration>() + + private var boundsAnimator: Animator? = null + + constructor( + transitions: Transitions + ) : this(transitions, Supplier { SurfaceControl.Transaction() }) + + /** Starts a quick resize transition. */ + fun startTransition( + wct: WindowContainerTransaction, + taskId: Int, + windowDecoration: DesktopModeWindowDecoration + ) { + // Pause relayout until the transition animation finishes. + windowDecoration.incrementRelayoutBlock() + transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this) + taskToDecorationMap.put(taskId, windowDecoration) + } + + override fun startAnimation( + transition: IBinder, + info: TransitionInfo, + startTransaction: SurfaceControl.Transaction, + finishTransaction: SurfaceControl.Transaction, + finishCallback: Transitions.TransitionFinishCallback + ): Boolean { + val change = findRelevantChange(info) + val leash = change.leash + val taskId = change.taskInfo.taskId + val startBounds = change.startAbsBounds + val endBounds = change.endAbsBounds + val windowDecor = + taskToDecorationMap.removeReturnOld(taskId) + ?: throw IllegalStateException("Window decoration not found for task $taskId") + + val tx = transactionSupplier.get() + boundsAnimator?.cancel() + boundsAnimator = + ValueAnimator.ofObject(rectEvaluator, startBounds, endBounds) + .setDuration(RESIZE_DURATION_MS) + .apply { + addListener( + onStart = { + startTransaction + .setPosition( + leash, + startBounds.left.toFloat(), + startBounds.top.toFloat() + ) + .setWindowCrop(leash, startBounds.width(), startBounds.height()) + .show(leash) + windowDecor.showResizeVeil(startTransaction, startBounds) + }, + onEnd = { + finishTransaction + .setPosition( + leash, + endBounds.left.toFloat(), + endBounds.top.toFloat() + ) + .setWindowCrop(leash, endBounds.width(), endBounds.height()) + .show(leash) + windowDecor.hideResizeVeil() + finishCallback.onTransitionFinished(null, null) + boundsAnimator = null + } + ) + addUpdateListener { anim -> + val rect = anim.animatedValue as Rect + tx.setPosition(leash, rect.left.toFloat(), rect.top.toFloat()) + .setWindowCrop(leash, rect.width(), rect.height()) + .show(leash) + windowDecor.updateResizeVeil(tx, rect) + } + start() + } + return true + } + + override fun handleRequest( + transition: IBinder, + request: TransitionRequestInfo + ): WindowContainerTransaction? { + return null + } + + private fun findRelevantChange(info: TransitionInfo): TransitionInfo.Change { + val matchingChanges = + info.changes.filter { c -> + !isWallpaper(c) && isValidTaskChange(c) && c.mode == TRANSIT_CHANGE + } + if (matchingChanges.size != 1) { + throw IllegalStateException( + "Expected 1 relevant change but found: ${matchingChanges.size}" + ) + } + return matchingChanges.first() + } + + private fun isWallpaper(change: TransitionInfo.Change): Boolean { + return (change.flags and TransitionInfo.FLAG_IS_WALLPAPER) != 0 + } + + private fun isValidTaskChange(change: TransitionInfo.Change): Boolean { + return change.taskInfo != null && change.taskInfo?.taskId != -1 + } + + companion object { + private const val RESIZE_DURATION_MS = 300L + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md index 99922fbc2d95..f9ea1d4e2a07 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md @@ -21,11 +21,17 @@ This code itself will not compile by itself, but the `protologtool` will preproc building to check the log state (is enabled) before printing the print format style log. **Notes** -- ProtoLogs currently only work from soong builds (ie. via make/mp). We need to reimplement the - tool for use with SysUI-studio +- ProtoLogs are only fully supported from soong builds (ie. via make/mp). In SysUI-studio it falls + back to log via Logcat - Non-text ProtoLogs are not currently supported with the Shell library (you can't view them with traces in Winscope) +### Kotlin + +Protolog tool does not yet have support for Kotlin code (see [b/168581922](https://b.corp.google.com/issues/168581922)). +For logging in Kotlin, use the [KtProtoLog](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt) +class which has a similar API to the Java ProtoLog class. + ### Enabling ProtoLog command line logging Run these commands to enable protologs for both WM Core and WM Shell to print to logcat. ```shell diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java index 04fc79acadbd..55e34fe3d836 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java @@ -19,9 +19,15 @@ package com.android.wm.shell.freeform; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.app.ActivityManager; import android.app.WindowConfiguration; +import android.content.Context; +import android.graphics.Rect; import android.os.IBinder; +import android.util.ArrayMap; import android.view.SurfaceControl; import android.view.WindowManager; import android.window.TransitionInfo; @@ -31,6 +37,8 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.WindowDecorViewModel; @@ -39,23 +47,37 @@ import java.util.ArrayList; import java.util.List; /** - * The {@link Transitions.TransitionHandler} that handles freeform task maximizing and restoring - * transitions. + * The {@link Transitions.TransitionHandler} that handles freeform task maximizing, closing, and + * restoring transitions. */ public class FreeformTaskTransitionHandler implements Transitions.TransitionHandler, FreeformTaskTransitionStarter { - + private static final int CLOSE_ANIM_DURATION = 400; + private final Context mContext; private final Transitions mTransitions; private final WindowDecorViewModel mWindowDecorViewModel; + private final DisplayController mDisplayController; + private final ShellExecutor mMainExecutor; + private final ShellExecutor mAnimExecutor; private final List<IBinder> mPendingTransitionTokens = new ArrayList<>(); + private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>(); + public FreeformTaskTransitionHandler( ShellInit shellInit, Transitions transitions, - WindowDecorViewModel windowDecorViewModel) { + Context context, + WindowDecorViewModel windowDecorViewModel, + DisplayController displayController, + ShellExecutor mainExecutor, + ShellExecutor animExecutor) { mTransitions = transitions; + mContext = context; mWindowDecorViewModel = windowDecorViewModel; + mDisplayController = displayController; + mMainExecutor = mainExecutor; + mAnimExecutor = animExecutor; if (Transitions.ENABLE_SHELL_TRANSITIONS) { shellInit.addInitCallback(this::onInit, this); } @@ -103,6 +125,14 @@ public class FreeformTaskTransitionHandler @NonNull SurfaceControl.Transaction finishT, @NonNull Transitions.TransitionFinishCallback finishCallback) { boolean transitionHandled = false; + final ArrayList<Animator> animations = new ArrayList<>(); + final Runnable onAnimFinish = () -> { + if (!animations.isEmpty()) return; + mMainExecutor.execute(() -> { + mAnimations.remove(transition); + finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + }); + }; for (TransitionInfo.Change change : info.getChanges()) { if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) { continue; @@ -121,21 +151,45 @@ public class FreeformTaskTransitionHandler case WindowManager.TRANSIT_TO_BACK: transitionHandled |= startMinimizeTransition(transition); break; + case WindowManager.TRANSIT_CLOSE: + if (change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FREEFORM) { + transitionHandled |= startCloseTransition(transition, change, + finishT, animations, onAnimFinish); + } + break; } } - - mPendingTransitionTokens.remove(transition); - if (!transitionHandled) { return false; } - + mAnimations.put(transition, animations); + // startT must be applied before animations start. startT.apply(); - mTransitions.getMainExecutor().execute( - () -> finishCallback.onTransitionFinished(null, null)); + mAnimExecutor.execute(() -> { + for (Animator anim : animations) { + anim.start(); + } + }); + // Run this here in case no animators are created. + onAnimFinish.run(); + mPendingTransitionTokens.remove(transition); return true; } + @Override + public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + ArrayList<Animator> animations = mAnimations.get(mergeTarget); + if (animations == null) return; + mAnimExecutor.execute(() -> { + for (Animator anim : animations) { + anim.end(); + } + }); + + } + private boolean startChangeTransition( IBinder transition, int type, @@ -165,6 +219,36 @@ public class FreeformTaskTransitionHandler return mPendingTransitionTokens.contains(transition); } + private boolean startCloseTransition(IBinder transition, TransitionInfo.Change change, + SurfaceControl.Transaction finishT, ArrayList<Animator> animations, + Runnable onAnimFinish) { + if (!mPendingTransitionTokens.contains(transition)) return false; + int screenHeight = mDisplayController + .getDisplayLayout(change.getTaskInfo().displayId).height(); + ValueAnimator animator = new ValueAnimator(); + animator.setDuration(CLOSE_ANIM_DURATION) + .setFloatValues(0f, 1f); + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + SurfaceControl sc = change.getLeash(); + finishT.hide(sc); + Rect startBounds = new Rect(change.getTaskInfo().configuration.windowConfiguration + .getBounds()); + animator.addUpdateListener(animation -> { + t.setPosition(sc, startBounds.left, + startBounds.top + (animation.getAnimatedFraction() * screenHeight)); + t.apply(); + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + animations.remove(animator); + onAnimFinish.run(); + } + }); + animations.add(animator); + return true; + } + @Nullable @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java index 9729a4007bac..da455f85d908 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java @@ -33,9 +33,10 @@ import android.view.WindowManager; import androidx.annotation.NonNull; import com.android.wm.shell.R; -import com.android.wm.shell.bubbles.DismissView; -import com.android.wm.shell.common.DismissCircleView; +import com.android.wm.shell.bubbles.DismissViewUtils; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.bubbles.DismissCircleView; +import com.android.wm.shell.common.bubbles.DismissView; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; import com.android.wm.shell.pip.PipUiEventLogger; @@ -106,6 +107,7 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen } mTargetViewContainer = new DismissView(mContext); + DismissViewUtils.setup(mTargetViewContainer); mTargetView = mTargetViewContainer.getCircle(); mTargetViewContainer.setOnApplyWindowInsetsListener((view, windowInsets) -> { if (!windowInsets.equals(mWindowInsets)) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java index 2a61445b27ba..b0fa9936d879 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java @@ -49,7 +49,7 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { Consts.TAG_WM_SPLIT_SCREEN), WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), - WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, + WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM_SHELL), WM_SHELL_FLOATING_APPS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 843e5af67af9..837f11803ab2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -567,7 +567,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) { // Tasks that are always on top (e.g. bubbles), will handle their own transition // as they are on top of everything else. So cancel the merge here. - cancel("task #" + taskInfo.taskId + " is always_on_top"); + cancel(false /* toHome */, false /* withScreenshots */, + "task #" + taskInfo.taskId + " is always_on_top"); return; } final boolean isRootTask = taskInfo != null diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java index 89538cb394d4..e52235fda80f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java @@ -24,6 +24,9 @@ import android.window.WindowContainerTransaction; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.windowdecor.WindowDecorViewModel; + +import java.util.Optional; /** * Main stage for split-screen mode. When split-screen is active all standard activity types launch @@ -35,9 +38,10 @@ class MainStage extends StageTaskListener { MainStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, - SurfaceSession surfaceSession, IconProvider iconProvider) { + SurfaceSession surfaceSession, IconProvider iconProvider, + Optional<WindowDecorViewModel> windowDecorViewModel) { super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, - iconProvider); + iconProvider, windowDecorViewModel); } boolean isActive() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java index 8639b36faf4c..9903113c5453 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java @@ -25,6 +25,9 @@ import android.window.WindowContainerTransaction; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.windowdecor.WindowDecorViewModel; + +import java.util.Optional; /** * Side stage for split-screen mode. Only tasks that are explicitly pinned to this stage show up @@ -37,9 +40,10 @@ class SideStage extends StageTaskListener { SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, - SurfaceSession surfaceSession, IconProvider iconProvider) { + SurfaceSession surfaceSession, IconProvider iconProvider, + Optional<WindowDecorViewModel> windowDecorViewModel) { super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, - iconProvider); + iconProvider, windowDecorViewModel); } boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index e7a367f1992d..e294229038f2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -76,6 +76,7 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ExternalInterfaceBinder; +import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SingleInstanceRemoteListener; @@ -94,6 +95,7 @@ import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.windowdecor.WindowDecorViewModel; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -171,6 +173,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, private final TransactionPool mTransactionPool; private final IconProvider mIconProvider; private final Optional<RecentTasksController> mRecentTasksOptional; + private final LaunchAdjacentController mLaunchAdjacentController; + private final Optional<WindowDecorViewModel> mWindowDecorViewModel; private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler; private final String[] mAppsSupportMultiInstances; @@ -197,6 +201,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, TransactionPool transactionPool, IconProvider iconProvider, Optional<RecentTasksController> recentTasks, + LaunchAdjacentController launchAdjacentController, + Optional<WindowDecorViewModel> windowDecorViewModel, ShellExecutor mainExecutor) { mShellCommandHandler = shellCommandHandler; mShellController = shellController; @@ -213,6 +219,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mTransactionPool = transactionPool; mIconProvider = iconProvider; mRecentTasksOptional = recentTasks; + mLaunchAdjacentController = launchAdjacentController; + mWindowDecorViewModel = windowDecorViewModel; mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this); // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic // override for this controller from the base module @@ -242,6 +250,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, TransactionPool transactionPool, IconProvider iconProvider, RecentTasksController recentTasks, + LaunchAdjacentController launchAdjacentController, + WindowDecorViewModel windowDecorViewModel, ShellExecutor mainExecutor, StageCoordinator stageCoordinator) { mShellCommandHandler = shellCommandHandler; @@ -259,6 +269,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mTransactionPool = transactionPool; mIconProvider = iconProvider; mRecentTasksOptional = Optional.of(recentTasks); + mLaunchAdjacentController = launchAdjacentController; + mWindowDecorViewModel = Optional.of(windowDecorViewModel); mStageCoordinator = stageCoordinator; mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this); shellInit.addInitCallback(this::onInit, this); @@ -291,13 +303,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mStageCoordinator = createStageCoordinator(); } mDragAndDropController.ifPresent(controller -> controller.setSplitScreenController(this)); + mWindowDecorViewModel.ifPresent(viewModel -> viewModel.setSplitScreenController(this)); } protected StageCoordinator createStageCoordinator() { return new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, mTaskOrganizer, mDisplayController, mDisplayImeController, - mDisplayInsetsController, mTransitions, mTransactionPool, - mIconProvider, mMainExecutor, mRecentTasksOptional); + mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider, + mMainExecutor, mRecentTasksOptional, mLaunchAdjacentController, + mWindowDecorViewModel); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 64f759856899..bf70d48e5801 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -121,6 +121,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.common.ScreenshotUtils; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; @@ -138,6 +139,7 @@ import com.android.wm.shell.transition.LegacyTransitions; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.util.SplitBounds; import com.android.wm.shell.util.TransitionUtil; +import com.android.wm.shell.windowdecor.WindowDecorViewModel; import java.io.PrintWriter; import java.util.ArrayList; @@ -193,6 +195,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // if user is opening another task(s). private final ArrayList<Integer> mPausingTasks = new ArrayList<>(); private final Optional<RecentTasksController> mRecentTasks; + private final LaunchAdjacentController mLaunchAdjacentController; + private final Optional<WindowDecorViewModel> mWindowDecorViewModel; private final Rect mTempRect1 = new Rect(); private final Rect mTempRect2 = new Rect(); @@ -270,7 +274,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, DisplayInsetsController displayInsetsController, Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider, ShellExecutor mainExecutor, - Optional<RecentTasksController> recentTasks) { + Optional<RecentTasksController> recentTasks, + LaunchAdjacentController launchAdjacentController, + Optional<WindowDecorViewModel> windowDecorViewModel) { mContext = context; mDisplayId = displayId; mSyncQueue = syncQueue; @@ -278,6 +284,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mLogger = new SplitscreenEventLogger(); mMainExecutor = mainExecutor; mRecentTasks = recentTasks; + mLaunchAdjacentController = launchAdjacentController; + mWindowDecorViewModel = windowDecorViewModel; taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */); @@ -288,7 +296,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mMainStageListener, mSyncQueue, mSurfaceSession, - iconProvider); + iconProvider, + mWindowDecorViewModel); mSideStage = new SideStage( mContext, mTaskOrganizer, @@ -296,7 +305,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSideStageListener, mSyncQueue, mSurfaceSession, - iconProvider); + iconProvider, + mWindowDecorViewModel); mDisplayController = displayController; mDisplayImeController = displayImeController; mDisplayInsetsController = displayInsetsController; @@ -323,7 +333,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, DisplayInsetsController displayInsetsController, SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool, ShellExecutor mainExecutor, - Optional<RecentTasksController> recentTasks) { + Optional<RecentTasksController> recentTasks, + LaunchAdjacentController launchAdjacentController, + Optional<WindowDecorViewModel> windowDecorViewModel) { mContext = context; mDisplayId = displayId; mSyncQueue = syncQueue; @@ -340,6 +352,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mLogger = new SplitscreenEventLogger(); mMainExecutor = mainExecutor; mRecentTasks = recentTasks; + mLaunchAdjacentController = launchAdjacentController; + mWindowDecorViewModel = windowDecorViewModel; mDisplayController.addDisplayWindowListener(this); transitions.addHandler(this); mSplitUnsupportedToast = Toast.makeText(mContext, @@ -1733,7 +1747,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mRootTaskInfo == null || mRootTaskInfo.taskId != taskInfo.taskId) { throw new IllegalArgumentException(this + "\n Unknown task info changed: " + taskInfo); } - + mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo)); mRootTaskInfo = taskInfo; if (mSplitLayout != null && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration) @@ -1781,7 +1795,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, wct.reparent(mSideStage.mRootTaskInfo.token, mRootTaskInfo.token, true); // Make the stages adjacent to each other so they occlude what's behind them. wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token); - wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token); setRootForceTranslucent(true, wct); mSplitLayout.getInvisibleBounds(mTempRect1); wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1); @@ -1789,6 +1802,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSyncQueue.runInSync(t -> { t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.top); }); + mLaunchAdjacentController.setLaunchAdjacentRoot(mSideStage.mRootTaskInfo.token); } /** Callback when split roots have child task appeared under it, this is a little different from @@ -1818,9 +1832,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void onRootTaskVanished() { final WindowContainerTransaction wct = new WindowContainerTransaction(); - if (mRootTaskInfo != null) { - wct.clearLaunchAdjacentFlagRoot(mRootTaskInfo.token); - } + mLaunchAdjacentController.clearLaunchAdjacentRoot(); applyExitSplitScreen(null /* childrenToTop */, wct, EXIT_REASON_ROOT_TASK_VANISHED); mDisplayInsetsController.removeInsetsChangedListener(mDisplayId, mSplitLayout); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index a01eddbc9b9f..3ef4f024a8ea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -51,8 +51,10 @@ import com.android.wm.shell.common.SurfaceUtils; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.split.SplitDecorManager; import com.android.wm.shell.splitscreen.SplitScreen.StageType; +import com.android.wm.shell.windowdecor.WindowDecorViewModel; import java.io.PrintWriter; +import java.util.Optional; import java.util.function.Predicate; /** @@ -87,6 +89,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { private final SurfaceSession mSurfaceSession; private final SyncTransactionQueue mSyncQueue; private final IconProvider mIconProvider; + private final Optional<WindowDecorViewModel> mWindowDecorViewModel; protected ActivityManager.RunningTaskInfo mRootTaskInfo; protected SurfaceControl mRootLeash; @@ -98,12 +101,14 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, - SurfaceSession surfaceSession, IconProvider iconProvider) { + SurfaceSession surfaceSession, IconProvider iconProvider, + Optional<WindowDecorViewModel> windowDecorViewModel) { mContext = context; mCallbacks = callbacks; mSyncQueue = syncQueue; mSurfaceSession = surfaceSession; mIconProvider = iconProvider; + mWindowDecorViewModel = windowDecorViewModel; taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this); } @@ -202,6 +207,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { @Override @CallSuper public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { + mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo)); if (mRootTaskInfo.taskId == taskInfo.taskId) { // Inflates split decor view only when the root task is visible. if (!ENABLE_SHELL_TRANSITIONS && mRootTaskInfo.isVisible != taskInfo.isVisible) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java index 27d520d81c41..a2301b133426 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java @@ -27,6 +27,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; @@ -58,6 +59,7 @@ public class TvSplitScreenController extends SplitScreenController { private final TransactionPool mTransactionPool; private final IconProvider mIconProvider; private final Optional<RecentTasksController> mRecentTasksOptional; + private final LaunchAdjacentController mLaunchAdjacentController; private final Handler mMainHandler; private final SystemWindows mSystemWindows; @@ -77,13 +79,15 @@ public class TvSplitScreenController extends SplitScreenController { TransactionPool transactionPool, IconProvider iconProvider, Optional<RecentTasksController> recentTasks, + LaunchAdjacentController launchAdjacentController, ShellExecutor mainExecutor, Handler mainHandler, SystemWindows systemWindows) { super(context, shellInit, shellCommandHandler, shellController, shellTaskOrganizer, syncQueue, rootTDAOrganizer, displayController, displayImeController, displayInsetsController, dragAndDropController, transitions, transactionPool, - iconProvider, recentTasks, mainExecutor); + iconProvider, recentTasks, launchAdjacentController, Optional.empty(), + mainExecutor); mTaskOrganizer = shellTaskOrganizer; mSyncQueue = syncQueue; @@ -96,6 +100,7 @@ public class TvSplitScreenController extends SplitScreenController { mTransactionPool = transactionPool; mIconProvider = iconProvider; mRecentTasksOptional = recentTasks; + mLaunchAdjacentController = launchAdjacentController; mMainHandler = mainHandler; mSystemWindows = systemWindows; @@ -111,7 +116,7 @@ public class TvSplitScreenController extends SplitScreenController { mTaskOrganizer, mDisplayController, mDisplayImeController, mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider, mMainExecutor, mMainHandler, - mRecentTasksOptional, mSystemWindows); + mRecentTasksOptional, mLaunchAdjacentController, mSystemWindows); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java index 4d563fbb7f04..79476919221e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java @@ -24,6 +24,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; @@ -51,10 +52,11 @@ public class TvStageCoordinator extends StageCoordinator IconProvider iconProvider, ShellExecutor mainExecutor, Handler mainHandler, Optional<RecentTasksController> recentTasks, + LaunchAdjacentController launchAdjacentController, SystemWindows systemWindows) { super(context, displayId, syncQueue, taskOrganizer, displayController, displayImeController, displayInsetsController, transitions, transactionPool, iconProvider, - mainExecutor, recentTasks); + mainExecutor, recentTasks, launchAdjacentController, Optional.empty()); mTvSplitMenuController = new TvSplitMenuController(context, this, systemWindows, mainHandler); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index d0a361a8ecd2..d9edde16a863 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -44,6 +44,9 @@ import android.window.WindowContainerTransactionCallback; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.split.SplitScreenUtils; +import com.android.wm.shell.desktopmode.DesktopModeController; +import com.android.wm.shell.desktopmode.DesktopModeStatus; +import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.phone.PipTouchHandler; @@ -70,6 +73,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, private RecentsTransitionHandler mRecentsHandler; private StageCoordinator mSplitHandler; private final KeyguardTransitionHandler mKeyguardHandler; + private DesktopModeController mDesktopModeController; + private DesktopTasksController mDesktopTasksController; private UnfoldTransitionHandler mUnfoldHandler; private static class MixedTransition { @@ -87,8 +92,11 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, /** Keyguard exit/occlude/unocclude transition. */ static final int TYPE_KEYGUARD = 5; + /** Recents Transition while in desktop mode. */ + static final int TYPE_RECENTS_DURING_DESKTOP = 6; + /** Fuld/Unfold transition. */ - static final int TYPE_UNFOLD = 6; + static final int TYPE_UNFOLD = 7; /** The default animation for this mixed transition. */ static final int ANIM_TYPE_DEFAULT = 0; @@ -142,6 +150,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, Optional<PipTouchHandler> pipTouchHandlerOptional, Optional<RecentsTransitionHandler> recentsHandlerOptional, KeyguardTransitionHandler keyguardHandler, + Optional<DesktopModeController> desktopModeControllerOptional, + Optional<DesktopTasksController> desktopTasksControllerOptional, Optional<UnfoldTransitionHandler> unfoldHandler) { mPlayer = player; mKeyguardHandler = keyguardHandler; @@ -159,6 +169,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, if (mRecentsHandler != null) { mRecentsHandler.addMixer(this); } + mDesktopModeController = desktopModeControllerOptional.orElse(null); + mDesktopTasksController = desktopTasksControllerOptional.orElse(null); mUnfoldHandler = unfoldHandler.orElse(null); }, this); } @@ -239,7 +251,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, @Override public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) { - if (mRecentsHandler != null && mSplitHandler.isSplitScreenVisible()) { + if (mRecentsHandler != null && (mSplitHandler.isSplitScreenVisible() + || DesktopModeStatus.isActive(mPlayer.getContext()))) { return this; } return null; @@ -254,6 +267,13 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition); mixed.mLeftoversHandler = mRecentsHandler; mActiveTransitions.add(mixed); + } else if (DesktopModeStatus.isActive(mPlayer.getContext())) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while " + + "desktop mode is active, so treat it as Mixed."); + final MixedTransition mixed = new MixedTransition( + MixedTransition.TYPE_RECENTS_DURING_DESKTOP, transition); + mixed.mLeftoversHandler = mRecentsHandler; + mActiveTransitions.add(mixed); } else { throw new IllegalStateException("Accepted a recents transition but don't know how to" + " handle it"); @@ -340,6 +360,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) { return animateKeyguard(mixed, info, startTransaction, finishTransaction, finishCallback); + } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) { + return animateRecentsDuringDesktop(mixed, info, startTransaction, finishTransaction, + finishCallback); } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) { return animateUnfold(mixed, info, startTransaction, finishTransaction, finishCallback); } else { @@ -641,6 +664,30 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, return true; } + private boolean animateRecentsDuringDesktop(@NonNull final MixedTransition mixed, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + boolean consumed = mRecentsHandler.startAnimation( + mixed.mTransition, info, startTransaction, finishTransaction, finishCallback); + if (!consumed) { + return false; + } + //Sync desktop mode state (proto 1) + if (mDesktopModeController != null) { + mDesktopModeController.syncSurfaceState(info, finishTransaction); + return true; + } + //Sync desktop mode state (proto 2) + if (mDesktopTasksController != null) { + mDesktopTasksController.syncSurfaceState(info, finishTransaction); + return true; + } + + return false; + } + private boolean animateUnfold(@NonNull final MixedTransition mixed, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @@ -727,6 +774,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, finishCallback); } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) { mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); + } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) { + mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, + finishCallback); } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) { mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); } else { @@ -754,6 +804,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) { mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT); + } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) { + mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) { mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 2327d86ab618..75659960bc32 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -27,6 +27,7 @@ import static android.view.WindowManager.TRANSIT_SLEEP; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.fixScale; +import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; import static android.window.TransitionInfo.FLAG_IS_OCCLUDED; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP; @@ -161,6 +162,10 @@ public class Transitions implements RemoteCallable<Transitions>, public static final int TRANSIT_CANCEL_ENTERING_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 13; + /** Transition type to animate the toggle resize between the max and default desktop sizes. */ + public static final int TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE = + WindowManager.TRANSIT_FIRST_CUSTOM + 14; + private final WindowOrganizer mOrganizer; private final Context mContext; private final ShellExecutor mMainExecutor; @@ -724,11 +729,15 @@ public class Transitions implements RemoteCallable<Transitions>, final int changeSize = info.getChanges().size(); boolean taskChange = false; boolean transferStartingWindow = false; + int noAnimationBehindStartingWindow = 0; boolean allOccluded = changeSize > 0; for (int i = changeSize - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); taskChange |= change.getTaskInfo() != null; transferStartingWindow |= change.hasFlags(FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT); + if (change.hasAllFlags(FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_NO_ANIMATION)) { + noAnimationBehindStartingWindow++; + } if (!change.hasFlags(FLAG_IS_OCCLUDED)) { allOccluded = false; } @@ -736,9 +745,11 @@ public class Transitions implements RemoteCallable<Transitions>, // There does not need animation when: // A. Transfer starting window. Apply transfer starting window directly if there is no other // task change. Since this is an activity->activity situation, we can detect it by selecting - // transitions with only 2 changes where neither are tasks and one is a starting-window - // recipient. - if (!taskChange && transferStartingWindow && changeSize == 2 + // transitions with only 2 changes where + // 1. neither are tasks, and + // 2. one is a starting-window recipient, or all change is behind starting window. + if (!taskChange && (transferStartingWindow || noAnimationBehindStartingWindow == changeSize) + && changeSize == 2 // B. It's visibility change if the TRANSIT_TO_BACK/TO_FRONT happened when all // changes are underneath another change. || ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java index f81fc6fbea49..6bba0d1386fb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java @@ -110,7 +110,7 @@ public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator, for (int i = state.sourceSize() - 1; i >= 0; i--) { final InsetsSource source = state.sourceAt(i); if (source.getType() == WindowInsets.Type.navigationBars() - && source.insetsRoundedCornerFrame()) { + && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) { return source; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java index a4cf149cc3b5..21994a997be5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java @@ -50,11 +50,11 @@ import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldBackgroundController; +import dagger.Lazy; + import java.util.Optional; import java.util.concurrent.Executor; -import dagger.Lazy; - /** * This helper class contains logic that calculates scaling and cropping parameters * for the folding/unfolding animation. As an input it receives TaskInfo objects and @@ -149,7 +149,7 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator, for (int i = state.sourceSize() - 1; i >= 0; i--) { final InsetsSource source = state.sourceAt(i); if (source.getType() == WindowInsets.Type.navigationBars() - && source.insetsRoundedCornerFrame()) { + && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) { return source; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index 39fb7936747e..cf1692018518 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -33,11 +33,14 @@ import android.window.TransitionInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; +import androidx.annotation.Nullable; + import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; +import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.transition.Transitions; /** @@ -89,6 +92,9 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { } @Override + public void setSplitScreenController(SplitScreenController splitScreenController) {} + + @Override public boolean onTaskOpening( RunningTaskInfo taskInfo, SurfaceControl taskSurface, @@ -187,7 +193,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { final DragPositioningCallback dragPositioningCallback = new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController, - null /* disallowedAreaForEndBounds */); + 0 /* disallowedAreaForEndBoundsHeight */); final CaptionTouchEventListener touchEventListener = new CaptionTouchEventListener(taskInfo, dragPositioningCallback); windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); @@ -254,7 +260,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { * @return {@code true} if a drag is happening; or {@code false} if it is not */ @Override - public boolean handleMotionEvent(MotionEvent e) { + public boolean handleMotionEvent(@Nullable View v, MotionEvent e) { final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { return false; @@ -268,7 +274,10 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { return false; } case MotionEvent.ACTION_MOVE: { - int dragPointerIdx = e.findPointerIndex(mDragPointerId); + if (e.findPointerIndex(mDragPointerId) == -1) { + mDragPointerId = e.getPointerId(0); + } + final int dragPointerIdx = e.findPointerIndex(mDragPointerId); mDragPositioningCallback.onDragPositioningMove( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); mIsDragging = true; @@ -276,7 +285,10 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { } case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { - int dragPointerIdx = e.findPointerIndex(mDragPointerId); + if (e.findPointerIndex(mDragPointerId) == -1) { + mDragPointerId = e.getPointerId(0); + } + final int dragPointerIdx = e.findPointerIndex(mDragPointerId); mDragPositioningCallback.onDragPositioningEnd( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); final boolean wasDragging = mIsDragging; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 116af7094e13..b217bd39a446 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -113,7 +113,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL mRelayoutParams.reset(); mRelayoutParams.mRunningTaskInfo = taskInfo; mRelayoutParams.mLayoutResId = R.layout.caption_window_decor; - mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height; + mRelayoutParams.mCaptionHeightId = getCaptionHeightId(); mRelayoutParams.mShadowRadiusId = shadowRadiusID; mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; @@ -143,6 +143,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL mHandler, mChoreographer, mDisplay.getDisplayId(), + 0 /* taskCornerRadius */, mDecorationContainerSurface, mDragPositioningCallback); } @@ -221,4 +222,9 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL closeDragResizeListener(); super.close(); } + + @Override + int getCaptionHeightId() { + return R.dimen.freeform_decor_caption_height; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 9fd57d7e1201..7245bc91cfca 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -19,6 +19,8 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; @@ -42,6 +44,7 @@ import android.os.IBinder; import android.os.Looper; import android.util.SparseArray; import android.view.Choreographer; +import android.view.GestureDetector; import android.view.InputChannel; import android.view.InputEvent; import android.view.InputEventReceiver; @@ -53,6 +56,7 @@ import android.view.View; import android.view.WindowManager; import android.window.TransitionInfo; import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -106,7 +110,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final Supplier<SurfaceControl.Transaction> mTransactionFactory; private final Transitions mTransitions; - private Optional<SplitScreenController> mSplitScreenController; + private SplitScreenController mSplitScreenController; private ValueAnimator mDragToDesktopValueAnimator; private final Rect mDragToDesktopAnimationStartBounds = new Rect(); @@ -121,8 +125,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { SyncTransactionQueue syncQueue, Transitions transitions, Optional<DesktopModeController> desktopModeController, - Optional<DesktopTasksController> desktopTasksController, - Optional<SplitScreenController> splitScreenController) { + Optional<DesktopTasksController> desktopTasksController) { this( context, mainHandler, @@ -133,7 +136,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { transitions, desktopModeController, desktopTasksController, - splitScreenController, new DesktopModeWindowDecoration.Factory(), new InputMonitorFactory(), SurfaceControl.Transaction::new); @@ -150,7 +152,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { Transitions transitions, Optional<DesktopModeController> desktopModeController, Optional<DesktopTasksController> desktopTasksController, - Optional<SplitScreenController> splitScreenController, DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory, InputMonitorFactory inputMonitorFactory, Supplier<SurfaceControl.Transaction> transactionFactory) { @@ -160,7 +161,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class); mTaskOrganizer = taskOrganizer; mDisplayController = displayController; - mSplitScreenController = splitScreenController; mSyncQueue = syncQueue; mTransitions = transitions; mDesktopModeController = desktopModeController; @@ -177,6 +177,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } @Override + public void setSplitScreenController(SplitScreenController splitScreenController) { + mSplitScreenController = splitScreenController; + } + + @Override public boolean onTaskOpening( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, @@ -193,7 +198,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change) { if (change.getMode() == WindowManager.TRANSIT_CHANGE - && (info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE)) { + && (info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE + || info.getType() == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE + || info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE + || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE)) { mWindowDecorByTaskId.get(change.getTaskInfo().taskId) .addTransitionPausingRelayout(transition); } @@ -236,7 +244,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId); - if (!shouldShowWindowDecor(taskInfo)) { if (decoration != null) { destroyWindowDecoration(taskInfo); @@ -275,15 +282,17 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } } - private class DesktopModeTouchEventListener implements - View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler { + private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener + implements View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler { private final int mTaskId; private final WindowContainerToken mTaskToken; private final DragPositioningCallback mDragPositioningCallback; private final DragDetector mDragDetector; + private final GestureDetector mGestureDetector; private boolean mIsDragging; + private boolean mShouldClick; private int mDragPointerId = -1; private DesktopModeTouchEventListener( @@ -293,6 +302,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mTaskToken = taskInfo.token; mDragPositioningCallback = dragPositioningCallback; mDragDetector = new DragDetector(this); + mGestureDetector = new GestureDetector(mContext, this); } @Override @@ -301,14 +311,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final int id = v.getId(); if (id == R.id.close_window || id == R.id.close_button) { mTaskOperations.closeTask(mTaskToken); - if (mSplitScreenController.isPresent() - && mSplitScreenController.get().isSplitScreenVisible()) { - int remainingTaskPosition = mTaskId == mSplitScreenController.get() + if (mSplitScreenController != null + && mSplitScreenController.isSplitScreenVisible()) { + int remainingTaskPosition = mTaskId == mSplitScreenController .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT).taskId ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT; - ActivityManager.RunningTaskInfo remainingTask = mSplitScreenController.get() + ActivityManager.RunningTaskInfo remainingTask = mSplitScreenController .getTaskInfo(remainingTaskPosition); - mSplitScreenController.get().moveTaskToFullscreen(remainingTask.taskId); + mSplitScreenController.moveTaskToFullscreen(remainingTask.taskId); } } else if (id == R.id.back_button) { mTaskOperations.injectBackKey(); @@ -321,7 +331,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } } else if (id == R.id.desktop_button) { mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true)); - mDesktopTasksController.ifPresent(c -> c.moveToDesktop(mTaskId)); + if (mDesktopTasksController.isPresent()) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + // App sometimes draws before the insets from WindowDecoration#relayout have + // been added, so they must be added here + mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct); + mDesktopTasksController.get().moveToDesktop(mTaskId, wct); + } decoration.closeHandleMenu(); } else if (id == R.id.fullscreen_button) { mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false)); @@ -347,7 +363,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return false; } moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId)); - return mDragDetector.onMotionEvent(e); + return mDragDetector.onMotionEvent(v, e); } private void moveTaskToFront(RunningTaskInfo taskInfo) { @@ -362,7 +378,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { * @return {@code true} if the motion event is handled. */ @Override - public boolean handleMotionEvent(MotionEvent e) { + public boolean handleMotionEvent(@Nullable View v, MotionEvent e) { final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); if (DesktopModeStatus.isProto2Enabled() && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { @@ -373,6 +389,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { == WINDOWING_MODE_FULLSCREEN) { return false; } + if (mGestureDetector.onTouchEvent(e)) { + return true; + } switch (e.getActionMasked()) { case MotionEvent.ACTION_DOWN: { mDragPointerId = e.getPointerId(0); @@ -380,21 +399,38 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { 0 /* ctrlType */, e.getRawX(0), e.getRawY(0)); mIsDragging = false; - return false; + mShouldClick = true; + return true; } case MotionEvent.ACTION_MOVE: { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); + if (e.findPointerIndex(mDragPointerId) == -1) { + mDragPointerId = e.getPointerId(0); + } final int dragPointerIdx = e.findPointerIndex(mDragPointerId); mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo, decoration.mTaskSurface, e.getRawY(dragPointerIdx))); mDragPositioningCallback.onDragPositioningMove( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); mIsDragging = true; + mShouldClick = false; return true; } case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { + final boolean wasDragging = mIsDragging; + if (!wasDragging) { + if (mShouldClick && v != null) { + v.performClick(); + mShouldClick = false; + return true; + } + return false; + } + if (e.findPointerIndex(mDragPointerId) == -1) { + mDragPointerId = e.getPointerId(0); + } final int dragPointerIdx = e.findPointerIndex(mDragPointerId); // Position of the task is calculated by subtracting the raw location of the // motion event (the location of the motion relative to the display) by the @@ -405,14 +441,22 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDragPositioningCallback.onDragPositioningEnd( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo, - position)); - final boolean wasDragging = mIsDragging; + position, e.getRawY(), mWindowDecorByTaskId.get(mTaskId))); mIsDragging = false; - return wasDragging; + return true; } } return true; } + + @Override + public boolean onDoubleTap(@NonNull MotionEvent e) { + final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); + mDesktopTasksController.ifPresent(c -> { + c.toggleDesktopTaskSize(taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId)); + }); + return true; + } } // InputEventReceiver to listen for touch input outside of caption bounds @@ -540,9 +584,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { relevantDecor.mTaskInfo.configuration.windowConfiguration.getBounds()); boolean dragFromStatusBarAllowed = false; if (DesktopModeStatus.isProto2Enabled()) { - // In proto2 any full screen task can be dragged to freeform - dragFromStatusBarAllowed = relevantDecor.mTaskInfo.getWindowingMode() - == WINDOWING_MODE_FULLSCREEN; + // In proto2 any full screen or multi-window task can be dragged to + // freeform. + final int windowingMode = relevantDecor.mTaskInfo.getWindowingMode(); + dragFromStatusBarAllowed = windowingMode == WINDOWING_MODE_FULLSCREEN + || windowingMode == WINDOWING_MODE_MULTI_WINDOW; } if (dragFromStatusBarAllowed && relevantDecor.checkTouchEventInHandle(ev)) { @@ -571,9 +617,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return; } else if (mDragToDesktopAnimationStarted) { Point position = new Point((int) ev.getX(), (int) ev.getY()); + relevantDecor.incrementRelayoutBlock(); mDesktopTasksController.ifPresent( - c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo, - position)); + c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo, position)); mDragToDesktopAnimationStarted = false; return; } @@ -699,8 +745,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { @Nullable private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) { - if (mSplitScreenController.isPresent() - && mSplitScreenController.get().isSplitScreenVisible()) { + if (mSplitScreenController != null && mSplitScreenController.isSplitScreenVisible()) { // We can't look at focused task here as only one task will have focus. return getSplitScreenDecor(ev); } else { @@ -711,9 +756,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { @Nullable private DesktopModeWindowDecoration getSplitScreenDecor(MotionEvent ev) { ActivityManager.RunningTaskInfo topOrLeftTask = - mSplitScreenController.get().getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT); + mSplitScreenController.getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT); ActivityManager.RunningTaskInfo bottomOrRightTask = - mSplitScreenController.get().getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT); + mSplitScreenController.getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT); if (topOrLeftTask != null && topOrLeftTask.getConfiguration() .windowConfiguration.getBounds().contains((int) ev.getX(), (int) ev.getY())) { return mWindowDecorByTaskId.get(topOrLeftTask.taskId); @@ -765,11 +810,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) { if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true; - if (mSplitScreenController.isPresent() - && mSplitScreenController.get().isTaskRootOrStageRoot(taskInfo.taskId)) { + if (mSplitScreenController != null + && mSplitScreenController.isTaskRootOrStageRoot(taskInfo.taskId)) { return false; } return DesktopModeStatus.isProto2Enabled() + && taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD && mDisplayController.getDisplayContext(taskInfo.displayId) .getResources().getConfiguration().smallestScreenWidthDp >= 600; @@ -796,9 +842,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mMainChoreographer, mSyncQueue); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); + windowDecoration.createResizeVeil(); final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback( - windowDecoration, taskInfo); + windowDecoration); final DesktopModeTouchEventListener touchEventListener = new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback); @@ -811,20 +858,17 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { incrementEventReceiverTasks(taskInfo.displayId); } private DragPositioningCallback createDragPositioningCallback( - @NonNull DesktopModeWindowDecoration windowDecoration, - @NonNull RunningTaskInfo taskInfo) { - final int screenWidth = mDisplayController.getDisplayLayout(taskInfo.displayId).width(); - final Rect disallowedAreaForEndBounds = new Rect(0, 0, screenWidth, - getStatusBarHeight(taskInfo.displayId)); + @NonNull DesktopModeWindowDecoration windowDecoration) { + final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize( + R.dimen.desktop_mode_transition_area_height); if (!DesktopModeStatus.isVeiledResizeEnabled()) { return new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, - mDisplayController, disallowedAreaForEndBounds, mDragStartListener, - mTransactionFactory); + mDisplayController, mDragStartListener, mTransactionFactory, + transitionAreaHeight); } else { - windowDecoration.createResizeVeil(); return new VeiledResizeTaskPositioner(mTaskOrganizer, windowDecoration, - mDisplayController, disallowedAreaForEndBounds, mDragStartListener, - mTransitions); + mDisplayController, mDragStartListener, mTransitions, + transitionAreaHeight); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index ce11b2604559..bc89385a0d13 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -23,7 +23,6 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; -import android.content.res.TypedArray; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; @@ -39,6 +38,7 @@ import android.view.View; import android.view.ViewConfiguration; import android.window.WindowContainerTransaction; +import com.android.internal.policy.ScreenDecorationsUtils; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; @@ -178,15 +178,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mRelayoutParams.reset(); mRelayoutParams.mRunningTaskInfo = taskInfo; mRelayoutParams.mLayoutResId = windowDecorLayoutId; - mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height; + mRelayoutParams.mCaptionHeightId = getCaptionHeightId(); mRelayoutParams.mShadowRadiusId = shadowRadiusID; mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; - final TypedArray ta = mContext.obtainStyledAttributes( - new int[]{android.R.attr.dialogCornerRadius}); - mRelayoutParams.mCornerRadius = ta.getDimensionPixelSize(0, 0); - ta.recycle(); - + mRelayoutParams.mCornerRadius = + (int) ScreenDecorationsUtils.getWindowCornerRadius(mContext); relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo @@ -235,6 +232,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mHandler, mChoreographer, mDisplay.getDisplayId(), + mRelayoutParams.mCornerRadius, mDecorationContainerSurface, mDragPositioningCallback); } @@ -294,23 +292,37 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } /** - * Fade in the resize veil + * Show the resize veil. */ - void showResizeVeil(Rect taskBounds) { + public void showResizeVeil(Rect taskBounds) { mResizeVeil.showVeil(mTaskSurface, taskBounds); } /** + * Show the resize veil. + */ + public void showResizeVeil(SurfaceControl.Transaction tx, Rect taskBounds) { + mResizeVeil.showVeil(tx, mTaskSurface, taskBounds, false /* fadeIn */); + } + + /** * Set new bounds for the resize veil */ - void updateResizeVeil(Rect newBounds) { + public void updateResizeVeil(Rect newBounds) { mResizeVeil.updateResizeVeil(newBounds); } /** + * Set new bounds for the resize veil + */ + public void updateResizeVeil(SurfaceControl.Transaction tx, Rect newBounds) { + mResizeVeil.updateResizeVeil(tx, newBounds); + } + + /** * Fade the resize veil out. */ - void hideResizeVeil() { + public void hideResizeVeil() { mResizeVeil.hideVeil(); } @@ -479,6 +491,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } } + @Override + int getCaptionHeightId() { + return R.dimen.freeform_decor_caption_height; + } + /** * Add transition to mTransitionsPausingRelayout */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java index 65b5a7a17afe..da268988bac7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java @@ -24,6 +24,9 @@ import static android.view.MotionEvent.ACTION_UP; import android.graphics.PointF; import android.view.MotionEvent; +import android.view.View; + +import androidx.annotation.Nullable; /** * A detector for touch inputs that differentiates between drag and click inputs. It receives a flow @@ -54,14 +57,24 @@ class DragDetector { * * @return the result returned by {@link #mEventHandler}, or the result when * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed - */ + */ boolean onMotionEvent(MotionEvent ev) { + return onMotionEvent(null /* view */, ev); + } + + /** + * The receiver of the {@link MotionEvent} flow. + * + * @return the result returned by {@link #mEventHandler}, or the result when + * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed + */ + boolean onMotionEvent(View v, MotionEvent ev) { final boolean isTouchScreen = (ev.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN; if (!isTouchScreen) { // Only touches generate noisy moves, so mouse/trackpad events don't need to filtered // to take the slop threshold into consideration. - return mEventHandler.handleMotionEvent(ev); + return mEventHandler.handleMotionEvent(v, ev); } switch (ev.getActionMasked()) { case ACTION_DOWN: { @@ -69,12 +82,15 @@ class DragDetector { float rawX = ev.getRawX(0); float rawY = ev.getRawY(0); mInputDownPoint.set(rawX, rawY); - mResultOfDownAction = mEventHandler.handleMotionEvent(ev); + mResultOfDownAction = mEventHandler.handleMotionEvent(v, ev); return mResultOfDownAction; } case ACTION_MOVE: { + if (ev.findPointerIndex(mDragPointerId) == -1) { + mDragPointerId = ev.getPointerId(0); + } + final int dragPointerIndex = ev.findPointerIndex(mDragPointerId); if (!mIsDragEvent) { - int dragPointerIndex = ev.findPointerIndex(mDragPointerId); float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x; float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y; // Touches generate noisy moves, so only once the move is past the touch @@ -84,7 +100,7 @@ class DragDetector { // The event handler should only be notified about 'move' events if a drag has been // detected. if (mIsDragEvent) { - return mEventHandler.handleMotionEvent(ev); + return mEventHandler.handleMotionEvent(v, ev); } else { return mResultOfDownAction; } @@ -92,10 +108,10 @@ class DragDetector { case ACTION_UP: case ACTION_CANCEL: { resetState(); - return mEventHandler.handleMotionEvent(ev); + return mEventHandler.handleMotionEvent(v, ev); } default: - return mEventHandler.handleMotionEvent(ev); + return mEventHandler.handleMotionEvent(v, ev); } } @@ -111,6 +127,6 @@ class DragDetector { } interface MotionEventHandler { - boolean handleMotionEvent(MotionEvent ev); + boolean handleMotionEvent(@Nullable View v, MotionEvent ev); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java index 09e29bcbcf9f..e32bd42acf74 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java @@ -83,8 +83,6 @@ public class DragPositioningCallbackUtility { // Make sure the new resizing destination in any direction falls within the stable bounds. // If not, set the bounds back to the old location that was valid to avoid conflicts with // some regions such as the gesture area. - displayController.getDisplayLayout(windowDecoration.mDisplay.getDisplayId()) - .getStableBounds(stableBounds); if ((ctrlType & CTRL_TYPE_LEFT) != 0) { final int candidateLeft = repositionTaskBounds.left + (int) delta.x; repositionTaskBounds.left = (candidateLeft > stableBounds.left) @@ -136,7 +134,7 @@ public class DragPositioningCallbackUtility { repositionTaskBounds.top); } - static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, + private static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, PointF repositionStartPoint, float x, float y) { final float deltaX = x - repositionStartPoint.x; final float deltaY = y - repositionStartPoint.y; @@ -145,6 +143,23 @@ public class DragPositioningCallbackUtility { } /** + * Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If + * the bounds are outside of the stable bounds, they are shifted to place task at the top of the + * stable bounds. + */ + static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, Rect stableBounds, + PointF repositionStartPoint, float x, float y) { + updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint, + x, y); + + // If task is outside of stable bounds (in the status bar area), shift the task down. + if (stableBounds.top > repositionTaskBounds.top) { + final int yShift = stableBounds.top - repositionTaskBounds.top; + repositionTaskBounds.offset(0, yShift); + } + } + + /** * Apply a bounds change to a task. * @param windowDecoration decor of task we are changing bounds for * @param taskBounds new bounds of this task diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index 287d86187288..e5fc66afbdfc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -42,6 +42,7 @@ import android.view.InputEventReceiver; import android.view.MotionEvent; import android.view.PointerIcon; import android.view.SurfaceControl; +import android.view.View; import android.view.ViewConfiguration; import android.view.WindowManagerGlobal; @@ -55,7 +56,6 @@ import com.android.internal.view.BaseIWindow; */ class DragResizeInputListener implements AutoCloseable { private static final String TAG = "DragResizeInputListener"; - private final IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession(); private final Handler mHandler; private final Choreographer mChoreographer; @@ -73,6 +73,7 @@ class DragResizeInputListener implements AutoCloseable { private int mTaskHeight; private int mResizeHandleThickness; private int mCornerSize; + private int mTaskCornerRadius; private Rect mLeftTopCornerBounds; private Rect mRightTopCornerBounds; @@ -87,12 +88,14 @@ class DragResizeInputListener implements AutoCloseable { Handler handler, Choreographer choreographer, int displayId, + int taskCornerRadius, SurfaceControl decorationSurface, DragPositioningCallback callback) { mInputManager = context.getSystemService(InputManager.class); mHandler = handler; mChoreographer = choreographer; mDisplayId = displayId; + mTaskCornerRadius = taskCornerRadius; mDecorationSurface = decorationSurface; // Use a fake window as the backing surface is a container layer and we don't want to create // a buffer layer for it so we can't use ViewRootImpl. @@ -126,12 +129,7 @@ class DragResizeInputListener implements AutoCloseable { } /** - * Updates geometry of this drag resize handler. Needs to be called every time there is a size - * change to notify the input event receiver it's ready to take the next input event. Otherwise - * it'll keep batching move events and the drag resize process is stalled. - * - * This is also used to update the touch regions of this handler every event dispatched here is - * a potential resize request. + * Updates the geometry (the touch region) of this drag resize handler. * * @param taskWidth The width of the task. * @param taskHeight The height of the task. @@ -303,7 +301,7 @@ class DragResizeInputListener implements AutoCloseable { } @Override - public boolean handleMotionEvent(MotionEvent e) { + public boolean handleMotionEvent(View v, MotionEvent e) { boolean result = false; // Check if this is a touch event vs mouse event. // Touch events are tracked in four corners. Other events are tracked in resize edges. @@ -383,19 +381,64 @@ class DragResizeInputListener implements AutoCloseable { @DragPositioningCallback.CtrlType private int calculateResizeHandlesCtrlType(float x, float y) { int ctrlType = 0; - if (x < 0) { + // mTaskCornerRadius is only used in comparing with corner regions. Comparisons with + // sides will use the bounds specified in setGeometry and not go into task bounds. + if (x < mTaskCornerRadius) { ctrlType |= CTRL_TYPE_LEFT; } - if (x > mTaskWidth) { + if (x > mTaskWidth - mTaskCornerRadius) { ctrlType |= CTRL_TYPE_RIGHT; } - if (y < 0) { + if (y < mTaskCornerRadius) { ctrlType |= CTRL_TYPE_TOP; } - if (y > mTaskHeight) { + if (y > mTaskHeight - mTaskCornerRadius) { ctrlType |= CTRL_TYPE_BOTTOM; } - return ctrlType; + return checkDistanceFromCenter(ctrlType, x, y); + } + + // If corner input is not within appropriate distance of corner radius, do not use it. + // If input is not on a corner or is within valid distance, return ctrlType. + @DragPositioningCallback.CtrlType + private int checkDistanceFromCenter(@DragPositioningCallback.CtrlType int ctrlType, + float x, float y) { + int centerX; + int centerY; + + // Determine center of rounded corner circle; this is simply the corner if radius is 0. + switch (ctrlType) { + case CTRL_TYPE_LEFT | CTRL_TYPE_TOP: { + centerX = mTaskCornerRadius; + centerY = mTaskCornerRadius; + break; + } + case CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM: { + centerX = mTaskCornerRadius; + centerY = mTaskHeight - mTaskCornerRadius; + break; + } + case CTRL_TYPE_RIGHT | CTRL_TYPE_TOP: { + centerX = mTaskWidth - mTaskCornerRadius; + centerY = mTaskCornerRadius; + break; + } + case CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM: { + centerX = mTaskWidth - mTaskCornerRadius; + centerY = mTaskHeight - mTaskCornerRadius; + break; + } + default: { + return ctrlType; + } + } + double distanceFromCenter = Math.hypot(x - centerX, y - centerY); + + if (distanceFromCenter < mTaskCornerRadius + mResizeHandleThickness + && distanceFromCenter >= mTaskCornerRadius) { + return ctrlType; + } + return 0; } @DragPositioningCallback.CtrlType diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java index 9082323452c9..917abf5524a4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java @@ -21,8 +21,6 @@ import android.graphics.Rect; import android.view.SurfaceControl; import android.window.WindowContainerTransaction; -import androidx.annotation.Nullable; - import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; @@ -42,28 +40,31 @@ class FluidResizeTaskPositioner implements DragPositioningCallback { private final Rect mTaskBoundsAtDragStart = new Rect(); private final PointF mRepositionStartPoint = new PointF(); private final Rect mRepositionTaskBounds = new Rect(); - // If a task move (not resize) finishes in this region, the positioner will not attempt to + // If a task move (not resize) finishes with the positions y less than this value, do not // finalize the bounds there using WCT#setBounds - private final Rect mDisallowedAreaForEndBounds; + private final int mDisallowedAreaForEndBoundsHeight; private boolean mHasDragResized; private int mCtrlType; FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration, - DisplayController displayController, @Nullable Rect disallowedAreaForEndBounds) { - this(taskOrganizer, windowDecoration, displayController, disallowedAreaForEndBounds, - dragStartListener -> {}, SurfaceControl.Transaction::new); + DisplayController displayController, int disallowedAreaForEndBoundsHeight) { + this(taskOrganizer, windowDecoration, displayController, dragStartListener -> {}, + SurfaceControl.Transaction::new, disallowedAreaForEndBoundsHeight); } FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration, - DisplayController displayController, @Nullable Rect disallowedAreaForEndBounds, + DisplayController displayController, DragPositioningCallbackUtility.DragStartListener dragStartListener, - Supplier<SurfaceControl.Transaction> supplier) { + Supplier<SurfaceControl.Transaction> supplier, + int disallowedAreaForEndBoundsHeight) { mTaskOrganizer = taskOrganizer; mWindowDecoration = windowDecoration; mDisplayController = displayController; - mDisallowedAreaForEndBounds = new Rect(disallowedAreaForEndBounds); mDragStartListener = dragStartListener; mTransactionSupplier = supplier; + mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight; + mDisplayController.getDisplayLayout(windowDecoration.mDisplay.getDisplayId()) + .getStableBounds(mStableBounds); } @Override @@ -121,10 +122,10 @@ class FluidResizeTaskPositioner implements DragPositioningCallback { } mTaskOrganizer.applyTransaction(wct); } else if (mCtrlType == CTRL_TYPE_UNDEFINED - && !mDisallowedAreaForEndBounds.contains((int) x, (int) y)) { + && y > mDisallowedAreaForEndBoundsHeight) { final WindowContainerTransaction wct = new WindowContainerTransaction(); - DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds, - mTaskBoundsAtDragStart, mRepositionStartPoint, x, y); + DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds, + mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y); wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds); mTaskOrganizer.applyTransaction(wct); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java index 82771095cd82..bfce72bcadf0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java @@ -102,11 +102,17 @@ public class ResizeVeil { } /** - * Animate veil's alpha to 1, fading it in. + * Shows the veil surface/view. + * + * @param t the transaction to apply in sync with the veil draw + * @param parentSurface the surface that the veil should be a child of + * @param taskBounds the bounds of the task that owns the veil + * @param fadeIn if true, the veil will fade-in with an animation, if false, it will be shown + * immediately */ - public void showVeil(SurfaceControl parentSurface, Rect taskBounds) { + public void showVeil(SurfaceControl.Transaction t, SurfaceControl parentSurface, + Rect taskBounds, boolean fadeIn) { // Parent surface can change, ensure it is up to date. - SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); if (!parentSurface.equals(mParentSurface)) { t.reparent(mVeilSurface, parentSurface); mParentSurface = parentSurface; @@ -115,22 +121,36 @@ public class ResizeVeil { int backgroundColorId = getBackgroundColorId(); mViewHost.getView().setBackgroundColor(mContext.getColor(backgroundColorId)); - final ValueAnimator animator = new ValueAnimator(); - animator.setFloatValues(0f, 1f); - animator.setDuration(RESIZE_ALPHA_DURATION); - animator.addUpdateListener(animation -> { - t.setAlpha(mVeilSurface, animator.getAnimatedFraction()); - t.apply(); - }); - relayout(taskBounds, t); - t.show(mVeilSurface) - .addTransactionCommittedListener(mContext.getMainExecutor(), () -> animator.start()) - .setAlpha(mVeilSurface, 0); + if (fadeIn) { + final ValueAnimator animator = new ValueAnimator(); + animator.setFloatValues(0f, 1f); + animator.setDuration(RESIZE_ALPHA_DURATION); + animator.addUpdateListener(animation -> { + t.setAlpha(mVeilSurface, animator.getAnimatedFraction()); + t.apply(); + }); + + t.show(mVeilSurface) + .addTransactionCommittedListener( + mContext.getMainExecutor(), () -> animator.start()) + .setAlpha(mVeilSurface, 0); + } else { + // Show the veil immediately at full opacity. + t.show(mVeilSurface).setAlpha(mVeilSurface, 1); + } mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t); } /** + * Animate veil's alpha to 1, fading it in. + */ + public void showVeil(SurfaceControl parentSurface, Rect taskBounds) { + SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); + showVeil(t, parentSurface, taskBounds, true /* fadeIn */); + } + + /** * Update veil bounds to match bounds changes. * @param newBounds bounds to update veil to. */ @@ -147,6 +167,16 @@ public class ResizeVeil { */ public void updateResizeVeil(Rect newBounds) { SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); + updateResizeVeil(t, newBounds); + } + + /** + * Calls relayout to update task and veil bounds. + * + * @param t a transaction to be applied in sync with the veil draw. + * @param newBounds bounds to update veil to. + */ + public void updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds) { relayout(newBounds, t); mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java index 58c78e6a5b9f..bf3ff3fa83ef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java @@ -53,33 +53,35 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, private final Rect mTaskBoundsAtDragStart = new Rect(); private final PointF mRepositionStartPoint = new PointF(); private final Rect mRepositionTaskBounds = new Rect(); - // If a task move (not resize) finishes in this region, the positioner will not attempt to + // If a task move (not resize) finishes with the positions y less than this value, do not // finalize the bounds there using WCT#setBounds - private final Rect mDisallowedAreaForEndBounds = new Rect(); + private final int mDisallowedAreaForEndBoundsHeight; private final Supplier<SurfaceControl.Transaction> mTransactionSupplier; private int mCtrlType; public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, DesktopModeWindowDecoration windowDecoration, DisplayController displayController, - Rect disallowedAreaForEndBounds, DragPositioningCallbackUtility.DragStartListener dragStartListener, - Transitions transitions) { - this(taskOrganizer, windowDecoration, displayController, disallowedAreaForEndBounds, - dragStartListener, SurfaceControl.Transaction::new, transitions); + Transitions transitions, + int disallowedAreaForEndBoundsHeight) { + this(taskOrganizer, windowDecoration, displayController, dragStartListener, + SurfaceControl.Transaction::new, transitions, disallowedAreaForEndBoundsHeight); } public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, DesktopModeWindowDecoration windowDecoration, DisplayController displayController, - Rect disallowedAreaForEndBounds, DragPositioningCallbackUtility.DragStartListener dragStartListener, - Supplier<SurfaceControl.Transaction> supplier, Transitions transitions) { + Supplier<SurfaceControl.Transaction> supplier, Transitions transitions, + int disallowedAreaForEndBoundsHeight) { mTaskOrganizer = taskOrganizer; mDesktopWindowDecoration = windowDecoration; mDisplayController = displayController; mDragStartListener = dragStartListener; - mDisallowedAreaForEndBounds.set(disallowedAreaForEndBounds); mTransactionSupplier = supplier; mTransitions = transitions; + mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight; + mDisplayController.getDisplayLayout(windowDecoration.mDisplay.getDisplayId()) + .getStableBounds(mStableBounds); } @Override @@ -110,8 +112,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, } else if (mCtrlType == CTRL_TYPE_UNDEFINED) { final SurfaceControl.Transaction t = mTransactionSupplier.get(); DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration, - mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, - x, y); + mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y); t.apply(); } } @@ -138,9 +139,9 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, // won't be called. mDesktopWindowDecoration.hideResizeVeil(); } - } else if (!mDisallowedAreaForEndBounds.contains((int) x, (int) y)) { - DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds, - mTaskBoundsAtDragStart, mRepositionStartPoint, x, y); + } else if (y > mDisallowedAreaForEndBoundsHeight) { + DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds, + mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y); DragPositioningCallbackUtility.applyTaskBoundsChange(new WindowContainerTransaction(), mDesktopWindowDecoration, mRepositionTaskBounds, mTaskOrganizer); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java index 9f03d9aec166..ae1a3d914be3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java @@ -22,6 +22,7 @@ import android.view.SurfaceControl; import android.window.TransitionInfo; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; +import com.android.wm.shell.splitscreen.SplitScreenController; /** * The interface used by some {@link com.android.wm.shell.ShellTaskOrganizer.TaskListener} to help @@ -39,6 +40,11 @@ public interface WindowDecorViewModel { void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter); /** + * Sets the {@link SplitScreenController} if available. + */ + void setSplitScreenController(SplitScreenController splitScreenController); + + /** * Creates a window decoration for the given task. Can be {@code null} for Fullscreen tasks but * not Freeform ones. * diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index ac5ff2075901..ddc7fef0599f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -237,7 +237,12 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId); final int captionWidth = taskBounds.width(); + + // We use mDecorationContainerSurface to define input window for task resizing; by layering + // it in front of mCaptionContainerSurface, we can allow it to handle input prior to + // caption view itself, treating corner inputs as resize events rather than repositioning. startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight) + .setLayer(mCaptionContainerSurface, -1) .show(mCaptionContainerSurface); if (ViewRootImpl.CAPTION_ON_SHELL) { @@ -248,6 +253,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight + params.mCaptionY; wct.addInsetsSource(mTaskInfo.token, mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect); + wct.addInsetsSource(mTaskInfo.token, + mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(), + mCaptionInsetsRect); } else { startT.hide(mCaptionContainerSurface); } @@ -264,6 +272,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .setColor(mTaskSurface, mTmpColor) .show(mTaskSurface); finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y) + .setShadowRadius(mTaskSurface, shadowRadius) .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight); if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { startT.setCornerRadius(mTaskSurface, params.mCornerRadius); @@ -301,6 +310,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } } + int getCaptionHeightId() { + return Resources.ID_NULL; + } + /** * Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or * registers {@link #mOnDisplaysChangedListener} if it doesn't. @@ -345,6 +358,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> final WindowContainerTransaction wct = mWindowContainerTransactionSupplier.get(); wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */, WindowInsets.Type.captionBar()); + wct.removeInsetsSource(mTaskInfo.token, + mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures()); mTaskOrganizer.applyTransaction(wct); } @@ -413,6 +428,21 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mSurfaceControlTransactionSupplier); } + /** + * Adds caption inset source to a WCT + */ + public void addCaptionInset(WindowContainerTransaction wct) { + final int captionHeightId = getCaptionHeightId(); + if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL) { + return; + } + + final int captionHeight = loadDimensionPixelSize(mContext.getResources(), captionHeightId); + final Rect captionInsets = new Rect(0, 0, 0, captionHeight); + wct.addInsetsSource(mTaskInfo.token, mOwner, 0 /* index */, WindowInsets.Type.captionBar(), + captionInsets); + } + static class RelayoutParams { RunningTaskInfo mRunningTaskInfo; int mLayoutResId; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt index 514ea52cb8ae..d293cf73a0f7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt @@ -1,28 +1,35 @@ package com.android.wm.shell.windowdecor.viewholder import android.app.ActivityManager.RunningTaskInfo +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.content.Context import android.graphics.Color import android.view.View +import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS /** * Encapsulates the root [View] of a window decoration and its children to facilitate looking up * children (via findViewById) and updating to the latest data from [RunningTaskInfo]. */ internal abstract class DesktopModeWindowDecorationViewHolder(rootView: View) { - val context: Context = rootView.context + val context: Context = rootView.context - /** - * A signal to the view holder that new data is available and that the views should be updated - * to reflect it. - */ - abstract fun bindData(taskInfo: RunningTaskInfo) + /** + * A signal to the view holder that new data is available and that the views should be updated to + * reflect it. + */ + abstract fun bindData(taskInfo: RunningTaskInfo) - /** - * Whether the caption items should use the 'light' color variant so that there's good contrast - * with the caption background color. - */ - protected fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean { - return Color.valueOf(taskInfo.taskDescription.statusBarColor).luminance() < 0.5 + /** + * Whether the caption items should use the 'light' color variant so that there's good contrast + * with the caption background color. + */ + protected fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean { + return if (Color.alpha(taskInfo.taskDescription.statusBarColor) != 0 && + taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) { + Color.valueOf(taskInfo.taskDescription.statusBarColor).luminance() < 0.5 + } else { + taskInfo.taskDescription.statusBarAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0 } + } } diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp index b6696c70dbb1..e382a0f0aeda 100644 --- a/libs/WindowManager/Shell/tests/flicker/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/Android.bp @@ -23,14 +23,33 @@ package { default_applicable_licenses: ["frameworks_base_license"], } -android_test { - name: "WMShellFlickerTests", +filegroup { + name: "WMShellFlickerTestsBase-src", + srcs: ["src/com/android/wm/shell/flicker/*.kt"], +} + +filegroup { + name: "WMShellFlickerTestsBubbles-src", + srcs: ["src/**/bubble/*.kt"], +} + +filegroup { + name: "WMShellFlickerTestsPip-src", + srcs: ["src/**/pip/*.kt"], +} + +filegroup { + name: "WMShellFlickerTestsSplitScreen-src", srcs: [ - "src/**/*.java", - "src/**/*.kt", + "src/**/splitscreen/*.kt", + "src/**/splitscreen/benchmark/*.kt", ], - manifest: "AndroidManifest.xml", - test_config: "AndroidTest.xml", +} + +java_defaults { + name: "WMShellFlickerTestsDefault", + manifest: "manifests/AndroidManifest.xml", + test_config_template: "AndroidTestTemplate.xml", platform_apis: true, certificate: "platform", optimize: { @@ -40,16 +59,70 @@ android_test { libs: ["android.test.runner"], static_libs: [ "androidx.test.ext.junit", + "flickertestapplib", "flickerlib", - "flickerlib-apphelpers", "flickerlib-helpers", - "truth-prebuilt", - "app-helpers-core", + "platform-test-annotations", + "wm-flicker-common-app-helpers", + "wm-flicker-common-assertions", "launcher-helper-lib", "launcher-aosp-tapl", - "wm-flicker-common-assertions", - "wm-flicker-common-app-helpers", - "platform-test-annotations", - "flickertestapplib", + ], + data: [ + ":FlickerTestApp", + "trace_config/*", + ], +} + +android_test { + name: "WMShellFlickerTestsOther", + defaults: ["WMShellFlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestOther.xml"], + package_name: "com.android.wm.shell.flicker", + instrumentation_target_package: "com.android.wm.shell.flicker", + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + exclude_srcs: [ + ":WMShellFlickerTestsBubbles-src", + ":WMShellFlickerTestsPip-src", + ":WMShellFlickerTestsSplitScreen-src", + ], +} + +android_test { + name: "WMShellFlickerTestsBubbles", + defaults: ["WMShellFlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestBubbles.xml"], + package_name: "com.android.wm.shell.flicker.bubbles", + instrumentation_target_package: "com.android.wm.shell.flicker.bubbles", + srcs: [ + ":WMShellFlickerTestsBase-src", + ":WMShellFlickerTestsBubbles-src", + ], +} + +android_test { + name: "WMShellFlickerTestsPip", + defaults: ["WMShellFlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestPip.xml"], + package_name: "com.android.wm.shell.flicker.pip", + instrumentation_target_package: "com.android.wm.shell.flicker.pip", + srcs: [ + ":WMShellFlickerTestsBase-src", + ":WMShellFlickerTestsPip-src", + ], +} + +android_test { + name: "WMShellFlickerTestsSplitScreen", + defaults: ["WMShellFlickerTestsDefault"], + additional_manifests: ["manifests/AndroidManifestSplitScreen.xml"], + package_name: "com.android.wm.shell.flicker.splitscreen", + instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen", + srcs: [ + ":WMShellFlickerTestsBase-src", + ":WMShellFlickerTestsSplitScreen-src", ], } diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml deleted file mode 100644 index b5937ae80f0a..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml +++ /dev/null @@ -1,53 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * Copyright 2020 Google Inc. All Rights Reserved. - --> -<configuration description="Runs WindowManager Shell Flicker Tests"> - <option name="test-tag" value="FlickerTests" /> - <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> - <!-- keeps the screen on during tests --> - <option name="screen-always-on" value="on" /> - <!-- prevents the phone from restarting --> - <option name="force-skip-system-props" value="true" /> - <!-- set WM tracing verbose level to all --> - <option name="run-command" value="cmd window tracing level all" /> - <!-- set WM tracing to frame (avoid incomplete states) --> - <option name="run-command" value="cmd window tracing frame" /> - <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests --> - <option name="run-command" value="pm disable com.google.android.internal.betterbug" /> - <!-- ensure lock screen mode is swipe --> - <option name="run-command" value="locksettings set-disabled false" /> - <!-- restart launcher to activate TAPL --> - <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" /> - <!-- Ensure output directory is empty at the start --> - <option name="run-command" value="rm -rf /sdcard/flicker" /> - <!-- Increase trace size: 20mb for WM and 80mb for SF --> - <option name="run-command" value="cmd window tracing size 20480" /> - <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920" /> - </target_preparer> - <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> - <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" /> - <option name="run-command" value="settings put system show_touches 1" /> - <option name="run-command" value="settings put system pointer_location 1" /> - <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" /> - <option name="teardown-command" value="settings delete system show_touches" /> - <option name="teardown-command" value="settings delete system pointer_location" /> - <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" /> - </target_preparer> - <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> - <option name="cleanup-apks" value="true"/> - <option name="test-file-name" value="WMShellFlickerTests.apk"/> - <option name="test-file-name" value="FlickerTestApp.apk" /> - </target_preparer> - <test class="com.android.tradefed.testtype.AndroidJUnitTest"> - <option name="package" value="com.android.wm.shell.flicker"/> - <option name="shell-timeout" value="6600s" /> - <option name="test-timeout" value="6000s" /> - <option name="hidden-api-checks" value="false" /> - </test> - <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> - <option name="directory-keys" value="/sdcard/flicker" /> - <option name="collect-on-run-ended-only" value="true" /> - <option name="clean-up" value="true" /> - </metrics_collector> -</configuration> diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml new file mode 100644 index 000000000000..991d7b5480c4 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml @@ -0,0 +1,99 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<configuration description="Runs WindowManager Shell Flicker Tests {MODULE}"> + <option name="test-tag" value="FlickerTests"/> + <!-- Needed for storing the perfetto trace files in the sdcard/test_results--> + <option name="isolated-storage" value="false"/> + + <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> + <!-- keeps the screen on during tests --> + <option name="screen-always-on" value="on"/> + <!-- prevents the phone from restarting --> + <option name="force-skip-system-props" value="true"/> + <!-- set WM tracing verbose level to all --> + <option name="run-command" value="cmd window tracing level all"/> + <!-- set WM tracing to frame (avoid incomplete states) --> + <option name="run-command" value="cmd window tracing frame"/> + <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests --> + <option name="run-command" value="pm disable com.google.android.internal.betterbug"/> + <!-- ensure lock screen mode is swipe --> + <option name="run-command" value="locksettings set-disabled false"/> + <!-- restart launcher to activate TAPL --> + <option name="run-command" + value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher"/> + <!-- Increase trace size: 20mb for WM and 80mb for SF --> + <option name="run-command" value="cmd window tracing size 20480"/> + <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="test-user-token" value="%TEST_USER%"/> + <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> + <option name="run-command" value="settings put system show_touches 1"/> + <option name="run-command" value="settings put system pointer_location 1"/> + <option name="teardown-command" + value="settings delete secure show_ime_with_hard_keyboard"/> + <option name="teardown-command" value="settings delete system show_touches"/> + <option name="teardown-command" value="settings delete system pointer_location"/> + <option name="teardown-command" + value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true"/> + <option name="test-file-name" value="{MODULE}.apk"/> + <option name="test-file-name" value="FlickerTestApp.apk"/> + </target_preparer> + <!-- Needed for pushing the trace config file --> + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="push-file" + key="trace_config.textproto" + value="/data/misc/perfetto-traces/trace_config.textproto" + /> + <!--Install the content provider automatically when we push some file in sdcard folder.--> + <!--Needed to avoid the installation during the test suite.--> + <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/> + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="{PACKAGE}"/> + <option name="shell-timeout" value="6600s"/> + <option name="test-timeout" value="6000s"/> + <option name="hidden-api-checks" value="false"/> + <option name="device-listeners" value="android.device.collectors.PerfettoListener"/> + <!-- PerfettoListener related arguments --> + <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/> + <option name="instrumentation-arg" + key="perfetto_config_file" + value="trace_config.textproto" + /> + <option name="instrumentation-arg" key="per_run" value="true"/> + </test> + <!-- Needed for pulling the collected trace config on to the host --> + <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> + <option name="pull-pattern-keys" value="perfetto_file_path"/> + <option name="directory-keys" + value="/data/user/0/com.android.wm.shell.flicker/files"/> + <option name="directory-keys" + value="/data/user/0/com.android.wm.shell.flicker.bubbles/files"/> + <option name="directory-keys" + value="/data/user/0/com.android.server.wm.flicker.pip/files"/> + <option name="directory-keys" + value="/data/user/0/com.android.server.wm.flicker.splitscreen/files"/> + <option name="collect-on-run-ended-only" value="true"/> + <option name="clean-up" value="true"/> + </metrics_collector> +</configuration> diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml index 4721741611cf..6a87de47def4 100644 --- a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml +++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml @@ -15,6 +15,7 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" package="com.android.wm.shell.flicker"> <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/> @@ -57,10 +58,11 @@ <action android:name="android.service.notification.NotificationListenerService" /> </intent-filter> </service> - </application> - <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="com.android.wm.shell.flicker" - android:label="WindowManager Shell Flicker Tests"> - </instrumentation> + <!-- (b/197936012) Remove startup provider due to test timeout issue --> + <provider + android:name="androidx.startup.InitializationProvider" + android:authorities="${applicationId}.androidx-startup" + tools:node="remove" /> + </application> </manifest> diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml new file mode 100644 index 000000000000..437871f1bb8f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.wm.shell.flicker.bubble"> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.wm.shell.flicker.bubble" + android:label="WindowManager Flicker Tests"> + </instrumentation> +</manifest> diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml new file mode 100644 index 000000000000..cf642f63a41d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.wm.shell.flicker"> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.wm.shell.flicker" + android:label="WindowManager Flicker Tests"> + </instrumentation> +</manifest> diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml new file mode 100644 index 000000000000..5a8155a66d30 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.wm.shell.flicker.pip"> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.wm.shell.flicker.pip" + android:label="WindowManager Flicker Tests"> + </instrumentation> +</manifest> diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml new file mode 100644 index 000000000000..887d8db3042f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.wm.shell.flicker.splitscreen"> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.wm.shell.flicker.splitscreen" + android:label="WindowManager Flicker Tests"> + </instrumentation> +</manifest> diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt index e06e074ee98a..0f3e0f5ef043 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt @@ -19,14 +19,14 @@ package com.android.wm.shell.flicker import android.app.Instrumentation import android.tools.device.flicker.junit.FlickerBuilderProvider import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.platform.app.InstrumentationRegistry import com.android.launcher3.tapl.LauncherInstrumentation abstract class BaseBenchmarkTest @JvmOverloads constructor( - protected open val flicker: FlickerTest, + protected open val flicker: LegacyFlickerTest, protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(), protected val tapl: LauncherInstrumentation = LauncherInstrumentation() ) { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt index c98c5a0ad1a6..d2fe9fec460c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt @@ -18,7 +18,7 @@ package com.android.wm.shell.flicker import android.app.Instrumentation import android.tools.common.traces.component.ComponentNameMatcher -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.platform.app.InstrumentationRegistry import com.android.launcher3.tapl.LauncherInstrumentation @@ -30,7 +30,7 @@ import com.android.launcher3.tapl.LauncherInstrumentation abstract class BaseTest @JvmOverloads constructor( - override val flicker: FlickerTest, + override val flicker: LegacyFlickerTest, instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(), tapl: LauncherInstrumentation = LauncherInstrumentation() ) : BaseBenchmarkTest(flicker, instrumentation, tapl), ICommonAssertions diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt index 798cc95c020f..9cc03a56e9a2 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt @@ -23,18 +23,18 @@ import android.tools.common.datatypes.Region import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject import android.tools.common.flicker.subject.layers.LayersTraceSubject import android.tools.common.traces.component.IComponentMatcher -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.helpers.WindowUtils -fun FlickerTest.appPairsDividerIsVisibleAtEnd() { +fun LegacyFlickerTest.appPairsDividerIsVisibleAtEnd() { assertLayersEnd { this.isVisible(APP_PAIR_SPLIT_DIVIDER_COMPONENT) } } -fun FlickerTest.appPairsDividerIsInvisibleAtEnd() { +fun LegacyFlickerTest.appPairsDividerIsInvisibleAtEnd() { assertLayersEnd { this.notContains(APP_PAIR_SPLIT_DIVIDER_COMPONENT) } } -fun FlickerTest.appPairsDividerBecomesVisible() { +fun LegacyFlickerTest.appPairsDividerBecomesVisible() { assertLayers { this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT) .then() @@ -42,7 +42,7 @@ fun FlickerTest.appPairsDividerBecomesVisible() { } } -fun FlickerTest.splitScreenEntered( +fun LegacyFlickerTest.splitScreenEntered( component1: IComponentMatcher, component2: IComponentMatcher, fromOtherApp: Boolean, @@ -69,7 +69,7 @@ fun FlickerTest.splitScreenEntered( splitScreenDividerIsVisibleAtEnd() } -fun FlickerTest.splitScreenDismissed( +fun LegacyFlickerTest.splitScreenDismissed( component1: IComponentMatcher, component2: IComponentMatcher, toHome: Boolean @@ -87,27 +87,27 @@ fun FlickerTest.splitScreenDismissed( splitScreenDividerIsInvisibleAtEnd() } -fun FlickerTest.splitScreenDividerIsVisibleAtStart() { +fun LegacyFlickerTest.splitScreenDividerIsVisibleAtStart() { assertLayersStart { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) } } -fun FlickerTest.splitScreenDividerIsVisibleAtEnd() { +fun LegacyFlickerTest.splitScreenDividerIsVisibleAtEnd() { assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) } } -fun FlickerTest.splitScreenDividerIsInvisibleAtStart() { +fun LegacyFlickerTest.splitScreenDividerIsInvisibleAtStart() { assertLayersStart { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) } } -fun FlickerTest.splitScreenDividerIsInvisibleAtEnd() { +fun LegacyFlickerTest.splitScreenDividerIsInvisibleAtEnd() { assertLayersEnd { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) } } -fun FlickerTest.splitScreenDividerBecomesVisible() { +fun LegacyFlickerTest.splitScreenDividerBecomesVisible() { layerBecomesVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) } -fun FlickerTest.splitScreenDividerBecomesInvisible() { +fun LegacyFlickerTest.splitScreenDividerBecomesInvisible() { assertLayers { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) .then() @@ -115,23 +115,23 @@ fun FlickerTest.splitScreenDividerBecomesInvisible() { } } -fun FlickerTest.layerBecomesVisible(component: IComponentMatcher) { +fun LegacyFlickerTest.layerBecomesVisible(component: IComponentMatcher) { assertLayers { this.isInvisible(component).then().isVisible(component) } } -fun FlickerTest.layerBecomesInvisible(component: IComponentMatcher) { +fun LegacyFlickerTest.layerBecomesInvisible(component: IComponentMatcher) { assertLayers { this.isVisible(component).then().isInvisible(component) } } -fun FlickerTest.layerIsVisibleAtEnd(component: IComponentMatcher) { +fun LegacyFlickerTest.layerIsVisibleAtEnd(component: IComponentMatcher) { assertLayersEnd { this.isVisible(component) } } -fun FlickerTest.layerKeepVisible(component: IComponentMatcher) { +fun LegacyFlickerTest.layerKeepVisible(component: IComponentMatcher) { assertLayers { this.isVisible(component) } } -fun FlickerTest.splitAppLayerBoundsBecomesVisible( +fun LegacyFlickerTest.splitAppLayerBoundsBecomesVisible( component: IComponentMatcher, landscapePosLeft: Boolean, portraitPosTop: Boolean @@ -150,7 +150,7 @@ fun FlickerTest.splitAppLayerBoundsBecomesVisible( } } -fun FlickerTest.splitAppLayerBoundsBecomesVisibleByDrag(component: IComponentMatcher) { +fun LegacyFlickerTest.splitAppLayerBoundsBecomesVisibleByDrag(component: IComponentMatcher) { assertLayers { this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true) .then() @@ -161,7 +161,7 @@ fun FlickerTest.splitAppLayerBoundsBecomesVisibleByDrag(component: IComponentMat } } -fun FlickerTest.splitAppLayerBoundsBecomesInvisible( +fun LegacyFlickerTest.splitAppLayerBoundsBecomesInvisible( component: IComponentMatcher, landscapePosLeft: Boolean, portraitPosTop: Boolean @@ -180,7 +180,7 @@ fun FlickerTest.splitAppLayerBoundsBecomesInvisible( } } -fun FlickerTest.splitAppLayerBoundsIsVisibleAtEnd( +fun LegacyFlickerTest.splitAppLayerBoundsIsVisibleAtEnd( component: IComponentMatcher, landscapePosLeft: Boolean, portraitPosTop: Boolean @@ -195,7 +195,7 @@ fun FlickerTest.splitAppLayerBoundsIsVisibleAtEnd( } } -fun FlickerTest.splitAppLayerBoundsKeepVisible( +fun LegacyFlickerTest.splitAppLayerBoundsKeepVisible( component: IComponentMatcher, landscapePosLeft: Boolean, portraitPosTop: Boolean @@ -210,7 +210,7 @@ fun FlickerTest.splitAppLayerBoundsKeepVisible( } } -fun FlickerTest.splitAppLayerBoundsChanges( +fun LegacyFlickerTest.splitAppLayerBoundsChanges( component: IComponentMatcher, landscapePosLeft: Boolean, portraitPosTop: Boolean @@ -304,7 +304,7 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider( } } -fun FlickerTest.appWindowBecomesVisible(component: IComponentMatcher) { +fun LegacyFlickerTest.appWindowBecomesVisible(component: IComponentMatcher) { assertWm { this.isAppWindowInvisible(component) .then() @@ -316,39 +316,39 @@ fun FlickerTest.appWindowBecomesVisible(component: IComponentMatcher) { } } -fun FlickerTest.appWindowBecomesInvisible(component: IComponentMatcher) { +fun LegacyFlickerTest.appWindowBecomesInvisible(component: IComponentMatcher) { assertWm { this.isAppWindowVisible(component).then().isAppWindowInvisible(component) } } -fun FlickerTest.appWindowIsVisibleAtStart(component: IComponentMatcher) { +fun LegacyFlickerTest.appWindowIsVisibleAtStart(component: IComponentMatcher) { assertWmStart { this.isAppWindowVisible(component) } } -fun FlickerTest.appWindowIsVisibleAtEnd(component: IComponentMatcher) { +fun LegacyFlickerTest.appWindowIsVisibleAtEnd(component: IComponentMatcher) { assertWmEnd { this.isAppWindowVisible(component) } } -fun FlickerTest.appWindowIsInvisibleAtStart(component: IComponentMatcher) { +fun LegacyFlickerTest.appWindowIsInvisibleAtStart(component: IComponentMatcher) { assertWmStart { this.isAppWindowInvisible(component) } } -fun FlickerTest.appWindowIsInvisibleAtEnd(component: IComponentMatcher) { +fun LegacyFlickerTest.appWindowIsInvisibleAtEnd(component: IComponentMatcher) { assertWmEnd { this.isAppWindowInvisible(component) } } -fun FlickerTest.appWindowIsNotContainAtStart(component: IComponentMatcher) { +fun LegacyFlickerTest.appWindowIsNotContainAtStart(component: IComponentMatcher) { assertWmStart { this.notContains(component) } } -fun FlickerTest.appWindowKeepVisible(component: IComponentMatcher) { +fun LegacyFlickerTest.appWindowKeepVisible(component: IComponentMatcher) { assertWm { this.isAppWindowVisible(component) } } -fun FlickerTest.dockedStackDividerIsVisibleAtEnd() { +fun LegacyFlickerTest.dockedStackDividerIsVisibleAtEnd() { assertLayersEnd { this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) } } -fun FlickerTest.dockedStackDividerBecomesVisible() { +fun LegacyFlickerTest.dockedStackDividerBecomesVisible() { assertLayers { this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT) .then() @@ -356,7 +356,7 @@ fun FlickerTest.dockedStackDividerBecomesVisible() { } } -fun FlickerTest.dockedStackDividerBecomesInvisible() { +fun LegacyFlickerTest.dockedStackDividerBecomesInvisible() { assertLayers { this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) .then() @@ -364,11 +364,11 @@ fun FlickerTest.dockedStackDividerBecomesInvisible() { } } -fun FlickerTest.dockedStackDividerNotExistsAtEnd() { +fun LegacyFlickerTest.dockedStackDividerNotExistsAtEnd() { assertLayersEnd { this.notContains(DOCKED_STACK_DIVIDER_COMPONENT) } } -fun FlickerTest.appPairsPrimaryBoundsIsVisibleAtEnd( +fun LegacyFlickerTest.appPairsPrimaryBoundsIsVisibleAtEnd( rotation: Rotation, primaryComponent: IComponentMatcher ) { @@ -380,7 +380,7 @@ fun FlickerTest.appPairsPrimaryBoundsIsVisibleAtEnd( } } -fun FlickerTest.dockedStackPrimaryBoundsIsVisibleAtEnd( +fun LegacyFlickerTest.dockedStackPrimaryBoundsIsVisibleAtEnd( rotation: Rotation, primaryComponent: IComponentMatcher ) { @@ -392,7 +392,7 @@ fun FlickerTest.dockedStackPrimaryBoundsIsVisibleAtEnd( } } -fun FlickerTest.appPairsSecondaryBoundsIsVisibleAtEnd( +fun LegacyFlickerTest.appPairsSecondaryBoundsIsVisibleAtEnd( rotation: Rotation, secondaryComponent: IComponentMatcher ) { @@ -404,7 +404,7 @@ fun FlickerTest.appPairsSecondaryBoundsIsVisibleAtEnd( } } -fun FlickerTest.dockedStackSecondaryBoundsIsVisibleAtEnd( +fun LegacyFlickerTest.dockedStackSecondaryBoundsIsVisibleAtEnd( rotation: Rotation, secondaryComponent: IComponentMatcher ) { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt index 02d9a056afbf..7b3290125bae 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt @@ -18,7 +18,7 @@ package com.android.wm.shell.flicker import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd @@ -32,7 +32,7 @@ import org.junit.Assume import org.junit.Test interface ICommonAssertions { - val flicker: FlickerTest + val flicker: LegacyFlickerTest /** Checks that all parts of the screen are covered during the transition */ @Presubmit @Test fun entireScreenCovered() = flicker.entireScreenCovered() diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt index 61781565270b..0f9579d58929 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt @@ -20,21 +20,20 @@ import android.content.Context import android.system.helpers.CommandsHelper import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import com.android.server.wm.flicker.helpers.setRotation +import android.tools.device.flicker.legacy.FlickerTestData +import android.tools.device.flicker.legacy.LegacyFlickerTest import com.android.server.wm.flicker.helpers.LetterboxAppHelper -import android.tools.device.flicker.legacy.FlickerTestFactory -import android.tools.device.flicker.legacy.IFlickerTestData +import com.android.server.wm.flicker.helpers.setRotation import com.android.wm.shell.flicker.BaseTest -import com.android.wm.shell.flicker.appWindowIsVisibleAtStart import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd +import com.android.wm.shell.flicker.appWindowIsVisibleAtStart +import com.android.wm.shell.flicker.appWindowKeepVisible import com.android.wm.shell.flicker.layerKeepVisible import org.junit.After import org.junit.Assume import org.junit.Before -import org.junit.runners.Parameterized -abstract class BaseAppCompat(flicker: FlickerTest) : BaseTest(flicker) { +abstract class BaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) { protected val context: Context = instrumentation.context protected val letterboxApp = LetterboxAppHelper(instrumentation) lateinit var cmdHelper: CommandsHelper @@ -48,9 +47,7 @@ abstract class BaseAppCompat(flicker: FlickerTest) : BaseTest(flicker) { letterboxApp.launchViaIntent(wmHelper) setEndRotation() } - teardown { - letterboxApp.exit(wmHelper) - } + teardown { letterboxApp.exit(wmHelper) } } @Before @@ -58,12 +55,13 @@ abstract class BaseAppCompat(flicker: FlickerTest) : BaseTest(flicker) { cmdHelper = CommandsHelper.getInstance(instrumentation) Assume.assumeTrue(tapl.isTablet && isIgnoreOrientationRequest()) letterboxStyle = mapLetterboxStyle() + resetLetterboxStyle() setLetterboxEducationEnabled(false) } @After fun after() { - resetLetterboxEducationEnabled() + resetLetterboxStyle() } private fun mapLetterboxStyle(): HashMap<String, String> { @@ -87,9 +85,8 @@ abstract class BaseAppCompat(flicker: FlickerTest) : BaseTest(flicker) { return letterboxStyle } - private fun resetLetterboxEducationEnabled() { - val enabled = getLetterboxStyle().getValue("Is education enabled") - cmdHelper.executeShellCommand("wm set-letterbox-style --isEducationEnabled $enabled") + private fun resetLetterboxStyle() { + cmdHelper.executeShellCommand("wm reset-letterbox-style") } private fun setLetterboxEducationEnabled(enabled: Boolean) { @@ -101,9 +98,9 @@ abstract class BaseAppCompat(flicker: FlickerTest) : BaseTest(flicker) { return res != null && res.contains("true") } - fun IFlickerTestData.setStartRotation() = setRotation(flicker.scenario.startRotation) + fun FlickerTestData.setStartRotation() = setRotation(flicker.scenario.startRotation) - fun IFlickerTestData.setEndRotation() = setRotation(flicker.scenario.endRotation) + fun FlickerTestData.setEndRotation() = setRotation(flicker.scenario.endRotation) /** Checks that app entering letterboxed state have rounded corners */ fun assertLetterboxAppAtStartHasRoundedCorners() { @@ -126,25 +123,21 @@ abstract class BaseAppCompat(flicker: FlickerTest) : BaseTest(flicker) { flicker.appWindowIsVisibleAtEnd(letterboxApp) } + fun assertLetterboxAppKeepVisible() { + assertLetterboxAppWindowKeepVisible() + assertLetterboxAppLayerKeepVisible() + } + fun assertAppLetterboxedAtEnd() = - flicker.assertLayersEnd { isVisible(ComponentNameMatcher.LETTERBOX) } + flicker.assertLayersEnd { isVisible(ComponentNameMatcher.LETTERBOX) } fun assertAppLetterboxedAtStart() = - flicker.assertLayersStart { isVisible(ComponentNameMatcher.LETTERBOX) } + flicker.assertLayersStart { isVisible(ComponentNameMatcher.LETTERBOX) } + + fun assertAppStaysLetterboxed() = + flicker.assertLayers { isVisible(ComponentNameMatcher.LETTERBOX) } fun assertLetterboxAppLayerKeepVisible() = flicker.layerKeepVisible(letterboxApp) - companion object { - /** - * Creates the test configurations. - * - * See [FlickerTestFactory.rotationTests] for configuring screen orientation and - * navigation modes. - */ - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTest> { - return FlickerTestFactory.rotationTests() - } - } + fun assertLetterboxAppWindowKeepVisible() = flicker.appWindowKeepVisible(letterboxApp) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt index c2141a370f10..a7bd2584ba23 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt @@ -17,10 +17,12 @@ package com.android.wm.shell.flicker.appcompat import android.platform.test.annotations.Postsubmit +import android.tools.common.flicker.assertions.FlickerTest import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import org.junit.Test import org.junit.runner.RunWith @@ -45,7 +47,7 @@ import org.junit.runners.Parameterized @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -class OpenAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flicker) { +class OpenAppInSizeCompatModeTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit @@ -70,9 +72,7 @@ class OpenAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flicker) @Test fun letterboxedAppHasRoundedCorners() = assertLetterboxAppAtEndHasRoundedCorners() - @Postsubmit - @Test - fun appIsLetterboxedAtEnd() = assertAppLetterboxedAtEnd() + @Postsubmit @Test fun appIsLetterboxedAtEnd() = assertAppLetterboxedAtEnd() /** * Checks that the [ComponentNameMatcher.ROTATION] layer appears during the transition, doesn't @@ -90,4 +90,18 @@ class OpenAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flicker) .isInvisible(ComponentNameMatcher.ROTATION) } } + + companion object { + /** + * Creates the test configurations. + * + * See [LegacyFlickerTestFactory.rotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return LegacyFlickerTestFactory.rotationTests() + } + } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt new file mode 100644 index 000000000000..6fe88cacbbc7 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt @@ -0,0 +1,276 @@ +/* + * 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 com.android.wm.shell.flicker.appcompat + +import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.RequiresDevice +import android.tools.common.flicker.assertions.FlickerTest +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.datatypes.Rect +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test quick switching to letterboxed app from launcher + * + * To run this test: `atest WMShellFlickerTestsOther:QuickSwitchLauncherToLetterboxAppTest` + * + * Actions: + * ``` + * Launch a letterboxed app + * Navigate home to show launcher + * Swipe right from the bottom of the screen to quick switch back to the app + * ``` + */ + +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) : + BaseAppCompat(flicker) { + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + tapl.setExpectedRotationCheckEnabled(false) + + tapl.setExpectedRotation(flicker.scenario.startRotation.value) + + letterboxApp.launchViaIntent(wmHelper) + tapl.goHome() + wmHelper + .StateSyncBuilder() + .withHomeActivityVisible() + .withWindowSurfaceDisappeared(letterboxApp) + .waitForAndVerify() + + startDisplayBounds = + wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found") + } + transitions { + tapl.workspace.quickSwitchToPreviousApp() + wmHelper + .StateSyncBuilder() + .withFullScreenApp(letterboxApp) + .withNavOrTaskBarVisible() + .withStatusBarVisible() + .waitForAndVerify() + } + teardown { letterboxApp.exit(wmHelper) } + } + + /** + * Checks that [letterboxApp] is the top window at the end of the transition once we have fully + * quick switched from the launcher back to the [letterboxApp]. + */ + @Postsubmit + @Test + fun endsWithAppBeingOnTop() { + flicker.assertWmEnd { this.isAppWindowOnTop(letterboxApp) } + } + + /** Checks that the transition starts with the home activity being tagged as visible. */ + @Postsubmit + @Test + fun startsWithHomeActivityFlaggedVisible() { + flicker.assertWmStart { this.isHomeActivityVisible() } + } + + /** + * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] windows + * filling/covering exactly display size + */ + @Postsubmit + @Test + fun startsWithLauncherWindowsCoverFullScreen() { + flicker.assertWmStart { + this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] layers + * filling/covering exactly the display size. + */ + @Postsubmit + @Test + fun startsWithLauncherLayersCoverFullScreen() { + flicker.assertLayersStart { + this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] being the top + * window. + */ + @Postsubmit + @Test + fun startsWithLauncherBeingOnTop() { + flicker.assertWmStart { this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) } + } + + /** + * Checks that the transition ends with the home activity being flagged as not visible. By this + * point we should have quick switched away from the launcher back to the [letterboxApp]. + */ + @Postsubmit + @Test + fun endsWithHomeActivityFlaggedInvisible() { + flicker.assertWmEnd { this.isHomeActivityInvisible() } + } + + /** + * Checks that [letterboxApp]'s window starts off invisible and becomes visible at some point + * before the end of the transition and then stays visible until the end of the transition. + */ + @Postsubmit + @Test + fun appWindowBecomesAndStaysVisible() { + flicker.assertWm { + this.isAppWindowInvisible(letterboxApp) + .then() + .isAppWindowVisible(letterboxApp) } + } + + /** + * Checks that [letterboxApp]'s layer starts off invisible and becomes visible at some point + * before the end of the transition and then stays visible until the end of the transition. + */ + @Postsubmit + @Test + fun appLayerBecomesAndStaysVisible() { + flicker.assertLayers { this.isInvisible(letterboxApp).then().isVisible(letterboxApp) } + } + + /** + * Checks that the [ComponentNameMatcher.LAUNCHER] window starts off visible and becomes + * invisible at some point before the end of the transition and then stays invisible until the + * end of the transition. + */ + @Postsubmit + @Test + fun launcherWindowBecomesAndStaysInvisible() { + flicker.assertWm { + this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) + .then() + .isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER) + } + } + + /** + * Checks that the [ComponentNameMatcher.LAUNCHER] layer starts off visible and becomes + * invisible at some point before the end of the transition and then stays invisible until the + * end of the transition. + */ + @Postsubmit + @Test + fun launcherLayerBecomesAndStaysInvisible() { + flicker.assertLayers { + this.isVisible(ComponentNameMatcher.LAUNCHER) + .then() + .isInvisible(ComponentNameMatcher.LAUNCHER) + } + } + + /** + * Checks that the [ComponentNameMatcher.LAUNCHER] window is visible at least until the app + * window is visible. Ensures that at any point, either the launcher or [letterboxApp] windows + * are at least partially visible. + */ + @Postsubmit + @Test + fun appWindowIsVisibleOnceLauncherWindowIsInvisible() { + flicker.assertWm { + this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) + .then() + .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isAppWindowVisible(letterboxApp) + } + } + + /** + * Checks that the [ComponentNameMatcher.LAUNCHER] layer is visible at least until the app layer + * is visible. Ensures that at any point, either the launcher or [letterboxApp] layers are at + * least partially visible. + */ + @Postsubmit + @Test + fun appLayerIsVisibleOnceLauncherLayerIsInvisible() { + flicker.assertLayers { + this.isVisible(ComponentNameMatcher.LAUNCHER) + .then() + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isVisible(letterboxApp) + } + } + + /** + * Checks that the [ComponentNameMatcher.LETTERBOX] layer is visible as soon as the + * [letterboxApp] layer is visible at the end of the transition once we have fully quick + * switched from the launcher back to the [letterboxApp]. + */ + @Postsubmit + @Test + fun appAndLetterboxLayersBothVisibleOnceLauncherIsInvisible() { + flicker.assertLayers { + this.isVisible(ComponentNameMatcher.LAUNCHER) + .then() + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isVisible(letterboxApp) + .isVisible(ComponentNameMatcher.LETTERBOX) } + } + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } + + companion object { + /** {@inheritDoc} */ + private var startDisplayBounds = Rect.EMPTY + + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return LegacyFlickerTestFactory.nonRotationTests( + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL), + supportedRotations = listOf(Rotation.ROTATION_90) + ) + } + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt new file mode 100644 index 000000000000..e875aae431a1 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt @@ -0,0 +1,97 @@ +/* + * 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 com.android.wm.shell.flicker.appcompat + +import android.platform.test.annotations.Postsubmit +import android.tools.common.Rotation +import android.tools.common.flicker.assertions.FlickerTest +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import android.tools.device.helpers.WindowUtils +import androidx.test.filters.RequiresDevice +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +/** + * Test launching a fixed portrait letterboxed app in landscape and repositioning to the right. + * + * To run this test: `atest WMShellFlickerTests:RepositionFixedPortraitAppTest` Actions: + * + * ``` + * Launch a fixed portrait app in landscape to letterbox app + * Double tap to the right to reposition app and wait for app to move + * ``` + * + * Notes: + * + * ``` + * Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [BaseTest] + * ``` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +class RepositionFixedPortraitAppTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) { + + val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation).bounds + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit + get() = { + setup { + setStartRotation() + letterboxApp.launchViaIntent(wmHelper) + } + transitions { + letterboxApp.repositionHorizontally(displayBounds, true) + letterboxApp.waitForAppToMoveHorizontallyTo(wmHelper, displayBounds, true) + } + teardown { + letterboxApp.repositionHorizontally(displayBounds, false) + letterboxApp.exit(wmHelper) + } + } + + @Postsubmit + @Test + fun letterboxedAppHasRoundedCorners() = assertLetterboxAppAtEndHasRoundedCorners() + + @Postsubmit @Test fun letterboxAppLayerKeepVisible() = assertLetterboxAppLayerKeepVisible() + + @Postsubmit @Test fun appStaysLetterboxed() = assertAppStaysLetterboxed() + + @Postsubmit @Test fun appKeepVisible() = assertLetterboxAppKeepVisible() + + companion object { + /** + * Creates the test configurations. + * + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return LegacyFlickerTestFactory.nonRotationTests( + supportedRotations = listOf(Rotation.ROTATION_90) + ) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt index b0e1a42306df..a18a144b4bf1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt @@ -17,9 +17,11 @@ package com.android.wm.shell.flicker.appcompat import android.platform.test.annotations.Postsubmit +import android.tools.common.flicker.assertions.FlickerTest import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.helpers.WindowUtils import androidx.test.filters.RequiresDevice import org.junit.Test @@ -46,7 +48,7 @@ import org.junit.runners.Parameterized @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -class RestartAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flicker) { +class RestartAppInSizeCompatModeTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit @@ -69,13 +71,9 @@ class RestartAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flick } } - @Postsubmit - @Test - fun appLayerKeepVisible() = assertLetterboxAppLayerKeepVisible() + @Postsubmit @Test fun appLayerKeepVisible() = assertLetterboxAppLayerKeepVisible() - @Postsubmit - @Test - fun appIsLetterboxedAtStart() = assertAppLetterboxedAtStart() + @Postsubmit @Test fun appIsLetterboxedAtStart() = assertAppLetterboxedAtStart() @Postsubmit @Test @@ -88,4 +86,18 @@ class RestartAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flick val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.endRotation) flicker.assertLayersEnd { visibleRegion(letterboxApp).coversAtMost(displayBounds) } } + + companion object { + /** + * Creates the test configurations. + * + * See [LegacyFlickerTestFactory.rotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return LegacyFlickerTestFactory.rotationTests() + } + } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt index bab81d79c804..5c7d1d8df2e8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt @@ -23,9 +23,9 @@ import android.content.pm.PackageManager import android.os.ServiceManager import android.tools.common.Rotation import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory -import android.tools.device.flicker.legacy.IFlickerTestData +import android.tools.device.flicker.legacy.FlickerTestData +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.helpers.SYSTEMUI_PACKAGE import androidx.test.uiautomator.By import androidx.test.uiautomator.UiObject2 @@ -35,7 +35,7 @@ import com.android.wm.shell.flicker.BaseTest import org.junit.runners.Parameterized /** Base configurations for Bubble flicker tests */ -abstract class BaseBubbleScreen(flicker: FlickerTest) : BaseTest(flicker) { +abstract class BaseBubbleScreen(flicker: LegacyFlickerTest) : BaseTest(flicker) { protected val context: Context = instrumentation.context protected val testApp = LaunchBubbleHelper(instrumentation) @@ -72,28 +72,31 @@ abstract class BaseBubbleScreen(flicker: FlickerTest) : BaseTest(flicker) { uid, NotificationManager.BUBBLE_PREFERENCE_NONE ) - testApp.exit() + device.wait( + Until.gone(By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), + FIND_OBJECT_TIMEOUT + ) + testApp.exit(wmHelper) } extraSpec(this) } } - protected fun IFlickerTestData.waitAndGetAddBubbleBtn(): UiObject2? = + protected fun FlickerTestData.waitAndGetAddBubbleBtn(): UiObject2? = device.wait(Until.findObject(By.text("Add Bubble")), FIND_OBJECT_TIMEOUT) - protected fun IFlickerTestData.waitAndGetCancelAllBtn(): UiObject2? = + protected fun FlickerTestData.waitAndGetCancelAllBtn(): UiObject2? = device.wait(Until.findObject(By.text("Cancel All Bubble")), FIND_OBJECT_TIMEOUT) companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) - } - const val FIND_OBJECT_TIMEOUT = 2000L + const val FIND_OBJECT_TIMEOUT = 4000L const val SYSTEM_UI_PACKAGE = SYSTEMUI_PACKAGE const val BUBBLE_RES_NAME = "bubble_view" } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt index 2474ecf74cf9..bc565bc5fd42 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt @@ -21,7 +21,7 @@ import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.filters.RequiresDevice import androidx.test.uiautomator.By import androidx.test.uiautomator.UiObject2 @@ -44,7 +44,8 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FlakyTest(bugId = 217777115) -open class ChangeActiveActivityFromBubbleTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) { +open class ChangeActiveActivityFromBubbleTest(flicker: LegacyFlickerTest) : + BaseBubbleScreen(flicker) { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = buildTransition { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt index bdfdad59c600..abc6b9f9a746 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt @@ -17,11 +17,11 @@ package com.android.wm.shell.flicker.bubble import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import org.junit.runner.RunWith import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -open class ChangeActiveActivityFromBubbleTestCfArm(flicker: FlickerTest) : +open class ChangeActiveActivityFromBubbleTestCfArm(flicker: LegacyFlickerTest) : ChangeActiveActivityFromBubbleTest(flicker) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt index 8474ce0e64e5..3f28ae848d1f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt @@ -19,9 +19,10 @@ package com.android.wm.shell.flicker.bubble import android.content.Context import android.graphics.Point import android.platform.test.annotations.Presubmit +import android.tools.common.flicker.subject.layers.LayersTraceSubject import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import android.util.DisplayMetrics import android.view.WindowManager import androidx.test.filters.RequiresDevice @@ -44,7 +45,7 @@ import org.junit.runners.Parameterized @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -open class DragToDismissBubbleScreenTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) { +open class DragToDismissBubbleScreenTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) { private val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager private val displaySize = DisplayMetrics() @@ -73,4 +74,14 @@ open class DragToDismissBubbleScreenTest(flicker: FlickerTest) : BaseBubbleScree open fun testAppIsAlwaysVisible() { flicker.assertLayers { this.isVisible(testApp) } } + + @Presubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + flicker.assertLayers { + this.visibleLayersShownMoreThanOneConsecutiveEntry( + LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS + listOf(testApp) + ) + } + } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt index 62fa7b4516c7..ee55eca31072 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt @@ -17,11 +17,11 @@ package com.android.wm.shell.flicker.bubble import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import org.junit.runner.RunWith import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -class DragToDismissBubbleScreenTestCfArm(flicker: FlickerTest) : +class DragToDismissBubbleScreenTestCfArm(flicker: LegacyFlickerTest) : DragToDismissBubbleScreenTest(flicker) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt index 889e1771593d..26aca1830889 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt @@ -21,7 +21,7 @@ import android.platform.test.annotations.Postsubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import android.view.WindowInsets import android.view.WindowManager import androidx.test.filters.RequiresDevice @@ -48,7 +48,8 @@ import org.junit.runners.Parameterized @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -class OpenActivityFromBubbleOnLocksreenTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) { +class OpenActivityFromBubbleOnLocksreenTest(flicker: LegacyFlickerTest) : + BaseBubbleScreen(flicker) { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit @@ -80,7 +81,7 @@ class OpenActivityFromBubbleOnLocksreenTest(flicker: FlickerTest) : BaseBubbleSc instrumentation.uiAutomation.syncInputTransactions() val showBubble = device.wait( - Until.findObject(By.res("com.android.systemui", "bubble_view")), + Until.findObject(By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), FIND_OBJECT_TIMEOUT ) showBubble?.click() ?: error("Bubble notify not found") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt index 07ba41333071..508539411aa0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt @@ -19,7 +19,7 @@ package com.android.wm.shell.flicker.bubble import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.filters.RequiresDevice import androidx.test.uiautomator.By import androidx.test.uiautomator.Until @@ -42,7 +42,7 @@ import org.junit.runners.Parameterized @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -open class OpenActivityFromBubbleTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) { +open class OpenActivityFromBubbleTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt index 6c61710d6284..6a46d23ad2a1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt @@ -17,10 +17,11 @@ package com.android.wm.shell.flicker.bubble import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import org.junit.runner.RunWith import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -class OpenActivityFromBubbleTestCfArm(flicker: FlickerTest) : OpenActivityFromBubbleTest(flicker) +class OpenActivityFromBubbleTestCfArm(flicker: LegacyFlickerTest) : + OpenActivityFromBubbleTest(flicker) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt index 29f76d01af83..a926bb7d85c3 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt @@ -19,7 +19,7 @@ package com.android.wm.shell.flicker.bubble import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.filters.RequiresDevice import androidx.test.uiautomator.By import androidx.test.uiautomator.Until @@ -41,7 +41,7 @@ import org.junit.runners.Parameterized @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -open class SendBubbleNotificationTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) { +open class SendBubbleNotificationTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit @@ -55,6 +55,7 @@ open class SendBubbleNotificationTest(flicker: FlickerTest) : BaseBubbleScreen(f FIND_OBJECT_TIMEOUT ) ?: error("No bubbles found") + device.waitForIdle() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt index e323ebf3b5c8..a401cb494822 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt @@ -17,11 +17,11 @@ package com.android.wm.shell.flicker.bubble import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import org.junit.runner.RunWith import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -open class SendBubbleNotificationTestCfArm(flicker: FlickerTest) : +open class SendBubbleNotificationTestCfArm(flicker: LegacyFlickerTest) : SendBubbleNotificationTest(flicker) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt index f7ce87088040..36bbafb4c05e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt @@ -24,8 +24,8 @@ import android.tools.common.Rotation import android.tools.device.apphelpers.StandardAppHelper import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.helpers.WindowUtils import android.tools.device.traces.parsers.toFlickerComponent import androidx.test.filters.RequiresDevice @@ -69,17 +69,17 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: FlickerTest) : - AutoEnterPipOnGoToHomeTest(flicker) { +class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: LegacyFlickerTest) : + AutoEnterPipOnGoToHomeTest(flicker) { private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) /** Second app used to enter split screen mode */ protected val secondAppForSplitScreen = getSplitScreenApp(instrumentation) fun getSplitScreenApp(instrumentation: Instrumentation): StandardAppHelper = - SimpleAppHelper( - instrumentation, - ActivityOptions.SplitScreen.Primary.LABEL, - ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent() - ) + SimpleAppHelper( + instrumentation, + ActivityOptions.SplitScreen.Primary.LABEL, + ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent() + ) /** Defines the transition used to run the test */ override val transition: FlickerBuilder.() -> Unit @@ -91,11 +91,11 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: FlickerTest) : enterSplitScreen() // wait until split screen is established wmHelper - .StateSyncBuilder() - .withWindowSurfaceAppeared(pipApp) - .withWindowSurfaceAppeared(secondAppForSplitScreen) - .withSplitDividerVisible() - .waitForAndVerify() + .StateSyncBuilder() + .withWindowSurfaceAppeared(pipApp) + .withWindowSurfaceAppeared(secondAppForSplitScreen) + .withSplitDividerVisible() + .waitForAndVerify() pipApp.enableAutoEnterForPipActivity() } teardown { @@ -120,8 +120,8 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: FlickerTest) : // contains more than 3 task views. We need to use uiautomator directly to find the // second task to split. tapl.workspace.switchToOverview().overviewActions.clickSplit() - val snapshots = tapl.device.wait(Until.findObjects(overviewSnapshotSelector), - TIMEOUT_MS) + val snapshots = + tapl.device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS) if (snapshots == null || snapshots.size < 1) { error("Fail to find a overview snapshot to split.") } @@ -137,12 +137,12 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: FlickerTest) : snapshots[0].click() } else { tapl.workspace - .switchToOverview() - .currentTask - .tapMenu() - .tapSplitMenuItem() - .currentTask - .open() + .switchToOverview() + .currentTask + .tapMenu() + .tapSplitMenuItem() + .currentTask + .open() } SystemClock.sleep(TIMEOUT_MS) } @@ -190,11 +190,10 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( - // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. - supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( + // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt index b95732e43357..2f7a25ea586d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt @@ -19,7 +19,7 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.filters.RequiresDevice import org.junit.Assume import org.junit.FixMethodOrder @@ -53,10 +53,9 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(flicker) { - override val thisTransition: FlickerBuilder.() -> Unit = { - transitions { tapl.goHome() } - } +open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : + EnterPipViaAppUiButtonTest(flicker) { + override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } } override val defaultEnterPip: FlickerBuilder.() -> Unit = { setup { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt index afcc1729ed16..68bc9a28967e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt @@ -20,7 +20,7 @@ import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.filters.RequiresDevice import org.junit.FixMethodOrder import org.junit.Test @@ -53,7 +53,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class ClosePipBySwipingDownTest(flicker: FlickerTest) : ClosePipTransition(flicker) { +open class ClosePipBySwipingDownTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { val pipRegion = wmHelper.getWindowRegion(pipApp).bounds diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt index 02f60100d069..7a668897fbbe 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt @@ -18,8 +18,8 @@ package com.android.wm.shell.flicker.pip import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -28,20 +28,20 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class ClosePipBySwipingDownTestCfArm(flicker: FlickerTest) : ClosePipBySwipingDownTest(flicker) { +class ClosePipBySwipingDownTestCfArm(flicker: LegacyFlickerTest) : + ClosePipBySwipingDownTest(flicker) { companion object { /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation - * and navigation modes. + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen + * orientation and navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt index e52b71e602f9..a17144b7cef3 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt @@ -20,14 +20,14 @@ import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.common.traces.component.ComponentNameMatcher.Companion.LAUNCHER import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import com.android.server.wm.flicker.helpers.setRotation import org.junit.Test import org.junit.runners.Parameterized /** Base class for exiting pip (closing pip window) without returning to the app */ -abstract class ClosePipTransition(flicker: FlickerTest) : PipTransition(flicker) { +abstract class ClosePipTransition(flicker: LegacyFlickerTest) : PipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { setup { this.setRotation(flicker.scenario.startRotation) } teardown { this.setRotation(Rotation.ROTATION_0) } @@ -74,15 +74,14 @@ abstract class ClosePipTransition(flicker: FlickerTest) : PipTransition(flicker) /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation - * and navigation modes. + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen + * orientation and navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt index 86fe583c94e6..dc48696f3197 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt @@ -19,7 +19,7 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.filters.RequiresDevice import org.junit.FixMethodOrder import org.junit.Test @@ -53,7 +53,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class ClosePipWithDismissButtonTest(flicker: FlickerTest) : ClosePipTransition(flicker) { +open class ClosePipWithDismissButtonTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { pipApp.closePipWindow(wmHelper) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt index 05262feceba5..718b14babc4f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt @@ -18,8 +18,8 @@ package com.android.wm.shell.flicker.pip import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -28,21 +28,20 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class ClosePipWithDismissButtonTestCfArm(flicker: FlickerTest) : +open class ClosePipWithDismissButtonTestCfArm(flicker: LegacyFlickerTest) : ClosePipWithDismissButtonTest(flicker) { companion object { /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation - * and navigation modes. + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen + * orientation and navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt index 01d67cc35a14..5e392628aa6a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt @@ -19,7 +19,7 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.filters.RequiresDevice import org.junit.Assume import org.junit.FixMethodOrder @@ -44,10 +44,8 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterPipOnUserLeaveHintTest(flicker: FlickerTest) : EnterPipTransition(flicker) { - override val thisTransition: FlickerBuilder.() -> Unit = { - transitions { tapl.goHome() } - } +open class EnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) { + override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } } override val defaultEnterPip: FlickerBuilder.() -> Unit = { setup { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt index 90f99c0c4cae..2b3e76a964c4 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt @@ -17,7 +17,7 @@ package com.android.wm.shell.flicker.pip import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -27,4 +27,5 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class EnterPipOnUserLeaveHintTestCfArm(flicker: FlickerTest) : EnterPipOnUserLeaveHintTest(flicker) +class EnterPipOnUserLeaveHintTestCfArm(flicker: LegacyFlickerTest) : + EnterPipOnUserLeaveHintTest(flicker) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt index 5480144ba1ce..ec35837bc8dd 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt @@ -21,11 +21,12 @@ import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.tools.common.Rotation +import android.tools.common.flicker.assertions.FlickerTest import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.helpers.WindowUtils import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.entireScreenCovered @@ -68,15 +69,13 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterPipToOtherOrientation(flicker: FlickerTest) : PipTransition(flicker) { +open class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(flicker) { private val testApp = FixedOrientationAppHelper(instrumentation) private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90) private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) override val thisTransition: FlickerBuilder.() -> Unit = { - teardown { - testApp.exit(wmHelper) - } + teardown { testApp.exit(wmHelper) } transitions { // Enter PiP, and assert that the PiP is within bounds now that the device is back // in portrait @@ -95,14 +94,13 @@ open class EnterPipToOtherOrientation(flicker: FlickerTest) : PipTransition(flic setup { // Launch a portrait only app on the fullscreen stack testApp.launchViaIntent( - wmHelper, - stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString()) + wmHelper, + stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString()) ) // Launch the PiP activity fixed as landscape, but don't enter PiP pipApp.launchViaIntent( - wmHelper, - stringExtras = - mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()) + wmHelper, + stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()) ) } } @@ -207,13 +205,13 @@ open class EnterPipToOtherOrientation(flicker: FlickerTest) : PipTransition(flic /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + return LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt index 58416660826f..92642197e9be 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt @@ -17,9 +17,10 @@ package com.android.wm.shell.flicker.pip import android.tools.common.Rotation +import android.tools.common.flicker.assertions.FlickerTest import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -29,19 +30,19 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterPipToOtherOrientationCfArm(flicker: FlickerTest) : +open class EnterPipToOtherOrientationCfArm(flicker: LegacyFlickerTest) : EnterPipToOtherOrientation(flicker) { companion object { /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + return LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt index cdbdb85a9195..6d20740e239c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt @@ -20,16 +20,14 @@ import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import org.junit.Test import org.junit.runners.Parameterized -abstract class EnterPipTransition(flicker: FlickerTest) : PipTransition(flicker) { +abstract class EnterPipTransition(flicker: LegacyFlickerTest) : PipTransition(flicker) { override val defaultEnterPip: FlickerBuilder.() -> Unit = { - setup { - pipApp.launchViaIntent(wmHelper) - } + setup { pipApp.launchViaIntent(wmHelper) } } /** Checks [pipApp] window remains visible throughout the animation */ @@ -126,15 +124,14 @@ abstract class EnterPipTransition(flicker: FlickerTest) : PipTransition(flicker) /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation - * and navigation modes. + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen + * orientation and navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt index 95725b64a48a..76c811cbbeea 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt @@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.pip import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.filters.RequiresDevice import org.junit.FixMethodOrder import org.junit.runner.RunWith @@ -50,7 +50,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterPipViaAppUiButtonTest(flicker: FlickerTest) : EnterPipTransition(flicker) { +open class EnterPipViaAppUiButtonTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { pipApp.clickEnterPipButton(wmHelper) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt index 4390f0bb70b2..78e80497747c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt @@ -18,8 +18,8 @@ package com.android.wm.shell.flicker.pip import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -28,20 +28,20 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class EnterPipViaAppUiButtonTestCfArm(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(flicker) { +class EnterPipViaAppUiButtonTestCfArm(flicker: LegacyFlickerTest) : + EnterPipViaAppUiButtonTest(flicker) { companion object { /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation - * and navigation modes. + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen + * orientation and navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt index 5ac9829b6c8f..dfffba831dc3 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt @@ -19,14 +19,14 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.common.traces.component.ComponentNameMatcher -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import com.android.server.wm.flicker.helpers.SimpleAppHelper import org.junit.Test import org.junit.runners.Parameterized /** Base class for pip expand tests */ -abstract class ExitPipToAppTransition(flicker: FlickerTest) : PipTransition(flicker) { +abstract class ExitPipToAppTransition(flicker: LegacyFlickerTest) : PipTransition(flicker) { protected val testApp = SimpleAppHelper(instrumentation) /** @@ -130,15 +130,14 @@ abstract class ExitPipToAppTransition(flicker: FlickerTest) : PipTransition(flic /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt index 0b3d16a8087d..b80b7483ba4d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt @@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.pip import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.filters.RequiresDevice import org.junit.FixMethodOrder import org.junit.runner.RunWith @@ -52,7 +52,8 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class ExitPipToAppViaExpandButtonTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) { +open class ExitPipToAppViaExpandButtonTest(flicker: LegacyFlickerTest) : + ExitPipToAppTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { setup { // launch an app behind the pip one diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt index eccb85d98798..e25c0d6eddc0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt @@ -18,8 +18,8 @@ package com.android.wm.shell.flicker.pip import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -28,21 +28,20 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class ExitPipToAppViaExpandButtonTestCfArm(flicker: FlickerTest) : +class ExitPipToAppViaExpandButtonTestCfArm(flicker: LegacyFlickerTest) : ExitPipToAppViaExpandButtonTest(flicker) { companion object { /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt index bb2d40becdc9..f003ed8a77e0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt @@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.pip import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.filters.RequiresDevice import org.junit.FixMethodOrder import org.junit.runner.RunWith @@ -51,7 +51,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class ExitPipToAppViaIntentTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) { +open class ExitPipToAppViaIntentTest(flicker: LegacyFlickerTest) : ExitPipToAppTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { setup { // launch an app behind the pip one diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt index 6ab6a1f0bb73..be19f3cd1970 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt @@ -18,8 +18,8 @@ package com.android.wm.shell.flicker.pip import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -28,20 +28,20 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class ExitPipToAppViaIntentTestCfArm(flicker: FlickerTest) : ExitPipToAppViaIntentTest(flicker) { +class ExitPipToAppViaIntentTestCfArm(flicker: LegacyFlickerTest) : + ExitPipToAppViaIntentTest(flicker) { companion object { /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation - * and navigation modes. + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen + * orientation and navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt index fd16b6ea6ada..a1d3a117482e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt @@ -21,8 +21,8 @@ import android.tools.common.Rotation import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import org.junit.FixMethodOrder import org.junit.Test @@ -55,7 +55,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class ExpandPipOnDoubleClickTest(flicker: FlickerTest) : PipTransition(flicker) { +open class ExpandPipOnDoubleClickTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { pipApp.doubleClickPipWindow(wmHelper) } } @@ -142,15 +142,14 @@ open class ExpandPipOnDoubleClickTest(flicker: FlickerTest) : PipTransition(flic /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt index c09623490041..3095cac94598 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt @@ -18,8 +18,8 @@ package com.android.wm.shell.flicker.pip import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -28,21 +28,20 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class ExpandPipOnDoubleClickTestTestCfArm(flicker: FlickerTest) : +class ExpandPipOnDoubleClickTestTestCfArm(flicker: LegacyFlickerTest) : ExpandPipOnDoubleClickTest(flicker) { companion object { /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt index 253aa4cae5c7..8c8d280aea9a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt @@ -20,8 +20,8 @@ import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import org.junit.FixMethodOrder import org.junit.Test @@ -34,7 +34,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class ExpandPipOnPinchOpenTest(flicker: FlickerTest) : PipTransition(flicker) { +open class ExpandPipOnPinchOpenTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.25f, 30) } } @@ -55,15 +55,14 @@ open class ExpandPipOnPinchOpenTest(flicker: FlickerTest) : PipTransition(flicke /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt index e064bf2ee921..1a1ce6823f3b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt @@ -18,8 +18,8 @@ package com.android.wm.shell.flicker.pip import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -28,20 +28,20 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class ExpandPipOnPinchOpenTestCfArm(flicker: FlickerTest) : ExpandPipOnPinchOpenTest(flicker) { +class ExpandPipOnPinchOpenTestCfArm(flicker: LegacyFlickerTest) : + ExpandPipOnPinchOpenTest(flicker) { companion object { /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt index 094060f86691..4f88184e3c20 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt @@ -19,7 +19,7 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.Direction import org.junit.FixMethodOrder @@ -55,7 +55,8 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class MovePipDownOnShelfHeightChange(flicker: FlickerTest) : MovePipShelfHeightTransition(flicker) { +class MovePipDownOnShelfHeightChange(flicker: LegacyFlickerTest) : + MovePipShelfHeightTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { teardown { testApp.exit(wmHelper) } transitions { testApp.launchViaIntent(wmHelper) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt index ff51c27bf116..dffc822e7aec 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt @@ -18,11 +18,12 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.tools.common.Rotation +import android.tools.common.flicker.assertions.FlickerTest import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.helpers.WindowUtils import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.ImeAppHelper @@ -38,7 +39,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class MovePipOnImeVisibilityChangeTest(flicker: FlickerTest) : PipTransition(flicker) { +open class MovePipOnImeVisibilityChangeTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { private val imeApp = ImeAppHelper(instrumentation) override val thisTransition: FlickerBuilder.() -> Unit = { @@ -80,7 +81,7 @@ open class MovePipOnImeVisibilityChangeTest(flicker: FlickerTest) : PipTransitio @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + return LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt index d3d77d20662e..63292a4f2ca3 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt @@ -17,9 +17,10 @@ package com.android.wm.shell.flicker.pip import android.tools.common.Rotation +import android.tools.common.flicker.assertions.FlickerTest import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -28,7 +29,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class MovePipOnImeVisibilityChangeTestCfArm(flicker: FlickerTest) : +class MovePipOnImeVisibilityChangeTestCfArm(flicker: LegacyFlickerTest) : MovePipOnImeVisibilityChangeTest(flicker) { companion object { private const val TAG_IME_VISIBLE = "imeIsVisible" @@ -36,7 +37,7 @@ class MovePipOnImeVisibilityChangeTestCfArm(flicker: FlickerTest) : @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + return LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt index 109354ab5c79..9a2fa095c8cb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt @@ -19,15 +19,15 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.common.flicker.subject.region.RegionSubject -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper import com.android.wm.shell.flicker.Direction import org.junit.Test import org.junit.runners.Parameterized /** Base class for pip tests with Launcher shelf height change */ -abstract class MovePipShelfHeightTransition(flicker: FlickerTest) : PipTransition(flicker) { +abstract class MovePipShelfHeightTransition(flicker: LegacyFlickerTest) : PipTransition(flicker) { protected val testApp = FixedOrientationAppHelper(instrumentation) /** Checks [pipApp] window remains visible throughout the animation */ @@ -111,15 +111,14 @@ abstract class MovePipShelfHeightTransition(flicker: FlickerTest) : PipTransitio /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt index 27b061b67a85..afb4af6c5b21 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt @@ -19,7 +19,7 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.Direction import org.junit.FixMethodOrder @@ -55,14 +55,13 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class MovePipUpOnShelfHeightChangeTest(flicker: FlickerTest) : +open class MovePipUpOnShelfHeightChangeTest(flicker: LegacyFlickerTest) : MovePipShelfHeightTransition(flicker) { - override val thisTransition: FlickerBuilder.() -> Unit = - { - setup { testApp.launchViaIntent(wmHelper) } - transitions { tapl.pressHome() } - teardown { testApp.exit(wmHelper) } - } + override val thisTransition: FlickerBuilder.() -> Unit = { + setup { testApp.launchViaIntent(wmHelper) } + transitions { tapl.pressHome() } + teardown { testApp.exit(wmHelper) } + } /** Checks that the visible region of [pipApp] window always moves up during the animation. */ @Presubmit @Test fun pipWindowMovesUp() = pipWindowMoves(Direction.UP) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt index 9f81ba8eee87..7085d559f3f2 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt @@ -20,8 +20,8 @@ import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.RequiresDevice import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import com.android.server.wm.flicker.testapp.ActivityOptions import org.junit.FixMethodOrder import org.junit.Test @@ -34,7 +34,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class PipDragTest(flicker: FlickerTest) : PipTransition(flicker) { +class PipDragTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { private var isDraggedLeft: Boolean = true override val thisTransition: FlickerBuilder.() -> Unit = { @@ -81,13 +81,11 @@ class PipDragTest(flicker: FlickerTest) : PipTransition(flicker) { /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt index 9fe9f52fd4af..2b87766daab3 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt @@ -21,8 +21,8 @@ import android.platform.test.annotations.Postsubmit import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.setRotation @@ -38,7 +38,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class PipDragThenSnapTest(flicker: FlickerTest) : PipTransition(flicker) { +class PipDragThenSnapTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { // represents the direction in which the pip window should be snapping private var willSnapRight: Boolean = true @@ -99,15 +99,14 @@ class PipDragThenSnapTest(flicker: FlickerTest) : PipTransition(flicker) { /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt index 60bf5ffdc7af..adc5ee32cdd3 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt @@ -21,8 +21,8 @@ import android.platform.test.annotations.Postsubmit import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import org.junit.FixMethodOrder import org.junit.Test @@ -36,7 +36,7 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @FlakyTest(bugId = 270677470) -class PipPinchInTest(flicker: FlickerTest) : PipTransition(flicker) { +class PipPinchInTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { override val thisTransition: FlickerBuilder.() -> Unit = { transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) } } @@ -57,15 +57,14 @@ class PipPinchInTest(flicker: FlickerTest) : PipTransition(flicker) { /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt index 17a178f78de3..096af39488e9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt @@ -22,7 +22,7 @@ import android.platform.test.annotations.Presubmit import android.tools.common.Rotation import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome import android.tools.device.helpers.WindowUtils import com.android.server.wm.flicker.helpers.PipAppHelper @@ -32,7 +32,7 @@ import com.android.wm.shell.flicker.BaseTest import com.google.common.truth.Truth import org.junit.Test -abstract class PipTransition(flicker: FlickerTest) : BaseTest(flicker) { +abstract class PipTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) { protected val pipApp = PipAppHelper(instrumentation) protected val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation) protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation) @@ -78,16 +78,16 @@ abstract class PipTransition(flicker: FlickerTest) : BaseTest(flicker) { /** Defines the default method of entering PiP */ protected open val defaultEnterPip: FlickerBuilder.() -> Unit = { setup { - pipApp.launchViaIntentAndWaitForPip(wmHelper, - stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true")) + pipApp.launchViaIntentAndWaitForPip( + wmHelper, + stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true") + ) } } /** Defines the default teardown required to clean up after the test */ protected open val defaultTeardown: FlickerBuilder.() -> Unit = { - teardown { - pipApp.exit(wmHelper) - } + teardown { pipApp.exit(wmHelper) } } @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt index c618e5a24fdf..c315e744bd55 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt @@ -21,10 +21,11 @@ import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.tools.common.Rotation +import android.tools.common.flicker.assertions.FlickerTest import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.helpers.WindowUtils import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.testapp.ActivityOptions @@ -46,7 +47,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class SetRequestedOrientationWhilePinned(flicker: FlickerTest) : PipTransition(flicker) { +open class SetRequestedOrientationWhilePinned(flicker: LegacyFlickerTest) : PipTransition(flicker) { private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90) @@ -69,20 +70,19 @@ open class SetRequestedOrientationWhilePinned(flicker: FlickerTest) : PipTransit setup { // Launch the PiP activity fixed as landscape. pipApp.launchViaIntent( - wmHelper, - stringExtras = - mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()) + wmHelper, + stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()) ) // Enter PiP. broadcastActionTrigger.doAction(ActivityOptions.Pip.ACTION_ENTER_PIP) // System bar may fade out during fixed rotation. wmHelper - .StateSyncBuilder() - .withPipShown() - .withRotation(Rotation.ROTATION_0) - .withNavOrTaskBarVisible() - .withStatusBarVisible() - .waitForAndVerify() + .StateSyncBuilder() + .withPipShown() + .withRotation(Rotation.ROTATION_0) + .withNavOrTaskBarVisible() + .withStatusBarVisible() + .waitForAndVerify() } } @@ -150,7 +150,7 @@ open class SetRequestedOrientationWhilePinned(flicker: FlickerTest) : PipTransit @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + return LegacyFlickerTestFactory.nonRotationTests( supportedRotations = listOf(Rotation.ROTATION_0) ) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt index 43d6c8f26126..0ff9cfff873e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt @@ -17,10 +17,11 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit +import android.tools.common.flicker.assertions.FlickerTest import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.helpers.WindowUtils import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.SimpleAppHelper @@ -58,7 +59,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class ShowPipAndRotateDisplay(flicker: FlickerTest) : PipTransition(flicker) { +open class ShowPipAndRotateDisplay(flicker: LegacyFlickerTest) : PipTransition(flicker) { private val testApp = SimpleAppHelper(instrumentation) private val screenBoundsStart = WindowUtils.getDisplayBounds(flicker.scenario.startRotation) private val screenBoundsEnd = WindowUtils.getDisplayBounds(flicker.scenario.endRotation) @@ -154,13 +155,13 @@ open class ShowPipAndRotateDisplay(flicker: FlickerTest) : PipTransition(flicker /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation - * and navigation modes. + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen + * orientation and navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTest> { - return FlickerTestFactory.rotationTests() + return LegacyFlickerTestFactory.rotationTests() } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt index b7a2c47e3b32..25164711b2e5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt @@ -16,9 +16,10 @@ package com.android.wm.shell.flicker.pip +import android.tools.common.flicker.assertions.FlickerTest import android.tools.device.flicker.junit.FlickerParametersRunnerFactory -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -27,18 +28,18 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class ShowPipAndRotateDisplayCfArm(flicker: FlickerTest) : ShowPipAndRotateDisplay(flicker) { +class ShowPipAndRotateDisplayCfArm(flicker: LegacyFlickerTest) : ShowPipAndRotateDisplay(flicker) { companion object { /** * Creates the test configurations. * - * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation - * and navigation modes. + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen + * orientation and navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTest> { - return FlickerTestFactory.rotationTests() + return LegacyFlickerTestFactory.rotationTests() } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt index d1f0980786bf..a43ad9b4dd39 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt @@ -23,8 +23,8 @@ import android.tools.common.traces.component.ComponentNameMatcher import android.tools.common.traces.component.EdgeExtensionComponentMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.ICommonAssertions import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT @@ -45,13 +45,13 @@ import org.junit.runners.Parameterized /** * Test copy content from the left to the right side of the split-screen. * - * To run this test: `atest WMShellFlickerTests:CopyContentInSplit` + * To run this test: `atest WMShellFlickerTestsSplitScreen:CopyContentInSplit` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class CopyContentInSplit(override val flicker: FlickerTest) : +class CopyContentInSplit(override val flicker: LegacyFlickerTest) : CopyContentInSplitBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit get() = { @@ -136,8 +136,6 @@ class CopyContentInSplit(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt index 4505b9978b76..0b8f109b0e42 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt @@ -21,7 +21,7 @@ import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.helpers.WindowUtils import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.ICommonAssertions @@ -41,13 +41,13 @@ import org.junit.runners.Parameterized /** * Test dismiss split screen by dragging the divider bar. * - * To run this test: `atest WMShellFlickerTests:DismissSplitScreenByDivider` + * To run this test: `atest WMShellFlickerTestsSplitScreen:DismissSplitScreenByDivider` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class DismissSplitScreenByDivider(override val flicker: FlickerTest) : +class DismissSplitScreenByDivider(override val flicker: LegacyFlickerTest) : DismissSplitScreenByDividerBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt index e05b22141e4d..38d4b4029c64 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt @@ -20,8 +20,8 @@ import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.ICommonAssertions import com.android.wm.shell.flicker.appWindowBecomesInvisible @@ -38,13 +38,13 @@ import org.junit.runners.Parameterized /** * Test dismiss split screen by go home. * - * To run this test: `atest WMShellFlickerTests:DismissSplitScreenByGoHome` + * To run this test: `atest WMShellFlickerTestsSplitScreen:DismissSplitScreenByGoHome` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class DismissSplitScreenByGoHome(override val flicker: FlickerTest) : +class DismissSplitScreenByGoHome(override val flicker: LegacyFlickerTest) : DismissSplitScreenByGoHomeBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit get() = { @@ -154,8 +154,6 @@ class DismissSplitScreenByGoHome(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt index 63c5d14d85e1..a118c08b35e2 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt @@ -21,8 +21,8 @@ import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.ICommonAssertions import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT @@ -43,13 +43,13 @@ import org.junit.runners.Parameterized /** * Test resize split by dragging the divider bar. * - * To run this test: `atest WMShellFlickerTests:DragDividerToResize` + * To run this test: `atest WMShellFlickerTestsSplitScreen:DragDividerToResize` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class DragDividerToResize(override val flicker: FlickerTest) : +class DragDividerToResize(override val flicker: LegacyFlickerTest) : DragDividerToResizeBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit get() = { @@ -127,8 +127,6 @@ class DragDividerToResize(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt index e55868675da7..05c048050b3b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt @@ -22,8 +22,8 @@ import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.ICommonAssertions import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT @@ -45,13 +45,13 @@ import org.junit.runners.Parameterized * Test enter split screen by dragging app icon from all apps. This test is only for large screen * devices. * - * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromAllApps` + * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenByDragFromAllApps` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class EnterSplitScreenByDragFromAllApps(override val flicker: FlickerTest) : +class EnterSplitScreenByDragFromAllApps(override val flicker: LegacyFlickerTest) : EnterSplitScreenByDragFromAllAppsBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit get() = { @@ -160,11 +160,10 @@ class EnterSplitScreenByDragFromAllApps(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt index ab8ecc54e71c..3a75fa60a865 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt @@ -22,8 +22,8 @@ import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.ICommonAssertions import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT @@ -44,13 +44,13 @@ import org.junit.runners.Parameterized * Test enter split screen by dragging app icon from notification. This test is only for large * screen devices. * - * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromNotification` + * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenByDragFromNotification` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class EnterSplitScreenByDragFromNotification(override val flicker: FlickerTest) : +class EnterSplitScreenByDragFromNotification(override val flicker: LegacyFlickerTest) : EnterSplitScreenByDragFromNotificationBenchmark(flicker), ICommonAssertions { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit @@ -162,11 +162,10 @@ class EnterSplitScreenByDragFromNotification(override val flicker: FlickerTest) companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt index 516ca97bc531..6d73f92637db 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt @@ -21,8 +21,8 @@ import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.ICommonAssertions import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd @@ -41,13 +41,13 @@ import org.junit.runners.Parameterized /** * Test enter split screen by dragging a shortcut. This test is only for large screen devices. * - * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromShortcut` + * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenByDragFromShortcut` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class EnterSplitScreenByDragFromShortcut(override val flicker: FlickerTest) : +class EnterSplitScreenByDragFromShortcut(override val flicker: LegacyFlickerTest) : EnterSplitScreenByDragFromShortcutBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit @@ -105,11 +105,10 @@ class EnterSplitScreenByDragFromShortcut(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt index 4af7e248b660..15cae6947f88 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt @@ -22,8 +22,8 @@ import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.ICommonAssertions import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT @@ -45,13 +45,13 @@ import org.junit.runners.Parameterized * Test enter split screen by dragging app icon from taskbar. This test is only for large screen * devices. * - * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromTaskbar` + * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenByDragFromTaskbar` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class EnterSplitScreenByDragFromTaskbar(override val flicker: FlickerTest) : +class EnterSplitScreenByDragFromTaskbar(override val flicker: LegacyFlickerTest) : EnterSplitScreenByDragFromTaskbarBenchmark(flicker), ICommonAssertions { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit @@ -163,10 +163,9 @@ class EnterSplitScreenByDragFromTaskbar(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt index faad9e82ffef..90399fca2574 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt @@ -20,8 +20,8 @@ import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.ICommonAssertions import com.android.wm.shell.flicker.appWindowBecomesVisible @@ -40,13 +40,13 @@ import org.junit.runners.Parameterized /** * Test enter split screen from Overview. * - * To run this test: `atest WMShellFlickerTests:EnterSplitScreenFromOverview` + * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenFromOverview` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class EnterSplitScreenFromOverview(override val flicker: FlickerTest) : +class EnterSplitScreenFromOverview(override val flicker: LegacyFlickerTest) : EnterSplitScreenFromOverviewBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit get() = { @@ -106,8 +106,6 @@ class EnterSplitScreenFromOverview(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt index 195b73a14a72..580b153421a4 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt @@ -18,11 +18,11 @@ package com.android.wm.shell.flicker.splitscreen import android.content.Context import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTest import com.android.server.wm.flicker.helpers.setRotation import com.android.wm.shell.flicker.BaseBenchmarkTest -abstract class SplitScreenBase(flicker: FlickerTest) : BaseBenchmarkTest(flicker) { +abstract class SplitScreenBase(flicker: LegacyFlickerTest) : BaseBenchmarkTest(flicker) { protected val context: Context = instrumentation.context protected val primaryApp = SplitScreenUtils.getPrimary(instrumentation) protected val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt index 1063dfd8d737..f4828f14d5ed 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt @@ -39,6 +39,7 @@ import com.android.server.wm.flicker.helpers.NonResizeableAppHelper import com.android.server.wm.flicker.helpers.NotificationAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.flicker.testapp.ActivityOptions.SplitScreen.Primary import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME import org.junit.Assert.assertNotNull @@ -112,6 +113,17 @@ internal object SplitScreenUtils { waitForSplitComplete(wmHelper, primaryApp, secondaryApp) } + fun enterSplitViaIntent( + wmHelper: WindowManagerStateHelper, + primaryApp: StandardAppHelper, + secondaryApp: StandardAppHelper + ) { + val stringExtras = mapOf(Primary.EXTRA_LAUNCH_ADJACENT to "true") + primaryApp.launchViaIntent(wmHelper, null, null, + stringExtras) + waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) { // Note: The initial split position in landscape is different between tablet and phone. // In landscape, tablet will let the first app split to right side, and phone will @@ -314,14 +326,14 @@ internal object SplitScreenUtils { dividerBar.drag( Point( if (dragToRight) { - displayBounds.width * 4 / 5 + displayBounds.right } else { - displayBounds.width * 1 / 5 + displayBounds.left }, if (dragToBottom) { - displayBounds.height * 4 / 5 + displayBounds.bottom } else { - displayBounds.height * 1 / 5 + displayBounds.top } ) ) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt index 03b8a75a1f32..e0a47b394ba1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt @@ -22,8 +22,8 @@ import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.ICommonAssertions import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT @@ -44,13 +44,13 @@ import org.junit.runners.Parameterized /** * Test double tap the divider bar to switch the two apps. * - * To run this test: `atest WMShellFlickerTests:SwitchAppByDoubleTapDivider` + * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchAppByDoubleTapDivider` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class SwitchAppByDoubleTapDivider(override val flicker: FlickerTest) : +class SwitchAppByDoubleTapDivider(override val flicker: LegacyFlickerTest) : SwitchAppByDoubleTapDividerBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit get() = { @@ -120,11 +120,10 @@ class SwitchAppByDoubleTapDivider(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt index 078d95de1dd0..a4060092b422 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt @@ -21,8 +21,8 @@ import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.ICommonAssertions import com.android.wm.shell.flicker.appWindowBecomesVisible @@ -39,13 +39,13 @@ import org.junit.runners.Parameterized /** * Test quick switch to split pair from another app. * - * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromAnotherApp` + * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchBackToSplitFromAnotherApp` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class SwitchBackToSplitFromAnotherApp(override val flicker: FlickerTest) : +class SwitchBackToSplitFromAnotherApp(override val flicker: LegacyFlickerTest) : SwitchBackToSplitFromAnotherAppBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit get() = { @@ -149,11 +149,10 @@ class SwitchBackToSplitFromAnotherApp(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt index 7c84243e00d7..251bd1030da3 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt @@ -21,8 +21,8 @@ import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.ICommonAssertions import com.android.wm.shell.flicker.appWindowBecomesVisible @@ -39,13 +39,13 @@ import org.junit.runners.Parameterized /** * Test quick switch to split pair from home. * - * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromHome` + * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchBackToSplitFromHome` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class SwitchBackToSplitFromHome(override val flicker: FlickerTest) : +class SwitchBackToSplitFromHome(override val flicker: LegacyFlickerTest) : SwitchBackToSplitFromHomeBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit get() = { @@ -149,11 +149,10 @@ class SwitchBackToSplitFromHome(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt index 7c46d3e099a2..1dd45fef30cc 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt @@ -21,8 +21,8 @@ import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.ICommonAssertions import com.android.wm.shell.flicker.appWindowBecomesVisible @@ -39,13 +39,13 @@ import org.junit.runners.Parameterized /** * Test switch back to split pair from recent. * - * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromRecent` + * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchBackToSplitFromRecent` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class SwitchBackToSplitFromRecent(override val flicker: FlickerTest) : +class SwitchBackToSplitFromRecent(override val flicker: LegacyFlickerTest) : SwitchBackToSplitFromRecentBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit get() = { @@ -149,11 +149,10 @@ class SwitchBackToSplitFromRecent(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt index 674ba40f6a1f..8f867df3fea1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt @@ -21,8 +21,8 @@ import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.ICommonAssertions import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT @@ -47,13 +47,13 @@ import org.junit.runners.Parameterized /** * Test quick switch between two split pairs. * - * To run this test: `atest WMShellFlickerTests:SwitchBetweenSplitPairs` + * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchBetweenSplitPairs` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class SwitchBetweenSplitPairs(override val flicker: FlickerTest) : +class SwitchBetweenSplitPairs(override val flicker: LegacyFlickerTest) : SwitchBetweenSplitPairsBenchmark(flicker), ICommonAssertions { override val transition: FlickerBuilder.() -> Unit get() = { @@ -223,8 +223,6 @@ class SwitchBetweenSplitPairs(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt index 676c150815ad..994d6cbfaa1f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt @@ -21,8 +21,8 @@ import android.tools.common.NavBar import android.tools.common.flicker.subject.region.RegionSubject import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.ICommonAssertions import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT @@ -37,21 +37,21 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test unlocking insecure keyguard to back to split screen tasks and verify the transition behavior. + * Test unlocking insecure keyguard to back to split screen tasks and verify the transition + * behavior. * - * To run this test: `atest WMShellFlickerTests:UnlockKeyguardToSplitScreen` + * To run this test: `atest WMShellFlickerTestsSplitScreen:UnlockKeyguardToSplitScreen` */ @RequiresDevice @Postsubmit @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class UnlockKeyguardToSplitScreen(override val flicker: FlickerTest) : - UnlockKeyguardToSplitScreenBenchmark(flicker), ICommonAssertions { +class UnlockKeyguardToSplitScreen(override val flicker: LegacyFlickerTest) : + UnlockKeyguardToSplitScreenBenchmark(flicker), ICommonAssertions { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = { - defaultSetup(this) defaultTeardown(this) thisTransition(this) } @@ -65,33 +65,35 @@ class UnlockKeyguardToSplitScreen(override val flicker: FlickerTest) : @Test fun primaryAppBoundsIsVisibleAtEnd() = - flicker.splitAppLayerBoundsIsVisibleAtEnd( - primaryApp, - landscapePosLeft = false, - portraitPosTop = false - ) + flicker.splitAppLayerBoundsIsVisibleAtEnd( + primaryApp, + landscapePosLeft = false, + portraitPosTop = false + ) @Test fun secondaryAppBoundsIsVisibleAtEnd() = - flicker.splitAppLayerBoundsIsVisibleAtEnd( - secondaryApp, - landscapePosLeft = true, - portraitPosTop = true - ) + flicker.splitAppLayerBoundsIsVisibleAtEnd( + secondaryApp, + landscapePosLeft = true, + portraitPosTop = true + ) - @Test - fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp) + @Test fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp) - @Test - fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(secondaryApp) + @Test fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(secondaryApp) @Test fun notOverlapsForPrimaryAndSecondaryAppLayers() { flicker.assertLayers { this.invoke("notOverlapsForPrimaryAndSecondaryLayers") { - val primaryAppRegions = it.subjects.filter { subject -> - subject.name.contains(primaryApp.toLayerName()) && subject.isVisible - }.mapNotNull { primaryApp -> primaryApp.layer.visibleRegion }.toTypedArray() + val primaryAppRegions = + it.subjects + .filter { subject -> + subject.name.contains(primaryApp.toLayerName()) && subject.isVisible + } + .mapNotNull { primaryApp -> primaryApp.layer.visibleRegion } + .toTypedArray() val primaryAppRegionArea = RegionSubject(primaryAppRegions, it.timestamp) it.visibleRegion(secondaryApp).notOverlaps(primaryAppRegionArea.region) @@ -102,10 +104,9 @@ class UnlockKeyguardToSplitScreen(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( - supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt index c3c5f88eaa29..d1ca9eac198d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt @@ -21,8 +21,8 @@ import android.platform.test.annotations.Presubmit import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils @@ -36,7 +36,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class CopyContentInSplitBenchmark(override val flicker: FlickerTest) : +open class CopyContentInSplitBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val textEditApp = SplitScreenUtils.getIme(instrumentation) protected val magnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#") @@ -72,8 +72,6 @@ open class CopyContentInSplitBenchmark(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt index 37cd18fad521..73acb1f0cc47 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt @@ -20,8 +20,8 @@ import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenDismissed import com.android.wm.shell.flicker.splitscreen.SplitScreenBase @@ -36,7 +36,8 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class DismissSplitScreenByDividerBenchmark(flicker: FlickerTest) : SplitScreenBase(flicker) { +open class DismissSplitScreenByDividerBenchmark(override val flicker: LegacyFlickerTest) : + SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) } @@ -76,8 +77,6 @@ open class DismissSplitScreenByDividerBenchmark(flicker: FlickerTest) : SplitScr companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt index 0ec6dc96bcd9..86ffd2af6748 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt @@ -20,8 +20,8 @@ import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenDismissed import com.android.wm.shell.flicker.splitscreen.SplitScreenBase @@ -36,7 +36,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class DismissSplitScreenByGoHomeBenchmark(override val flicker: FlickerTest) : +open class DismissSplitScreenByGoHomeBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -63,8 +63,6 @@ open class DismissSplitScreenByGoHomeBenchmark(override val flicker: FlickerTest companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt index 190e2e765bcc..dfde3b669813 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt @@ -20,8 +20,8 @@ import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils @@ -37,7 +37,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class DragDividerToResizeBenchmark(override val flicker: FlickerTest) : +open class DragDividerToResizeBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -69,8 +69,6 @@ open class DragDividerToResizeBenchmark(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt index 3a1d1a4415c3..d13e4134a961 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt @@ -21,8 +21,8 @@ import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase @@ -39,7 +39,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: FlickerTest) : +open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit @@ -84,11 +84,10 @@ open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: Flic companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt index 2033b7d64416..1d4166922b13 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt @@ -21,8 +21,8 @@ import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase @@ -39,8 +39,9 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterSplitScreenByDragFromNotificationBenchmark(override val flicker: FlickerTest) : - SplitScreenBase(flicker) { +open class EnterSplitScreenByDragFromNotificationBenchmark( + override val flicker: LegacyFlickerTest +) : SplitScreenBase(flicker) { protected val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation) protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -81,11 +82,10 @@ open class EnterSplitScreenByDragFromNotificationBenchmark(override val flicker: companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt index b7a7110afe25..b4bafa79cd48 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt @@ -21,8 +21,8 @@ import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase @@ -39,7 +39,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterSplitScreenByDragFromShortcutBenchmark(flicker: FlickerTest) : +open class EnterSplitScreenByDragFromShortcutBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { @Before fun before() { @@ -84,11 +84,10 @@ open class EnterSplitScreenByDragFromShortcutBenchmark(flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt index b1ce62f99e6c..da44ecdb9304 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt @@ -21,8 +21,8 @@ import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase @@ -39,7 +39,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: FlickerTest) : +open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -84,10 +84,9 @@ open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: Flic companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt index 14f07453b7d1..af06d6da2518 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt @@ -20,8 +20,8 @@ import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase @@ -36,7 +36,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class EnterSplitScreenFromOverviewBenchmark(override val flicker: FlickerTest) : +open class EnterSplitScreenFromOverviewBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -72,8 +72,6 @@ open class EnterSplitScreenFromOverviewBenchmark(override val flicker: FlickerTe companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt index 65fb1358a9b0..23156b5d1628 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt @@ -22,8 +22,8 @@ import android.tools.common.NavBar import android.tools.common.Rotation import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.helpers.WindowUtils import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.filters.RequiresDevice @@ -39,7 +39,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: FlickerTest) : +open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -145,11 +145,10 @@ open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: FlickerTes companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt index b333aba447a2..2d810d3e2631 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt @@ -21,8 +21,8 @@ import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase @@ -37,7 +37,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: FlickerTest) : +open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation) @@ -71,11 +71,10 @@ open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: Flicke companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt index a27540efdad7..f6df1e42d1b7 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt @@ -21,8 +21,8 @@ import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase @@ -37,7 +37,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class SwitchBackToSplitFromHomeBenchmark(override val flicker: FlickerTest) : +open class SwitchBackToSplitFromHomeBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -69,11 +69,10 @@ open class SwitchBackToSplitFromHomeBenchmark(override val flicker: FlickerTest) companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt index 18bf4ff054e0..ba46bdcdad21 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt @@ -21,8 +21,8 @@ import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase @@ -37,7 +37,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class SwitchBackToSplitFromRecentBenchmark(override val flicker: FlickerTest) : +open class SwitchBackToSplitFromRecentBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { @@ -69,11 +69,10 @@ open class SwitchBackToSplitFromRecentBenchmark(override val flicker: FlickerTes companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt index c5fe61e26733..0d871e500688 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt @@ -20,8 +20,8 @@ import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils @@ -35,7 +35,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class SwitchBetweenSplitPairsBenchmark(override val flicker: FlickerTest) : +open class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlickerTest) : SplitScreenBase(flicker) { protected val thirdApp = SplitScreenUtils.getIme(instrumentation) protected val fourthApp = SplitScreenUtils.getSendNotification(instrumentation) @@ -70,8 +70,6 @@ open class SwitchBetweenSplitPairsBenchmark(override val flicker: FlickerTest) : companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests() - } + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt index 5f16e5b4d65e..7952b7125a34 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt @@ -19,8 +19,8 @@ package com.android.wm.shell.flicker.splitscreen.benchmark import android.tools.common.NavBar import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder -import android.tools.device.flicker.legacy.FlickerTest -import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitscreen.SplitScreenBase import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils @@ -33,11 +33,11 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -open class UnlockKeyguardToSplitScreenBenchmark(override val flicker: FlickerTest) : - SplitScreenBase(flicker) { +open class UnlockKeyguardToSplitScreenBenchmark(override val flicker: LegacyFlickerTest) : + SplitScreenBase(flicker) { protected val thisTransition: FlickerBuilder.() -> Unit get() = { - setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) } + setup { SplitScreenUtils.enterSplitViaIntent(wmHelper, primaryApp, secondaryApp) } transitions { device.sleep() wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() @@ -58,10 +58,9 @@ open class UnlockKeyguardToSplitScreenBenchmark(override val flicker: FlickerTes companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTest> { - return FlickerTestFactory.nonRotationTests( - supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) ) - } } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto new file mode 100644 index 000000000000..406ada97a07d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto @@ -0,0 +1,75 @@ +# 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. + +# proto-message: TraceConfig + +# Enable periodic flushing of the trace buffer into the output file. +write_into_file: true + +# Writes the userspace buffer into the file every 1s. +file_write_period_ms: 2500 + +# See b/126487238 - we need to guarantee ordering of events. +flush_period_ms: 30000 + +# The trace buffers needs to be big enough to hold |file_write_period_ms| of +# trace data. The trace buffer sizing depends on the number of trace categories +# enabled and the device activity. + +# RSS events +buffers: { + size_kb: 63488 + fill_policy: RING_BUFFER +} + +data_sources { + config { + name: "linux.process_stats" + target_buffer: 0 + # polled per-process memory counters and process/thread names. + # If you don't want the polled counters, remove the "process_stats_config" + # section, but keep the data source itself as it still provides on-demand + # thread/process naming for ftrace data below. + process_stats_config { + scan_all_processes_on_start: true + } + } +} + +data_sources: { + config { + name: "linux.ftrace" + ftrace_config { + ftrace_events: "ftrace/print" + ftrace_events: "task/task_newtask" + ftrace_events: "task/task_rename" + atrace_categories: "ss" + atrace_categories: "wm" + atrace_categories: "am" + atrace_categories: "aidl" + atrace_categories: "input" + atrace_categories: "binder_driver" + atrace_categories: "sched_process_exit" + atrace_apps: "com.android.server.wm.flicker.testapp" + atrace_apps: "com.android.systemui" + atrace_apps: "com.android.wm.shell.flicker" + atrace_apps: "com.android.wm.shell.flicker.other" + atrace_apps: "com.android.wm.shell.flicker.bubbles" + atrace_apps: "com.android.wm.shell.flicker.pip" + atrace_apps: "com.android.wm.shell.flicker.splitscreen" + atrace_apps: "com.google.android.apps.nexuslauncher" + } + } +} + diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java index 8278c67a9b4f..0dc16f44340f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java @@ -64,18 +64,27 @@ public class BubbleOverflowTest extends ShellTestCase { } @Test - public void test_initialize() { + public void test_initialize_forStack() { assertThat(mOverflow.getExpandedView()).isNull(); - mOverflow.initialize(mBubbleController); + mOverflow.initialize(mBubbleController, /* forBubbleBar= */ false); assertThat(mOverflow.getExpandedView()).isNotNull(); assertThat(mOverflow.getExpandedView().getBubbleKey()).isEqualTo(BubbleOverflow.KEY); + assertThat(mOverflow.getBubbleBarExpandedView()).isNull(); + } + + @Test + public void test_initialize_forBubbleBar() { + mOverflow.initialize(mBubbleController, /* forBubbleBar= */ true); + + assertThat(mOverflow.getBubbleBarExpandedView()).isNotNull(); + assertThat(mOverflow.getExpandedView()).isNull(); } @Test public void test_cleanUpExpandedState() { - mOverflow.createExpandedView(); + mOverflow.initialize(mBubbleController, /* forBubbleBar= */ false); assertThat(mOverflow.getExpandedView()).isNotNull(); mOverflow.cleanUpExpandedState(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockToken.java index 09d474d1f97c..a97c19f17412 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockToken.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.desktopmode; +package com.android.wm.shell; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -25,16 +25,16 @@ import android.window.WindowContainerToken; /** * {@link WindowContainerToken} wrapper that supports a mock binder */ -class MockToken { +public class MockToken { private final WindowContainerToken mToken; - MockToken() { + public MockToken() { mToken = mock(WindowContainerToken.class); IBinder binder = mock(IBinder.class); when(mToken.asBinder()).thenReturn(binder); } - WindowContainerToken token() { + public WindowContainerToken token() { return mToken; } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt new file mode 100644 index 000000000000..6d9d62d3ad92 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt @@ -0,0 +1,190 @@ +/* + * 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 com.android.wm.shell.bubbles + +import android.app.ActivityTaskManager +import android.content.pm.LauncherApps +import android.content.pm.ShortcutInfo +import android.util.SparseArray +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.bubbles.storage.BubbleEntity +import com.android.wm.shell.bubbles.storage.BubblePersistentRepository +import com.android.wm.shell.common.ShellExecutor +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.mock +import org.mockito.Mockito.spy + +class BubbleDataRepositoryTest : ShellTestCase() { + + private val user0BubbleEntities = listOf( + BubbleEntity( + userId = 0, + packageName = "com.example.messenger", + shortcutId = "shortcut-1", + key = "0k1", + desiredHeight = 120, + desiredHeightResId = 0, + title = null, + taskId = 1, + locus = null, + isDismissable = true + ), + BubbleEntity( + userId = 10, + packageName = "com.example.chat", + shortcutId = "alice and bob", + key = "0k2", + desiredHeight = 0, + desiredHeightResId = 16537428, + title = "title", + taskId = 2, + locus = null + ), + BubbleEntity( + userId = 0, + packageName = "com.example.messenger", + shortcutId = "shortcut-2", + key = "0k3", + desiredHeight = 120, + desiredHeightResId = 0, + title = null, + taskId = ActivityTaskManager.INVALID_TASK_ID, + locus = null + ) + ) + + private val user1BubbleEntities = listOf( + BubbleEntity( + userId = 1, + packageName = "com.example.messenger", + shortcutId = "shortcut-1", + key = "1k1", + desiredHeight = 120, + desiredHeightResId = 0, + title = null, + taskId = 3, + locus = null, + isDismissable = true + ), + BubbleEntity( + userId = 12, + packageName = "com.example.chat", + shortcutId = "alice and bob", + key = "1k2", + desiredHeight = 0, + desiredHeightResId = 16537428, + title = "title", + taskId = 4, + locus = null + ), + BubbleEntity( + userId = 1, + packageName = "com.example.messenger", + shortcutId = "shortcut-2", + key = "1k3", + desiredHeight = 120, + desiredHeightResId = 0, + title = null, + taskId = ActivityTaskManager.INVALID_TASK_ID, + locus = null + ), + BubbleEntity( + userId = 12, + packageName = "com.example.chat", + shortcutId = "alice", + key = "1k4", + desiredHeight = 0, + desiredHeightResId = 16537428, + title = "title", + taskId = 5, + locus = null + ) + ) + + private val mainExecutor = mock(ShellExecutor::class.java) + private val launcherApps = mock(LauncherApps::class.java) + + private val persistedBubbles = SparseArray<List<BubbleEntity>>() + + private lateinit var dataRepository: BubbleDataRepository + private lateinit var persistentRepository: BubblePersistentRepository + + @Before + fun setup() { + persistentRepository = spy(BubblePersistentRepository(mContext)) + dataRepository = BubbleDataRepository(launcherApps, mainExecutor, persistentRepository) + + // Add the bubbles to the persistent repository + persistedBubbles.put(0, user0BubbleEntities) + persistedBubbles.put(1, user1BubbleEntities) + persistentRepository.persistsToDisk(persistedBubbles) + } + + @After + fun teardown() { + // Clean up any persisted bubbles for the next run + persistentRepository.persistsToDisk(SparseArray()) + } + + @Test + fun testLoadBubbles_invalidParent() { + val activeUserIds = listOf(10, 1, 12) // Missing user 0 in persistedBubbles + dataRepository.loadBubbles(1, activeUserIds) { + // Verify that user 0 has been removed from the persisted list + val entitiesByUser = persistentRepository.readFromDisk() + assertThat(entitiesByUser.get(0)).isNull() + } + } + + @Test + fun testLoadBubbles_invalidChild() { + val activeUserIds = listOf(0, 10, 1) // Missing user 1's child user 12 + dataRepository.loadBubbles(1, activeUserIds) { + // Build a list to compare against + val user1BubblesWithoutUser12 = mutableListOf<Bubble>() + val user1EntitiesWithoutUser12 = mutableListOf<BubbleEntity>() + for (entity in user1BubbleEntities) { + if (entity.userId != 12) { + user1BubblesWithoutUser12.add(entity.toBubble()) + user1EntitiesWithoutUser12.add(entity) + } + } + + // Verify that user 12 has been removed from the persisted list + val entitiesByUser = persistentRepository.readFromDisk() + assertThat(entitiesByUser.get(1)).isEqualTo(user1EntitiesWithoutUser12) + } + } + + private fun BubbleEntity.toBubble(): Bubble { + return Bubble( + key, + mock(ShortcutInfo::class.java), + desiredHeight, + desiredHeightResId, + title, + taskId, + locus, + isDismissable, + mainExecutor, + mock(Bubbles.BubbleMetadataFlagListener::class.java) + ) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java new file mode 100644 index 000000000000..d38b848fbb4d --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java @@ -0,0 +1,67 @@ +/* + * 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 com.android.wm.shell.bubbles.bar; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.graphics.drawable.ColorDrawable; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.core.content.ContextCompat; +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class BubbleBarHandleViewTest extends ShellTestCase { + private BubbleBarHandleView mHandleView; + + @Before + public void setup() { + mHandleView = new BubbleBarHandleView(mContext); + } + + @Test + public void testUpdateHandleColor_lightBg() { + mHandleView.updateHandleColor(false /* isRegionDark */, false /* animated */); + + assertTrue(mHandleView.getClipToOutline()); + assertTrue(mHandleView.getBackground() instanceof ColorDrawable); + ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground(); + assertEquals(bgDrawable.getColor(), + ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_dark)); + } + + @Test + public void testUpdateHandleColor_darkBg() { + mHandleView.updateHandleColor(true /* isRegionDark */, false /* animated */); + + assertTrue(mHandleView.getClipToOutline()); + assertTrue(mHandleView.getBackground() instanceof ColorDrawable); + ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground(); + assertEquals(bgDrawable.getColor(), + ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_light)); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt new file mode 100644 index 000000000000..9dc816b65d2e --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt @@ -0,0 +1,172 @@ +/* + * 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 com.android.wm.shell.common + +import android.os.IBinder +import android.testing.AndroidTestingRunner +import android.window.WindowContainerTransaction +import android.window.WindowContainerTransaction.HierarchyOp +import androidx.test.filters.SmallTest +import com.android.wm.shell.MockToken +import com.android.wm.shell.ShellTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.atLeastOnce +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class LaunchAdjacentControllerTest : ShellTestCase() { + + private lateinit var controller: LaunchAdjacentController + + @Mock private lateinit var syncQueue: SyncTransactionQueue + + @Before + fun setUp() { + controller = LaunchAdjacentController(syncQueue) + } + + @Test + fun newInstance_enabledByDefault() { + assertThat(controller.launchAdjacentEnabled).isTrue() + } + + @Test + fun setLaunchAdjacentRoot_launchAdjacentEnabled_setsFlagRoot() { + val token = MockToken().token() + controller.setLaunchAdjacentRoot(token) + val wct = getLatestTransactionOrFail() + assertThat(wct.getSetLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder()) + } + + @Test + fun setLaunchAdjacentRoot_launchAdjacentDisabled_doesNotUpdateFlagRoot() { + val token = MockToken().token() + controller.launchAdjacentEnabled = false + controller.setLaunchAdjacentRoot(token) + verify(syncQueue, never()).queue(any()) + } + + @Test + fun clearLaunchAdjacentRoot_launchAdjacentEnabled_clearsFlagRoot() { + val token = MockToken().token() + controller.setLaunchAdjacentRoot(token) + controller.clearLaunchAdjacentRoot() + val wct = getLatestTransactionOrFail() + assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder()) + } + + @Test + fun clearLaunchAdjacentRoot_launchAdjacentDisabled_clearsFlagRoot() { + val token = MockToken().token() + controller.setLaunchAdjacentRoot(token) + controller.launchAdjacentEnabled = false + clearInvocations(syncQueue) + + controller.clearLaunchAdjacentRoot() + val wct = getLatestTransactionOrFail() + assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder()) + } + + @Test + fun setLaunchAdjacentEnabled_wasDisabledWithContainerSet_setsFlagRoot() { + val token = MockToken().token() + controller.setLaunchAdjacentRoot(token) + controller.launchAdjacentEnabled = false + clearInvocations(syncQueue) + + controller.launchAdjacentEnabled = true + val wct = getLatestTransactionOrFail() + assertThat(wct.getSetLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder()) + } + + @Test + fun setLaunchAdjacentEnabled_containerNotSet_doesNotUpdateFlagRoot() { + controller.launchAdjacentEnabled = false + controller.launchAdjacentEnabled = true + verify(syncQueue, never()).queue(any()) + } + + @Test + fun setLaunchAdjacentEnabled_multipleTimes_setsFlagRootOnce() { + val token = MockToken().token() + controller.setLaunchAdjacentRoot(token) + controller.launchAdjacentEnabled = true + controller.launchAdjacentEnabled = true + // Only execute once + verify(syncQueue).queue(any()) + } + + @Test + fun setLaunchAdjacentDisabled_containerSet_clearsFlagRoot() { + val token = MockToken().token() + controller.setLaunchAdjacentRoot(token) + controller.launchAdjacentEnabled = false + val wct = getLatestTransactionOrFail() + assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder()) + } + + @Test + fun setLaunchAdjacentDisabled_containerNotSet_doesNotUpdateFlagRoot() { + controller.launchAdjacentEnabled = false + verify(syncQueue, never()).queue(any()) + } + + @Test + fun setLaunchAdjacentDisabled_multipleTimes_setsFlagRootOnce() { + val token = MockToken().token() + controller.setLaunchAdjacentRoot(token) + clearInvocations(syncQueue) + controller.launchAdjacentEnabled = false + controller.launchAdjacentEnabled = false + // Only execute once + verify(syncQueue).queue(any()) + } + + private fun getLatestTransactionOrFail(): WindowContainerTransaction { + val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + verify(syncQueue, atLeastOnce()).queue(arg.capture()) + return arg.allValues.last().also { assertThat(it).isNotNull() } + } +} + +private fun WindowContainerTransaction.getSetLaunchAdjacentFlagRootContainer(): IBinder { + return hierarchyOps + // Find the operation with the correct type + .filter { op -> op.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT } + // For set flag root operation, toTop is false + .filter { op -> !op.toTop } + .map { it.container } + .first() +} + +private fun WindowContainerTransaction.getClearLaunchAdjacentFlagRootContainer(): IBinder { + return hierarchyOps + // Find the operation with the correct type + .filter { op -> op.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT } + // For clear flag root operation, toTop is true + .filter { op -> op.toTop } + .map { it.container } + .first() +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java new file mode 100644 index 000000000000..145c8f0ab8af --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java @@ -0,0 +1,123 @@ +/* + * 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 com.android.wm.shell.common.split; + +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; + +import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CURSOR_HOVER_STATES_ENABLED; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.SystemClock; +import android.provider.DeviceConfig; +import android.view.InputDevice; +import android.view.InsetsState; +import android.view.MotionEvent; + +import androidx.test.annotation.UiThreadTest; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayImeController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Tests for {@link DividerView} */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DividerViewTest extends ShellTestCase { + private @Mock SplitWindowManager.ParentContainerCallbacks mCallbacks; + private @Mock SplitLayout.SplitLayoutHandler mSplitLayoutHandler; + private @Mock DisplayController mDisplayController; + private @Mock DisplayImeController mDisplayImeController; + private @Mock ShellTaskOrganizer mTaskOrganizer; + private SplitLayout mSplitLayout; + private DividerView mDividerView; + + @Before + @UiThreadTest + public void setup() { + MockitoAnnotations.initMocks(this); + Configuration configuration = getConfiguration(); + mSplitLayout = new SplitLayout("TestSplitLayout", mContext, configuration, + mSplitLayoutHandler, mCallbacks, mDisplayController, mDisplayImeController, + mTaskOrganizer, SplitLayout.PARALLAX_NONE); + SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager", + mContext, + configuration, mCallbacks); + splitWindowManager.init(mSplitLayout, new InsetsState()); + mDividerView = spy((DividerView) splitWindowManager.getDividerView()); + } + + @Test + @UiThreadTest + public void testHoverDividerView() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CURSOR_HOVER_STATES_ENABLED, + "true", false); + + Rect dividerBounds = mSplitLayout.getDividerBounds(); + int x = dividerBounds.centerX(); + int y = dividerBounds.centerY(); + long downTime = SystemClock.uptimeMillis(); + mDividerView.onHoverEvent(getMotionEvent(downTime, MotionEvent.ACTION_HOVER_ENTER, x, y)); + + verify(mDividerView, times(1)).setHovering(); + + mDividerView.onHoverEvent(getMotionEvent(downTime, MotionEvent.ACTION_HOVER_EXIT, x, y)); + + verify(mDividerView, times(1)).releaseHovering(); + + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CURSOR_HOVER_STATES_ENABLED, + "false", false); + } + + private static MotionEvent getMotionEvent(long eventTime, int action, float x, float y) { + MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties(); + properties.id = 0; + properties.toolType = MotionEvent.TOOL_TYPE_UNKNOWN; + + MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords(); + coords.pressure = 1; + coords.size = 1; + coords.x = x; + coords.y = y; + + return MotionEvent.obtain(eventTime, eventTime, action, 1, + new MotionEvent.PointerProperties[]{properties}, + new MotionEvent.PointerCoords[]{coords}, 0, 0, 1.0f, 1.0f, 0, 0, + InputDevice.SOURCE_TOUCHSCREEN, 0); + } + + private static Configuration getConfiguration() { + final Configuration configuration = new Configuration(); + configuration.unset(); + configuration.orientation = ORIENTATION_LANDSCAPE; + configuration.windowConfiguration.setRotation(0); + configuration.windowConfiguration.setBounds(new Rect(0, 0, 1080, 2160)); + return configuration; + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java index d6387ee5ae13..605a762a395f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java @@ -59,6 +59,7 @@ import android.window.WindowContainerTransaction.HierarchyOp; import androidx.test.filters.SmallTest; import com.android.dx.mockito.inline.extended.StaticMockitoSession; +import com.android.wm.shell.MockToken; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt index 3bc2f0e8674e..3fe78efdf2b1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt @@ -129,6 +129,18 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { } @Test + fun addListener_notifiesStashed() { + repo.setStashed(DEFAULT_DISPLAY, true) + val listener = TestVisibilityListener() + val executor = TestShellExecutor() + repo.addVisibleTasksListener(listener, executor) + executor.flushAll() + + assertThat(listener.stashedOnDefaultDisplay).isTrue() + assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1) + } + + @Test fun addListener_tasksOnDifferentDisplay_doesNotNotify() { repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true) val listener = TestVisibilityListener() @@ -313,6 +325,65 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { assertThat(tasks.first()).isEqualTo(6) } + @Test + fun setStashed_stateIsUpdatedForTheDisplay() { + repo.setStashed(DEFAULT_DISPLAY, true) + assertThat(repo.isStashed(DEFAULT_DISPLAY)).isTrue() + assertThat(repo.isStashed(SECOND_DISPLAY)).isFalse() + + repo.setStashed(DEFAULT_DISPLAY, false) + assertThat(repo.isStashed(DEFAULT_DISPLAY)).isFalse() + } + + @Test + fun setStashed_notifyListener() { + val listener = TestVisibilityListener() + val executor = TestShellExecutor() + repo.addVisibleTasksListener(listener, executor) + repo.setStashed(DEFAULT_DISPLAY, true) + executor.flushAll() + assertThat(listener.stashedOnDefaultDisplay).isTrue() + assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1) + + repo.setStashed(DEFAULT_DISPLAY, false) + executor.flushAll() + assertThat(listener.stashedOnDefaultDisplay).isFalse() + assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(2) + } + + @Test + fun setStashed_secondCallDoesNotNotify() { + val listener = TestVisibilityListener() + val executor = TestShellExecutor() + repo.addVisibleTasksListener(listener, executor) + repo.setStashed(DEFAULT_DISPLAY, true) + repo.setStashed(DEFAULT_DISPLAY, true) + executor.flushAll() + assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1) + } + + @Test + fun setStashed_tracksPerDisplay() { + val listener = TestVisibilityListener() + val executor = TestShellExecutor() + repo.addVisibleTasksListener(listener, executor) + + repo.setStashed(DEFAULT_DISPLAY, true) + executor.flushAll() + assertThat(listener.stashedOnDefaultDisplay).isTrue() + assertThat(listener.stashedOnSecondaryDisplay).isFalse() + + repo.setStashed(SECOND_DISPLAY, true) + executor.flushAll() + assertThat(listener.stashedOnDefaultDisplay).isTrue() + assertThat(listener.stashedOnSecondaryDisplay).isTrue() + + repo.setStashed(DEFAULT_DISPLAY, false) + executor.flushAll() + assertThat(listener.stashedOnDefaultDisplay).isFalse() + assertThat(listener.stashedOnSecondaryDisplay).isTrue() + } + class TestListener : DesktopModeTaskRepository.ActiveTasksListener { var activeChangesOnDefaultDisplay = 0 var activeChangesOnSecondaryDisplay = 0 @@ -332,6 +403,12 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { var visibleChangesOnDefaultDisplay = 0 var visibleChangesOnSecondaryDisplay = 0 + var stashedOnDefaultDisplay = false + var stashedOnSecondaryDisplay = false + + var stashedChangesOnDefaultDisplay = 0 + var stashedChangesOnSecondaryDisplay = 0 + override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) { when (displayId) { DEFAULT_DISPLAY -> { @@ -345,6 +422,20 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { else -> fail("Visible task listener received unexpected display id: $displayId") } } + + override fun onStashedChanged(displayId: Int, stashed: Boolean) { + when (displayId) { + DEFAULT_DISPLAY -> { + stashedOnDefaultDisplay = stashed + stashedChangesOnDefaultDisplay++ + } + SECOND_DISPLAY -> { + stashedOnSecondaryDisplay = stashed + stashedChangesOnDefaultDisplay++ + } + else -> fail("Visible task listener received unexpected display id: $displayId") + } + } } companion object { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 1335ebf105a6..1477cf7415cf 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -39,17 +39,20 @@ import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.ExtendedMockito.never import com.android.dx.mockito.inline.extended.StaticMockitoSession +import com.android.wm.shell.MockToken import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.LaunchAdjacentController import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask +import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions @@ -77,6 +80,7 @@ import org.mockito.Mockito.`when` as whenever class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var testExecutor: ShellExecutor + @Mock lateinit var shellCommandHandler: ShellCommandHandler @Mock lateinit var shellController: ShellController @Mock lateinit var displayController: DisplayController @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer @@ -85,12 +89,16 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var transitions: Transitions @Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler + @Mock lateinit var mToggleResizeDesktopTaskTransitionHandler: + ToggleResizeDesktopTaskTransitionHandler + @Mock lateinit var launchAdjacentController: LaunchAdjacentController private lateinit var mockitoSession: StaticMockitoSession private lateinit var controller: DesktopTasksController private lateinit var shellInit: ShellInit private lateinit var desktopModeTaskRepository: DesktopModeTaskRepository + private val shellExecutor = TestShellExecutor() // Mock running tasks are registered here so we can get the list from mock shell task organizer private val runningTasks = mutableListOf<RunningTaskInfo>() @@ -114,6 +122,7 @@ class DesktopTasksControllerTest : ShellTestCase() { return DesktopTasksController( context, shellInit, + shellCommandHandler, shellController, displayController, shellTaskOrganizer, @@ -122,8 +131,10 @@ class DesktopTasksControllerTest : ShellTestCase() { transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler, + mToggleResizeDesktopTaskTransitionHandler, desktopModeTaskRepository, - TestShellExecutor() + launchAdjacentController, + shellExecutor ) } @@ -262,8 +273,9 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun moveToDesktop() { + fun moveToDesktop_displayFullscreen_windowingModeSetToFreeform() { val task = setUpFullscreenTask() + task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN controller.moveToDesktop(task) val wct = getLatestWct(expectTransition = TRANSIT_CHANGE) assertThat(wct.changes[task.token.asBinder()]?.windowingMode) @@ -271,6 +283,16 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun moveToDesktop_displayFreeform_windowingModeSetToUndefined() { + val task = setUpFullscreenTask() + task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM + controller.moveToDesktop(task) + val wct = getLatestWct(expectTransition = TRANSIT_CHANGE) + assertThat(wct.changes[task.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) + } + + @Test fun moveToDesktop_nonExistentTask_doesNothing() { controller.moveToDesktop(999) verifyWCTNotExecuted() @@ -317,12 +339,23 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun moveToFullscreen() { + fun moveToFullscreen_displayFullscreen_windowingModeSetToUndefined() { val task = setUpFreeformTask() + task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN controller.moveToFullscreen(task) val wct = getLatestWct(expectTransition = TRANSIT_CHANGE) assertThat(wct.changes[task.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FULLSCREEN) + .isEqualTo(WINDOWING_MODE_UNDEFINED) + } + + @Test + fun moveToFullscreen_displayFreeform_windowingModeSetToFullscreen() { + val task = setUpFreeformTask() + task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM + controller.moveToFullscreen(task) + val wct = getLatestWct(expectTransition = TRANSIT_CHANGE) + assertThat(wct.changes[task.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) } @Test @@ -345,6 +378,18 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun moveTaskToFront_postsWctWithReorderOp() { + val task1 = setUpFreeformTask() + setUpFreeformTask() + + controller.moveTaskToFront(task1) + + val wct = getLatestWct(expectTransition = TRANSIT_TO_FRONT) + assertThat(wct.hierarchyOps).hasSize(1) + wct.assertReorderAt(index = 0, task1) + } + + @Test fun moveToNextDisplay_noOtherDisplays() { whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY)) val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY) @@ -451,6 +496,27 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun handleRequest_fullscreenTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + + val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY) + markTaskHidden(stashedFreeformTask) + + val fullscreenTask = createFullscreenTask(DEFAULT_DISPLAY) + + controller.stashDesktopApps(DEFAULT_DISPLAY) + + val result = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + assertThat(result).isNotNull() + result!!.assertReorderSequence(stashedFreeformTask, fullscreenTask) + assertThat(result.changes[fullscreenTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + + // Stashed state should be cleared + assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse() + } + + @Test fun handleRequest_freeformTask_freeformVisible_returnNull() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -501,6 +567,25 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun handleRequest_freeformTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + + val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY) + markTaskHidden(stashedFreeformTask) + + val freeformTask = createFreeformTask(DEFAULT_DISPLAY) + + controller.stashDesktopApps(DEFAULT_DISPLAY) + + val result = controller.handleRequest(Binder(), createTransition(freeformTask)) + assertThat(result).isNotNull() + result?.assertReorderSequence(stashedFreeformTask, freeformTask) + + // Stashed state should be cleared + assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse() + } + + @Test fun handleRequest_notOpenOrToFrontTransition_returnNull() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -539,6 +624,46 @@ class DesktopTasksControllerTest : ShellTestCase() { assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull() } + @Test + fun stashDesktopApps_stateUpdates() { + controller.stashDesktopApps(DEFAULT_DISPLAY) + + assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isTrue() + assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isFalse() + } + + @Test + fun hideStashedDesktopApps_stateUpdates() { + desktopModeTaskRepository.setStashed(DEFAULT_DISPLAY, true) + desktopModeTaskRepository.setStashed(SECOND_DISPLAY, true) + controller.hideStashedDesktopApps(DEFAULT_DISPLAY) + + assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse() + // Check that second display is not affected + assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isTrue() + } + + @Test + fun desktopTasksVisibilityChange_visible_setLaunchAdjacentDisabled() { + val task = setUpFreeformTask() + clearInvocations(launchAdjacentController) + + markTaskVisible(task) + shellExecutor.flushAll() + verify(launchAdjacentController).launchAdjacentEnabled = false + } + + @Test + fun desktopTasksVisibilityChange_invisible_setLaunchAdjacentEnabled() { + val task = setUpFreeformTask() + markTaskVisible(task) + clearInvocations(launchAdjacentController) + + markTaskHidden(task) + shellExecutor.flushAll() + verify(launchAdjacentController).launchAdjacentEnabled = true + } + private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { val task = createFreeformTask(displayId) whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt index cf1ff3214d87..29a757c19d98 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt @@ -22,6 +22,7 @@ import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.view.Display.DEFAULT_DISPLAY +import com.android.wm.shell.MockToken import com.android.wm.shell.TestRunningTaskInfoBuilder class DesktopTestHelpers { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java index 68cb57c14d8c..b1befc46f383 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java @@ -41,6 +41,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Optional; + /** Tests for {@link MainStage} */ @SmallTest @RunWith(AndroidJUnit4.class) @@ -61,7 +63,7 @@ public class MainStageTests extends ShellTestCase { MockitoAnnotations.initMocks(this); mRootTaskInfo = new TestRunningTaskInfoBuilder().build(); mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, - mSyncQueue, mSurfaceSession, mIconProvider); + mSyncQueue, mSurfaceSession, mIconProvider, Optional.empty()); mMainStage.onTaskAppeared(mRootTaskInfo, mRootLeash); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java index 3b42a48b5a40..549bd3fcabfb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java @@ -46,6 +46,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; +import java.util.Optional; + /** Tests for {@link SideStage} */ @SmallTest @RunWith(AndroidJUnit4.class) @@ -66,7 +68,7 @@ public class SideStageTests extends ShellTestCase { MockitoAnnotations.initMocks(this); mRootTask = new TestRunningTaskInfoBuilder().build(); mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, - mSyncQueue, mSurfaceSession, mIconProvider); + mSyncQueue, mSurfaceSession, mIconProvider, Optional.empty()); mSideStage.onTaskAppeared(mRootTask, mRootLeash); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java index fb17d8799bda..e8a1e91acd4d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java @@ -61,6 +61,7 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; @@ -71,6 +72,7 @@ import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.sysui.ShellSharedConstants; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.windowdecor.WindowDecorViewModel; import org.junit.Before; import org.junit.Test; @@ -102,6 +104,8 @@ public class SplitScreenControllerTests extends ShellTestCase { @Mock IconProvider mIconProvider; @Mock StageCoordinator mStageCoordinator; @Mock RecentTasksController mRecentTasks; + @Mock LaunchAdjacentController mLaunchAdjacentController; + @Mock WindowDecorViewModel mWindowDecorViewModel; @Captor ArgumentCaptor<Intent> mIntentCaptor; private ShellController mShellController; @@ -117,7 +121,8 @@ public class SplitScreenControllerTests extends ShellTestCase { mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue, mRootTDAOrganizer, mDisplayController, mDisplayImeController, mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool, - mIconProvider, mRecentTasks, mMainExecutor, mStageCoordinator)); + mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel, + mMainExecutor, mStageCoordinator)); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java index 4e446c684d86..a3009a55198f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java @@ -31,12 +31,14 @@ import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.split.SplitLayout; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.windowdecor.WindowDecorViewModel; import java.util.Optional; @@ -76,10 +78,13 @@ public class SplitTestUtils { DisplayInsetsController insetsController, SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool, ShellExecutor mainExecutor, - Optional<RecentTasksController> recentTasks) { + Optional<RecentTasksController> recentTasks, + LaunchAdjacentController launchAdjacentController, + Optional<WindowDecorViewModel> windowDecorViewModel) { super(context, displayId, syncQueue, taskOrganizer, mainStage, sideStage, displayController, imeController, insetsController, splitLayout, - transitions, transactionPool, mainExecutor, recentTasks); + transitions, transactionPool, mainExecutor, recentTasks, + launchAdjacentController, windowDecorViewModel); // Prepare root task for testing. mRootTask = new TestRunningTaskInfoBuilder().build(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index 44c76de945b0..5efd9ad97a3e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -70,6 +70,7 @@ import com.android.wm.shell.TransitionInfoBuilder; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; @@ -77,6 +78,7 @@ import com.android.wm.shell.common.split.SplitDecorManager; import com.android.wm.shell.common.split.SplitLayout; import com.android.wm.shell.transition.DefaultMixedHandler; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.windowdecor.WindowDecorViewModel; import org.junit.Before; import org.junit.Test; @@ -101,7 +103,9 @@ public class SplitTransitionTests extends ShellTestCase { @Mock private Transitions mTransitions; @Mock private SurfaceSession mSurfaceSession; @Mock private IconProvider mIconProvider; + @Mock private WindowDecorViewModel mWindowDecorViewModel; @Mock private ShellExecutor mMainExecutor; + @Mock private LaunchAdjacentController mLaunchAdjacentController; @Mock private DefaultMixedHandler mMixedHandler; private SplitLayout mSplitLayout; private MainStage mMainStage; @@ -123,16 +127,17 @@ public class SplitTransitionTests extends ShellTestCase { mSplitLayout = SplitTestUtils.createMockSplitLayout(); mMainStage = spy(new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, - mIconProvider)); + mIconProvider, Optional.of(mWindowDecorViewModel))); mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface()); mSideStage = spy(new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, - mIconProvider)); + mIconProvider, Optional.of(mWindowDecorViewModel))); mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface()); mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions, - mTransactionPool, mMainExecutor, Optional.empty()); + mTransactionPool, mMainExecutor, Optional.empty(), + mLaunchAdjacentController, Optional.empty()); mStageCoordinator.setMixedHandler(mMixedHandler); mSplitScreenTransitions = mStageCoordinator.getSplitTransitions(); doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class)) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index 2dcdc74e8ae7..e59d09cd1ee1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -65,6 +65,7 @@ import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; @@ -107,6 +108,8 @@ public class StageCoordinatorTests extends ShellTestCase { private DisplayInsetsController mDisplayInsetsController; @Mock private TransactionPool mTransactionPool; + @Mock + private LaunchAdjacentController mLaunchAdjacentController; private final Rect mBounds1 = new Rect(10, 20, 30, 40); private final Rect mBounds2 = new Rect(5, 10, 15, 20); @@ -130,7 +133,7 @@ public class StageCoordinatorTests extends ShellTestCase { mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool, - mMainExecutor, Optional.empty())); + mMainExecutor, Optional.empty(), mLaunchAdjacentController, Optional.empty())); mDividerLeash = new SurfaceControl.Builder(mSurfaceSession).setName("fakeDivider").build(); when(mSplitLayout.getBounds1()).thenReturn(mBounds1); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java index 1a1bebd28aef..df1e2e16f485 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java @@ -43,6 +43,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.windowdecor.WindowDecorViewModel; import org.junit.Before; import org.junit.Test; @@ -52,6 +53,8 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Optional; + /** * Tests for {@link StageTaskListener} * Build/Install/Run: @@ -71,6 +74,8 @@ public final class StageTaskListenerTests extends ShellTestCase { private SyncTransactionQueue mSyncQueue; @Mock private IconProvider mIconProvider; + @Mock + private WindowDecorViewModel mWindowDecorViewModel; @Captor private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor; private SurfaceSession mSurfaceSession = new SurfaceSession(); @@ -89,7 +94,8 @@ public final class StageTaskListenerTests extends ShellTestCase { mCallbacks, mSyncQueue, mSurfaceSession, - mIconProvider); + mIconProvider, + Optional.of(mWindowDecorViewModel)); mRootTask = new TestRunningTaskInfoBuilder().build(); mRootTask.parentTaskId = INVALID_TASK_ID; mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession).setName("test").build(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java index 41bab95b7dd4..adc2a6fbff23 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java @@ -53,7 +53,6 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.desktopmode.DesktopModeController; import com.android.wm.shell.desktopmode.DesktopTasksController; -import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.transition.Transitions; import org.junit.Before; @@ -82,7 +81,6 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase { @Mock private ShellTaskOrganizer mTaskOrganizer; @Mock private DisplayController mDisplayController; @Mock private DisplayLayout mDisplayLayout; - @Mock private SplitScreenController mSplitScreenController; @Mock private SyncTransactionQueue mSyncQueue; @Mock private DesktopModeController mDesktopModeController; @Mock private DesktopTasksController mDesktopTasksController; @@ -92,6 +90,7 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase { @Mock private DesktopModeWindowDecorViewModel.InputMonitorFactory mMockInputMonitorFactory; @Mock private Supplier<SurfaceControl.Transaction> mTransactionFactory; @Mock private SurfaceControl.Transaction mTransaction; + @Mock private Display mDisplay; private final List<InputManager> mMockInputManagers = new ArrayList<>(); private DesktopModeWindowDecorViewModel mDesktopModeWindowDecorViewModel; @@ -111,7 +110,6 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase { mTransitions, Optional.of(mDesktopModeController), Optional.of(mDesktopTasksController), - Optional.of(mSplitScreenController), mDesktopModeWindowDecorFactory, mMockInputMonitorFactory, mTransactionFactory @@ -129,6 +127,9 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase { final InputChannel[] inputChannels = InputChannel.openInputChannelPair(TAG); inputChannels[0].dispose(); when(mInputMonitor.getInputChannel()).thenReturn(inputChannels[1]); + + mDesktopModeWindowDecoration.mDisplay = mDisplay; + doReturn(Display.DEFAULT_DISPLAY).when(mDisplay).getDisplayId(); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt index 8f84008e8d2d..3fbab0f9e2bb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt @@ -55,7 +55,7 @@ class DragDetectorTest { fun setUp() { MockitoAnnotations.initMocks(this) - `when`(eventHandler.handleMotionEvent(any())).thenReturn(true) + `when`(eventHandler.handleMotionEvent(any(), any())).thenReturn(true) dragDetector = DragDetector(eventHandler) dragDetector.setTouchSlop(SLOP) @@ -72,13 +72,13 @@ class DragDetectorTest { @Test fun testNoMove_passesDownAndUp() { assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && it.source == InputDevice.SOURCE_TOUCHSCREEN }) assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_UP && it.x == X && it.y == Y && it.source == InputDevice.SOURCE_TOUCHSCREEN }) @@ -86,12 +86,12 @@ class DragDetectorTest { @Test fun testMoveInSlop_touch_passesDownAndUp() { - `when`(eventHandler.handleMotionEvent(argThat { + `when`(eventHandler.handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_DOWN })).thenReturn(false) assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && it.source == InputDevice.SOURCE_TOUCHSCREEN }) @@ -99,12 +99,12 @@ class DragDetectorTest { val newX = X + SLOP - 1 assertFalse( dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y))) - verify(eventHandler, never()).handleMotionEvent(argThat { + verify(eventHandler, never()).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_MOVE }) assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y && it.source == InputDevice.SOURCE_TOUCHSCREEN }) @@ -112,13 +112,13 @@ class DragDetectorTest { @Test fun testMoveInSlop_mouse_passesDownMoveAndUp() { - `when`(eventHandler.handleMotionEvent(argThat { + `when`(eventHandler.handleMotionEvent(any(), argThat { it.action == MotionEvent.ACTION_DOWN })).thenReturn(false) assertFalse(dragDetector.onMotionEvent( createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && it.source == InputDevice.SOURCE_MOUSE }) @@ -126,14 +126,14 @@ class DragDetectorTest { val newX = X + SLOP - 1 assertTrue(dragDetector.onMotionEvent( createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y, isTouch = false))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y && it.source == InputDevice.SOURCE_MOUSE }) assertTrue(dragDetector.onMotionEvent( createMotionEvent(MotionEvent.ACTION_UP, newX, Y, isTouch = false))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y && it.source == InputDevice.SOURCE_MOUSE }) @@ -141,25 +141,25 @@ class DragDetectorTest { @Test fun testMoveBeyondSlop_passesDownMoveAndUp() { - `when`(eventHandler.handleMotionEvent(argThat { + `when`(eventHandler.handleMotionEvent(any(), argThat { it.action == MotionEvent.ACTION_DOWN })).thenReturn(false) assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && it.source == InputDevice.SOURCE_TOUCHSCREEN }) val newX = X + SLOP + 1 assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y && it.source == InputDevice.SOURCE_TOUCHSCREEN }) assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y && it.source == InputDevice.SOURCE_TOUCHSCREEN }) @@ -167,12 +167,12 @@ class DragDetectorTest { @Test fun testPassesHoverEnter() { - `when`(eventHandler.handleMotionEvent(argThat { + `when`(eventHandler.handleMotionEvent(any(), argThat { it.action == MotionEvent.ACTION_HOVER_ENTER })).thenReturn(false) assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_ENTER))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_HOVER_ENTER && it.x == X && it.y == Y }) } @@ -180,7 +180,7 @@ class DragDetectorTest { @Test fun testPassesHoverMove() { assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_MOVE))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_HOVER_MOVE && it.x == X && it.y == Y }) } @@ -188,7 +188,7 @@ class DragDetectorTest { @Test fun testPassesHoverExit() { assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_EXIT))) - verify(eventHandler).handleMotionEvent(argThat { + verify(eventHandler).handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_HOVER_EXIT && it.x == X && it.y == Y }) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt index 69604ddf0af1..6f0599aa8243 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt @@ -22,6 +22,7 @@ import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFI import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.argThat @@ -71,16 +72,6 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - taskPositioner = - FluidResizeTaskPositioner( - mockShellTaskOrganizer, - mockWindowDecoration, - mockDisplayController, - DISALLOWED_AREA_FOR_END_BOUNDS, - mockDragStartListener, - mockTransactionFactory - ) - whenever(taskToken.asBinder()).thenReturn(taskBinder) whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout) whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI) @@ -101,6 +92,15 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { } mockWindowDecoration.mDisplay = mockDisplay whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID } + + taskPositioner = FluidResizeTaskPositioner( + mockShellTaskOrganizer, + mockWindowDecoration, + mockDisplayController, + mockDragStartListener, + mockTransactionFactory, + DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT + ) } @Test @@ -544,7 +544,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { ) val newX = STARTING_BOUNDS.right.toFloat() + 5 - val newY = STARTING_BOUNDS.top.toFloat() + 5 + val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1 taskPositioner.onDragPositioningMove( newX, newY @@ -614,6 +614,38 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { }) } + @Test + fun testDragResize_drag_taskPositionedInStableBounds() { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_UNDEFINED, // drag + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat() + ) + + val newX = STARTING_BOUNDS.left.toFloat() + val newY = STABLE_BOUNDS.top.toFloat() - 5 + taskPositioner.onDragPositioningMove( + newX, + newY + ) + verify(mockTransaction).setPosition(any(), eq(newX), eq(newY)) + + taskPositioner.onDragPositioningEnd( + newX, + newY + ) + // Verify task's top bound is set to stable bounds top since dragged outside stable bounds + // but not in disallowed end bounds area. + verify(mockShellTaskOrganizer).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 && + change.configuration.windowConfiguration.bounds.top == + STABLE_BOUNDS.top + } + }) + } + companion object { private const val TASK_ID = 5 private const val MIN_WIDTH = 10 @@ -622,10 +654,11 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { private const val DEFAULT_MIN = 40 private const val DISPLAY_ID = 1 private const val NAVBAR_HEIGHT = 50 + private const val CAPTION_HEIGHT = 50 + private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10 private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600) - private val STARTING_BOUNDS = Rect(0, 0, 100, 100) + private val STARTING_BOUNDS = Rect(100, 100, 200, 200) private val STABLE_INSETS = Rect(0, 50, 0, 0) - private val DISALLOWED_AREA_FOR_END_BOUNDS = Rect(0, 0, 300, 300) private val DISALLOWED_RESIZE_AREA = Rect( DISPLAY_BOUNDS.left, DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT, @@ -633,7 +666,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { DISPLAY_BOUNDS.bottom) private val STABLE_BOUNDS = Rect( DISPLAY_BOUNDS.left, - DISPLAY_BOUNDS.top, + DISPLAY_BOUNDS.top + CAPTION_HEIGHT, DISPLAY_BOUNDS.right, DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT ) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt index 4147dd8e6152..3465ddd9d101 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt @@ -89,17 +89,6 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - taskPositioner = - VeiledResizeTaskPositioner( - mockShellTaskOrganizer, - mockDesktopWindowDecoration, - mockDisplayController, - DISALLOWED_AREA_FOR_END_BOUNDS, - mockDragStartListener, - mockTransactionFactory, - mockTransitions - ) - whenever(taskToken.asBinder()).thenReturn(taskBinder) whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout) whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI) @@ -119,6 +108,17 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } mockDesktopWindowDecoration.mDisplay = mockDisplay whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID } + + taskPositioner = + VeiledResizeTaskPositioner( + mockShellTaskOrganizer, + mockDesktopWindowDecoration, + mockDisplayController, + mockDragStartListener, + mockTransactionFactory, + mockTransitions, + DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT + ) } @Test @@ -269,7 +269,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { ) val newX = STARTING_BOUNDS.left.toFloat() + 5 - val newY = STARTING_BOUNDS.top.toFloat() + 5 + val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1 taskPositioner.onDragPositioningMove( newX, newY @@ -334,6 +334,38 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { }) } + @Test + fun testDragResize_drag_taskPositionedInStableBounds() { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_UNDEFINED, // drag + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat() + ) + + val newX = STARTING_BOUNDS.left.toFloat() + val newY = STABLE_BOUNDS.top.toFloat() - 5 + taskPositioner.onDragPositioningMove( + newX, + newY + ) + verify(mockTransaction).setPosition(any(), eq(newX), eq(newY)) + + taskPositioner.onDragPositioningEnd( + newX, + newY + ) + // Verify task's top bound is set to stable bounds top since dragged outside stable bounds + // but not in disallowed end bounds area. + verify(mockShellTaskOrganizer).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 && + change.configuration.windowConfiguration.bounds.top == + STABLE_BOUNDS.top + } + }) + } + companion object { private const val TASK_ID = 5 private const val MIN_WIDTH = 10 @@ -342,12 +374,13 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { private const val DEFAULT_MIN = 40 private const val DISPLAY_ID = 1 private const val NAVBAR_HEIGHT = 50 + private const val CAPTION_HEIGHT = 50 + private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10 private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600) - private val STARTING_BOUNDS = Rect(0, 0, 100, 100) - private val DISALLOWED_AREA_FOR_END_BOUNDS = Rect(0, 0, 50, 50) + private val STARTING_BOUNDS = Rect(100, 100, 200, 200) private val STABLE_BOUNDS = Rect( DISPLAY_BOUNDS.left, - DISPLAY_BOUNDS.top, + DISPLAY_BOUNDS.top + CAPTION_HEIGHT, DISPLAY_BOUNDS.right, DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT ) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index 5a2326b9c393..7fc1c99bb44e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -57,7 +57,6 @@ import android.window.SurfaceSyncGroup; import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; -import androidx.test.platform.app.InstrumentationRegistry; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; @@ -316,7 +315,8 @@ public class WindowDecorationTests extends ShellTestCase { releaseOrder.verify(t).remove(captionContainerSurface); releaseOrder.verify(t).remove(decorContainerSurface); releaseOrder.verify(t).apply(); - verify(mMockWindowContainerTransaction) + // Expect to remove two insets sources, the caption insets and the mandatory gesture insets. + verify(mMockWindowContainerTransaction, Mockito.times(2)) .removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt()); } @@ -410,15 +410,17 @@ public class WindowDecorationTests extends ShellTestCase { verify(additionalWindowSurfaceBuilder).build(); verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 0, 0); final int width = WindowDecoration.loadDimensionPixelSize( - mContext.getResources(), mCaptionMenuWidthId); + windowDecor.mDecorWindowContext.getResources(), mCaptionMenuWidthId); final int height = WindowDecoration.loadDimensionPixelSize( - mContext.getResources(), mRelayoutParams.mCaptionHeightId); + windowDecor.mDecorWindowContext.getResources(), mRelayoutParams.mCaptionHeightId); verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, width, height); - final int shadowRadius = WindowDecoration.loadDimensionPixelSize(mContext.getResources(), + final int shadowRadius = WindowDecoration.loadDimensionPixelSize( + windowDecor.mDecorWindowContext.getResources(), mCaptionMenuShadowRadiusId); verify(mMockSurfaceControlAddWindowT) .setShadowRadius(additionalWindowSurface, shadowRadius); - final int cornerRadius = WindowDecoration.loadDimensionPixelSize(mContext.getResources(), + final int cornerRadius = WindowDecoration.loadDimensionPixelSize( + windowDecor.mDecorWindowContext.getResources(), mCaptionMenuCornerRadiusId); verify(mMockSurfaceControlAddWindowT) .setCornerRadius(additionalWindowSurface, cornerRadius); @@ -513,8 +515,7 @@ public class WindowDecorationTests extends ShellTestCase { private TestWindowDecoration createWindowDecoration( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) { - return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(), - mMockDisplayController, mMockShellTaskOrganizer, + return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer, taskInfo, testSurface, new MockObjectSupplier<>(mMockSurfaceControlBuilders, () -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))), diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp index 00919dc3f22a..f71e7289bd37 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.cpp +++ b/libs/hwui/pipeline/skia/ShaderCache.cpp @@ -79,7 +79,7 @@ bool ShaderCache::validateCache(const void* identity, ssize_t size) { void ShaderCache::initShaderDiskCache(const void* identity, ssize_t size) { ATRACE_NAME("initShaderDiskCache"); - std::lock_guard<std::mutex> lock(mMutex); + std::lock_guard lock(mMutex); // Emulators can switch between different renders either as part of config // or snapshot migration. Also, program binaries may not work well on some @@ -92,7 +92,7 @@ void ShaderCache::initShaderDiskCache(const void* identity, ssize_t size) { } void ShaderCache::setFilename(const char* filename) { - std::lock_guard<std::mutex> lock(mMutex); + std::lock_guard lock(mMutex); mFilename = filename; } @@ -104,7 +104,7 @@ BlobCache* ShaderCache::getBlobCacheLocked() { sk_sp<SkData> ShaderCache::load(const SkData& key) { ATRACE_NAME("ShaderCache::load"); size_t keySize = key.size(); - std::lock_guard<std::mutex> lock(mMutex); + std::lock_guard lock(mMutex); if (!mInitialized) { return nullptr; } @@ -181,13 +181,18 @@ void ShaderCache::saveToDiskLocked() { auto key = sIDKey; set(mBlobCache.get(), &key, sizeof(key), mIDHash.data(), mIDHash.size()); } + // The most straightforward way to make ownership shared + mMutex.unlock(); + mMutex.lock_shared(); mBlobCache->writeToFile(); + mMutex.unlock_shared(); + mMutex.lock(); } } void ShaderCache::store(const SkData& key, const SkData& data, const SkString& /*description*/) { ATRACE_NAME("ShaderCache::store"); - std::lock_guard<std::mutex> lock(mMutex); + std::lock_guard lock(mMutex); mNumShadersCachedInRam++; ATRACE_FORMAT("HWUI RAM cache: %d shaders", mNumShadersCachedInRam); @@ -229,7 +234,7 @@ void ShaderCache::store(const SkData& key, const SkData& data, const SkString& / mSavePending = true; std::thread deferredSaveThread([this]() { usleep(mDeferredSaveDelayMs * 1000); // milliseconds to microseconds - std::lock_guard<std::mutex> lock(mMutex); + std::lock_guard lock(mMutex); // Store file on disk if there a new shader or Vulkan pipeline cache size changed. if (mCacheDirty || mNewPipelineCacheSize != mOldPipelineCacheSize) { saveToDiskLocked(); @@ -245,11 +250,12 @@ void ShaderCache::store(const SkData& key, const SkData& data, const SkString& / void ShaderCache::onVkFrameFlushed(GrDirectContext* context) { { - std::lock_guard<std::mutex> lock(mMutex); - + mMutex.lock_shared(); if (!mInitialized || !mTryToStorePipelineCache) { + mMutex.unlock_shared(); return; } + mMutex.unlock_shared(); } mInStoreVkPipelineInProgress = true; context->storeVkPipelineCacheData(); diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h index f5506d60f811..2f91c778b8a0 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.h +++ b/libs/hwui/pipeline/skia/ShaderCache.h @@ -19,8 +19,10 @@ #include <GrContextOptions.h> #include <SkRefCnt.h> #include <cutils/compiler.h> +#include <ftl/shared_mutex.h> +#include <utils/Mutex.h> + #include <memory> -#include <mutex> #include <string> #include <vector> @@ -99,20 +101,20 @@ private: * this will do so, loading the serialized cache contents from disk if * possible. */ - BlobCache* getBlobCacheLocked(); + BlobCache* getBlobCacheLocked() REQUIRES(mMutex); /** * "validateCache" updates the cache to match the given identity. If the * cache currently has the wrong identity, all entries in the cache are cleared. */ - bool validateCache(const void* identity, ssize_t size); + bool validateCache(const void* identity, ssize_t size) REQUIRES(mMutex); /** - * "saveToDiskLocked" attemps to save the current contents of the cache to + * "saveToDiskLocked" attempts to save the current contents of the cache to * disk. If the identity hash exists, we will insert the identity hash into * the cache for next validation. */ - void saveToDiskLocked(); + void saveToDiskLocked() REQUIRES(mMutex); /** * "mInitialized" indicates whether the ShaderCache is in the initialized @@ -122,7 +124,7 @@ private: * the load and store methods will return without performing any cache * operations. */ - bool mInitialized = false; + bool mInitialized GUARDED_BY(mMutex) = false; /** * "mBlobCache" is the cache in which the key/value blob pairs are stored. It @@ -131,7 +133,7 @@ private: * The blob cache contains the Android build number. We treat version mismatches as an empty * cache (logic implemented in BlobCache::unflatten). */ - std::unique_ptr<FileBlobCache> mBlobCache; + std::unique_ptr<FileBlobCache> mBlobCache GUARDED_BY(mMutex); /** * "mFilename" is the name of the file for storing cache contents in between @@ -140,7 +142,7 @@ private: * empty string indicates that the cache should not be saved to or restored * from disk. */ - std::string mFilename; + std::string mFilename GUARDED_BY(mMutex); /** * "mIDHash" is the current identity hash for the cache validation. It is @@ -149,7 +151,7 @@ private: * indicates that cache validation is not performed, and the hash should * not be stored on disk. */ - std::vector<uint8_t> mIDHash; + std::vector<uint8_t> mIDHash GUARDED_BY(mMutex); /** * "mSavePending" indicates whether or not a deferred save operation is @@ -159,7 +161,7 @@ private: * contents to disk, unless mDeferredSaveDelayMs is 0 in which case saving * is disabled. */ - bool mSavePending = false; + bool mSavePending GUARDED_BY(mMutex) = false; /** * "mObservedBlobValueSize" is the maximum value size observed by the cache reading function. @@ -174,16 +176,16 @@ private: unsigned int mDeferredSaveDelayMs = 4 * 1000; /** - * "mMutex" is the mutex used to prevent concurrent access to the member + * "mMutex" is the shared mutex used to prevent concurrent access to the member * variables. It must be locked whenever the member variables are accessed. */ - mutable std::mutex mMutex; + mutable ftl::SharedMutex mMutex; /** * If set to "true", the next call to onVkFrameFlushed, will invoke * GrCanvas::storeVkPipelineCacheData. This does not guarantee that data will be stored on disk. */ - bool mTryToStorePipelineCache = true; + bool mTryToStorePipelineCache GUARDED_BY(mMutex) = true; /** * This flag is used by "ShaderCache::store" to distinguish between shader data and @@ -195,16 +197,16 @@ private: * "mNewPipelineCacheSize" has the size of the new Vulkan pipeline cache data. It is used * to prevent unnecessary disk writes, if the pipeline cache size has not changed. */ - size_t mNewPipelineCacheSize = -1; + size_t mNewPipelineCacheSize GUARDED_BY(mMutex) = -1; /** * "mOldPipelineCacheSize" has the size of the Vulkan pipeline cache data stored on disk. */ - size_t mOldPipelineCacheSize = -1; + size_t mOldPipelineCacheSize GUARDED_BY(mMutex) = -1; /** * "mCacheDirty" is true when there is new shader cache data, which is not saved to disk. */ - bool mCacheDirty = false; + bool mCacheDirty GUARDED_BY(mMutex) = false; /** * "sCache" is the singleton ShaderCache object. @@ -221,7 +223,7 @@ private: * interesting to keep track of how many shaders are stored in RAM. This * class provides a convenient entry point for that. */ - int mNumShadersCachedInRam = 0; + int mNumShadersCachedInRam GUARDED_BY(mMutex) = 0; friend class ShaderCacheTestUtils; // used for unit testing }; diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 16b35ffcabac..a5518eb9f854 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -405,8 +405,17 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy // If the previous frame was dropped we don't need to hold onto it, so // just keep using the previous frame's structure instead - if (!wasSkipped(mCurrentFrameInfo)) { + if (wasSkipped(mCurrentFrameInfo)) { + // Use the oldest skipped frame in case we skip more than a single frame + if (!mSkippedFrameInfo) { + mSkippedFrameInfo.emplace(); + mSkippedFrameInfo->vsyncId = + mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId); + mSkippedFrameInfo->startTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime); + } + } else { mCurrentFrameInfo = mJankTracker.startFrame(); + mSkippedFrameInfo.reset(); } mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo); @@ -602,10 +611,18 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) { if (vsyncId != UiFrameInfoBuilder::INVALID_VSYNC_ID) { const auto inputEventId = static_cast<int32_t>(mCurrentFrameInfo->get(FrameInfoIndex::InputEventId)); - native_window_set_frame_timeline_info( - mNativeSurface->getNativeWindow(), frameCompleteNr, vsyncId, inputEventId, - mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime), - solelyTextureViewUpdates); + const ANativeWindowFrameTimelineInfo ftl = { + .frameNumber = frameCompleteNr, + .frameTimelineVsyncId = vsyncId, + .inputEventId = inputEventId, + .startTimeNanos = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime), + .useForRefreshRateSelection = solelyTextureViewUpdates, + .skippedFrameVsyncId = mSkippedFrameInfo ? mSkippedFrameInfo->vsyncId + : UiFrameInfoBuilder::INVALID_VSYNC_ID, + .skippedFrameStartTimeNanos = + mSkippedFrameInfo ? mSkippedFrameInfo->startTime : 0, + }; + native_window_set_frame_timeline_info(mNativeSurface->getNativeWindow(), ftl); } } diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 5219b5757008..32ac5af94c14 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -366,6 +366,12 @@ private: ColorMode mColorMode = ColorMode::Default; float mTargetSdrHdrRatio = 1.f; + + struct SkippedFrameInfo { + int64_t vsyncId; + int64_t startTime; + }; + std::optional<SkippedFrameInfo> mSkippedFrameInfo; }; } /* namespace renderthread */ diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp index 7bcd45c6b643..9aa2e1db4461 100644 --- a/libs/hwui/tests/unit/ShaderCacheTests.cpp +++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp @@ -49,7 +49,7 @@ public: */ static void reinitializeAllFields(ShaderCache& cache) { ShaderCache newCache = ShaderCache(); - std::lock_guard<std::mutex> lock(cache.mMutex); + std::lock_guard lock(cache.mMutex), newLock(newCache.mMutex); // By order of declaration cache.mInitialized = newCache.mInitialized; cache.mBlobCache.reset(nullptr); @@ -72,7 +72,7 @@ public: * manually, as seen in the "terminate" testing helper function. */ static void setSaveDelayMs(ShaderCache& cache, unsigned int saveDelayMs) { - std::lock_guard<std::mutex> lock(cache.mMutex); + std::lock_guard lock(cache.mMutex); cache.mDeferredSaveDelayMs = saveDelayMs; } @@ -81,7 +81,7 @@ public: * Next call to "initShaderDiskCache" will load again the in-memory cache from disk. */ static void terminate(ShaderCache& cache, bool saveContent) { - std::lock_guard<std::mutex> lock(cache.mMutex); + std::lock_guard lock(cache.mMutex); if (saveContent) { cache.saveToDiskLocked(); } @@ -93,6 +93,7 @@ public: */ template <typename T> static bool validateCache(ShaderCache& cache, std::vector<T> hash) { + std::lock_guard lock(cache.mMutex); return cache.validateCache(hash.data(), hash.size() * sizeof(T)); } @@ -108,7 +109,7 @@ public: */ static void waitForPendingSave(ShaderCache& cache, const int timeoutMs = 50) { { - std::lock_guard<std::mutex> lock(cache.mMutex); + std::lock_guard lock(cache.mMutex); ASSERT_TRUE(cache.mSavePending); } bool saving = true; @@ -123,7 +124,7 @@ public: usleep(delayMicroseconds); elapsedMilliseconds += (float)delayMicroseconds / 1000; - std::lock_guard<std::mutex> lock(cache.mMutex); + std::lock_guard lock(cache.mMutex); saving = cache.mSavePending; } } |