summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Shell/Android.bp2
-rw-r--r--libs/WindowManager/Shell/AndroidManifest.xml2
-rw-r--r--libs/WindowManager/Shell/aconfig/Android.bp1
-rw-r--r--libs/WindowManager/Shell/aconfig/automotive.aconfig11
-rw-r--r--libs/WindowManager/Shell/aconfig/multitasking.aconfig14
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt5
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/common/TestShellExecutor.kt (renamed from libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/TestShellExecutor.kt)4
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml2
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-iw/strings.xml24
-rw-r--r--libs/WindowManager/Shell/res/values-te/strings.xml2
-rw-r--r--libs/WindowManager/Shell/shared/Android.bp1
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java4
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleAnythingFlagHelper.java48
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt227
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt49
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt44
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsShellCommandHandler.kt51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java76
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt293
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java5
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppsWithBackNavigationLandscape.kt51
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppsWithBackNavigationPortrait.kt48
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/EnterDesktopWithDragExistingWindowsTest.kt27
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithBackNavigation.kt73
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithAppHandleMenuExistingWindows.kt70
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDragExistingWindows.kt74
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenUnlimitedApps.kt11
-rw-r--r--libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt4
-rw-r--r--libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt4
-rw-r--r--libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt4
-rw-r--r--libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/RecentTasksUtils.kt27
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt54
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java21
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java30
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java121
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt611
-rw-r--r--libs/androidfw/ApkParsing.cpp10
-rw-r--r--libs/androidfw/include/androidfw/ApkParsing.h2
-rw-r--r--libs/androidfw/tests/ApkParsing_test.cpp26
-rw-r--r--libs/hwui/aconfig/hwui_flags.aconfig10
-rw-r--r--libs/hwui/hwui/DrawTextFunctor.h23
-rw-r--r--libs/hwui/renderthread/RenderThread.cpp9
70 files changed, 2205 insertions, 264 deletions
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 957d1b835ec2..bcb6c4f555f7 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -170,9 +170,9 @@ android_library {
"res",
],
static_libs: [
+ "//frameworks/base/packages/SystemUI/aconfig:com_android_systemui_flags_lib",
"//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
"//frameworks/libs/systemui:iconloader_base",
- "//packages/apps/Car/SystemUI/aconfig:com_android_systemui_car_flags_lib",
"PlatformAnimationLib",
"WindowManager-Shell-lite-proto",
"WindowManager-Shell-proto",
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index 4f1cd9780f8b..9c15319585b7 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -32,7 +32,7 @@
<activity
android:name=".desktopmode.DesktopWallpaperActivity"
android:excludeFromRecents="true"
- android:launchMode="singleInstance"
+ android:launchMode="singleInstancePerTask"
android:showForAllUsers="true"
android:theme="@style/DesktopWallpaperTheme" />
diff --git a/libs/WindowManager/Shell/aconfig/Android.bp b/libs/WindowManager/Shell/aconfig/Android.bp
index 7f8f57b172ff..f8da7fa86cff 100644
--- a/libs/WindowManager/Shell/aconfig/Android.bp
+++ b/libs/WindowManager/Shell/aconfig/Android.bp
@@ -4,6 +4,7 @@ aconfig_declarations {
container: "system",
srcs: [
"multitasking.aconfig",
+ "automotive.aconfig",
],
}
diff --git a/libs/WindowManager/Shell/aconfig/automotive.aconfig b/libs/WindowManager/Shell/aconfig/automotive.aconfig
new file mode 100644
index 000000000000..2f25aa460ec1
--- /dev/null
+++ b/libs/WindowManager/Shell/aconfig/automotive.aconfig
@@ -0,0 +1,11 @@
+# proto-file: build/make/tools/aconfig/aconfig_protos/protos/aconfig.proto
+
+package: "com.android.wm.shell"
+container: "system"
+
+flag {
+ name: "enable_auto_task_stack_controller"
+ namespace: "multitasking"
+ description: "Enables auto task stack controller to manage task stacks on automotive"
+ bug: "384082238"
+}
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 688bf83224aa..b10b099c970b 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -13,13 +13,6 @@ flag {
}
flag {
- name: "enable_split_contextual"
- namespace: "multitasking"
- description: "Enables invoking split contextually"
- bug: "276361926"
-}
-
-flag {
name: "enable_taskbar_navbar_unification"
namespace: "multitasking"
description: "Enables taskbar / navbar unification"
@@ -104,6 +97,13 @@ flag {
}
flag {
+ name: "enable_create_any_bubble"
+ namespace: "multitasking"
+ description: "Enable UI affordances to create bubbles via launcher app icons"
+ bug: "385220199"
+}
+
+flag {
name: "only_reuse_bubbled_task_when_launched_from_bubble"
namespace: "multitasking"
description: "Allow reusing bubbled tasks for new activities only when launching from bubbles"
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
index c62d2a06bad5..90ea7d35015e 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
@@ -34,7 +34,6 @@ import com.android.internal.protolog.ProtoLog
import com.android.internal.statusbar.IStatusBarService
import com.android.wm.shell.Flags
import com.android.wm.shell.ShellTaskOrganizer
-import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
import com.android.wm.shell.bubbles.properties.ProdBubbleProperties
import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
@@ -44,6 +43,7 @@ import com.android.wm.shell.common.DisplayInsetsController
import com.android.wm.shell.common.FloatingContentCoordinator
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.common.TaskStackListenerImpl
+import com.android.wm.shell.common.TestShellExecutor
import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.shared.TransactionPool
import com.android.wm.shell.shared.bubbles.BubbleBarLocation
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
index ab2e552c7a3d..a7eebd6159e4 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -36,10 +36,10 @@ import com.android.internal.protolog.ProtoLog
import com.android.launcher3.icons.BubbleIconFactory
import com.android.wm.shell.Flags
import com.android.wm.shell.R
-import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix
import com.android.wm.shell.common.FloatingContentCoordinator
+import com.android.wm.shell.common.TestShellExecutor
import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors.directExecutor
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt
index 3043e2bcb0be..a83327bbadee 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt
@@ -35,7 +35,6 @@ import com.android.internal.protolog.ProtoLog
import com.android.internal.statusbar.IStatusBarService
import com.android.launcher3.icons.BubbleIconFactory
import com.android.wm.shell.ShellTaskOrganizer
-import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.bubbles.properties.BubbleProperties
import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
import com.android.wm.shell.common.DisplayController
@@ -44,6 +43,7 @@ import com.android.wm.shell.common.DisplayInsetsController
import com.android.wm.shell.common.FloatingContentCoordinator
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.common.TaskStackListenerImpl
+import com.android.wm.shell.common.TestShellExecutor
import com.android.wm.shell.shared.TransactionPool
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt
index bcaa63bfad36..750178678785 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt
@@ -22,9 +22,9 @@ import android.content.res.Resources
import android.view.LayoutInflater
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.wm.shell.R
-import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.bubbles.BubbleViewInfoTask.BubbleViewInfo
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView
+import com.android.wm.shell.common.TestShellExecutor
import com.google.common.util.concurrent.MoreExecutors.directExecutor
/** Helper to create a [Bubble] instance */
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
index c45f6903c2e1..9e58b5be9d0d 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
@@ -36,7 +36,6 @@ import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.protolog.ProtoLog
-import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.bubbles.Bubble
import com.android.wm.shell.bubbles.BubbleExpandedViewManager
import com.android.wm.shell.bubbles.BubbleLogger
@@ -46,6 +45,7 @@ import com.android.wm.shell.bubbles.BubbleTaskView
import com.android.wm.shell.bubbles.DeviceConfig
import com.android.wm.shell.bubbles.FakeBubbleExpandedViewManager
import com.android.wm.shell.bubbles.FakeBubbleFactory
+import com.android.wm.shell.common.TestShellExecutor
import com.android.wm.shell.taskview.TaskView
import com.android.wm.shell.taskview.TaskViewTaskController
import com.google.common.truth.Truth.assertThat
@@ -290,11 +290,10 @@ class BubbleBarAnimationHelperTest {
assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
- val bbevBottom = bbev.contentBottomOnScreen + bubblePositioner.insets.top
activityScenario.onActivity {
// notify that the IME top coordinate is greater than the bottom of the expanded view.
// there's no overlap so it should not be clipped.
- animationHelper.onImeTopChanged(bbevBottom * 2)
+ animationHelper.onImeTopChanged(bbev.contentBottomOnScreen * 2)
}
val outline = Outline()
bbev.outlineProvider.getOutline(bbev, outline)
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
index bfc798bb9c79..fbbcff2dee92 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
@@ -33,7 +33,6 @@ import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.R
-import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.bubbles.Bubble
import com.android.wm.shell.bubbles.BubbleExpandedViewManager
import com.android.wm.shell.bubbles.BubbleLogger
@@ -44,6 +43,7 @@ import com.android.wm.shell.bubbles.DeviceConfig
import com.android.wm.shell.bubbles.FakeBubbleExpandedViewManager
import com.android.wm.shell.bubbles.RegionSamplingProvider
import com.android.wm.shell.bubbles.UiEventSubject.Companion.assertThat
+import com.android.wm.shell.common.TestShellExecutor
import com.android.wm.shell.shared.handles.RegionSamplingHelper
import com.android.wm.shell.taskview.TaskView
import com.android.wm.shell.taskview.TaskViewTaskController
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
index 9b1645e9534c..5c5dde7da351 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
@@ -36,7 +36,6 @@ import com.android.internal.protolog.ProtoLog
import com.android.internal.statusbar.IStatusBarService
import com.android.wm.shell.R
import com.android.wm.shell.ShellTaskOrganizer
-import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.bubbles.Bubble
import com.android.wm.shell.bubbles.BubbleController
import com.android.wm.shell.bubbles.BubbleData
@@ -58,6 +57,7 @@ import com.android.wm.shell.common.DisplayInsetsController
import com.android.wm.shell.common.FloatingContentCoordinator
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.common.TaskStackListenerImpl
+import com.android.wm.shell.common.TestShellExecutor
import com.android.wm.shell.shared.TransactionPool
import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
import com.android.wm.shell.shared.bubbles.BubbleBarLocation
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/TestShellExecutor.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/common/TestShellExecutor.kt
index ef8e71c2590b..6b549b42cdcd 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/TestShellExecutor.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/common/TestShellExecutor.kt
@@ -14,9 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell
-
-import com.android.wm.shell.common.ShellExecutor
+package com.android.wm.shell.common
/**
* Simple implementation of [ShellExecutor] that collects all runnables and executes them
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
index 7347fbad5f5d..fc8b29912955 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
@@ -38,7 +38,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/bubble_popup_text_margin"
android:maxWidth="@dimen/bubble_popup_content_max_width"
- android:maxLines="1"
+ android:maxLines="2"
android:ellipsize="end"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Headline"
android:textColor="@androidprv:color/materialColorOnSurface"
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
index f0e1871168dd..1616707954f5 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
@@ -38,7 +38,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/bubble_popup_text_margin"
android:maxWidth="@dimen/bubble_popup_content_max_width"
- android:maxLines="1"
+ android:maxLines="2"
android:ellipsize="end"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Headline"
android:textColor="@androidprv:color/materialColorOnSurface"
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index 6f18eda13caf..cf9c18b43a5e 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -23,7 +23,7 @@
<string name="pip_menu_title" msgid="5393619322111827096">"תפריט"</string>
<string name="pip_menu_accessibility_title" msgid="8129016817688656249">"תפריט \'תמונה בתוך תמונה\'"</string>
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> במצב תמונה בתוך תמונה"</string>
- <string name="pip_notification_message" msgid="8854051911700302620">"אם אינך רוצה שהתכונה הזו תשמש את <xliff:g id="NAME">%s</xliff:g>, יש להקיש כדי לפתוח את ההגדרות ולהשבית את התכונה."</string>
+ <string name="pip_notification_message" msgid="8854051911700302620">"אם אינך רוצה שהתכונה הזו תשמש את <xliff:g id="NAME">%s</xliff:g>, יש ללחוץ כדי לפתוח את ההגדרות ולהשבית את התכונה."</string>
<string name="pip_play" msgid="3496151081459417097">"הפעלה"</string>
<string name="pip_pause" msgid="690688849510295232">"השהיה"</string>
<string name="pip_skip_to_next" msgid="8403429188794867653">"אפשר לדלג אל הבא"</string>
@@ -53,7 +53,7 @@
<string name="accessibility_split_top" msgid="2789329702027147146">"פיצול למעלה"</string>
<string name="accessibility_split_bottom" msgid="8694551025220868191">"פיצול למטה"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"איך להשתמש בתכונה \'מצב שימוש ביד אחת\'"</string>
- <string name="one_handed_tutorial_description" msgid="3486582858591353067">"כדי לצאת, יש להחליק למעלה מתחתית המסך או להקיש במקום כלשהו במסך מעל האפליקציה"</string>
+ <string name="one_handed_tutorial_description" msgid="3486582858591353067">"כדי לצאת, יש להחליק למעלה מתחתית המסך או ללחוץ במקום כלשהו במסך מעל האפליקציה"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"הפעלה של מצב שימוש ביד אחת"</string>
<string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"יציאה ממצב שימוש ביד אחת"</string>
<string name="bubbles_settings_button_description" msgid="1301286017420516912">"הגדרות לבועות של <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
@@ -76,33 +76,33 @@
<string name="bubble_fullscreen_text" msgid="1006758103218086231">"הצגה במסך מלא"</string>
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"אין להציג בועות לשיחה"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"לדבר בבועות"</string>
- <string name="bubbles_user_education_description" msgid="4215862563054175407">"שיחות חדשות מופיעות כסמלים צפים, או בועות. יש להקיש כדי לפתוח בועה. יש לגרור כדי להזיז אותה."</string>
+ <string name="bubbles_user_education_description" msgid="4215862563054175407">"שיחות חדשות מופיעות כסמלים צפים, או בועות. יש ללחוץ כדי לפתוח בועה. יש לגרור כדי להזיז אותה."</string>
<string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"שליטה בבועות, בכל זמן"</string>
- <string name="bubbles_user_education_manage" msgid="3460756219946517198">"יש להקיש על \'ניהול\' כדי להשבית את הבועות מהאפליקציה הזו"</string>
+ <string name="bubbles_user_education_manage" msgid="3460756219946517198">"יש ללחוץ על \'ניהול\' כדי להשבית את הבועות מהאפליקציה הזו"</string>
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"הבנתי"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"אין בועות מהזמן האחרון"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"בועות אחרונות ובועות שנסגרו יופיעו כאן"</string>
<string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"צ\'אט בבועות"</string>
- <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"שיחות חדשות מופיעות כסמלים בפינה התחתונה של המסך. אפשר להקיש כדי להרחיב אותן או לגרור כדי לסגור אותן."</string>
+ <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"שיחות חדשות מופיעות כסמלים בפינה התחתונה של המסך. אפשר ללחוץ כדי להרחיב אותן או לגרור כדי לסגור אותן."</string>
<string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"שליטה בבועות בכל זמן"</string>
- <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"אפשר להקיש כאן כדי לקבוע אילו אפליקציות ושיחות יוכלו להופיע בבועות"</string>
+ <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"אפשר ללחוץ כאן כדי לקבוע אילו אפליקציות ושיחות יוכלו להופיע בבועות"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"בועה"</string>
<string name="manage_bubbles_text" msgid="7730624269650594419">"ניהול"</string>
<string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"הבועה נסגרה."</string>
<string name="bubble_shortcut_label" msgid="666269077944378311">"בועות"</string>
<string name="bubble_shortcut_long_label" msgid="6088437544312894043">"הצגת הבועות"</string>
- <string name="restart_button_description" msgid="4564728020654658478">"כדי לראות טוב יותר יש להקיש ולהפעיל את האפליקציה הזו מחדש"</string>
+ <string name="restart_button_description" msgid="4564728020654658478">"כדי לראות טוב יותר יש ללחוץ ולהפעיל את האפליקציה הזו מחדש"</string>
<string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"אפשר לשנות את יחס הגובה-רוחב של האפליקציה הזו ב\'הגדרות\'"</string>
<string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"שינוי יחס גובה-רוחב"</string>
- <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"בעיות במצלמה?\nאפשר להקיש כדי לבצע התאמה מחדש"</string>
- <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"הבעיה לא נפתרה?\nאפשר להקיש כדי לחזור לגרסה הקודמת"</string>
- <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"אין בעיות במצלמה? אפשר להקיש כדי לסגור."</string>
+ <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"בעיות במצלמה?\nאפשר ללחוץ כדי לבצע התאמה מחדש"</string>
+ <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"הבעיה לא נפתרה?\nאפשר ללחוץ כדי לחזור לגרסה הקודמת"</string>
+ <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"אין בעיות במצלמה? אפשר ללחוץ כדי לסגור."</string>
<string name="windowing_app_handle_education_tooltip" msgid="2929643449849791854">"תפריט האפליקציה נמצא כאן"</string>
<string name="windowing_desktop_mode_image_button_education_tooltip" msgid="2523468503353474095">"כדי לפתוח כמה אפליקציות יחד, צריך לעבור למצב תצוגה למחשב"</string>
<string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"אפשר לחזור למסך מלא בכל שלב מתפריט האפליקציה"</string>
<string name="letterbox_education_dialog_title" msgid="7739895354143295358">"רוצה לראות ולעשות יותר?"</string>
<string name="letterbox_education_split_screen_text" msgid="449233070804658627">"צריך לגרור אפליקציה אחרת כדי להשתמש במסך המפוצל"</string>
- <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"צריך להקיש הקשה כפולה מחוץ לאפליקציה כדי למקם אותה מחדש"</string>
+ <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"צריך ללחוץ לחיצה כפולה מחוץ לאפליקציה כדי למקם אותה מחדש"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"הבנתי"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"מרחיבים כדי לקבל מידע נוסף."</string>
<string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"להפעיל מחדש לתצוגה טובה יותר?"</string>
@@ -110,7 +110,7 @@
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"ביטול"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"הפעלה מחדש"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"לא להציג שוב"</string>
- <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"אפשר להקיש הקשה כפולה כדי\nלהעביר את האפליקציה למקום אחר"</string>
+ <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"אפשר ללחוץ לחיצה כפולה כדי\nלהעביר את האפליקציה למקום אחר"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"הגדלה"</string>
<string name="minimize_button_text" msgid="271592547935841753">"מזעור"</string>
<string name="close_button_text" msgid="2913281996024033299">"סגירה"</string>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 9e0f107f7e55..abc4d08cd3ca 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -25,7 +25,7 @@
<string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> చిత్రంలో చిత్రం రూపంలో ఉంది"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> ఈ లక్షణాన్ని ఉపయోగించకూడదు అని మీరు అనుకుంటే, సెట్టింగ్‌లను తెరవడానికి ట్యాప్ చేసి, దీన్ని ఆఫ్ చేయండి."</string>
<string name="pip_play" msgid="3496151081459417097">"ప్లే చేయి"</string>
- <string name="pip_pause" msgid="690688849510295232">"పాజ్ చేయి"</string>
+ <string name="pip_pause" msgid="690688849510295232">"పాజ్ చేయండి"</string>
<string name="pip_skip_to_next" msgid="8403429188794867653">"దాటవేసి తర్వాత దానికి వెళ్లు"</string>
<string name="pip_skip_to_prev" msgid="7172158111196394092">"దాటవేసి మునుపటి దానికి వెళ్లు"</string>
<string name="accessibility_action_pip_resize" msgid="4623966104749543182">"సైజ్‌ మార్చు"</string>
diff --git a/libs/WindowManager/Shell/shared/Android.bp b/libs/WindowManager/Shell/shared/Android.bp
index c3ee0f76f550..d7669ed5cf34 100644
--- a/libs/WindowManager/Shell/shared/Android.bp
+++ b/libs/WindowManager/Shell/shared/Android.bp
@@ -56,6 +56,7 @@ android_library {
static_libs: [
"androidx.core_core-animation",
"androidx.dynamicanimation_dynamicanimation",
+ "com_android_wm_shell_flags_lib",
"jsr330",
],
kotlincflags: ["-Xjvm-default=all"],
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index e033f673d07d..840de2c86f92 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -40,7 +40,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
-import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.graphics.Rect;
import android.util.ArrayMap;
@@ -57,6 +56,9 @@ public class TransitionUtil {
/** Flag applied to a transition change to identify it as a divider bar for animation. */
public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
+ /** Flag applied to a transition change to identify it as a desktop wallpaper activity. */
+ public static final int FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY = FLAG_FIRST_CUSTOM << 1;
+
/** @return true if the transition was triggered by opening something vs closing something */
public static boolean isOpeningType(@WindowManager.TransitionType int type) {
return type == TRANSIT_OPEN
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleAnythingFlagHelper.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleAnythingFlagHelper.java
new file mode 100644
index 000000000000..e1f1d0c32eb0
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleAnythingFlagHelper.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2025 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.shared.bubbles;
+
+import com.android.wm.shell.Flags;
+
+/**
+ * Bubble anything has some dependent flags, this class simplifies the checks.
+ * (TODO: b/389737359 - remove this when the feature is launched).
+ */
+public class BubbleAnythingFlagHelper {
+
+ private BubbleAnythingFlagHelper() {}
+
+ /** Whether creating any bubble or the overall bubble anything feature is enabled. */
+ public static boolean enableCreateAnyBubble() {
+ return enableBubbleAnything() || Flags.enableCreateAnyBubble();
+ }
+
+ /**
+ * Whether creating any bubble and transforming to fullscreen, or the overall bubble anything
+ * feature is enabled.
+ */
+ public static boolean enableBubbleToFullscreen() {
+ return enableBubbleAnything()
+ || (Flags.enableBubbleToFullscreen()
+ && Flags.enableCreateAnyBubble());
+ }
+
+ /** Whether the overall bubble anything feature is enabled. */
+ public static boolean enableBubbleAnything() {
+ return Flags.enableBubbleAnything();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java
index 8cd7b0f48003..10023c9dba40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java
@@ -16,7 +16,9 @@
package com.android.wm.shell.appzoomout;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.systemui.Flags.spatialModelAppPushback;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
@@ -92,7 +94,9 @@ public class AppZoomOutController implements RemoteCallable<AppZoomOutController
mDisplayAreaOrganizer = displayAreaOrganizer;
mMainExecutor = mainExecutor;
- shellInit.addInitCallback(this::onInit, this);
+ if (spatialModelAppPushback()) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
}
private void onInit() {
@@ -100,6 +104,7 @@ public class AppZoomOutController implements RemoteCallable<AppZoomOutController
mDisplayController.addDisplayWindowListener(mDisplaysChangedListener);
mDisplayController.addDisplayChangingController(this);
+ updateDisplayLayout(mContext.getDisplayId());
mDisplayAreaOrganizer.registerOrganizer();
}
@@ -135,7 +140,9 @@ public class AppZoomOutController implements RemoteCallable<AppZoomOutController
public void onDisplayChange(int displayId, int fromRotation, int toRotation,
@Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction wct) {
// TODO: verify if there is synchronization issues.
- mDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation);
+ if (toRotation != ROTATION_UNDEFINED) {
+ mDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation);
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt
index f8f284238a98..8171312762ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt
@@ -33,7 +33,7 @@ import android.view.WindowManager.TRANSIT_CHANGE
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
-import com.android.systemui.car.Flags.autoTaskStackWindowing
+import com.android.wm.shell.Flags.enableAutoTaskStackController
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.ShellExecutor
@@ -66,7 +66,7 @@ class AutoTaskStackControllerImpl @Inject constructor(
private val defaultRootTaskPerDisplay = mutableMapOf<Int, Int>()
init {
- if (!autoTaskStackWindowing()) {
+ if (!enableAutoTaskStackController()) {
throw IllegalStateException("Failed to initialize" +
"AutoTaskStackController as the auto_task_stack_windowing TS flag is disabled.")
} else {
@@ -220,7 +220,7 @@ class AutoTaskStackControllerImpl @Inject constructor(
displayId: Int,
listener: RootTaskStackListener
) {
- if (!autoTaskStackWindowing()) {
+ if (!enableAutoTaskStackController()) {
Slog.e(
TAG, "Failed to create root task stack as the " +
"auto_task_stack_windowing TS flag is disabled."
@@ -236,7 +236,7 @@ class AutoTaskStackControllerImpl @Inject constructor(
}
override fun setDefaultRootTaskStackOnDisplay(displayId: Int, rootTaskStackId: Int?) {
- if (!autoTaskStackWindowing()) {
+ if (!enableAutoTaskStackController()) {
Slog.e(
TAG, "Failed to set default root task stack as the " +
"auto_task_stack_windowing TS flag is disabled."
@@ -280,7 +280,7 @@ class AutoTaskStackControllerImpl @Inject constructor(
}
override fun startTransition(transaction: AutoTaskStackTransaction): IBinder? {
- if (!autoTaskStackWindowing()) {
+ if (!enableAutoTaskStackController()) {
Slog.e(
TAG, "Failed to start transaction as the " +
"auto_task_stack_windowing TS flag is disabled."
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 862906a11424..62995319db80 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
@@ -136,23 +136,15 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl
// Update bitmap
val fg = InsetDrawable(overflowBtn?.iconDrawable, overflowIconInset)
- bitmap =
- iconFactory
- .createBadgedIconBitmap(AdaptiveIconDrawable(ColorDrawable(colorAccent), fg))
- .icon
+ val drawable = AdaptiveIconDrawable(ColorDrawable(colorAccent), fg)
+ bitmap = iconFactory.createBadgedIconBitmap(drawable).icon
// Update dot path
dotPath =
PathParser.createPathFromPathData(
res.getString(com.android.internal.R.string.config_icon_mask)
)
- val scale =
- iconFactory.normalizer.getScale(
- iconView!!.iconDrawable,
- null /* outBounds */,
- null /* path */,
- null /* outMaskShape */
- )
+ val scale = iconFactory.normalizer.getScale(iconView!!.iconDrawable)
val radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f
val matrix = Matrix()
matrix.setScale(
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 e073b02dc630..ac5b9c9866ed 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
@@ -674,9 +674,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
if (mTaskView != null) {
mTaskView.getBoundsOnScreen(mTempBounds);
}
- // return the bottom of the content rect, adjusted for insets so the result is in screen
- // coordinate
- return mTempBounds.bottom + mPositioner.getInsets().top;
+ return mTempBounds.bottom;
}
/** Update the amount by which to clip the expanded view at the bottom. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index eb1e72790a25..c9890a5b4963 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -447,8 +447,10 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
}
- private int imeTop(float surfaceOffset) {
- return mImeFrame.top + (int) surfaceOffset;
+ private int imeTop(float surfaceOffset, float surfacePositionY) {
+ // surfaceOffset is already offset by the surface's top inset, so we need to subtract
+ // the top inset so that the return value is in screen coordinates.
+ return mImeFrame.top + (int) (surfaceOffset - surfacePositionY);
}
private boolean calcIsFloating(InsetsSource imeSource) {
@@ -581,7 +583,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
final float alpha = (mAnimateAlpha || isFloating)
? (value - hiddenY) / (shownY - hiddenY) : 1f;
t.setAlpha(animatingLeash, alpha);
- dispatchPositionChanged(mDisplayId, imeTop(value), t);
+ dispatchPositionChanged(mDisplayId, imeTop(value, defaultY), t);
t.apply();
mTransactionPool.release(t);
});
@@ -600,11 +602,12 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
t.setPosition(animatingLeash, x, value);
if (DEBUG) {
Slog.d(TAG, "onAnimationStart d:" + mDisplayId + " top:"
- + imeTop(hiddenY) + "->" + imeTop(shownY)
+ + imeTop(hiddenY, defaultY) + "->" + imeTop(shownY, defaultY)
+ " showing:" + (mAnimationDirection == DIRECTION_SHOW));
}
- int flags = dispatchStartPositioning(mDisplayId, imeTop(hiddenY),
- imeTop(shownY), mAnimationDirection == DIRECTION_SHOW, isFloating, t);
+ int flags = dispatchStartPositioning(mDisplayId, imeTop(hiddenY, defaultY),
+ imeTop(shownY, defaultY), mAnimationDirection == DIRECTION_SHOW,
+ isFloating, t);
mAnimateAlpha = (flags & ImePositionProcessor.IME_ANIMATION_NO_ALPHA) == 0;
final float alpha = (mAnimateAlpha || isFloating)
? (value - hiddenY) / (shownY - hiddenY)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
index cdfa14bbc4e2..9b9988457808 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.desktopmode
+import com.android.window.flags.Flags
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
import com.android.wm.shell.sysui.ShellCommandHandler
import java.io.PrintWriter
@@ -26,35 +27,28 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl
override fun onShellCommand(args: Array<String>, pw: PrintWriter): Boolean =
when (args[0]) {
- "moveToDesktop" -> {
- if (!runMoveToDesktop(args, pw)) {
- pw.println("Task not found. Please enter a valid taskId.")
- false
- } else {
- true
- }
- }
- "moveToNextDisplay" -> {
- if (!runMoveToNextDisplay(args, pw)) {
- pw.println("Task not found. Please enter a valid taskId.")
- false
- } else {
- true
- }
- }
+ "moveTaskToDesk" -> runMoveTaskToDesk(args, pw)
+ "moveToNextDisplay" -> runMoveToNextDisplay(args, pw)
+ "createDesk" -> runCreateDesk(args, pw)
+ "activateDesk" -> runActivateDesk(args, pw)
+ "removeDesk" -> runRemoveDesk(args, pw)
+ "removeAllDesks" -> runRemoveAllDesks(args, pw)
+ "moveTaskToFront" -> runMoveTaskToFront(args, pw)
+ "moveTaskOutOfDesk" -> runMoveTaskOutOfDesk(args, pw)
+ "canCreateDesk" -> runCanCreateDesk(args, pw)
+ "getActiveDeskId" -> runGetActiveDeskId(args, pw)
else -> {
pw.println("Invalid command: ${args[0]}")
false
}
}
- private fun runMoveToDesktop(args: Array<String>, pw: PrintWriter): Boolean {
+ private fun runMoveTaskToDesk(args: Array<String>, pw: PrintWriter): Boolean {
if (args.size < 2) {
// First argument is the action name.
pw.println("Error: task id should be provided as arguments")
return false
}
-
val taskId =
try {
args[1].toInt()
@@ -62,7 +56,22 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl
pw.println("Error: task id should be an integer")
return false
}
- return controller.moveTaskToDesktop(taskId, transitionSource = UNKNOWN)
+ if (!Flags.enableMultipleDesktopsBackend()) {
+ return controller.moveTaskToDesktop(taskId, transitionSource = UNKNOWN)
+ }
+ if (args.size < 3) {
+ pw.println("Error: desk id should be provided as arguments")
+ return false
+ }
+ val deskId =
+ try {
+ args[2].toInt()
+ } catch (e: NumberFormatException) {
+ pw.println("Error: desk id should be an integer")
+ return false
+ }
+ pw.println("Not implemented.")
+ return false
}
private fun runMoveToNextDisplay(args: Array<String>, pw: PrintWriter): Boolean {
@@ -84,10 +93,184 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl
return true
}
+ private fun runCreateDesk(args: Array<String>, pw: PrintWriter): Boolean {
+ if (!Flags.enableMultipleDesktopsBackend()) {
+ pw.println("Not supported.")
+ return false
+ }
+ if (args.size < 2) {
+ // First argument is the action name.
+ pw.println("Error: desk id should be provided as arguments")
+ return false
+ }
+ val displayId =
+ try {
+ args[1].toInt()
+ } catch (e: NumberFormatException) {
+ pw.println("Error: display id should be an integer")
+ return false
+ }
+ pw.println("Not implemented.")
+ return false
+ }
+
+ private fun runActivateDesk(args: Array<String>, pw: PrintWriter): Boolean {
+ if (!Flags.enableMultipleDesktopsBackend()) {
+ pw.println("Not supported.")
+ return false
+ }
+ if (args.size < 2) {
+ // First argument is the action name.
+ pw.println("Error: desk id should be provided as arguments")
+ return false
+ }
+ val deskId =
+ try {
+ args[1].toInt()
+ } catch (e: NumberFormatException) {
+ pw.println("Error: desk id should be an integer")
+ return false
+ }
+ pw.println("Not implemented.")
+ return false
+ }
+
+ private fun runRemoveDesk(args: Array<String>, pw: PrintWriter): Boolean {
+ if (!Flags.enableMultipleDesktopsBackend()) {
+ pw.println("Not supported.")
+ return false
+ }
+ if (args.size < 2) {
+ // First argument is the action name.
+ pw.println("Error: desk id should be provided as arguments")
+ return false
+ }
+ val deskId =
+ try {
+ args[1].toInt()
+ } catch (e: NumberFormatException) {
+ pw.println("Error: desk id should be an integer")
+ return false
+ }
+ pw.println("Not implemented.")
+ return false
+ }
+
+ private fun runRemoveAllDesks(args: Array<String>, pw: PrintWriter): Boolean {
+ if (!Flags.enableMultipleDesktopsBackend()) {
+ pw.println("Not supported.")
+ return false
+ }
+ pw.println("Not implemented.")
+ return false
+ }
+
+ private fun runMoveTaskToFront(args: Array<String>, pw: PrintWriter): Boolean {
+ if (!Flags.enableMultipleDesktopsBackend()) {
+ pw.println("Not supported.")
+ return false
+ }
+ if (args.size < 2) {
+ // First argument is the action name.
+ pw.println("Error: task id should be provided as arguments")
+ return false
+ }
+ val taskId =
+ try {
+ args[1].toInt()
+ } catch (e: NumberFormatException) {
+ pw.println("Error: task id should be an integer")
+ return false
+ }
+ pw.println("Not implemented.")
+ return false
+ }
+
+ private fun runMoveTaskOutOfDesk(args: Array<String>, pw: PrintWriter): Boolean {
+ if (!Flags.enableMultipleDesktopsBackend()) {
+ pw.println("Not supported.")
+ return false
+ }
+ if (args.size < 2) {
+ // First argument is the action name.
+ pw.println("Error: task id should be provided as arguments")
+ return false
+ }
+ val taskId =
+ try {
+ args[1].toInt()
+ } catch (e: NumberFormatException) {
+ pw.println("Error: task id should be an integer")
+ return false
+ }
+ pw.println("Not implemented.")
+ return false
+ }
+
+ private fun runCanCreateDesk(args: Array<String>, pw: PrintWriter): Boolean {
+ if (!Flags.enableMultipleDesktopsBackend()) {
+ pw.println("Not supported.")
+ return false
+ }
+ val displayId =
+ try {
+ args[1].toInt()
+ } catch (e: NumberFormatException) {
+ pw.println("Error: display id should be an integer")
+ return false
+ }
+ pw.println("Not implemented.")
+ return false
+ }
+
+ private fun runGetActiveDeskId(args: Array<String>, pw: PrintWriter): Boolean {
+ if (!Flags.enableMultipleDesktopsBackend()) {
+ pw.println("Not supported.")
+ return false
+ }
+ if (args.size < 2) {
+ // First argument is the action name.
+ pw.println("Error: task id should be provided as arguments")
+ return false
+ }
+ val displayId =
+ try {
+ args[1].toInt()
+ } catch (e: NumberFormatException) {
+ pw.println("Error: display id should be an integer")
+ return false
+ }
+ pw.println("Not implemented.")
+ return false
+ }
+
override fun printShellCommandHelp(pw: PrintWriter, prefix: String) {
- pw.println("$prefix moveToDesktop <taskId> ")
- pw.println("$prefix Move a task with given id to desktop mode.")
- pw.println("$prefix moveToNextDisplay <taskId> ")
+ if (!Flags.enableMultipleDesktopsBackend()) {
+ pw.println("$prefix moveTaskToDesk <taskId> ")
+ pw.println("$prefix Move a task with given id to desktop mode.")
+ pw.println("$prefix moveToNextDisplay <taskId> ")
+ pw.println("$prefix Move a task with given id to next display.")
+ return
+ }
+ pw.println("$prefix moveTaskToDesk <taskId> <deskId>")
+ pw.println("$prefix Move a task with given id to the given desk and activate it.")
+ pw.println("$prefix moveToNextDisplay <taskId>")
pw.println("$prefix Move a task with given id to next display.")
+ pw.println("$prefix createDesk <displayId>")
+ pw.println("$prefix Creates a desk on the given display.")
+ pw.println("$prefix activateDesk <deskId>")
+ pw.println("$prefix Activates the given desk.")
+ pw.println("$prefix removeDesk <deskId> ")
+ pw.println("$prefix Removes the given desk and all of its windows.")
+ pw.println("$prefix removeAllDesks")
+ pw.println("$prefix Removes all the desks and their windows across all displays")
+ pw.println("$prefix moveTaskToFront <taskId>")
+ pw.println("$prefix Moves a task in front of its siblings.")
+ pw.println("$prefix moveTaskOutOfDesk <taskId>")
+ pw.println("$prefix Moves the given desktop task out of the desk into fullscreen mode.")
+ pw.println("$prefix canCreateDesk <displayId>")
+ pw.println("$prefix Whether creating a new desk in the given display is allowed.")
+ pw.println("$prefix getActivateDeskId <displayId>")
+ pw.println("$prefix Print the id of the active desk in the given display.")
}
}
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 73d15270c811..f38957e48dbf 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
@@ -552,7 +552,11 @@ class DesktopTasksController(
)
val wct = WindowContainerTransaction()
exitSplitIfApplicable(wct, taskInfo)
- moveHomeTask(wct, toTop = true)
+ if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
+ moveHomeTask(wct, toTop = true, taskInfo.displayId)
+ } else {
+ moveHomeTask(wct, toTop = true)
+ }
val taskIdToMinimize =
bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId)
addMoveToDesktopChanges(wct, taskInfo)
@@ -1309,11 +1313,15 @@ class DesktopTasksController(
): Int? {
logV("bringDesktopAppsToFront, newTaskId=%d", newTaskIdInFront)
// Move home to front, ensures that we go back home when all desktop windows are closed
- moveHomeTask(wct, toTop = true)
+ if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
+ moveHomeTask(wct, toTop = true, displayId)
+ } else {
+ moveHomeTask(wct, toTop = true)
+ }
// Currently, we only handle the desktop on the default display really.
if (
- (displayId == DEFAULT_DISPLAY || Flags.enableBugFixesForSecondaryDisplay()) &&
+ (displayId == DEFAULT_DISPLAY || Flags.enablePerDisplayDesktopWallpaperActivity()) &&
ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()
) {
// Add translucent wallpaper activity to show the wallpaper underneath
@@ -1359,9 +1367,13 @@ class DesktopTasksController(
return taskIdToMinimize
}
- private fun moveHomeTask(wct: WindowContainerTransaction, toTop: Boolean) {
+ private fun moveHomeTask(
+ wct: WindowContainerTransaction,
+ toTop: Boolean,
+ displayId: Int = DEFAULT_DISPLAY,
+ ) {
shellTaskOrganizer
- .getRunningTasks(context.displayId)
+ .getRunningTasks(displayId)
.firstOrNull { task -> task.activityType == ACTIVITY_TYPE_HOME }
?.let { homeTask -> wct.reorder(homeTask.getToken(), /* onTop= */ toTop) }
}
@@ -1370,12 +1382,19 @@ class DesktopTasksController(
logV("addWallpaperActivity")
if (Flags.enableDesktopWallpaperActivityForSystemUser()) {
val intent = Intent(context, DesktopWallpaperActivity::class.java)
+ if (
+ desktopWallpaperActivityTokenProvider.getToken(displayId) == null &&
+ Flags.enablePerDisplayDesktopWallpaperActivity()
+ ) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
+ }
val options =
ActivityOptions.makeBasic().apply {
launchWindowingMode = WINDOWING_MODE_FULLSCREEN
pendingIntentBackgroundActivityStartMode =
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
- if (Flags.enableBugFixesForSecondaryDisplay()) {
+ if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
launchDisplayId = displayId
}
}
@@ -1391,13 +1410,20 @@ class DesktopTasksController(
val userHandle = UserHandle.of(userId)
val userContext = context.createContextAsUser(userHandle, /* flags= */ 0)
val intent = Intent(userContext, DesktopWallpaperActivity::class.java)
+ if (
+ desktopWallpaperActivityTokenProvider.getToken(displayId) == null &&
+ Flags.enablePerDisplayDesktopWallpaperActivity()
+ ) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
+ }
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId)
val options =
ActivityOptions.makeBasic().apply {
launchWindowingMode = WINDOWING_MODE_FULLSCREEN
pendingIntentBackgroundActivityStartMode =
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
- if (Flags.enableBugFixesForSecondaryDisplay()) {
+ if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
launchDisplayId = displayId
}
}
@@ -1414,8 +1440,8 @@ class DesktopTasksController(
}
}
- private fun removeWallpaperActivity(wct: WindowContainerTransaction) {
- desktopWallpaperActivityTokenProvider.getToken()?.let { token ->
+ private fun removeWallpaperActivity(wct: WindowContainerTransaction, displayId: Int) {
+ desktopWallpaperActivityTokenProvider.getToken(displayId)?.let { token ->
logV("removeWallpaperActivity")
if (Flags.enableDesktopWallpaperActivityForSystemUser()) {
wct.reorder(token, /* onTop= */ false)
@@ -1440,9 +1466,6 @@ class DesktopTasksController(
if (!taskRepository.isOnlyVisibleNonClosingTask(taskId, displayId)) {
return
}
- if (displayId != DEFAULT_DISPLAY) {
- return
- }
} else if (
Flags.enableDesktopWindowingPip() &&
taskRepository.isMinimizedPipPresentInDisplay(displayId) &&
@@ -1457,7 +1480,7 @@ class DesktopTasksController(
desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted(
FULLSCREEN_ANIMATION_DURATION
)
- removeWallpaperActivity(wct)
+ removeWallpaperActivity(wct, displayId)
}
fun releaseVisualIndicator() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index b9a65fee7d4d..14c8429766cc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -60,7 +60,9 @@ class DesktopTasksTransitionObserver(
shellInit: ShellInit,
) : Transitions.TransitionObserver {
- private var transitionToCloseWallpaper: IBinder? = null
+ data class CloseWallpaperTransition(val transition: IBinder, val displayId: Int)
+
+ private var transitionToCloseWallpaper: CloseWallpaperTransition? = null
/* Pending PiP transition and its associated display id and task id. */
private var pendingPipTransitionAndPipTask: Triple<IBinder, Int, Int>? = null
private var currentProfileId: Int
@@ -248,9 +250,10 @@ class DesktopTasksTransitionObserver(
desktopRepository.getVisibleTaskCount(taskInfo.displayId) == 0 &&
change.mode == TRANSIT_CLOSE &&
taskInfo.windowingMode == WINDOWING_MODE_FREEFORM &&
- desktopWallpaperActivityTokenProvider.getToken() != null
+ desktopWallpaperActivityTokenProvider.getToken(taskInfo.displayId) != null
) {
- transitionToCloseWallpaper = transition
+ transitionToCloseWallpaper =
+ CloseWallpaperTransition(transition, taskInfo.displayId)
currentProfileId = taskInfo.userId
}
}
@@ -265,25 +268,28 @@ class DesktopTasksTransitionObserver(
}
override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
+ val lastSeenTransitionToCloseWallpaper = transitionToCloseWallpaper
// TODO: b/332682201 Update repository state
- if (transitionToCloseWallpaper == transition) {
+ if (lastSeenTransitionToCloseWallpaper?.transition == transition) {
// TODO: b/362469671 - Handle merging the animation when desktop is also closing.
- desktopWallpaperActivityTokenProvider.getToken()?.let { wallpaperActivityToken ->
- if (Flags.enableDesktopWallpaperActivityForSystemUser()) {
- transitions.startTransition(
- TRANSIT_TO_BACK,
- WindowContainerTransaction()
- .reorder(wallpaperActivityToken, /* onTop= */ false),
- null,
- )
- } else {
- transitions.startTransition(
- TRANSIT_CLOSE,
- WindowContainerTransaction().removeTask(wallpaperActivityToken),
- null,
- )
+ desktopWallpaperActivityTokenProvider
+ .getToken(lastSeenTransitionToCloseWallpaper.displayId)
+ ?.let { wallpaperActivityToken ->
+ if (Flags.enableDesktopWallpaperActivityForSystemUser()) {
+ transitions.startTransition(
+ TRANSIT_TO_BACK,
+ WindowContainerTransaction()
+ .reorder(wallpaperActivityToken, /* onTop= */ false),
+ null,
+ )
+ } else {
+ transitions.startTransition(
+ TRANSIT_CLOSE,
+ WindowContainerTransaction().removeTask(wallpaperActivityToken),
+ null,
+ )
+ }
}
- }
transitionToCloseWallpaper = null
} else if (pendingPipTransitionAndPipTask?.first == transition) {
val desktopRepository = desktopUserRepositories.getProfile(currentProfileId)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 6e7740dc13e3..91150b0070ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -129,6 +129,20 @@ public abstract class PipTransitionController implements Transitions.TransitionH
}
/**
+ * Called when the Shell wants to start an exit-via-expand from Pip transition/animation.
+ */
+ public void startExpandTransition(WindowContainerTransaction out) {
+ // Default implementation does nothing.
+ }
+
+ /**
+ * Called when the Shell wants to start a remove Pip transition/animation.
+ */
+ public void startRemoveTransition(boolean withFadeout) {
+ // Default implementation does nothing.
+ }
+
+ /**
* Called when the Shell wants to start resizing Pip transition/animation.
*
* @param duration the suggested duration for resize animation.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index 9babe9e9e4eb..e22cd37ab4b7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -344,13 +344,22 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
*/
@Override
public void dismissPip() {
+ dismissPip(true /* withFadeout */);
+ }
+
+ /**
+ * Dismisses the pinned stack.
+ *
+ * @param withFadeout should animate with fadeout for the removal
+ */
+ public void dismissPip(boolean withFadeout) {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: removePip: callers=\n%s", TAG, Debug.getCallers(5, " "));
}
cancelPhysicsAnimation();
mMenuController.hideMenu(ANIM_TYPE_DISMISS, false /* resize */);
- mPipScheduler.scheduleRemovePip();
+ mPipScheduler.scheduleRemovePip(withFadeout);
}
/** Sets the movement bounds to use to constrain PIP position animations. */
@@ -473,7 +482,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
mPipBoundsState.getMovementBounds().bottom + getBounds().height() * 2,
0,
mSpringConfig)
- .withEndActions(this::dismissPip);
+ .withEndActions(() -> dismissPip(false /* withFadeout */));
startBoundsAnimator(
getBounds().left /* toX */, getBounds().bottom + getBounds().height() /* toY */);
@@ -772,7 +781,6 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
case PipTransitionState.EXITING_PIP:
// We need to force finish any local animators if about to leave PiP, to avoid
// breaking the state (e.g. leashes are cleaned up upon exit).
- if (!mPipBoundsState.getMotionBoundsState().isInMotion()) break;
cancelPhysicsAnimation();
settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */);
break;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index ea8dac982703..ed532cad0523 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -19,9 +19,6 @@ package com.android.wm.shell.pip2.phone;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
-
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
@@ -112,19 +109,6 @@ public class PipScheduler {
return wct;
}
- @Nullable
- private WindowContainerTransaction getRemovePipTransaction() {
- WindowContainerToken pipTaskToken = mPipTransitionState.getPipTaskToken();
- if (pipTaskToken == null) {
- return null;
- }
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setBounds(pipTaskToken, null);
- wct.setWindowingMode(pipTaskToken, WINDOWING_MODE_UNDEFINED);
- wct.reorder(pipTaskToken, false);
- return wct;
- }
-
/**
* Schedules exit PiP via expand transition.
*/
@@ -133,21 +117,16 @@ public class PipScheduler {
if (!mPipTransitionState.isInPip()) return;
WindowContainerTransaction wct = getExitPipViaExpandTransaction();
if (wct != null) {
- mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct,
- null /* destinationBounds */);
+ mPipTransitionController.startExpandTransition(wct);
}
});
}
/** Schedules remove PiP transition. */
- public void scheduleRemovePip() {
+ public void scheduleRemovePip(boolean withFadeout) {
mMainExecutor.execute(() -> {
if (!mPipTransitionState.isInPip()) return;
- WindowContainerTransaction wct = getRemovePipTransaction();
- if (wct != null) {
- mPipTransitionController.startExitTransition(TRANSIT_REMOVE_PIP, wct,
- null /* destinationBounds */);
- }
+ mPipTransitionController.startRemoveTransition(withFadeout);
});
}
@@ -216,9 +195,11 @@ public class PipScheduler {
* @param degrees the angle to rotate the bounds to.
*/
public void scheduleUserResizePip(Rect toBounds, float degrees) {
- if (toBounds.isEmpty()) {
+ if (toBounds.isEmpty() || !mPipTransitionState.isInPip()) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Attempted to user resize PIP to empty bounds, aborting.", TAG);
+ "%s: Attempted to user resize PIP in invalid state, aborting;"
+ + "toBounds=%s, mPipTransitionState=%s",
+ TAG, toBounds, mPipTransitionState);
return;
}
SurfaceControl leash = mPipTransitionState.getPinnedTaskLeash();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 0a42c71ce095..4902455cae16 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.pip2.phone;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
@@ -126,6 +127,7 @@ public class PipTransition extends PipTransitionController implements
@Nullable
private IBinder mResizeTransition;
private int mBoundsChangeDuration = BOUNDS_CHANGE_JUMPCUT_DURATION;
+ private boolean mPendingRemoveWithFadeout;
//
@@ -184,15 +186,19 @@ public class PipTransition extends PipTransitionController implements
//
@Override
- public void startExitTransition(int type, WindowContainerTransaction out,
- @Nullable Rect destinationBounds) {
- if (out == null) {
- return;
- }
- IBinder transition = mTransitions.startTransition(type, out, this);
- if (type == TRANSIT_EXIT_PIP) {
- mExitViaExpandTransition = transition;
- }
+ public void startExpandTransition(WindowContainerTransaction out) {
+ if (out == null) return;
+ mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
+ mExitViaExpandTransition = mTransitions.startTransition(TRANSIT_EXIT_PIP, out, this);
+ }
+
+ @Override
+ public void startRemoveTransition(boolean withFadeout) {
+ final WindowContainerTransaction wct = getRemovePipTransaction();
+ if (wct == null) return;
+ mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
+ mPendingRemoveWithFadeout = withFadeout;
+ mTransitions.startTransition(TRANSIT_REMOVE_PIP, wct, this);
}
@Override
@@ -284,7 +290,6 @@ public class PipTransition extends PipTransitionController implements
finishCallback);
} else if (transition == mExitViaExpandTransition) {
mExitViaExpandTransition = null;
- mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
return startExpandAnimation(info, startTransaction, finishTransaction, finishCallback);
} else if (transition == mResizeTransition) {
mResizeTransition = null;
@@ -690,11 +695,19 @@ public class PipTransition extends PipTransitionController implements
TransitionInfo.Change pipChange = getChangeByToken(info,
mPipTransitionState.getPipTaskToken());
mFinishCallback = finishCallback;
- PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipChange.getLeash(),
- startTransaction, PipAlphaAnimator.FADE_OUT);
+
finishTransaction.setAlpha(pipChange.getLeash(), 0f);
- animator.setAnimationEndCallback(this::finishTransition);
- animator.start();
+ if (mPendingRemoveWithFadeout) {
+ PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipChange.getLeash(),
+ startTransaction, PipAlphaAnimator.FADE_OUT);
+ animator.setAnimationEndCallback(this::finishTransition);
+ animator.start();
+ } else {
+ // Jumpcut to a faded-out PiP if no fadeout animation was requested.
+ startTransaction.setAlpha(pipChange.getLeash(), 0f);
+ startTransaction.apply();
+ finishTransition();
+ }
return true;
}
@@ -824,6 +837,19 @@ public class PipTransition extends PipTransitionController implements
return wct;
}
+ @Nullable
+ private WindowContainerTransaction getRemovePipTransaction() {
+ WindowContainerToken pipTaskToken = mPipTransitionState.getPipTaskToken();
+ if (pipTaskToken == null) {
+ return null;
+ }
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(pipTaskToken, null);
+ wct.setWindowingMode(pipTaskToken, WINDOWING_MODE_UNDEFINED);
+ wct.reorder(pipTaskToken, false);
+ return wct;
+ }
+
private boolean isAutoEnterInButtonNavigation(@NonNull TransitionRequestInfo requestInfo) {
final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipChange() != null
? requestInfo.getPipChange().getTaskInfo() : null;
@@ -1000,6 +1026,7 @@ public class PipTransition extends PipTransitionController implements
}
mPipTransitionState.setPinnedTaskLeash(null);
mPipTransitionState.setPipTaskInfo(null);
+ mPendingRemoveWithFadeout = false;
break;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
index 6f9f40a487c7..8805cbb0dfbd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -27,7 +27,9 @@ import android.window.WindowContainerToken;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.Preconditions;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import java.io.PrintWriter;
@@ -201,6 +203,13 @@ public class PipTransitionState {
Preconditions.checkArgument(extra != null && !extra.isEmpty(),
"No extra bundle for " + stateToString(state) + " state.");
}
+ if (!shouldTransitionToState(state)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Attempted to transition to an invalid state=%s, while in %s",
+ TAG, stateToString(state), this);
+ return;
+ }
+
if (mState != state) {
final int prevState = mState;
mState = state;
@@ -374,6 +383,17 @@ public class PipTransitionState {
return ++mPrevCustomState;
}
+ private boolean shouldTransitionToState(@TransitionState int newState) {
+ switch (newState) {
+ case SCHEDULED_BOUNDS_CHANGE:
+ // Allow scheduling bounds change only while in PiP, except for if another bounds
+ // change was scheduled but hasn't started playing yet.
+ return isInPip();
+ default:
+ return true;
+ }
+ }
+
private static String stateToString(int state) {
switch (state) {
case UNDEFINED: return "undefined";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index d6f9183a4281..0182588398a4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -54,7 +54,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
-import com.android.launcher3.Flags;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
@@ -103,6 +102,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
private final RecentTasksImpl mImpl = new RecentTasksImpl();
private final ActivityTaskManager mActivityTaskManager;
private final TaskStackTransitionObserver mTaskStackTransitionObserver;
+ private final RecentsShellCommandHandler mRecentsShellCommandHandler;
private RecentsTransitionHandler mTransitionHandler = null;
private IRecentTasksListener mListener;
private final boolean mPcFeatureEnabled;
@@ -167,6 +167,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
mDesktopUserRepositories = desktopUserRepositories;
mTaskStackTransitionObserver = taskStackTransitionObserver;
mMainExecutor = mainExecutor;
+ mRecentsShellCommandHandler = new RecentsShellCommandHandler(this);
shellInit.addInitCallback(this::onInit, this);
}
@@ -183,6 +184,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
mShellController.addExternalInterface(IRecentTasks.DESCRIPTOR,
this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
+ mShellCommandHandler.addCommandCallback("recents", mRecentsShellCommandHandler, this);
mUserId = ActivityManager.getCurrentUser();
mDesktopUserRepositories.ifPresent(
desktopUserRepositories ->
@@ -550,9 +552,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
groupedTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
mTaskSplitBoundsMap.get(pairedTaskId)));
} else {
- if (
- Flags.enableUseTopVisibleActivityForExcludeFromRecentTask()
- && isWallpaperTask(taskInfo)) {
+ if (isWallpaperTask(taskInfo)) {
// Don't add the wallpaper task as an entry in grouped tasks
continue;
}
@@ -656,6 +656,11 @@ public class RecentTasksController implements TaskStackListenerCallback,
return mActivityTaskManager.removeTask(taskId);
}
+ /** Removes all recent tasks that are visible. */
+ public void removeAllVisibleRecentTasks() throws RemoteException {
+ ActivityTaskManager.getService().removeAllVisibleRecentTasks();
+ }
+
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsShellCommandHandler.kt
new file mode 100644
index 000000000000..f786e079ed93
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsShellCommandHandler.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2025 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.recents
+
+import android.os.RemoteException
+import com.android.wm.shell.sysui.ShellCommandHandler.ShellCommandActionHandler
+import java.io.PrintWriter
+
+class RecentsShellCommandHandler(
+ private val recentTasksController: RecentTasksController
+) : ShellCommandActionHandler {
+ override fun onShellCommand(args: Array<out String>, pw: PrintWriter): Boolean {
+ when (args[0]) {
+ "clearAll" -> return runClearAll(pw)
+ else -> {
+ pw.println("Invalid command: " + args[0])
+ return false
+ }
+ }
+ }
+
+ override fun printShellCommandHelp(pw: PrintWriter, prefix: String) {
+ pw.println("${prefix}clearAll")
+ pw.println("$prefix Clears all visible recent tasks.")
+ }
+
+ private fun runClearAll(pw: PrintWriter): Boolean {
+ try {
+ recentTasksController.removeAllVisibleRecentTasks()
+ } catch (e: RemoteException) {
+ pw.println("Exception while removing visible recent tasks:")
+ e.printStackTrace(pw)
+ return false
+ }
+ return true
+ }
+} \ No newline at end of file
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 c27ef295db51..c9136b4ad18d 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
@@ -929,8 +929,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
for (int taskId : taskIds) {
ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
if (task != null) {
- wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED)
- .setBounds(task.token, null);
+ wct.setWindowingMode(task.getToken(), WINDOWING_MODE_UNDEFINED)
+ .setBounds(task.getToken(), null);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 81f444ba2af3..c5994f83429a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -633,7 +633,7 @@ public class SplashscreenContentDrawer {
private class ShapeIconFactory extends BaseIconFactory {
protected ShapeIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
- super(context, fillResIconDpi, iconBitmapSize, true /* shapeDetection */);
+ super(context, fillResIconDpi, iconBitmapSize);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 8dd14983e122..5c7dd078ee45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -536,9 +536,12 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
}
return;
}
+ // Cache it to avoid NPE and make sure to remove it from recents history.
+ // mTaskToken can be cleared in onTaskVanished() when the task is removed.
+ final WindowContainerToken taskToken = mTaskToken;
mShellExecutor.execute(() -> {
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.removeTask(mTaskToken);
+ wct.removeTask(taskToken);
mTaskViewTransitions.closeTaskView(wct, this);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java
index 6d01e247b48d..e04682a4b86f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java
@@ -17,6 +17,8 @@
package com.android.wm.shell.transition;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
@@ -66,36 +68,45 @@ public class FocusTransitionObserver {
if (!enableDisplayFocusInShellTransitions()) {
return;
}
+ final SparseArray<RunningTaskInfo> lastTransitionFocusedTasks =
+ mFocusedTaskOnDisplay.clone();
+
final List<TransitionInfo.Change> changes = info.getChanges();
for (int i = changes.size() - 1; i >= 0; i--) {
final TransitionInfo.Change change = changes.get(i);
final RunningTaskInfo task = change.getTaskInfo();
- if (task != null
- && (change.hasFlags(FLAG_MOVED_TO_TOP) || change.getMode() == TRANSIT_OPEN)) {
- final RunningTaskInfo lastFocusedTaskOnDisplay =
- mFocusedTaskOnDisplay.get(task.displayId);
- if (lastFocusedTaskOnDisplay != null) {
- mTmpTasksToBeNotified.add(lastFocusedTaskOnDisplay);
+ if (task != null) {
+ if (change.hasFlags(FLAG_MOVED_TO_TOP) || change.getMode() == TRANSIT_OPEN) {
+ updateFocusedTaskPerDisplay(task, task.displayId);
+ } else {
+ // Update focus assuming that any task moved to another display is focused in
+ // the new display.
+ // TODO(sahok): remove this logic when b/388665104 is fixed
+ final boolean isBeyondDisplay = change.getStartDisplayId() != INVALID_DISPLAY
+ && change.getEndDisplayId() != INVALID_DISPLAY
+ && change.getStartDisplayId() != change.getEndDisplayId();
+
+ RunningTaskInfo lastTransitionFocusedTaskOnStartDisplay =
+ lastTransitionFocusedTasks.get(change.getStartDisplayId());
+ final boolean isLastTransitionFocused =
+ lastTransitionFocusedTaskOnStartDisplay != null
+ && task.taskId
+ == lastTransitionFocusedTaskOnStartDisplay.taskId;
+ if (change.getMode() == TRANSIT_CHANGE && isBeyondDisplay
+ && isLastTransitionFocused) {
+ // The task have moved to another display and keeps its focus.
+ // MOVE_TO_TOP is not reported but we need to update the focused task in
+ // the end display.
+ updateFocusedTaskPerDisplay(task, change.getEndDisplayId());
+ }
}
- mTmpTasksToBeNotified.add(task);
- mFocusedTaskOnDisplay.put(task.displayId, task);
}
+
if (change.hasFlags(FLAG_IS_DISPLAY) && change.hasFlags(FLAG_MOVED_TO_TOP)) {
if (mFocusedDisplayId != change.getEndDisplayId()) {
- final RunningTaskInfo lastGloballyFocusedTask =
- mFocusedTaskOnDisplay.get(mFocusedDisplayId);
- if (lastGloballyFocusedTask != null) {
- mTmpTasksToBeNotified.add(lastGloballyFocusedTask);
- }
- mFocusedDisplayId = change.getEndDisplayId();
- notifyFocusedDisplayChanged();
- final RunningTaskInfo currentGloballyFocusedTask =
- mFocusedTaskOnDisplay.get(mFocusedDisplayId);
- if (currentGloballyFocusedTask != null) {
- mTmpTasksToBeNotified.add(currentGloballyFocusedTask);
- }
+ updateFocusedDisplay(change.getEndDisplayId());
}
}
}
@@ -103,6 +114,31 @@ public class FocusTransitionObserver {
mTmpTasksToBeNotified.clear();
}
+ private void updateFocusedTaskPerDisplay(RunningTaskInfo task, int displayId) {
+ final RunningTaskInfo lastFocusedTaskOnDisplay =
+ mFocusedTaskOnDisplay.get(displayId);
+ if (lastFocusedTaskOnDisplay != null) {
+ mTmpTasksToBeNotified.add(lastFocusedTaskOnDisplay);
+ }
+ mTmpTasksToBeNotified.add(task);
+ mFocusedTaskOnDisplay.put(displayId, task);
+ }
+
+ private void updateFocusedDisplay(int endDisplayId) {
+ final RunningTaskInfo lastGloballyFocusedTask =
+ mFocusedTaskOnDisplay.get(mFocusedDisplayId);
+ if (lastGloballyFocusedTask != null) {
+ mTmpTasksToBeNotified.add(lastGloballyFocusedTask);
+ }
+ mFocusedDisplayId = endDisplayId;
+ notifyFocusedDisplayChanged();
+ final RunningTaskInfo currentGloballyFocusedTask =
+ mFocusedTaskOnDisplay.get(mFocusedDisplayId);
+ if (currentGloballyFocusedTask != null) {
+ mTmpTasksToBeNotified.add(currentGloballyFocusedTask);
+ }
+ }
+
/**
* Sets the focus transition listener that receives any transitions resulting in focus switch.
* This is for calls from outside the Shell, within the host process.
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 d5929f010e02..abfb41bb513a 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
@@ -39,6 +39,7 @@ import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPI
import static com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived;
import static com.android.window.flags.Flags.ensureWallpaperInTransitions;
+import static com.android.wm.shell.shared.TransitionUtil.FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY;
import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
@@ -84,6 +85,7 @@ import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes;
+import com.android.wm.shell.desktopmode.DesktopWallpaperActivity;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.FocusTransitionListener;
@@ -795,6 +797,14 @@ public class Transitions implements RemoteCallable<Transitions>,
mReadyDuringSync.remove(active);
}
+ // If any of the changes are on DesktopWallpaperActivity, add the flag to the change.
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (change.getTaskInfo() != null
+ && DesktopWallpaperActivity.isWallpaperTask(change.getTaskInfo())) {
+ change.setFlags(FLAG_IS_DESKTOP_WALLPAPER_ACTIVITY);
+ }
+ }
+
final Track track = getOrCreateTrack(info.getTrack());
track.mReadyTransitions.add(active);
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 a90c6fbdae1d..d6d393f2500c 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
@@ -2022,7 +2022,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
Supplier<SurfaceControl.Transaction> transactionFactory,
Handler handler) {
final TaskPositioner taskPositioner = DesktopModeStatus.isVeiledResizeEnabled()
- ? new VeiledResizeTaskPositioner(
+ // TODO(b/383632995): Update when the flag is launched.
+ ? (Flags.enableConnectedDisplaysWindowDrag()
+ ? new MultiDisplayVeiledResizeTaskPositioner(
taskOrganizer,
windowDecoration,
displayController,
@@ -2030,6 +2032,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
transitions,
interactionJankMonitor,
handler)
+ : new VeiledResizeTaskPositioner(
+ taskOrganizer,
+ windowDecoration,
+ displayController,
+ dragEventListener,
+ transitions,
+ interactionJankMonitor,
+ handler))
: new FluidResizeTaskPositioner(
taskOrganizer,
transitions,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
new file mode 100644
index 000000000000..8dc921c986ce
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2024 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.windowdecor
+
+import android.graphics.PointF
+import android.graphics.Rect
+import android.os.Handler
+import android.os.IBinder
+import android.os.Looper
+import android.view.Choreographer
+import android.view.Surface
+import android.view.SurfaceControl
+import android.view.WindowManager
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import com.android.internal.jank.Cuj
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.transition.Transitions
+import java.util.concurrent.TimeUnit
+import java.util.function.Supplier
+
+/**
+ * A task positioner that also takes into account resizing a
+ * [com.android.wm.shell.windowdecor.ResizeVeil] and dragging move across multiple displays.
+ * - If the drag is resizing the task, we resize the veil instead.
+ * - If the drag is repositioning, we consider multi-display topology if needed, and update in the
+ * typical manner.
+ */
+class MultiDisplayVeiledResizeTaskPositioner(
+ private val taskOrganizer: ShellTaskOrganizer,
+ private val desktopWindowDecoration: DesktopModeWindowDecoration,
+ private val displayController: DisplayController,
+ dragEventListener: DragPositioningCallbackUtility.DragEventListener,
+ private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
+ private val transitions: Transitions,
+ private val interactionJankMonitor: InteractionJankMonitor,
+ @ShellMainThread private val handler: Handler,
+) : TaskPositioner, Transitions.TransitionHandler {
+ private val dragEventListeners =
+ mutableListOf<DragPositioningCallbackUtility.DragEventListener>()
+ private val stableBounds = Rect()
+ private val taskBoundsAtDragStart = Rect()
+ private val repositionStartPoint = PointF()
+ private val repositionTaskBounds = Rect()
+ private val isResizing: Boolean
+ get() =
+ (ctrlType and DragPositioningCallback.CTRL_TYPE_TOP) != 0 ||
+ (ctrlType and DragPositioningCallback.CTRL_TYPE_BOTTOM) != 0 ||
+ (ctrlType and DragPositioningCallback.CTRL_TYPE_LEFT) != 0 ||
+ (ctrlType and DragPositioningCallback.CTRL_TYPE_RIGHT) != 0
+
+ @DragPositioningCallback.CtrlType private var ctrlType = 0
+ private var isResizingOrAnimatingResize = false
+ @Surface.Rotation private var rotation = 0
+
+ constructor(
+ taskOrganizer: ShellTaskOrganizer,
+ windowDecoration: DesktopModeWindowDecoration,
+ displayController: DisplayController,
+ dragEventListener: DragPositioningCallbackUtility.DragEventListener,
+ transitions: Transitions,
+ interactionJankMonitor: InteractionJankMonitor,
+ @ShellMainThread handler: Handler,
+ ) : this(
+ taskOrganizer,
+ windowDecoration,
+ displayController,
+ dragEventListener,
+ Supplier<SurfaceControl.Transaction> { SurfaceControl.Transaction() },
+ transitions,
+ interactionJankMonitor,
+ handler,
+ )
+
+ init {
+ dragEventListeners.add(dragEventListener)
+ }
+
+ override fun onDragPositioningStart(ctrlType: Int, displayId: Int, x: Float, y: Float): Rect {
+ this.ctrlType = ctrlType
+ taskBoundsAtDragStart.set(
+ desktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.bounds
+ )
+ repositionStartPoint[x] = y
+ if (isResizing) {
+ // Capture CUJ for re-sizing window in DW mode.
+ interactionJankMonitor.begin(
+ createLongTimeoutJankConfigBuilder(Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
+ )
+ if (!desktopWindowDecoration.mHasGlobalFocus) {
+ val wct = WindowContainerTransaction()
+ wct.reorder(
+ desktopWindowDecoration.mTaskInfo.token,
+ /* onTop= */ true,
+ /* includingParents= */ true,
+ )
+ taskOrganizer.applyTransaction(wct)
+ }
+ }
+ for (dragEventListener in dragEventListeners) {
+ dragEventListener.onDragStart(desktopWindowDecoration.mTaskInfo.taskId)
+ }
+ repositionTaskBounds.set(taskBoundsAtDragStart)
+ val rotation =
+ desktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.displayRotation
+ if (stableBounds.isEmpty || this.rotation != rotation) {
+ this.rotation = rotation
+ displayController
+ .getDisplayLayout(desktopWindowDecoration.mDisplay.displayId)!!
+ .getStableBounds(stableBounds)
+ }
+ return Rect(repositionTaskBounds)
+ }
+
+ override fun onDragPositioningMove(displayId: Int, x: Float, y: Float): Rect {
+ check(Looper.myLooper() == handler.looper) {
+ "This method must run on the shell main thread."
+ }
+ val delta = DragPositioningCallbackUtility.calculateDelta(x, y, repositionStartPoint)
+ if (
+ isResizing &&
+ DragPositioningCallbackUtility.changeBounds(
+ ctrlType,
+ repositionTaskBounds,
+ taskBoundsAtDragStart,
+ stableBounds,
+ delta,
+ displayController,
+ desktopWindowDecoration,
+ )
+ ) {
+ if (!isResizingOrAnimatingResize) {
+ for (dragEventListener in dragEventListeners) {
+ dragEventListener.onDragMove(desktopWindowDecoration.mTaskInfo.taskId)
+ }
+ desktopWindowDecoration.showResizeVeil(repositionTaskBounds)
+ isResizingOrAnimatingResize = true
+ } else {
+ desktopWindowDecoration.updateResizeVeil(repositionTaskBounds)
+ }
+ } else if (ctrlType == DragPositioningCallback.CTRL_TYPE_UNDEFINED) {
+ // Begin window drag CUJ instrumentation only when drag position moves.
+ interactionJankMonitor.begin(
+ createLongTimeoutJankConfigBuilder(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
+ )
+ val t = transactionSupplier.get()
+ DragPositioningCallbackUtility.setPositionOnDrag(
+ desktopWindowDecoration,
+ repositionTaskBounds,
+ taskBoundsAtDragStart,
+ repositionStartPoint,
+ t,
+ x,
+ y,
+ )
+ t.setFrameTimeline(Choreographer.getInstance().vsyncId)
+ t.apply()
+ }
+ return Rect(repositionTaskBounds)
+ }
+
+ override fun onDragPositioningEnd(displayId: Int, x: Float, y: Float): Rect {
+ val delta = DragPositioningCallbackUtility.calculateDelta(x, y, repositionStartPoint)
+ if (isResizing) {
+ if (taskBoundsAtDragStart != repositionTaskBounds) {
+ DragPositioningCallbackUtility.changeBounds(
+ ctrlType,
+ repositionTaskBounds,
+ taskBoundsAtDragStart,
+ stableBounds,
+ delta,
+ displayController,
+ desktopWindowDecoration,
+ )
+ desktopWindowDecoration.updateResizeVeil(repositionTaskBounds)
+ val wct = WindowContainerTransaction()
+ wct.setBounds(desktopWindowDecoration.mTaskInfo.token, repositionTaskBounds)
+ transitions.startTransition(WindowManager.TRANSIT_CHANGE, wct, this)
+ } else {
+ // If bounds haven't changed, perform necessary veil reset here as startAnimation
+ // won't be called.
+ resetVeilIfVisible()
+ }
+ interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
+ } else {
+ DragPositioningCallbackUtility.updateTaskBounds(
+ repositionTaskBounds,
+ taskBoundsAtDragStart,
+ repositionStartPoint,
+ x,
+ y,
+ )
+ interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
+ }
+
+ ctrlType = DragPositioningCallback.CTRL_TYPE_UNDEFINED
+ taskBoundsAtDragStart.setEmpty()
+ repositionStartPoint[0f] = 0f
+ return Rect(repositionTaskBounds)
+ }
+
+ private fun resetVeilIfVisible() {
+ if (isResizingOrAnimatingResize) {
+ desktopWindowDecoration.hideResizeVeil()
+ isResizingOrAnimatingResize = false
+ }
+ }
+
+ private fun createLongTimeoutJankConfigBuilder(@Cuj.CujType cujType: Int) =
+ InteractionJankMonitor.Configuration.Builder.withSurface(
+ cujType,
+ desktopWindowDecoration.mContext,
+ desktopWindowDecoration.mTaskSurface,
+ handler,
+ )
+ .setTimeout(LONG_CUJ_TIMEOUT_MS)
+
+ override fun startAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction,
+ finishCallback: Transitions.TransitionFinishCallback,
+ ): Boolean {
+ for (change in info.changes) {
+ val sc = change.leash
+ val endBounds = change.endAbsBounds
+ val endPosition = change.endRelOffset
+ startTransaction
+ .setWindowCrop(sc, endBounds.width(), endBounds.height())
+ .setPosition(sc, endPosition.x.toFloat(), endPosition.y.toFloat())
+ finishTransaction
+ .setWindowCrop(sc, endBounds.width(), endBounds.height())
+ .setPosition(sc, endPosition.x.toFloat(), endPosition.y.toFloat())
+ }
+
+ startTransaction.apply()
+ resetVeilIfVisible()
+ ctrlType = DragPositioningCallback.CTRL_TYPE_UNDEFINED
+ finishCallback.onTransitionFinished(null /* wct */)
+ isResizingOrAnimatingResize = false
+ interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
+ return true
+ }
+
+ /**
+ * We should never reach this as this handler's transitions are only started from shell
+ * explicitly.
+ */
+ override fun handleRequest(
+ transition: IBinder,
+ request: TransitionRequestInfo,
+ ): WindowContainerTransaction? {
+ return null
+ }
+
+ override fun isResizingOrAnimating() = isResizingOrAnimatingResize
+
+ override fun addDragEventListener(
+ dragEventListener: DragPositioningCallbackUtility.DragEventListener
+ ) {
+ dragEventListeners.add(dragEventListener)
+ }
+
+ override fun removeDragEventListener(
+ dragEventListener: DragPositioningCallbackUtility.DragEventListener
+ ) {
+ dragEventListeners.remove(dragEventListener)
+ }
+
+ companion object {
+ // Timeout used for resize and drag CUJs, this is longer than the default timeout to avoid
+ // timing out in the middle of a resize or drag action.
+ private val LONG_CUJ_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(/* duration= */ 10L)
+ }
+}
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 e011cc08903b..d2c79d76e6c1 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,7 +53,12 @@ import java.util.function.Supplier;
* {@link com.android.wm.shell.windowdecor.ResizeVeil}.
* If the drag is resizing the task, we resize the veil instead.
* If the drag is repositioning, we update in the typical manner.
+ * <p>
+ * @deprecated This class will be replaced by
+ * {@link com.android.wm.shell.windowdecor.MultiDisplayVeiledResizeTaskPositioner}.
+ * TODO(b/383632995): Remove this class after MultiDisplayVeiledResizeTaskPositioner is launched.
*/
+@Deprecated
public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.TransitionHandler {
// Timeout used for resize and drag CUJs, this is longer than the default timeout to avoid
// timing out in the middle of a resize or drag action.
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppsWithBackNavigationLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppsWithBackNavigationLandscape.kt
new file mode 100644
index 000000000000..0feb13d9dded
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppsWithBackNavigationLandscape.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 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
+
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_APP
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_LAST_APP
+import com.android.wm.shell.scenarios.CloseAllAppsWithBackNavigation
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAllAppsWithBackNavigationLandscape : CloseAllAppsWithBackNavigation(
+ Rotation.ROTATION_90
+) {
+ // TODO(b/390043833): Add verifications that TO_BACK transition is captured when the back
+ // navigation button is pressed.
+ @ExpectedScenarios(["CLOSE_APP", "CLOSE_LAST_APP"])
+ @Test
+ override fun closeAllAppsInDesktop() = super.closeAllAppsInDesktop()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig()
+ .use(FlickerServiceConfig.DEFAULT)
+ .use(CLOSE_APP)
+ .use(CLOSE_LAST_APP)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppsWithBackNavigationPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppsWithBackNavigationPortrait.kt
new file mode 100644
index 000000000000..3b77ea5e5264
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/CloseAllAppsWithBackNavigationPortrait.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 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
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_APP
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CLOSE_LAST_APP
+import com.android.wm.shell.scenarios.CloseAllAppsWithBackNavigation
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAllAppsWithBackNavigationPortrait : CloseAllAppsWithBackNavigation() {
+ // TODO(b/390043833): Add verifications that TO_BACK transition is captured when the back
+ // navigation button is pressed.
+ @ExpectedScenarios(["CLOSE_APP", "CLOSE_LAST_APP"])
+ @Test
+ override fun closeAllAppsInDesktop() = super.closeAllAppsInDesktop()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig()
+ .use(FlickerServiceConfig.DEFAULT)
+ .use(CLOSE_APP)
+ .use(CLOSE_LAST_APP)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/EnterDesktopWithDragExistingWindowsTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/EnterDesktopWithDragExistingWindowsTest.kt
new file mode 100644
index 000000000000..2b26bbfb68cb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/EnterDesktopWithDragExistingWindowsTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2025 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.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.EnterDesktopWithDragExistingWindows
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [EnterDesktopWithDragExistingWindows]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class EnterDesktopWithDragExistingWindowsTest : EnterDesktopWithDragExistingWindows()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithBackNavigation.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithBackNavigation.kt
new file mode 100644
index 000000000000..319df49cdc03
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithBackNavigation.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 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.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.MailAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class CloseAllAppsWithBackNavigation(val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation))
+ private val nonResizeableApp = DesktopModeAppHelper(NonResizeableAppHelper(instrumentation))
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ Assume.assumeTrue(Flags.enableDesktopWindowingBackNavigation())
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+ testApp.enterDesktopMode(wmHelper, device)
+ mailApp.launchViaIntent(wmHelper)
+ nonResizeableApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun closeAllAppsInDesktop() {
+ nonResizeableApp.closeDesktopApp(wmHelper, device, usingBackNavigation = true)
+ mailApp.closeDesktopApp(wmHelper, device, usingBackNavigation = true)
+ testApp.closeDesktopApp(wmHelper, device, usingBackNavigation = true)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithAppHandleMenuExistingWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithAppHandleMenuExistingWindows.kt
new file mode 100644
index 000000000000..2de0830dedb5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithAppHandleMenuExistingWindows.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2025 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.scenarios
+
+import android.platform.test.annotations.Postsubmit
+import android.app.Instrumentation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.window.flags.Flags
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class EnterDesktopWithAppHandleMenuExistingWindows {
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val imeApp = ImeAppHelper(instrumentation)
+ private val newTaskApp = NewTasksAppHelper(instrumentation)
+ private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ testApp.enterDesktopMode(wmHelper, device)
+ imeApp.launchViaIntent(wmHelper)
+ newTaskApp.launchViaIntent(wmHelper)
+ testApp.launchViaIntent(wmHelper)
+ testApp.exitDesktopWithDragToTopDragZone(wmHelper, device)
+ }
+
+ @Test
+ open fun reenterDesktopWithAppHandleMenu() {
+ testApp.enterDesktopModeFromAppHandleMenu(wmHelper, device)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ newTaskApp.exit(wmHelper)
+ imeApp.exit(wmHelper)
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDragExistingWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDragExistingWindows.kt
new file mode 100644
index 000000000000..814478af67c1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDragExistingWindows.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2025 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.scenarios
+
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Test Base Class")
+abstract class EnterDesktopWithDragExistingWindows
+constructor(
+ val rotation: Rotation = Rotation.ROTATION_0,
+ isResizeable: Boolean = true,
+ isLandscapeApp: Boolean = true,
+) : DesktopScenarioCustomAppTestBase(isResizeable, isLandscapeApp) {
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+ private val imeApp = ImeAppHelper(instrumentation)
+ private val newTaskApp = NewTasksAppHelper(instrumentation)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+ ChangeDisplayOrientationRule.setRotation(rotation)
+ tapl.enableTransientTaskbar(false)
+
+ testApp.enterDesktopMode(wmHelper, device)
+ imeApp.launchViaIntent(wmHelper)
+ newTaskApp.launchViaIntent(wmHelper)
+ testApp.launchViaIntent(wmHelper)
+ testApp.exitDesktopWithDragToTopDragZone(wmHelper, device)
+ }
+
+ @Test
+ open fun reenterDesktopWithDrag() {
+ // By default this method uses drag to desktop
+ testApp.enterDesktopMode(wmHelper, device)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ newTaskApp.exit(wmHelper)
+ imeApp.exit(wmHelper)
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenUnlimitedApps.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenUnlimitedApps.kt
index 367c4a437018..16a01d570575 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenUnlimitedApps.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenUnlimitedApps.kt
@@ -17,6 +17,8 @@
package com.android.wm.shell.scenarios
import android.app.Instrumentation
+import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
@@ -43,7 +45,7 @@ abstract class OpenUnlimitedApps()
private val device = UiDevice.getInstance(instrumentation)
private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
- private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation))
+ private val mailApp = MailAppHelper(instrumentation)
private val maxNum = DesktopModeStatus.getMaxTaskLimit(instrumentation.context)
@@ -61,7 +63,12 @@ abstract class OpenUnlimitedApps()
// Launch new [openTaskNum] tasks.
for (i in 1..openTaskNum) {
- mailApp.launchViaIntent(wmHelper)
+ mailApp.launchViaIntent(
+ wmHelper,
+ mailApp.openAppIntent.apply {
+ addFlags(FLAG_ACTIVITY_MULTIPLE_TASK or FLAG_ACTIVITY_NEW_TASK)
+ }
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt
index ba4654260864..31d89f92f744 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt
@@ -24,6 +24,7 @@ import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.wm.shell.Utils
+import com.android.wm.shell.flicker.utils.RecentTasksUtils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
@@ -61,7 +62,6 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
@After
fun teardown() {
- primaryApp.exit(wmHelper)
- secondaryApp.exit(wmHelper)
+ RecentTasksUtils.clearAllVisibleRecentTasks(instrumentation)
}
}
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt
index d774a31220da..1af6cac39085 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt
@@ -24,6 +24,7 @@ import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.wm.shell.Utils
+import com.android.wm.shell.flicker.utils.RecentTasksUtils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
@@ -74,7 +75,6 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
@After
fun teardown() {
- primaryApp.exit(wmHelper)
- secondaryApp.exit(wmHelper)
+ RecentTasksUtils.clearAllVisibleRecentTasks(instrumentation)
}
}
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt
index 5aa161911a9a..8ad8c7bd7a7f 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt
@@ -24,6 +24,7 @@ import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.wm.shell.Utils
+import com.android.wm.shell.flicker.utils.RecentTasksUtils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
@@ -60,7 +61,6 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
@After
fun teardown() {
- primaryApp.exit(wmHelper)
- secondaryApp.exit(wmHelper)
+ RecentTasksUtils.clearAllVisibleRecentTasks(instrumentation)
}
}
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt
index 668f3678bb38..da0ace472153 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt
@@ -24,6 +24,7 @@ import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.wm.shell.Utils
+import com.android.wm.shell.flicker.utils.RecentTasksUtils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
@@ -61,7 +62,6 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
@After
fun teardown() {
- primaryApp.exit(wmHelper)
- secondaryApp.exit(wmHelper)
+ RecentTasksUtils.clearAllVisibleRecentTasks(instrumentation)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/RecentTasksUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/RecentTasksUtils.kt
new file mode 100644
index 000000000000..aa262f9dfd6a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/RecentTasksUtils.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2025 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.utils
+
+import android.app.Instrumentation
+
+object RecentTasksUtils {
+ fun clearAllVisibleRecentTasks(instrumentation: Instrumentation) {
+ instrumentation.uiAutomation.executeShellCommand(
+ "dumpsys activity service SystemUIService WMShell recents clearAll"
+ )
+ }
+} \ No newline at end of file
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 95ed8b4d4511..2b986d184c20 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
@@ -601,7 +601,32 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ )
+ fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_perDisplayWallpaperEnabled_shouldShowWallpaper() {
+ val homeTask = setUpHomeTask(SECOND_DISPLAY)
+ val task1 = setUpFreeformTask(SECOND_DISPLAY)
+ val task2 = setUpFreeformTask(SECOND_DISPLAY)
+ markTaskHidden(task1)
+ markTaskHidden(task2)
+
+ controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(4)
+ // Expect order to be from bottom: home, wallpaperIntent, task1, task2
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertPendingIntentAt(index = 1, desktopWallpaperIntent)
+ wct.assertReorderAt(index = 2, task1)
+ wct.assertReorderAt(index = 3, task2)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY)
fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() {
val homeTask = setUpHomeTask(SECOND_DISPLAY)
val task1 = setUpFreeformTask(SECOND_DISPLAY)
@@ -1775,8 +1800,35 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(
+ FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ )
+ fun moveToNextDisplay_wallpaperOnSystemUser_reorderWallpaperToBack() {
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Create a mock for the target display area: second display
+ val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY))
+ .thenReturn(secondDisplayArea)
+ // Add a task and a wallpaper
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+
+ controller.moveToNextDisplay(task.taskId)
+
+ with(getLatestWct(type = TRANSIT_CHANGE)) {
+ val wallpaperChange =
+ hierarchyOps.find { op -> op.container == wallpaperToken.asBinder() }
+ assertNotNull(wallpaperChange)
+ assertThat(wallpaperChange.type).isEqualTo(HIERARCHY_OP_TYPE_REORDER)
+ }
+ }
+
+ @Test
@EnableFlags(FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY)
- fun moveToNextDisplay_removeWallpaper() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
+ fun moveToNextDisplay_wallpaperNotOnSystemUser_removeWallpaper() {
// Set up two display ids
whenever(rootTaskDisplayAreaOrganizer.displayIds)
.thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
index c42f6c35bcb0..aef44a40fa0f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
@@ -16,14 +16,10 @@
package com.android.wm.shell.pip2.phone;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
-
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.when;
import static org.mockito.kotlin.MatchersKt.eq;
@@ -124,12 +120,13 @@ public class PipSchedulerTest {
.setCallsite("PipSchedulerTest")
.build();
when(mMockPipTransitionState.getPinnedTaskLeash()).thenReturn(testLeash);
+ // PiP is in a valid state by default.
+ when(mMockPipTransitionState.isInPip()).thenReturn(true);
}
@Test
public void scheduleExitPipViaExpand_nullTaskToken_noop() {
setNullPipTaskToken();
- when(mMockPipTransitionState.isInPip()).thenReturn(true);
mPipScheduler.scheduleExitPipViaExpand();
@@ -137,14 +134,12 @@ public class PipSchedulerTest {
assertNotNull(mRunnableArgumentCaptor.getValue());
mRunnableArgumentCaptor.getValue().run();
- verify(mMockPipTransitionController, never())
- .startExitTransition(eq(TRANSIT_EXIT_PIP), any(), isNull());
+ verify(mMockPipTransitionController, never()).startExpandTransition(any());
}
@Test
public void scheduleExitPipViaExpand_exitTransitionCalled() {
setMockPipTaskToken();
- when(mMockPipTransitionState.isInPip()).thenReturn(true);
mPipScheduler.scheduleExitPipViaExpand();
@@ -152,23 +147,21 @@ public class PipSchedulerTest {
assertNotNull(mRunnableArgumentCaptor.getValue());
mRunnableArgumentCaptor.getValue().run();
- verify(mMockPipTransitionController, times(1))
- .startExitTransition(eq(TRANSIT_EXIT_PIP), any(), isNull());
+ verify(mMockPipTransitionController, times(1)).startExpandTransition(any());
}
@Test
public void removePipAfterAnimation() {
setMockPipTaskToken();
- when(mMockPipTransitionState.isInPip()).thenReturn(true);
- mPipScheduler.scheduleRemovePip();
+ mPipScheduler.scheduleRemovePip(true /* withFadeout */);
verify(mMockMainExecutor, times(1)).execute(mRunnableArgumentCaptor.capture());
assertNotNull(mRunnableArgumentCaptor.getValue());
mRunnableArgumentCaptor.getValue().run();
verify(mMockPipTransitionController, times(1))
- .startExitTransition(eq(TRANSIT_REMOVE_PIP), any(), isNull());
+ .startRemoveTransition(true /* withFadeout */);
}
@Test
@@ -192,7 +185,6 @@ public class PipSchedulerTest {
@Test
public void scheduleAnimateResizePip_boundsConfig_setsConfigAtEnd() {
setMockPipTaskToken();
- when(mMockPipTransitionState.isInPip()).thenReturn(true);
mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true);
@@ -233,7 +225,6 @@ public class PipSchedulerTest {
@Test
public void scheduleAnimateResizePip_resizeTransition() {
setMockPipTaskToken();
- when(mMockPipTransitionState.isInPip()).thenReturn(true);
mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true, TEST_RESIZE_DURATION);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java
index 571ae93e1aec..fa9b5903bc3c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java
@@ -110,4 +110,22 @@ public class PipTransitionStateTest extends ShellTestCase {
mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
mPipTransitionState.removePipTransitionStateChangedListener(mStateChangedListener);
}
+
+ @Test
+ public void testBoundsChangeState_notInPip() {
+ Bundle extra = new Bundle();
+ extra.putParcelable(EXTRA_ENTRY_KEY, mEmptyParcelable);
+
+ mPipTransitionState.setState(PipTransitionState.UNDEFINED);
+ mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
+ Assert.assertEquals(PipTransitionState.UNDEFINED, mPipTransitionState.getState());
+
+ mPipTransitionState.setState(PipTransitionState.ENTERING_PIP, extra);
+ mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
+ Assert.assertEquals(PipTransitionState.ENTERING_PIP, mPipTransitionState.getState());
+
+ mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
+ mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
+ Assert.assertEquals(PipTransitionState.EXITING_PIP, mPipTransitionState.getState());
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 7e5d6ce38c5a..28f4ea0c7ada 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -22,13 +22,13 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.launcher3.Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK;
import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE;
import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FREEFORM;
import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FULLSCREEN;
import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -65,8 +65,8 @@ import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.view.SurfaceControl;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
@@ -253,7 +253,6 @@ public class RecentTasksControllerTest extends ShellTestCase {
t3.taskId, -1);
}
- @EnableFlags(FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK)
@Test
public void testGetRecentTasks_removesDesktopWallpaperActivity() {
RecentTaskInfo t1 = makeTaskInfo(1);
@@ -753,15 +752,9 @@ public class RecentTasksControllerTest extends ShellTestCase {
/**
* Helper to set the raw task list on the controller.
*/
- private ArrayList<RecentTaskInfo> setRawList(
- RecentTaskInfo... tasks) {
- ArrayList<RecentTaskInfo> rawList = new ArrayList<>();
- for (RecentTaskInfo task : tasks) {
- rawList.add(task);
- }
- doReturn(rawList).when(mActivityTaskManager).getRecentTasks(anyInt(), anyInt(),
+ private void setRawList(RecentTaskInfo... tasks) {
+ doReturn(Arrays.asList(tasks)).when(mActivityTaskManager).getRecentTasks(anyInt(), anyInt(),
anyInt());
- return rawList;
}
/**
@@ -794,8 +787,9 @@ public class RecentTasksControllerTest extends ShellTestCase {
assertNull(pair.getSplitBounds());
}
}
- assertTrue("Expected: " + Arrays.toString(expectedTaskIds)
+ assertArrayEquals("Expected: " + Arrays.toString(expectedTaskIds)
+ " Received: " + Arrays.toString(flattenedTaskIds),
- Arrays.equals(flattenedTaskIds, expectedTaskIds));
+ flattenedTaskIds,
+ expectedTaskIds);
}
}
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 2d0ea5fdc884..7a88ace3f85f 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
@@ -17,6 +17,7 @@
package com.android.wm.shell.splitscreen;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED;
@@ -32,6 +33,8 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.atLeastOnce;
@@ -50,9 +53,11 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.view.SurfaceControl;
import android.window.RemoteTransition;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.test.annotation.UiThreadTest;
@@ -84,6 +89,7 @@ import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -425,6 +431,30 @@ public class StageCoordinatorTests extends ShellTestCase {
.startFullscreenTransition(any(), any());
}
+
+ @Test
+ public void startTask_ensureWindowingModeCleared() {
+ SplitScreenTransitions splitScreenTransitions =
+ spy(mStageCoordinator.getSplitTransitions());
+ mStageCoordinator.setSplitTransitions(splitScreenTransitions);
+ ArgumentCaptor<WindowContainerTransaction> wctCaptor =
+ ArgumentCaptor.forClass(WindowContainerTransaction.class);
+ int taskId = 18;
+ IBinder binder = mock(IBinder.class);
+ ActivityManager.RunningTaskInfo rti = mock(ActivityManager.RunningTaskInfo.class);
+ WindowContainerToken mockToken = mock(WindowContainerToken.class);
+ when(mockToken.asBinder()).thenReturn(binder);
+ when(rti.getToken()).thenReturn(mockToken);
+ when(mTaskOrganizer.getRunningTaskInfo(taskId)).thenReturn(rti);
+ mStageCoordinator.startTask(taskId, SPLIT_POSITION_TOP_OR_LEFT, null /*options*/,
+ null, SPLIT_INDEX_UNDEFINED);
+ verify(splitScreenTransitions).startEnterTransition(anyInt(),
+ wctCaptor.capture(), any(), any(), anyInt(), anyBoolean());
+
+ int windowingMode = wctCaptor.getValue().getChanges().get(binder).getWindowingMode();
+ assertEquals(windowingMode, WINDOWING_MODE_UNDEFINED);
+ }
+
private Transitions createTestTransitions() {
ShellInit shellInit = new ShellInit(mMainExecutor);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java
index 015ea20767e9..74c2f0e6753a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java
@@ -17,7 +17,9 @@
package com.android.wm.shell.transition;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
@@ -127,19 +129,136 @@ public class FocusTransitionObserverTest extends ShellTestCase {
true /* isFocusedOnDisplay */, false /* isFocusedGlobally */);
}
+ @Test
+ public void testTaskFocusSwitch() throws RemoteException {
+ final SurfaceControl.Transaction tx = mock(SurfaceControl.Transaction.class);
+
+ // Open 2 tasks on the default display.
+ TransitionInfo info = mock(TransitionInfo.class);
+ final List<TransitionInfo.Change> changes = new ArrayList<>();
+ setupTaskChange(changes, 1 /* taskId */, TRANSIT_OPEN,
+ DEFAULT_DISPLAY, true /* focused */);
+ when(info.getChanges()).thenReturn(changes);
+ mFocusTransitionObserver.updateFocusState(info);
+ mShellExecutor.flushAll();
+ verify(mListener, never()).onFocusedDisplayChanged(anyInt());
+ verify(mListener, times(1)).onFocusedTaskChanged(1 /* taskId */,
+ true /* isFocusedOnDisplay */, true /* isFocusedGlobally */);
+ clearInvocations(mListener);
+ changes.clear();
+
+ setupTaskChange(changes, 2 /* taskId */, TRANSIT_OPEN,
+ DEFAULT_DISPLAY, true /* focused */);
+ when(info.getChanges()).thenReturn(changes);
+ mFocusTransitionObserver.updateFocusState(info);
+ mShellExecutor.flushAll();
+ verify(mListener, times(1)).onFocusedTaskChanged(1 /* taskId */,
+ false /* isFocusedOnDisplay */, false /* isFocusedGlobally */);
+ verify(mListener, times(1)).onFocusedTaskChanged(2 /* taskId */,
+ true /* isFocusedOnDisplay */, true /* isFocusedGlobally */);
+ clearInvocations(mListener);
+ changes.clear();
+
+ // Moving a task to front.
+ changes.clear();
+ setupTaskChange(changes, 1 /* taskId */, TRANSIT_TO_FRONT,
+ DEFAULT_DISPLAY, true /* focused */);
+ when(info.getChanges()).thenReturn(changes);
+ mFocusTransitionObserver.updateFocusState(info);
+ mShellExecutor.flushAll();
+ verify(mListener, times(1)).onFocusedTaskChanged(1 /* taskId */,
+ true /* isFocusedOnDisplay */, true /* isFocusedGlobally */);
+ verify(mListener, times(1)).onFocusedTaskChanged(2 /* taskId */,
+ false /* isFocusedOnDisplay */, false /* isFocusedGlobally */);
+ }
+
+
+ @Test
+ public void testTaskMoveToAnotherDisplay() throws RemoteException {
+ final SurfaceControl.Transaction tx = mock(SurfaceControl.Transaction.class);
+
+ // First, open a task on the default display.
+ TransitionInfo info = mock(TransitionInfo.class);
+ final List<TransitionInfo.Change> changes = new ArrayList<>();
+ setupTaskChange(changes, 1 /* taskId */, TRANSIT_OPEN,
+ DEFAULT_DISPLAY, true /* focused */);
+ when(info.getChanges()).thenReturn(changes);
+ mFocusTransitionObserver.updateFocusState(info);
+ mShellExecutor.flushAll();
+ verify(mListener, never()).onFocusedDisplayChanged(anyInt());
+ verify(mListener, times(1)).onFocusedTaskChanged(1 /* taskId */,
+ true /* isFocusedOnDisplay */, true /* isFocusedGlobally */);
+ clearInvocations(mListener);
+ changes.clear();
+
+ // Open 2 tasks on the secondary display.
+ setupTaskChange(changes, 2 /* taskId */, TRANSIT_OPEN,
+ SECONDARY_DISPLAY_ID, true /* focused */);
+ setupDisplayToTopChange(changes, SECONDARY_DISPLAY_ID);
+ when(info.getChanges()).thenReturn(changes);
+ mFocusTransitionObserver.updateFocusState(info);
+ mShellExecutor.flushAll();
+ verify(mListener, times(1))
+ .onFocusedDisplayChanged(SECONDARY_DISPLAY_ID);
+ verify(mListener, times(1)).onFocusedTaskChanged(1 /* taskId */,
+ true /* isFocusedOnDisplay */, false /* isFocusedGlobally */);
+ verify(mListener, times(1)).onFocusedTaskChanged(2 /* taskId */,
+ true /* isFocusedOnDisplay */, true /* isFocusedGlobally */);
+ clearInvocations(mListener);
+ changes.clear();
+
+ setupTaskChange(changes, 3 /* taskId */, TRANSIT_OPEN,
+ SECONDARY_DISPLAY_ID, true /* focused */);
+ setupDisplayToTopChange(changes, SECONDARY_DISPLAY_ID);
+ when(info.getChanges()).thenReturn(changes);
+ mFocusTransitionObserver.updateFocusState(info);
+ mShellExecutor.flushAll();
+ verify(mListener, times(1)).onFocusedTaskChanged(2 /* taskId */,
+ false /* isFocusedOnDisplay */, false /* isFocusedGlobally */);
+ verify(mListener, times(1)).onFocusedTaskChanged(3 /* taskId */,
+ true /* isFocusedOnDisplay */, true /* isFocusedGlobally */);
+ clearInvocations(mListener);
+ changes.clear();
+
+ // Move focused task in the secondary display to the default display
+ setupTaskChange(changes, 3 /* taskId */, TRANSIT_CHANGE,
+ SECONDARY_DISPLAY_ID, DEFAULT_DISPLAY, true /* focused */);
+ setupTaskChange(changes, 2 /* taskId */, TRANSIT_TO_FRONT,
+ SECONDARY_DISPLAY_ID, true /* focused */);
+ setupDisplayToTopChange(changes, DEFAULT_DISPLAY);
+ when(info.getChanges()).thenReturn(changes);
+ mFocusTransitionObserver.updateFocusState(info);
+ mShellExecutor.flushAll();
+ verify(mListener, times(1)).onFocusedTaskChanged(1 /* taskId */,
+ false /* isFocusedOnDisplay */, false /* isFocusedGlobally */);
+ verify(mListener, times(1)).onFocusedTaskChanged(2 /* taskId */,
+ true /* isFocusedOnDisplay */, false /* isFocusedGlobally */);
+ verify(mListener, times(1)).onFocusedTaskChanged(3 /* taskId */,
+ true /* isFocusedOnDisplay */, true /* isFocusedGlobally */);
+ clearInvocations(mListener);
+ }
+
private void setupTaskChange(List<TransitionInfo.Change> changes, int taskId,
@TransitionMode int mode, int displayId, boolean focused) {
+ setupTaskChange(changes, taskId, mode, displayId, displayId, focused);
+ }
+
+ private void setupTaskChange(List<TransitionInfo.Change> changes, int taskId,
+ @TransitionMode int mode, int startDisplayId, int endDisplayId, boolean focused) {
TransitionInfo.Change change = mock(TransitionInfo.Change.class);
RunningTaskInfo taskInfo = mock(RunningTaskInfo.class);
taskInfo.taskId = taskId;
taskInfo.isFocused = focused;
when(change.hasFlags(FLAG_MOVED_TO_TOP)).thenReturn(focused);
- taskInfo.displayId = displayId;
+ taskInfo.displayId = endDisplayId;
+ when(change.getStartDisplayId()).thenReturn(startDisplayId);
+ when(change.getEndDisplayId()).thenReturn(endDisplayId);
when(change.getTaskInfo()).thenReturn(taskInfo);
when(change.getMode()).thenReturn(mode);
changes.add(change);
}
+
private void setupDisplayToTopChange(List<TransitionInfo.Change> changes, int displayId) {
TransitionInfo.Change change = mock(TransitionInfo.Change.class);
when(change.hasFlags(FLAG_MOVED_TO_TOP)).thenReturn(true);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index dd645fd968e4..0a19be4eb959 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -1781,6 +1781,7 @@ public class ShellTransitionTests extends ShellTestCase {
taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
taskInfo.configuration.windowConfiguration.setActivityType(activityType);
taskInfo.token = mock(WindowContainerToken.class);
+ taskInfo.baseIntent = mock(Intent.class);
return taskInfo;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index 8af8285d031c..b44af4733fd2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -182,6 +182,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
spyContext.addMockSystemService(InputManager::class.java, mockInputManager)
desktopModeEventLogger = mock<DesktopModeEventLogger>()
whenever(mockDesktopUserRepositories.current).thenReturn(mockDesktopRepository)
+ whenever(mockDisplayController.getDisplayContext(any())).thenReturn(spyContext)
whenever(mockDesktopUserRepositories.getProfile(anyInt()))
.thenReturn(mockDesktopRepository)
desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
new file mode 100644
index 000000000000..f179cac32244
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
@@ -0,0 +1,611 @@
+/*
+ * Copyright (C) 2024 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.windowdecor
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Point
+import android.graphics.Rect
+import android.os.Handler
+import android.os.IBinder
+import android.os.Looper
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.Surface.ROTATION_0
+import android.view.Surface.ROTATION_270
+import android.view.Surface.ROTATION_90
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.TransitionInfo
+import android.window.WindowContainerToken
+import androidx.test.filters.SmallTest
+import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
+import com.android.internal.jank.InteractionJankMonitor
+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.DisplayLayout
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED
+import java.util.function.Supplier
+import junit.framework.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [MultiDisplayVeiledResizeTaskPositioner].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:MultiDisplayVeiledResizeTaskPositionerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
+
+ @Mock private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
+ @Mock private lateinit var mockDesktopWindowDecoration: DesktopModeWindowDecoration
+ @Mock
+ private lateinit var mockDragEventListener: DragPositioningCallbackUtility.DragEventListener
+
+ @Mock private lateinit var taskToken: WindowContainerToken
+ @Mock private lateinit var taskBinder: IBinder
+
+ @Mock private lateinit var mockDisplayController: DisplayController
+ @Mock private lateinit var mockDisplayLayout: DisplayLayout
+ @Mock private lateinit var mockDisplay: Display
+ @Mock private lateinit var mockTransactionFactory: Supplier<SurfaceControl.Transaction>
+ @Mock private lateinit var mockTransaction: SurfaceControl.Transaction
+ @Mock private lateinit var mockTransitionBinder: IBinder
+ @Mock private lateinit var mockTransitionInfo: TransitionInfo
+ @Mock private lateinit var mockFinishCallback: TransitionFinishCallback
+ @Mock private lateinit var mockTransitions: Transitions
+ @Mock private lateinit var mockContext: Context
+ @Mock private lateinit var mockResources: Resources
+ @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+ private val mainHandler = Handler(Looper.getMainLooper())
+
+ private lateinit var taskPositioner: MultiDisplayVeiledResizeTaskPositioner
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ mockDesktopWindowDecoration.mDisplay = mockDisplay
+ mockDesktopWindowDecoration.mDecorWindowContext = mockContext
+ whenever(mockContext.getResources()).thenReturn(mockResources)
+ whenever(taskToken.asBinder()).thenReturn(taskBinder)
+ whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
+ whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+ if (
+ mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_90 ||
+ mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_270
+ ) {
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS_LANDSCAPE)
+ } else {
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS_PORTRAIT)
+ }
+ }
+ `when`(mockTransactionFactory.get()).thenReturn(mockTransaction)
+ mockDesktopWindowDecoration.mTaskInfo =
+ ActivityManager.RunningTaskInfo().apply {
+ taskId = TASK_ID
+ token = taskToken
+ minWidth = MIN_WIDTH
+ minHeight = MIN_HEIGHT
+ defaultMinSize = DEFAULT_MIN
+ displayId = DISPLAY_ID
+ configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
+ configuration.windowConfiguration.displayRotation = ROTATION_90
+ isResizeable = true
+ }
+ `when`(mockDesktopWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
+ mockDesktopWindowDecoration.mDisplay = mockDisplay
+ whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+
+ taskPositioner =
+ MultiDisplayVeiledResizeTaskPositioner(
+ mockShellTaskOrganizer,
+ mockDesktopWindowDecoration,
+ mockDisplayController,
+ mockDragEventListener,
+ mockTransactionFactory,
+ mockTransitions,
+ mockInteractionJankMonitor,
+ mainHandler,
+ )
+ }
+
+ @Test
+ fun testDragResize_noMove_doesNotShowResizeVeil() = runOnUiThread {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+ verify(mockDesktopWindowDecoration, never()).showResizeVeil(STARTING_BOUNDS)
+
+ taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ verify(mockTransitions, never())
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0 &&
+ change.configuration.windowConfiguration.bounds == STARTING_BOUNDS
+ }
+ },
+ eq(taskPositioner),
+ )
+ verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
+ }
+
+ @Test
+ fun testDragResize_movesTask_doesNotShowResizeVeil() = runOnUiThread {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED,
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat() + 60,
+ STARTING_BOUNDS.top.toFloat() + 100,
+ )
+ val rectAfterMove = Rect(STARTING_BOUNDS)
+ rectAfterMove.left += 60
+ rectAfterMove.right += 60
+ rectAfterMove.top += 100
+ rectAfterMove.bottom += 100
+ verify(mockTransaction)
+ .setPosition(any(), eq(rectAfterMove.left.toFloat()), eq(rectAfterMove.top.toFloat()))
+
+ val endBounds =
+ taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat() + 70,
+ STARTING_BOUNDS.top.toFloat() + 20,
+ )
+ val rectAfterEnd = Rect(STARTING_BOUNDS)
+ rectAfterEnd.left += 70
+ rectAfterEnd.right += 70
+ rectAfterEnd.top += 20
+ rectAfterEnd.bottom += 20
+
+ verify(mockDesktopWindowDecoration, never()).showResizeVeil(any())
+ verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
+ Assert.assertEquals(rectAfterEnd, endBounds)
+ }
+
+ @Test
+ fun testDragResize_resize_boundsUpdateOnEnd() = runOnUiThread {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+ DISPLAY_ID,
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
+ STARTING_BOUNDS.right.toFloat() + 10,
+ STARTING_BOUNDS.top.toFloat() + 10,
+ )
+
+ val rectAfterMove = Rect(STARTING_BOUNDS)
+ rectAfterMove.right += 10
+ rectAfterMove.top += 10
+ verify(mockDesktopWindowDecoration).showResizeVeil(rectAfterMove)
+ verify(mockShellTaskOrganizer, never())
+ .applyTransaction(
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterMove
+ }
+ }
+ )
+
+ taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
+ STARTING_BOUNDS.right.toFloat() + 20,
+ STARTING_BOUNDS.top.toFloat() + 20,
+ )
+ val rectAfterEnd = Rect(rectAfterMove)
+ rectAfterEnd.right += 10
+ rectAfterEnd.top += 10
+ verify(mockDesktopWindowDecoration).updateResizeVeil(any())
+ verify(mockTransitions)
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterEnd
+ }
+ },
+ eq(taskPositioner),
+ )
+ }
+
+ @Test
+ fun testDragResize_noEffectiveMove_skipsTransactionOnEnd() = runOnUiThread {
+ taskPositioner.onDragPositioningStart(
+ DISPLAY_ID,
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat() + 10,
+ STARTING_BOUNDS.top.toFloat() + 10,
+ )
+
+ verify(mockTransitions, never())
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0 &&
+ change.configuration.windowConfiguration.bounds == STARTING_BOUNDS
+ }
+ },
+ eq(taskPositioner),
+ )
+
+ verify(mockShellTaskOrganizer, never())
+ .applyTransaction(
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0)
+ }
+ }
+ )
+ }
+
+ @Test
+ fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() = runOnUiThread {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED, // drag
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ val newX = STARTING_BOUNDS.left.toFloat() + 5
+ val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1
+ taskPositioner.onDragPositioningMove(DISPLAY_ID, newX, newY)
+
+ taskPositioner.onDragPositioningEnd(DISPLAY_ID, newX, newY)
+
+ verify(mockShellTaskOrganizer, never())
+ .applyTransaction(
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0)
+ }
+ }
+ )
+ }
+
+ @Test
+ fun testDragResize_resize_resizingTaskReorderedToTopWhenNotFocused() = runOnUiThread {
+ mockDesktopWindowDecoration.mHasGlobalFocus = false
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT, // Resize right
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ // Verify task is reordered to top
+ verify(mockShellTaskOrganizer)
+ .applyTransaction(
+ argThat { wct ->
+ return@argThat wct.hierarchyOps.any { hierarchyOps ->
+ hierarchyOps.container == taskBinder && hierarchyOps.toTop
+ }
+ }
+ )
+ }
+
+ @Test
+ fun testDragResize_resize_resizingTaskNotReorderedToTopWhenFocused() = runOnUiThread {
+ mockDesktopWindowDecoration.mHasGlobalFocus = true
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT, // Resize right
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ // Verify task is not reordered to top
+ verify(mockShellTaskOrganizer, never())
+ .applyTransaction(
+ argThat { wct ->
+ return@argThat wct.hierarchyOps.any { hierarchyOps ->
+ hierarchyOps.container == taskBinder && hierarchyOps.toTop
+ }
+ }
+ )
+ }
+
+ @Test
+ fun testDragResize_drag_draggedTaskNotReorderedToTop() = runOnUiThread {
+ mockDesktopWindowDecoration.mHasGlobalFocus = false
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED, // drag
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ // Verify task is not reordered to top since task is already brought to top before dragging
+ // begins
+ verify(mockShellTaskOrganizer, never())
+ .applyTransaction(
+ argThat { wct ->
+ return@argThat wct.hierarchyOps.any { hierarchyOps ->
+ hierarchyOps.container == taskBinder && hierarchyOps.toTop
+ }
+ }
+ )
+ }
+
+ @Test
+ fun testDragResize_drag_updatesStableBoundsOnRotate() = runOnUiThread {
+ // Test landscape stable bounds
+ performDrag(
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat() + 2000,
+ STARTING_BOUNDS.bottom.toFloat() + 2000,
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+ )
+ val rectAfterDrag = Rect(STARTING_BOUNDS)
+ rectAfterDrag.right += 2000
+ rectAfterDrag.bottom = STABLE_BOUNDS_LANDSCAPE.bottom
+ // First drag; we should fetch stable bounds.
+ verify(mockDisplayLayout, times(1)).getStableBounds(any())
+ verify(mockTransitions)
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterDrag
+ }
+ },
+ eq(taskPositioner),
+ )
+ // Drag back to starting bounds.
+ performDrag(
+ STARTING_BOUNDS.right.toFloat() + 2000,
+ STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.bottom.toFloat(),
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+ )
+
+ // Display did not rotate; we should use previous stable bounds
+ verify(mockDisplayLayout, times(1)).getStableBounds(any())
+
+ // Rotate the screen to portrait
+ mockDesktopWindowDecoration.mTaskInfo.apply {
+ configuration.windowConfiguration.displayRotation = ROTATION_0
+ }
+ // Test portrait stable bounds
+ performDrag(
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat() + 2000,
+ STARTING_BOUNDS.bottom.toFloat() + 2000,
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+ )
+ rectAfterDrag.right = STABLE_BOUNDS_PORTRAIT.right
+ rectAfterDrag.bottom = STARTING_BOUNDS.bottom + 2000
+
+ verify(mockTransitions)
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterDrag
+ }
+ },
+ eq(taskPositioner),
+ )
+ // Display has rotated; we expect a new stable bounds.
+ verify(mockDisplayLayout, times(2)).getStableBounds(any())
+ }
+
+ @Test
+ fun testIsResizingOrAnimatingResizeSet() = runOnUiThread {
+ Assert.assertFalse(taskPositioner.isResizingOrAnimating)
+
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat() - 20,
+ STARTING_BOUNDS.top.toFloat() - 20,
+ )
+
+ // isResizingOrAnimating should be set to true after move during a resize
+ Assert.assertTrue(taskPositioner.isResizingOrAnimating)
+ verify(mockDragEventListener, times(1)).onDragMove(eq(TASK_ID))
+
+ taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ // isResizingOrAnimating should be not be set till false until after transition animation
+ Assert.assertTrue(taskPositioner.isResizingOrAnimating)
+ }
+
+ @Test
+ fun testIsResizingOrAnimatingResizeResetAfterStartAnimation() = runOnUiThread {
+ performDrag(
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ STARTING_BOUNDS.left.toFloat() - 20,
+ STARTING_BOUNDS.top.toFloat() - 20,
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ )
+
+ taskPositioner.startAnimation(
+ mockTransitionBinder,
+ mockTransitionInfo,
+ mockTransaction,
+ mockTransaction,
+ mockFinishCallback,
+ )
+
+ // isResizingOrAnimating should be set to false until after transition successfully consumed
+ Assert.assertFalse(taskPositioner.isResizingOrAnimating)
+ }
+
+ @Test
+ fun testStartAnimation_useEndRelOffset() = runOnUiThread {
+ val changeMock = mock(TransitionInfo.Change::class.java)
+ val startTransaction = mock(Transaction::class.java)
+ val finishTransaction = mock(Transaction::class.java)
+ val point = Point(10, 20)
+ val bounds = Rect(1, 2, 3, 4)
+ `when`(changeMock.leash).thenReturn(mock(SurfaceControl::class.java))
+ `when`(changeMock.endRelOffset).thenReturn(point)
+ `when`(changeMock.endAbsBounds).thenReturn(bounds)
+ `when`(mockTransitionInfo.changes).thenReturn(listOf(changeMock))
+ `when`(startTransaction.setWindowCrop(any(), eq(bounds.width()), eq(bounds.height())))
+ .thenReturn(startTransaction)
+ `when`(finishTransaction.setWindowCrop(any(), eq(bounds.width()), eq(bounds.height())))
+ .thenReturn(finishTransaction)
+
+ taskPositioner.startAnimation(
+ mockTransitionBinder,
+ mockTransitionInfo,
+ startTransaction,
+ finishTransaction,
+ mockFinishCallback,
+ )
+
+ verify(startTransaction).setPosition(any(), eq(point.x.toFloat()), eq(point.y.toFloat()))
+ verify(finishTransaction).setPosition(any(), eq(point.x.toFloat()), eq(point.y.toFloat()))
+ verify(changeMock).endRelOffset
+ }
+
+ private fun performDrag(startX: Float, startY: Float, endX: Float, endY: Float, ctrlType: Int) {
+ taskPositioner.onDragPositioningStart(ctrlType, DISPLAY_ID, startX, startY)
+ taskPositioner.onDragPositioningMove(DISPLAY_ID, endX, endY)
+
+ taskPositioner.onDragPositioningEnd(DISPLAY_ID, endX, endY)
+ }
+
+ companion object {
+ private const val TASK_ID = 5
+ private const val MIN_WIDTH = 10
+ private const val MIN_HEIGHT = 10
+ private const val DENSITY_DPI = 20
+ 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(100, 100, 200, 200)
+ private val STABLE_BOUNDS_LANDSCAPE =
+ Rect(
+ DISPLAY_BOUNDS.left,
+ DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
+ DISPLAY_BOUNDS.right,
+ DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
+ )
+ private val STABLE_BOUNDS_PORTRAIT =
+ Rect(
+ DISPLAY_BOUNDS.top,
+ DISPLAY_BOUNDS.left + CAPTION_HEIGHT,
+ DISPLAY_BOUNDS.bottom,
+ DISPLAY_BOUNDS.right - NAVBAR_HEIGHT,
+ )
+ private val VALID_DRAG_AREA =
+ Rect(
+ DISPLAY_BOUNDS.left - 100,
+ STABLE_BOUNDS_LANDSCAPE.top,
+ DISPLAY_BOUNDS.right - 100,
+ DISPLAY_BOUNDS.bottom - 100,
+ )
+ }
+}
diff --git a/libs/androidfw/ApkParsing.cpp b/libs/androidfw/ApkParsing.cpp
index 7eedfdb5c921..b80c8755f23e 100644
--- a/libs/androidfw/ApkParsing.cpp
+++ b/libs/androidfw/ApkParsing.cpp
@@ -33,7 +33,7 @@ const size_t LIB_SUFFIX_LEN = LIB_SUFFIX.size();
static const std::array<std::string_view, 2> abis = {"arm64-v8a", "x86_64"};
namespace android::util {
-const char* ValidLibraryPathLastSlash(const char* fileName, bool suppress64Bit, bool debuggable) {
+const char* ValidLibraryPathLastSlash(const char* fileName, bool suppress64Bit) {
// Make sure the filename is at least to the minimum library name size.
const size_t fileNameLen = strlen(fileName);
static const size_t minLength = APK_LIB_LEN + 2 + LIB_PREFIX_LEN + 1 + LIB_SUFFIX_LEN;
@@ -66,14 +66,6 @@ const char* ValidLibraryPathLastSlash(const char* fileName, bool suppress64Bit,
return nullptr;
}
- if (!debuggable) {
- // Make sure the filename starts with lib and ends with ".so".
- if (strncmp(fileName + fileNameLen - LIB_SUFFIX_LEN, LIB_SUFFIX.data(), LIB_SUFFIX_LEN) != 0
- || strncmp(lastSlash, LIB_PREFIX.data(), LIB_PREFIX_LEN) != 0) {
- return nullptr;
- }
- }
-
// Don't include 64 bit versions if they are suppressed
if (suppress64Bit && std::find(abis.begin(), abis.end(), std::string_view(
fileName + APK_LIB_LEN, lastSlash - fileName - APK_LIB_LEN)) != abis.end()) {
diff --git a/libs/androidfw/include/androidfw/ApkParsing.h b/libs/androidfw/include/androidfw/ApkParsing.h
index 194eaae8e12a..b288e152f383 100644
--- a/libs/androidfw/include/androidfw/ApkParsing.h
+++ b/libs/androidfw/include/androidfw/ApkParsing.h
@@ -24,7 +24,7 @@ extern const size_t APK_LIB_LEN;
namespace android::util {
// Checks if filename is a valid library path and returns a pointer to the last slash in the path
// if it is, nullptr otherwise
-const char* ValidLibraryPathLastSlash(const char* filename, bool suppress64Bit, bool debuggable);
+const char* ValidLibraryPathLastSlash(const char* filename, bool suppress64Bit);
// Equivalent to android.os.FileUtils.isFilenameSafe
bool isFilenameSafe(const char* filename);
diff --git a/libs/androidfw/tests/ApkParsing_test.cpp b/libs/androidfw/tests/ApkParsing_test.cpp
index ac1dc9b88463..f1f9d7166914 100644
--- a/libs/androidfw/tests/ApkParsing_test.cpp
+++ b/libs/androidfw/tests/ApkParsing_test.cpp
@@ -27,57 +27,45 @@ using ::testing::NotNull;
namespace android {
TEST(ApkParsingTest, ValidArm64Path) {
const char* path = "lib/arm64-v8a/library.so";
- auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false);
+ auto lastSlash = util::ValidLibraryPathLastSlash(path, false);
ASSERT_THAT(lastSlash, NotNull());
ASSERT_THAT(lastSlash, Eq(path + 13));
}
TEST(ApkParsingTest, ValidArm64PathButSuppressed) {
const char* path = "lib/arm64-v8a/library.so";
- auto lastSlash = util::ValidLibraryPathLastSlash(path, true, false);
+ auto lastSlash = util::ValidLibraryPathLastSlash(path, true);
ASSERT_THAT(lastSlash, IsNull());
}
TEST(ApkParsingTest, ValidArm32Path) {
const char* path = "lib/armeabi-v7a/library.so";
- auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false);
+ auto lastSlash = util::ValidLibraryPathLastSlash(path, false);
ASSERT_THAT(lastSlash, NotNull());
ASSERT_THAT(lastSlash, Eq(path + 15));
}
-TEST(ApkParsingTest, InvalidMustStartWithLib) {
- const char* path = "lib/arm64-v8a/random.so";
- auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false);
- ASSERT_THAT(lastSlash, IsNull());
-}
-
-TEST(ApkParsingTest, InvalidMustEndInSo) {
- const char* path = "lib/arm64-v8a/library.txt";
- auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false);
- ASSERT_THAT(lastSlash, IsNull());
-}
-
TEST(ApkParsingTest, InvalidCharacter) {
const char* path = "lib/arm64-v8a/lib#.so";
- auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false);
+ auto lastSlash = util::ValidLibraryPathLastSlash(path, false);
ASSERT_THAT(lastSlash, IsNull());
}
TEST(ApkParsingTest, InvalidSubdirectories) {
const char* path = "lib/arm64-v8a/anything/library.so";
- auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false);
+ auto lastSlash = util::ValidLibraryPathLastSlash(path, false);
ASSERT_THAT(lastSlash, IsNull());
}
TEST(ApkParsingTest, InvalidFileAtRoot) {
const char* path = "lib/library.so";
- auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false);
+ auto lastSlash = util::ValidLibraryPathLastSlash(path, false);
ASSERT_THAT(lastSlash, IsNull());
}
TEST(ApkParsingTest, InvalidPrefix) {
const char* path = "assets/libhello.so";
- auto lastSlash = util::ValidLibraryPathLastSlash(path, false, false);
+ auto lastSlash = util::ValidLibraryPathLastSlash(path, false);
ASSERT_THAT(lastSlash, IsNull());
}
} \ No newline at end of file
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index 5e71d3360f39..2851dd8b1003 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -42,6 +42,16 @@ flag {
}
flag {
+ name: "high_contrast_text_inner_text_color"
+ namespace: "accessibility"
+ description: "Render text color by modifying its brightness instead of defaulting to black and white"
+ bug: "384793956"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "hdr_10bit_plus"
namespace: "core_graphics"
description: "Use 10101010 and FP16 formats for HDR-UI when available"
diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h
index e05c3d695463..008b693edf02 100644
--- a/libs/hwui/hwui/DrawTextFunctor.h
+++ b/libs/hwui/hwui/DrawTextFunctor.h
@@ -37,6 +37,9 @@ namespace flags {
constexpr bool high_contrast_text_small_text_rect() {
return false;
}
+constexpr bool high_contrast_text_inner_text_color() {
+ return false;
+}
} // namespace flags
#endif
@@ -126,7 +129,25 @@ public:
// inner
gDrawTextBlobMode = DrawTextBlobMode::HctInner;
Paint innerPaint(paint);
- simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
+ if (flags::high_contrast_text_inner_text_color()) {
+ // Preserve some color information while still ensuring sufficient contrast.
+ // Thus we increase the lightness to make the color stand out against a black
+ // background, and vice-versa. For grayscale, we retain some gray to indicate
+ // states like disabled or to distinguish links.
+ bool isGrayscale = abs(lab.a) < 1 && abs(lab.b) < 1;
+ if (isGrayscale) {
+ if (darken) {
+ lab.L = lab.L < 40 ? 0 : 20;
+ } else {
+ lab.L = lab.L > 60 ? 100 : 80;
+ }
+ } else {
+ lab.L = darken ? 20 : 90;
+ }
+ simplifyPaint(uirenderer::LabToSRGB(lab, SK_AlphaOPAQUE), &innerPaint);
+ } else {
+ simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
+ }
innerPaint.setStyle(SkPaint::kFill_Style);
canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, totalAdvance);
gDrawTextBlobMode = DrawTextBlobMode::Normal;
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 69fe40c755ea..6ab8e4e0e2ab 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -323,6 +323,7 @@ void RenderThread::initGrContextOptions(GrContextOptions& options) {
}
void RenderThread::destroyRenderingContext() {
+ ATRACE_CALL();
mFunctorManager.onContextDestroyed();
if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) {
if (mEglManager->hasEglContext()) {
@@ -520,7 +521,10 @@ void RenderThread::preload() {
// EGL driver is always preloaded only if HWUI renders with GL.
if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) {
if (Properties::earlyPreloadGlContext()) {
- queue().post([this]() { requireGlContext(); });
+ queue().post([this]() {
+ ATRACE_NAME("earlyPreloadGlContext");
+ requireGlContext();
+ });
} else {
std::thread eglInitThread([]() { eglGetDisplay(EGL_DEFAULT_DISPLAY); });
eglInitThread.detach();
@@ -528,9 +532,6 @@ void RenderThread::preload() {
} else {
requireVkContext();
}
- if (Properties::earlyPreloadGlContext()) {
- queue().post([]() { GraphicBufferAllocator::getInstance(); });
- }
HardwareBitmapUploader::initialize();
}