diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
index 4d68f5ac5d..a010057324 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
@@ -20,7 +20,6 @@ import android.animation.AnimatorListenerAdapter
import android.content.Intent
import android.graphics.PointF
import android.os.SystemClock
-import android.os.Trace
import android.util.Log
import android.view.Display.DEFAULT_DISPLAY
import android.view.View
@@ -30,7 +29,6 @@ import androidx.annotation.UiThread
import androidx.annotation.VisibleForTesting
import com.android.app.displaylib.DisplayRepository
import com.android.app.displaylib.PerDisplayRepository
-import com.android.app.tracing.traceSection
import com.android.internal.jank.Cuj
import com.android.launcher3.DeviceProfile
import com.android.launcher3.PagedView
@@ -200,7 +198,6 @@ constructor(
ensureActive()
onCommandFinished(command)
}
- }
}
}
@@ -541,7 +538,6 @@ constructor(
command.addListener(recentAnimListener)
taskAnimationManager.notifyRecentsAnimationState(recentAnimListener)
} else {
- // Lawnchair-TODO: What name to put in intent here?
val intent =
Intent(interactionHandler.getLaunchIntent())
.putExtra(ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID, gestureState.gestureId)
@@ -551,7 +547,6 @@ constructor(
interactionHandler.onGestureStarted(false /*isLikelyToStartNewTask*/)
command.addListener(recentAnimListener)
}
- //Trace.beginAsyncSection(TRANSITION_NAME, 0)
OverviewCommandHelperProtoLogProxy.logSwitchingViaRecentsAnim(command)
return false
}
@@ -569,7 +564,6 @@ constructor(
) {
OverviewCommandHelperProtoLogProxy.logSwitchingViaRecentsAnimComplete(command)
command.removeListener(handler)
- Trace.endAsyncSection(TRANSITION_NAME, 0)
onRecentsViewFocusUpdated(command)
onCommandResult()
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index 83458324f2..80f3913b91 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -20,7 +20,6 @@ import android.graphics.Bitmap
import android.graphics.Matrix
import android.view.View
import android.view.View.OnClickListener
-import com.android.app.tracing.traceSection
import com.android.launcher3.Flags.enableRefactorTaskContentView
import com.android.launcher3.Flags.enableRefactorTaskThumbnail
import com.android.launcher3.model.data.TaskViewItemInfo
diff --git a/wmshell/Android.bp b/wmshell/Android.bp
index 25d3067a34..39e8e69df2 100644
--- a/wmshell/Android.bp
+++ b/wmshell/Android.bp
@@ -27,7 +27,9 @@ java_library {
name: "wm_shell_protolog-groups",
srcs: [
"src/com/android/wm/shell/protolog/ShellProtoLogGroup.java",
- ":protolog-common-src",
+ ],
+ static_libs: [
+ "protolog-common-lib",
],
}
@@ -39,28 +41,6 @@ filegroup {
path: "src",
}
-// Sources that have no dependencies that can be used directly downstream of this library
-// TODO(b/322791067): move these sources to WindowManager-Shell-shared
-filegroup {
- name: "wm_shell_util-sources",
- srcs: [
- "src/com/android/wm/shell/animation/Interpolators.java",
- "src/com/android/wm/shell/common/bubbles/*.kt",
- "src/com/android/wm/shell/common/bubbles/*.java",
- "src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt",
- "src/com/android/wm/shell/common/split/SplitScreenConstants.java",
- "src/com/android/wm/shell/common/TransactionPool.java",
- "src/com/android/wm/shell/common/TriangleShape.java",
- "src/com/android/wm/shell/common/desktopmode/*.kt",
- "src/com/android/wm/shell/draganddrop/DragAndDropConstants.java",
- "src/com/android/wm/shell/pip/PipContentOverlay.java",
- "src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java",
- "src/com/android/wm/shell/sysui/ShellSharedConstants.java",
- "src/com/android/wm/shell/util/**/*.java",
- ],
- path: "src",
-}
-
// Aidls which can be used directly downstream of this library
filegroup {
name: "wm_shell-aidls",
@@ -79,74 +59,40 @@ filegroup {
path: "src",
}
-genrule {
+java_genrule {
name: "wm_shell_protolog_src",
srcs: [
":protolog-impl",
- ":wm_shell_protolog-groups",
":wm_shell-sources",
+ ":wm_shell_protolog-groups",
],
tools: ["protologtool"],
cmd: "$(location protologtool) transform-protolog-calls " +
- "--protolog-class com.android.internal.protolog.common.ProtoLog " +
+ "--protolog-class com.android.internal.protolog.ProtoLog " +
"--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " +
"--loggroups-jar $(location :wm_shell_protolog-groups) " +
"--viewer-config-file-path /system_ext/etc/wmshell.protolog.pb " +
- "--legacy-viewer-config-file-path /system_ext/etc/wmshell.protolog.json.gz " +
- "--legacy-output-file-path /data/misc/wmtrace/shell_log.winscope " +
"--output-srcjar $(out) " +
"$(locations :wm_shell-sources)",
out: ["wm_shell_protolog.srcjar"],
}
-genrule {
- name: "generate-wm_shell_protolog.json",
- srcs: [
- ":wm_shell_protolog-groups",
- ":wm_shell-sources",
- ],
- tools: ["protologtool"],
- cmd: "$(location protologtool) generate-viewer-config " +
- "--protolog-class com.android.internal.protolog.common.ProtoLog " +
- "--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " +
- "--loggroups-jar $(location :wm_shell_protolog-groups) " +
- "--viewer-config-type json " +
- "--viewer-config $(out) " +
- "$(locations :wm_shell-sources)",
- out: ["wm_shell_protolog.json"],
-}
-
-genrule {
+java_genrule {
name: "gen-wmshell.protolog.pb",
srcs: [
- ":wm_shell_protolog-groups",
":wm_shell-sources",
+ ":wm_shell_protolog-groups",
],
tools: ["protologtool"],
cmd: "$(location protologtool) generate-viewer-config " +
- "--protolog-class com.android.internal.protolog.common.ProtoLog " +
+ "--protolog-class com.android.internal.protolog.ProtoLog " +
"--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " +
"--loggroups-jar $(location :wm_shell_protolog-groups) " +
- "--viewer-config-type proto " +
"--viewer-config $(out) " +
"$(locations :wm_shell-sources)",
out: ["wmshell.protolog.pb"],
}
-genrule {
- name: "protolog.json.gz",
- srcs: [":generate-wm_shell_protolog.json"],
- out: ["wmshell.protolog.json.gz"],
- cmd: "gzip -c < $(in) > $(out)",
-}
-
-prebuilt_etc {
- name: "wmshell.protolog.json.gz",
- system_ext_specific: true,
- src: ":protolog.json.gz",
- filename_from_src: true,
-}
-
prebuilt_etc {
name: "wmshell.protolog.pb",
system_ext_specific: true,
@@ -166,69 +112,76 @@ java_library {
},
}
-filegroup {
- name: "wm_shell-shared-aidls",
-
- srcs: [
- "shared/**/*.aidl",
- ],
-
- path: "shared/src",
-}
-
java_library {
- name: "WindowManager-Shell-shared",
+ name: "WindowManager-Shell-lite-proto",
srcs: [
- "shared/**/*.java",
- "shared/**/*.kt",
- ":wm_shell-shared-aidls",
- ],
- static_libs: [
- "androidx.dynamicanimation_dynamicanimation",
- "jsr330",
+ "src/com/android/wm/shell/desktopmode/education/data/proto/**/*.proto",
+ "src/com/android/wm/shell/desktopmode/persistence/*.proto",
],
+ proto: {
+ type: "lite",
+ },
}
android_library {
name: "WindowManager-Shell",
srcs: [
- ":wm_shell_protolog_src",
// TODO(b/168581922) protologtool do not support kotlin(*.kt)
- ":wm_shell-sources-kt",
":wm_shell-aidls",
+ ":wm_shell-shared-aidls",
+ ":wm_shell-sources-kt",
+ ":wm_shell_protolog_src",
+ "src/com/android/wm/shell/EventLogTags.logtags",
],
resource_dirs: [
"res",
],
static_libs: [
- "androidx.appcompat_appcompat",
- "androidx.core_core-animation",
- "androidx.core_core-ktx",
- "androidx.arch.core_core-runtime",
- "androidx.compose.material3_material3",
- "androidx-constraintlayout_constraintlayout",
- "androidx.dynamicanimation_dynamicanimation",
- "androidx.recyclerview_recyclerview",
- "kotlinx-coroutines-android",
- "kotlinx-coroutines-core",
+ "//frameworks/base/packages/SystemUI/aconfig:com_android_systemui_flags_lib",
"//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
"//frameworks/libs/systemui:iconloader_base",
- "com_android_wm_shell_flags_lib",
- "com.android.window.flags.window-aconfig-java",
+ "//frameworks/libs/systemui:msdl",
+ "//frameworks/libs/systemui:tracinglib-platform",
+ "//frameworks/libs/systemui:view_capture",
+ "PlatformAnimationLib",
+ "WindowManager-Shell-lite-proto",
"WindowManager-Shell-proto",
"WindowManager-Shell-shared",
- "perfetto_trace_java_protos",
+ "androidx-constraintlayout_constraintlayout",
+ "androidx.appcompat_appcompat",
+ "androidx.arch.core_core-runtime",
+ "androidx.compose.material3_material3",
+ "androidx.core_core-ktx",
+ "androidx.datastore_datastore",
+ "androidx.dynamicanimation_dynamicanimation",
+ "androidx.recyclerview_recyclerview",
+ "com_android_launcher3_flags_lib",
+ "com_android_wm_shell_flags_lib",
"dagger2",
"jsr330",
+ "kotlinx-coroutines-android",
+ "kotlinx-coroutines-core",
+ "mechanics",
+ "perfetto_trace_java_protos",
],
libs: [
// Soong fails to automatically add this dependency because all the
// *.kt sources are inside a filegroup.
"kotlin-annotations",
],
+ flags_packages: [
+ "com_android_wm_shell_flags",
+ ],
kotlincflags: ["-Xjvm-default=all"],
manifest: "AndroidManifest.xml",
plugins: ["dagger2-compiler"],
use_resource_processor: true,
}
+
+java_defaults {
+ name: "wmshell_defaults",
+ required: [
+ "wmshell.protolog.pb",
+ ],
+}
diff --git a/wmshell/OWNERS b/wmshell/OWNERS
index ebebd8a52c..68970e68de 100644
--- a/wmshell/OWNERS
+++ b/wmshell/OWNERS
@@ -1,5 +1,7 @@
-xutan@google.com
+jorgegil@google.com
+pbdr@google.com
+pragyabajoria@google.com
# Give submodule owners in shell resource approval
-per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com, nmusgrave@google.com, pbdr@google.com, tkachenkoi@google.com, mpodolian@google.com, liranb@google.com
+per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, lbill@google.com, madym@google.com, vaniadesmonda@google.com, pbdr@google.com, mpodolian@google.com, liranb@google.com, pragyabajoria@google.com, uysalorhan@google.com, gsennton@google.com, mattsziklay@google.com, mdehaini@google.com, peanutbutter@google.com, jeremysim@google.com
per-file res*/*/tv_*.xml = bronger@google.com
diff --git a/wmshell/aconfig/Android.bp b/wmshell/aconfig/Android.bp
index 7f8f57b172..f8da7fa86c 100644
--- a/wmshell/aconfig/Android.bp
+++ b/wmshell/aconfig/Android.bp
@@ -4,6 +4,7 @@ aconfig_declarations {
container: "system",
srcs: [
"multitasking.aconfig",
+ "automotive.aconfig",
],
}
diff --git a/wmshell/aconfig/OWNERS b/wmshell/aconfig/OWNERS
index 9eba0f2dea..eacadeacab 100644
--- a/wmshell/aconfig/OWNERS
+++ b/wmshell/aconfig/OWNERS
@@ -1,3 +1,4 @@
# Owners for flag changes
madym@google.com
-hwwang@google.com
\ No newline at end of file
+hwwang@google.com
+sqsun@google.com
\ No newline at end of file
diff --git a/wmshell/aconfig/multitasking.aconfig b/wmshell/aconfig/multitasking.aconfig
index 112eb617e7..69e2aca01a 100644
--- a/wmshell/aconfig/multitasking.aconfig
+++ b/wmshell/aconfig/multitasking.aconfig
@@ -1,21 +1,10 @@
# proto-file: build/make/tools/aconfig/aconfig_protos/protos/aconfig.proto
+# proto-message: flag_declarations
+# Project link: http://gantry/projects/android_platform_multitasking
package: "com.android.wm.shell"
container: "system"
-flag {
- name: "enable_app_pairs"
- namespace: "multitasking"
- description: "Enables the ability to create and save app pairs to the Home screen"
- bug: "274835596"
-}
-
-flag {
- name: "enable_split_contextual"
- namespace: "multitasking"
- description: "Enables invoking split contextually"
- bug: "276361926"
-}
flag {
name: "enable_taskbar_navbar_unification"
@@ -25,18 +14,10 @@ flag {
}
flag {
- name: "enable_pip2_implementation"
+ name: "enable_pip2"
namespace: "multitasking"
description: "Enables the new implementation of PiP (PiP2)"
- bug: "290220798"
- is_fixed_read_only: true
-}
-
-flag {
- name: "enable_left_right_split_in_portrait"
- namespace: "multitasking"
- description: "Enables left/right split in portrait"
- bug: "291018646"
+ bug: "311462191"
}
flag {
@@ -102,6 +83,13 @@ flag {
bug: "342245211"
}
+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"
@@ -113,10 +101,156 @@ flag {
}
flag {
- name: "animate_bubble_size_change"
+ name: "enable_bubble_app_compat_fixes"
namespace: "multitasking"
- description: "Turns on the animation for bubble bar icons size change"
- bug: "335575529"
+ description: "Enable fixes for app compat (e.g. insets and layout issues) for bubbles"
+ bug: "384610402"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_taskbar_on_phones"
+ namespace: "multitasking"
+ description: "Enables taskbar on phones"
+ bug: "348007377"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_shell_top_task_tracking"
+ namespace: "multitasking"
+ description: "Enables tracking top tasks from the shell"
+ bug: "346588978"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_recents_bookend_transition"
+ namespace: "multitasking"
+ description: "Use a finish-transition to clean up recents instead of the finish-WCT"
+ bug: "346588978"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_bubble_to_fullscreen"
+ namespace: "multitasking"
+ description: "Enable an option to move bubbles to fullscreen"
+ bug: "363326492"
+}
+
+flag {
+ name: "enable_flexible_two_app_split"
+ namespace: "multitasking"
+ description: "Enables only 2 app 90:10 split"
+ bug: "349828130"
+}
+
+flag {
+ name: "enable_flexible_split"
+ namespace: "multitasking"
+ description: "Enables flexibile split feature for split screen"
+ bug: "349828130"
+}
+
+flag {
+ name: "task_view_repository"
+ namespace: "multitasking"
+ description: "Factor task-view state tracking out of taskviewtransitions"
+ bug: "384976265"
+}
+
+flag {
+ name: "fix_missing_user_change_callbacks"
+ namespace: "multitasking"
+ description: "Fix a race condition that could make Shell miss a user change callback."
+ bug: "404251029"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_bubble_bar_on_phones"
+ namespace: "multitasking"
+ description: "Try out bubble bar on phones"
+ bug: "394869612"
+}
+
+flag {
+ name: "enable_bubble_task_view_listener"
+ namespace: "multitasking"
+ description: "Use the same taskview listener for bubble bar and floating"
+ bug: "272102927"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_gsf"
+ namespace: "multitasking"
+ description: "Applies GSF font styles to multitasking."
+ bug: "400534660"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_magnetic_split_divider"
+ namespace: "multitasking"
+ description: "Makes the split divider snap 'magnetically' to available snap points during drag"
+ bug: "383631946"
+}
+
+flag {
+ name: "enable_dynamic_insets_for_app_launch"
+ namespace: "multitasking"
+ description: "Enables dynamic insets for app launch so the window is properly cropped"
+ bug: "336511494"
+}
+
+flag {
+ name: "task_view_transitions_refactor"
+ namespace: "windowing_frontend"
+ description: "Refactor TaskViewTransitions.startAnimation"
+ bug: "402454136"
+}
+
+flag {
+ name: "enable_pip_box_shadows"
+ namespace: "multitasking"
+ description: "Enables box shadows for PiP"
+ bug: "367464660"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_bubble_swipe_up_cleanup"
+ namespace: "multitasking"
+ description: "Enable clean up of bubbles swipe gesture handling"
+ bug: "391909607"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_enter_split_remove_bubble"
+ namespace: "multitasking"
+ description: "Enable removing a task from bubbles when it is entering split screen"
+ bug: "396315875"
metadata {
purpose: PURPOSE_BUGFIX
}
diff --git a/wmshell/multivalentTests/Android.bp b/wmshell/multivalentTests/Android.bp
index 1ad19c9f30..5d14dc054b 100644
--- a/wmshell/multivalentTests/Android.bp
+++ b/wmshell/multivalentTests/Android.bp
@@ -35,7 +35,6 @@ android_app {
android_robolectric_test {
name: "WMShellRobolectricTests",
instrumentation_for: "WindowManagerShellRobolectric",
- upstream: true,
java_resource_dirs: [
"robolectric/config",
],
@@ -50,9 +49,14 @@ android_robolectric_test {
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.ext.junit",
- "mockito-robolectric-prebuilt",
+ "mockito-robolectric-prebuilt", // mockito deps order matters!
"mockito-kotlin2",
+ "WMShellTests-utils", // order matters because it depends on mockito-kotlin2
+ "platform-parametric-runner-lib",
"truth",
+ "flag-junit-base",
+ "flag-junit",
+ "testables",
],
auto_gen_config: true,
}
@@ -63,22 +67,25 @@ android_test {
"src/**/*.kt",
],
static_libs: [
+ "WMShellTests-utils",
"WindowManager-Shell",
- "junit",
"androidx.core_core-animation-testing",
- "androidx.test.runner",
- "androidx.test.rules",
"androidx.test.ext.junit",
+ "androidx.test.rules",
+ "androidx.test.runner",
"frameworks-base-testutils",
+ "junit",
"mockito-kotlin2",
"mockito-target-extended-minus-junit4",
- "truth",
+ "platform-parametric-runner-lib",
"platform-test-annotations",
"platform-test-rules",
+ "testables",
+ "truth",
],
libs: [
- "android.test.base",
- "android.test.runner",
+ "android.test.base.stubs.system",
+ "android.test.runner.stubs.system",
],
jni_libs: [
"libdexmakerjvmtiagent",
diff --git a/wmshell/multivalentTests/AndroidManifest.xml b/wmshell/multivalentTests/AndroidManifest.xml
index f8f8338e5f..4341eaaf9f 100644
--- a/wmshell/multivalentTests/AndroidManifest.xml
+++ b/wmshell/multivalentTests/AndroidManifest.xml
@@ -1,8 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wmshell/multivalentTests/robolectric/config/robolectric.properties b/wmshell/multivalentTests/robolectric/config/robolectric.properties
index 7a0527ccaa..d50d976c9e 100644
--- a/wmshell/multivalentTests/robolectric/config/robolectric.properties
+++ b/wmshell/multivalentTests/robolectric/config/robolectric.properties
@@ -1,2 +1,3 @@
sdk=NEWEST_SDK
+graphicsMode=NATIVE
diff --git a/wmshell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/wmshell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
index 9e1440d571..3b67d13106 100644
--- a/wmshell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
+++ b/wmshell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
@@ -27,10 +27,11 @@ import android.view.WindowManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.internal.protolog.common.ProtoLog
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT
-import com.android.wm.shell.common.bubbles.BubbleBarLocation
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+import com.android.wm.shell.shared.bubbles.DeviceConfig
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors.directExecutor
import org.junit.Before
@@ -154,19 +155,19 @@ class BubblePositionerTest {
/** Test that the default resting position on tablet is middle right. */
@Test
- fun testGetDefaultPosition_appBubble_onTablet() {
+ fun testGetDefaultPosition_noteBubble_onTablet() {
positioner.update(defaultDeviceConfig.copy(isLargeScreen = true))
val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
- val startPosition = positioner.getDefaultStartPosition(true /* isAppBubble */)
+ val startPosition = positioner.getDefaultStartPosition(true /* isNoteBubble */)
assertThat(startPosition.x).isEqualTo(allowableStackRegion.right)
assertThat(startPosition.y).isEqualTo(defaultYPosition)
}
@Test
- fun testGetRestingPosition_appBubble_onTablet_RTL() {
+ fun testGetRestingPosition_noteBubble_onTablet_RTL() {
positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
- val startPosition = positioner.getDefaultStartPosition(true /* isAppBubble */)
+ val startPosition = positioner.getDefaultStartPosition(true /* isNoteBubble */)
assertThat(startPosition.x).isEqualTo(allowableStackRegion.left)
assertThat(startPosition.y).isEqualTo(defaultYPosition)
}
@@ -221,7 +222,7 @@ class BubblePositionerTest {
positioner.setShowingInBubbleBar(true)
positioner.update(deviceConfig)
- positioner.bubbleBarTopOnScreen = 2500
+ positioner.updateBubbleBarTopOnScreen(100)
val spaceBetweenTopInsetAndBubbleBarInLandscape = 1680
val expandedViewVerticalSpacing =
@@ -247,7 +248,7 @@ class BubblePositionerTest {
)
positioner.setShowingInBubbleBar(true)
positioner.update(deviceConfig)
- positioner.bubbleBarTopOnScreen = 2500
+ positioner.updateBubbleBarTopOnScreen(100)
val spaceBetweenTopInsetAndBubbleBarInLandscape = 180
val expandedViewSpacing =
@@ -268,7 +269,8 @@ class BubblePositionerTest {
)
positioner.update(deviceConfig)
val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+ val bubble =
+ Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor())
assertThat(positioner.getExpandedViewHeight(bubble)).isEqualTo(MAX_HEIGHT)
}
@@ -294,6 +296,7 @@ class BubblePositionerTest {
0 /* taskId */,
null /* locus */,
true /* isDismissable */,
+ directExecutor(),
directExecutor()
) {}
@@ -322,6 +325,7 @@ class BubblePositionerTest {
0 /* taskId */,
null /* locus */,
true /* isDismissable */,
+ directExecutor(),
directExecutor()
) {}
@@ -416,7 +420,8 @@ class BubblePositionerTest {
positioner.update(deviceConfig)
val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+ val bubble =
+ Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor())
// This bubble will have max height so it'll always be top aligned
assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
@@ -433,7 +438,8 @@ class BubblePositionerTest {
positioner.update(deviceConfig)
val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+ val bubble =
+ Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor())
// Always top aligned in phone portrait
assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
@@ -452,7 +458,8 @@ class BubblePositionerTest {
positioner.update(deviceConfig)
val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+ val bubble =
+ Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor())
// This bubble will have max height which is always top aligned on small tablets
assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
@@ -470,7 +477,8 @@ class BubblePositionerTest {
positioner.update(deviceConfig)
val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+ val bubble =
+ Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor())
// This bubble will have max height which is always top aligned on small tablets
assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
@@ -489,7 +497,8 @@ class BubblePositionerTest {
positioner.update(deviceConfig)
val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+ val bubble =
+ Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor())
// This bubble will have max height which is always top aligned on landscape, large tablet
assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
@@ -507,7 +516,8 @@ class BubblePositionerTest {
positioner.update(deviceConfig)
val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+ val bubble =
+ Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor())
val manageButtonHeight =
context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_height)
@@ -575,26 +585,115 @@ class BubblePositionerTest {
@Test
fun testGetBubbleBarExpandedViewBounds_onLeft() {
- testGetBubbleBarExpandedViewBounds(onLeft = true, isOverflow = false)
+ verifyGetBubbleBarExpandedViewBounds(onLeft = true, isOverflow = false)
}
@Test
fun testGetBubbleBarExpandedViewBounds_onRight() {
- testGetBubbleBarExpandedViewBounds(onLeft = false, isOverflow = false)
+ verifyGetBubbleBarExpandedViewBounds(onLeft = false, isOverflow = false)
}
@Test
fun testGetBubbleBarExpandedViewBounds_isOverflow_onLeft() {
- testGetBubbleBarExpandedViewBounds(onLeft = true, isOverflow = true)
+ verifyGetBubbleBarExpandedViewBounds(onLeft = true, isOverflow = true)
}
@Test
fun testGetBubbleBarExpandedViewBounds_isOverflow_onRight() {
- testGetBubbleBarExpandedViewBounds(onLeft = false, isOverflow = true)
+ verifyGetBubbleBarExpandedViewBounds(onLeft = false, isOverflow = true)
}
- private fun testGetBubbleBarExpandedViewBounds(onLeft: Boolean, isOverflow: Boolean) {
- positioner.setShowingInBubbleBar(true)
+ @Test
+ fun testGetTaskViewRestBounds_phone() {
+ val deviceConfig =
+ defaultDeviceConfig.copy(
+ insets = Insets.of(10, 20, 5, 15),
+ windowBounds = Rect(0, 0, 1800, 2600)
+ )
+ positioner.update(deviceConfig)
+
+ val padding = context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding)
+ val top = positioner.expandedViewYTopAligned
+ val left = positioner.insets.left + padding
+ val right = positioner.screenRect.right - positioner.insets.right - padding
+ val bottom = top + positioner.getMaxExpandedViewHeight(false)
+
+ val rect = Rect()
+ positioner.getTaskViewRestBounds(rect)
+ assertThat(rect).isEqualTo(Rect(left, top, right, bottom))
+ }
+
+ @Test
+ fun getExpandedViewContainerPadding_largeScreen_fitsMaxViewWidth() {
+ val expandedViewWidth = context.resources.getDimensionPixelSize(
+ R.dimen.bubble_expanded_view_largescreen_width
+ )
+ // set the screen size so that it is wide enough to fit the maximum width size
+ val screenWidth = expandedViewWidth * 2
+ positioner.update(
+ defaultDeviceConfig.copy(
+ windowBounds = Rect(0, 0, screenWidth, 2000),
+ isLargeScreen = true,
+ isLandscape = false
+ )
+ )
+ val paddings =
+ positioner.getExpandedViewContainerPadding(/* onLeft= */ true, /* isOverflow= */ false)
+
+ val padding = context.resources.getDimensionPixelSize(
+ R.dimen.bubble_expanded_view_largescreen_landscape_padding
+ )
+ val right = screenWidth - expandedViewWidth - padding
+ assertThat(paddings).isEqualTo(intArrayOf(padding - positioner.pointerSize, 0, right, 0))
+ }
+
+ @Test
+ fun getExpandedViewContainerPadding_largeScreen_doesNotFitMaxViewWidth() {
+ positioner.update(
+ defaultDeviceConfig.copy(
+ windowBounds = Rect(0, 0, 600, 2000),
+ isLargeScreen = true,
+ isLandscape = false
+ )
+ )
+ val paddings =
+ positioner.getExpandedViewContainerPadding(/* onLeft= */ true, /* isOverflow= */ false)
+
+ val padding = context.resources.getDimensionPixelSize(
+ R.dimen.bubble_expanded_view_largescreen_landscape_padding
+ )
+ // the screen is not wide enough to fit the maximum width size, so the view fills the screen
+ // minus left and right padding
+ assertThat(paddings).isEqualTo(intArrayOf(padding - positioner.pointerSize, 0, padding, 0))
+ }
+
+ @Test
+ fun getExpandedViewContainerPadding_smallTablet() {
+ val screenWidth = 500
+ positioner.update(
+ defaultDeviceConfig.copy(
+ windowBounds = Rect(0, 0, screenWidth, 2000),
+ isLargeScreen = true,
+ isSmallTablet = true,
+ isLandscape = false
+ )
+ )
+ val paddings =
+ positioner.getExpandedViewContainerPadding(/* onLeft= */ true, /* isOverflow= */ false)
+
+ // for small tablets, the view width is set to be 0.72 * screen width
+ val viewWidth = (screenWidth * 0.72).toInt()
+ val padding = (screenWidth - viewWidth) / 2
+ assertThat(paddings).isEqualTo(intArrayOf(padding - positioner.pointerSize, 0, padding, 0))
+ }
+
+ private fun verifyGetBubbleBarExpandedViewBounds(onLeft: Boolean, isOverflow: Boolean) {
+ positioner.isShowingInBubbleBar = true
+ positioner.bubbleBarLocation = if (onLeft) {
+ BubbleBarLocation.LEFT
+ } else {
+ BubbleBarLocation.RIGHT
+ }
val windowBounds = Rect(0, 0, 2000, 2600)
val insets = Insets.of(10, 20, 5, 15)
val deviceConfig =
@@ -607,7 +706,7 @@ class BubblePositionerTest {
positioner.update(deviceConfig)
val bubbleBarHeight = 100
- positioner.bubbleBarTopOnScreen = windowBounds.bottom - insets.bottom - bubbleBarHeight
+ positioner.updateBubbleBarTopOnScreen(bubbleBarHeight)
val expandedViewPadding =
context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding)
@@ -632,8 +731,14 @@ class BubblePositionerTest {
val bounds = Rect()
positioner.getBubbleBarExpandedViewBounds(onLeft, isOverflow, bounds)
-
assertThat(bounds).isEqualTo(expectedBounds)
+
+ if (!isOverflow) {
+ val bounds2 = Rect()
+ // In bubble bar mode this should return the same bounds
+ positioner.getTaskViewRestBounds(bounds2)
+ assertThat(bounds2).isEqualTo(expectedBounds)
+ }
}
private val defaultYPosition: Float
diff --git a/wmshell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/wmshell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
index 327e205955..da1fae9a04 100644
--- a/wmshell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ b/wmshell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -23,36 +23,39 @@ import android.content.res.Resources
import android.graphics.Color
import android.graphics.drawable.Icon
import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
-import android.view.IWindowManager
import android.view.WindowManager
-import android.view.WindowManagerGlobal
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.internal.protolog.common.ProtoLog
+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.bubbles.BubbleStackView.SurfaceSynchronizer
+import com.android.wm.shell.bubbles.Bubbles.BubbleExpandListener
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.ShellExecutor
+import com.android.wm.shell.common.TestShellExecutor
import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
-import com.android.wm.shell.taskview.TaskView
-import com.android.wm.shell.taskview.TaskViewTaskController
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors.directExecutor
import org.junit.After
+import org.junit.Assert.fail
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
import org.mockito.kotlin.mock
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
+import org.mockito.kotlin.never
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit
import java.util.function.Consumer
@@ -66,14 +69,16 @@ class BubbleStackViewTest {
private val context = ApplicationProvider.getApplicationContext()
private lateinit var positioner: BubblePositioner
+ private lateinit var bubbleLogger: BubbleLogger
private lateinit var iconFactory: BubbleIconFactory
private lateinit var expandedViewManager: FakeBubbleExpandedViewManager
private lateinit var bubbleStackView: BubbleStackView
- private lateinit var shellExecutor: ShellExecutor
- private lateinit var windowManager: IWindowManager
+ private lateinit var shellExecutor: TestShellExecutor
+ private lateinit var windowManager: WindowManager
private lateinit var bubbleTaskViewFactory: BubbleTaskViewFactory
private lateinit var bubbleData: BubbleData
private lateinit var bubbleStackViewManager: FakeBubbleStackViewManager
+ private lateinit var surfaceSynchronizer: FakeSurfaceSynchronizer
private var sysuiProxy = mock()
@Before
@@ -81,9 +86,8 @@ class BubbleStackViewTest {
PhysicsAnimatorTestUtils.prepareForTest()
// Disable protolog tool when running the tests from studio
ProtoLog.REQUIRE_PROTOLOGTOOL = false
- windowManager = WindowManagerGlobal.getWindowManagerService()!!
shellExecutor = TestShellExecutor()
- val windowManager = context.getSystemService(WindowManager::class.java)
+ windowManager = context.getSystemService(WindowManager::class.java)
iconFactory =
BubbleIconFactory(
context,
@@ -95,24 +99,27 @@ class BubbleStackViewTest {
)
)
positioner = BubblePositioner(context, windowManager)
+ bubbleLogger = BubbleLogger(UiEventLoggerFake())
bubbleData =
BubbleData(
context,
- BubbleLogger(UiEventLoggerFake()),
+ bubbleLogger,
positioner,
BubbleEducationController(context),
+ shellExecutor,
shellExecutor
)
bubbleStackViewManager = FakeBubbleStackViewManager()
expandedViewManager = FakeBubbleExpandedViewManager()
- bubbleTaskViewFactory = FakeBubbleTaskViewFactory()
+ bubbleTaskViewFactory = FakeBubbleTaskViewFactory(context, shellExecutor)
+ surfaceSynchronizer = FakeSurfaceSynchronizer()
bubbleStackView =
BubbleStackView(
context,
bubbleStackViewManager,
positioner,
bubbleData,
- null,
+ surfaceSynchronizer,
FloatingContentCoordinator(),
{ sysuiProxy },
shellExecutor
@@ -165,6 +172,7 @@ class BubbleStackViewTest {
// This will eventually propagate an update back to the stack view, but setting the
// entire pipeline is outside the scope of a unit test.
assertThat(bubbleData.isExpanded).isTrue()
+ shellExecutor.flushAll()
}
assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
@@ -173,8 +181,207 @@ class BubbleStackViewTest {
assertThat(lastUpdate!!.expanded).isTrue()
}
+ @Test
+ fun expandStack_imeHidden() {
+ val bubble = createAndInflateBubble()
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.addBubble(bubble)
+ }
+
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
+
+ positioner.setImeVisible(false, 0)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ // simulate a request from the bubble data listener to expand the stack
+ bubbleStackView.isExpanded = true
+ verify(sysuiProxy).onStackExpandChanged(true)
+ shellExecutor.flushAll()
+ }
+
+ assertThat(bubbleStackViewManager.onImeHidden).isNull()
+ }
+
+ @Test
+ fun collapseStack_imeHidden() {
+ val bubble = createAndInflateBubble()
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.addBubble(bubble)
+ }
+
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
+
+ positioner.setImeVisible(false, 0)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ // simulate a request from the bubble data listener to expand the stack
+ bubbleStackView.isExpanded = true
+ verify(sysuiProxy).onStackExpandChanged(true)
+ shellExecutor.flushAll()
+ }
+
+ assertThat(bubbleStackViewManager.onImeHidden).isNull()
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ // simulate a request from the bubble data listener to collapse the stack
+ bubbleStackView.isExpanded = false
+ verify(sysuiProxy).onStackExpandChanged(false)
+ shellExecutor.flushAll()
+ }
+
+ assertThat(bubbleStackViewManager.onImeHidden).isNull()
+ }
+
+ @Test
+ fun expandStack_waitsForIme() {
+ val bubble = createAndInflateBubble()
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.addBubble(bubble)
+ }
+
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
+
+ positioner.setImeVisible(true, 100)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ // simulate a request from the bubble data listener to expand the stack
+ bubbleStackView.isExpanded = true
+ }
+
+ val onImeHidden = bubbleStackViewManager.onImeHidden
+ assertThat(onImeHidden).isNotNull()
+ verify(sysuiProxy, never()).onStackExpandChanged(any())
+ positioner.setImeVisible(false, 0)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ onImeHidden!!.run()
+ verify(sysuiProxy).onStackExpandChanged(true)
+ shellExecutor.flushAll()
+ }
+ }
+
+ @Test
+ fun collapseStack_waitsForIme() {
+ val bubble = createAndInflateBubble()
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.addBubble(bubble)
+ }
+
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
+
+ positioner.setImeVisible(true, 100)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ // simulate a request from the bubble data listener to expand the stack
+ bubbleStackView.isExpanded = true
+ }
+
+ var onImeHidden = bubbleStackViewManager.onImeHidden
+ assertThat(onImeHidden).isNotNull()
+ verify(sysuiProxy, never()).onStackExpandChanged(any())
+ positioner.setImeVisible(false, 0)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ onImeHidden!!.run()
+ verify(sysuiProxy).onStackExpandChanged(true)
+ shellExecutor.flushAll()
+ }
+
+ bubbleStackViewManager.onImeHidden = null
+ positioner.setImeVisible(true, 100)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ // simulate a request from the bubble data listener to collapse the stack
+ bubbleStackView.isExpanded = false
+ }
+
+ onImeHidden = bubbleStackViewManager.onImeHidden
+ assertThat(onImeHidden).isNotNull()
+ verify(sysuiProxy, never()).onStackExpandChanged(false)
+ positioner.setImeVisible(false, 0)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ onImeHidden!!.run()
+ verify(sysuiProxy).onStackExpandChanged(false)
+ shellExecutor.flushAll()
+ }
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_BUBBLE_SWIPE_UP_CLEANUP)
+ @Test
+ fun expandStack_clearsImeRunnable() {
+ val bubble = createAndInflateBubble()
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.addBubble(bubble)
+ }
+
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
+
+ // Set up a pending runnable to be cleared
+ bubbleStackViewManager.onImeHidden = Runnable {
+ fail("IME runnable should not be called when IME is hidden")
+ }
+
+ positioner.setImeVisible(false, 0)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ // simulate a request from the bubble data listener to expand the stack
+ bubbleStackView.isExpanded = true
+ verify(sysuiProxy).onStackExpandChanged(true)
+ shellExecutor.flushAll()
+ }
+
+ // Ime runnable is reset
+ assertThat(bubbleStackViewManager.onImeHidden).isNull()
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_BUBBLE_SWIPE_UP_CLEANUP)
+ @Test
+ fun collapseStack_clearsImeRunnable() {
+ val bubble = createAndInflateBubble()
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.addBubble(bubble)
+ }
+
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
+
+ positioner.setImeVisible(false, 0)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ // simulate a request from the bubble data listener to expand the stack
+ bubbleStackView.isExpanded = true
+ verify(sysuiProxy).onStackExpandChanged(true)
+ shellExecutor.flushAll()
+ }
+
+ // Set up a pending runnable to be cleared
+ bubbleStackViewManager.onImeHidden = Runnable {
+ fail("IME runnable should not be called when IME is hidden")
+ }
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ // simulate a request from the bubble data listener to collapse the stack
+ bubbleStackView.isExpanded = false
+ verify(sysuiProxy).onStackExpandChanged(false)
+ shellExecutor.flushAll()
+ }
+
+ // Check that the runnable is cleared
+ assertThat(bubbleStackViewManager.onImeHidden).isNull()
+ }
+
@Test
fun tapDifferentBubble_shouldReorder() {
+ surfaceSynchronizer.isActive = false
val bubble1 = createAndInflateChatBubble(key = "bubble1")
val bubble2 = createAndInflateChatBubble(key = "bubble2")
InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -203,6 +410,7 @@ class BubbleStackViewTest {
bubbleStackView.setSelectedBubble(bubble2)
bubbleStackView.isExpanded = true
+ shellExecutor.flushAll()
}
assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
@@ -220,6 +428,7 @@ class BubbleStackViewTest {
// tap on bubble1 to select it
InstrumentationRegistry.getInstrumentation().runOnMainSync {
bubble1.iconView!!.performClick()
+ shellExecutor.flushAll()
}
assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
@@ -230,6 +439,7 @@ class BubbleStackViewTest {
// listener wired up.
bubbleStackView.setSelectedBubble(bubble1)
bubble1.iconView!!.performClick()
+ shellExecutor.flushAll()
}
assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
@@ -241,6 +451,147 @@ class BubbleStackViewTest {
.inOrder()
}
+ @Test
+ fun tapDifferentBubble_imeVisible_shouldWaitForIme() {
+ val bubble1 = createAndInflateChatBubble(key = "bubble1")
+ val bubble2 = createAndInflateChatBubble(key = "bubble2")
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.addBubble(bubble1)
+ bubbleStackView.addBubble(bubble2)
+ }
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+
+ assertThat(bubbleStackView.bubbleCount).isEqualTo(2)
+ assertThat(bubbleData.bubbles).hasSize(2)
+ assertThat(bubbleData.selectedBubble).isEqualTo(bubble2)
+ assertThat(bubble2.iconView).isNotNull()
+
+ val expandListener = FakeBubbleExpandListener()
+ bubbleStackView.setExpandListener(expandListener)
+
+ var lastUpdate: BubbleData.Update? = null
+ val semaphore = Semaphore(0)
+ val listener =
+ BubbleData.Listener { update ->
+ lastUpdate = update
+ semaphore.release()
+ }
+ bubbleData.setListener(listener)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubble2.iconView!!.performClick()
+ assertThat(bubbleData.isExpanded).isTrue()
+
+ bubbleStackView.setSelectedBubble(bubble2)
+ bubbleStackView.isExpanded = true
+ shellExecutor.flushAll()
+ }
+
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(lastUpdate!!.expanded).isTrue()
+ assertThat(lastUpdate!!.bubbles.map { it.key })
+ .containsExactly("bubble2", "bubble1")
+ .inOrder()
+
+ // wait for idle to allow the animation to start
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ // wait for the expansion animation to complete before interacting with the bubbles
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
+ AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y)
+
+ // make the IME visible and tap on bubble1 to select it
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ positioner.setImeVisible(true, 100)
+ bubble1.iconView!!.performClick()
+ // we have to set the selected bubble in the stack view manually because we don't have a
+ // listener wired up.
+ bubbleStackView.setSelectedBubble(bubble1)
+ shellExecutor.flushAll()
+ }
+
+ val onImeHidden = bubbleStackViewManager.onImeHidden
+ assertThat(onImeHidden).isNotNull()
+
+ assertThat(expandListener.bubblesExpandedState).isEqualTo(mapOf("bubble2" to true))
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ onImeHidden!!.run()
+ shellExecutor.flushAll()
+ }
+
+ assertThat(expandListener.bubblesExpandedState)
+ .isEqualTo(mapOf("bubble1" to true, "bubble2" to false))
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
+ }
+
+ @Test
+ fun tapDifferentBubble_imeHidden_updatesImmediately() {
+ val bubble1 = createAndInflateChatBubble(key = "bubble1")
+ val bubble2 = createAndInflateChatBubble(key = "bubble2")
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.addBubble(bubble1)
+ bubbleStackView.addBubble(bubble2)
+ }
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+
+ assertThat(bubbleStackView.bubbleCount).isEqualTo(2)
+ assertThat(bubbleData.bubbles).hasSize(2)
+ assertThat(bubbleData.selectedBubble).isEqualTo(bubble2)
+ assertThat(bubble2.iconView).isNotNull()
+
+ val expandListener = FakeBubbleExpandListener()
+ bubbleStackView.setExpandListener(expandListener)
+
+ var lastUpdate: BubbleData.Update? = null
+ val semaphore = Semaphore(0)
+ val listener =
+ BubbleData.Listener { update ->
+ lastUpdate = update
+ semaphore.release()
+ }
+ bubbleData.setListener(listener)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubble2.iconView!!.performClick()
+ assertThat(bubbleData.isExpanded).isTrue()
+
+ bubbleStackView.setSelectedBubble(bubble2)
+ bubbleStackView.isExpanded = true
+ shellExecutor.flushAll()
+ }
+
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(lastUpdate!!.expanded).isTrue()
+ assertThat(lastUpdate!!.bubbles.map { it.key })
+ .containsExactly("bubble2", "bubble1")
+ .inOrder()
+
+ // wait for idle to allow the animation to start
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ // wait for the expansion animation to complete before interacting with the bubbles
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
+ AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y)
+
+ // make the IME hidden and tap on bubble1 to select it
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ positioner.setImeVisible(false, 0)
+ bubble1.iconView!!.performClick()
+ // we have to set the selected bubble in the stack view manually because we don't have a
+ // listener wired up.
+ bubbleStackView.setSelectedBubble(bubble1)
+ shellExecutor.flushAll()
+ }
+
+ val onImeHidden = bubbleStackViewManager.onImeHidden
+ assertThat(onImeHidden).isNull()
+
+ assertThat(expandListener.bubblesExpandedState)
+ .isEqualTo(mapOf("bubble1" to true, "bubble2" to false))
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
+ }
+
@EnableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
@Test
fun testCreateStackView_noOverflowContents_noOverflow() {
@@ -350,6 +701,122 @@ class BubbleStackViewTest {
assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isGreaterThan(-1)
}
+ @Test
+ fun removeFromWindow_stopMonitoringSwipeUpGesture() {
+ bubbleStackView = spy(bubbleStackView)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ // No way to add to window in the test environment right now so just pretend
+ bubbleStackView.onDetachedFromWindow()
+ }
+ verify(bubbleStackView).stopMonitoringSwipeUpGesture()
+ }
+
+ @Test
+ fun animateExpand_expandRunsRunnable() {
+ bubbleStackView = spy(bubbleStackView)
+ val bubble = createAndInflateChatBubble(key = "bubble")
+
+ assertThat(bubble.expandedView).isNotNull()
+
+ var afterTransitionRan = false
+ val semaphore = Semaphore(0)
+
+ // Expand animation runs on a delay so wait for it.
+ val runnable = Runnable {
+ afterTransitionRan = true
+ semaphore.release()
+ }
+
+ assertThat(bubbleStackView.isExpanded).isFalse()
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.addBubble(bubble)
+ bubbleStackView.setSelectedBubble(bubble)
+ bubbleStackView.animateExpand(null, runnable)
+ bubbleStackView.isExpanded = true
+ shellExecutor.flushAll()
+ }
+
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(bubbleStackView.isExpanded).isTrue()
+ assertThat(afterTransitionRan).isTrue()
+ }
+
+ @Test
+ fun animateExpand_switchRunsRunnable() {
+ bubbleStackView = spy(bubbleStackView)
+ val bubble = createAndInflateChatBubble(key = "bubble")
+ val bubble2 = createAndInflateChatBubble(key = "bubble2")
+
+ var afterTransitionRan = false
+ val semaphore = Semaphore(0)
+
+ // Expand animation runs on a delay so wait for it.
+ val runnable = Runnable {
+ afterTransitionRan = true
+ semaphore.release()
+ }
+ assertThat(bubbleStackView.isExpanded).isFalse()
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.addBubble(bubble)
+ bubbleStackView.addBubble(bubble2)
+ bubbleStackView.setSelectedBubble(bubble)
+ bubbleStackView.isExpanded = true
+ shellExecutor.flushAll()
+ }
+
+ assertThat(bubbleStackView.isExpanded).isTrue()
+ assertThat(bubbleStackView.expandedBubble!!.key).isEqualTo(bubble.key)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.animateExpand(null, runnable)
+ bubbleStackView.setSelectedBubble(bubble2)
+ shellExecutor.flushAll()
+ }
+
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(bubbleStackView.isExpanded).isTrue()
+ assertThat(bubbleStackView.expandedBubble!!.key).isEqualTo(bubble2.key)
+ assertThat(afterTransitionRan).isTrue()
+ }
+
+ @Test
+ fun canExpandView_true_triggersContinueExpand() {
+ bubbleStackView = spy(bubbleStackView)
+ val bubble = createAndInflateChatBubble(key = "bubble")
+ val bubbleTransition = mock()
+ bubble.preparingTransition = bubbleTransition
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.addBubble(bubble)
+ }
+
+ assertThat(bubbleStackView.isExpanded).isFalse()
+ assertThat(bubbleStackView.canExpandView(bubble)).isTrue()
+ verify(bubbleTransition).continueExpand()
+ }
+
+ @Test
+ fun canExpandView_false() {
+ bubbleStackView = spy(bubbleStackView)
+ val bubble = createAndInflateChatBubble(key = "bubble")
+ val bubbleTransition = mock()
+ bubble.preparingTransition = bubbleTransition
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.addBubble(bubble)
+ bubbleStackView.setSelectedBubble(bubble)
+ bubbleStackView.isExpanded = true
+ shellExecutor.flushAll()
+ }
+
+ assertThat(bubbleStackView.isExpanded).isTrue()
+ assertThat(bubbleStackView.expandedBubble!!.key).isEqualTo(bubble.key)
+ assertThat(bubbleStackView.canExpandView(bubble)).isFalse()
+ verify(bubbleTransition, never()).continueExpand()
+ }
+
private fun createAndInflateChatBubble(key: String): Bubble {
val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button)
val shortcutInfo = ShortcutInfo.Builder(context, "fakeId").setIcon(icon).build()
@@ -363,6 +830,7 @@ class BubbleStackViewTest {
/* taskId= */ 0,
"locus",
/* isDismissable= */ true,
+ directExecutor(),
directExecutor()
) {}
inflateBubble(bubble)
@@ -372,7 +840,8 @@ class BubbleStackViewTest {
private fun createAndInflateBubble(): Bubble {
val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), icon, directExecutor())
+ val bubble =
+ Bubble.createAppBubble(intent, UserHandle(1), icon, directExecutor(), directExecutor())
inflateBubble(bubble)
return bubble
}
@@ -393,6 +862,7 @@ class BubbleStackViewTest {
bubbleStackView,
null,
iconFactory,
+ FakeBubbleAppInfoProvider(),
false
)
@@ -401,6 +871,7 @@ class BubbleStackViewTest {
}
private class FakeBubbleStackViewManager : BubbleStackViewManager {
+ var onImeHidden: Runnable? = null
override fun onAllBubblesAnimatedOut() {}
@@ -408,55 +879,26 @@ class BubbleStackViewTest {
override fun checkNotificationPanelExpandedState(callback: Consumer) {}
- override fun hideCurrentInputMethod() {}
- }
-
- private class TestShellExecutor : ShellExecutor {
-
- override fun execute(runnable: Runnable) {
- runnable.run()
+ override fun hideCurrentInputMethod(onImeHidden: Runnable?) {
+ this.onImeHidden = onImeHidden
}
- override fun executeDelayed(r: Runnable, delayMillis: Long) {
- r.run()
- }
-
- override fun removeCallbacks(r: Runnable?) {}
-
- override fun hasCallback(r: Runnable): Boolean = false
- }
-
- private inner class FakeBubbleTaskViewFactory : BubbleTaskViewFactory {
- override fun create(): BubbleTaskView {
- val taskViewTaskController = mock()
- val taskView = TaskView(context, taskViewTaskController)
- return BubbleTaskView(taskView, shellExecutor)
+ override fun clearImeHiddenRunnable() {
+ this.onImeHidden = null
}
}
- private inner class FakeBubbleExpandedViewManager : BubbleExpandedViewManager {
+ private class FakeBubbleExpandListener : BubbleExpandListener {
+ val bubblesExpandedState = mutableMapOf()
+ override fun onBubbleExpandChanged(isExpanding: Boolean, key: String) {
+ bubblesExpandedState[key] = isExpanding
+ }
+ }
- override val overflowBubbles: List
- get() = emptyList()
-
- override fun setOverflowListener(listener: BubbleData.Listener) {}
-
- override fun collapseStack() {}
-
- override fun updateWindowFlagsForBackpress(intercept: Boolean) {}
-
- override fun promoteBubbleFromOverflow(bubble: Bubble) {}
-
- override fun removeBubble(key: String, reason: Int) {}
-
- override fun dismissBubble(bubble: Bubble, reason: Int) {}
-
- override fun setAppBubbleTaskId(key: String, taskId: Int) {}
-
- override fun isStackExpanded(): Boolean = false
-
- override fun isShowingAsBubbleBar(): Boolean = false
-
- override fun hideCurrentInputMethod() {}
+ private class FakeSurfaceSynchronizer : SurfaceSynchronizer {
+ var isActive = true
+ override fun syncSurfaceAndRun(callback: Runnable) {
+ if (isActive) callback.run()
+ }
}
}
diff --git a/wmshell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt b/wmshell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
index 398fd554f0..18d940e9d5 100644
--- a/wmshell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
+++ b/wmshell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
@@ -16,39 +16,45 @@
package com.android.wm.shell.bubbles
+import android.app.ActivityManager
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.content.ComponentName
import android.content.Context
+import android.platform.test.flag.junit.FlagsParameterization
+import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags.FLAG_EXCLUDE_TASK_FROM_RECENTS
+import com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper
import com.android.wm.shell.taskview.TaskView
-
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors.directExecutor
-import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
+import org.mockito.kotlin.stub
import org.mockito.kotlin.verify
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class BubbleTaskViewTest {
+@RunWith(ParameterizedAndroidJunit4::class)
+class BubbleTaskViewTest(flags: FlagsParameterization) {
+
+ @get:Rule
+ val setFlagsRule = SetFlagsRule(flags)
- private lateinit var bubbleTaskView: BubbleTaskView
private val context = ApplicationProvider.getApplicationContext()
- private lateinit var taskView: TaskView
-
- @Before
- fun setUp() {
- taskView = mock()
- bubbleTaskView = BubbleTaskView(taskView, directExecutor())
- }
+ private val componentName = ComponentName(context, "TestClass")
+ private val taskView = mock()
+ private val bubbleTaskView = BubbleTaskView(taskView, directExecutor())
@Test
fun onTaskCreated_updatesState() {
- val componentName = ComponentName(context, "TestClass")
bubbleTaskView.listener.onTaskCreated(123, componentName)
assertThat(bubbleTaskView.taskId).isEqualTo(123)
@@ -68,25 +74,64 @@ class BubbleTaskViewTest {
}
bubbleTaskView.delegateListener = delegateListener
- val componentName = ComponentName(context, "TestClass")
- bubbleTaskView.listener.onTaskCreated(123, componentName)
+ bubbleTaskView.listener.onTaskCreated(123 /* taskId */, componentName)
assertThat(actualTaskId).isEqualTo(123)
assertThat(actualComponentName).isEqualTo(componentName)
}
@Test
- fun cleanup_invalidTaskId_doesNotRemoveTask() {
+ fun cleanup_invalidTaskId_removesTask() {
bubbleTaskView.cleanup()
- verify(taskView, never()).removeTask()
+ verify(taskView).removeTask()
}
@Test
fun cleanup_validTaskId_removesTask() {
- val componentName = ComponentName(context, "TestClass")
- bubbleTaskView.listener.onTaskCreated(123, componentName)
+ bubbleTaskView.listener.onTaskCreated(123 /* taskId */, componentName)
bubbleTaskView.cleanup()
+
verify(taskView).removeTask()
}
+
+ @Test
+ fun cleanup_noneFullscreenTask_removesTask() {
+ bubbleTaskView.listener.onTaskCreated(123 /* taskId */, componentName)
+
+ bubbleTaskView.cleanup()
+
+ verify(taskView, never()).unregisterTask()
+ verify(taskView).removeTask()
+ }
+
+ @Test
+ fun cleanup_fullscreenTask_removesOrUnregistersTask() {
+ val fullScreenTaskInfo = ActivityManager.RunningTaskInfo().apply {
+ configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ }
+ taskView.stub {
+ on { taskInfo } doReturn fullScreenTaskInfo
+ }
+ bubbleTaskView.listener.onTaskCreated(123 /* taskId */, componentName)
+
+ bubbleTaskView.cleanup()
+
+ if (BubbleAnythingFlagHelper.enableCreateAnyBubbleWithForceExcludedFromRecents()) {
+ verify(taskView).unregisterTask()
+ verify(taskView, never()).removeTask()
+ } else {
+ verify(taskView, never()).unregisterTask()
+ verify(taskView).removeTask()
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams() = FlagsParameterization.allCombinationsOf(
+ FLAG_ENABLE_CREATE_ANY_BUBBLE,
+ FLAG_EXCLUDE_TASK_FROM_RECENTS,
+ )
+ }
}
diff --git a/wmshell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/wmshell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
index ace2c13105..eaa0937376 100644
--- a/wmshell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
+++ b/wmshell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
@@ -27,16 +27,16 @@ import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
-import com.android.internal.protolog.common.ProtoLog
+import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.BubblePositioner
-import com.android.wm.shell.bubbles.DeviceConfig
-import com.android.wm.shell.common.bubbles.BaseBubblePinController
-import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_IN_DURATION
-import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_OUT_DURATION
-import com.android.wm.shell.common.bubbles.BubbleBarLocation
-import com.android.wm.shell.common.bubbles.BubbleBarLocation.LEFT
-import com.android.wm.shell.common.bubbles.BubbleBarLocation.RIGHT
+import com.android.wm.shell.shared.bubbles.BaseBubblePinController
+import com.android.wm.shell.shared.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_IN_DURATION
+import com.android.wm.shell.shared.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_OUT_DURATION
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation.LEFT
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation.RIGHT
+import com.android.wm.shell.shared.bubbles.DeviceConfig
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
@@ -74,6 +74,7 @@ class BubbleExpandedViewPinControllerTest {
@Before
fun setUp() {
ProtoLog.REQUIRE_PROTOLOGTOOL = false
+ ProtoLog.init()
container = FrameLayout(context)
val windowManager = context.getSystemService(WindowManager::class.java)
positioner = BubblePositioner(context, windowManager)
@@ -85,11 +86,10 @@ class BubbleExpandedViewPinControllerTest {
isSmallTablet = false,
isLandscape = true,
isRtl = false,
- insets = Insets.of(10, 20, 30, 40)
+ insets = Insets.of(10, 20, 30, 40),
)
positioner.update(deviceConfig)
- positioner.bubbleBarTopOnScreen =
- SCREEN_HEIGHT - deviceConfig.insets.bottom - BUBBLE_BAR_HEIGHT
+ positioner.updateBubbleBarTopOnScreen(BUBBLE_BAR_HEIGHT)
controller = BubbleExpandedViewPinController(context, container, positioner)
testListener = TestLocationChangeListener()
controller.setListener(testListener)
@@ -407,12 +407,26 @@ class BubbleExpandedViewPinControllerTest {
assertThat(testListener.locationReleases).containsExactly(RIGHT)
}
+ /** Send drag start event when on left */
+ @Test
+ fun start_onLeft_sendStartEventOnLeft() {
+ getInstrumentation().runOnMainSync { controller.onDragStart(initialLocationOnLeft = true) }
+ assertThat(testListener.locationStart).containsExactly(LEFT)
+ }
+
+ /** Send drag start event when on right */
+ @Test
+ fun start_onRight_sendStartEventOnRight() {
+ getInstrumentation().runOnMainSync { controller.onDragStart(initialLocationOnLeft = false) }
+ assertThat(testListener.locationStart).containsExactly(RIGHT)
+ }
+
private fun getExpectedDropTargetBoundsOnLeft(): Rect =
Rect().also {
positioner.getBubbleBarExpandedViewBounds(
true /* onLeft */,
false /* isOverflowExpanded */,
- it
+ it,
)
}
@@ -421,7 +435,7 @@ class BubbleExpandedViewPinControllerTest {
positioner.getBubbleBarExpandedViewBounds(
false /* onLeft */,
false /* isOverflowExpanded */,
- it
+ it,
)
}
@@ -446,8 +460,14 @@ class BubbleExpandedViewPinControllerTest {
}
internal class TestLocationChangeListener : BaseBubblePinController.LocationChangeListener {
+ val locationStart = mutableListOf()
val locationChanges = mutableListOf()
val locationReleases = mutableListOf()
+
+ override fun onStart(location: BubbleBarLocation) {
+ locationStart.add(location)
+ }
+
override fun onChange(location: BubbleBarLocation) {
locationChanges.add(location)
}
diff --git a/wmshell/multivalentTestsForDevice/Android.bp b/wmshell/multivalentTestsForDevice/Android.bp
deleted file mode 100644
index 1ad19c9f30..0000000000
--- a/wmshell/multivalentTestsForDevice/Android.bp
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (C) 2019 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 {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
- default_team: "trendy_team_multitasking_windowing",
-}
-
-android_app {
- name: "WindowManagerShellRobolectric",
- platform_apis: true,
- static_libs: [
- "WindowManager-Shell",
- ],
- manifest: "AndroidManifestRobolectric.xml",
- use_resource_processor: true,
-}
-
-android_robolectric_test {
- name: "WMShellRobolectricTests",
- instrumentation_for: "WindowManagerShellRobolectric",
- upstream: true,
- java_resource_dirs: [
- "robolectric/config",
- ],
- srcs: [
- "src/**/*.kt",
- ],
- // TODO(b/323188766): Include BubbleStackViewTest once the robolectric issue is fixed.
- exclude_srcs: ["src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt"],
- static_libs: [
- "junit",
- "androidx.core_core-animation-testing",
- "androidx.test.runner",
- "androidx.test.rules",
- "androidx.test.ext.junit",
- "mockito-robolectric-prebuilt",
- "mockito-kotlin2",
- "truth",
- ],
- auto_gen_config: true,
-}
-
-android_test {
- name: "WMShellMultivalentTestsOnDevice",
- srcs: [
- "src/**/*.kt",
- ],
- static_libs: [
- "WindowManager-Shell",
- "junit",
- "androidx.core_core-animation-testing",
- "androidx.test.runner",
- "androidx.test.rules",
- "androidx.test.ext.junit",
- "frameworks-base-testutils",
- "mockito-kotlin2",
- "mockito-target-extended-minus-junit4",
- "truth",
- "platform-test-annotations",
- "platform-test-rules",
- ],
- libs: [
- "android.test.base",
- "android.test.runner",
- ],
- jni_libs: [
- "libdexmakerjvmtiagent",
- "libstaticjvmtiagent",
- ],
- kotlincflags: ["-Xjvm-default=all"],
- optimize: {
- enabled: false,
- },
- test_suites: ["device-tests"],
- platform_apis: true,
- certificate: "platform",
- aaptflags: [
- "--extra-packages",
- "com.android.wm.shell",
- ],
- manifest: "AndroidManifest.xml",
-}
diff --git a/wmshell/multivalentTestsForDevice/AndroidManifest.xml b/wmshell/multivalentTestsForDevice/AndroidManifest.xml
deleted file mode 100644
index f8f8338e5f..0000000000
--- a/wmshell/multivalentTestsForDevice/AndroidManifest.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/wmshell/multivalentTestsForDevice/AndroidManifestRobolectric.xml b/wmshell/multivalentTestsForDevice/AndroidManifestRobolectric.xml
deleted file mode 100644
index ffcd7d46fb..0000000000
--- a/wmshell/multivalentTestsForDevice/AndroidManifestRobolectric.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
diff --git a/wmshell/multivalentTestsForDevice/AndroidTest.xml b/wmshell/multivalentTestsForDevice/AndroidTest.xml
deleted file mode 100644
index 36fe8ec337..0000000000
--- a/wmshell/multivalentTestsForDevice/AndroidTest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/wmshell/multivalentTestsForDevice/OWNERS b/wmshell/multivalentTestsForDevice/OWNERS
deleted file mode 100644
index 24c1a3a6d4..0000000000
--- a/wmshell/multivalentTestsForDevice/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-atsjenk@google.com
-liranb@google.com
-madym@google.com
-
diff --git a/wmshell/multivalentTestsForDevice/robolectric/config/robolectric.properties b/wmshell/multivalentTestsForDevice/robolectric/config/robolectric.properties
deleted file mode 100644
index 7a0527ccaa..0000000000
--- a/wmshell/multivalentTestsForDevice/robolectric/config/robolectric.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-sdk=NEWEST_SDK
-
diff --git a/wmshell/multivalentTestsForDevice/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/wmshell/multivalentTestsForDevice/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
deleted file mode 100644
index 9e1440d571..0000000000
--- a/wmshell/multivalentTestsForDevice/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
+++ /dev/null
@@ -1,670 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.wm.shell.bubbles
-
-import android.content.Context
-import android.content.Intent
-import android.content.pm.ShortcutInfo
-import android.content.res.Resources
-import android.graphics.Insets
-import android.graphics.PointF
-import android.graphics.Rect
-import android.os.UserHandle
-import android.view.WindowManager
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.internal.protolog.common.ProtoLog
-import com.android.wm.shell.R
-import com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT
-import com.android.wm.shell.common.bubbles.BubbleBarLocation
-import com.google.common.truth.Truth.assertThat
-import com.google.common.util.concurrent.MoreExecutors.directExecutor
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-/** Tests operations and the resulting state managed by [BubblePositioner]. */
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class BubblePositionerTest {
-
- private lateinit var positioner: BubblePositioner
- private val context = ApplicationProvider.getApplicationContext()
- private val resources: Resources
- get() = context.resources
-
- private val defaultDeviceConfig =
- DeviceConfig(
- windowBounds = Rect(0, 0, 1000, 2000),
- isLargeScreen = false,
- isSmallTablet = false,
- isLandscape = false,
- isRtl = false,
- insets = Insets.of(0, 0, 0, 0)
- )
-
- @Before
- fun setUp() {
- ProtoLog.REQUIRE_PROTOLOGTOOL = false
- val windowManager = context.getSystemService(WindowManager::class.java)
- positioner = BubblePositioner(context, windowManager)
- }
-
- @Test
- fun testUpdate() {
- val insets = Insets.of(10, 20, 5, 15)
- val screenBounds = Rect(0, 0, 1000, 1200)
- val availableRect = Rect(screenBounds)
- availableRect.inset(insets)
- positioner.update(defaultDeviceConfig.copy(insets = insets, windowBounds = screenBounds))
- assertThat(positioner.availableRect).isEqualTo(availableRect)
- assertThat(positioner.isLandscape).isFalse()
- assertThat(positioner.isLargeScreen).isFalse()
- assertThat(positioner.insets).isEqualTo(insets)
- }
-
- @Test
- fun testShowBubblesVertically_phonePortrait() {
- positioner.update(defaultDeviceConfig)
- assertThat(positioner.showBubblesVertically()).isFalse()
- }
-
- @Test
- fun testShowBubblesVertically_phoneLandscape() {
- positioner.update(defaultDeviceConfig.copy(isLandscape = true))
- assertThat(positioner.isLandscape).isTrue()
- assertThat(positioner.showBubblesVertically()).isTrue()
- }
-
- @Test
- fun testShowBubblesVertically_tablet() {
- positioner.update(defaultDeviceConfig.copy(isLargeScreen = true))
- assertThat(positioner.showBubblesVertically()).isTrue()
- }
-
- /** If a resting position hasn't been set, calling it will return the default position. */
- @Test
- fun testGetRestingPosition_returnsDefaultPosition() {
- positioner.update(defaultDeviceConfig)
- val restingPosition = positioner.getRestingPosition()
- val defaultPosition = positioner.defaultStartPosition
- assertThat(restingPosition).isEqualTo(defaultPosition)
- }
-
- /** If a resting position has been set, it'll return that instead of the default position. */
- @Test
- fun testGetRestingPosition_returnsRestingPosition() {
- positioner.update(defaultDeviceConfig)
- val restingPosition = PointF(100f, 100f)
- positioner.restingPosition = restingPosition
- assertThat(positioner.getRestingPosition()).isEqualTo(restingPosition)
- }
-
- /** Test that the default resting position on phone is in upper left. */
- @Test
- fun testGetRestingPosition_bubble_onPhone() {
- positioner.update(defaultDeviceConfig)
- val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
- val restingPosition = positioner.getRestingPosition()
- assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left)
- assertThat(restingPosition.y).isEqualTo(defaultYPosition)
- }
-
- @Test
- fun testGetRestingPosition_bubble_onPhone_RTL() {
- positioner.update(defaultDeviceConfig.copy(isRtl = true))
- val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
- val restingPosition = positioner.getRestingPosition()
- assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right)
- assertThat(restingPosition.y).isEqualTo(defaultYPosition)
- }
-
- /** Test that the default resting position on tablet is middle left. */
- @Test
- fun testGetRestingPosition_chatBubble_onTablet() {
- positioner.update(defaultDeviceConfig.copy(isLargeScreen = true))
- val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
- val restingPosition = positioner.getRestingPosition()
- assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left)
- assertThat(restingPosition.y).isEqualTo(defaultYPosition)
- }
-
- @Test
- fun testGetRestingPosition_chatBubble_onTablet_RTL() {
- positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
- val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
- val restingPosition = positioner.getRestingPosition()
- assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right)
- assertThat(restingPosition.y).isEqualTo(defaultYPosition)
- }
-
- /** Test that the default resting position on tablet is middle right. */
- @Test
- fun testGetDefaultPosition_appBubble_onTablet() {
- positioner.update(defaultDeviceConfig.copy(isLargeScreen = true))
- val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
- val startPosition = positioner.getDefaultStartPosition(true /* isAppBubble */)
- assertThat(startPosition.x).isEqualTo(allowableStackRegion.right)
- assertThat(startPosition.y).isEqualTo(defaultYPosition)
- }
-
- @Test
- fun testGetRestingPosition_appBubble_onTablet_RTL() {
- positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
- val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
- val startPosition = positioner.getDefaultStartPosition(true /* isAppBubble */)
- assertThat(startPosition.x).isEqualTo(allowableStackRegion.left)
- assertThat(startPosition.y).isEqualTo(defaultYPosition)
- }
-
- @Test
- fun testGetRestingPosition_afterBoundsChange() {
- positioner.update(
- defaultDeviceConfig.copy(isLargeScreen = true, windowBounds = Rect(0, 0, 2000, 1600))
- )
-
- // Set the resting position to the right side
- var allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
- val restingPosition = PointF(allowableStackRegion.right, allowableStackRegion.centerY())
- positioner.restingPosition = restingPosition
-
- // Now make the device smaller
- positioner.update(
- defaultDeviceConfig.copy(isLargeScreen = false, windowBounds = Rect(0, 0, 1000, 1600))
- )
-
- // Check the resting position is on the correct side
- allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
- assertThat(positioner.restingPosition.x).isEqualTo(allowableStackRegion.right)
- }
-
- @Test
- fun testHasUserModifiedDefaultPosition_false() {
- positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
- assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse()
- positioner.restingPosition = positioner.defaultStartPosition
- assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse()
- }
-
- @Test
- fun testHasUserModifiedDefaultPosition_true() {
- positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
- assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse()
- positioner.restingPosition = PointF(0f, 100f)
- assertThat(positioner.hasUserModifiedDefaultPosition()).isTrue()
- }
-
- @Test
- fun testBubbleBarExpandedViewHeightAndWidth() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- // portrait orientation
- isLandscape = false,
- isLargeScreen = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
-
- positioner.setShowingInBubbleBar(true)
- positioner.update(deviceConfig)
- positioner.bubbleBarTopOnScreen = 2500
-
- val spaceBetweenTopInsetAndBubbleBarInLandscape = 1680
- val expandedViewVerticalSpacing =
- resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding)
- val expectedHeight =
- spaceBetweenTopInsetAndBubbleBarInLandscape - 2 * expandedViewVerticalSpacing
- val expectedWidth = resources.getDimensionPixelSize(R.dimen.bubble_bar_expanded_view_width)
-
- assertThat(positioner.getExpandedViewWidthForBubbleBar(false)).isEqualTo(expectedWidth)
- assertThat(positioner.getExpandedViewHeightForBubbleBar(false)).isEqualTo(expectedHeight)
- }
-
- @Test
- fun testBubbleBarExpandedViewHeightAndWidth_screenWidthTooSmall() {
- val screenWidth = 300
- val deviceConfig =
- defaultDeviceConfig.copy(
- // portrait orientation
- isLandscape = false,
- isLargeScreen = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, screenWidth, 2600)
- )
- positioner.setShowingInBubbleBar(true)
- positioner.update(deviceConfig)
- positioner.bubbleBarTopOnScreen = 2500
-
- val spaceBetweenTopInsetAndBubbleBarInLandscape = 180
- val expandedViewSpacing =
- resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding)
- val expectedHeight = spaceBetweenTopInsetAndBubbleBarInLandscape - 2 * expandedViewSpacing
- val expectedWidth = screenWidth - 15 /* horizontal insets */ - 2 * expandedViewSpacing
- assertThat(positioner.getExpandedViewWidthForBubbleBar(false)).isEqualTo(expectedWidth)
- assertThat(positioner.getExpandedViewHeightForBubbleBar(false)).isEqualTo(expectedHeight)
- }
-
- @Test
- fun testGetExpandedViewHeight_max() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isLargeScreen = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
- val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
-
- assertThat(positioner.getExpandedViewHeight(bubble)).isEqualTo(MAX_HEIGHT)
- }
-
- @Test
- fun testGetExpandedViewHeight_customHeight_valid() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isLargeScreen = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
- val minHeight =
- context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_default_height)
- val bubble =
- Bubble(
- "key",
- ShortcutInfo.Builder(context, "id").build(),
- minHeight + 100 /* desiredHeight */,
- 0 /* desiredHeightResId */,
- "title",
- 0 /* taskId */,
- null /* locus */,
- true /* isDismissable */,
- directExecutor()
- ) {}
-
- // Ensure the height is the same as the desired value
- assertThat(positioner.getExpandedViewHeight(bubble))
- .isEqualTo(bubble.getDesiredHeight(context))
- }
-
- @Test
- fun testGetExpandedViewHeight_customHeight_tooSmall() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isLargeScreen = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- val bubble =
- Bubble(
- "key",
- ShortcutInfo.Builder(context, "id").build(),
- 10 /* desiredHeight */,
- 0 /* desiredHeightResId */,
- "title",
- 0 /* taskId */,
- null /* locus */,
- true /* isDismissable */,
- directExecutor()
- ) {}
-
- // Ensure the height is the same as the desired value
- val minHeight =
- context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_default_height)
- assertThat(positioner.getExpandedViewHeight(bubble)).isEqualTo(minHeight)
- }
-
- @Test
- fun testGetMaxExpandedViewHeight_onLargeTablet() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isLargeScreen = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- val manageButtonHeight =
- context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_height)
- val pointerWidth = context.resources.getDimensionPixelSize(R.dimen.bubble_pointer_width)
- val expandedViewPadding =
- context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding)
- val expectedHeight =
- 1800 - 2 * 20 - manageButtonHeight - pointerWidth - expandedViewPadding * 2
- assertThat(positioner.getMaxExpandedViewHeight(false /* isOverflow */))
- .isEqualTo(expectedHeight)
- }
-
- @Test
- fun testAreBubblesBottomAligned_largeScreen_true() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isLargeScreen = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- assertThat(positioner.areBubblesBottomAligned()).isTrue()
- }
-
- @Test
- fun testAreBubblesBottomAligned_largeScreen_landscape_false() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isLargeScreen = true,
- isLandscape = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- assertThat(positioner.areBubblesBottomAligned()).isFalse()
- }
-
- @Test
- fun testAreBubblesBottomAligned_smallTablet_false() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isLargeScreen = true,
- isSmallTablet = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- assertThat(positioner.areBubblesBottomAligned()).isFalse()
- }
-
- @Test
- fun testAreBubblesBottomAligned_phone_false() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- assertThat(positioner.areBubblesBottomAligned()).isFalse()
- }
-
- @Test
- fun testExpandedViewY_phoneLandscape() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isLandscape = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
-
- // This bubble will have max height so it'll always be top aligned
- assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(positioner.getExpandedViewYTopAligned())
- }
-
- @Test
- fun testExpandedViewY_phonePortrait() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
-
- // Always top aligned in phone portrait
- assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(positioner.getExpandedViewYTopAligned())
- }
-
- @Test
- fun testExpandedViewY_smallTabletLandscape() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isSmallTablet = true,
- isLandscape = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
-
- // This bubble will have max height which is always top aligned on small tablets
- assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(positioner.getExpandedViewYTopAligned())
- }
-
- @Test
- fun testExpandedViewY_smallTabletPortrait() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isSmallTablet = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
-
- // This bubble will have max height which is always top aligned on small tablets
- assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(positioner.getExpandedViewYTopAligned())
- }
-
- @Test
- fun testExpandedViewY_largeScreenLandscape() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isLargeScreen = true,
- isLandscape = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
-
- // This bubble will have max height which is always top aligned on landscape, large tablet
- assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(positioner.getExpandedViewYTopAligned())
- }
-
- @Test
- fun testExpandedViewY_largeScreenPortrait() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isLargeScreen = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
-
- val manageButtonHeight =
- context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_height)
- val manageButtonPlusMargin =
- manageButtonHeight +
- 2 * context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_margin)
- val pointerWidth = context.resources.getDimensionPixelSize(R.dimen.bubble_pointer_width)
-
- val expectedExpandedViewY =
- positioner.availableRect.bottom -
- manageButtonPlusMargin -
- positioner.getExpandedViewHeightForLargeScreen() -
- pointerWidth
-
- // Bubbles are bottom aligned on portrait, large tablet
- assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(expectedExpandedViewY)
- }
-
- @Test
- fun testGetTaskViewContentWidth_onLeft() {
- positioner.update(defaultDeviceConfig.copy(insets = Insets.of(100, 0, 200, 0)))
- val taskViewWidth = positioner.getTaskViewContentWidth(true /* onLeft */)
- val paddings =
- positioner.getExpandedViewContainerPadding(true /* onLeft */, false /* isOverflow */)
- assertThat(taskViewWidth)
- .isEqualTo(positioner.screenRect.width() - paddings[0] - paddings[2])
- }
-
- @Test
- fun testGetTaskViewContentWidth_onRight() {
- positioner.update(defaultDeviceConfig.copy(insets = Insets.of(100, 0, 200, 0)))
- val taskViewWidth = positioner.getTaskViewContentWidth(false /* onLeft */)
- val paddings =
- positioner.getExpandedViewContainerPadding(false /* onLeft */, false /* isOverflow */)
- assertThat(taskViewWidth)
- .isEqualTo(positioner.screenRect.width() - paddings[0] - paddings[2])
- }
-
- @Test
- fun testIsBubbleBarOnLeft_defaultsToRight() {
- positioner.bubbleBarLocation = BubbleBarLocation.DEFAULT
- assertThat(positioner.isBubbleBarOnLeft).isFalse()
-
- // Check that left and right return expected position
- positioner.bubbleBarLocation = BubbleBarLocation.LEFT
- assertThat(positioner.isBubbleBarOnLeft).isTrue()
- positioner.bubbleBarLocation = BubbleBarLocation.RIGHT
- assertThat(positioner.isBubbleBarOnLeft).isFalse()
- }
-
- @Test
- fun testIsBubbleBarOnLeft_rtlEnabled_defaultsToLeft() {
- positioner.update(defaultDeviceConfig.copy(isRtl = true))
-
- positioner.bubbleBarLocation = BubbleBarLocation.DEFAULT
- assertThat(positioner.isBubbleBarOnLeft).isTrue()
-
- // Check that left and right return expected position
- positioner.bubbleBarLocation = BubbleBarLocation.LEFT
- assertThat(positioner.isBubbleBarOnLeft).isTrue()
- positioner.bubbleBarLocation = BubbleBarLocation.RIGHT
- assertThat(positioner.isBubbleBarOnLeft).isFalse()
- }
-
- @Test
- fun testGetBubbleBarExpandedViewBounds_onLeft() {
- testGetBubbleBarExpandedViewBounds(onLeft = true, isOverflow = false)
- }
-
- @Test
- fun testGetBubbleBarExpandedViewBounds_onRight() {
- testGetBubbleBarExpandedViewBounds(onLeft = false, isOverflow = false)
- }
-
- @Test
- fun testGetBubbleBarExpandedViewBounds_isOverflow_onLeft() {
- testGetBubbleBarExpandedViewBounds(onLeft = true, isOverflow = true)
- }
-
- @Test
- fun testGetBubbleBarExpandedViewBounds_isOverflow_onRight() {
- testGetBubbleBarExpandedViewBounds(onLeft = false, isOverflow = true)
- }
-
- private fun testGetBubbleBarExpandedViewBounds(onLeft: Boolean, isOverflow: Boolean) {
- positioner.setShowingInBubbleBar(true)
- val windowBounds = Rect(0, 0, 2000, 2600)
- val insets = Insets.of(10, 20, 5, 15)
- val deviceConfig =
- defaultDeviceConfig.copy(
- isLargeScreen = true,
- isLandscape = true,
- insets = insets,
- windowBounds = windowBounds
- )
- positioner.update(deviceConfig)
-
- val bubbleBarHeight = 100
- positioner.bubbleBarTopOnScreen = windowBounds.bottom - insets.bottom - bubbleBarHeight
-
- val expandedViewPadding =
- context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding)
-
- val left: Int
- val right: Int
- if (onLeft) {
- // Pin to the left, calculate right
- left = deviceConfig.insets.left + expandedViewPadding
- right = left + positioner.getExpandedViewWidthForBubbleBar(isOverflow)
- } else {
- // Pin to the right, calculate left
- right =
- deviceConfig.windowBounds.right - deviceConfig.insets.right - expandedViewPadding
- left = right - positioner.getExpandedViewWidthForBubbleBar(isOverflow)
- }
- // Above the bubble bar
- val bottom = positioner.bubbleBarTopOnScreen - expandedViewPadding
- // Calculate right and top based on size
- val top = bottom - positioner.getExpandedViewHeightForBubbleBar(isOverflow)
- val expectedBounds = Rect(left, top, right, bottom)
-
- val bounds = Rect()
- positioner.getBubbleBarExpandedViewBounds(onLeft, isOverflow, bounds)
-
- assertThat(bounds).isEqualTo(expectedBounds)
- }
-
- private val defaultYPosition: Float
- /**
- * Calculates the Y position bubbles should be placed based on the config. Based on the
- * calculations in [BubblePositioner.getDefaultStartPosition] and
- * [BubbleStackView.RelativeStackPosition].
- */
- get() {
- val isTablet = positioner.isLargeScreen
-
- // On tablet the position is centered, on phone it is an offset from the top.
- val desiredY =
- if (isTablet) {
- positioner.screenRect.height() / 2f - positioner.bubbleSize / 2f
- } else {
- context.resources
- .getDimensionPixelOffset(R.dimen.bubble_stack_starting_offset_y)
- .toFloat()
- }
- // Since we're visually centering the bubbles on tablet, use total screen height rather
- // than the available height.
- val height =
- if (isTablet) {
- positioner.screenRect.height()
- } else {
- positioner.availableRect.height()
- }
- val offsetPercent = (desiredY / height).coerceIn(0f, 1f)
- val allowableStackRegion =
- positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
- return allowableStackRegion.top + allowableStackRegion.height() * offsetPercent
- }
-}
diff --git a/wmshell/multivalentTestsForDevice/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/wmshell/multivalentTestsForDevice/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
deleted file mode 100644
index 327e205955..0000000000
--- a/wmshell/multivalentTestsForDevice/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
- * 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.bubbles
-
-import android.content.Context
-import android.content.Intent
-import android.content.pm.ShortcutInfo
-import android.content.res.Resources
-import android.graphics.Color
-import android.graphics.drawable.Icon
-import android.os.UserHandle
-import android.platform.test.flag.junit.SetFlagsRule
-import android.view.IWindowManager
-import android.view.WindowManager
-import android.view.WindowManagerGlobal
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.internal.protolog.common.ProtoLog
-import com.android.launcher3.icons.BubbleIconFactory
-import com.android.wm.shell.Flags
-import com.android.wm.shell.R
-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.ShellExecutor
-import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
-import com.android.wm.shell.taskview.TaskView
-import com.android.wm.shell.taskview.TaskViewTaskController
-import com.google.common.truth.Truth.assertThat
-import com.google.common.util.concurrent.MoreExecutors.directExecutor
-import org.junit.After
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.kotlin.mock
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
-import java.util.concurrent.Semaphore
-import java.util.concurrent.TimeUnit
-import java.util.function.Consumer
-
-/** Unit tests for [BubbleStackView]. */
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class BubbleStackViewTest {
-
- @get:Rule val setFlagsRule = SetFlagsRule()
-
- private val context = ApplicationProvider.getApplicationContext()
- private lateinit var positioner: BubblePositioner
- private lateinit var iconFactory: BubbleIconFactory
- private lateinit var expandedViewManager: FakeBubbleExpandedViewManager
- private lateinit var bubbleStackView: BubbleStackView
- private lateinit var shellExecutor: ShellExecutor
- private lateinit var windowManager: IWindowManager
- private lateinit var bubbleTaskViewFactory: BubbleTaskViewFactory
- private lateinit var bubbleData: BubbleData
- private lateinit var bubbleStackViewManager: FakeBubbleStackViewManager
- private var sysuiProxy = mock()
-
- @Before
- fun setUp() {
- PhysicsAnimatorTestUtils.prepareForTest()
- // Disable protolog tool when running the tests from studio
- ProtoLog.REQUIRE_PROTOLOGTOOL = false
- windowManager = WindowManagerGlobal.getWindowManagerService()!!
- shellExecutor = TestShellExecutor()
- val windowManager = context.getSystemService(WindowManager::class.java)
- iconFactory =
- BubbleIconFactory(
- context,
- context.resources.getDimensionPixelSize(R.dimen.bubble_size),
- context.resources.getDimensionPixelSize(R.dimen.bubble_badge_size),
- Color.BLACK,
- context.resources.getDimensionPixelSize(
- com.android.internal.R.dimen.importance_ring_stroke_width
- )
- )
- positioner = BubblePositioner(context, windowManager)
- bubbleData =
- BubbleData(
- context,
- BubbleLogger(UiEventLoggerFake()),
- positioner,
- BubbleEducationController(context),
- shellExecutor
- )
- bubbleStackViewManager = FakeBubbleStackViewManager()
- expandedViewManager = FakeBubbleExpandedViewManager()
- bubbleTaskViewFactory = FakeBubbleTaskViewFactory()
- bubbleStackView =
- BubbleStackView(
- context,
- bubbleStackViewManager,
- positioner,
- bubbleData,
- null,
- FloatingContentCoordinator(),
- { sysuiProxy },
- shellExecutor
- )
-
- context
- .getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
- .edit()
- .putBoolean(StackEducationView.PREF_STACK_EDUCATION, true)
- .apply()
- }
-
- @After
- fun tearDown() {
- PhysicsAnimatorTestUtils.tearDown()
- }
-
- @Test
- fun addBubble() {
- val bubble = createAndInflateBubble()
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- bubbleStackView.addBubble(bubble)
- }
- InstrumentationRegistry.getInstrumentation().waitForIdleSync()
- assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
- }
-
- @Test
- fun tapBubbleToExpand() {
- val bubble = createAndInflateBubble()
-
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- bubbleStackView.addBubble(bubble)
- }
-
- InstrumentationRegistry.getInstrumentation().waitForIdleSync()
- assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
- var lastUpdate: BubbleData.Update? = null
- val semaphore = Semaphore(0)
- val listener =
- BubbleData.Listener { update ->
- lastUpdate = update
- semaphore.release()
- }
- bubbleData.setListener(listener)
-
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- bubble.iconView!!.performClick()
- // we're checking the expanded state in BubbleData because that's the source of truth.
- // This will eventually propagate an update back to the stack view, but setting the
- // entire pipeline is outside the scope of a unit test.
- assertThat(bubbleData.isExpanded).isTrue()
- }
-
- assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
- assertThat(lastUpdate).isNotNull()
- assertThat(lastUpdate!!.expandedChanged).isTrue()
- assertThat(lastUpdate!!.expanded).isTrue()
- }
-
- @Test
- fun tapDifferentBubble_shouldReorder() {
- val bubble1 = createAndInflateChatBubble(key = "bubble1")
- val bubble2 = createAndInflateChatBubble(key = "bubble2")
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- bubbleStackView.addBubble(bubble1)
- bubbleStackView.addBubble(bubble2)
- }
- InstrumentationRegistry.getInstrumentation().waitForIdleSync()
-
- assertThat(bubbleStackView.bubbleCount).isEqualTo(2)
- assertThat(bubbleData.bubbles).hasSize(2)
- assertThat(bubbleData.selectedBubble).isEqualTo(bubble2)
- assertThat(bubble2.iconView).isNotNull()
-
- var lastUpdate: BubbleData.Update? = null
- val semaphore = Semaphore(0)
- val listener =
- BubbleData.Listener { update ->
- lastUpdate = update
- semaphore.release()
- }
- bubbleData.setListener(listener)
-
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- bubble2.iconView!!.performClick()
- assertThat(bubbleData.isExpanded).isTrue()
-
- bubbleStackView.setSelectedBubble(bubble2)
- bubbleStackView.isExpanded = true
- }
-
- assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
- assertThat(lastUpdate!!.expanded).isTrue()
- assertThat(lastUpdate!!.bubbles.map { it.key })
- .containsExactly("bubble2", "bubble1")
- .inOrder()
-
- // wait for idle to allow the animation to start
- InstrumentationRegistry.getInstrumentation().waitForIdleSync()
- // wait for the expansion animation to complete before interacting with the bubbles
- PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
- AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y)
-
- // tap on bubble1 to select it
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- bubble1.iconView!!.performClick()
- }
- assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
- assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
-
- // tap on bubble1 again to collapse the stack
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- // we have to set the selected bubble in the stack view manually because we don't have a
- // listener wired up.
- bubbleStackView.setSelectedBubble(bubble1)
- bubble1.iconView!!.performClick()
- }
-
- assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
- assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
- assertThat(bubbleData.isExpanded).isFalse()
- assertThat(lastUpdate!!.orderChanged).isTrue()
- assertThat(lastUpdate!!.bubbles.map { it.key })
- .containsExactly("bubble1", "bubble2")
- .inOrder()
- }
-
- @EnableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
- @Test
- fun testCreateStackView_noOverflowContents_noOverflow() {
- bubbleStackView =
- BubbleStackView(
- context,
- bubbleStackViewManager,
- positioner,
- bubbleData,
- null,
- FloatingContentCoordinator(),
- { sysuiProxy },
- shellExecutor
- )
-
- assertThat(bubbleData.overflowBubbles).isEmpty()
- val bubbleOverflow = bubbleData.overflow
- // Overflow shouldn't be attached
- assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isEqualTo(-1)
- }
-
- @EnableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
- @Test
- fun testCreateStackView_hasOverflowContents_hasOverflow() {
- // Add a bubble to the overflow
- val bubble1 = createAndInflateChatBubble(key = "bubble1")
- bubbleData.notificationEntryUpdated(bubble1, false, false)
- bubbleData.dismissBubbleWithKey(bubble1.key, Bubbles.DISMISS_USER_GESTURE)
- assertThat(bubbleData.overflowBubbles).isNotEmpty()
-
- bubbleStackView =
- BubbleStackView(
- context,
- bubbleStackViewManager,
- positioner,
- bubbleData,
- null,
- FloatingContentCoordinator(),
- { sysuiProxy },
- shellExecutor
- )
- val bubbleOverflow = bubbleData.overflow
- assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isGreaterThan(-1)
- }
-
- @DisableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
- @Test
- fun testCreateStackView_noOverflowContents_hasOverflow() {
- bubbleStackView =
- BubbleStackView(
- context,
- bubbleStackViewManager,
- positioner,
- bubbleData,
- null,
- FloatingContentCoordinator(),
- { sysuiProxy },
- shellExecutor
- )
-
- assertThat(bubbleData.overflowBubbles).isEmpty()
- val bubbleOverflow = bubbleData.overflow
- assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isGreaterThan(-1)
- }
-
- @EnableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
- @Test
- fun showOverflow_true() {
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- bubbleStackView.showOverflow(true)
- }
- InstrumentationRegistry.getInstrumentation().waitForIdleSync()
-
- val bubbleOverflow = bubbleData.overflow
- assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isGreaterThan(-1)
- }
-
- @EnableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
- @Test
- fun showOverflow_false() {
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- bubbleStackView.showOverflow(true)
- }
- InstrumentationRegistry.getInstrumentation().waitForIdleSync()
- val bubbleOverflow = bubbleData.overflow
- assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isGreaterThan(-1)
-
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- bubbleStackView.showOverflow(false)
- }
- InstrumentationRegistry.getInstrumentation().waitForIdleSync()
-
- // The overflow should've been removed
- assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isEqualTo(-1)
- }
-
- @DisableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
- @Test
- fun showOverflow_ignored() {
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- bubbleStackView.showOverflow(false)
- }
- InstrumentationRegistry.getInstrumentation().waitForIdleSync()
-
- // showOverflow should've been ignored, so the overflow would be attached
- val bubbleOverflow = bubbleData.overflow
- assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isGreaterThan(-1)
- }
-
- private fun createAndInflateChatBubble(key: String): Bubble {
- val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button)
- val shortcutInfo = ShortcutInfo.Builder(context, "fakeId").setIcon(icon).build()
- val bubble =
- Bubble(
- key,
- shortcutInfo,
- /* desiredHeight= */ 6,
- Resources.ID_NULL,
- "title",
- /* taskId= */ 0,
- "locus",
- /* isDismissable= */ true,
- directExecutor()
- ) {}
- inflateBubble(bubble)
- return bubble
- }
-
- private fun createAndInflateBubble(): Bubble {
- val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
- val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), icon, directExecutor())
- inflateBubble(bubble)
- return bubble
- }
-
- private fun inflateBubble(bubble: Bubble) {
- bubble.setInflateSynchronously(true)
- bubbleData.notificationEntryUpdated(bubble, true, false)
-
- val semaphore = Semaphore(0)
- val callback: BubbleViewInfoTask.Callback =
- BubbleViewInfoTask.Callback { semaphore.release() }
- bubble.inflate(
- callback,
- context,
- expandedViewManager,
- bubbleTaskViewFactory,
- positioner,
- bubbleStackView,
- null,
- iconFactory,
- false
- )
-
- assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
- assertThat(bubble.isInflated).isTrue()
- }
-
- private class FakeBubbleStackViewManager : BubbleStackViewManager {
-
- override fun onAllBubblesAnimatedOut() {}
-
- override fun updateWindowFlagsForBackpress(interceptBack: Boolean) {}
-
- override fun checkNotificationPanelExpandedState(callback: Consumer) {}
-
- override fun hideCurrentInputMethod() {}
- }
-
- private class TestShellExecutor : ShellExecutor {
-
- override fun execute(runnable: Runnable) {
- runnable.run()
- }
-
- override fun executeDelayed(r: Runnable, delayMillis: Long) {
- r.run()
- }
-
- override fun removeCallbacks(r: Runnable?) {}
-
- override fun hasCallback(r: Runnable): Boolean = false
- }
-
- private inner class FakeBubbleTaskViewFactory : BubbleTaskViewFactory {
- override fun create(): BubbleTaskView {
- val taskViewTaskController = mock()
- val taskView = TaskView(context, taskViewTaskController)
- return BubbleTaskView(taskView, shellExecutor)
- }
- }
-
- private inner class FakeBubbleExpandedViewManager : BubbleExpandedViewManager {
-
- override val overflowBubbles: List
- get() = emptyList()
-
- override fun setOverflowListener(listener: BubbleData.Listener) {}
-
- override fun collapseStack() {}
-
- override fun updateWindowFlagsForBackpress(intercept: Boolean) {}
-
- override fun promoteBubbleFromOverflow(bubble: Bubble) {}
-
- override fun removeBubble(key: String, reason: Int) {}
-
- override fun dismissBubble(bubble: Bubble, reason: Int) {}
-
- override fun setAppBubbleTaskId(key: String, taskId: Int) {}
-
- override fun isStackExpanded(): Boolean = false
-
- override fun isShowingAsBubbleBar(): Boolean = false
-
- override fun hideCurrentInputMethod() {}
- }
-}
diff --git a/wmshell/multivalentTestsForDevice/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt b/wmshell/multivalentTestsForDevice/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
deleted file mode 100644
index 398fd554f0..0000000000
--- a/wmshell/multivalentTestsForDevice/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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.bubbles
-
-import android.content.ComponentName
-import android.content.Context
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.wm.shell.taskview.TaskView
-
-import com.google.common.truth.Truth.assertThat
-import com.google.common.util.concurrent.MoreExecutors.directExecutor
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.never
-import org.mockito.kotlin.verify
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class BubbleTaskViewTest {
-
- private lateinit var bubbleTaskView: BubbleTaskView
- private val context = ApplicationProvider.getApplicationContext()
- private lateinit var taskView: TaskView
-
- @Before
- fun setUp() {
- taskView = mock()
- bubbleTaskView = BubbleTaskView(taskView, directExecutor())
- }
-
- @Test
- fun onTaskCreated_updatesState() {
- val componentName = ComponentName(context, "TestClass")
- bubbleTaskView.listener.onTaskCreated(123, componentName)
-
- assertThat(bubbleTaskView.taskId).isEqualTo(123)
- assertThat(bubbleTaskView.componentName).isEqualTo(componentName)
- assertThat(bubbleTaskView.isCreated).isTrue()
- }
-
- @Test
- fun onTaskCreated_callsDelegateListener() {
- var actualTaskId = -1
- var actualComponentName: ComponentName? = null
- val delegateListener = object : TaskView.Listener {
- override fun onTaskCreated(taskId: Int, name: ComponentName) {
- actualTaskId = taskId
- actualComponentName = name
- }
- }
- bubbleTaskView.delegateListener = delegateListener
-
- val componentName = ComponentName(context, "TestClass")
- bubbleTaskView.listener.onTaskCreated(123, componentName)
-
- assertThat(actualTaskId).isEqualTo(123)
- assertThat(actualComponentName).isEqualTo(componentName)
- }
-
- @Test
- fun cleanup_invalidTaskId_doesNotRemoveTask() {
- bubbleTaskView.cleanup()
- verify(taskView, never()).removeTask()
- }
-
- @Test
- fun cleanup_validTaskId_removesTask() {
- val componentName = ComponentName(context, "TestClass")
- bubbleTaskView.listener.onTaskCreated(123, componentName)
-
- bubbleTaskView.cleanup()
- verify(taskView).removeTask()
- }
-}
diff --git a/wmshell/multivalentTestsForDevice/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/wmshell/multivalentTestsForDevice/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
deleted file mode 100644
index ace2c13105..0000000000
--- a/wmshell/multivalentTestsForDevice/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
+++ /dev/null
@@ -1,459 +0,0 @@
-/*
- * 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.bubbles.bar
-
-import android.content.Context
-import android.graphics.Insets
-import android.graphics.PointF
-import android.graphics.Rect
-import android.view.View
-import android.view.WindowManager
-import android.widget.FrameLayout
-import androidx.core.animation.AnimatorTestRule
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
-import com.android.internal.protolog.common.ProtoLog
-import com.android.wm.shell.R
-import com.android.wm.shell.bubbles.BubblePositioner
-import com.android.wm.shell.bubbles.DeviceConfig
-import com.android.wm.shell.common.bubbles.BaseBubblePinController
-import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_IN_DURATION
-import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_OUT_DURATION
-import com.android.wm.shell.common.bubbles.BubbleBarLocation
-import com.android.wm.shell.common.bubbles.BubbleBarLocation.LEFT
-import com.android.wm.shell.common.bubbles.BubbleBarLocation.RIGHT
-import com.google.common.truth.Truth.assertThat
-import org.junit.After
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-/** Tests for [BubbleExpandedViewPinController] */
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class BubbleExpandedViewPinControllerTest {
-
- companion object {
- @JvmField @ClassRule val animatorTestRule: AnimatorTestRule = AnimatorTestRule()
-
- const val SCREEN_WIDTH = 2000
- const val SCREEN_HEIGHT = 1000
-
- const val BUBBLE_BAR_HEIGHT = 50
- }
-
- private val context = ApplicationProvider.getApplicationContext()
- private lateinit var positioner: BubblePositioner
- private lateinit var container: FrameLayout
-
- private lateinit var controller: BubbleExpandedViewPinController
- private lateinit var testListener: TestLocationChangeListener
-
- private val dropTargetView: View?
- get() = container.findViewById(R.id.bubble_bar_drop_target)
-
- private val pointOnLeft = PointF(100f, 100f)
- private val pointOnRight = PointF(1900f, 500f)
-
- @Before
- fun setUp() {
- ProtoLog.REQUIRE_PROTOLOGTOOL = false
- container = FrameLayout(context)
- val windowManager = context.getSystemService(WindowManager::class.java)
- positioner = BubblePositioner(context, windowManager)
- positioner.setShowingInBubbleBar(true)
- val deviceConfig =
- DeviceConfig(
- windowBounds = Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT),
- isLargeScreen = true,
- isSmallTablet = false,
- isLandscape = true,
- isRtl = false,
- insets = Insets.of(10, 20, 30, 40)
- )
- positioner.update(deviceConfig)
- positioner.bubbleBarTopOnScreen =
- SCREEN_HEIGHT - deviceConfig.insets.bottom - BUBBLE_BAR_HEIGHT
- controller = BubbleExpandedViewPinController(context, container, positioner)
- testListener = TestLocationChangeListener()
- controller.setListener(testListener)
- }
-
- @After
- fun tearDown() {
- getInstrumentation().runOnMainSync { controller.onDragEnd() }
- waitForAnimateOut()
- }
-
- /** Dragging on same side should not show drop target or trigger location changes */
- @Test
- fun drag_stayOnRightSide() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = false)
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- controller.onDragEnd()
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNull()
- assertThat(testListener.locationChanges).isEmpty()
- assertThat(testListener.locationReleases).containsExactly(RIGHT)
- }
-
- /** Dragging on same side should not show drop target or trigger location changes */
- @Test
- fun drag_stayOnLeftSide() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = true)
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- controller.onDragEnd()
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNull()
- assertThat(testListener.locationChanges).isEmpty()
- assertThat(testListener.locationReleases).containsExactly(LEFT)
- }
-
- /** Drag crosses to the other side. Show drop target and trigger a location change. */
- @Test
- fun drag_rightToLeft() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = false)
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- }
- waitForAnimateIn()
-
- assertThat(dropTargetView).isNotNull()
- assertThat(dropTargetView!!.alpha).isEqualTo(1f)
- assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnLeft())
- assertThat(testListener.locationChanges).containsExactly(LEFT)
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /** Drag crosses to the other side. Show drop target and trigger a location change. */
- @Test
- fun drag_leftToRight() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = true)
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- }
- waitForAnimateIn()
-
- assertThat(dropTargetView).isNotNull()
- assertThat(dropTargetView!!.alpha).isEqualTo(1f)
- assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnRight())
- assertThat(testListener.locationChanges).containsExactly(RIGHT)
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /**
- * Drop target does not initially show on the side that the drag starts. Check that it shows up
- * after the dragging the view to other side and back to the initial side.
- */
- @Test
- fun drag_rightToLeftToRight() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = false)
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNull()
-
- getInstrumentation().runOnMainSync { controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) }
- waitForAnimateIn()
- assertThat(dropTargetView).isNotNull()
-
- getInstrumentation().runOnMainSync {
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- }
- waitForAnimateOut()
- waitForAnimateIn()
- assertThat(dropTargetView).isNotNull()
- assertThat(dropTargetView!!.alpha).isEqualTo(1f)
- assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnRight())
- assertThat(testListener.locationChanges).containsExactly(LEFT, RIGHT).inOrder()
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /**
- * Drop target does not initially show on the side that the drag starts. Check that it shows up
- * after the dragging the view to other side and back to the initial side.
- */
- @Test
- fun drag_leftToRightToLeft() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = true)
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNull()
-
- getInstrumentation().runOnMainSync {
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNotNull()
-
- getInstrumentation().runOnMainSync { controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) }
- waitForAnimateOut()
- waitForAnimateIn()
- assertThat(dropTargetView).isNotNull()
- assertThat(dropTargetView!!.alpha).isEqualTo(1f)
- assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnLeft())
- assertThat(testListener.locationChanges).containsExactly(RIGHT, LEFT).inOrder()
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /**
- * Drag from right to left, but stay in exclusion rect around the dismiss view. Drop target
- * should not show and location change should not trigger.
- */
- @Test
- fun drag_rightToLeft_inExclusionRect() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = false)
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- // Exclusion rect is around the bottom center area of the screen
- controller.onDragUpdate(SCREEN_WIDTH / 2f - 50, SCREEN_HEIGHT - 100f)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNull()
- assertThat(testListener.locationChanges).isEmpty()
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /**
- * Drag from left to right, but stay in exclusion rect around the dismiss view. Drop target
- * should not show and location change should not trigger.
- */
- @Test
- fun drag_leftToRight_inExclusionRect() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = true)
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- // Exclusion rect is around the bottom center area of the screen
- controller.onDragUpdate(SCREEN_WIDTH / 2f + 50, SCREEN_HEIGHT - 100f)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNull()
- assertThat(testListener.locationChanges).isEmpty()
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /**
- * Drag to dismiss target and back to the same side should not cause the drop target to show.
- */
- @Test
- fun drag_rightToDismissToRight() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = false)
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- controller.onStuckToDismissTarget()
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNull()
- assertThat(testListener.locationChanges).isEmpty()
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /**
- * Drag to dismiss target and back to the same side should not cause the drop target to show.
- */
- @Test
- fun drag_leftToDismissToLeft() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = true)
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- controller.onStuckToDismissTarget()
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNull()
- assertThat(testListener.locationChanges).isEmpty()
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /** Drag to dismiss target and other side should show drop target on the other side. */
- @Test
- fun drag_rightToDismissToLeft() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = false)
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- controller.onStuckToDismissTarget()
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNotNull()
- assertThat(dropTargetView!!.alpha).isEqualTo(1f)
- assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnLeft())
-
- assertThat(testListener.locationChanges).containsExactly(LEFT)
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /** Drag to dismiss target and other side should show drop target on the other side. */
- @Test
- fun drag_leftToDismissToRight() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = true)
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- controller.onStuckToDismissTarget()
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNotNull()
- assertThat(dropTargetView!!.alpha).isEqualTo(1f)
- assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnRight())
-
- assertThat(testListener.locationChanges).containsExactly(RIGHT)
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /**
- * Drag to dismiss should trigger a location change to the initial location, if the current
- * location is different. And hide the drop target.
- */
- @Test
- fun drag_rightToLeftToDismiss() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = false)
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNotNull()
- assertThat(dropTargetView!!.alpha).isEqualTo(1f)
-
- getInstrumentation().runOnMainSync { controller.onStuckToDismissTarget() }
- waitForAnimateOut()
- assertThat(dropTargetView!!.alpha).isEqualTo(0f)
-
- assertThat(testListener.locationChanges).containsExactly(LEFT, RIGHT).inOrder()
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /**
- * Drag to dismiss should trigger a location change to the initial location, if the current
- * location is different. And hide the drop target.
- */
- @Test
- fun drag_leftToRightToDismiss() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = true)
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNotNull()
- assertThat(dropTargetView!!.alpha).isEqualTo(1f)
- getInstrumentation().runOnMainSync { controller.onStuckToDismissTarget() }
- waitForAnimateOut()
- assertThat(dropTargetView!!.alpha).isEqualTo(0f)
- assertThat(testListener.locationChanges).containsExactly(RIGHT, LEFT).inOrder()
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /** Finishing drag should remove drop target and send location update. */
- @Test
- fun drag_rightToLeftRelease() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = false)
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNotNull()
-
- getInstrumentation().runOnMainSync { controller.onDragEnd() }
- waitForAnimateOut()
- assertThat(dropTargetView).isNull()
- assertThat(testListener.locationChanges).containsExactly(LEFT)
- assertThat(testListener.locationReleases).containsExactly(LEFT)
- }
-
- /** Finishing drag should remove drop target and send location update. */
- @Test
- fun drag_leftToRightRelease() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = true)
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNotNull()
-
- getInstrumentation().runOnMainSync { controller.onDragEnd() }
- waitForAnimateOut()
- assertThat(dropTargetView).isNull()
- assertThat(testListener.locationChanges).containsExactly(RIGHT)
- assertThat(testListener.locationReleases).containsExactly(RIGHT)
- }
-
- private fun getExpectedDropTargetBoundsOnLeft(): Rect =
- Rect().also {
- positioner.getBubbleBarExpandedViewBounds(
- true /* onLeft */,
- false /* isOverflowExpanded */,
- it
- )
- }
-
- private fun getExpectedDropTargetBoundsOnRight(): Rect =
- Rect().also {
- positioner.getBubbleBarExpandedViewBounds(
- false /* onLeft */,
- false /* isOverflowExpanded */,
- it
- )
- }
-
- private fun waitForAnimateIn() {
- // Advance animator for on-device test
- getInstrumentation().runOnMainSync {
- animatorTestRule.advanceTimeBy(DROP_TARGET_ALPHA_IN_DURATION)
- }
- }
-
- private fun waitForAnimateOut() {
- // Advance animator for on-device test
- getInstrumentation().runOnMainSync {
- animatorTestRule.advanceTimeBy(DROP_TARGET_ALPHA_OUT_DURATION)
- }
- }
-
- private fun View.bounds(): Rect {
- return Rect(0, 0, layoutParams.width, layoutParams.height).also { rect ->
- rect.offsetTo(x.toInt(), y.toInt())
- }
- }
-
- internal class TestLocationChangeListener : BaseBubblePinController.LocationChangeListener {
- val locationChanges = mutableListOf()
- val locationReleases = mutableListOf()
- override fun onChange(location: BubbleBarLocation) {
- locationChanges.add(location)
- }
-
- override fun onRelease(location: BubbleBarLocation) {
- locationReleases.add(location)
- }
- }
-}
diff --git a/wmshell/multivalentTestsForDeviceless/Android.bp b/wmshell/multivalentTestsForDeviceless/Android.bp
deleted file mode 100644
index 1ad19c9f30..0000000000
--- a/wmshell/multivalentTestsForDeviceless/Android.bp
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (C) 2019 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 {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
- default_team: "trendy_team_multitasking_windowing",
-}
-
-android_app {
- name: "WindowManagerShellRobolectric",
- platform_apis: true,
- static_libs: [
- "WindowManager-Shell",
- ],
- manifest: "AndroidManifestRobolectric.xml",
- use_resource_processor: true,
-}
-
-android_robolectric_test {
- name: "WMShellRobolectricTests",
- instrumentation_for: "WindowManagerShellRobolectric",
- upstream: true,
- java_resource_dirs: [
- "robolectric/config",
- ],
- srcs: [
- "src/**/*.kt",
- ],
- // TODO(b/323188766): Include BubbleStackViewTest once the robolectric issue is fixed.
- exclude_srcs: ["src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt"],
- static_libs: [
- "junit",
- "androidx.core_core-animation-testing",
- "androidx.test.runner",
- "androidx.test.rules",
- "androidx.test.ext.junit",
- "mockito-robolectric-prebuilt",
- "mockito-kotlin2",
- "truth",
- ],
- auto_gen_config: true,
-}
-
-android_test {
- name: "WMShellMultivalentTestsOnDevice",
- srcs: [
- "src/**/*.kt",
- ],
- static_libs: [
- "WindowManager-Shell",
- "junit",
- "androidx.core_core-animation-testing",
- "androidx.test.runner",
- "androidx.test.rules",
- "androidx.test.ext.junit",
- "frameworks-base-testutils",
- "mockito-kotlin2",
- "mockito-target-extended-minus-junit4",
- "truth",
- "platform-test-annotations",
- "platform-test-rules",
- ],
- libs: [
- "android.test.base",
- "android.test.runner",
- ],
- jni_libs: [
- "libdexmakerjvmtiagent",
- "libstaticjvmtiagent",
- ],
- kotlincflags: ["-Xjvm-default=all"],
- optimize: {
- enabled: false,
- },
- test_suites: ["device-tests"],
- platform_apis: true,
- certificate: "platform",
- aaptflags: [
- "--extra-packages",
- "com.android.wm.shell",
- ],
- manifest: "AndroidManifest.xml",
-}
diff --git a/wmshell/multivalentTestsForDeviceless/AndroidManifest.xml b/wmshell/multivalentTestsForDeviceless/AndroidManifest.xml
deleted file mode 100644
index f8f8338e5f..0000000000
--- a/wmshell/multivalentTestsForDeviceless/AndroidManifest.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/wmshell/multivalentTestsForDeviceless/AndroidManifestRobolectric.xml b/wmshell/multivalentTestsForDeviceless/AndroidManifestRobolectric.xml
deleted file mode 100644
index ffcd7d46fb..0000000000
--- a/wmshell/multivalentTestsForDeviceless/AndroidManifestRobolectric.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
diff --git a/wmshell/multivalentTestsForDeviceless/AndroidTest.xml b/wmshell/multivalentTestsForDeviceless/AndroidTest.xml
deleted file mode 100644
index 36fe8ec337..0000000000
--- a/wmshell/multivalentTestsForDeviceless/AndroidTest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/wmshell/multivalentTestsForDeviceless/OWNERS b/wmshell/multivalentTestsForDeviceless/OWNERS
deleted file mode 100644
index 24c1a3a6d4..0000000000
--- a/wmshell/multivalentTestsForDeviceless/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-atsjenk@google.com
-liranb@google.com
-madym@google.com
-
diff --git a/wmshell/multivalentTestsForDeviceless/robolectric/config/robolectric.properties b/wmshell/multivalentTestsForDeviceless/robolectric/config/robolectric.properties
deleted file mode 100644
index 7a0527ccaa..0000000000
--- a/wmshell/multivalentTestsForDeviceless/robolectric/config/robolectric.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-sdk=NEWEST_SDK
-
diff --git a/wmshell/multivalentTestsForDeviceless/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/wmshell/multivalentTestsForDeviceless/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
deleted file mode 100644
index 9e1440d571..0000000000
--- a/wmshell/multivalentTestsForDeviceless/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
+++ /dev/null
@@ -1,670 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.wm.shell.bubbles
-
-import android.content.Context
-import android.content.Intent
-import android.content.pm.ShortcutInfo
-import android.content.res.Resources
-import android.graphics.Insets
-import android.graphics.PointF
-import android.graphics.Rect
-import android.os.UserHandle
-import android.view.WindowManager
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.internal.protolog.common.ProtoLog
-import com.android.wm.shell.R
-import com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT
-import com.android.wm.shell.common.bubbles.BubbleBarLocation
-import com.google.common.truth.Truth.assertThat
-import com.google.common.util.concurrent.MoreExecutors.directExecutor
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-/** Tests operations and the resulting state managed by [BubblePositioner]. */
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class BubblePositionerTest {
-
- private lateinit var positioner: BubblePositioner
- private val context = ApplicationProvider.getApplicationContext()
- private val resources: Resources
- get() = context.resources
-
- private val defaultDeviceConfig =
- DeviceConfig(
- windowBounds = Rect(0, 0, 1000, 2000),
- isLargeScreen = false,
- isSmallTablet = false,
- isLandscape = false,
- isRtl = false,
- insets = Insets.of(0, 0, 0, 0)
- )
-
- @Before
- fun setUp() {
- ProtoLog.REQUIRE_PROTOLOGTOOL = false
- val windowManager = context.getSystemService(WindowManager::class.java)
- positioner = BubblePositioner(context, windowManager)
- }
-
- @Test
- fun testUpdate() {
- val insets = Insets.of(10, 20, 5, 15)
- val screenBounds = Rect(0, 0, 1000, 1200)
- val availableRect = Rect(screenBounds)
- availableRect.inset(insets)
- positioner.update(defaultDeviceConfig.copy(insets = insets, windowBounds = screenBounds))
- assertThat(positioner.availableRect).isEqualTo(availableRect)
- assertThat(positioner.isLandscape).isFalse()
- assertThat(positioner.isLargeScreen).isFalse()
- assertThat(positioner.insets).isEqualTo(insets)
- }
-
- @Test
- fun testShowBubblesVertically_phonePortrait() {
- positioner.update(defaultDeviceConfig)
- assertThat(positioner.showBubblesVertically()).isFalse()
- }
-
- @Test
- fun testShowBubblesVertically_phoneLandscape() {
- positioner.update(defaultDeviceConfig.copy(isLandscape = true))
- assertThat(positioner.isLandscape).isTrue()
- assertThat(positioner.showBubblesVertically()).isTrue()
- }
-
- @Test
- fun testShowBubblesVertically_tablet() {
- positioner.update(defaultDeviceConfig.copy(isLargeScreen = true))
- assertThat(positioner.showBubblesVertically()).isTrue()
- }
-
- /** If a resting position hasn't been set, calling it will return the default position. */
- @Test
- fun testGetRestingPosition_returnsDefaultPosition() {
- positioner.update(defaultDeviceConfig)
- val restingPosition = positioner.getRestingPosition()
- val defaultPosition = positioner.defaultStartPosition
- assertThat(restingPosition).isEqualTo(defaultPosition)
- }
-
- /** If a resting position has been set, it'll return that instead of the default position. */
- @Test
- fun testGetRestingPosition_returnsRestingPosition() {
- positioner.update(defaultDeviceConfig)
- val restingPosition = PointF(100f, 100f)
- positioner.restingPosition = restingPosition
- assertThat(positioner.getRestingPosition()).isEqualTo(restingPosition)
- }
-
- /** Test that the default resting position on phone is in upper left. */
- @Test
- fun testGetRestingPosition_bubble_onPhone() {
- positioner.update(defaultDeviceConfig)
- val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
- val restingPosition = positioner.getRestingPosition()
- assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left)
- assertThat(restingPosition.y).isEqualTo(defaultYPosition)
- }
-
- @Test
- fun testGetRestingPosition_bubble_onPhone_RTL() {
- positioner.update(defaultDeviceConfig.copy(isRtl = true))
- val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
- val restingPosition = positioner.getRestingPosition()
- assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right)
- assertThat(restingPosition.y).isEqualTo(defaultYPosition)
- }
-
- /** Test that the default resting position on tablet is middle left. */
- @Test
- fun testGetRestingPosition_chatBubble_onTablet() {
- positioner.update(defaultDeviceConfig.copy(isLargeScreen = true))
- val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
- val restingPosition = positioner.getRestingPosition()
- assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left)
- assertThat(restingPosition.y).isEqualTo(defaultYPosition)
- }
-
- @Test
- fun testGetRestingPosition_chatBubble_onTablet_RTL() {
- positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
- val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
- val restingPosition = positioner.getRestingPosition()
- assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right)
- assertThat(restingPosition.y).isEqualTo(defaultYPosition)
- }
-
- /** Test that the default resting position on tablet is middle right. */
- @Test
- fun testGetDefaultPosition_appBubble_onTablet() {
- positioner.update(defaultDeviceConfig.copy(isLargeScreen = true))
- val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
- val startPosition = positioner.getDefaultStartPosition(true /* isAppBubble */)
- assertThat(startPosition.x).isEqualTo(allowableStackRegion.right)
- assertThat(startPosition.y).isEqualTo(defaultYPosition)
- }
-
- @Test
- fun testGetRestingPosition_appBubble_onTablet_RTL() {
- positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
- val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
- val startPosition = positioner.getDefaultStartPosition(true /* isAppBubble */)
- assertThat(startPosition.x).isEqualTo(allowableStackRegion.left)
- assertThat(startPosition.y).isEqualTo(defaultYPosition)
- }
-
- @Test
- fun testGetRestingPosition_afterBoundsChange() {
- positioner.update(
- defaultDeviceConfig.copy(isLargeScreen = true, windowBounds = Rect(0, 0, 2000, 1600))
- )
-
- // Set the resting position to the right side
- var allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
- val restingPosition = PointF(allowableStackRegion.right, allowableStackRegion.centerY())
- positioner.restingPosition = restingPosition
-
- // Now make the device smaller
- positioner.update(
- defaultDeviceConfig.copy(isLargeScreen = false, windowBounds = Rect(0, 0, 1000, 1600))
- )
-
- // Check the resting position is on the correct side
- allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
- assertThat(positioner.restingPosition.x).isEqualTo(allowableStackRegion.right)
- }
-
- @Test
- fun testHasUserModifiedDefaultPosition_false() {
- positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
- assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse()
- positioner.restingPosition = positioner.defaultStartPosition
- assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse()
- }
-
- @Test
- fun testHasUserModifiedDefaultPosition_true() {
- positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
- assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse()
- positioner.restingPosition = PointF(0f, 100f)
- assertThat(positioner.hasUserModifiedDefaultPosition()).isTrue()
- }
-
- @Test
- fun testBubbleBarExpandedViewHeightAndWidth() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- // portrait orientation
- isLandscape = false,
- isLargeScreen = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
-
- positioner.setShowingInBubbleBar(true)
- positioner.update(deviceConfig)
- positioner.bubbleBarTopOnScreen = 2500
-
- val spaceBetweenTopInsetAndBubbleBarInLandscape = 1680
- val expandedViewVerticalSpacing =
- resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding)
- val expectedHeight =
- spaceBetweenTopInsetAndBubbleBarInLandscape - 2 * expandedViewVerticalSpacing
- val expectedWidth = resources.getDimensionPixelSize(R.dimen.bubble_bar_expanded_view_width)
-
- assertThat(positioner.getExpandedViewWidthForBubbleBar(false)).isEqualTo(expectedWidth)
- assertThat(positioner.getExpandedViewHeightForBubbleBar(false)).isEqualTo(expectedHeight)
- }
-
- @Test
- fun testBubbleBarExpandedViewHeightAndWidth_screenWidthTooSmall() {
- val screenWidth = 300
- val deviceConfig =
- defaultDeviceConfig.copy(
- // portrait orientation
- isLandscape = false,
- isLargeScreen = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, screenWidth, 2600)
- )
- positioner.setShowingInBubbleBar(true)
- positioner.update(deviceConfig)
- positioner.bubbleBarTopOnScreen = 2500
-
- val spaceBetweenTopInsetAndBubbleBarInLandscape = 180
- val expandedViewSpacing =
- resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding)
- val expectedHeight = spaceBetweenTopInsetAndBubbleBarInLandscape - 2 * expandedViewSpacing
- val expectedWidth = screenWidth - 15 /* horizontal insets */ - 2 * expandedViewSpacing
- assertThat(positioner.getExpandedViewWidthForBubbleBar(false)).isEqualTo(expectedWidth)
- assertThat(positioner.getExpandedViewHeightForBubbleBar(false)).isEqualTo(expectedHeight)
- }
-
- @Test
- fun testGetExpandedViewHeight_max() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isLargeScreen = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
- val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
-
- assertThat(positioner.getExpandedViewHeight(bubble)).isEqualTo(MAX_HEIGHT)
- }
-
- @Test
- fun testGetExpandedViewHeight_customHeight_valid() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isLargeScreen = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
- val minHeight =
- context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_default_height)
- val bubble =
- Bubble(
- "key",
- ShortcutInfo.Builder(context, "id").build(),
- minHeight + 100 /* desiredHeight */,
- 0 /* desiredHeightResId */,
- "title",
- 0 /* taskId */,
- null /* locus */,
- true /* isDismissable */,
- directExecutor()
- ) {}
-
- // Ensure the height is the same as the desired value
- assertThat(positioner.getExpandedViewHeight(bubble))
- .isEqualTo(bubble.getDesiredHeight(context))
- }
-
- @Test
- fun testGetExpandedViewHeight_customHeight_tooSmall() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isLargeScreen = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- val bubble =
- Bubble(
- "key",
- ShortcutInfo.Builder(context, "id").build(),
- 10 /* desiredHeight */,
- 0 /* desiredHeightResId */,
- "title",
- 0 /* taskId */,
- null /* locus */,
- true /* isDismissable */,
- directExecutor()
- ) {}
-
- // Ensure the height is the same as the desired value
- val minHeight =
- context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_default_height)
- assertThat(positioner.getExpandedViewHeight(bubble)).isEqualTo(minHeight)
- }
-
- @Test
- fun testGetMaxExpandedViewHeight_onLargeTablet() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isLargeScreen = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- val manageButtonHeight =
- context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_height)
- val pointerWidth = context.resources.getDimensionPixelSize(R.dimen.bubble_pointer_width)
- val expandedViewPadding =
- context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding)
- val expectedHeight =
- 1800 - 2 * 20 - manageButtonHeight - pointerWidth - expandedViewPadding * 2
- assertThat(positioner.getMaxExpandedViewHeight(false /* isOverflow */))
- .isEqualTo(expectedHeight)
- }
-
- @Test
- fun testAreBubblesBottomAligned_largeScreen_true() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isLargeScreen = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- assertThat(positioner.areBubblesBottomAligned()).isTrue()
- }
-
- @Test
- fun testAreBubblesBottomAligned_largeScreen_landscape_false() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isLargeScreen = true,
- isLandscape = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- assertThat(positioner.areBubblesBottomAligned()).isFalse()
- }
-
- @Test
- fun testAreBubblesBottomAligned_smallTablet_false() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isLargeScreen = true,
- isSmallTablet = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- assertThat(positioner.areBubblesBottomAligned()).isFalse()
- }
-
- @Test
- fun testAreBubblesBottomAligned_phone_false() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- assertThat(positioner.areBubblesBottomAligned()).isFalse()
- }
-
- @Test
- fun testExpandedViewY_phoneLandscape() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isLandscape = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
-
- // This bubble will have max height so it'll always be top aligned
- assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(positioner.getExpandedViewYTopAligned())
- }
-
- @Test
- fun testExpandedViewY_phonePortrait() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
-
- // Always top aligned in phone portrait
- assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(positioner.getExpandedViewYTopAligned())
- }
-
- @Test
- fun testExpandedViewY_smallTabletLandscape() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isSmallTablet = true,
- isLandscape = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
-
- // This bubble will have max height which is always top aligned on small tablets
- assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(positioner.getExpandedViewYTopAligned())
- }
-
- @Test
- fun testExpandedViewY_smallTabletPortrait() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isSmallTablet = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
-
- // This bubble will have max height which is always top aligned on small tablets
- assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(positioner.getExpandedViewYTopAligned())
- }
-
- @Test
- fun testExpandedViewY_largeScreenLandscape() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isLargeScreen = true,
- isLandscape = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
-
- // This bubble will have max height which is always top aligned on landscape, large tablet
- assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(positioner.getExpandedViewYTopAligned())
- }
-
- @Test
- fun testExpandedViewY_largeScreenPortrait() {
- val deviceConfig =
- defaultDeviceConfig.copy(
- isLargeScreen = true,
- insets = Insets.of(10, 20, 5, 15),
- windowBounds = Rect(0, 0, 1800, 2600)
- )
- positioner.update(deviceConfig)
-
- val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
-
- val manageButtonHeight =
- context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_height)
- val manageButtonPlusMargin =
- manageButtonHeight +
- 2 * context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_margin)
- val pointerWidth = context.resources.getDimensionPixelSize(R.dimen.bubble_pointer_width)
-
- val expectedExpandedViewY =
- positioner.availableRect.bottom -
- manageButtonPlusMargin -
- positioner.getExpandedViewHeightForLargeScreen() -
- pointerWidth
-
- // Bubbles are bottom aligned on portrait, large tablet
- assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
- .isEqualTo(expectedExpandedViewY)
- }
-
- @Test
- fun testGetTaskViewContentWidth_onLeft() {
- positioner.update(defaultDeviceConfig.copy(insets = Insets.of(100, 0, 200, 0)))
- val taskViewWidth = positioner.getTaskViewContentWidth(true /* onLeft */)
- val paddings =
- positioner.getExpandedViewContainerPadding(true /* onLeft */, false /* isOverflow */)
- assertThat(taskViewWidth)
- .isEqualTo(positioner.screenRect.width() - paddings[0] - paddings[2])
- }
-
- @Test
- fun testGetTaskViewContentWidth_onRight() {
- positioner.update(defaultDeviceConfig.copy(insets = Insets.of(100, 0, 200, 0)))
- val taskViewWidth = positioner.getTaskViewContentWidth(false /* onLeft */)
- val paddings =
- positioner.getExpandedViewContainerPadding(false /* onLeft */, false /* isOverflow */)
- assertThat(taskViewWidth)
- .isEqualTo(positioner.screenRect.width() - paddings[0] - paddings[2])
- }
-
- @Test
- fun testIsBubbleBarOnLeft_defaultsToRight() {
- positioner.bubbleBarLocation = BubbleBarLocation.DEFAULT
- assertThat(positioner.isBubbleBarOnLeft).isFalse()
-
- // Check that left and right return expected position
- positioner.bubbleBarLocation = BubbleBarLocation.LEFT
- assertThat(positioner.isBubbleBarOnLeft).isTrue()
- positioner.bubbleBarLocation = BubbleBarLocation.RIGHT
- assertThat(positioner.isBubbleBarOnLeft).isFalse()
- }
-
- @Test
- fun testIsBubbleBarOnLeft_rtlEnabled_defaultsToLeft() {
- positioner.update(defaultDeviceConfig.copy(isRtl = true))
-
- positioner.bubbleBarLocation = BubbleBarLocation.DEFAULT
- assertThat(positioner.isBubbleBarOnLeft).isTrue()
-
- // Check that left and right return expected position
- positioner.bubbleBarLocation = BubbleBarLocation.LEFT
- assertThat(positioner.isBubbleBarOnLeft).isTrue()
- positioner.bubbleBarLocation = BubbleBarLocation.RIGHT
- assertThat(positioner.isBubbleBarOnLeft).isFalse()
- }
-
- @Test
- fun testGetBubbleBarExpandedViewBounds_onLeft() {
- testGetBubbleBarExpandedViewBounds(onLeft = true, isOverflow = false)
- }
-
- @Test
- fun testGetBubbleBarExpandedViewBounds_onRight() {
- testGetBubbleBarExpandedViewBounds(onLeft = false, isOverflow = false)
- }
-
- @Test
- fun testGetBubbleBarExpandedViewBounds_isOverflow_onLeft() {
- testGetBubbleBarExpandedViewBounds(onLeft = true, isOverflow = true)
- }
-
- @Test
- fun testGetBubbleBarExpandedViewBounds_isOverflow_onRight() {
- testGetBubbleBarExpandedViewBounds(onLeft = false, isOverflow = true)
- }
-
- private fun testGetBubbleBarExpandedViewBounds(onLeft: Boolean, isOverflow: Boolean) {
- positioner.setShowingInBubbleBar(true)
- val windowBounds = Rect(0, 0, 2000, 2600)
- val insets = Insets.of(10, 20, 5, 15)
- val deviceConfig =
- defaultDeviceConfig.copy(
- isLargeScreen = true,
- isLandscape = true,
- insets = insets,
- windowBounds = windowBounds
- )
- positioner.update(deviceConfig)
-
- val bubbleBarHeight = 100
- positioner.bubbleBarTopOnScreen = windowBounds.bottom - insets.bottom - bubbleBarHeight
-
- val expandedViewPadding =
- context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding)
-
- val left: Int
- val right: Int
- if (onLeft) {
- // Pin to the left, calculate right
- left = deviceConfig.insets.left + expandedViewPadding
- right = left + positioner.getExpandedViewWidthForBubbleBar(isOverflow)
- } else {
- // Pin to the right, calculate left
- right =
- deviceConfig.windowBounds.right - deviceConfig.insets.right - expandedViewPadding
- left = right - positioner.getExpandedViewWidthForBubbleBar(isOverflow)
- }
- // Above the bubble bar
- val bottom = positioner.bubbleBarTopOnScreen - expandedViewPadding
- // Calculate right and top based on size
- val top = bottom - positioner.getExpandedViewHeightForBubbleBar(isOverflow)
- val expectedBounds = Rect(left, top, right, bottom)
-
- val bounds = Rect()
- positioner.getBubbleBarExpandedViewBounds(onLeft, isOverflow, bounds)
-
- assertThat(bounds).isEqualTo(expectedBounds)
- }
-
- private val defaultYPosition: Float
- /**
- * Calculates the Y position bubbles should be placed based on the config. Based on the
- * calculations in [BubblePositioner.getDefaultStartPosition] and
- * [BubbleStackView.RelativeStackPosition].
- */
- get() {
- val isTablet = positioner.isLargeScreen
-
- // On tablet the position is centered, on phone it is an offset from the top.
- val desiredY =
- if (isTablet) {
- positioner.screenRect.height() / 2f - positioner.bubbleSize / 2f
- } else {
- context.resources
- .getDimensionPixelOffset(R.dimen.bubble_stack_starting_offset_y)
- .toFloat()
- }
- // Since we're visually centering the bubbles on tablet, use total screen height rather
- // than the available height.
- val height =
- if (isTablet) {
- positioner.screenRect.height()
- } else {
- positioner.availableRect.height()
- }
- val offsetPercent = (desiredY / height).coerceIn(0f, 1f)
- val allowableStackRegion =
- positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
- return allowableStackRegion.top + allowableStackRegion.height() * offsetPercent
- }
-}
diff --git a/wmshell/multivalentTestsForDeviceless/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/wmshell/multivalentTestsForDeviceless/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
deleted file mode 100644
index 327e205955..0000000000
--- a/wmshell/multivalentTestsForDeviceless/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
- * 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.bubbles
-
-import android.content.Context
-import android.content.Intent
-import android.content.pm.ShortcutInfo
-import android.content.res.Resources
-import android.graphics.Color
-import android.graphics.drawable.Icon
-import android.os.UserHandle
-import android.platform.test.flag.junit.SetFlagsRule
-import android.view.IWindowManager
-import android.view.WindowManager
-import android.view.WindowManagerGlobal
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.internal.protolog.common.ProtoLog
-import com.android.launcher3.icons.BubbleIconFactory
-import com.android.wm.shell.Flags
-import com.android.wm.shell.R
-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.ShellExecutor
-import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
-import com.android.wm.shell.taskview.TaskView
-import com.android.wm.shell.taskview.TaskViewTaskController
-import com.google.common.truth.Truth.assertThat
-import com.google.common.util.concurrent.MoreExecutors.directExecutor
-import org.junit.After
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.kotlin.mock
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
-import java.util.concurrent.Semaphore
-import java.util.concurrent.TimeUnit
-import java.util.function.Consumer
-
-/** Unit tests for [BubbleStackView]. */
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class BubbleStackViewTest {
-
- @get:Rule val setFlagsRule = SetFlagsRule()
-
- private val context = ApplicationProvider.getApplicationContext()
- private lateinit var positioner: BubblePositioner
- private lateinit var iconFactory: BubbleIconFactory
- private lateinit var expandedViewManager: FakeBubbleExpandedViewManager
- private lateinit var bubbleStackView: BubbleStackView
- private lateinit var shellExecutor: ShellExecutor
- private lateinit var windowManager: IWindowManager
- private lateinit var bubbleTaskViewFactory: BubbleTaskViewFactory
- private lateinit var bubbleData: BubbleData
- private lateinit var bubbleStackViewManager: FakeBubbleStackViewManager
- private var sysuiProxy = mock()
-
- @Before
- fun setUp() {
- PhysicsAnimatorTestUtils.prepareForTest()
- // Disable protolog tool when running the tests from studio
- ProtoLog.REQUIRE_PROTOLOGTOOL = false
- windowManager = WindowManagerGlobal.getWindowManagerService()!!
- shellExecutor = TestShellExecutor()
- val windowManager = context.getSystemService(WindowManager::class.java)
- iconFactory =
- BubbleIconFactory(
- context,
- context.resources.getDimensionPixelSize(R.dimen.bubble_size),
- context.resources.getDimensionPixelSize(R.dimen.bubble_badge_size),
- Color.BLACK,
- context.resources.getDimensionPixelSize(
- com.android.internal.R.dimen.importance_ring_stroke_width
- )
- )
- positioner = BubblePositioner(context, windowManager)
- bubbleData =
- BubbleData(
- context,
- BubbleLogger(UiEventLoggerFake()),
- positioner,
- BubbleEducationController(context),
- shellExecutor
- )
- bubbleStackViewManager = FakeBubbleStackViewManager()
- expandedViewManager = FakeBubbleExpandedViewManager()
- bubbleTaskViewFactory = FakeBubbleTaskViewFactory()
- bubbleStackView =
- BubbleStackView(
- context,
- bubbleStackViewManager,
- positioner,
- bubbleData,
- null,
- FloatingContentCoordinator(),
- { sysuiProxy },
- shellExecutor
- )
-
- context
- .getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
- .edit()
- .putBoolean(StackEducationView.PREF_STACK_EDUCATION, true)
- .apply()
- }
-
- @After
- fun tearDown() {
- PhysicsAnimatorTestUtils.tearDown()
- }
-
- @Test
- fun addBubble() {
- val bubble = createAndInflateBubble()
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- bubbleStackView.addBubble(bubble)
- }
- InstrumentationRegistry.getInstrumentation().waitForIdleSync()
- assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
- }
-
- @Test
- fun tapBubbleToExpand() {
- val bubble = createAndInflateBubble()
-
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- bubbleStackView.addBubble(bubble)
- }
-
- InstrumentationRegistry.getInstrumentation().waitForIdleSync()
- assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
- var lastUpdate: BubbleData.Update? = null
- val semaphore = Semaphore(0)
- val listener =
- BubbleData.Listener { update ->
- lastUpdate = update
- semaphore.release()
- }
- bubbleData.setListener(listener)
-
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- bubble.iconView!!.performClick()
- // we're checking the expanded state in BubbleData because that's the source of truth.
- // This will eventually propagate an update back to the stack view, but setting the
- // entire pipeline is outside the scope of a unit test.
- assertThat(bubbleData.isExpanded).isTrue()
- }
-
- assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
- assertThat(lastUpdate).isNotNull()
- assertThat(lastUpdate!!.expandedChanged).isTrue()
- assertThat(lastUpdate!!.expanded).isTrue()
- }
-
- @Test
- fun tapDifferentBubble_shouldReorder() {
- val bubble1 = createAndInflateChatBubble(key = "bubble1")
- val bubble2 = createAndInflateChatBubble(key = "bubble2")
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- bubbleStackView.addBubble(bubble1)
- bubbleStackView.addBubble(bubble2)
- }
- InstrumentationRegistry.getInstrumentation().waitForIdleSync()
-
- assertThat(bubbleStackView.bubbleCount).isEqualTo(2)
- assertThat(bubbleData.bubbles).hasSize(2)
- assertThat(bubbleData.selectedBubble).isEqualTo(bubble2)
- assertThat(bubble2.iconView).isNotNull()
-
- var lastUpdate: BubbleData.Update? = null
- val semaphore = Semaphore(0)
- val listener =
- BubbleData.Listener { update ->
- lastUpdate = update
- semaphore.release()
- }
- bubbleData.setListener(listener)
-
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- bubble2.iconView!!.performClick()
- assertThat(bubbleData.isExpanded).isTrue()
-
- bubbleStackView.setSelectedBubble(bubble2)
- bubbleStackView.isExpanded = true
- }
-
- assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
- assertThat(lastUpdate!!.expanded).isTrue()
- assertThat(lastUpdate!!.bubbles.map { it.key })
- .containsExactly("bubble2", "bubble1")
- .inOrder()
-
- // wait for idle to allow the animation to start
- InstrumentationRegistry.getInstrumentation().waitForIdleSync()
- // wait for the expansion animation to complete before interacting with the bubbles
- PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
- AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y)
-
- // tap on bubble1 to select it
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- bubble1.iconView!!.performClick()
- }
- assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
- assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
-
- // tap on bubble1 again to collapse the stack
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- // we have to set the selected bubble in the stack view manually because we don't have a
- // listener wired up.
- bubbleStackView.setSelectedBubble(bubble1)
- bubble1.iconView!!.performClick()
- }
-
- assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
- assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
- assertThat(bubbleData.isExpanded).isFalse()
- assertThat(lastUpdate!!.orderChanged).isTrue()
- assertThat(lastUpdate!!.bubbles.map { it.key })
- .containsExactly("bubble1", "bubble2")
- .inOrder()
- }
-
- @EnableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
- @Test
- fun testCreateStackView_noOverflowContents_noOverflow() {
- bubbleStackView =
- BubbleStackView(
- context,
- bubbleStackViewManager,
- positioner,
- bubbleData,
- null,
- FloatingContentCoordinator(),
- { sysuiProxy },
- shellExecutor
- )
-
- assertThat(bubbleData.overflowBubbles).isEmpty()
- val bubbleOverflow = bubbleData.overflow
- // Overflow shouldn't be attached
- assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isEqualTo(-1)
- }
-
- @EnableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
- @Test
- fun testCreateStackView_hasOverflowContents_hasOverflow() {
- // Add a bubble to the overflow
- val bubble1 = createAndInflateChatBubble(key = "bubble1")
- bubbleData.notificationEntryUpdated(bubble1, false, false)
- bubbleData.dismissBubbleWithKey(bubble1.key, Bubbles.DISMISS_USER_GESTURE)
- assertThat(bubbleData.overflowBubbles).isNotEmpty()
-
- bubbleStackView =
- BubbleStackView(
- context,
- bubbleStackViewManager,
- positioner,
- bubbleData,
- null,
- FloatingContentCoordinator(),
- { sysuiProxy },
- shellExecutor
- )
- val bubbleOverflow = bubbleData.overflow
- assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isGreaterThan(-1)
- }
-
- @DisableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
- @Test
- fun testCreateStackView_noOverflowContents_hasOverflow() {
- bubbleStackView =
- BubbleStackView(
- context,
- bubbleStackViewManager,
- positioner,
- bubbleData,
- null,
- FloatingContentCoordinator(),
- { sysuiProxy },
- shellExecutor
- )
-
- assertThat(bubbleData.overflowBubbles).isEmpty()
- val bubbleOverflow = bubbleData.overflow
- assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isGreaterThan(-1)
- }
-
- @EnableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
- @Test
- fun showOverflow_true() {
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- bubbleStackView.showOverflow(true)
- }
- InstrumentationRegistry.getInstrumentation().waitForIdleSync()
-
- val bubbleOverflow = bubbleData.overflow
- assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isGreaterThan(-1)
- }
-
- @EnableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
- @Test
- fun showOverflow_false() {
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- bubbleStackView.showOverflow(true)
- }
- InstrumentationRegistry.getInstrumentation().waitForIdleSync()
- val bubbleOverflow = bubbleData.overflow
- assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isGreaterThan(-1)
-
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- bubbleStackView.showOverflow(false)
- }
- InstrumentationRegistry.getInstrumentation().waitForIdleSync()
-
- // The overflow should've been removed
- assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isEqualTo(-1)
- }
-
- @DisableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
- @Test
- fun showOverflow_ignored() {
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- bubbleStackView.showOverflow(false)
- }
- InstrumentationRegistry.getInstrumentation().waitForIdleSync()
-
- // showOverflow should've been ignored, so the overflow would be attached
- val bubbleOverflow = bubbleData.overflow
- assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isGreaterThan(-1)
- }
-
- private fun createAndInflateChatBubble(key: String): Bubble {
- val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button)
- val shortcutInfo = ShortcutInfo.Builder(context, "fakeId").setIcon(icon).build()
- val bubble =
- Bubble(
- key,
- shortcutInfo,
- /* desiredHeight= */ 6,
- Resources.ID_NULL,
- "title",
- /* taskId= */ 0,
- "locus",
- /* isDismissable= */ true,
- directExecutor()
- ) {}
- inflateBubble(bubble)
- return bubble
- }
-
- private fun createAndInflateBubble(): Bubble {
- val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
- val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button)
- val bubble = Bubble.createAppBubble(intent, UserHandle(1), icon, directExecutor())
- inflateBubble(bubble)
- return bubble
- }
-
- private fun inflateBubble(bubble: Bubble) {
- bubble.setInflateSynchronously(true)
- bubbleData.notificationEntryUpdated(bubble, true, false)
-
- val semaphore = Semaphore(0)
- val callback: BubbleViewInfoTask.Callback =
- BubbleViewInfoTask.Callback { semaphore.release() }
- bubble.inflate(
- callback,
- context,
- expandedViewManager,
- bubbleTaskViewFactory,
- positioner,
- bubbleStackView,
- null,
- iconFactory,
- false
- )
-
- assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
- assertThat(bubble.isInflated).isTrue()
- }
-
- private class FakeBubbleStackViewManager : BubbleStackViewManager {
-
- override fun onAllBubblesAnimatedOut() {}
-
- override fun updateWindowFlagsForBackpress(interceptBack: Boolean) {}
-
- override fun checkNotificationPanelExpandedState(callback: Consumer) {}
-
- override fun hideCurrentInputMethod() {}
- }
-
- private class TestShellExecutor : ShellExecutor {
-
- override fun execute(runnable: Runnable) {
- runnable.run()
- }
-
- override fun executeDelayed(r: Runnable, delayMillis: Long) {
- r.run()
- }
-
- override fun removeCallbacks(r: Runnable?) {}
-
- override fun hasCallback(r: Runnable): Boolean = false
- }
-
- private inner class FakeBubbleTaskViewFactory : BubbleTaskViewFactory {
- override fun create(): BubbleTaskView {
- val taskViewTaskController = mock()
- val taskView = TaskView(context, taskViewTaskController)
- return BubbleTaskView(taskView, shellExecutor)
- }
- }
-
- private inner class FakeBubbleExpandedViewManager : BubbleExpandedViewManager {
-
- override val overflowBubbles: List
- get() = emptyList()
-
- override fun setOverflowListener(listener: BubbleData.Listener) {}
-
- override fun collapseStack() {}
-
- override fun updateWindowFlagsForBackpress(intercept: Boolean) {}
-
- override fun promoteBubbleFromOverflow(bubble: Bubble) {}
-
- override fun removeBubble(key: String, reason: Int) {}
-
- override fun dismissBubble(bubble: Bubble, reason: Int) {}
-
- override fun setAppBubbleTaskId(key: String, taskId: Int) {}
-
- override fun isStackExpanded(): Boolean = false
-
- override fun isShowingAsBubbleBar(): Boolean = false
-
- override fun hideCurrentInputMethod() {}
- }
-}
diff --git a/wmshell/multivalentTestsForDeviceless/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt b/wmshell/multivalentTestsForDeviceless/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
deleted file mode 100644
index 398fd554f0..0000000000
--- a/wmshell/multivalentTestsForDeviceless/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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.bubbles
-
-import android.content.ComponentName
-import android.content.Context
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.wm.shell.taskview.TaskView
-
-import com.google.common.truth.Truth.assertThat
-import com.google.common.util.concurrent.MoreExecutors.directExecutor
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.never
-import org.mockito.kotlin.verify
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class BubbleTaskViewTest {
-
- private lateinit var bubbleTaskView: BubbleTaskView
- private val context = ApplicationProvider.getApplicationContext()
- private lateinit var taskView: TaskView
-
- @Before
- fun setUp() {
- taskView = mock()
- bubbleTaskView = BubbleTaskView(taskView, directExecutor())
- }
-
- @Test
- fun onTaskCreated_updatesState() {
- val componentName = ComponentName(context, "TestClass")
- bubbleTaskView.listener.onTaskCreated(123, componentName)
-
- assertThat(bubbleTaskView.taskId).isEqualTo(123)
- assertThat(bubbleTaskView.componentName).isEqualTo(componentName)
- assertThat(bubbleTaskView.isCreated).isTrue()
- }
-
- @Test
- fun onTaskCreated_callsDelegateListener() {
- var actualTaskId = -1
- var actualComponentName: ComponentName? = null
- val delegateListener = object : TaskView.Listener {
- override fun onTaskCreated(taskId: Int, name: ComponentName) {
- actualTaskId = taskId
- actualComponentName = name
- }
- }
- bubbleTaskView.delegateListener = delegateListener
-
- val componentName = ComponentName(context, "TestClass")
- bubbleTaskView.listener.onTaskCreated(123, componentName)
-
- assertThat(actualTaskId).isEqualTo(123)
- assertThat(actualComponentName).isEqualTo(componentName)
- }
-
- @Test
- fun cleanup_invalidTaskId_doesNotRemoveTask() {
- bubbleTaskView.cleanup()
- verify(taskView, never()).removeTask()
- }
-
- @Test
- fun cleanup_validTaskId_removesTask() {
- val componentName = ComponentName(context, "TestClass")
- bubbleTaskView.listener.onTaskCreated(123, componentName)
-
- bubbleTaskView.cleanup()
- verify(taskView).removeTask()
- }
-}
diff --git a/wmshell/multivalentTestsForDeviceless/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/wmshell/multivalentTestsForDeviceless/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
deleted file mode 100644
index ace2c13105..0000000000
--- a/wmshell/multivalentTestsForDeviceless/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
+++ /dev/null
@@ -1,459 +0,0 @@
-/*
- * 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.bubbles.bar
-
-import android.content.Context
-import android.graphics.Insets
-import android.graphics.PointF
-import android.graphics.Rect
-import android.view.View
-import android.view.WindowManager
-import android.widget.FrameLayout
-import androidx.core.animation.AnimatorTestRule
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
-import com.android.internal.protolog.common.ProtoLog
-import com.android.wm.shell.R
-import com.android.wm.shell.bubbles.BubblePositioner
-import com.android.wm.shell.bubbles.DeviceConfig
-import com.android.wm.shell.common.bubbles.BaseBubblePinController
-import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_IN_DURATION
-import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_OUT_DURATION
-import com.android.wm.shell.common.bubbles.BubbleBarLocation
-import com.android.wm.shell.common.bubbles.BubbleBarLocation.LEFT
-import com.android.wm.shell.common.bubbles.BubbleBarLocation.RIGHT
-import com.google.common.truth.Truth.assertThat
-import org.junit.After
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-/** Tests for [BubbleExpandedViewPinController] */
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class BubbleExpandedViewPinControllerTest {
-
- companion object {
- @JvmField @ClassRule val animatorTestRule: AnimatorTestRule = AnimatorTestRule()
-
- const val SCREEN_WIDTH = 2000
- const val SCREEN_HEIGHT = 1000
-
- const val BUBBLE_BAR_HEIGHT = 50
- }
-
- private val context = ApplicationProvider.getApplicationContext()
- private lateinit var positioner: BubblePositioner
- private lateinit var container: FrameLayout
-
- private lateinit var controller: BubbleExpandedViewPinController
- private lateinit var testListener: TestLocationChangeListener
-
- private val dropTargetView: View?
- get() = container.findViewById(R.id.bubble_bar_drop_target)
-
- private val pointOnLeft = PointF(100f, 100f)
- private val pointOnRight = PointF(1900f, 500f)
-
- @Before
- fun setUp() {
- ProtoLog.REQUIRE_PROTOLOGTOOL = false
- container = FrameLayout(context)
- val windowManager = context.getSystemService(WindowManager::class.java)
- positioner = BubblePositioner(context, windowManager)
- positioner.setShowingInBubbleBar(true)
- val deviceConfig =
- DeviceConfig(
- windowBounds = Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT),
- isLargeScreen = true,
- isSmallTablet = false,
- isLandscape = true,
- isRtl = false,
- insets = Insets.of(10, 20, 30, 40)
- )
- positioner.update(deviceConfig)
- positioner.bubbleBarTopOnScreen =
- SCREEN_HEIGHT - deviceConfig.insets.bottom - BUBBLE_BAR_HEIGHT
- controller = BubbleExpandedViewPinController(context, container, positioner)
- testListener = TestLocationChangeListener()
- controller.setListener(testListener)
- }
-
- @After
- fun tearDown() {
- getInstrumentation().runOnMainSync { controller.onDragEnd() }
- waitForAnimateOut()
- }
-
- /** Dragging on same side should not show drop target or trigger location changes */
- @Test
- fun drag_stayOnRightSide() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = false)
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- controller.onDragEnd()
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNull()
- assertThat(testListener.locationChanges).isEmpty()
- assertThat(testListener.locationReleases).containsExactly(RIGHT)
- }
-
- /** Dragging on same side should not show drop target or trigger location changes */
- @Test
- fun drag_stayOnLeftSide() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = true)
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- controller.onDragEnd()
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNull()
- assertThat(testListener.locationChanges).isEmpty()
- assertThat(testListener.locationReleases).containsExactly(LEFT)
- }
-
- /** Drag crosses to the other side. Show drop target and trigger a location change. */
- @Test
- fun drag_rightToLeft() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = false)
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- }
- waitForAnimateIn()
-
- assertThat(dropTargetView).isNotNull()
- assertThat(dropTargetView!!.alpha).isEqualTo(1f)
- assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnLeft())
- assertThat(testListener.locationChanges).containsExactly(LEFT)
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /** Drag crosses to the other side. Show drop target and trigger a location change. */
- @Test
- fun drag_leftToRight() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = true)
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- }
- waitForAnimateIn()
-
- assertThat(dropTargetView).isNotNull()
- assertThat(dropTargetView!!.alpha).isEqualTo(1f)
- assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnRight())
- assertThat(testListener.locationChanges).containsExactly(RIGHT)
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /**
- * Drop target does not initially show on the side that the drag starts. Check that it shows up
- * after the dragging the view to other side and back to the initial side.
- */
- @Test
- fun drag_rightToLeftToRight() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = false)
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNull()
-
- getInstrumentation().runOnMainSync { controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) }
- waitForAnimateIn()
- assertThat(dropTargetView).isNotNull()
-
- getInstrumentation().runOnMainSync {
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- }
- waitForAnimateOut()
- waitForAnimateIn()
- assertThat(dropTargetView).isNotNull()
- assertThat(dropTargetView!!.alpha).isEqualTo(1f)
- assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnRight())
- assertThat(testListener.locationChanges).containsExactly(LEFT, RIGHT).inOrder()
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /**
- * Drop target does not initially show on the side that the drag starts. Check that it shows up
- * after the dragging the view to other side and back to the initial side.
- */
- @Test
- fun drag_leftToRightToLeft() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = true)
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNull()
-
- getInstrumentation().runOnMainSync {
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNotNull()
-
- getInstrumentation().runOnMainSync { controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) }
- waitForAnimateOut()
- waitForAnimateIn()
- assertThat(dropTargetView).isNotNull()
- assertThat(dropTargetView!!.alpha).isEqualTo(1f)
- assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnLeft())
- assertThat(testListener.locationChanges).containsExactly(RIGHT, LEFT).inOrder()
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /**
- * Drag from right to left, but stay in exclusion rect around the dismiss view. Drop target
- * should not show and location change should not trigger.
- */
- @Test
- fun drag_rightToLeft_inExclusionRect() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = false)
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- // Exclusion rect is around the bottom center area of the screen
- controller.onDragUpdate(SCREEN_WIDTH / 2f - 50, SCREEN_HEIGHT - 100f)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNull()
- assertThat(testListener.locationChanges).isEmpty()
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /**
- * Drag from left to right, but stay in exclusion rect around the dismiss view. Drop target
- * should not show and location change should not trigger.
- */
- @Test
- fun drag_leftToRight_inExclusionRect() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = true)
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- // Exclusion rect is around the bottom center area of the screen
- controller.onDragUpdate(SCREEN_WIDTH / 2f + 50, SCREEN_HEIGHT - 100f)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNull()
- assertThat(testListener.locationChanges).isEmpty()
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /**
- * Drag to dismiss target and back to the same side should not cause the drop target to show.
- */
- @Test
- fun drag_rightToDismissToRight() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = false)
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- controller.onStuckToDismissTarget()
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNull()
- assertThat(testListener.locationChanges).isEmpty()
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /**
- * Drag to dismiss target and back to the same side should not cause the drop target to show.
- */
- @Test
- fun drag_leftToDismissToLeft() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = true)
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- controller.onStuckToDismissTarget()
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNull()
- assertThat(testListener.locationChanges).isEmpty()
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /** Drag to dismiss target and other side should show drop target on the other side. */
- @Test
- fun drag_rightToDismissToLeft() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = false)
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- controller.onStuckToDismissTarget()
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNotNull()
- assertThat(dropTargetView!!.alpha).isEqualTo(1f)
- assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnLeft())
-
- assertThat(testListener.locationChanges).containsExactly(LEFT)
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /** Drag to dismiss target and other side should show drop target on the other side. */
- @Test
- fun drag_leftToDismissToRight() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = true)
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- controller.onStuckToDismissTarget()
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNotNull()
- assertThat(dropTargetView!!.alpha).isEqualTo(1f)
- assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnRight())
-
- assertThat(testListener.locationChanges).containsExactly(RIGHT)
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /**
- * Drag to dismiss should trigger a location change to the initial location, if the current
- * location is different. And hide the drop target.
- */
- @Test
- fun drag_rightToLeftToDismiss() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = false)
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNotNull()
- assertThat(dropTargetView!!.alpha).isEqualTo(1f)
-
- getInstrumentation().runOnMainSync { controller.onStuckToDismissTarget() }
- waitForAnimateOut()
- assertThat(dropTargetView!!.alpha).isEqualTo(0f)
-
- assertThat(testListener.locationChanges).containsExactly(LEFT, RIGHT).inOrder()
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /**
- * Drag to dismiss should trigger a location change to the initial location, if the current
- * location is different. And hide the drop target.
- */
- @Test
- fun drag_leftToRightToDismiss() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = true)
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNotNull()
- assertThat(dropTargetView!!.alpha).isEqualTo(1f)
- getInstrumentation().runOnMainSync { controller.onStuckToDismissTarget() }
- waitForAnimateOut()
- assertThat(dropTargetView!!.alpha).isEqualTo(0f)
- assertThat(testListener.locationChanges).containsExactly(RIGHT, LEFT).inOrder()
- assertThat(testListener.locationReleases).isEmpty()
- }
-
- /** Finishing drag should remove drop target and send location update. */
- @Test
- fun drag_rightToLeftRelease() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = false)
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNotNull()
-
- getInstrumentation().runOnMainSync { controller.onDragEnd() }
- waitForAnimateOut()
- assertThat(dropTargetView).isNull()
- assertThat(testListener.locationChanges).containsExactly(LEFT)
- assertThat(testListener.locationReleases).containsExactly(LEFT)
- }
-
- /** Finishing drag should remove drop target and send location update. */
- @Test
- fun drag_leftToRightRelease() {
- getInstrumentation().runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = true)
- controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
- controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
- }
- waitForAnimateIn()
- assertThat(dropTargetView).isNotNull()
-
- getInstrumentation().runOnMainSync { controller.onDragEnd() }
- waitForAnimateOut()
- assertThat(dropTargetView).isNull()
- assertThat(testListener.locationChanges).containsExactly(RIGHT)
- assertThat(testListener.locationReleases).containsExactly(RIGHT)
- }
-
- private fun getExpectedDropTargetBoundsOnLeft(): Rect =
- Rect().also {
- positioner.getBubbleBarExpandedViewBounds(
- true /* onLeft */,
- false /* isOverflowExpanded */,
- it
- )
- }
-
- private fun getExpectedDropTargetBoundsOnRight(): Rect =
- Rect().also {
- positioner.getBubbleBarExpandedViewBounds(
- false /* onLeft */,
- false /* isOverflowExpanded */,
- it
- )
- }
-
- private fun waitForAnimateIn() {
- // Advance animator for on-device test
- getInstrumentation().runOnMainSync {
- animatorTestRule.advanceTimeBy(DROP_TARGET_ALPHA_IN_DURATION)
- }
- }
-
- private fun waitForAnimateOut() {
- // Advance animator for on-device test
- getInstrumentation().runOnMainSync {
- animatorTestRule.advanceTimeBy(DROP_TARGET_ALPHA_OUT_DURATION)
- }
- }
-
- private fun View.bounds(): Rect {
- return Rect(0, 0, layoutParams.width, layoutParams.height).also { rect ->
- rect.offsetTo(x.toInt(), y.toInt())
- }
- }
-
- internal class TestLocationChangeListener : BaseBubblePinController.LocationChangeListener {
- val locationChanges = mutableListOf()
- val locationReleases = mutableListOf()
- override fun onChange(location: BubbleBarLocation) {
- locationChanges.add(location)
- }
-
- override fun onRelease(location: BubbleBarLocation) {
- locationReleases.add(location)
- }
- }
-}
diff --git a/wmshell/res/drawable/bubble_ic_create_bubble.xml b/wmshell/res/drawable/bubble_ic_create_bubble.xml
index 920671a242..c9de8b81c0 100644
--- a/wmshell/res/drawable/bubble_ic_create_bubble.xml
+++ b/wmshell/res/drawable/bubble_ic_create_bubble.xml
@@ -15,11 +15,11 @@
~ limitations under the License.
-->
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ android:pathData="m464,480 l-90,-90q-11,-11 -11.5,-27.5T374,334q11,-11 28,-11t28,11l90,89v-49q0,-17 11.5,-28.5T560,334q17,0 28.5,11.5T600,374v146q0,17 -11.5,28.5T560,560L414,560q-17,0 -28.5,-11.5T374,520q0,-17 11.5,-28.5T414,480h50ZM760,800q-50,0 -85,-35t-35,-85q0,-50 35,-85t85,-35q50,0 85,35t35,85q0,50 -35,85t-85,35ZM160,800q-33,0 -56.5,-23.5T80,720v-480q0,-33 23.5,-56.5T160,160h640q33,0 56.5,23.5T880,240v200q0,17 -11.5,28.5T840,480q-17,0 -28.5,-11.5T800,440v-200L160,240v480h360q17,0 28.5,11.5T560,760q0,17 -11.5,28.5T520,800L160,800Z"
+ android:fillColor="#FF000000"/>
diff --git a/wmshell/res/drawable/bubble_ic_stop_bubble.xml b/wmshell/res/drawable/bubble_ic_stop_bubble.xml
index 8609576ce7..b06112266d 100644
--- a/wmshell/res/drawable/bubble_ic_stop_bubble.xml
+++ b/wmshell/res/drawable/bubble_ic_stop_bubble.xml
@@ -15,11 +15,11 @@
~ limitations under the License.
-->
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ android:pathData="M160,800q-33,0 -56.5,-23.5T80,720v-480q0,-33 23.5,-56.5T160,160h640q33,0 56.5,23.5T880,240v200q0,17 -11.5,28.5T840,480q-17,0 -28.5,-11.5T800,440v-200L160,240v480h360q17,0 28.5,11.5T560,760q0,17 -11.5,28.5T520,800L160,800ZM496,400 L586,490q11,11 11,27.5T586,546q-12,12 -28.5,12T529,546l-89,-89v49q0,17 -11.5,28.5T400,546q-17,0 -28.5,-11.5T360,506v-146q0,-17 11.5,-28.5T400,320h146q17,0 28.5,11.5T586,360q0,17 -11.5,28.5T546,400h-50ZM760,800q-50,0 -85,-35t-35,-85q0,-50 35,-85t85,-35q50,0 85,35t35,85q0,50 -35,85t-85,35Z"
+ android:fillColor="#FF000000"/>
diff --git a/wmshell/res/layout/desktop_mode_app_header.xml b/wmshell/res/layout/desktop_mode_app_header.xml
index b898e4b06c..04681a8ba9 100644
--- a/wmshell/res/layout/desktop_mode_app_header.xml
+++ b/wmshell/res/layout/desktop_mode_app_header.xml
@@ -60,6 +60,20 @@
android:focusable="false"
tools:text="Gmail"/>
+
+
@@ -164,12 +165,33 @@
android:src="@drawable/desktop_mode_ic_handle_menu_change_aspect_ratio"
style="@style/DesktopModeHandleMenuActionButton"/>
-
+
+
+
+
+
+
-
-
-
-
-
-
+
diff --git a/wmshell/res/layout/letterbox_restart_dialog_layout.xml b/wmshell/res/layout/letterbox_restart_dialog_layout.xml
index 462a49ccb1..33af38bbf6 100644
--- a/wmshell/res/layout/letterbox_restart_dialog_layout.xml
+++ b/wmshell/res/layout/letterbox_restart_dialog_layout.xml
@@ -50,6 +50,7 @@
android:orientation="vertical">
+
+
"Hierdie app kan net in 1 venster oopgemaak word"
"App sal dalk nie op \'n sekondêre skerm werk nie."
"App steun nie begin op sekondêre skerms nie."
+ "App steun nie borrel nie"
"Skermverdeler"
"Skermverdeler"
"Volskerm links"
@@ -133,12 +134,18 @@
"Bestuur vensters"
"Verander aspekverhouding"
"Optimeer aansig"
+ "Fout-ikoon"
"Maak toe"
"Maak kieslys toe"
"%1$s (werkskermvensters)"
"Maksimeer skerm"
"Verander grootte"
"App kan nie hierheen geskuif word nie"
+ "Maak tans nuwe appvenster oop"
+ "Maak tans appvenster toe"
+ "Minimeer tans appvenster"
+ "%1$s (gefokus)"
+ "%1$s (nie gefokus nie)"
"Meesleurend"
"Stel terug"
"Maksimeer"
diff --git a/wmshell/res/values-am/strings.xml b/wmshell/res/values-am/strings.xml
index 97aa47f3e1..df65c5592c 100644
--- a/wmshell/res/values-am/strings.xml
+++ b/wmshell/res/values-am/strings.xml
@@ -36,6 +36,7 @@
"ይህ መተግበሪያ መከፈት የሚችለው በ1 መስኮት ብቻ ነው"
"መተግበሪያ በሁለተኛ ማሳያ ላይ ላይሠራ ይችላል።"
"መተግበሪያ በሁለተኛ ማሳያዎች ላይ ማስጀመርን አይደግፍም።"
+ "መተግበሪያ ዓረፋን አይደግፍም"
"የተከፈለ የማያ ገፅ ከፋይ"
"የተከፈለ የማያ ገፅ ከፋይ"
"የግራ ሙሉ ማያ ገፅ"
@@ -133,12 +134,18 @@
"መስኮቶችን አስተዳድር"
"ምጥጥነ ገፅታ ለውጥ"
"ዕይታን አትባ"
+ "የስህተት አዶ"
"ዝጋ"
"ምናሌ ዝጋ"
"%1$s (ዴስክቶፕ መስኮት)"
"የማያ ገጹ መጠን አሳድግ"
"መጠን ቀይር"
"መተግበሪያ ወደዚህ መንቀሳቀስ አይችልም"
+ "አዲስ የመተግበሪያ መስኮት በመክፈት ላይ"
+ "የመተግበሪያ መስኮትን በመዝጋት ላይ"
+ "የመተግበሪያ መስኮትን በማሳነስ ላይ"
+ "%1$s (ትኩረት የተደረገበት)"
+ "%1$s (ትኩረት ያልተደረገበት)"
"አስማጭ"
"ወደነበረበት መልስ"
"አሳድግ"
diff --git a/wmshell/res/values-ar/strings.xml b/wmshell/res/values-ar/strings.xml
index 1de9ce0199..93a69b1895 100644
--- a/wmshell/res/values-ar/strings.xml
+++ b/wmshell/res/values-ar/strings.xml
@@ -36,6 +36,7 @@
"لا يمكن فتح هذا التطبيق إلا في نافذة واحدة."
"قد لا يعمل التطبيق على شاشة عرض ثانوية."
"لا يمكن تشغيل التطبيق على شاشات عرض ثانوية."
+ "لا يتيح التطبيق إظهار المهام على شكل فقاعة"
"أداة تقسيم الشاشة"
"أداة تقسيم الشاشة"
"عرض النافذة اليسرى بملء الشاشة"
@@ -133,12 +134,18 @@
"إدارة النوافذ"
"تغيير نسبة العرض إلى الارتفاع"
"تحسين طريقة العرض"
+ "رمز الخطأ"
"إغلاق"
"إغلاق القائمة"
"%1$s (عرض المحتوى في النافذة الحالية على سطح المكتب)"
"تكبير الشاشة إلى أقصى حدّ"
"تغيير الحجم"
"لا يمكن نقل التطبيق إلى هنا"
+ "جارٍ فتح نافذة تطبيق جديدة"
+ "جارٍ إغلاق نافذة التطبيق"
+ "جارٍ تصغير نافذة التطبيق"
+ "\"%1$s\" (تم التركيز عليه)"
+ "%1$s (غير مركّز عليه)"
"مجسَّم"
"استعادة"
"تكبير"
diff --git a/wmshell/res/values-as/strings.xml b/wmshell/res/values-as/strings.xml
index 96f2aaaf1e..297bc7253a 100644
--- a/wmshell/res/values-as/strings.xml
+++ b/wmshell/res/values-as/strings.xml
@@ -36,6 +36,7 @@
"এই এপ্টো কেৱল ১ খন ৱিণ্ড’ত খুলিব পাৰি"
"গৌণ ডিছপ্লেত এপে সঠিকভাৱে কাম নকৰিব পাৰে।"
"গৌণ ডিছপ্লেত এপ্ লঞ্চ কৰিব নোৱাৰি।"
+ "এপে বাবল সমৰ্থন নকৰে"
"স্প্লিট স্ক্ৰীনৰ বিভাজক"
"স্প্লিট স্ক্ৰীনৰ বিভাজক"
"বাওঁফালৰ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"
@@ -133,12 +134,18 @@
"ৱিণ্ড’ পৰিচালনা কৰক"
"আকাৰৰ অনুপাত সলনি কৰক"
"ভিউ অপ্টিমাইজ কৰক"
+ "আসোঁৱাহৰ চিহ্ন"
"বন্ধ কৰক"
"মেনু বন্ধ কৰক"
"%1$s (ডেস্কটপ ৱিণ্ড’ৱিং)"
"স্ক্ৰীন মেক্সিমাইজ কৰক"
"আকাৰ সলনি কৰক"
"ইয়ালৈ এপ্টো আনিব নোৱাৰি"
+ "নতুন এপৰ ৱিণ্ড’ খুলি থকা হৈছে"
+ "এপৰ ৱিণ্ড’ বন্ধ কৰি থকা হৈছে"
+ "এপৰ ৱিণ্ড’ মিনিমাইজ কৰা হৈছে"
+ "%1$s (ফ’কাছ কৰা)"
+ "%1$s (ফ’কাছ কৰা নাই)"
"ইমাৰ্ছিভ"
"পুনঃস্থাপন কৰক"
"মেক্সিমাইজ কৰক"
diff --git a/wmshell/res/values-az/strings.xml b/wmshell/res/values-az/strings.xml
index 72736edd77..a1a3288615 100644
--- a/wmshell/res/values-az/strings.xml
+++ b/wmshell/res/values-az/strings.xml
@@ -36,6 +36,7 @@
"Bu tətbiq yalnız 1 pəncərədə açıla bilər"
"Tətbiq ikinci ekranda işləməyə bilər."
"Tətbiq ikinci ekranda başlamağı dəstəkləmir."
+ "Tətbiq qabarcığı dəstəkləmir"
"Bölünmüş ekran ayırıcısı"
"Bölünmüş ekran ayırıcısı"
"Sol tam ekran"
@@ -133,12 +134,18 @@
"Pəncərələri idarə edin"
"Tərəflər nisbətini dəyişin"
"Görünüşü optimallaşdırın"
+ "Xəta ikonası"
"Bağlayın"
"Menyunu bağlayın"
"%1$s (Masaüstü pəncərə rejimi)"
"Ekranı maksimum böyüdün"
"Ölçüsünü dəyişin"
"Tətbiqi bura köçürmək mümkün deyil"
+ "Yeni tətbiq pəncərəsi açılır"
+ "Tətbiq pəncərəsi bağlanır"
+ "Tətbiq pəncərəsi kiçildilir"
+ "%1$s (Fokuslanmış)"
+ "%1$s (Fokuslanmamış)"
"İmmersiv"
"Bərpa edin"
"Böyüdün"
diff --git a/wmshell/res/values-b+sr+Latn/strings.xml b/wmshell/res/values-b+sr+Latn/strings.xml
index ac1be357d9..9ef65c942a 100644
--- a/wmshell/res/values-b+sr+Latn/strings.xml
+++ b/wmshell/res/values-b+sr+Latn/strings.xml
@@ -36,6 +36,7 @@
"Ova aplikacija može da se otvori samo u jednom prozoru"
"Aplikacija možda neće funkcionisati na sekundarnom ekranu."
"Aplikacija ne podržava pokretanje na sekundarnim ekranima."
+ "Aplikacija ne podržava oblačić"
"Razdelnik podeljenog ekrana"
"Razdelnik podeljenog ekrana"
"Režim celog ekrana za levi ekran"
@@ -77,14 +78,14 @@
"Odbaci oblačić"
"Prebaci na ceo ekran"
"Ne koristi oblačiće za konverzaciju"
- "Ćaskajte u oblačićima"
+ "Četujte u oblačićima"
"Nove konverzacije se prikazuju kao plutajuće ikone ili oblačići. Dodirnite da biste otvorili oblačić. Prevucite da biste ga premestili."
"Kontrolišite oblačiće u bilo kom trenutku"
"Dodirnite Upravljajte da biste isključili oblačiće iz ove aplikacije"
"Važi"
"Nema nedavnih oblačića"
"Ovde se prikazuju nedavni i odbačeni oblačići"
- "Ćaskajte u oblačićima"
+ "Četujte u oblačićima"
"Nove konverzacije se pojavljuju kao ikone u donjem uglu ekrana. Dodirnite da biste ih proširili ili prevucite da biste ih odbacili."
"Kontrolišite oblačiće u svakom trenutku"
"Dodirnite ovde i odredite koje aplikacije i konverzacije mogu da imaju oblačić"
@@ -133,12 +134,18 @@
"Upravljajte prozorima"
"Promeni razmeru"
"Optimizujte prikaz"
+ "Ikona greške"
"Zatvorite"
"Zatvorite meni"
"%1$s (prikaz računarskih prozora)"
"Povećaj ekran"
"Prilagodi"
"Aplikacija ne može da se premesti ovde"
+ "Otvara se novi prozor aplikacije"
+ "Zatvara se prozor aplikacije"
+ "Umanjuje se prozor aplikacije"
+ "%1$s (fokusirano)"
+ "%1$s (nije fokusirano)"
"Imerzivno"
"Vrati"
"Uvećaj"
diff --git a/wmshell/res/values-be/strings.xml b/wmshell/res/values-be/strings.xml
index 2a2bd9683e..db2d164c95 100644
--- a/wmshell/res/values-be/strings.xml
+++ b/wmshell/res/values-be/strings.xml
@@ -36,6 +36,7 @@
"Гэту праграму можна адкрыць толькі ў адным акне"
"Праграма можа не працаваць на дадатковых экранах."
"Праграма не падтрымлівае запуск на дадатковых экранах."
+ "У праграме не падтрымліваецца ўсплывальны чат"
"Раздзяляльнік падзеленага экрана"
"Раздзяляльнік падзеленага экрана"
"Левы экран – поўнаэкранны рэжым"
@@ -133,12 +134,18 @@
"Кіраваць вокнамі"
"Змяніць суадносіны бакоў"
"Аптымізаваць прагляд"
+ "Значок памылкі"
"Закрыць"
"Закрыць меню"
"%1$s (рэжым вокнаў працоўнага стала)"
"Разгарнуць на ўвесь экран"
"Змяніць памер"
"Нельга перамясціць сюды праграму"
+ "Адкрываецца новае акно праграмы"
+ "Закрываецца акно праграмы"
+ "Згортваецца акно праграмы"
+ "%1$s (у фокусе)"
+ "%1$s (не ў фокусе)"
"З эфектам прысутнасці"
"Аднавіць"
"Разгарнуць"
diff --git a/wmshell/res/values-bg/strings.xml b/wmshell/res/values-bg/strings.xml
index f5a93d0eb5..dcd55d7248 100644
--- a/wmshell/res/values-bg/strings.xml
+++ b/wmshell/res/values-bg/strings.xml
@@ -36,6 +36,7 @@
"Това приложение може да се отвори само в 1 прозорец"
"Възможно е приложението да не работи на алтернативни дисплеи."
"Приложението не поддържа използването на алтернативни дисплеи."
+ "Приложението не поддържа балончета"
"Разделител в режима за разделен екран"
"Разделител в режима за разделен екран"
"Ляв екран: Показване на цял екран"
@@ -133,12 +134,18 @@
"Управление на прозорците"
"Промяна на съотношението"
"Оптимизиране на изгледа"
+ "Икона за грешка"
"Затваряне"
"Затваряне на менюто"
"%1$s (режим за настолни компютри)"
"Увеличаване на екрана"
"Нов размер"
"Приложението не може да бъде преместено тук"
+ "Отваря се нов прозорец на приложението"
+ "Прозорецът на приложението се затваря"
+ "Прозорецът на приложението се намалява"
+ "%1$s (притежава фокуса)"
+ "%1$s (не притежава фокуса)"
"Реалистично"
"Възстановяване"
"Увеличаване"
diff --git a/wmshell/res/values-bn/strings.xml b/wmshell/res/values-bn/strings.xml
index 2cc109c8a0..a031c24d88 100644
--- a/wmshell/res/values-bn/strings.xml
+++ b/wmshell/res/values-bn/strings.xml
@@ -36,6 +36,7 @@
"এই অ্যাপটি শুধুমাত্র ১টি উইন্ডোতে খোলা যেতে পারে"
"সেকেন্ডারি ডিসপ্লেতে অ্যাপটি কাজ নাও করতে পারে।"
"সেকেন্ডারি ডিসপ্লেতে অ্যাপ লঞ্চ করা যাবে না।"
+ "অ্যাপে বাবল কাজ করে না"
"স্প্লিট স্ক্রিন বিভাজক"
"স্প্লিট স্ক্রিন বিভাজক"
"বাঁ দিকের অংশ নিয়ে পূর্ণ স্ক্রিন"
@@ -133,12 +134,18 @@
"উইন্ডো ম্যানেজ করুন"
"অ্যাস্পেক্ট রেশিও পরিবর্তন করুন"
"ভিউ অপ্টিমাইজ করুন"
+ "সমস্যার আইকন"
"বন্ধ করুন"
"\'মেনু\' বন্ধ করুন"
"%1$s (ডেস্কটপ উইন্ডোইং)"
"স্ক্রিন বড় করুন"
"ছোট বড় করুন"
"অ্যাপটি এখানে সরানো যাবে না"
+ "নতুন অ্যাপ উইন্ডো খোলা হচ্ছে"
+ "অ্যাপের উইন্ডো বন্ধ করা হচ্ছে"
+ "অ্যাপ উইন্ডো ছোট করা হয়েছে"
+ "%1$s (ফোকাস করা হয়েছে)"
+ "%1$s (ফোকাস করা নেই)"
"ইমারসিভ"
"ফিরিয়ে আনুন"
"বড় করুন"
diff --git a/wmshell/res/values-bs/strings.xml b/wmshell/res/values-bs/strings.xml
index 34b23ba9e1..9bce493380 100644
--- a/wmshell/res/values-bs/strings.xml
+++ b/wmshell/res/values-bs/strings.xml
@@ -36,6 +36,7 @@
"Ova aplikacija se može otvoriti samo u 1 prozoru"
"Aplikacija možda neće raditi na sekundarnom ekranu."
"Aplikacija ne podržava pokretanje na sekundarnim ekranima."
+ "Aplikacija ne podržava oblačić"
"Razdjelnik podijeljenog ekrana"
"Razdjelnik podijeljenog ekrana"
"Lijevo cijeli ekran"
@@ -133,12 +134,18 @@
"Upravljanje prozorima"
"Promjena formata slike"
"Optimiziranje prikaza"
+ "Ikona greške"
"Zatvaranje"
"Zatvaranje menija"
"%1$s (računarski prikaz prozora)"
"Maksimiziraj ekran"
"Promijeni veličinu"
"Ne možete premjestiti aplikaciju ovdje"
+ "Otvaranje novog prozora aplikacije"
+ "Zatvaranje prozora aplikacije"
+ "Minimiziranje prozora aplikacije"
+ "%1$s (fokusirano)"
+ "%1$s (nije fokusirano)"
"Uvjerljivo"
"Vraćanje"
"Maksimiziraj"
diff --git a/wmshell/res/values-ca/strings.xml b/wmshell/res/values-ca/strings.xml
index ccb12b31af..cfa93554d3 100644
--- a/wmshell/res/values-ca/strings.xml
+++ b/wmshell/res/values-ca/strings.xml
@@ -36,6 +36,7 @@
"Aquesta aplicació només pot obrir-se en 1 finestra"
"És possible que l\'aplicació no funcioni en una pantalla secundària."
"L\'aplicació no es pot obrir en pantalles secundàries."
+ "L\'aplicació no admet la bombolla"
"Separador de pantalla dividida"
"Separador de pantalla dividida"
"Pantalla esquerra completa"
@@ -133,12 +134,18 @@
"Gestiona les finestres"
"Canvia la relació d\'aspecte"
"Visualització d\'Optimize"
+ "Icona d\'error"
"Tanca"
"Tanca el menú"
"%1$s (sistema de finestres de tipus escriptori)"
"Maximitza la pantalla"
"Canvia la mida"
"L\'aplicació no es pot moure aquí"
+ "S\'està obrint una finestra nova de l\'aplicació"
+ "S\'està tancant la finestra de l\'aplicació"
+ "S\'està minimitzant la finestra de l\'aplicació"
+ "%1$s (té el focus)"
+ "%1$s (no té el focus)"
"Immersiu"
"Restaura"
"Maximitza"
diff --git a/wmshell/res/values-cs/strings.xml b/wmshell/res/values-cs/strings.xml
index 9d9d127284..4f1ac77539 100644
--- a/wmshell/res/values-cs/strings.xml
+++ b/wmshell/res/values-cs/strings.xml
@@ -36,6 +36,7 @@
"Tuto aplikaci lze otevřít jen v jednom okně"
"Aplikace na sekundárním displeji nemusí fungovat."
"Aplikace nepodporuje spuštění na sekundárních displejích."
+ "Aplikace bublinu nepodporuje"
"Čára rozdělující obrazovku"
"Čára rozdělující obrazovku"
"Levá část na celou obrazovku"
@@ -133,12 +134,18 @@
"Spravovat okna"
"Změnit poměr stran"
"Optimalizovat zobrazení"
+ "Ikona chyby"
"Zavřít"
"Zavřít nabídku"
"%1$s (okna na ploše)"
"Maximalizovat obrazovku"
"Změnit velikost"
"Aplikaci sem nelze přesunout"
+ "Otevírání nového okna aplikace"
+ "Zavírání okna aplikace"
+ "Minimalizace okna aplikace"
+ "%1$s (označeno)"
+ "%1$s (neoznačeno)"
"Pohlcující"
"Obnovit"
"Maximalizovat"
diff --git a/wmshell/res/values-da/strings.xml b/wmshell/res/values-da/strings.xml
index 17cb246033..07ae6e9b51 100644
--- a/wmshell/res/values-da/strings.xml
+++ b/wmshell/res/values-da/strings.xml
@@ -36,6 +36,7 @@
"Denne app kan kun åbnes i 1 vindue"
"Appen fungerer muligvis ikke på sekundære skærme."
"Appen kan ikke åbnes på sekundære skærme."
+ "Appen understøtter ikke bobler"
"Adskiller til opdelt skærm"
"Adskiller til opdelt skærm"
"Vis venstre del i fuld skærm"
@@ -133,12 +134,18 @@
"Administrer vinduer"
"Skift billedformat"
"Optimer visning"
+ "Fejlikon"
"Luk"
"Luk menu"
"%1$s (vinduer på skrivebordet)"
"Maksimér skærm"
"Tilpas størrelse"
"Apps kan ikke flyttes hertil"
+ "Et nyt appvindue er åbnet"
+ "Lukker appvindue"
+ "Appvinduet er minimeret"
+ "%1$s (fremhævet)"
+ "%1$s (ikke fremhævet)"
"Opslugende"
"Gendan"
"Maksimér"
diff --git a/wmshell/res/values-de/strings.xml b/wmshell/res/values-de/strings.xml
index c84d01437e..9a4b4529f2 100644
--- a/wmshell/res/values-de/strings.xml
+++ b/wmshell/res/values-de/strings.xml
@@ -36,6 +36,7 @@
"Diese App kann nur in einem einzigen Fenster geöffnet werden"
"Die App funktioniert auf einem sekundären Display möglicherweise nicht."
"Die App unterstützt den Start auf sekundären Displays nicht."
+ "App unterstützt keine Bubbles"
"Bildschirmteiler"
"Bildschirmteiler"
"Vollbild links"
@@ -133,12 +134,18 @@
"Fenster verwalten"
"Seitenverhältnis ändern"
"Ansicht optimieren"
+ "Fehlersymbol"
"Schließen"
"Menü schließen"
"%1$s (Desktop-Freiform-Fenster)"
"Bildschirm maximieren"
"Größe ändern"
"Die App kann nicht hierher verschoben werden"
+ "Neues App-Fenster wird geöffnet"
+ "App-Fenster wird geschlossen"
+ "App-Fenster wird minimiert"
+ "%1$s (Fokussiert)"
+ "%1$s (Nicht fokussiert)"
"Immersiv"
"Wiederherstellen"
"Maximieren"
diff --git a/wmshell/res/values-el/strings.xml b/wmshell/res/values-el/strings.xml
index 9faf44bcc1..a85b1ceed1 100644
--- a/wmshell/res/values-el/strings.xml
+++ b/wmshell/res/values-el/strings.xml
@@ -36,6 +36,7 @@
"Αυτή η εφαρμογή μπορεί να ανοίξει μόνο σε ένα παράθυρο"
"Η εφαρμογή ίσως να μην λειτουργήσει σε δευτερεύουσα οθόνη."
"Η εφαρμογή δεν υποστηρίζει την εκκίνηση σε δευτερεύουσες οθόνες."
+ "Η εφαρμογή δεν υποστηρίζει συννεφάκια"
"Διαχωριστικό οθόνης"
"Διαχωριστικό οθόνης"
"Αριστερή πλήρης οθόνη"
@@ -133,12 +134,18 @@
"Διαχείριση παραθύρων"
"Αλλαγή λόγου διαστάσεων"
"Βελτιστοποίηση προβολής"
+ "Εικονίδιο σφάλματος"
"Κλείσιμο"
"Κλείσιμο μενού"
"%1$s (Προσαρμογή σε παράθυρο στην επιφάνεια εργασίας)"
"Μεγιστοποίηση οθόνης"
"Αλλαγή μεγέθους"
"Δεν είναι δυνατή η μετακίνηση της εφαρμογής εδώ"
+ "Άνοιγμα νέου παραθύρου εφαρμογής"
+ "Κλείσιμο παραθύρου εφαρμογής"
+ "Ελαχιστοποίηση παραθύρου εφαρμογής"
+ "%1$s (Με εστίαση)"
+ "%1$s (Χωρίς εστίαση)"
"Καθηλωτικό"
"Επαναφορά"
"Μεγιστοποίηση"
diff --git a/wmshell/res/values-en-rAU/strings.xml b/wmshell/res/values-en-rAU/strings.xml
index dcc055449f..8a19f39182 100644
--- a/wmshell/res/values-en-rAU/strings.xml
+++ b/wmshell/res/values-en-rAU/strings.xml
@@ -36,6 +36,7 @@
"This app can only be opened in one window"
"App may not work on a secondary display."
"App does not support launch on secondary displays."
+ "App does not support bubble"
"Split screen divider"
"Split screen divider"
"Left full screen"
@@ -133,12 +134,18 @@
"Manage windows"
"Change aspect ratio"
"Optimise view"
+ "Error icon"
"Close"
"Close menu"
"%1$s (Desktop windowing)"
"Maximise screen"
"Resize"
"App can\'t be moved here"
+ "Opening new app window"
+ "Closing app window"
+ "Minimising app window"
+ "%1$s (focused)"
+ "%1$s (not focused)"
"Immersive"
"Restore"
"Maximise"
diff --git a/wmshell/res/values-en-rCA/strings.xml b/wmshell/res/values-en-rCA/strings.xml
index 3e2de7cd3b..094203d8cb 100644
--- a/wmshell/res/values-en-rCA/strings.xml
+++ b/wmshell/res/values-en-rCA/strings.xml
@@ -36,6 +36,7 @@
"This app can only be opened in 1 window"
"App may not work on a secondary display."
"App does not support launch on secondary displays."
+ "App does not support bubble"
"Split screen divider"
"Split screen divider"
"Left full screen"
@@ -133,12 +134,18 @@
"Manage Windows"
"Change aspect ratio"
"Optimize View"
+ "Error Icon"
"Close"
"Close Menu"
"%1$s (Desktop windowing)"
"Maximize Screen"
"Resize"
"App can\'t be moved here"
+ "Opening new app window"
+ "Closing app window"
+ "Minimizing app window"
+ "%1$s (Focused)"
+ "%1$s (Not Focused)"
"Immersive"
"Restore"
"Maximize"
diff --git a/wmshell/res/values-en-rGB/strings.xml b/wmshell/res/values-en-rGB/strings.xml
index dcc055449f..8a19f39182 100644
--- a/wmshell/res/values-en-rGB/strings.xml
+++ b/wmshell/res/values-en-rGB/strings.xml
@@ -36,6 +36,7 @@
"This app can only be opened in one window"
"App may not work on a secondary display."
"App does not support launch on secondary displays."
+ "App does not support bubble"
"Split screen divider"
"Split screen divider"
"Left full screen"
@@ -133,12 +134,18 @@
"Manage windows"
"Change aspect ratio"
"Optimise view"
+ "Error icon"
"Close"
"Close menu"
"%1$s (Desktop windowing)"
"Maximise screen"
"Resize"
"App can\'t be moved here"
+ "Opening new app window"
+ "Closing app window"
+ "Minimising app window"
+ "%1$s (focused)"
+ "%1$s (not focused)"
"Immersive"
"Restore"
"Maximise"
diff --git a/wmshell/res/values-en-rIN/strings.xml b/wmshell/res/values-en-rIN/strings.xml
index dcc055449f..8a19f39182 100644
--- a/wmshell/res/values-en-rIN/strings.xml
+++ b/wmshell/res/values-en-rIN/strings.xml
@@ -36,6 +36,7 @@
"This app can only be opened in one window"
"App may not work on a secondary display."
"App does not support launch on secondary displays."
+ "App does not support bubble"
"Split screen divider"
"Split screen divider"
"Left full screen"
@@ -133,12 +134,18 @@
"Manage windows"
"Change aspect ratio"
"Optimise view"
+ "Error icon"
"Close"
"Close menu"
"%1$s (Desktop windowing)"
"Maximise screen"
"Resize"
"App can\'t be moved here"
+ "Opening new app window"
+ "Closing app window"
+ "Minimising app window"
+ "%1$s (focused)"
+ "%1$s (not focused)"
"Immersive"
"Restore"
"Maximise"
diff --git a/wmshell/res/values-es-rUS/strings.xml b/wmshell/res/values-es-rUS/strings.xml
index 0f048a5324..66a4b10e2c 100644
--- a/wmshell/res/values-es-rUS/strings.xml
+++ b/wmshell/res/values-es-rUS/strings.xml
@@ -36,6 +36,7 @@
"Esta app solo puede estar abierta en 1 ventana"
"Es posible que la app no funcione en una pantalla secundaria."
"La app no puede iniciarse en pantallas secundarias."
+ "La app no admite la burbuja"
"Divisor de pantalla dividida"
"Divisor de pantalla dividida"
"Pantalla izquierda completa"
@@ -133,12 +134,18 @@
"Administrar ventanas"
"Cambiar relación de aspecto"
"Optimizar vista"
+ "Ícono de error"
"Cerrar"
"Cerrar menú"
"%1$s (renderización en ventanas de escritorio)"
"Maximizar pantalla"
"Dividir pantalla"
"No se puede mover la app aquí"
+ "Abriendo una nueva ventana de la app"
+ "Cerrando la ventana de la app"
+ "Minimizando la ventana de la app"
+ "%1$s (enfocada)"
+ "%1$s (sin enfocar)"
"Inmersivo"
"Restablecer"
"Maximizar"
diff --git a/wmshell/res/values-es/strings.xml b/wmshell/res/values-es/strings.xml
index ab2b70e7cd..ca500a77c6 100644
--- a/wmshell/res/values-es/strings.xml
+++ b/wmshell/res/values-es/strings.xml
@@ -36,6 +36,7 @@
"Esta aplicación solo puede abrirse en una ventana"
"Es posible que la aplicación no funcione en una pantalla secundaria."
"La aplicación no se puede abrir en pantallas secundarias."
+ "La aplicación no es compatible con burbujas"
"Divisor de pantalla dividida"
"Divisor de pantalla dividida"
"Pantalla izquierda completa"
@@ -133,12 +134,18 @@
"Gestionar ventanas"
"Cambiar relación de aspecto"
"Optimizar vista"
+ "Icono de error"
"Cerrar"
"Cerrar menú"
"%1$s (modo Ventanas)"
"Maximizar pantalla"
"Dividir pantalla"
"La aplicación no se puede mover aquí"
+ "Abriendo nueva ventana de la aplicación"
+ "Cerrando ventana de la aplicación"
+ "Minimizando ventana de la aplicación"
+ "%1$s (enfocada)"
+ "%1$s (no enfocada)"
"Inmersivo"
"Restaurar"
"Maximizar"
diff --git a/wmshell/res/values-et/strings.xml b/wmshell/res/values-et/strings.xml
index 3560439318..541240bd8a 100644
--- a/wmshell/res/values-et/strings.xml
+++ b/wmshell/res/values-et/strings.xml
@@ -36,6 +36,7 @@
"Selle rakenduse saab avada ainult ühes aknas"
"Rakendus ei pruugi teisesel ekraanil töötada."
"Rakendus ei toeta teisestel ekraanidel käivitamist."
+ "Rakendus ei toeta mulle"
"Jagatud ekraanikuva jaotur"
"Jagatud ekraanikuva jaotur"
"Vasak täisekraan"
@@ -133,12 +134,18 @@
"Akende haldamine"
"Kuvasuhte muutmine"
"Vaate optimeerimine"
+ "Veaikoon"
"Sule"
"Sule menüü"
"%1$s (töölaua aknad)"
"Kuva täisekraanil"
"Suuruse muutmine"
"Rakendust ei saa siia teisaldada"
+ "Rakenduse avamine uues aknas"
+ "Rakenduse akna sulgemine"
+ "Rakenduse akna minimeerimine"
+ "%1$s (fookuses)"
+ "%1$s (mitte fookuses)"
"Mahuta"
"Taasta"
"Maksimeeri"
diff --git a/wmshell/res/values-eu/strings.xml b/wmshell/res/values-eu/strings.xml
index 7a2e11a63e..c3530db55a 100644
--- a/wmshell/res/values-eu/strings.xml
+++ b/wmshell/res/values-eu/strings.xml
@@ -36,6 +36,7 @@
"Leiho bakar batean ireki daiteke aplikazioa"
"Baliteke aplikazioak ez funtzionatzea bigarren mailako pantailetan."
"Aplikazioa ezin da exekutatu bigarren mailako pantailatan."
+ "Aplikazioak ez ditu onartzen burbuilak"
"Pantaila-zatitzailea"
"Pantaila-zatitzailea"
"Ezarri ezkerraldea pantaila osoan"
@@ -133,12 +134,18 @@
"Kudeatu leihoak"
"Aldatu aspektu-erlazioa"
"Optimizatu ikuspegia"
+ "Errore-ikonoa"
"Itxi"
"Itxi menua"
"%1$s (ordenagailuan leihoak erabiltzeko modua)"
"Handitu pantaila"
"Aldatu tamaina"
"Aplikazioa ezin da hona ekarri"
+ "Ireki da aplikazioaren leiho berri bat"
+ "Aplikazioaren leihoa ixten"
+ "Minimizatu da aplikazioaren leihoa"
+ "%1$s (fokuratuta)"
+ "%1$s (fokuratu gabe)"
"Murgiltzailea"
"Leheneratu"
"Maximizatu"
diff --git a/wmshell/res/values-fa/strings.xml b/wmshell/res/values-fa/strings.xml
index 0b11c8d5bc..f889737796 100644
--- a/wmshell/res/values-fa/strings.xml
+++ b/wmshell/res/values-fa/strings.xml
@@ -36,6 +36,7 @@
"این برنامه فقط در ۱ پنجره میتواند باز شود"
"ممکن است برنامه در نمایشگر ثانویه کار نکند."
"برنامه از راهاندازی در نمایشگرهای ثانویه پشتیبانی نمیکند."
+ "این برنامه از حبابک پشتیبانی نمیکند"
"تقسیمکننده صفحهٔ دونیمه"
"تقسیمکننده صفحهٔ دونیمه"
"تمامصفحه چپ"
@@ -133,12 +134,18 @@
"مدیریت کردن پنجرهها"
"تغییر نسبت ابعادی"
"بهینهسازی نما"
+ "نماد خطا"
"بستن"
"بستن منو"
"%1$s (پردازش پنجرهای رایانه)"
"بزرگ کردن صفحه"
"تغییر اندازه"
"برنامه را نمیتوان به اینجا منتقل کرد"
+ "باز کردن پنجره برنامه جدید"
+ "بستن پنجره برنامه"
+ "کوچک کردن پنجره برنامه"
+ "%1$s (کانونیشده)"
+ "%1$s (کانونینشده)"
"فراگیر"
"بازیابی"
"بزرگ کردن"
diff --git a/wmshell/res/values-fi/strings.xml b/wmshell/res/values-fi/strings.xml
index d50d693041..a33939e17e 100644
--- a/wmshell/res/values-fi/strings.xml
+++ b/wmshell/res/values-fi/strings.xml
@@ -36,6 +36,7 @@
"Tämän sovelluksen voi avata vain yhdessä ikkunassa"
"Sovellus ei ehkä toimi toissijaisella näytöllä."
"Sovellus ei tue käynnistämistä toissijaisilla näytöillä."
+ "Sovellus ei tue kuplia"
"Näytönjakaja"
"Näytönjakaja"
"Vasen koko näytölle"
@@ -133,12 +134,18 @@
"Hallinnoi ikkunoita"
"Vaihda kuvasuhdetta"
"Optimoi näkymä"
+ "Virhekuvake"
"Sulje"
"Sulje valikko"
"%1$s (työpöydän ikkunointi)"
"Suurenna näyttö"
"Muuta kokoa"
"Sovellusta ei voi siirtää tänne"
+ "Avataan uutta sovellusikkunaa"
+ "Suljetaan sovellusikkunaa"
+ "Pienennetään sovellusikkunaa"
+ "%1$s (Keskitetty)"
+ "%1$s (Ei keskitetty)"
"Immersiivinen"
"Palauta"
"Suurenna"
diff --git a/wmshell/res/values-fr-rCA/strings.xml b/wmshell/res/values-fr-rCA/strings.xml
index 5a81df2b32..d5612ec98d 100644
--- a/wmshell/res/values-fr-rCA/strings.xml
+++ b/wmshell/res/values-fr-rCA/strings.xml
@@ -36,6 +36,7 @@
"Cette appli ne peut être ouverte que dans une seule fenêtre."
"Il est possible que l\'appli ne fonctionne pas sur un écran secondaire."
"L\'appli ne peut pas être lancée sur des écrans secondaires."
+ "L\'appli ne prend pas en charge la bulle"
"Séparateur d\'écran partagé"
"Séparateur d\'écran partagé"
"Plein écran à la gauche"
@@ -133,12 +134,18 @@
"Gérer les fenêtres"
"Modifier les proportions"
"Optimiser l\'affichage"
+ "Icône d\'erreur"
"Fermer"
"Fermer le menu"
"%1$s (fenêtrage du bureau)"
"Agrandir l\'écran"
"Redimensionner"
"Impossible de déplacer l\'appli ici"
+ "Ouverture d\'une nouvelle fenêtre d\'appli en cours…"
+ "Fermeture de la fenêtre de l\'appli en cours…"
+ "Réduction de la fenêtre d\'appli en cours…"
+ "%1$s (mise en évidence)"
+ "%1$s (non mise en évidence)"
"Immersif"
"Restaurer"
"Agrandir"
diff --git a/wmshell/res/values-fr/strings.xml b/wmshell/res/values-fr/strings.xml
index 6c260d166b..0868fa0e83 100644
--- a/wmshell/res/values-fr/strings.xml
+++ b/wmshell/res/values-fr/strings.xml
@@ -36,6 +36,7 @@
"Cette appli ne peut être ouverte que dans 1 fenêtre"
"Il est possible que l\'application ne fonctionne pas sur un écran secondaire."
"L\'application ne peut pas être lancée sur des écrans secondaires."
+ "L\'appli n\'est pas compatible avec les bulles"
"Séparateur d\'écran partagé"
"Séparateur d\'écran partagé"
"Écran de gauche en plein écran"
@@ -55,7 +56,7 @@
"Affichée en haut"
"Affichée en haut"
"Utilisation du mode une main"
- "Pour quitter, balayez l\'écran de bas en haut ou appuyez n\'importe où au-dessus de l\'application"
+ "Pour quitter, balayez vers le haut depuis le bas de l\'écran ou appuyez n\'importe où au-dessus de l\'application"
"Démarrer le mode une main"
"Quitter le mode une main"
"Paramètres des bulles de %1$s"
@@ -133,12 +134,18 @@
"Gérer les fenêtres"
"Modifier le format"
"Optimiser l\'affichage"
+ "Icône Erreur"
"Fermer"
"Fermer le menu"
"%1$s (fenêtrage de bureau)"
"Mettre en plein écran"
"Redimensionner"
"Impossible de déplacer l\'appli ici"
+ "Ouverture d\'une nouvelle fenêtre de l\'application"
+ "Fermeture de la fenêtre de l\'application"
+ "Réduction de la fenêtre de l\'application"
+ "%1$s (sélectionné)"
+ "%1$s (non sélectionné)"
"Immersif"
"Restaurer"
"Agrandir"
diff --git a/wmshell/res/values-gl/strings.xml b/wmshell/res/values-gl/strings.xml
index b9ccf765c8..5edadbacb3 100644
--- a/wmshell/res/values-gl/strings.xml
+++ b/wmshell/res/values-gl/strings.xml
@@ -36,6 +36,7 @@
"Esta aplicación só se pode abrir en 1 ventá"
"É posible que a aplicación non funcione nunha pantalla secundaria."
"A aplicación non se pode iniciar en pantallas secundarias."
+ "A aplicación non admite as burbullas"
"Divisor de pantalla dividida"
"Divisor de pantalla dividida"
"Pantalla completa á esquerda"
@@ -133,12 +134,18 @@
"Xestionar as ventás"
"Cambiar a proporción"
"Optimizar a vista"
+ "Icona de erro"
"Pechar"
"Pechar o menú"
"%1$s (modo con ventás tipo ordenador)"
"Maximizar pantalla"
"Cambiar tamaño"
"Non se pode mover aquí a aplicación"
+ "Abrindo nova ventá da aplicación"
+ "Pechando ventá da aplicación"
+ "Minimizando ventá da aplicación"
+ "%1$s (enfocada)"
+ "%1$s (non enfocada)"
"Envolvente"
"Restaurar"
"Maximizar"
diff --git a/wmshell/res/values-gu/strings.xml b/wmshell/res/values-gu/strings.xml
index 50bf7b5fcf..857446be11 100644
--- a/wmshell/res/values-gu/strings.xml
+++ b/wmshell/res/values-gu/strings.xml
@@ -36,6 +36,7 @@
"આ ઍપ માત્ર 1 વિન્ડોમાં ખોલી શકાય છે"
"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર કદાચ કામ ન કરે."
"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર લૉન્ચનું સમર્થન કરતી નથી."
+ "ઍપ બબલને સપોર્ટ કરતી નથી"
"સ્ક્રીનને વિભાજિત કરતું વિભાજક"
"સ્ક્રીનને વિભાજિત કરતું વિભાજક"
"ડાબી પૂર્ણ સ્ક્રીન"
@@ -133,12 +134,18 @@
"વિન્ડો મેનેજ કરો"
"સાપેક્ષ ગુણોત્તર બદલો"
"વ્યૂ ઑપ્ટિમાઇઝ કરો"
+ "ભૂલનું આઇકન"
"બંધ કરો"
"મેનૂ બંધ કરો"
"%1$s (ડેસ્કટૉપ વિન્ડોઇંગ)"
"સ્ક્રીન કરો મોટી કરો"
"કદ બદલો"
"ઍપ અહીં ખસેડી શકાતી નથી"
+ "નવી વિન્ડો ખોલવી"
+ "ઍપની વિન્ડો બંધ કરી રહ્યાં છીએ"
+ "ઍપ વિન્ડો ન્યૂનતમ કરવી"
+ "%1$s (ફોકસ કરેલ)"
+ "%1$s (ફોકસ ન કરેલી)"
"ઇમર્સિવ"
"રિસ્ટોર કરો"
"મોટું કરો"
diff --git a/wmshell/res/values-hi/strings.xml b/wmshell/res/values-hi/strings.xml
index a0dd30bc2f..89a2249c8e 100644
--- a/wmshell/res/values-hi/strings.xml
+++ b/wmshell/res/values-hi/strings.xml
@@ -36,6 +36,7 @@
"इस ऐप्लिकेशन को सिर्फ़ एक विंडो में खोला जा सकता है"
"हो सकता है कि ऐप प्राइमरी (मुख्य) डिस्प्ले के अलावा बाकी दूसरे डिस्प्ले पर काम न करे."
"प्राइमरी (मुख्य) डिस्प्ले के अलावा बाकी दूसरे डिस्प्ले पर ऐप लॉन्च नहीं किया जा सकता."
+ "इस ऐप्लिकेशन पर बबल की सुविधा काम नहीं करती"
"स्प्लिट स्क्रीन डिवाइडर मोड"
"स्प्लिट स्क्रीन डिवाइडर मोड"
"बाईं स्क्रीन को फ़ुल स्क्रीन बनाएं"
@@ -133,12 +134,18 @@
"विंडो मैनेज करें"
"आसपेक्ट रेशियो (लंबाई-चौड़ाई का अनुपात) बदलें"
"व्यू को ऑप्टिमाइज़ करें"
+ "गड़बड़ी का आइकॉन"
"बंद करें"
"मेन्यू बंद करें"
"%1$s (डेस्कटॉप विंडोविंग)"
"स्क्रीन को बड़ा करें"
"साइज़ बदलें"
"ऐप्लिकेशन को यहां मूव नहीं किया जा सकता"
+ "ऐप्लिकेशन की नई विंडो खोली जा रही है"
+ "ऐप्लिकेशन की विंडो बंद की जा रही है"
+ "ऐप्लिकेशन की विंडो को छोटा किया जा रहा है"
+ "%1$s (फ़ोकस किया गया)"
+ "%1$s (फ़ोकस नहीं किया गया)"
"इमर्सिव"
"वापस लाएं"
"बड़ा करें"
diff --git a/wmshell/res/values-hr/strings.xml b/wmshell/res/values-hr/strings.xml
index c9e4d13f74..c0393b34c2 100644
--- a/wmshell/res/values-hr/strings.xml
+++ b/wmshell/res/values-hr/strings.xml
@@ -36,6 +36,7 @@
"Ova se aplikacija može otvoriti samo u jednom prozoru"
"Aplikacija možda neće funkcionirati na sekundarnom zaslonu."
"Aplikacija ne podržava pokretanje na sekundarnim zaslonima."
+ "Aplikacija ne podržava oblačić"
"Razdjelnik podijeljenog zaslona"
"Razdjelnik podijeljenog zaslona"
"Lijevi zaslon u cijeli zaslon"
@@ -133,12 +134,18 @@
"Upravljanje prozorima"
"Promijeni omjer slike"
"Optimiziraj prikaz"
+ "Ikona pogreške"
"Zatvorite"
"Zatvorite izbornik"
"%1$s (prikaz u prozorima na računalu)"
"Maksimalno povećaj zaslon"
"Promijeni veličinu"
"Aplikacija se ne može premjestiti ovdje"
+ "Otvaranje novog prozora aplikacije"
+ "Zatvaranje prozora aplikacije"
+ "Minimiziranje prozora aplikacije"
+ "%1$s (fokusirano)"
+ "%1$s (nije fokusirano)"
"Interaktivno"
"Vrati"
"Maksimiziraj"
diff --git a/wmshell/res/values-hu/strings.xml b/wmshell/res/values-hu/strings.xml
index 6dae47f83d..ea2ddc94ca 100644
--- a/wmshell/res/values-hu/strings.xml
+++ b/wmshell/res/values-hu/strings.xml
@@ -36,6 +36,7 @@
"Ez az alkalmazás csak egy ablakban nyitható meg"
"Előfordulhat, hogy az alkalmazás nem működik másodlagos kijelzőn."
"Az alkalmazást nem lehet másodlagos kijelzőn elindítani."
+ "Az alkalmazás nem támogatja a buborékot"
"Elválasztó az osztott képernyős nézetben"
"Elválasztó az osztott képernyős nézetben"
"Bal oldali teljes képernyőre"
@@ -133,12 +134,18 @@
"Ablakok kezelése"
"Méretarány módosítása"
"Nézet optimalizálása"
+ "Hiba ikon"
"Bezárás"
"Menü bezárása"
"%1$s (Asztali ablakkezelési mód)"
"Képernyő méretének maximalizálása"
"Átméretezés"
"Az alkalmazás nem helyezhető át ide"
+ "Új alkalmazásablak megnyitása…"
+ "Alkalmazásablak bezárása"
+ "Alkalmazásablak kis méretre állítása…"
+ "%1$s (Fókuszált)"
+ "%1$s (Nincs fókuszban)"
"Magával ragadó"
"Visszaállítás"
"Teljes méret"
diff --git a/wmshell/res/values-hy/strings.xml b/wmshell/res/values-hy/strings.xml
index b32bb946fa..8721d574e8 100644
--- a/wmshell/res/values-hy/strings.xml
+++ b/wmshell/res/values-hy/strings.xml
@@ -36,6 +36,7 @@
"Այս հավելվածը հնարավոր է բացել միայն մեկ պատուհանում"
"Հավելվածը կարող է չաշխատել լրացուցիչ էկրանի վրա"
"Հավելվածը չի աջակցում գործարկումը լրացուցիչ էկրանների վրա"
+ "Հավելվածը չի աջակցում ամպիկ"
"Տրոհված էկրանի բաժանիչ"
"Տրոհված էկրանի բաժանիչ"
"Ձախ էկրանը՝ լիաէկրան"
@@ -133,12 +134,18 @@
"Կառավարել պատուհանները"
"Փոխել կողմերի հարաբերակցությունը"
"Օպտիմալացնել"
+ "Սխալի պատկերակ"
"Փակել"
"Փակել ընտրացանկը"
"%1$s (համակարգչային պատուհաններ)"
"Ծավալել էկրանը"
"Փոխել չափը"
"Հավելվածը հնարավոր չէ տեղափոխել այստեղ"
+ "Հավելվածի նոր պատուհանը բացվում է"
+ "Հավելվածի պատուհանը փակվում է"
+ "Հավելվածի պատուհանը ծալվում է"
+ "Ընտրված է %1$s հավելվածը"
+ "%1$s հավելվածն ընտրված չէ"
"Ներկայության էֆեկտով"
"Վերականգնել"
"Ծավալել"
diff --git a/wmshell/res/values-in/strings.xml b/wmshell/res/values-in/strings.xml
index 14c6113a98..ba6c8fc5c4 100644
--- a/wmshell/res/values-in/strings.xml
+++ b/wmshell/res/values-in/strings.xml
@@ -36,6 +36,7 @@
"Aplikasi ini hanya dapat dibuka di 1 jendela"
"Aplikasi mungkin tidak berfungsi pada layar sekunder."
"Aplikasi tidak mendukung peluncuran pada layar sekunder."
+ "Aplikasi tidak mendukung balon"
"Pembagi layar terpisah"
"Pembagi layar terpisah"
"Layar penuh di kiri"
@@ -133,12 +134,18 @@
"Kelola Jendela"
"Ubah rasio aspek"
"Optimalkan Tampilan"
+ "Ikon Error"
"Tutup"
"Tutup Menu"
"%1$s (Mode jendela desktop)"
"Perbesar Layar"
"Ubah ukuran"
"Aplikasi tidak dapat dipindahkan ke sini"
+ "Membuka jendela aplikasi baru"
+ "Menutup jendela aplikasi"
+ "Meminimalkan jendela aplikasi"
+ "%1$s (Difokuskan)"
+ "%1$s (Tidak Difokuskan)"
"Imersif"
"Pulihkan"
"Maksimalkan"
diff --git a/wmshell/res/values-is/strings.xml b/wmshell/res/values-is/strings.xml
index 9e61839c86..74ade660c6 100644
--- a/wmshell/res/values-is/strings.xml
+++ b/wmshell/res/values-is/strings.xml
@@ -36,6 +36,7 @@
"Aðeins er hægt að opna þetta forrit í 1 glugga"
"Hugsanlegt er að forritið virki ekki á öðrum skjá."
"Forrit styður ekki opnun á öðrum skjá."
+ "Forrit styður ekki blöðru"
"Skilrúm skjáskiptingar"
"Skilrúm skjáskiptingar"
"Vinstri á öllum skjánum"
@@ -133,12 +134,18 @@
"Stjórna gluggum"
"Breyta myndhlutfalli"
"Fínstilla yfirlit"
+ "Villutákn"
"Loka"
"Loka valmynd"
"%1$s (gluggastilling í tölvu)"
"Stækka skjá"
"Breyta stærð"
"Ekki er hægt að færa forritið hingað"
+ "Opnar nýjan forritsglugga"
+ "Lokar forritsglugga"
+ "Minnkar forritsglugga"
+ "%1$s (í fókus)"
+ "%1$s (ekki í fókus)"
"Umlykjandi"
"Endurheimta"
"Stækka"
diff --git a/wmshell/res/values-it/strings.xml b/wmshell/res/values-it/strings.xml
index 34b66b127b..ad3f0d3e5a 100644
--- a/wmshell/res/values-it/strings.xml
+++ b/wmshell/res/values-it/strings.xml
@@ -36,6 +36,7 @@
"Questa app può essere aperta soltanto in 1 finestra"
"L\'app potrebbe non funzionare su un display secondario."
"L\'app non supporta l\'avvio su display secondari."
+ "L\'app non supporta la riduzione a icona"
"Strumento per schermo diviso"
"Strumento per schermo diviso"
"Schermata sinistra a schermo intero"
@@ -133,12 +134,18 @@
"Gestisci finestre"
"Cambia proporzioni"
"Ottimizza visualizzazione"
+ "Icona di errore"
"Chiudi"
"Chiudi il menu"
"%1$s (app in finestre)"
"Massimizza schermo"
"Ridimensiona"
"Impossibile spostare l\'app qui"
+ "Apertura di una nuova finestra dell\'app"
+ "Chiusura della finestra app in corso…"
+ "Riduzione a icona della finestra dell\'app"
+ "%1$s (selezionata)"
+ "%1$s (non selezionata)"
"Immersivo"
"Ripristina"
"Ingrandisci"
diff --git a/wmshell/res/values-iw/strings.xml b/wmshell/res/values-iw/strings.xml
index e02b706b11..1cccdb9cc4 100644
--- a/wmshell/res/values-iw/strings.xml
+++ b/wmshell/res/values-iw/strings.xml
@@ -36,6 +36,7 @@
"ניתן לפתוח את האפליקציה הזו רק בחלון אחד"
"ייתכן שהאפליקציה לא תפעל במסך משני."
"האפליקציה אינה תומכת בהפעלה במסכים משניים."
+ "האפליקציה לא תומכת בבועה"
"מחלק מסך מפוצל"
"מחלק מסך מפוצל"
"מסך שמאלי מלא"
@@ -133,12 +134,18 @@
"ניהול החלונות"
"שינוי יחס הגובה-רוחב"
"אופטימיזציה של התצוגה"
+ "סמל שגיאה"
"סגירה"
"סגירת התפריט"
"%1$s (שינוי דינמי של חלונות במחשב)"
"הגדלת המסך"
"שינוי הגודל"
"לא ניתן להעביר את האפליקציה לכאן"
+ "נפתח חלון חדש באפליקציה"
+ "החלון של האפליקציה עומד להיסגר"
+ "חלון האפליקציה התמזער"
+ "%1$s (במיקוד)"
+ "%1$s (לא במיקוד)"
"הטמעה"
"שחזור"
"הגדלה"
diff --git a/wmshell/res/values-ja/strings.xml b/wmshell/res/values-ja/strings.xml
index 19ac853d48..d2857861af 100644
--- a/wmshell/res/values-ja/strings.xml
+++ b/wmshell/res/values-ja/strings.xml
@@ -36,6 +36,7 @@
"このアプリはウィンドウが 1 つの場合のみ開くことができます"
"アプリはセカンダリ ディスプレイでは動作しないことがあります。"
"アプリはセカンダリ ディスプレイでの起動に対応していません。"
+ "アプリがふきだしに対応していません"
"分割画面の分割線"
"分割画面の分割線"
"左全画面"
@@ -133,12 +134,18 @@
"ウィンドウを管理する"
"アスペクト比を変更"
"ビューを最適化"
+ "エラーアイコン"
"閉じる"
"メニューを閉じる"
"%1$s(デスクトップ ウィンドウ)"
"画面の最大化"
"サイズ変更"
"アプリはここに移動できません"
+ "新しいアプリ ウィンドウを開いています"
+ "アプリのウィンドウを閉じています"
+ "アプリ ウィンドウを最小化しています"
+ "%1$s(フォーカス)"
+ "%1$s(非フォーカス)"
"没入モード"
"復元"
"最大化"
diff --git a/wmshell/res/values-ka/strings.xml b/wmshell/res/values-ka/strings.xml
index 97e5b8b22f..f604879e5a 100644
--- a/wmshell/res/values-ka/strings.xml
+++ b/wmshell/res/values-ka/strings.xml
@@ -36,6 +36,7 @@
"ამ აპის გახსნა შესაძლებელია მხოლოდ 1 ფანჯარაში"
"აპმა შეიძლება არ იმუშაოს მეორეულ ეკრანზე."
"აპს არ გააჩნია მეორეული ეკრანის მხარდაჭერა."
+ "ბუშტი არ არის მხარდაჭერილი აპის მიერ"
"ეკრანის გაყოფის გამყოფი"
"ეკრანის გაყოფის გამყოფი"
"მარცხენა ნაწილის სრულ ეკრანზე გაშლა"
@@ -133,12 +134,18 @@
"ფანჯრების მართვა"
"თანაფარდობის შეცვლა"
"ხედის ოპტიმიზება"
+ "შეცდომის ხატულა"
"დახურვა"
"მენიუს დახურვა"
"%1$s (დესკტოპის ფანჯრის რეჟიმი)"
"აპლიკაციის გაშლა სრულ ეკრანზე"
"ზომის შეცვლა"
"აპის აქ გადატანა შეუძლებელია"
+ "აპის ახალი ფანჯარა იხსნება"
+ "მიმდინარეობს აპის ფანჯრის დახურვა"
+ "აპის ფანჯარა იკეცება"
+ "%1$s (ფოკუსირებული)"
+ "%1$s (ფოკუსის გარეთ)"
"იმერსიული"
"აღდგენა"
"მაქსიმალურად გაშლა"
diff --git a/wmshell/res/values-kk/strings.xml b/wmshell/res/values-kk/strings.xml
index 21f0496588..d96485c65f 100644
--- a/wmshell/res/values-kk/strings.xml
+++ b/wmshell/res/values-kk/strings.xml
@@ -36,6 +36,7 @@
"Бұл қолданбаны тек 1 терезеден ашуға болады."
"Қолданба қосымша дисплейде жұмыс істемеуі мүмкін."
"Қолданба қосымша дисплейлерде іске қосуды қолдамайды."
+ "Қолданба қалқыма терезеге қолдау көрсетпейді."
"Экранды бөлу режимінің бөлгіші"
"Экранды бөлу режимінің бөлгіші"
"Сол жағын толық экранға шығару"
@@ -133,12 +134,18 @@
"Терезелерді басқару"
"Арақатынасты өзгерту"
"Көріністі оңтайландыру"
+ "Қате белгішесі"
"Жабу"
"Мәзірді жабу"
"%1$s (компьютерлік терезелер режимі)"
"Экранды ұлғайту"
"Өлшемін өзгерту"
"Қолданба бұл жерге қойылмайды."
+ "Жаңа қолданба терезесін ашу"
+ "Қолданба терезесі жабылып жатыр"
+ "Қолданба терезесін кішірейту"
+ "%1$s (ерекшеленген)"
+ "%1$s (ерекшеленбеген)"
"Иммерсивтік әсер"
"Қалпына келтіру"
"Жаю"
diff --git a/wmshell/res/values-km/strings.xml b/wmshell/res/values-km/strings.xml
index 99817c07f9..1fb1aaca9e 100644
--- a/wmshell/res/values-km/strings.xml
+++ b/wmshell/res/values-km/strings.xml
@@ -36,6 +36,7 @@
"អាចបើកកម្មវិធីនេះបានតែក្នុងវិនដូ 1 ប៉ុណ្ណោះ"
"កម្មវិធីនេះប្រហែលជាមិនដំណើរការនៅលើអេក្រង់បន្ទាប់បន្សំទេ។"
"កម្មវិធីនេះមិនអាចចាប់ផ្តើមនៅលើអេក្រង់បន្ទាប់បន្សំបានទេ។"
+ "កម្មវិធីមិនអាចប្រើផ្ទាំងសារបានទេ"
"បន្ទាត់ខណ្ឌចែកក្នុងមុខងារបំបែកអេក្រង់"
"បន្ទាត់ខណ្ឌចែកក្នុងមុខងារបំបែកអេក្រង់"
"អេក្រង់ពេញខាងឆ្វេង"
@@ -133,12 +134,18 @@
"គ្រប់គ្រងវិនដូ"
"ប្ដូរសមាមាត្រ"
"បង្កើនប្រសិទ្ធភាពការមើល"
+ "រូបបញ្ហា"
"បិទ"
"បិទម៉ឺនុយ"
"%1$s (មុខងារវិនដូកុំព្យូទ័រ)"
"ពង្រីកអេក្រង់"
"ប្ដូរទំហំ"
"មិនអាចផ្លាស់ទីកម្មវិធីមកទីនេះបានទេ"
+ "ការបើកវិនដូកម្មវិធីថ្មី"
+ "កំពុងបិទវិនដូកម្មវិធី"
+ "ការបង្រួមវិនដូកម្មវិធី"
+ "%1$s (បានផ្ដោត)"
+ "%1$s (មិនបានផ្ដោត)"
"ជក់ចិត្ត"
"ស្ដារ"
"ពង្រីក"
diff --git a/wmshell/res/values-kn/strings.xml b/wmshell/res/values-kn/strings.xml
index 5c465bfa94..178eca199e 100644
--- a/wmshell/res/values-kn/strings.xml
+++ b/wmshell/res/values-kn/strings.xml
@@ -36,6 +36,7 @@
"ಈ ಆ್ಯಪ್ ಅನ್ನು 1 ವಿಂಡೋದಲ್ಲಿ ಮಾತ್ರ ತೆರೆಯಬಹುದು"
"ಸೆಕೆಂಡರಿ ಡಿಸ್ಪ್ಲೇಗಳಲ್ಲಿ ಆ್ಯಪ್ ಕಾರ್ಯ ನಿರ್ವಹಿಸದೇ ಇರಬಹುದು."
"ಸೆಕೆಂಡರಿ ಡಿಸ್ಪ್ಲೇಗಳಲ್ಲಿ ಪ್ರಾರಂಭಿಸುವಿಕೆಯನ್ನು ಆ್ಯಪ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ."
+ "ಬಬಲ್ ಅನ್ನು ಆ್ಯಪ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ"
"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ ಡಿವೈಡರ್"
"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ ಡಿವೈಡರ್"
"ಎಡ ಫುಲ್ ಸ್ಕ್ರೀನ್"
@@ -133,12 +134,18 @@
"ವಿಂಡೋಗಳನ್ನು ನಿರ್ವಹಿಸಿ"
"ದೃಶ್ಯಾನುಪಾತವನ್ನು ಬದಲಾಯಿಸಿ"
"ವೀಕ್ಷಣೆಯನ್ನು ಆಪ್ಟಿಮೈಸ್ ಮಾಡಿ"
+ "ದೋಷದ ಐಕಾನ್"
"ಮುಚ್ಚಿ"
"ಮೆನು ಮುಚ್ಚಿ"
"%1$s (ಡೆಸ್ಕ್ಟಾಪ್ ವಿಂಡೋಯಿಂಗ್)"
"ಸ್ಕ್ರೀನ್ ಅನ್ನು ಮ್ಯಾಕ್ಸಿಮೈಸ್ ಮಾಡಿ"
"ಮರುಗಾತ್ರಗೊಳಿಸಿ"
"ಆ್ಯಪ್ ಅನ್ನು ಇಲ್ಲಿಗೆ ಸರಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"
+ "ಹೊಸ ಆ್ಯಪ್ ವಿಂಡೋವನ್ನು ತೆರೆಯಲಾಗುತ್ತಿದೆ"
+ "ಆ್ಯಪ್ ವಿಂಡೋವನ್ನು ಮುಚ್ಚಲಾಗುತ್ತಿದೆ"
+ "ಆ್ಯಪ್ ವಿಂಡೋವನ್ನು ಮಿನಿಮೈಸ್ ಮಾಡಲಾಗುತ್ತಿದೆ"
+ "%1$s (ಫೋಕಸ್ ಮಾಡಲಾಗಿದೆ)"
+ "%1$s (ಫೋಕಸ್ ಮಾಡಲಾಗಿಲ್ಲ)"
"ಇಮ್ಮರ್ಸಿವ್"
"ಮರುಸ್ಥಾಪಿಸಿ"
"ಮ್ಯಾಕ್ಸಿಮೈಸ್ ಮಾಡಿ"
diff --git a/wmshell/res/values-kn/strings_tv.xml b/wmshell/res/values-kn/strings_tv.xml
index efb79306cb..2e62e4f0d5 100644
--- a/wmshell/res/values-kn/strings_tv.xml
+++ b/wmshell/res/values-kn/strings_tv.xml
@@ -21,7 +21,7 @@
"(ಶೀರ್ಷಿಕೆ ರಹಿತ ಕಾರ್ಯಕ್ರಮ)"
"ಮುಚ್ಚಿರಿ"
"ಫುಲ್ ಸ್ಕ್ರೀನ್"
- "ಸರಿಸಿ"
+ "ಮೂವ್"
"ವಿಸ್ತೃತಗೊಳಿಸಿ"
"ಕುಗ್ಗಿಸಿ"
"ಕಂಟ್ರೋಲ್ಗಳಿಗಾಗಿ ""ಹೋಮ್"" ಅನ್ನು ಎರಡು ಬಾರಿ ಒತ್ತಿರಿ"
diff --git a/wmshell/res/values-ko/strings.xml b/wmshell/res/values-ko/strings.xml
index d856438a53..8787ddb323 100644
--- a/wmshell/res/values-ko/strings.xml
+++ b/wmshell/res/values-ko/strings.xml
@@ -36,6 +36,7 @@
"이 앱은 창 1개에서만 열 수 있습니다."
"앱이 보조 디스플레이에서 작동하지 않을 수도 있습니다."
"앱이 보조 디스플레이에서의 실행을 지원하지 않습니다."
+ "앱이 풍선을 지원하지 않습니다."
"화면 분할기"
"화면 분할기"
"왼쪽 화면 전체화면"
@@ -133,12 +134,18 @@
"창 관리"
"가로세로 비율 변경"
"뷰 최적화"
+ "오류 아이콘"
"닫기"
"메뉴 닫기"
"%1$s(데스크톱 윈도윙)"
"화면 최대화"
"크기 조절"
"앱을 여기로 이동할 수 없음"
+ "새 앱 창 열기"
+ "앱 창 닫기"
+ "앱 창 최소화"
+ "%1$s(포커스)"
+ "%1$s(포커스되지 않음)"
"몰입형"
"복원"
"최대화하기"
diff --git a/wmshell/res/values-ky/strings.xml b/wmshell/res/values-ky/strings.xml
index 8c15c6a9c2..c3b63d22f3 100644
--- a/wmshell/res/values-ky/strings.xml
+++ b/wmshell/res/values-ky/strings.xml
@@ -36,6 +36,7 @@
"Бул колдонмону 1 терезеде гана ачууга болот"
"Колдонмо кошумча экранда иштебей коюшу мүмкүн."
"Колдонмону кошумча экрандарда иштетүүгө болбойт."
+ "Колдонмо көбүкчөнү колдоого албайт"
"Экранды бөлгүч"
"Экранды бөлгүч"
"Сол жактагы экранды толук экран режимине өткөрүү"
@@ -133,12 +134,18 @@
"Терезелерди тескөө"
"Тараптардын катнашын өзгөртүү"
"Көрүнүштү оптималдаштыруу"
+ "Ката сүрөтчөсү"
"Жабуу"
"Менюну жабуу"
"%1$s (Иш тактанын терезелери)"
"Экранды чоңойтуу"
"Өлчөмүн өзгөртүү"
"Колдонмону бул жерге жылдырууга болбойт"
+ "Колдонмонун жаңы терезесин ачуу"
+ "Колдонмо терезеси жабылууда"
+ "Колдонмонун терезесин кичирейтүү"
+ "%1$s (Иштеп жатат)"
+ "%1$s (Иштебей турат)"
"Сүңгүтүүчү"
"Калыбына келтирүү"
"Чоңойтуу"
diff --git a/wmshell/res/values-lo/strings.xml b/wmshell/res/values-lo/strings.xml
index f3e6f81d50..b3e9ffcc56 100644
--- a/wmshell/res/values-lo/strings.xml
+++ b/wmshell/res/values-lo/strings.xml
@@ -36,6 +36,7 @@
"ແອັບນີ້ສາມາດເປີດໄດ້ໃນ 1 ໜ້າຈໍເທົ່ານັ້ນ"
"ແອັບອາດບໍ່ສາມາດໃຊ້ໄດ້ໃນໜ້າຈໍທີສອງ."
"ແອັບບໍ່ຮອງຮັບການເປີດໃນໜ້າຈໍທີສອງ."
+ "ແອັບບໍ່ຮອງຮັບຟອງ"
"ເສັ້ນແບ່ງໜ້າຈໍ"
"ເສັ້ນແບ່ງໜ້າຈໍ"
"ເຕັມໜ້າຈໍຊ້າຍ"
@@ -133,12 +134,18 @@
"ຈັດການໜ້າຈໍ"
"ປ່ຽນອັດຕາສ່ວນຮູບ"
"ມຸມມອງເພີ່ມປະສິດທິພາບ"
+ "ໄອຄອນຂໍ້ຜິດພາດ"
"ປິດ"
"ປິດເມນູ"
"%1$s (ໜ້າຈໍເດັສທັອບ)"
"ປັບຈໍໃຫຍ່ສຸດ"
"ປັບຂະໜາດ"
"ບໍ່ສາມາດຍ້າຍແອັບມາບ່ອນນີ້ໄດ້"
+ "ກຳລັງເປີດໜ້າຈໍແອັບໃໝ່"
+ "ກຳລັງປິດໜ້າຈໍແອັບ"
+ "ກຳລັງຫຍໍ້ໜ້າຈໍແອັບ"
+ "%1$s (ໂຟກັສ)"
+ "%1$s (ບໍ່ໂຟກັສ)"
"ສົມຈິງ"
"ກູ້ຄືນ"
"ຂະຫຍາຍໃຫຍ່ສຸດ"
diff --git a/wmshell/res/values-lt/strings.xml b/wmshell/res/values-lt/strings.xml
index d22db2e0a7..821d9efe47 100644
--- a/wmshell/res/values-lt/strings.xml
+++ b/wmshell/res/values-lt/strings.xml
@@ -36,6 +36,7 @@
"Šią programą galima atidaryti tik viename lange"
"Programa gali neveikti antriniame ekrane."
"Programa nepalaiko paleisties antriniuose ekranuose."
+ "Programa nepalaiko pokalbio debesėlio"
"Išskaidyto ekrano režimo daliklis"
"Išskaidyto ekrano režimo daliklis"
"Kairysis ekranas viso ekrano režimu"
@@ -133,12 +134,18 @@
"Tvarkyti langus"
"Keisti kraštinių santykį"
"Optimizuoti rodinį"
+ "Klaidos piktograma"
"Uždaryti"
"Uždaryti meniu"
"„%1$s“ (versijos staliniams kompiuteriams rodinys)"
"Išskleisti ekraną"
"Pakeisti dydį"
"Programos negalima perkelti čia"
+ "Atidaromas naujas programos langas"
+ "Uždaromas programos langas"
+ "Sumažinamas programos langas"
+ "„%1$s“ (paryškinta)"
+ "„%1$s“ (neparyškinta)"
"Įtraukiantis"
"Atkurti"
"Padidinti"
diff --git a/wmshell/res/values-lv/strings.xml b/wmshell/res/values-lv/strings.xml
index 0e9d4e4668..2d75741995 100644
--- a/wmshell/res/values-lv/strings.xml
+++ b/wmshell/res/values-lv/strings.xml
@@ -36,6 +36,7 @@
"Šo lietotni var atvērt tikai vienā logā"
"Lietotne, iespējams, nedarbosies sekundārajā displejā."
"Lietotnē netiek atbalstīta palaišana sekundārajos displejos."
+ "Lietotnē netiek atbalstīts burbulis"
"Ekrāna sadalītājs"
"Ekrāna sadalītājs"
"Kreisā daļa pa visu ekrānu"
@@ -133,12 +134,18 @@
"Pārvaldīt logus"
"Mainīt malu attiecību"
"Optimizēt skatu"
+ "Kļūdas ikona"
"Aizvērt"
"Aizvērt izvēlni"
"%1$s (darbvirsmas logošana)"
"Maksimizēt ekrānu"
"Mainīt lielumu"
"Lietotni nevar pārvietot šeit."
+ "Tiek atvērts jauns lietotnes logs"
+ "Tiek aizvērts lietotnes logs"
+ "Tiek minimizēts lietotnes logs"
+ "%1$s (fokusā)"
+ "%1$s (nav fokusā)"
"Iekļaujoši"
"Atjaunot"
"Maksimizēt"
diff --git a/wmshell/res/values-mk/strings.xml b/wmshell/res/values-mk/strings.xml
index 1b7fe571d4..c6328befba 100644
--- a/wmshell/res/values-mk/strings.xml
+++ b/wmshell/res/values-mk/strings.xml
@@ -36,6 +36,7 @@
"Апликацијава може да се отвори само во еден прозорец"
"Апликацијата може да не функционира на друг екран."
"Апликацијата не поддржува стартување на други екрани."
+ "Апликацијата не поддржува балонче"
"Разделник на поделен екран"
"Разделник на поделен екран"
"Левиот на цел екран"
@@ -133,12 +134,18 @@
"Управувајте со прозорците"
"Промени го соодносот"
"Оптимизирај го приказот"
+ "Икона за грешка"
"Затворете"
"Затворете го менито"
"%1$s (режим со прозорци на работната површина)"
"Максимизирај го екранот"
"Смени големина"
"Апликацијата не може да се премести овде"
+ "Отворање нов прозорец на апликацијата"
+ "Се затвора прозорецот на апликацијата"
+ "Минимизирање на прозорецот на апликацијата"
+ "%1$s (фокусирано)"
+ "%1$s (не е фокусирано)"
"Реалистично"
"Врати"
"Максимизирај"
diff --git a/wmshell/res/values-ml/strings.xml b/wmshell/res/values-ml/strings.xml
index 01b9dee890..6e10ce4642 100644
--- a/wmshell/res/values-ml/strings.xml
+++ b/wmshell/res/values-ml/strings.xml
@@ -36,6 +36,7 @@
"ഈ ആപ്പ് ഒരു വിൻഡോയിൽ മാത്രമേ തുറക്കാനാകൂ"
"രണ്ടാം ഡിസ്പ്ലേയിൽ ആപ്പ് പ്രവർത്തിച്ചേക്കില്ല."
"രണ്ടാം ഡിസ്പ്ലേകളിൽ സമാരംഭിക്കുന്നതിനെ ആപ്പ് അനുവദിക്കുന്നില്ല."
+ "ബബിളിനെ ആപ്പ് പിന്തുണയ്ക്കുന്നില്ല"
"സ്ക്രീൻ വിഭജന മോഡ് ഡിവൈഡർ"
"സ്ക്രീൻ വിഭജന മോഡ് ഡിവൈഡർ"
"ഇടത് പൂർണ്ണ സ്ക്രീൻ"
@@ -133,12 +134,18 @@
"വിൻഡോകൾ മാനേജ് ചെയ്യുക"
"വീക്ഷണ അനുപാതം മാറ്റുക"
"കാഴ്ച ഒപ്റ്റിമൈസ് ചെയ്യുക"
+ "പിശക് ഐക്കൺ"
"അടയ്ക്കുക"
"മെനു അടയ്ക്കുക"
"%1$s (ഡെസ്ക്ടോപ്പ് വിൻഡോയിംഗ്)"
"സ്ക്രീൻ വലുതാക്കുക"
"വലുപ്പം മാറ്റുക"
"ആപ്പ് ഇവിടേക്ക് നീക്കാനാകില്ല"
+ "പുതിയ ആപ്പ് വിൻഡോ തുറക്കുന്നു"
+ "ആപ്പ് വിൻഡോ അടയ്ക്കുന്നു"
+ "ആപ്പ് വിൻഡോ മിനിമൈസ് ചെയ്യുന്നു"
+ "%1$s (ഫോക്കസ് ചെയ്തവ)"
+ "%1$s (ഫോക്കസ് ചെയ്യാത്തവ)"
"ഇമേഴ്സീവ്"
"പുനഃസ്ഥാപിക്കുക"
"വലുതാക്കുക"
diff --git a/wmshell/res/values-mn/strings.xml b/wmshell/res/values-mn/strings.xml
index 8486c9e83f..5bf953a7cd 100644
--- a/wmshell/res/values-mn/strings.xml
+++ b/wmshell/res/values-mn/strings.xml
@@ -36,6 +36,7 @@
"Энэ аппыг зөвхөн 1 цонхонд нээх боломжтой"
"Апп хоёрдогч дэлгэцэд ажиллахгүй."
"Аппыг хоёрдогч дэлгэцэд эхлүүлэх боломжгүй."
+ "Апп бөмбөлгийг дэмждэггүй"
"Дэлгэцийг хуваах хуваагч"
"Дэлгэцийг хуваах хуваагч"
"Зүүн талын бүтэн дэлгэц"
@@ -133,12 +134,18 @@
"Цонхнуудыг удирдах"
"Аспектын харьцааг өөрчлөх"
"Харагдах байдлыг оновчлох"
+ "Алдааны дүрс тэмдэг"
"Хаах"
"Цэсийг хаах"
"%1$s (Дэлгэцийн цонх үүсгэх онцлог)"
"Дэлгэцийг томруулах"
"Хэмжээг өөрчлөх"
"Аппыг ийш зөөх боломжгүй"
+ "Аппын шинэ цонхыг нээж байна"
+ "Аппын цонхыг хааж байна"
+ "Аппын цонхыг жижгэрүүлж байна"
+ "%1$s (сонгосон)"
+ "%1$s (сонгоогүй)"
"Бодит мэт"
"Сэргээх"
"Томруулах"
diff --git a/wmshell/res/values-mr/strings.xml b/wmshell/res/values-mr/strings.xml
index 2cb04aca30..5dd4013a0a 100644
--- a/wmshell/res/values-mr/strings.xml
+++ b/wmshell/res/values-mr/strings.xml
@@ -36,6 +36,7 @@
"हे अॅप फक्त एका विंडोमध्ये उघडले जाऊ शकते"
"दुसऱ्या डिस्प्लेवर अॅप कदाचित चालणार नाही."
"दुसऱ्या डिस्प्लेवर अॅप लाँच होणार नाही."
+ "अॅप बबलला सपोर्ट करत नाही"
"स्प्लिट स्क्रीन विभाजक"
"स्प्लिट स्क्रीन विभाजक"
"डावी फुल स्क्रीन"
@@ -133,12 +134,18 @@
"विंडो व्यवस्थापित करा"
"आस्पेक्ट रेशो बदला"
"दृश्य ऑप्टिमाइझ करा"
+ "एरर आयकन"
"बंद करा"
"मेनू बंद करा"
"%1$s (डेस्कटॉप विंडोइंग)"
"स्क्रीन मोठी करा"
"आकार बदला"
"अॅप इथे हलवू शकत नाही"
+ "नवीन अॅप विंडो उघडत आहे"
+ "अॅपची विंडो बंद करत आहे"
+ "अॅप विंडो लहान करत आहे"
+ "%1$s (फोकस केलेले)"
+ "%1$s (फोकस न केलेले)"
"इमर्सिव्ह"
"रिस्टोअर करा"
"मोठे करा"
diff --git a/wmshell/res/values-ms/strings.xml b/wmshell/res/values-ms/strings.xml
index 1f6cb3bf4b..1ac684f890 100644
--- a/wmshell/res/values-ms/strings.xml
+++ b/wmshell/res/values-ms/strings.xml
@@ -36,6 +36,7 @@
"Apl ini hanya boleh dibuka dalam 1 tetingkap"
"Apl mungkin tidak berfungsi pada paparan kedua."
"Apl tidak menyokong pelancaran pada paparan kedua."
+ "Apl tidak menyokong gelembung"
"Pembahagi skrin pisah"
"Pembahagi skrin pisah"
"Skrin penuh kiri"
@@ -133,12 +134,18 @@
"Urus Tetingkap"
"Tukar nisbah bidang"
"Optimumkan Paparan"
+ "Ikon Ralat"
"Tutup"
"Tutup Menu"
"%1$s (Tetingkap desktop)"
"Maksimumkan Skrin"
"Ubah saiz"
"Apl tidak boleh dialihkan ke sini"
+ "Membuka tetingkap apl baharu"
+ "Menutup tetingkap apl"
+ "Meminimumkan tetingkap apl"
+ "%1$s (Terfokus)"
+ "%1$s (Tidak terfokus)"
"Mengasyikkan"
"Pulihkan"
"Maksimumkan"
diff --git a/wmshell/res/values-my/strings.xml b/wmshell/res/values-my/strings.xml
index 684a52bd39..35f4588546 100644
--- a/wmshell/res/values-my/strings.xml
+++ b/wmshell/res/values-my/strings.xml
@@ -36,6 +36,7 @@
"ဤအက်ပ်ကို ဝင်းဒိုး ၁ ခုတွင်သာ ဖွင့်နိုင်သည်"
"ဤအက်ပ်အနေဖြင့် ဒုတိယဖန်သားပြင်ပေါ်တွင် အလုပ်လုပ်မည် မဟုတ်ပါ။"
"ဤအက်ပ်အနေဖြင့် ဖွင့်ရန်စနစ်ကို ဒုတိယဖန်သားပြင်မှ အသုံးပြုရန် ပံ့ပိုးမထားပါ။"
+ "အက်ပ်သည် ပူဖောင်းကွက်ကို မပံ့ပိုးပါ"
"မျက်နှာပြင် ခွဲ၍ပြသခြင်း ပိုင်းခြားစနစ်"
"မျက်နှာပြင် ခွဲ၍ပြသခြင်း ပိုင်းခြားစနစ်"
"ဘယ်ဘက် မျက်နှာပြင်အပြည့်"
@@ -133,12 +134,18 @@
"ဝင်းဒိုးများ စီမံရန်"
"အချိုးအစား ပြောင်းရန်"
"မြင်ကွင်းကို ပိုကောင်းအောင်လုပ်ရန်"
+ "အမှားအယွင်း သင်္ကေတ"
"ပိတ်ရန်"
"မီနူး ပိတ်ရန်"
"%1$s (ဒက်စ်တော့ဝင်းဒိုးမုဒ်)"
"စခရင်ကို ချဲ့မည်"
"အရွယ်ပြင်ရန်"
"အက်ပ်ကို ဤနေရာသို့ ရွှေ့၍မရပါ"
+ "အက်ပ်ဝင်းဒိုးအသစ် ဖွင့်နေသည်"
+ "အက်ပ်ဝင်းဒိုး ပိတ်ခြင်း"
+ "အက်ပ်ဝင်းဒိုးကို ချုံ့နေသည်"
+ "%1$s (အာရုံပြုထားသည်)"
+ "%1$s (အာရုံပြုမထားပါ)"
"ဘက်ပေါင်းစုံ"
"ပြန်ပြောင်းရန်"
"ချဲ့ရန်"
diff --git a/wmshell/res/values-nb/strings.xml b/wmshell/res/values-nb/strings.xml
index 0b6dd8acce..a5db560430 100644
--- a/wmshell/res/values-nb/strings.xml
+++ b/wmshell/res/values-nb/strings.xml
@@ -36,6 +36,7 @@
"Denne appen kan bare åpnes i ett vindu"
"Appen fungerer kanskje ikke på en sekundær skjerm."
"Appen kan ikke kjøres på sekundære skjermer."
+ "Appen støtter ikke bobler"
"Skilleelement for delt skjerm"
"Skilleelement for delt skjerm"
"Utvid den venstre delen av skjermen til hele skjermen"
@@ -133,12 +134,18 @@
"Administrer vinduene"
"Endre høyde/bredde-forholdet"
"Optimaliser visning"
+ "Feilikon"
"Lukk"
"Lukk menyen"
"%1$s (datamaskin-vindusvisning)"
"Maksimer skjermen"
"Endre størrelse"
"Appen kan ikke flyttes hit"
+ "Åpner et nytt appvindu"
+ "Lukker appvinduet"
+ "Minimerer appvinduet"
+ "%1$s (fokusert)"
+ "%1$s (ikke fokusert)"
"Oppslukende"
"Gjenopprett"
"Maksimer"
diff --git a/wmshell/res/values-ne/strings.xml b/wmshell/res/values-ne/strings.xml
index b370a6f7f1..2b5f36772a 100644
--- a/wmshell/res/values-ne/strings.xml
+++ b/wmshell/res/values-ne/strings.xml
@@ -36,6 +36,7 @@
"यो एप एउटा विन्डोमा मात्र खोल्न मिल्छ"
"यो एपले सहायक प्रदर्शनमा काम नगर्नसक्छ।"
"एपले सहायक प्रदर्शनहरूमा लञ्च सुविधालाई समर्थन गर्दैन।"
+ "एपमा बबल प्रयोग गर्न मिल्दैन"
"स्प्लिट स्क्रिन डिभाइडर"
"स्प्लिट स्क्रिन डिभाइडर"
"बायाँ भाग फुल स्क्रिन"
@@ -133,12 +134,18 @@
"विन्डोहरू व्यवस्थापन गर्नुहोस्"
"एस्पेक्ट रेसियो परिवर्तन गर्नुहोस्"
"भ्यू अप्टिमाइज गर्नुहोस्"
+ "त्रुटि जनाउने आइकन"
"बन्द गर्नुहोस्"
"मेनु बन्द गर्नुहोस्"
"%1$s (डेस्कटप विन्डोइङ)"
"स्क्रिन ठुलो बनाउनुहोस्"
"आकार बदल्नुहोस्"
"एप सारेर यहाँ ल्याउन सकिएन"
+ "एपको नयाँ विन्डो खोल्नुहोस्"
+ "एपको विन्डो बन्द गरिँदै छ"
+ "एपको विन्डो मिनिमाइज गरिँदै छ"
+ "%1$s (फोकस गरिएको)"
+ "%1$s (फोकस नगरिएको)"
"इमर्सिभ"
"रिस्टोर गर्नुहोस्"
"ठुलो बनाउनुहोस्"
diff --git a/wmshell/res/values-nl/strings.xml b/wmshell/res/values-nl/strings.xml
index 19420bd07c..42d59593ba 100644
--- a/wmshell/res/values-nl/strings.xml
+++ b/wmshell/res/values-nl/strings.xml
@@ -36,6 +36,7 @@
"Deze app kan maar in 1 venster worden geopend"
"App werkt mogelijk niet op een secundair scherm."
"App kan niet op secundaire displays worden gestart."
+ "App ondersteunt geen bubbel"
"Scheiding voor gesplitst scherm"
"Scheiding voor gesplitst scherm"
"Linkerscherm op volledig scherm"
@@ -133,12 +134,18 @@
"Vensters beheren"
"Beeldverhouding wijzigen"
"Weergave optimaliseren"
+ "Fouticoon"
"Sluiten"
"Menu sluiten"
"%1$s (desktopvensterfunctie)"
"Scherm maximaliseren"
"Formaat aanpassen"
"Kan de app niet hierheen verplaatsen"
+ "Nieuw app-venster openen"
+ "App-venster sluiten"
+ "App-venster minimaliseren"
+ "%1$s (gefocust)"
+ "%1$s (niet gefocust)"
"Immersief"
"Herstellen"
"Maximaliseren"
diff --git a/wmshell/res/values-or/strings.xml b/wmshell/res/values-or/strings.xml
index 9659dc69d9..d0229f4b21 100644
--- a/wmshell/res/values-or/strings.xml
+++ b/wmshell/res/values-or/strings.xml
@@ -36,6 +36,7 @@
"ଏହି ଆପକୁ କେବଳ 1ଟି ୱିଣ୍ଡୋରେ ଖୋଲାଯାଇପାରିବ"
"ସେକେଣ୍ଡାରୀ ଡିସପ୍ଲେରେ ଆପ୍ କାମ ନକରିପାରେ।"
"ସେକେଣ୍ଡାରୀ ଡିସପ୍ଲେରେ ଆପ୍ ଲଞ୍ଚ ସପୋର୍ଟ କରେ ନାହିଁ।"
+ "ଆପ ବବଲକୁ ସପୋର୍ଟ କରେ ନାହିଁ"
"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ଡିଭାଇଡର"
"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ ଡିଭାଇଡର"
"ବାମ ପଟକୁ ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନ୍ କରନ୍ତୁ"
@@ -133,12 +134,18 @@
"ୱିଣ୍ଡୋଗୁଡ଼ିକୁ ପରିଚାଳନା କରନ୍ତୁ"
"ଚଉଡ଼ା ଓ ଉଚ୍ଚତାର ଅନୁପାତ ପରିବର୍ତ୍ତନ କରନ୍ତୁ"
"ଭ୍ୟୁ ଅପ୍ଟିମାଇଜ କରନ୍ତୁ"
+ "ତ୍ରୁଟି ଆଇକନ"
"ବନ୍ଦ କରନ୍ତୁ"
"ମେନୁ ବନ୍ଦ କରନ୍ତୁ"
"%1$s (ଡେସ୍କଟପ ୱିଣ୍ଡୋଇଂ)"
"ସ୍କ୍ରିନକୁ ବଡ଼ କରନ୍ତୁ"
"ରିସାଇଜ କରନ୍ତୁ"
"ଆପକୁ ଏଠାକୁ ମୁଭ କରାଯାଇପାରିବ ନାହିଁ"
+ "ନୂଆ ଆପ ୱିଣ୍ଡୋ ଖୋଲୁଛି"
+ "ଆପ ୱିଣ୍ଡୋକୁ ବନ୍ଦ କରାଯାଉଛି"
+ "ଆପ ୱିଣ୍ଡୋକୁ ଛୋଟ କରନ୍ତୁ"
+ "%1$s (ଫୋକସ କରାଯାଇଛି)"
+ "%1$s (ଫୋକସ କରାଯାଇନାହିଁ)"
"ଇମର୍ସିଭ"
"ରିଷ୍ଟୋର କରନ୍ତୁ"
"ବଡ଼ କରନ୍ତୁ"
diff --git a/wmshell/res/values-pa/strings.xml b/wmshell/res/values-pa/strings.xml
index 9e34af5c48..7740560f63 100644
--- a/wmshell/res/values-pa/strings.xml
+++ b/wmshell/res/values-pa/strings.xml
@@ -36,6 +36,7 @@
"ਇਹ ਐਪ ਸਿਰਫ਼ 1 ਵਿੰਡੋ ਵਿੱਚ ਖੋਲ੍ਹੀ ਜਾ ਸਕਦੀ ਹੈ"
"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਐਪ ਸੈਕੰਡਰੀ ਡਿਸਪਲੇ \'ਤੇ ਕੰਮ ਨਾ ਕਰੇ।"
"ਐਪ ਸੈਕੰਡਰੀ ਡਿਸਪਲੇਆਂ \'ਤੇ ਲਾਂਚ ਕਰਨ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ"
+ "ਐਪ ਬਬਲ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ"
"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਵਿਭਾਜਕ"
"ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਵਿਭਾਜਕ"
"ਖੱਬੇ ਪੂਰੀ ਸਕ੍ਰੀਨ"
@@ -133,12 +134,18 @@
"ਵਿੰਡੋਆਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ"
"ਆਕਾਰ ਅਨੁਪਾਤ ਬਦਲੋ"
"ਦ੍ਰਿਸ਼ ਨੂੰ ਸੁਯੋਗ ਬਣਾਓ"
+ "ਗੜਬੜ ਪ੍ਰਤੀਕ"
"ਬੰਦ ਕਰੋ"
"ਮੀਨੂ ਬੰਦ ਕਰੋ"
"%1$s (ਡੈਸਕਟਾਪ ਵਿੰਡੋ)"
"ਸਕ੍ਰੀਨ ਦਾ ਆਕਾਰ ਵਧਾਓ"
"ਆਕਾਰ ਬਦਲੋ"
"ਐਪ ਨੂੰ ਇੱਥੇ ਨਹੀਂ ਲਿਜਾਇਆ ਜਾ ਸਕਦਾ"
+ "ਨਵੀਂ ਐਪ ਵਿੰਡੋ ਨੂੰ ਖੋਲ੍ਹਿਆ ਜਾ ਰਿਹਾ ਹੈ"
+ "ਐਪ ਵਿੰਡੋ ਨੂੰ ਬੰਦ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"
+ "ਐਪ ਵਿੰਡੋ ਨੂੰ ਛੋਟਾ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"
+ "%1$s (ਫੋਕਸ ਵਿੱਚ ਹੈ)"
+ "%1$s (ਫੋਕਸ ਵਿੱਚ ਨਹੀਂ ਹੈ)"
"ਇਮਰਸਿਵ"
"ਮੁੜ-ਬਹਾਲ ਕਰੋ"
"ਵੱਡਾ ਕਰੋ"
diff --git a/wmshell/res/values-pl/strings.xml b/wmshell/res/values-pl/strings.xml
index 4c0613b358..f726b25305 100644
--- a/wmshell/res/values-pl/strings.xml
+++ b/wmshell/res/values-pl/strings.xml
@@ -36,6 +36,7 @@
"Ta aplikacja może być otwarta tylko w 1 oknie."
"Aplikacja może nie działać na dodatkowym ekranie."
"Aplikacja nie obsługuje uruchamiania na dodatkowych ekranach."
+ "Aplikacja nie obsługuje dymków"
"Linia dzielenia ekranu"
"Linia dzielenia ekranu"
"Lewa część ekranu na pełnym ekranie"
@@ -133,12 +134,18 @@
"Zarządzaj oknami"
"Zmień format obrazu"
"Zoptymalizuj widok"
+ "Ikona błędu"
"Zamknij"
"Zamknij menu"
"%1$s (tryb okien na pulpicie)"
"Maksymalizuj ekran"
"Zmień rozmiar"
"Nie można przenieść aplikacji tutaj"
+ "Otwarto nowe okno aplikacji"
+ "Zamykanie okna aplikacji"
+ "Zminimalizowano okno aplikacji"
+ "%1$s (aktywne okno)"
+ "%1$s (nieaktywne okno)"
"Tryb immersyjny"
"Przywróć"
"Maksymalizuj"
diff --git a/wmshell/res/values-pt-rBR/strings.xml b/wmshell/res/values-pt-rBR/strings.xml
index 1c2a3fa490..ef3b68f9a0 100644
--- a/wmshell/res/values-pt-rBR/strings.xml
+++ b/wmshell/res/values-pt-rBR/strings.xml
@@ -36,6 +36,7 @@
"Esse app só pode ser aberto em uma única janela"
"É possível que o app não funcione em uma tela secundária."
"O app não é compatível com a inicialização em telas secundárias."
+ "O app é incompatível com balões"
"Divisor de tela"
"Divisor de tela"
"Lado esquerdo em tela cheia"
@@ -133,12 +134,18 @@
"Gerenciar janelas"
"Mudar a proporção"
"Visualização otimizada"
+ "Ícone de erro"
"Fechar"
"Fechar menu"
"%1$s (modo janela para computador)"
"Ampliar tela"
"Redimensionar"
"Não é possível mover o app para cá"
+ "Abrindo uma nova janela do app"
+ "Fechando a janela do app"
+ "Minimizando a janela do app"
+ "%1$s (em primeiro plano)"
+ "%1$s (em segundo plano)"
"Imersivo"
"Restaurar"
"Maximizar"
diff --git a/wmshell/res/values-pt-rPT/strings.xml b/wmshell/res/values-pt-rPT/strings.xml
index ca1005d78d..ccfaa0a86b 100644
--- a/wmshell/res/values-pt-rPT/strings.xml
+++ b/wmshell/res/values-pt-rPT/strings.xml
@@ -36,6 +36,7 @@
"Esta app só pode ser aberta em 1 janela"
"A app pode não funcionar num ecrã secundário."
"A app não é compatível com o início em ecrãs secundários."
+ "A app não é compatível com o balão"
"Divisor do ecrã dividido"
"Divisor do ecrã dividido"
"Ecrã esquerdo inteiro"
@@ -133,12 +134,18 @@
"Gerir janelas"
"Alterar formato"
"Otimizar vista"
+ "Ícone de erro"
"Fechar"
"Fechar menu"
"%1$s (janelas de computador)"
"Maximizar ecrã"
"Redimensionar"
"Não é possível mover a app para aqui"
+ "A abrir a nova janela da app"
+ "A fechar a janela da app"
+ "A minimizar a janela da app"
+ "%1$s (com foco)"
+ "%1$s (sem foco)"
"Envolvente"
"Restaurar"
"Maximizar"
diff --git a/wmshell/res/values-pt/strings.xml b/wmshell/res/values-pt/strings.xml
index 1c2a3fa490..ef3b68f9a0 100644
--- a/wmshell/res/values-pt/strings.xml
+++ b/wmshell/res/values-pt/strings.xml
@@ -36,6 +36,7 @@
"Esse app só pode ser aberto em uma única janela"
"É possível que o app não funcione em uma tela secundária."
"O app não é compatível com a inicialização em telas secundárias."
+ "O app é incompatível com balões"
"Divisor de tela"
"Divisor de tela"
"Lado esquerdo em tela cheia"
@@ -133,12 +134,18 @@
"Gerenciar janelas"
"Mudar a proporção"
"Visualização otimizada"
+ "Ícone de erro"
"Fechar"
"Fechar menu"
"%1$s (modo janela para computador)"
"Ampliar tela"
"Redimensionar"
"Não é possível mover o app para cá"
+ "Abrindo uma nova janela do app"
+ "Fechando a janela do app"
+ "Minimizando a janela do app"
+ "%1$s (em primeiro plano)"
+ "%1$s (em segundo plano)"
"Imersivo"
"Restaurar"
"Maximizar"
diff --git a/wmshell/res/values-ro/strings.xml b/wmshell/res/values-ro/strings.xml
index d09da5be59..0fc2f79b4f 100644
--- a/wmshell/res/values-ro/strings.xml
+++ b/wmshell/res/values-ro/strings.xml
@@ -36,6 +36,7 @@
"Aplicația se poate deschide într-o singură fereastră"
"Este posibil ca aplicația să nu funcționeze pe un ecran secundar."
"Aplicația nu acceptă lansare pe ecrane secundare."
+ "Aplicația nu acceptă baloane"
"Separator pentru ecranul împărțit"
"Separator pentru ecranul împărțit"
"Partea stângă pe ecran complet"
@@ -133,12 +134,18 @@
"Gestionează ferestrele"
"Schimbă raportul de dimensiuni"
"Optimizează afișarea"
+ "Pictogramă de eroare"
"Închide"
"Închide meniul"
"%1$s (ferestre pe desktop)"
"Maximizează fereastra"
"Redimensionează"
"Aplicația nu poate fi mutată aici"
+ "Se deschide o fereastră nouă a aplicației"
+ "Se închide fereastra aplicației"
+ "Se minimizează fereastra aplicației"
+ "%1$s (Focalizată)"
+ "%1$s (Nefocalizată)"
"Captivant"
"Restabilește"
"Maximizează"
diff --git a/wmshell/res/values-ru/strings.xml b/wmshell/res/values-ru/strings.xml
index d9ff3075c4..d24e14093c 100644
--- a/wmshell/res/values-ru/strings.xml
+++ b/wmshell/res/values-ru/strings.xml
@@ -36,6 +36,7 @@
"Это приложение можно открыть только в одном окне."
"Приложение может не работать на дополнительном экране"
"Приложение не поддерживает запуск на дополнительных экранах"
+ "Приложение не поддерживает всплывающие чаты."
"Разделитель экрана"
"Разделитель экрана"
"Левый во весь экран"
@@ -100,7 +101,7 @@
"Не помогло?\nНажмите, чтобы отменить изменения."
"Нет проблем с камерой? Нажмите, чтобы закрыть."
"Здесь вы найдете меню приложения"
- "Чтобы открыть сразу несколько приложений, перейдите в режим компьютерных окон"
+ "Чтобы открыть сразу несколько приложений, перейдите в многооконный режим"
"Вернуться в полноэкранный режим можно из меню приложения"
"Выполняйте несколько задач одновременно"
"Перетащите сюда другое приложение, чтобы использовать разделение экрана."
@@ -121,7 +122,7 @@
"Обозначение приложения"
"Значок приложения"
"Полноэкранный режим"
- "Режим компьютерных окон"
+ "Многооконный режим"
"Разделить экран"
"Ещё"
"Плавающее окно"
@@ -133,12 +134,18 @@
"Управление окнами"
"Изменить соотношение сторон"
"Оптимизировать"
+ "Значок ошибки"
"Закрыть"
"Закрыть меню"
- "%1$s (режим компьютерных окон)"
+ "%1$s (многооконный режим)"
"Развернуть на весь экран"
"Изменить размер"
"Приложение нельзя сюда переместить"
+ "Открывается новое окно приложения"
+ "Закрытие окна приложения…"
+ "Сворачивается окно приложения"
+ "Приложение \"%1$s\" выбрано"
+ "Приложение \"%1$s\" не выбрано"
"Погружение"
"Восстановить"
"Развернуть"
diff --git a/wmshell/res/values-si/strings.xml b/wmshell/res/values-si/strings.xml
index 32daf89d5b..804fa30d62 100644
--- a/wmshell/res/values-si/strings.xml
+++ b/wmshell/res/values-si/strings.xml
@@ -36,6 +36,7 @@
"මෙම යෙදුම විවෘත කළ හැක්කේ 1 කවුළුවක පමණයි"
"යෙදුම ද්විතියික සංදර්ශකයක ක්රියා නොකළ හැකිය."
"යෙදුම ද්විතීයික සංදර්ශක මත දියත් කිරීම සඳහා සහාය නොදක්වයි."
+ "යෙදුම බුබුලට සහාය නොදක්වයි"
"බෙදුම් තිර වෙන්කරණය"
"බෙදුම් තිර වෙන්කරණය"
"වම් පූර්ණ තිරය"
@@ -133,12 +134,18 @@
"කවුළු කළමනාකරණය කරන්න"
"දර්ශන අනුපාතය වෙනස් කරන්න"
"දසුන ප්රශස්ත කරන්න"
+ "දෝෂ නිරූපකය"
"වසන්න"
"මෙනුව වසන්න"
"%1$s (ඩෙස්ක්ටොප් කවුළුකරණය)"
"තිරය උපරිම කරන්න"
"ප්රතිප්රමාණය කරන්න"
"යෙදුම මෙතැනට ගෙන යා නොහැක"
+ "නව යෙදුම් කවුළුව විවෘත කිරීම"
+ "යෙදුම් කවුළුව වැසීම"
+ "යෙදුම් කවුළුව අවම කිරීම"
+ "%1$s (අවධානිත)"
+ "%1$s (අවධානිත නොවේ)"
"ගිලෙන සුළු"
"ප්රතිසාධනය කරන්න"
"විහිදන්න"
diff --git a/wmshell/res/values-sk/strings.xml b/wmshell/res/values-sk/strings.xml
index fd5c7a7518..2d90b2c0df 100644
--- a/wmshell/res/values-sk/strings.xml
+++ b/wmshell/res/values-sk/strings.xml
@@ -36,6 +36,7 @@
"Táto aplikácia môže byť otvorená iba v jednom okne"
"Aplikácia nemusí fungovať na sekundárnej obrazovke."
"Aplikácia nepodporuje spúšťanie na sekundárnych obrazovkách."
+ "Aplikácia bublinu nepodporuje"
"Rozdeľovač obrazovky"
"Rozdeľovač obrazovky"
"Ľavá – na celú obrazovku"
@@ -133,12 +134,18 @@
"Spravovať okná"
"Zmeniť pomer strán"
"Optimalizovať zobrazenie"
+ "Ikona chyby"
"Zavrieť"
"Zavrieť ponuku"
"%1$s (windowing na pracovnej ploche)"
"Maximalizovať obrazovku"
"Zmeniť veľkosť"
"Aplikácia sa sem nedá presunúť"
+ "Otvára sa nové okno aplikácie"
+ "Zatvára sa okno aplikácie"
+ "Minimalizuje sa okno aplikácie"
+ "%1$s (označené)"
+ "%1$s (neoznačené)"
"Pútavé"
"Obnoviť"
"Maximalizovať"
diff --git a/wmshell/res/values-sl/strings.xml b/wmshell/res/values-sl/strings.xml
index 15a4589095..a8f6081126 100644
--- a/wmshell/res/values-sl/strings.xml
+++ b/wmshell/res/values-sl/strings.xml
@@ -36,6 +36,7 @@
"To aplikacijo je mogoče odpreti samo v enem oknu"
"Aplikacija morda ne bo delovala na sekundarnem zaslonu."
"Aplikacija ne podpira zagona na sekundarnih zaslonih."
+ "Aplikacija ne podpira oblačka"
"Razdelilnik zaslonov"
"Razdelilnik zaslonov"
"Levi v celozaslonski način"
@@ -133,12 +134,18 @@
"Upravljanje oken"
"Sprememba razmerja stranic"
"Optimizacija pogleda"
+ "Ikona za napako"
"Zapri"
"Zapri meni"
"%1$s (namizni način prikaza več oken hkrati)"
"Maksimiraj zaslon"
"Spremeni velikost"
"Aplikacije ni mogoče premakniti sem"
+ "Odpiranje novega okna aplikacije"
+ "Zapiranje okna aplikacije"
+ "Minimiranje okna aplikacije"
+ "%1$s (v fokusu)"
+ "%1$s (ni v fokusu)"
"Poglobljeno"
"Obnovi"
"Maksimiraj"
diff --git a/wmshell/res/values-sq/strings.xml b/wmshell/res/values-sq/strings.xml
index 77316aa860..c3d8978a81 100644
--- a/wmshell/res/values-sq/strings.xml
+++ b/wmshell/res/values-sq/strings.xml
@@ -36,6 +36,7 @@
"Ky aplikacion mund të hapet vetëm në 1 dritare"
"Aplikacioni mund të mos funksionojë në një ekran dytësor."
"Aplikacioni nuk mbështet nisjen në ekrane dytësore."
+ "Aplikacioni nuk e mbështet flluskën"
"Ndarësi i ekranit të ndarë"
"Ndarësi i ekranit të ndarë"
"Ekrani i plotë majtas"
@@ -133,12 +134,18 @@
"Menaxho dritaret"
"Ndrysho raportin e pamjes"
"Optimizo pamjen"
+ "Ikona e gabimit"
"Mbyll"
"Mbyll menynë"
"%1$s (modaliteti me dritare si në desktop)"
"Maksimizo ekranin"
"Ndrysho përmasat"
"Aplikacioni nuk mund të zhvendoset këtu"
+ "Po hapet një dritare e re e aplikacionit"
+ "Po mbyllet dritarja e aplikacionit"
+ "Dritarja e aplikacionit po minimizohet"
+ "%1$s (në fokus)"
+ "%1$s (jo në fokus)"
"Përfshirës"
"Restauro"
"Maksimizo"
diff --git a/wmshell/res/values-sr/strings.xml b/wmshell/res/values-sr/strings.xml
index 66bc2a62ae..d3b5911f90 100644
--- a/wmshell/res/values-sr/strings.xml
+++ b/wmshell/res/values-sr/strings.xml
@@ -36,6 +36,7 @@
"Ова апликација може да се отвори само у једном прозору"
"Апликација можда неће функционисати на секундарном екрану."
"Апликација не подржава покретање на секундарним екранима."
+ "Апликација не подржава облачић"
"Разделник подељеног екрана"
"Разделник подељеног екрана"
"Режим целог екрана за леви екран"
@@ -77,14 +78,14 @@
"Одбаци облачић"
"Пребаци на цео екран"
"Не користи облачиће за конверзацију"
- "Ћаскајте у облачићима"
+ "Четујте у облачићима"
"Нове конверзације се приказују као плутајуће иконе или облачићи. Додирните да бисте отворили облачић. Превуците да бисте га преместили."
"Контролишите облачиће у било ком тренутку"
"Додирните Управљајте да бисте искључили облачиће из ове апликације"
"Важи"
"Нема недавних облачића"
"Овде се приказују недавни и одбачени облачићи"
- "Ћаскајте у облачићима"
+ "Четујте у облачићима"
"Нове конверзације се појављују као иконе у доњем углу екрана. Додирните да бисте их проширили или превуците да бисте их одбацили."
"Контролишите облачиће у сваком тренутку"
"Додирните овде и одредите које апликације и конверзације могу да имају облачић"
@@ -133,12 +134,18 @@
"Управљајте прозорима"
"Промени размеру"
"Оптимизујте приказ"
+ "Икона грешке"
"Затворите"
"Затворите мени"
"%1$s (приказ рачунарских прозора)"
"Повећај екран"
"Прилагоди"
"Апликација не може да се премести овде"
+ "Отвара се нови прозор апликације"
+ "Затвара се прозор апликације"
+ "Умањује се прозор апликације"
+ "%1$s (фокусирано)"
+ "%1$s (није фокусирано)"
"Имерзивно"
"Врати"
"Увећај"
diff --git a/wmshell/res/values-sv/strings.xml b/wmshell/res/values-sv/strings.xml
index 2e77f642b7..12de73a01a 100644
--- a/wmshell/res/values-sv/strings.xml
+++ b/wmshell/res/values-sv/strings.xml
@@ -36,6 +36,7 @@
"Denna app kan bara vara öppen i ett fönster"
"Appen kanske inte fungerar på en sekundär skärm."
"Appen kan inte köras på en sekundär skärm."
+ "Appen har inte stöd för bubbla"
"Avdelare för delad skärm"
"Avdelare för delad skärm"
"Helskärm på vänster skärm"
@@ -104,7 +105,7 @@
"Återgå till helskärm när som helst från appmenyn"
"Se och gör mer"
"Dra till en annan app för att dela upp skärmen"
- "Tryck snabbt två gånger utanför en app för att flytta den"
+ "Dubbeltryck utanför en app för att flytta den"
"OK"
"Utöka för mer information."
"Vill du starta om för en bättre vy?"
@@ -112,7 +113,7 @@
"Avbryt"
"Starta om"
"Visa inte igen"
- "Tryck snabbt två gånger\nför att flytta denna app"
+ "Dubbeltryck\nför att flytta denna app"
"Utöka %1$s"
"Återställ %1$s"
"Minimera %1$s"
@@ -133,12 +134,18 @@
"Hantera fönster"
"Ändra bildformat"
"Optimera vy"
+ "Felikon"
"Stäng"
"Stäng menyn"
"%1$s (fönsterstapling)"
"Maximera skärmen"
"Ändra storlek"
"Det går inte att flytta appen hit"
+ "Öppnar nytt appfönster"
+ "Stänger appfönstret"
+ "Minimerar appfönstret"
+ "%1$s (i fokus)"
+ "%1$s (inte i fokus)"
"Immersiv"
"Återställ"
"Utöka"
diff --git a/wmshell/res/values-sw/strings.xml b/wmshell/res/values-sw/strings.xml
index 3b113375e7..f647be60ed 100644
--- a/wmshell/res/values-sw/strings.xml
+++ b/wmshell/res/values-sw/strings.xml
@@ -36,6 +36,7 @@
"Programu hii inaweza kufunguliwa katika dirisha 1 pekee"
"Huenda programu isifanye kazi kwenye dirisha lingine."
"Programu hii haiwezi kufunguliwa kwenye madirisha mengine."
+ "Programu haitumii kiputo"
"Kitenganishi cha kugawa skrini"
"Kitenganishi cha kugawa skrini"
"Skrini nzima ya kushoto"
@@ -133,12 +134,18 @@
"Dhibiti Windows"
"Badilisha uwiano"
"Boresha Mwonekano"
+ "Aikoni ya Hitilafu"
"Funga"
"Funga Menyu"
"%1$s (Kupanga madirisha ya kompyuta ya mezani)"
"Panua Dirisha kwenye Skrini"
"Badilisha ukubwa"
"Imeshindwa kuhamishia programu hapa"
+ "Kufungua dirisha jipya la programu"
+ "Inafunga dirisha la programu"
+ "Kupunguza dirisha la programu"
+ "%1$s (Imeangaziwa)"
+ "%1$s (Haiangaziwi)"
"Shirikishi"
"Rejesha"
"Panua"
diff --git a/wmshell/res/values-ta/strings.xml b/wmshell/res/values-ta/strings.xml
index 4ed3927900..8dcd767fb6 100644
--- a/wmshell/res/values-ta/strings.xml
+++ b/wmshell/res/values-ta/strings.xml
@@ -36,6 +36,7 @@
"இந்த ஆப்ஸை 1 சாளரத்தில் மட்டுமே திறக்க முடியும்"
"இரண்டாம்நிலைத் திரையில் ஆப்ஸ் வேலை செய்யாமல் போகக்கூடும்."
"இரண்டாம்நிலைத் திரைகளில் பயன்பாட்டைத் தொடங்க முடியாது."
+ "குமிழை ஆப்ஸ் ஆதரிப்பதில்லை"
"திரைப் பிரிப்பான்"
"திரைப் பிரிப்பான்"
"இடது புறம் முழுத் திரை"
@@ -97,7 +98,7 @@
"அமைப்புகளில் இந்த ஆப்ஸின் தோற்ற விகிதத்தை மாற்றும்"
"தோற்ற விகிதத்தை மாற்றும்"
"கேமரா தொடர்பான சிக்கல்களா?\nமீண்டும் பொருத்த தட்டவும்"
- "சிக்கல்கள் சரிசெய்யப்படவில்லையா?\nமாற்றியமைக்க தட்டவும்"
+ "சிக்கல்கள் சரிசெய்யப்படவில்லையா?\nமுன்போல் மாற்ற தட்டவும்"
"கேமரா தொடர்பான சிக்கல்கள் எதுவும் இல்லையா? நிராகரிக்க தட்டவும்."
"ஆப்ஸ் மெனுவை இங்கே பார்க்கலாம்"
"பல ஆப்ஸை ஒன்றாகத் திறக்க டெஸ்க்டாப் சாளரமாக்குதலுக்குச் செல்லலாம்"
@@ -133,12 +134,18 @@
"சாளரங்களை நிர்வகிக்கலாம்"
"தோற்ற விகிதத்தை மாற்று"
"காட்சியை மேம்படுத்தும்"
+ "பிழை ஐகான்"
"மூடும்"
"மெனுவை மூடும்"
"%1$s (டெஸ்க்டாப் சாளரமாக்குதல்)"
"திரையைப் பெரிதாக்கு"
"அளவை மாற்று"
"ஆப்ஸை இங்கே நகர்த்த முடியாது"
+ "புதிய ஆப்ஸ் சாளரத்தைத் திறக்கிறது"
+ "ஆப்ஸ் சாளரம் மூடப்படுகிறது"
+ "ஆப்ஸ் சாளரத்தைக் குறைக்கிறது"
+ "%1$s (மையப்படுத்தப்பட்டது)"
+ "%1$s (மையப்படுத்தப்படவில்லை)"
"முழுத்திரையும்"
"மீட்டெடுக்கும்"
"பெரிதாக்கும்"
diff --git a/wmshell/res/values-te/strings.xml b/wmshell/res/values-te/strings.xml
index d7f79ca472..d97f979e56 100644
--- a/wmshell/res/values-te/strings.xml
+++ b/wmshell/res/values-te/strings.xml
@@ -36,6 +36,7 @@
"ఈ యాప్ను 1 విండోలో మాత్రమే తెరవవచ్చు"
"ప్రత్యామ్నాయ డిస్ప్లేలో యాప్ పని చేయకపోవచ్చు."
"ప్రత్యామ్నాయ డిస్ప్లేల్లో ప్రారంభానికి యాప్ మద్దతు లేదు."
+ "యాప్ బబుల్ను సపోర్ట్ చేయట్లేదు"
"స్ప్లిట్ స్క్రీన్ డివైడర్"
"స్ప్లిట్ స్క్రీన్ డివైడర్"
"ఎడమవైపు ఫుల్-స్క్రీన్"
@@ -133,12 +134,18 @@
"విండోలను మేనేజ్ చేయండి"
"ఆకార నిష్పత్తిని మార్చండి"
"వీక్షణను ఆప్టిమైజ్ చేయండి"
+ "ఎర్రర్ చిహ్నం"
"మూసివేయండి"
"మెనూను మూసివేయండి"
"%1$s (డెస్క్టాప్ వీక్షణ)"
"స్క్రీన్ సైజ్ను పెంచండి"
"సైజ్ మార్చండి"
"యాప్ను ఇక్కడకి తరలించడం సాధ్యం కాదు"
+ "కొత్త యాప్ విండోను తెరుస్తోంది"
+ "యాప్ విండోను మూసివేస్తోంది"
+ "యాప్ విండోను మినిమైజ్ చేస్తోంది"
+ "%1$s (ఫోకస్డ్)"
+ "%1$s (ఫోకస్డ్గా లేదు)"
"లీనమయ్యే"
"రీస్టోర్ చేయండి"
"మ్యాగ్జిమైజ్ చేయండి"
diff --git a/wmshell/res/values-th/strings.xml b/wmshell/res/values-th/strings.xml
index cb456cbd6d..c34b76569a 100644
--- a/wmshell/res/values-th/strings.xml
+++ b/wmshell/res/values-th/strings.xml
@@ -36,6 +36,7 @@
"แอปนี้เปิดได้ใน 1 หน้าต่างเท่านั้น"
"แอปอาจไม่ทำงานในจอแสดงผลรอง"
"แอปไม่รองรับการเรียกใช้ในจอแสดงผลรอง"
+ "แอปไม่รองรับบับเบิล"
"เส้นแยกหน้าจอ"
"เส้นแยกหน้าจอ"
"เต็มหน้าจอทางซ้าย"
@@ -133,12 +134,18 @@
"จัดการหน้าต่าง"
"เปลี่ยนสัดส่วนการแสดงผล"
"เพิ่มประสิทธิภาพมุมมอง"
+ "ไอคอนข้อผิดพลาด"
"ปิด"
"ปิดเมนู"
"%1$s (หน้าต่างเดสก์ท็อป)"
"ขยายหน้าจอให้ใหญ่สุด"
"ปรับขนาด"
"ย้ายแอปมาที่นี่ไม่ได้"
+ "กำลังเปิดหน้าต่างแอปใหม่"
+ "กำลังปิดหน้าต่างแอป"
+ "กำลังย่อหน้าต่างแอป"
+ "%1$s (โฟกัสอยู่)"
+ "%1$s (ไม่โฟกัส)"
"สมจริง"
"คืนค่า"
"ขยายใหญ่สุด"
diff --git a/wmshell/res/values-tl/strings.xml b/wmshell/res/values-tl/strings.xml
index 83a7470251..58db934acd 100644
--- a/wmshell/res/values-tl/strings.xml
+++ b/wmshell/res/values-tl/strings.xml
@@ -36,6 +36,7 @@
"Sa 1 window lang puwedeng buksan ang app na ito"
"Maaaring hindi gumana ang app sa pangalawang display."
"Hindi sinusuportahan ng app ang paglulunsad sa mga pangalawang display."
+ "Hindi sinusuportahan ng app ang bubble"
"Divider ng split screen"
"Divider ng split screen"
"I-full screen ang nasa kaliwa"
@@ -133,12 +134,18 @@
"Pamahalaan ang Mga Window"
"Baguhin ang aspect ratio"
"I-optimize ang View"
+ "Icon ng Error"
"Isara"
"Isara ang Menu"
"%1$s (Desktop windowing)"
"I-maximize ang Screen"
"I-resize"
"Hindi mailipat dito ang app"
+ "Nagbubukas ng bagong window ng app"
+ "Isinasara ang window ng app"
+ "Mini-minimize ang window ng app"
+ "%1$s (Naka-focus)"
+ "%1$s (Hindi Naka-focus)"
"Immersive"
"I-restore"
"I-maximize"
diff --git a/wmshell/res/values-tr/strings.xml b/wmshell/res/values-tr/strings.xml
index 3d565bc028..540632be7c 100644
--- a/wmshell/res/values-tr/strings.xml
+++ b/wmshell/res/values-tr/strings.xml
@@ -36,6 +36,7 @@
"Bu uygulama yalnızca 1 pencerede açılabilir"
"Uygulama ikincil ekranda çalışmayabilir."
"Uygulama ikincil ekranlarda başlatılmayı desteklemiyor."
+ "Uygulama, balonu desteklemiyor"
"Bölünmüş ekran ayırıcı"
"Bölünmüş ekran ayırıcı"
"Solda tam ekran"
@@ -133,12 +134,18 @@
"Pencereleri yönet"
"En boy oranını değiştir"
"Görünümü Optimize Et"
+ "Hata Simgesi"
"Kapat"
"Menüyü kapat"
"%1$s (masaüstü pencereleme)"
"Ekranı Büyüt"
"Yeniden boyutlandır"
"Uygulama buraya taşınamıyor"
+ "Yeni uygulama penceresi açılıyor"
+ "Uygulama penceresi kapatılıyor"
+ "Uygulama penceresi simge durumuna küçültülüyor"
+ "%1$s (Odaklanmış)"
+ "%1$s (Odaklanmamış)"
"Tam ekran"
"Geri yükle"
"Ekranı kapla"
diff --git a/wmshell/res/values-uk/strings.xml b/wmshell/res/values-uk/strings.xml
index 6888497324..b42c50f31d 100644
--- a/wmshell/res/values-uk/strings.xml
+++ b/wmshell/res/values-uk/strings.xml
@@ -36,6 +36,7 @@
"Цей додаток можна відкрити лише в одному вікні"
"Додаток може не працювати на додатковому екрані."
"Додаток не підтримує запуск на додаткових екранах."
+ "Додаток не підтримує спливаючі чати"
"Розділювач екрана"
"Розділювач екрана"
"Ліве вікно на весь екран"
@@ -133,12 +134,18 @@
"Керувати вікнами"
"Змінити формат"
"Оптимізувати перегляд"
+ "Значок помилки"
"Закрити"
"Закрити меню"
"%1$s (режим вікон робочого стола)"
"Розгорнути екран"
"Змінити розмір"
"Сюди не можна перемістити додаток"
+ "Відкриття нового вікна додатка"
+ "Закриття вікна додатка"
+ "Згортання вікна додатка"
+ "%1$s (виділено)"
+ "%1$s (не виділено)"
"Реалістичність"
"Відновити"
"Розгорнути"
diff --git a/wmshell/res/values-ur/strings.xml b/wmshell/res/values-ur/strings.xml
index cf16e77272..53fd8100cb 100644
--- a/wmshell/res/values-ur/strings.xml
+++ b/wmshell/res/values-ur/strings.xml
@@ -36,6 +36,7 @@
"یہ ایپ صرف 1 ونڈو میں کھولی جا سکتی ہے"
"ممکن ہے ایپ ثانوی ڈسپلے پر کام نہ کرے۔"
"ایپ ثانوی ڈسپلیز پر شروعات کا تعاون نہیں کرتی۔"
+ "ایپ ببل کو سپورٹ نہیں کرتی ہے"
"اسپلٹ اسکرین ڈیوائیڈر"
"اسپلٹ اسکرین ڈیوائیڈر"
"بائیں فل اسکرین"
@@ -133,12 +134,18 @@
"ونڈوز کا نظم کریں"
"تناسبی شرح کو تبدیل کریں"
"منظر کو بہتر بنائیں"
+ "خرابی کا آئیکن"
"بند کریں"
"مینیو بند کریں"
"%1$s (ڈیسک ٹاپ ونڈوئنگ)"
"اسکرین کو بڑا کریں"
"سائز تبدیل کریں"
"ایپ کو یہاں منتقل نہیں کیا جا سکتا"
+ "نئی ایپ ونڈو کھل رہی ہے"
+ "ایپ ونڈو بند ہو رہی ہے"
+ "ایپ ونڈو چھوٹی ہو رہی ہے"
+ "%1$s (فوکس کردہ)"
+ "%1$s (فوکس کردہ نہیں)"
"عمیق"
"بحال کریں"
"بڑا کریں"
diff --git a/wmshell/res/values-uz/strings.xml b/wmshell/res/values-uz/strings.xml
index 9b1e30ee8f..8e2c245078 100644
--- a/wmshell/res/values-uz/strings.xml
+++ b/wmshell/res/values-uz/strings.xml
@@ -36,6 +36,7 @@
"Bu ilovani faqat 1 ta oynada ochish mumkin"
"Bu ilova qo‘shimcha ekranda ishlamasligi mumkin."
"Bu ilova qo‘shimcha ekranlarda ishga tushmaydi."
+ "Ilovada bulutcha ishlamaydi"
"Ekranni ikkiga ajratish chizigʻi"
"Ekranni ikkiga ajratish chizigʻi"
"Chapda to‘liq ekran"
@@ -133,12 +134,18 @@
"Oynalarni boshqarish"
"Tomonlar nisbatini oʻzgartirish"
"Koʻrinishni optimallashtirish"
+ "Xatolik belgisi"
"Yopish"
"Menyuni yopish"
"%1$s (Desktop rejimidagi oynalar)"
"Ekranni yoyish"
"Oʻlchamini oʻzgartirish"
"Ilova bu yerga surilmaydi"
+ "Yangi ilova oynasi ochilmoqda"
+ "Ilova oynasi yopilmoqda"
+ "Ilova oynasi kichraytirilmoqda"
+ "%1$s (fokuslangan)"
+ "%1$s (fokuslanmagan)"
"Immersiv"
"Tiklash"
"Yoyish"
diff --git a/wmshell/res/values-vi/strings.xml b/wmshell/res/values-vi/strings.xml
index 4397ccddc4..dd4066dfac 100644
--- a/wmshell/res/values-vi/strings.xml
+++ b/wmshell/res/values-vi/strings.xml
@@ -36,6 +36,7 @@
"Ứng dụng này chỉ có thể mở trong 1 cửa sổ"
"Ứng dụng có thể không hoạt động trên màn hình phụ."
"Ứng dụng không hỗ trợ khởi chạy trên màn hình phụ."
+ "Ứng dụng không hỗ trợ bong bóng"
"Trình chia đôi màn hình"
"Trình chia đôi màn hình"
"Toàn màn hình bên trái"
@@ -133,12 +134,18 @@
"Quản lý cửa sổ"
"Thay đổi tỷ lệ khung hình"
"Tối ưu hoá chế độ xem"
+ "Biểu tượng lỗi"
"Đóng"
"Đóng trình đơn"
"%1$s (Chế độ cửa sổ trên máy tính)"
"Mở rộng màn hình"
"Đổi kích thước"
"Không di chuyển được ứng dụng đến đây"
+ "Đang mở cửa sổ ứng dụng mới"
+ "Đang đóng cửa sổ ứng dụng"
+ "Đang thu nhỏ cửa sổ ứng dụng"
+ "%1$s (Đã lấy tiêu điểm)"
+ "%1$s (Chưa lấy tiêu điểm)"
"Hiển thị tối đa"
"Khôi phục"
"Phóng to tối đa"
diff --git a/wmshell/res/values-zh-rCN/strings.xml b/wmshell/res/values-zh-rCN/strings.xml
index 6986115d24..14ac1c39b8 100644
--- a/wmshell/res/values-zh-rCN/strings.xml
+++ b/wmshell/res/values-zh-rCN/strings.xml
@@ -36,6 +36,7 @@
"此应用只能在 1 个窗口中打开"
"应用可能无法在辅显示屏上正常运行。"
"应用不支持在辅显示屏上启动。"
+ "该应用不支持气泡框"
"分屏分隔线"
"分屏分隔线"
"左侧全屏"
@@ -133,12 +134,18 @@
"管理窗口"
"更改宽高比"
"优化视图"
+ "错误图标"
"关闭"
"关闭菜单"
"%1$s(窗口化模式)"
"最大化屏幕"
"调整大小"
"无法将应用移至此处"
+ "正在打开新的应用窗口"
+ "正在关闭应用窗口"
+ "正在最小化应用窗口"
+ "%1$s(已聚焦)"
+ "%1$s(未聚焦)"
"沉浸式"
"恢复"
"最大化"
diff --git a/wmshell/res/values-zh-rHK/strings.xml b/wmshell/res/values-zh-rHK/strings.xml
index 6252efb375..62e46db899 100644
--- a/wmshell/res/values-zh-rHK/strings.xml
+++ b/wmshell/res/values-zh-rHK/strings.xml
@@ -36,6 +36,7 @@
"此應用程式只可在 1 個視窗中開啟"
"應用程式可能無法在次要顯示屏上運作。"
"應用程式無法在次要顯示屏上啟動。"
+ "應用程式不支援小視窗"
"分割螢幕分隔線"
"分割螢幕分隔線"
"左邊全螢幕"
@@ -133,12 +134,18 @@
"管理視窗"
"變更長寬比"
"優化檢視模式"
+ "錯誤圖示"
"關閉"
"關閉選單"
"%1$s (桌面電腦視窗模式)"
"畫面最大化"
"調整大小"
"應用程式無法移至這裡"
+ "正在開啟新應用程式視窗"
+ "閂緊應用程式視窗"
+ "正在將應用程式視窗縮到最小"
+ "%1$s (焦點)"
+ "%1$s (非焦點)"
"身歷其境"
"還原"
"最大化"
diff --git a/wmshell/res/values-zh-rTW/strings.xml b/wmshell/res/values-zh-rTW/strings.xml
index 3c230349fd..f36f616355 100644
--- a/wmshell/res/values-zh-rTW/strings.xml
+++ b/wmshell/res/values-zh-rTW/strings.xml
@@ -36,6 +36,7 @@
"這個應用程式只能在 1 個視窗中開啟"
"應用程式可能無法在次要顯示器上運作。"
"應用程式無法在次要顯示器上啟動。"
+ "應用程式不支援泡泡"
"分割畫面分隔線"
"分割畫面分隔線"
"以全螢幕顯示左側畫面"
@@ -133,12 +134,18 @@
"管理視窗"
"變更顯示比例"
"最佳化檢視畫面"
+ "錯誤圖示"
"關閉"
"關閉選單"
"%1$s (電腦分割視窗)"
"畫面最大化"
"調整大小"
"應用程式無法移至此處"
+ "正在開啟新的應用程式視窗"
+ "正在關閉應用程式視窗"
+ "正在最小化應用程式視窗"
+ "%1$s (前景)"
+ "%1$s (背景)"
"沉浸"
"還原"
"最大化"
diff --git a/wmshell/res/values-zu/strings.xml b/wmshell/res/values-zu/strings.xml
index 61dacd4b4a..1d20e40900 100644
--- a/wmshell/res/values-zu/strings.xml
+++ b/wmshell/res/values-zu/strings.xml
@@ -36,6 +36,7 @@
"Le-app ingavulwa kuphela ewindini eli-1."
"Uhlelo lokusebenza kungenzeka lungasebenzi kusibonisi sesibili."
"Uhlelo lokusebenza alusekeli ukuqalisa kuzibonisi zesibili."
+ "I-app ayilisekeli ibhamuza"
"Isihlukanisi sokuhlukanisa isikrini"
"Isihlukanisi sokuhlukanisa isikrini"
"Isikrini esigcwele esingakwesokunxele"
@@ -133,12 +134,18 @@
"Phatha Amawindi"
"Shintsha ukubukeka kwesilinganiselo"
"Lungiselela Ukubuka"
+ "Isithonjana Sephutha"
"Vala"
"Vala Imenyu"
"%1$s (Ukwenziwa kwamawindi amaningi kwedeskithophu)"
"Khulisa Isikrini Sifike Ekugcineni"
"Shintsha usayizi"
"I-app ayikwazi ukuhanjiswa lapha"
+ "Ivula iwindi elisha le-app"
+ "Ivala iwindi le-app"
+ "Ukunciphisa iwindi le-app"
+ "%1$s (Igxilile)"
+ "%1$s (Akugxilile)"
"Okugxilile"
"Buyisela"
"Khulisa"
diff --git a/wmshell/res/values/attrs.xml b/wmshell/res/values/attrs.xml
index 4ba0468a74..94373906dd 100644
--- a/wmshell/res/values/attrs.xml
+++ b/wmshell/res/values/attrs.xml
@@ -30,4 +30,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wmshell/res/values/config.xml b/wmshell/res/values/config.xml
index 59e6c7786c..9463d9cbc0 100644
--- a/wmshell/res/values/config.xml
+++ b/wmshell/res/values/config.xml
@@ -23,10 +23,6 @@
TODO(b/238217847): This config is temporary until we refactor the base WMComponent. -->
true
-
- true
-
425
@@ -142,6 +138,10 @@
Needs to be less that or equal to 1. -->
- 0.5625
+
+ - 0.75
+
0x55
diff --git a/wmshell/res/values/dimen.xml b/wmshell/res/values/dimen.xml
index ca18c97f91..838c879d40 100644
--- a/wmshell/res/values/dimen.xml
+++ b/wmshell/res/values/dimen.xml
@@ -291,9 +291,6 @@
28dp
24dp
- 60dp
- 24dp
- 48dp
84dp
48dp
@@ -306,6 +303,8 @@
140dp
3dp
+
+ 60dp
24dp
@@ -662,6 +661,13 @@
6dp
+
+ 130dp
+
+ 16dp
+
+ 8dp
+
8dp
diff --git a/wmshell/res/values/strings.xml b/wmshell/res/values/strings.xml
index 1fd4704f78..508f9dff30 100644
--- a/wmshell/res/values/strings.xml
+++ b/wmshell/res/values/strings.xml
@@ -74,6 +74,8 @@
App may not work on a secondary display.
App does not support launch on secondary displays.
+
+ App does not support bubble
Split screen divider
@@ -316,6 +318,8 @@
Change aspect ratio
Optimize View
+
+ Error Icon
Close
@@ -328,6 +332,18 @@
Resize
App can\'t be moved here
+
+
+ Opening new app window
+
+ Closing app window
+
+ Minimizing app window
+
+ %1$s (Focused)
+
+ %1$s (Not Focused)
+
Immersive
diff --git a/wmshell/res/values/styles.xml b/wmshell/res/values/styles.xml
index 678a43e70c..b7030d96c6 100644
--- a/wmshell/res/values/styles.xml
+++ b/wmshell/res/values/styles.xml
@@ -43,8 +43,8 @@
@@ -113,23 +113,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wmshell/shared/src/com/android/wm/shell/shared/split/SplitBounds.java b/wmshell/shared/src/com/android/wm/shell/shared/split/SplitBounds.java
index 7c1faa667d..5e17d75206 100644
--- a/wmshell/shared/src/com/android/wm/shell/shared/split/SplitBounds.java
+++ b/wmshell/shared/src/com/android/wm/shell/shared/split/SplitBounds.java
@@ -15,12 +15,18 @@
*/
package com.android.wm.shell.shared.split;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
+import androidx.annotation.NonNull;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.Objects;
/**
@@ -47,13 +53,42 @@ public class SplitBounds implements Parcelable {
* the bounds were originally in
*/
public final boolean appsStackedVertically;
+ /**
+ * If {@code true}, that means at the time of creation of this object, the phone was in
+ * seascape orientation. This is important on devices with insets, because they do not split
+ * evenly -- one of the insets must be slightly larger to account for the inset.
+ * From landscape, it is the leftTop task that expands slightly.
+ * From seascape, it is the rightBottom task that expands slightly.
+ */
+ public final boolean initiatedFromSeascape;
+ /** @deprecated Use {@link #leftTopTaskIds} instead. */
+ @Deprecated
public final int leftTopTaskId;
+ /** @deprecated Use {@link #rightBottomTaskIds} instead. */
+ @Deprecated
public final int rightBottomTaskId;
+ @NonNull
+ public final List leftTopTaskIds;
+ @NonNull
+ public final List rightBottomTaskIds;
- public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds, int leftTopTaskId,
- int rightBottomTaskId, @PersistentSnapPosition int snapPosition) {
+ public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds,
+ int leftTopTaskId, int rightBottomTaskId,
+ @NonNull List leftTopTaskIds, @NonNull List rightBottomTaskIds,
+ @PersistentSnapPosition int snapPosition) {
+ if (leftTopTaskId == INVALID_TASK_ID || rightBottomTaskId == INVALID_TASK_ID
+ || leftTopTaskId == rightBottomTaskId
+ || leftTopTaskIds.isEmpty() || rightBottomTaskIds.isEmpty()) {
+ throw new IllegalArgumentException("The Split task ids are invalid:"
+ + " leftTopTaskId: " + leftTopTaskId
+ + " rightBottomTaskId: " + rightBottomTaskId
+ + " leftTopTaskId size: " + leftTopTaskIds.size()
+ + " rightBottomTaskId size: " + rightBottomTaskIds.size());
+ }
this.leftTopBounds = leftTopBounds;
this.rightBottomBounds = rightBottomBounds;
+ this.leftTopTaskIds = List.copyOf(leftTopTaskIds);
+ this.rightBottomTaskIds = List.copyOf(rightBottomTaskIds);
this.leftTopTaskId = leftTopTaskId;
this.rightBottomTaskId = rightBottomTaskId;
this.snapPosition = snapPosition;
@@ -63,11 +98,18 @@ public class SplitBounds implements Parcelable {
this.visualDividerBounds = new Rect(leftTopBounds.left, leftTopBounds.bottom,
leftTopBounds.right, rightBottomBounds.top);
appsStackedVertically = true;
+ initiatedFromSeascape = false;
} else {
// horizontal apps, vertical divider
this.visualDividerBounds = new Rect(leftTopBounds.right, leftTopBounds.top,
rightBottomBounds.left, leftTopBounds.bottom);
appsStackedVertically = false;
+ // The following check is unreliable on devices without insets
+ // (initiatedFromSeascape will always be set to false.) This happens to be OK for
+ // all our current uses, but should be refactored.
+ // TODO: Create a more reliable check, or refactor how splitting works on devices
+ // with insets.
+ initiatedFromSeascape = rightBottomBounds.width() > leftTopBounds.width();
}
float totalWidth = rightBottomBounds.right - leftTopBounds.left;
@@ -78,6 +120,46 @@ public class SplitBounds implements Parcelable {
dividerHeightPercent = visualDividerBounds.height() / totalHeight;
}
+ public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds, int leftTopTaskId,
+ int rightBottomTaskId, @PersistentSnapPosition int snapPosition) {
+ this(leftTopBounds, rightBottomBounds, leftTopTaskId, rightBottomTaskId,
+ Collections.singletonList(leftTopTaskId),
+ Collections.singletonList(rightBottomTaskId), snapPosition);
+ }
+
+ /**
+ * Returns the percentage size of the left/top task (compared to the full width/height of
+ * the split pair). E.g. if the left task is 4 units wide, the divider is 2 units, and the
+ * right task is 4 units, this method will return 0.4f.
+ */
+ public float getLeftTopTaskPercent() {
+ // topTaskPercent and leftTaskPercent are defined at creation time, and are not updated
+ // on device rotate, so we have to check appsStackedVertically to return the right
+ // creation-time measurements.
+ return appsStackedVertically ? topTaskPercent : leftTaskPercent;
+ }
+
+ /**
+ * Returns the percentage size of the divider's thickness (compared to the full width/height
+ * of the split pair). E.g. if the left task is 4 units wide, the divider is 2 units, and
+ * the right task is 4 units, this method will return 0.2f.
+ */
+ public float getDividerPercent() {
+ // dividerHeightPercent and dividerWidthPercent are defined at creation time, and are
+ // not updated on device rotate, so we have to check appsStackedVertically to return
+ // the right creation-time measurements.
+ return appsStackedVertically ? dividerHeightPercent : dividerWidthPercent;
+ }
+
+ /**
+ * Returns the percentage size of the right/bottom task (compared to the full width/height
+ * of the split pair). E.g. if the left task is 4 units wide, the divider is 2 units, and
+ * the right task is 4 units, this method will return 0.4f.
+ */
+ public float getRightBottomTaskPercent() {
+ return 1 - (getLeftTopTaskPercent() + getDividerPercent());
+ }
+
public SplitBounds(Parcel parcel) {
leftTopBounds = parcel.readTypedObject(Rect.CREATOR);
rightBottomBounds = parcel.readTypedObject(Rect.CREATOR);
@@ -85,11 +167,22 @@ public class SplitBounds implements Parcelable {
topTaskPercent = parcel.readFloat();
leftTaskPercent = parcel.readFloat();
appsStackedVertically = parcel.readBoolean();
- leftTopTaskId = parcel.readInt();
- rightBottomTaskId = parcel.readInt();
+ initiatedFromSeascape = parcel.readBoolean();
dividerWidthPercent = parcel.readFloat();
dividerHeightPercent = parcel.readFloat();
snapPosition = parcel.readInt();
+ leftTopTaskId = parcel.readInt();
+ rightBottomTaskId = parcel.readInt();
+ int size = parcel.readInt();
+ leftTopTaskIds = new ArrayList<>();
+ for (int i = 0; i < size; i++) {
+ leftTopTaskIds.add(parcel.readInt());
+ }
+ size = parcel.readInt();
+ rightBottomTaskIds = new ArrayList<>();
+ for (int i = 0; i < size; i++) {
+ rightBottomTaskIds.add(parcel.readInt());
+ }
}
@Override
@@ -100,11 +193,20 @@ public class SplitBounds implements Parcelable {
parcel.writeFloat(topTaskPercent);
parcel.writeFloat(leftTaskPercent);
parcel.writeBoolean(appsStackedVertically);
- parcel.writeInt(leftTopTaskId);
- parcel.writeInt(rightBottomTaskId);
+ parcel.writeBoolean(initiatedFromSeascape);
parcel.writeFloat(dividerWidthPercent);
parcel.writeFloat(dividerHeightPercent);
parcel.writeInt(snapPosition);
+ parcel.writeInt(leftTopTaskId);
+ parcel.writeInt(rightBottomTaskId);
+ parcel.writeInt(leftTopTaskIds.size());
+ for (Integer id : leftTopTaskIds) {
+ parcel.writeInt(id); // Write each Integer in the List
+ }
+ parcel.writeInt(rightBottomTaskIds.size());
+ for (Integer id : rightBottomTaskIds) {
+ parcel.writeInt(id); // Write each Integer in the List
+ }
}
@Override
@@ -121,19 +223,23 @@ public class SplitBounds implements Parcelable {
final SplitBounds other = (SplitBounds) obj;
return Objects.equals(leftTopBounds, other.leftTopBounds)
&& Objects.equals(rightBottomBounds, other.rightBottomBounds)
- && leftTopTaskId == other.leftTopTaskId
- && rightBottomTaskId == other.rightBottomTaskId;
+ && leftTopTaskIds.equals(other.leftTopTaskIds)
+ && rightBottomTaskIds.equals(other.rightBottomTaskIds)
+ && snapPosition == other.snapPosition;
}
@Override
public int hashCode() {
- return Objects.hash(leftTopBounds, rightBottomBounds, leftTopTaskId, rightBottomTaskId);
+ return Objects.hash(leftTopBounds, rightBottomBounds,
+ leftTopTaskId, rightBottomTaskId, leftTopTaskIds, rightBottomTaskIds);
}
@Override
public String toString() {
- return "LeftTop: " + leftTopBounds + ", taskId: " + leftTopTaskId + "\n"
- + "RightBottom: " + rightBottomBounds + ", taskId: " + rightBottomTaskId + "\n"
+ return "LeftTop: " + leftTopBounds + " taskId: " + leftTopTaskId
+ + ", taskIds: " + leftTopTaskIds + "\n"
+ + "RightBottom: " + rightBottomBounds + " taskId: " + rightBottomTaskId
+ + ", taskIds: " + rightBottomTaskIds + "\n"
+ "Divider: " + visualDividerBounds + "\n"
+ "AppsVertical? " + appsStackedVertically + "\n"
+ "snapPosition: " + snapPosition;
diff --git a/wmshell/tests/OWNERS b/wmshell/tests/OWNERS
index b8a19ad353..0c18bef982 100644
--- a/wmshell/tests/OWNERS
+++ b/wmshell/tests/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 1157642
+# Bug component: 928594
# includes OWNERS from parent directories
natanieljr@google.com
pablogamito@google.com
@@ -6,12 +6,18 @@ pablogamito@google.com
lbill@google.com
madym@google.com
hwwang@google.com
+gabiyev@google.com
chenghsiuchang@google.com
atsjenk@google.com
jorgegil@google.com
-nmusgrave@google.com
+vaniadesmonda@google.com
pbdr@google.com
-tkachenkoi@google.com
mpodolian@google.com
jeremysim@google.com
peanutbutter@google.com
+pragyabajoria@google.com
+uysalorhan@google.com
+gsennton@google.com
+mattsziklay@google.com
+mdehaini@google.com
+toshikikikuchi@google.com
diff --git a/wmshell/tests/flicker/Android.bp b/wmshell/tests/flicker/Android.bp
index 4abaf5bd4a..98b0bd0b58 100644
--- a/wmshell/tests/flicker/Android.bp
+++ b/wmshell/tests/flicker/Android.bp
@@ -30,7 +30,6 @@ filegroup {
java_library {
name: "wm-shell-flicker-utils",
- platform_apis: true,
optimize: {
enabled: false,
},
@@ -39,15 +38,15 @@ java_library {
],
static_libs: [
"androidx.test.ext.junit",
- "flickertestapplib",
+ "com_android_wm_shell_flags_lib",
"flickerlib",
"flickerlib-helpers",
+ "flickertestapplib",
+ "launcher-aosp-tapl",
+ "launcher-helper-lib",
"platform-test-annotations",
"wm-flicker-common-app-helpers",
"wm-flicker-common-assertions",
- "launcher-helper-lib",
- "launcher-aosp-tapl",
- "com_android_wm_shell_flags_lib",
],
}
@@ -59,19 +58,20 @@ java_defaults {
enabled: false,
},
test_suites: ["device-tests"],
- libs: ["android.test.runner"],
+ libs: ["android.test.runner.stubs.system"],
static_libs: [
- "wm-shell-flicker-utils",
"androidx.test.ext.junit",
- "flickertestapplib",
"flickerlib",
"flickerlib-helpers",
"flickerlib-trace_processor_shell",
+ "flickertestapplib",
+ "launcher-aosp-tapl",
+ "launcher-helper-lib",
"platform-test-annotations",
+ "platform-test-rules",
"wm-flicker-common-app-helpers",
"wm-flicker-common-assertions",
- "launcher-helper-lib",
- "launcher-aosp-tapl",
+ "wm-shell-flicker-utils",
],
data: [
":FlickerTestApp",
diff --git a/wmshell/tests/flicker/appcompat/Android.bp b/wmshell/tests/flicker/appcompat/Android.bp
index e151ab2c58..b016c9f941 100644
--- a/wmshell/tests/flicker/appcompat/Android.bp
+++ b/wmshell/tests/flicker/appcompat/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_lse_app_compat",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
@@ -23,21 +24,76 @@ package {
default_applicable_licenses: ["frameworks_base_license"],
}
-filegroup {
- name: "WMShellFlickerTestsAppCompat-src",
- srcs: [
- "src/**/*.kt",
- ],
-}
-
android_test {
- name: "WMShellFlickerTestsOther",
+ name: "WMShellFlickerTestsAppCompat",
defaults: ["WMShellFlickerTestsDefault"],
manifest: "AndroidManifest.xml",
package_name: "com.android.wm.shell.flicker",
instrumentation_target_package: "com.android.wm.shell.flicker",
test_config_template: "AndroidTestTemplate.xml",
- srcs: [":WMShellFlickerTestsAppCompat-src"],
+ srcs: ["src/**/*.kt"],
static_libs: ["WMShellFlickerTestsBase"],
data: ["trace_config/*"],
}
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for WMShellFlickerTestsAppCompat module
+
+test_module_config {
+ name: "WMShellFlickerTestsAppCompat-CatchAll",
+ base: "WMShellFlickerTestsAppCompat",
+ exclude_filters: [
+ "com.android.wm.shell.flicker.appcompat.OpenAppInSizeCompatModeTest",
+ "com.android.wm.shell.flicker.appcompat.OpenTransparentActivityTest",
+ "com.android.wm.shell.flicker.appcompat.QuickSwitchLauncherToLetterboxAppTest",
+ "com.android.wm.shell.flicker.appcompat.RepositionFixedPortraitAppTest",
+ "com.android.wm.shell.flicker.appcompat.RestartAppInSizeCompatModeTest",
+ "com.android.wm.shell.flicker.appcompat.RotateImmersiveAppInFullscreenTest",
+ ],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsAppCompat-OpenAppInSizeCompatModeTest",
+ base: "WMShellFlickerTestsAppCompat",
+ include_filters: ["com.android.wm.shell.flicker.appcompat.OpenAppInSizeCompatModeTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsAppCompat-OpenTransparentActivityTest",
+ base: "WMShellFlickerTestsAppCompat",
+ include_filters: ["com.android.wm.shell.flicker.appcompat.OpenTransparentActivityTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsAppCompat-QuickSwitchLauncherToLetterboxAppTest",
+ base: "WMShellFlickerTestsAppCompat",
+ include_filters: ["com.android.wm.shell.flicker.appcompat.QuickSwitchLauncherToLetterboxAppTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsAppCompat-RepositionFixedPortraitAppTest",
+ base: "WMShellFlickerTestsAppCompat",
+ include_filters: ["com.android.wm.shell.flicker.appcompat.RepositionFixedPortraitAppTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsAppCompat-RestartAppInSizeCompatModeTest",
+ base: "WMShellFlickerTestsAppCompat",
+ include_filters: ["com.android.wm.shell.flicker.appcompat.RestartAppInSizeCompatModeTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsAppCompat-RotateImmersiveAppInFullscreenTest",
+ base: "WMShellFlickerTestsAppCompat",
+ include_filters: ["com.android.wm.shell.flicker.appcompat.RotateImmersiveAppInFullscreenTest"],
+ test_suites: ["device-tests"],
+}
+
+// End breakdowns for FlickerTestsRotation module
+////////////////////////////////////////////////////////////////////////////////
diff --git a/wmshell/tests/flicker/appcompat/AndroidTestTemplate.xml b/wmshell/tests/flicker/appcompat/AndroidTestTemplate.xml
index f69a90cc79..a4ecac9dfe 100644
--- a/wmshell/tests/flicker/appcompat/AndroidTestTemplate.xml
+++ b/wmshell/tests/flicker/appcompat/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
+
+
+
+
@@ -44,6 +48,10 @@
+
+
+
+
diff --git a/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt b/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
index 16c2d47f9d..27303c1889 100644
--- a/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
+++ b/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
@@ -31,7 +31,7 @@ import org.junit.runners.Parameterized
/**
* Test launching app in size compat mode.
*
- * To run this test: `atest WMShellFlickerTestsOther:OpenAppInSizeCompatModeTest`
+ * To run this test: `atest WMShellFlickerTestsAppCompat:OpenAppInSizeCompatModeTest`
*
* Actions:
* ```
diff --git a/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt b/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
index d85b7718aa..e176f47d40 100644
--- a/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
+++ b/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.appcompat
import android.platform.test.annotations.Postsubmit
-import android.tools.Rotation
import android.tools.flicker.assertions.FlickerTest
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
@@ -32,7 +31,7 @@ import org.junit.runners.Parameterized
/**
* Test launching app in size compat mode.
*
- * To run this test: `atest WMShellFlickerTestsOther:OpenTransparentActivityTest`
+ * To run this test: `atest WMShellFlickerTestsAppCompat:OpenTransparentActivityTest`
*
* Actions:
* ```
@@ -109,9 +108,7 @@ class OpenTransparentActivityTest(flicker: LegacyFlickerTest) : TransparentBaseA
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection {
- return LegacyFlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(Rotation.ROTATION_90)
- )
+ return LegacyFlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
index 164534c14d..9b8c949a17 100644
--- a/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
+++ b/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
@@ -20,7 +20,6 @@ import android.graphics.Rect
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.RequiresDevice
import android.tools.NavBar
-import android.tools.Rotation
import android.tools.flicker.assertions.FlickerTest
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
@@ -36,7 +35,7 @@ import org.junit.runners.Parameterized
/**
* Test quick switching to letterboxed app from launcher
*
- * To run this test: `atest WMShellFlickerTestsOther:QuickSwitchLauncherToLetterboxAppTest`
+ * To run this test: `atest WMShellFlickerTestsAppCompat:QuickSwitchLauncherToLetterboxAppTest`
*
* Actions:
* ```
@@ -266,8 +265,7 @@ class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) : BaseAp
@JvmStatic
fun getParams(): Collection {
return LegacyFlickerTestFactory.nonRotationTests(
- supportedNavigationModes = listOf(NavBar.MODE_GESTURAL),
- supportedRotations = listOf(Rotation.ROTATION_90)
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
}
}
diff --git a/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt b/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
index 034d54b185..43ee1866d0 100644
--- a/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
+++ b/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.appcompat
import android.platform.test.annotations.Postsubmit
-import android.tools.Rotation
import android.tools.flicker.assertions.FlickerTest
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
@@ -32,7 +31,7 @@ import org.junit.runners.Parameterized
/**
* Test launching a fixed portrait letterboxed app in landscape and repositioning to the right.
*
- * To run this test: `atest WMShellFlickerTestsOther:RepositionFixedPortraitAppTest`
+ * To run this test: `atest WMShellFlickerTestsAppCompat:RepositionFixedPortraitAppTest`
*
* Actions:
*
@@ -91,9 +90,7 @@ class RepositionFixedPortraitAppTest(flicker: LegacyFlickerTest) : BaseAppCompat
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection {
- return LegacyFlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(Rotation.ROTATION_90)
- )
+ return LegacyFlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt b/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
index 443fac19c7..5459ef03e3 100644
--- a/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
+++ b/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
@@ -31,7 +31,7 @@ import org.junit.runners.Parameterized
/**
* Test restarting app in size compat mode.
*
- * To run this test: `atest WMShellFlickerTestsOther:RestartAppInSizeCompatModeTest`
+ * To run this test: `atest WMShellFlickerTestsAppCompat:RestartAppInSizeCompatModeTest`
*
* Actions:
* ```
diff --git a/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt b/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt
index 22543aa9f7..5bb96401bc 100644
--- a/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt
+++ b/wmshell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt
@@ -45,7 +45,7 @@ import org.junit.runners.Parameterized
/**
* Test rotating an immersive app in fullscreen.
*
- * To run this test: `atest WMShellFlickerTestsOther:RotateImmersiveAppInFullscreenTest`
+ * To run this test: `atest WMShellFlickerTestsAppCompat:RotateImmersiveAppInFullscreenTest`
*
* Actions:
* ```
diff --git a/wmshell/tests/flicker/bubble/Android.bp b/wmshell/tests/flicker/bubble/Android.bp
index f0b4f1faad..2ff7ab231c 100644
--- a/wmshell/tests/flicker/bubble/Android.bp
+++ b/wmshell/tests/flicker/bubble/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_multitasking_windowing",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
@@ -34,3 +35,57 @@ android_test {
static_libs: ["WMShellFlickerTestsBase"],
data: ["trace_config/*"],
}
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for WMShellFlickerTestsBubbles module
+
+test_module_config {
+ name: "WMShellFlickerTestsBubbles-CatchAll",
+ base: "WMShellFlickerTestsBubbles",
+ exclude_filters: [
+ "com.android.wm.shell.flicker.bubble.ChangeActiveActivityFromBubbleTest",
+ "com.android.wm.shell.flicker.bubble.DragToDismissBubbleScreenTest",
+ "com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleOnLocksreenTest",
+ "com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleTest",
+ "com.android.wm.shell.flicker.bubble.SendBubbleNotificationTest",
+ ],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsBubbles-ChangeActiveActivityFromBubbleTest",
+ base: "WMShellFlickerTestsBubbles",
+ include_filters: ["com.android.wm.shell.flicker.bubble.ChangeActiveActivityFromBubbleTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsBubbles-DragToDismissBubbleScreenTest",
+ base: "WMShellFlickerTestsBubbles",
+ include_filters: ["com.android.wm.shell.flicker.bubble.DragToDismissBubbleScreenTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsBubbles-OpenActivityFromBubbleOnLocksreenTest",
+ base: "WMShellFlickerTestsBubbles",
+ include_filters: ["com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleOnLocksreenTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsBubbles-OpenActivityFromBubbleTest",
+ base: "WMShellFlickerTestsBubbles",
+ include_filters: ["com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsBubbles-SendBubbleNotificationTest",
+ base: "WMShellFlickerTestsBubbles",
+ include_filters: ["com.android.wm.shell.flicker.bubble.SendBubbleNotificationTest"],
+ test_suites: ["device-tests"],
+}
+
+// End breakdowns for WMShellFlickerTestsBubbles module
+////////////////////////////////////////////////////////////////////////////////
diff --git a/wmshell/tests/flicker/bubble/AndroidTestTemplate.xml b/wmshell/tests/flicker/bubble/AndroidTestTemplate.xml
index b76d065657..75ffdc69c7 100644
--- a/wmshell/tests/flicker/bubble/AndroidTestTemplate.xml
+++ b/wmshell/tests/flicker/bubble/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
+
+
+
+
@@ -44,13 +48,19 @@
+
+
+
+
+
+
diff --git a/wmshell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt b/wmshell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
index 2ee53f4fce..d7ea9f3a8f 100644
--- a/wmshell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
+++ b/wmshell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
@@ -100,14 +100,14 @@ class OpenActivityFromBubbleOnLocksreenTest(flicker: LegacyFlickerTest) :
@Postsubmit
@Test
fun navBarLayerIsVisibleAtEnd() {
- Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(usesTaskbar)
flicker.navBarLayerIsVisibleAtEnd()
}
@Postsubmit
@Test
fun navBarLayerPositionAtEnd() {
- Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(usesTaskbar)
flicker.navBarLayerPositionAtEnd()
}
@@ -154,7 +154,7 @@ class OpenActivityFromBubbleOnLocksreenTest(flicker: LegacyFlickerTest) :
@Postsubmit
@Test
fun taskBarLayerIsVisibleAtEnd() {
- Assume.assumeTrue(flicker.scenario.isTablet)
+ Assume.assumeTrue(usesTaskbar)
flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
}
}
diff --git a/wmshell/tests/flicker/pip/Android.bp b/wmshell/tests/flicker/pip/Android.bp
index faeb342a44..b8ec1d76d5 100644
--- a/wmshell/tests/flicker/pip/Android.bp
+++ b/wmshell/tests/flicker/pip/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_multitasking_windowing",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
@@ -24,27 +25,8 @@ package {
}
filegroup {
- name: "WMShellFlickerTestsPip1-src",
- srcs: [
- "src/**/A*.kt",
- "src/**/B*.kt",
- "src/**/C*.kt",
- "src/**/D*.kt",
- "src/**/F*.kt",
- "src/**/S*.kt",
- ],
-}
-
-filegroup {
- name: "WMShellFlickerTestsPip2-src",
- srcs: [
- "src/**/E*.kt",
- ],
-}
-
-filegroup {
- name: "WMShellFlickerTestsPip3-src",
- srcs: ["src/**/*.kt"],
+ name: "WMShellFlickerTestsPipApps-src",
+ srcs: ["src/**/apps/*.kt"],
}
filegroup {
@@ -52,57 +34,15 @@ filegroup {
srcs: ["src/**/common/*.kt"],
}
-filegroup {
- name: "WMShellFlickerTestsPipApps-src",
- srcs: ["src/**/apps/*.kt"],
-}
-
android_test {
- name: "WMShellFlickerTestsPip1",
+ name: "WMShellFlickerTestsPip",
defaults: ["WMShellFlickerTestsDefault"],
manifest: "AndroidManifest.xml",
package_name: "com.android.wm.shell.flicker.pip",
instrumentation_target_package: "com.android.wm.shell.flicker.pip",
test_config_template: "AndroidTestTemplate.xml",
- srcs: [
- ":WMShellFlickerTestsPip1-src",
- ":WMShellFlickerTestsPipCommon-src",
- ],
- static_libs: ["WMShellFlickerTestsBase"],
- data: ["trace_config/*"],
-}
-
-android_test {
- name: "WMShellFlickerTestsPip2",
- defaults: ["WMShellFlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- package_name: "com.android.wm.shell.flicker.pip",
- instrumentation_target_package: "com.android.wm.shell.flicker.pip",
- test_config_template: "AndroidTestTemplate.xml",
- srcs: [
- ":WMShellFlickerTestsPip2-src",
- ":WMShellFlickerTestsPipCommon-src",
- ],
- static_libs: ["WMShellFlickerTestsBase"],
- data: ["trace_config/*"],
-}
-
-android_test {
- name: "WMShellFlickerTestsPip3",
- defaults: ["WMShellFlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- package_name: "com.android.wm.shell.flicker.pip",
- instrumentation_target_package: "com.android.wm.shell.flicker.pip",
- test_config_template: "AndroidTestTemplate.xml",
- srcs: [
- ":WMShellFlickerTestsPip3-src",
- ":WMShellFlickerTestsPipCommon-src",
- ],
- exclude_srcs: [
- ":WMShellFlickerTestsPip1-src",
- ":WMShellFlickerTestsPip2-src",
- ":WMShellFlickerTestsPipApps-src",
- ],
+ srcs: ["src/**/*.kt"],
+ exclude_srcs: [":WMShellFlickerTestsPipApps-src"],
static_libs: ["WMShellFlickerTestsBase"],
data: ["trace_config/*"],
}
@@ -135,8 +75,8 @@ android_test {
],
static_libs: ["WMShellFlickerTestsBase"],
test_suites: [
- "device-tests",
"csuite",
+ "device-tests",
],
data: ["trace_config/*"],
}
@@ -146,3 +86,259 @@ csuite_test {
test_plan_include: "csuitePlan.xml",
test_config_template: "csuiteDefaultTemplate.xml",
}
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for WMShellFlickerTestsPip module
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-CatchAll",
+ base: "WMShellFlickerTestsPip",
+ exclude_filters: [
+ "com.android.wm.shell.flicker.pip.AutoEnterPipOnGoToHomeTest",
+ "com.android.wm.shell.flicker.pip.AutoEnterPipWithSourceRectHintTest",
+ "com.android.wm.shell.flicker.pip.ClosePipBySwipingDownTest",
+ "com.android.wm.shell.flicker.pip.ClosePipWithDismissButtonTest",
+ "com.android.wm.shell.flicker.pip.EnterPipOnUserLeaveHintTest",
+ "com.android.wm.shell.flicker.pip.EnterPipViaAppUiButtonTest",
+ "com.android.wm.shell.flicker.pip.ExitPipToAppViaExpandButtonTest",
+ "com.android.wm.shell.flicker.pip.ExitPipToAppViaIntentTest",
+ "com.android.wm.shell.flicker.pip.ExpandPipOnDoubleClickTest",
+ "com.android.wm.shell.flicker.pip.ExpandPipOnPinchOpenTest",
+ "com.android.wm.shell.flicker.pip.FromSplitScreenAutoEnterPipOnGoToHomeTest",
+ "com.android.wm.shell.flicker.pip.FromSplitScreenEnterPipOnUserLeaveHintTest",
+ "com.android.wm.shell.flicker.pip.MovePipDownOnShelfHeightChange",
+ "com.android.wm.shell.flicker.pip.MovePipOnImeVisibilityChangeTest",
+ "com.android.wm.shell.flicker.pip.MovePipUpOnShelfHeightChangeTest",
+ "com.android.wm.shell.flicker.pip.PipAspectRatioChangeTest",
+ "com.android.wm.shell.flicker.pip.PipDragTest",
+ "com.android.wm.shell.flicker.pip.PipDragThenSnapTest",
+ "com.android.wm.shell.flicker.pip.PipPinchInTest",
+ "com.android.wm.shell.flicker.pip.SetRequestedOrientationWhilePinned",
+ "com.android.wm.shell.flicker.pip.ShowPipAndRotateDisplay",
+ "com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfAutoEnterPipOnGoToHomeTest",
+ "com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfEnterPipOnUserLeaveHintTest",
+ "com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfEnterPipToOtherOrientation",
+ "com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfEnterPipViaAppUiButtonTest",
+ "com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfExitPipToAppViaExpandButtonTest",
+ "com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfExitPipToAppViaIntentTest",
+ "com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfSetRequestedOrientationWhilePinned",
+ "com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfFromSplitScreenAutoEnterPipOnGoToHomeTest",
+ "com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfFromSplitScreenEnterPipOnUserLeaveHintTest",
+ ],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-AutoEnterPipOnGoToHomeTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.AutoEnterPipOnGoToHomeTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-AutoEnterPipWithSourceRectHintTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.AutoEnterPipWithSourceRectHintTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-ClosePipBySwipingDownTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.ClosePipBySwipingDownTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-ClosePipWithDismissButtonTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.ClosePipWithDismissButtonTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-EnterPipOnUserLeaveHintTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.EnterPipOnUserLeaveHintTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-EnterPipViaAppUiButtonTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.EnterPipViaAppUiButtonTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-ExitPipToAppViaExpandButtonTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.ExitPipToAppViaExpandButtonTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-ExitPipToAppViaIntentTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.ExitPipToAppViaIntentTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-ExpandPipOnDoubleClickTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.ExpandPipOnDoubleClickTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-ExpandPipOnPinchOpenTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.ExpandPipOnPinchOpenTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-FromSplitScreenAutoEnterPipOnGoToHomeTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.FromSplitScreenAutoEnterPipOnGoToHomeTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-FromSplitScreenEnterPipOnUserLeaveHintTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.FromSplitScreenEnterPipOnUserLeaveHintTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-MovePipDownOnShelfHeightChange",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.MovePipDownOnShelfHeightChange"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-MovePipOnImeVisibilityChangeTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.MovePipOnImeVisibilityChangeTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-MovePipUpOnShelfHeightChangeTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.MovePipUpOnShelfHeightChangeTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-PipAspectRatioChangeTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.PipAspectRatioChangeTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-PipDragTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.PipDragTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-PipDragThenSnapTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.PipDragThenSnapTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-PipPinchInTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.PipPinchInTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-SetRequestedOrientationWhilePinned",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.SetRequestedOrientationWhilePinned"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-ShowPipAndRotateDisplay",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.ShowPipAndRotateDisplay"],
+ test_suites: ["device-tests"],
+}
+
+// Not-match Parent test cases
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-BottomHalfExitPipToAppViaExpandButtonTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfExitPipToAppViaExpandButtonTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-BottomHalfExitPipToAppViaIntentTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfExitPipToAppViaIntentTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-BottomHalfAutoEnterPipOnGoToHomeTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfAutoEnterPipOnGoToHomeTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-BottomHalfEnterPipOnUserLeaveHintTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfEnterPipOnUserLeaveHintTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-BottomHalfEnterPipViaAppUiButtonTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfEnterPipViaAppUiButtonTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-BottomHalfEnterPipToOtherOrientation",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfEnterPipToOtherOrientation"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-BottomHalfSetRequestedOrientationWhilePinned",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfSetRequestedOrientationWhilePinned"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-BottomHalfFromSplitScreenAutoEnterPipOnGoToHomeTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfFromSplitScreenAutoEnterPipOnGoToHomeTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-BottomHalfFromSplitScreenEnterPipOnUserLeaveHintTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfFromSplitScreenEnterPipOnUserLeaveHintTest"],
+ test_suites: ["device-tests"],
+}
+
+// End breakdowns for WMShellFlickerTestsPip module
+////////////////////////////////////////////////////////////////////////////////
diff --git a/wmshell/tests/flicker/pip/AndroidTestTemplate.xml b/wmshell/tests/flicker/pip/AndroidTestTemplate.xml
index 041978c371..8003cbaada 100644
--- a/wmshell/tests/flicker/pip/AndroidTestTemplate.xml
+++ b/wmshell/tests/flicker/pip/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
+
+
+
+
@@ -44,6 +48,10 @@
+
+
+
+
@@ -103,4 +111,11 @@
+
+
+
+
+
diff --git a/wmshell/tests/flicker/pip/csuiteDefaultTemplate.xml b/wmshell/tests/flicker/pip/csuiteDefaultTemplate.xml
index bf040d2a95..34e4e744da 100644
--- a/wmshell/tests/flicker/pip/csuiteDefaultTemplate.xml
+++ b/wmshell/tests/flicker/pip/csuiteDefaultTemplate.xml
@@ -24,6 +24,10 @@
+
+
+
+
@@ -44,6 +48,8 @@
+
+
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index b85d7936ef..597674e5df 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -16,13 +16,18 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresDevice
+import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.EnterPipTransition
+import com.android.wm.shell.flicker.pip.common.widthNotSmallerThan
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -33,7 +38,7 @@ import org.junit.runners.Parameterized
/**
* Test entering pip from an app via auto-enter property when navigating to home.
*
- * To run this test: `atest WMShellFlickerTestsPip1:AutoEnterPipOnGoToHomeTest`
+ * To run this test: `atest WMShellFlickerTestsPip:AutoEnterPipOnGoToHomeTest`
*
* Actions:
* ```
@@ -55,7 +60,10 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
+ override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
override val defaultEnterPip: FlickerBuilder.() -> Unit = {
@@ -65,11 +73,10 @@ open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : EnterPipTran
}
}
- override val defaultTeardown: FlickerBuilder.() -> Unit = { teardown { pipApp.exit(wmHelper) } }
-
- @FlakyTest(bugId = 293133362)
+ @Postsubmit
@Test
override fun pipLayerReduces() {
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
flicker.assertLayers {
val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
@@ -78,10 +85,36 @@ open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : EnterPipTran
}
}
+ /** Checks that [pipApp] window's width is first decreasing then increasing. */
+ @Postsubmit
+ @Test
+ fun pipLayerWidthDecreasesThenIncreases() {
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ flicker.assertLayers {
+ val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
+ var previousLayer = pipLayerList[0]
+ var currentLayer = previousLayer
+ var i = 0
+ invoke("layer area is decreasing") {
+ if (i < pipLayerList.size - 1) {
+ previousLayer = currentLayer
+ currentLayer = pipLayerList[++i]
+ previousLayer.widthNotSmallerThan(currentLayer)
+ }
+ }.then().invoke("layer are is increasing", true /* isOptional */) {
+ if (i < pipLayerList.size - 1) {
+ previousLayer = currentLayer
+ currentLayer = pipLayerList[++i]
+ currentLayer.widthNotSmallerThan(previousLayer)
+ }
+ }
+ }
+ }
+
/** Checks that [pipApp] window is animated towards default position in right bottom corner */
@FlakyTest(bugId = 255578530)
@Test
- fun pipLayerMovesTowardsRightBottomCorner() {
+ open fun pipLayerMovesTowardsRightBottomCorner() {
// in gestural nav the swipe makes PiP first go upwards
Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
flicker.assertLayers {
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
index d059211088..5698023240 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
@@ -17,10 +17,13 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.traces.component.ComponentNameMatcher
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.wm.shell.Flags
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -30,7 +33,7 @@ import org.junit.runners.Parameterized
/**
* Test auto entering pip using a source rect hint.
*
- * To run this test: `atest WMShellFlickerTestsPip1:AutoEnterPipWithSourceRectHintTest`
+ * To run this test: `atest WMShellFlickerTestsPip:AutoEnterPipWithSourceRectHintTest`
*
* Actions:
* ```
@@ -52,8 +55,11 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class AutoEnterPipWithSourceRectHintTest(flicker: LegacyFlickerTest) :
AutoEnterPipOnGoToHomeTest(flicker) {
+ override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
override val defaultEnterPip: FlickerBuilder.() -> Unit = {
setup {
pipApp.launchViaIntent(wmHelper)
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
index a5e0550d9c..cc6e4b5a90 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
@@ -17,10 +17,12 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.traces.component.ComponentNameMatcher
+import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.ClosePipTransition
import org.junit.FixMethodOrder
import org.junit.Test
@@ -31,7 +33,7 @@ import org.junit.runners.Parameterized
/**
* Test closing a pip window by swiping it to the bottom-center of the screen
*
- * To run this test: `atest WMShellFlickerTestsPip1:ClosePipBySwipingDownTest`
+ * To run this test: `atest WMShellFlickerTestsPip:ClosePipBySwipingDownTest`
*
* Actions:
* ```
@@ -52,6 +54,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class ClosePipBySwipingDownTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions {
@@ -60,7 +63,7 @@ class ClosePipBySwipingDownTest(flicker: LegacyFlickerTest) : ClosePipTransition
val pipCenterY = pipRegion.centerY()
val displayCenterX = device.displayWidth / 2
val barComponent =
- if (flicker.scenario.isTablet) {
+ if (flicker.scenario.isTablet || Flags.enableTaskbarOnPhones()) {
ComponentNameMatcher.TASK_BAR
} else {
ComponentNameMatcher.NAV_BAR
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
index d177624378..880e4cd4e5 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
@@ -17,9 +17,12 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.ClosePipTransition
import org.junit.FixMethodOrder
import org.junit.Test
@@ -30,7 +33,7 @@ import org.junit.runners.Parameterized
/**
* Test closing a pip window via the dismiss button
*
- * To run this test: `atest WMShellFlickerTestsPip1:ClosePipWithDismissButtonTest`
+ * To run this test: `atest WMShellFlickerTestsPip:ClosePipWithDismissButtonTest`
*
* Actions:
* ```
@@ -52,7 +55,10 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class ClosePipWithDismissButtonTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) {
+ override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions { pipApp.closePipWindow(wmHelper) }
}
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index a86803d058..38f37b4bc1 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -17,9 +17,12 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.EnterPipTransition
import org.junit.Assume
import org.junit.FixMethodOrder
@@ -31,7 +34,7 @@ import org.junit.runners.Parameterized
/**
* Test entering pip from an app via [onUserLeaveHint] and by navigating to home.
*
- * To run this test: `atest WMShellFlickerTestsPip2:EnterPipOnUserLeaveHintTest`
+ * To run this test: `atest WMShellFlickerTestsPip:EnterPipOnUserLeaveHintTest`
*
* Actions:
* ```
@@ -43,7 +46,9 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class EnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
+ override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
override val defaultEnterPip: FlickerBuilder.() -> Unit = {
@@ -83,6 +88,7 @@ class EnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : EnterPipTransiti
super.pipOverlayLayerAppearThenDisappear()
}
+ // TODO(b/385086051): check if we can remove optional = true in the test.
@Presubmit
@Test
fun pipAppWindowVisibleChanges() {
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index a0a61fe2cf..e51fa45c30 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -16,9 +16,9 @@
package com.android.wm.shell.flicker.pip
-import android.app.Activity
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.Rotation
import android.tools.flicker.assertions.FlickerTest
import android.tools.flicker.junit.FlickerParametersRunnerFactory
@@ -27,26 +27,29 @@ import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.helpers.WindowUtils
import android.tools.traces.component.ComponentNameMatcher
+import android.view.WindowManagerGlobal
import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
+import com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP
import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
+import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.PipTransition
import com.android.wm.shell.flicker.pip.common.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
import com.android.wm.shell.flicker.pip.common.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
-import org.junit.Assume
-import org.junit.Before
+import org.junit.Assume.assumeFalse
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
+import java.lang.AssertionError
/**
* Test entering pip while changing orientation (from app in landscape to pip window in portrait)
*
- * To run this test: `atest WMShellFlickerTestsPip2:EnterPipToOtherOrientation`
+ * To run this test: `atest WMShellFlickerTestsPip:EnterPipToOtherOrientation`
*
* Actions:
* ```
@@ -68,9 +71,19 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(flicker) {
- private val testApp = FixedOrientationAppHelper(instrumentation)
- private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
+@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
+open class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+ override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+ internal val testApp = FixedOrientationAppHelper(instrumentation)
+ internal val ignoreOrientationRequest = WindowManagerGlobal.getWindowManagerService()
+ ?.getIgnoreOrientationRequest(WindowUtils.defaultDisplayId)
+ ?: throw AssertionError("WMS must not be null.")
+ internal val startingBounds = if (ignoreOrientationRequest) {
+ // If the device chooses to ignore orientation request, use the current display bounds.
+ WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
+ } else {
+ WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
+ }
private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
override val thisTransition: FlickerBuilder.() -> Unit = {
@@ -106,35 +119,28 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli
}
}
- /**
- * This test is not compatible with Tablets. When using [Activity.setRequestedOrientation] to
- * fix a orientation, Tablets instead keep the same orientation and add letterboxes
- */
- @Before
- fun setup() {
- Assume.assumeFalse(tapl.isTablet)
- }
-
/**
* Checks that all parts of the screen are covered at the start and end of the transition
- *
- * TODO b/197726599 Prevents all states from being checked
*/
@Presubmit
@Test
- fun entireScreenCoveredAtStartAndEnd() = flicker.entireScreenCovered(allStates = false)
+ fun entireScreenCoveredAtStartAndEnd() {
+ assumeFalse(tapl.isTablet)
+ flicker.entireScreenCovered()
+ }
/** Checks [pipApp] window remains visible and on top throughout the transition */
@Presubmit
@Test
fun pipAppWindowIsAlwaysOnTop() {
+ assumeFalse(tapl.isTablet)
flicker.assertWm { isAppWindowOnTop(pipApp) }
}
/** Checks that [testApp] window is not visible at the start */
@Presubmit
@Test
- fun testAppWindowInvisibleOnStart() {
+ open fun testAppWindowInvisibleOnStart() {
flicker.assertWmStart { isAppWindowInvisible(testApp) }
}
@@ -142,13 +148,15 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli
@Presubmit
@Test
fun testAppWindowVisibleOnEnd() {
+ assumeFalse(tapl.isTablet)
flicker.assertWmEnd { isAppWindowVisible(testApp) }
}
/** Checks that [testApp] layer is not visible at the start */
@Presubmit
@Test
- fun testAppLayerInvisibleOnStart() {
+ open fun testAppLayerInvisibleOnStart() {
+ assumeFalse(tapl.isTablet)
flicker.assertLayersStart { isInvisible(testApp) }
}
@@ -156,6 +164,7 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli
@Presubmit
@Test
fun testAppLayerVisibleOnEnd() {
+ assumeFalse(tapl.isTablet)
flicker.assertLayersEnd { isVisible(testApp) }
}
@@ -165,21 +174,15 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli
*/
@Presubmit
@Test
- fun pipAppLayerCoversFullScreenOnStart() {
- Assume.assumeFalse(tapl.isTablet)
- flicker.assertLayersStart { visibleRegion(pipApp).coversExactly(startingBounds) }
- }
-
- /**
- * Checks that the visible region of [pipApp] covers the full display area at the start of the
- * transition
- */
- @Postsubmit
- @Test
- fun pipAppLayerPlusLetterboxCoversFullScreenOnStartTablet() {
- Assume.assumeFalse(tapl.isTablet)
+ open fun pipAppLayerCoversFullScreenOnStart() {
flicker.assertLayersStart {
- visibleRegion(pipApp.or(ComponentNameMatcher.LETTERBOX)).coversExactly(startingBounds)
+ visibleRegion(
+ if (ignoreOrientationRequest) {
+ pipApp.or(ComponentNameMatcher.LETTERBOX)
+ } else {
+ pipApp
+ }
+ ).coversExactly(startingBounds)
}
}
@@ -190,6 +193,7 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli
@Presubmit
@Test
fun testAppPlusPipLayerCoversFullScreenOnEnd() {
+ assumeFalse(tapl.isTablet)
flicker.assertLayersEnd {
val pipRegion = visibleRegion(pipApp).region
visibleRegion(testApp).plus(pipRegion).coversExactly(endingBounds)
@@ -199,6 +203,7 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli
@Postsubmit
@Test
fun menuOverlayMatchesTaskSurface() {
+ assumeFalse(tapl.isTablet)
flicker.assertLayersEnd {
val pipAppRegion = visibleRegion(pipApp)
val pipMenuRegion = visibleRegion(ComponentNameMatcher.PIP_MENU_OVERLAY)
@@ -209,6 +214,7 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli
@Presubmit
@Test
fun pipLayerRemainInsideVisibleBounds() {
+ assumeFalse(tapl.isTablet)
// during the transition we assert the center point is within the display bounds, since it
// might go outside of bounds as we resize from landscape fullscreen to destination bounds,
// and once the animation is over we assert that it's fully within the display bounds, at
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
index d92f55af57..97cc9d2992 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
@@ -16,9 +16,12 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.EnterPipTransition
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -28,7 +31,7 @@ import org.junit.runners.Parameterized
/**
* Test entering pip from an app by interacting with the app UI
*
- * To run this test: `atest WMShellFlickerTestsPip2:EnterPipViaAppUiButtonTest`
+ * To run this test: `atest WMShellFlickerTestsPip:EnterPipViaAppUiButtonTest`
*
* Actions:
* ```
@@ -49,7 +52,10 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
open class EnterPipViaAppUiButtonTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
+ override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions { pipApp.clickEnterPipButton(wmHelper) }
}
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
index 8c0817d6e2..80e4c47a5f 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
@@ -16,9 +16,13 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -28,7 +32,7 @@ import org.junit.runners.Parameterized
/**
* Test expanding a pip window back to full screen via the expand button
*
- * To run this test: `atest WMShellFlickerTestsPip2:ExitPipToAppViaExpandButtonTest`
+ * To run this test: `atest WMShellFlickerTestsPip:ExitPipToAppViaExpandButtonTest`
*
* Actions:
* ```
@@ -48,11 +52,15 @@ import org.junit.runners.Parameterized
* apps are running before setup
* ```
*/
+@FlakyTest(bugId = 391734110)
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class ExitPipToAppViaExpandButtonTest(flicker: LegacyFlickerTest) :
ExitPipToAppTransition(flicker) {
+ override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
override val thisTransition: FlickerBuilder.() -> Unit = {
setup {
// launch an app behind the pip one
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
index 90a9623056..2f4c8008f2 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
@@ -16,9 +16,12 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
@@ -28,7 +31,7 @@ import org.junit.runners.Parameterized
/**
* Test expanding a pip window back to full screen via an intent
*
- * To run this test: `atest WMShellFlickerTestsPip2:ExitPipToAppViaIntentTest`
+ * To run this test: `atest WMShellFlickerTestsPip:ExitPipToAppViaIntentTest`
*
* Actions:
* ```
@@ -50,7 +53,10 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class ExitPipToAppViaIntentTest(flicker: LegacyFlickerTest) : ExitPipToAppTransition(flicker) {
+ override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
override val thisTransition: FlickerBuilder.() -> Unit = {
setup {
// launch an app behind the pip one
@@ -58,7 +64,7 @@ class ExitPipToAppViaIntentTest(flicker: LegacyFlickerTest) : ExitPipToAppTransi
}
transitions {
// This will bring PipApp to fullscreen
- pipApp.exitPipToFullScreenViaIntent(wmHelper)
+ pipApp.exitPipToOriginalTaskViaIntent(wmHelper)
// Wait until the other app is no longer visible
wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify()
}
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index 9306c77a1c..4f189fc619 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -17,12 +17,14 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.Rotation
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.traces.component.ComponentNameMatcher
+import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
@@ -33,7 +35,7 @@ import org.junit.runners.Parameterized
/**
* Test expanding a pip window by double-clicking it
*
- * To run this test: `atest WMShellFlickerTestsPip2:ExpandPipOnDoubleClickTest`
+ * To run this test: `atest WMShellFlickerTestsPip:ExpandPipOnDoubleClickTest`
*
* Actions:
* ```
@@ -54,6 +56,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class ExpandPipOnDoubleClickTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions { pipApp.doubleClickPipWindow(wmHelper) }
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
index 1964e3cebc..4d72b03d03 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
@@ -17,11 +17,13 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.Rotation
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
@@ -33,6 +35,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class ExpandPipOnPinchOpenTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.25f, 30) }
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
index cb8ee27f29..aa893ed65e 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
@@ -16,7 +16,9 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresDevice
import android.tools.Rotation
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
@@ -24,8 +26,6 @@ import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.helpers.WindowUtils
import android.tools.traces.parsers.toFlickerComponent
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.wm.shell.flicker.utils.SplitScreenUtils
@@ -39,7 +39,7 @@ import org.junit.runners.Parameterized
/**
* Test entering pip from an app via auto-enter property when navigating to home from split screen.
*
- * To run this test: `atest WMShellFlickerTestsPip1:FromSplitScreenAutoEnterPipOnGoToHomeTest`
+ * To run this test: `atest WMShellFlickerTestsPip:FromSplitScreenAutoEnterPipOnGoToHomeTest`
*
* Actions:
* ```
@@ -62,11 +62,11 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class FromSplitScreenAutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) :
+open class FromSplitScreenAutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) :
AutoEnterPipOnGoToHomeTest(flicker) {
private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
/** Second app used to enter split screen mode */
- private val secondAppForSplitScreen =
+ internal val secondAppForSplitScreen =
SimpleAppHelper(
instrumentation,
ActivityOptions.SplitScreen.Primary.LABEL,
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
index d03d7799d6..8e7cb56c0f 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
@@ -16,7 +16,9 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresDevice
import android.tools.Rotation
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
@@ -24,8 +26,7 @@ import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.helpers.WindowUtils
import android.tools.traces.parsers.toFlickerComponent
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.wm.shell.flicker.pip.common.EnterPipTransition
@@ -40,7 +41,7 @@ import org.junit.runners.Parameterized
/**
* Test entering pip from an app via auto-enter property when navigating to home from split screen.
*
- * To run this test: `atest WMShellFlickerTestsPip1:FromSplitScreenEnterPipOnUserLeaveHintTest`
+ * To run this test: `atest WMShellFlickerTestsPip:FromSplitScreenEnterPipOnUserLeaveHintTest`
*
* Actions:
* ```
@@ -63,11 +64,13 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class FromSplitScreenEnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) :
+@FlakyTest(bugId = 386333280)
+open class FromSplitScreenEnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) :
EnterPipTransition(flicker) {
+ override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
/** Second app used to enter split screen mode */
- private val secondAppForSplitScreen =
+ internal val secondAppForSplitScreen =
SimpleAppHelper(
instrumentation,
ActivityOptions.SplitScreen.Primary.LABEL,
@@ -182,12 +185,6 @@ class FromSplitScreenEnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) :
}
}
- /** {@inheritDoc} */
- @FlakyTest(bugId = 312446524)
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
/** {@inheritDoc} */
@Test
@FlakyTest(bugId = 336510055)
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
index 265eb4416a..d979b428d8 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
@@ -17,10 +17,12 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresDevice
+import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
-import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.MovePipShelfHeightTransition
import com.android.wm.shell.flicker.utils.Direction
import org.junit.FixMethodOrder
@@ -32,7 +34,7 @@ import org.junit.runners.Parameterized
/**
* Test Pip movement with Launcher shelf height change (increase).
*
- * To run this test: `atest WMShellFlickerTestsPip3:MovePipDownOnShelfHeightChange`
+ * To run this test: `atest WMShellFlickerTestsPip:MovePipDownOnShelfHeightChange`
*
* Actions:
* ```
@@ -56,6 +58,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class MovePipDownOnShelfHeightChange(flicker: LegacyFlickerTest) :
MovePipShelfHeightTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
index 04fedf4f25..88d78edae9 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.Rotation
import android.tools.flicker.assertions.FlickerTest
import android.tools.flicker.junit.FlickerParametersRunnerFactory
@@ -27,6 +28,7 @@ import android.tools.helpers.WindowUtils
import android.tools.traces.component.ComponentNameMatcher
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
@@ -41,6 +43,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class MovePipOnImeVisibilityChangeTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
private val imeApp = ImeAppHelper(instrumentation)
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
index 8d6be64da2..c533800b37 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
@@ -17,10 +17,12 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresDevice
+import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
-import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.MovePipShelfHeightTransition
import com.android.wm.shell.flicker.utils.Direction
import org.junit.FixMethodOrder
@@ -32,7 +34,7 @@ import org.junit.runners.Parameterized
/**
* Test Pip movement with Launcher shelf height change (decrease).
*
- * To run this test: `atest WMShellFlickerTestsPip3:MovePipUpOnShelfHeightChangeTest`
+ * To run this test: `atest WMShellFlickerTestsPip:MovePipUpOnShelfHeightChangeTest`
*
* Actions:
* ```
@@ -56,6 +58,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
open class MovePipUpOnShelfHeightChangeTest(flicker: LegacyFlickerTest) :
MovePipShelfHeightTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
@@ -65,7 +68,8 @@ open class MovePipUpOnShelfHeightChangeTest(flicker: LegacyFlickerTest) :
}
/** Checks that the visible region of [pipApp] window always moves up during the animation. */
- @Presubmit @Test fun pipWindowMovesUp() = pipWindowMoves(Direction.UP)
+ @Presubmit
+ @Test fun pipWindowMovesUp() = pipWindowMoves(Direction.UP)
/** Checks that the visible region of [pipApp] layer always moves up during the animation. */
@Presubmit @Test fun pipLayerMovesUp() = pipLayerMoves(Direction.UP)
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
index 70be58f065..14ae93a81c 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
@@ -17,11 +17,14 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.Rotation
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
@@ -33,9 +36,12 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class PipAspectRatioChangeTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+ override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
override val thisTransition: FlickerBuilder.() -> Unit = {
- transitions { pipApp.changeAspectRatio() }
+ transitions { pipApp.changeAspectRatio(wmHelper) }
}
@Presubmit
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
index a4df69fc65..81162c6f53 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
@@ -18,11 +18,13 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
+import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
@@ -35,6 +37,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class PipDragTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
private var isDraggedLeft: Boolean = true
@@ -46,7 +49,8 @@ class PipDragTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
val stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true")
setup {
tapl.setEnableRotation(true)
- pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
+ pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras)
+ pipApp.waitForPip(wmHelper)
// determine the direction of dragging to test for
isDraggedLeft = pipApp.isCloserToRightEdge(wmHelper)
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
index 90b9798c63..6118d73796 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
@@ -17,16 +17,18 @@
package com.android.wm.shell.flicker.pip
import android.graphics.Rect
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.RequiresDevice
+import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.Rotation
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.flicker.rules.RemoveAllTasksButHomeRule
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
@@ -40,6 +42,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class PipDragThenSnapTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
// represents the direction in which the pip window should be snapping
private var willSnapRight: Boolean = true
@@ -56,7 +59,8 @@ class PipDragThenSnapTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
// Launch the PIP activity and wait for it to enter PiP mode
setRotation(Rotation.ROTATION_0)
RemoveAllTasksButHomeRule.removeAllTasksButHome()
- pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
+ pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras)
+ pipApp.waitForPip(wmHelper)
// get the initial region bounds and cache them
val initRegion = pipApp.getWindowRect(wmHelper)
@@ -181,6 +185,13 @@ class PipDragThenSnapTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
super.taskBarWindowIsAlwaysVisible()
}
+ // Overridden to remove @Postsubmit annotation
+ @Test
+ @FlakyTest(bugId = 294993100)
+ override fun pipLayerHasCorrectCornersAtEnd() {
+ // No rounded corners as we go back to fullscreen in new orientation.
+ }
+
companion object {
/**
* Creates the test configurations.
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
index 16d08e5e90..61c59cc455 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
@@ -17,13 +17,16 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresDevice
+import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.Rotation
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.flicker.subject.exceptions.IncorrectRegionException
-import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
@@ -36,7 +39,10 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class PipPinchInTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
+ override val pipApp: PipAppHelper = PipAppHelper(instrumentation)
+
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) }
}
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
index ed2a0a718c..86c32de646 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
@@ -17,8 +17,11 @@
package com.android.wm.shell.flicker.pip
import android.app.Activity
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresDevice
+import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.Rotation
import android.tools.flicker.assertions.FlickerTest
import android.tools.flicker.junit.FlickerParametersRunnerFactory
@@ -26,10 +29,9 @@ import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.helpers.WindowUtils
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
+import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.PipTransition
import com.android.wm.shell.flicker.pip.common.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
import org.junit.Assume
@@ -41,16 +43,35 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test exiting Pip with orientation changes. To run this test:
- * `atest WMShellFlickerTestsPip1:SetRequestedOrientationWhilePinned`
+ * Test leaving pip while changing orientation (from pip window in portrait to app in landscape)
+ *
+ * To run this test: `atest WMShellFlickerTestsPip:SetRequestedOrientationWhilePinned`
+ *
+ * Actions:
+ * ```
+ * Launch [pipApp] on a fixed landscape orientation
+ * Broadcast action [ACTION_ENTER_PIP] to enter pip mode in portrait
+ * Restore PIP from the original task to landscape
+ * ```
+ *
+ * Notes:
+ * ```
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
open class SetRequestedOrientationWhilePinned(flicker: LegacyFlickerTest) : PipTransition(flicker) {
private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
- private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
+ internal open val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions {
@@ -128,7 +149,7 @@ open class SetRequestedOrientationWhilePinned(flicker: LegacyFlickerTest) : PipT
@Presubmit
@Test
- fun pipAppLayerCoversFullScreen() {
+ open fun pipAppLayerCoversDisplayBoundsOnEnd() {
flicker.assertLayersEnd { visibleRegion(pipApp).coversExactly(endingBounds) }
}
@@ -147,6 +168,12 @@ open class SetRequestedOrientationWhilePinned(flicker: LegacyFlickerTest) : PipT
@Test
override fun entireScreenCovered() = super.entireScreenCovered()
+ @Postsubmit
+ @Test
+ override fun pipLayerHasCorrectCornersAtEnd() {
+ flicker.assertLayersEnd { hasNoRoundedCorners(pipApp) }
+ }
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
index 9109eafacf..e72251fb7a 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresFlagsDisabled
import android.tools.flicker.assertions.FlickerTest
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
@@ -25,6 +26,7 @@ import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.wm.shell.Flags
import com.android.wm.shell.flicker.pip.common.PipTransition
import org.junit.FixMethodOrder
import org.junit.Test
@@ -35,7 +37,7 @@ import org.junit.runners.Parameterized
/**
* Test Pip Stack in bounds after rotations.
*
- * To run this test: `atest WMShellFlickerTestsPip1:ShowPipAndRotateDisplay`
+ * To run this test: `atest WMShellFlickerTestsPip:ShowPipAndRotateDisplay`
*
* Actions:
* ```
@@ -58,6 +60,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2)
class ShowPipAndRotateDisplay(flicker: LegacyFlickerTest) : PipTransition(flicker) {
private val testApp = SimpleAppHelper(instrumentation)
private val screenBoundsStart = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
index 65b60ce102..0867f654bc 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
@@ -18,7 +18,6 @@ package com.android.wm.shell.flicker.pip.apps
import android.platform.test.annotations.Postsubmit
import android.tools.Rotation
-import android.tools.device.apphelpers.StandardAppHelper
import android.tools.flicker.junit.FlickerBuilderProvider
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
@@ -29,8 +28,6 @@ import org.junit.Test
import org.junit.runners.Parameterized
abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
- protected abstract val standardAppHelper: StandardAppHelper
-
protected abstract val permissions: Array
@FlickerBuilderProvider
@@ -39,7 +36,7 @@ abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTran
instrumentation.uiAutomation.adoptShellPermissionIdentity()
for (permission in permissions) {
instrumentation.uiAutomation.grantRuntimePermission(
- standardAppHelper.packageName,
+ pipApp.packageName,
permission
)
}
@@ -48,18 +45,18 @@ abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTran
}
}
- /** Checks [standardAppHelper] window remains visible throughout the animation */
+ /** Checks [pipApp] window remains visible throughout the animation */
@Postsubmit
@Test
override fun pipAppWindowAlwaysVisible() {
- flicker.assertWm { this.isAppWindowVisible(standardAppHelper.packageNameMatcher) }
+ flicker.assertWm { this.isAppWindowVisible(pipApp.packageNameMatcher) }
}
- /** Checks [standardAppHelper] layer remains visible throughout the animation */
+ /** Checks [pipApp] layer remains visible throughout the animation */
@Postsubmit
@Test
override fun pipAppLayerAlwaysVisible() {
- flicker.assertLayers { this.isVisible(standardAppHelper.packageNameMatcher) }
+ flicker.assertLayers { this.isVisible(pipApp.packageNameMatcher) }
}
/** Checks the content overlay appears then disappears during the animation */
@@ -70,39 +67,39 @@ abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTran
}
/**
- * Checks that [standardAppHelper] window remains inside the display bounds throughout the whole
+ * Checks that [pipApp] window remains inside the display bounds throughout the whole
* animation
*/
@Postsubmit
@Test
override fun pipWindowRemainInsideVisibleBounds() {
- flicker.assertWmVisibleRegion(standardAppHelper.packageNameMatcher) {
+ flicker.assertWmVisibleRegion(pipApp.packageNameMatcher) {
coversAtMost(displayBounds)
}
}
/**
- * Checks that the [standardAppHelper] layer remains inside the display bounds throughout the
+ * Checks that the [pipApp] layer remains inside the display bounds throughout the
* whole animation
*/
@Postsubmit
@Test
override fun pipLayerOrOverlayRemainInsideVisibleBounds() {
flicker.assertLayersVisibleRegion(
- standardAppHelper.packageNameMatcher.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
+ pipApp.packageNameMatcher.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
) {
coversAtMost(displayBounds)
}
}
- /** Checks that the visible region of [standardAppHelper] always reduces during the animation */
+ /** Checks that the visible region of [pipApp] always reduces during the animation */
@Postsubmit
@Test
override fun pipLayerReduces() {
flicker.assertLayers {
val pipLayerList =
this.layers {
- standardAppHelper.packageNameMatcher.layerMatchesAnyOf(it) && it.isVisible
+ pipApp.packageNameMatcher.layerMatchesAnyOf(it) && it.isVisible
}
pipLayerList.zipWithNext { previous, current ->
current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
@@ -110,14 +107,14 @@ abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTran
}
}
- /** Checks that [standardAppHelper] window becomes pinned */
+ /** Checks that [pipApp] window becomes pinned */
@Postsubmit
@Test
override fun pipWindowBecomesPinned() {
flicker.assertWm {
- invoke("pipWindowIsNotPinned") { it.isNotPinned(standardAppHelper.packageNameMatcher) }
+ invoke("pipWindowIsNotPinned") { it.isNotPinned(pipApp.packageNameMatcher) }
.then()
- .invoke("pipWindowIsPinned") { it.isPinned(standardAppHelper.packageNameMatcher) }
+ .invoke("pipWindowIsPinned") { it.isPinned(pipApp.packageNameMatcher) }
}
}
@@ -129,14 +126,14 @@ abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTran
}
/**
- * Checks that the focus changes between the [standardAppHelper] window and the launcher when
+ * Checks that the focus changes between the [pipApp] window and the launcher when
* closing the pip window
*/
@Postsubmit
@Test
override fun focusChanges() {
flicker.assertEventLog {
- this.focusChanges(standardAppHelper.packageName, "NexusLauncherActivity")
+ this.focusChanges(pipApp.packageName, "NexusLauncherActivity")
}
}
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
index 1fc9d9910a..651c92308c 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
@@ -29,6 +29,8 @@ import android.tools.device.apphelpers.MapsAppHelper
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.subject.layers.LayersTraceSubject.Companion.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS
+import android.tools.traces.component.ComponentRegexMatcher
import androidx.test.filters.RequiresDevice
import org.junit.Assume
import org.junit.FixMethodOrder
@@ -40,7 +42,7 @@ import org.junit.runners.Parameterized
/**
* Test entering pip from Maps app by interacting with the app UI
*
- * To run this test: `atest WMShellFlickerTests:MapsEnterPipTest`
+ * To run this test: `atest WMShellFlickerTestsPipAppsCSuite:MapsEnterPipTest`
*
* Actions:
* ```
@@ -63,7 +65,7 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
- override val standardAppHelper: MapsAppHelper = MapsAppHelper(instrumentation)
+ override val pipApp: MapsAppHelper = MapsAppHelper(instrumentation)
override val permissions: Array =
arrayOf(Manifest.permission.POST_NOTIFICATIONS, Manifest.permission.ACCESS_FINE_LOCATION)
@@ -110,23 +112,23 @@ open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition
// normal app open through the Launcher All Apps
// var mapsAddressOption = "Golden Gate Bridge"
- // standardAppHelper.open()
- // standardAppHelper.doSearch(mapsAddressOption)
- // standardAppHelper.getDirections()
- // standardAppHelper.startNavigation();
+ // pipApp.open()
+ // pipApp.doSearch(mapsAddressOption)
+ // pipApp.getDirections()
+ // pipApp.startNavigation();
- standardAppHelper.launchViaIntent(
+ pipApp.launchViaIntent(
wmHelper,
MapsAppHelper.getMapIntent(MapsAppHelper.INTENT_NAVIGATION)
)
- standardAppHelper.waitForNavigationToStart()
+ pipApp.waitForNavigationToStart()
}
}
override val defaultTeardown: FlickerBuilder.() -> Unit = {
teardown {
- standardAppHelper.exit(wmHelper)
+ pipApp.exit(wmHelper)
mainHandler.removeCallbacks(updateLocation)
// the main looper callback might have tried to provide a new location after the
// provider is no longer in test mode, causing a crash, this prevents it from happening
@@ -137,14 +139,14 @@ open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition
override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
- /** Checks [standardAppHelper] layer remains visible throughout the animation */
+ /** Checks [pipApp] layer remains visible throughout the animation */
@Postsubmit
@Test
override fun pipAppLayerAlwaysVisible() {
// For Maps the transition goes through the UI mode change that adds a snapshot overlay so
// we assert only start/end layers matching the app instead.
- flicker.assertLayersStart { this.isVisible(standardAppHelper.packageNameMatcher) }
- flicker.assertLayersEnd { this.isVisible(standardAppHelper.packageNameMatcher) }
+ flicker.assertLayersStart { this.isVisible(pipApp.packageNameMatcher) }
+ flicker.assertLayersEnd { this.isVisible(pipApp.packageNameMatcher) }
}
@Postsubmit
@@ -154,4 +156,15 @@ open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition
Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
super.focusChanges()
}
+
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ flicker.assertLayers {
+ this.visibleLayersShownMoreThanOneConsecutiveEntry(
+ ignoreLayers = VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS
+ + ComponentRegexMatcher(Regex("Background for .* SurfaceView\\[com\\.google\\.android\\.apps\\.maps/com\\.google\\.android\\.maps\\.MapsActivity\\]\\#\\d+"))
+ )
+ }
+ }
}
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
index 3a0eeb6799..be4cd780e4 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
@@ -38,7 +38,7 @@ import org.junit.runners.Parameterized
/**
* Test entering pip from Netflix app by interacting with the app UI
*
- * To run this test: `atest WMShellFlickerTests:NetflixEnterPipTest`
+ * To run this test: `atest WMShellFlickerTestsPipAppsCSuite:NetflixEnterPipTest`
*
* Actions:
* ```
@@ -61,7 +61,7 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
- override val standardAppHelper: NetflixAppHelper = NetflixAppHelper(instrumentation)
+ override val pipApp: NetflixAppHelper = NetflixAppHelper(instrumentation)
private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
@@ -69,17 +69,17 @@ open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransit
override val defaultEnterPip: FlickerBuilder.() -> Unit = {
setup {
- standardAppHelper.launchViaIntent(
+ pipApp.launchViaIntent(
wmHelper,
NetflixAppHelper.getNetflixWatchVideoIntent("81605060"),
ComponentNameMatcher(NetflixAppHelper.PACKAGE_NAME, NetflixAppHelper.WATCH_ACTIVITY)
)
- standardAppHelper.waitForVideoPlaying()
+ pipApp.waitForVideoPlaying()
}
}
override val defaultTeardown: FlickerBuilder.() -> Unit = {
- teardown { standardAppHelper.exit(wmHelper) }
+ teardown { pipApp.exit(wmHelper) }
}
override val thisTransition: FlickerBuilder.() -> Unit = {
@@ -103,7 +103,7 @@ open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransit
@Postsubmit
@Test
override fun taskBarLayerIsVisibleAtStartAndEnd() {
- Assume.assumeTrue(flicker.scenario.isTablet)
+ Assume.assumeTrue(usesTaskbar)
// Netflix starts in immersive fullscreen mode, so taskbar bar is not visible at start
flicker.assertLayersStart { this.isInvisible(ComponentNameMatcher.TASK_BAR) }
flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
@@ -143,7 +143,7 @@ open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransit
// might go outside of bounds as we resize from landscape fullscreen to destination bounds,
// and once the animation is over we assert that it's fully within the display bounds, at
// which point the device also performs orientation change from landscape to portrait
- flicker.assertWmVisibleRegion(standardAppHelper.packageNameMatcher) {
+ flicker.assertWmVisibleRegion(pipApp.packageNameMatcher) {
regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds)
}
}
@@ -156,7 +156,7 @@ open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransit
// and once the animation is over we assert that it's fully within the display bounds, at
// which point the device also performs orientation change from landscape to portrait
// since Netflix uses source rect hint, there is no PiP overlay present
- flicker.assertLayersVisibleRegion(standardAppHelper.packageNameMatcher) {
+ flicker.assertLayersVisibleRegion(pipApp.packageNameMatcher) {
regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds)
}
}
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
index 35ed8de3a4..3e4ff3075f 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
@@ -34,7 +34,7 @@ import org.junit.runners.Parameterized
/**
* Test entering pip from YouTube app by interacting with the app UI
*
- * To run this test: `atest WMShellFlickerTests:YouTubeEnterPipTest`
+ * To run this test: `atest WMShellFlickerTestsPipAppsCSuite:YouTubeEnterPipTest`
*
* Actions:
* ```
@@ -57,23 +57,23 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class YouTubeEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) {
- override val standardAppHelper: YouTubeAppHelper = YouTubeAppHelper(instrumentation)
+ override val pipApp: YouTubeAppHelper = YouTubeAppHelper(instrumentation)
override val permissions: Array = arrayOf(Manifest.permission.POST_NOTIFICATIONS)
override val defaultEnterPip: FlickerBuilder.() -> Unit = {
setup {
- standardAppHelper.launchViaIntent(
+ pipApp.launchViaIntent(
wmHelper,
- YouTubeAppHelper.getYoutubeVideoIntent("HPcEAtoXXLA"),
+ YouTubeAppHelper.getYoutubeVideoIntent("3KtWfp0UopM"),
ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "")
)
- standardAppHelper.waitForVideoPlaying()
+ pipApp.waitForVideoPlaying()
}
}
override val defaultTeardown: FlickerBuilder.() -> Unit = {
- teardown { standardAppHelper.exit(wmHelper) }
+ teardown { pipApp.exit(wmHelper) }
}
override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
index 879034f325..2c6cb50346 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt
@@ -38,7 +38,7 @@ import org.junit.runners.Parameterized
/**
* Test entering pip from YouTube app by interacting with the app UI
*
- * To run this test: `atest WMShellFlickerTests:YouTubeEnterPipTest`
+ * To run this test: `atest WMShellFlickerTestsPipAppsCSuite:YouTubeEnterPipTest`
*
* Actions:
* ```
@@ -63,7 +63,7 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class YouTubeEnterPipToOtherOrientationTest(flicker: LegacyFlickerTest) :
YouTubeEnterPipTest(flicker) {
- override val standardAppHelper: YouTubeAppHelper = YouTubeAppHelper(instrumentation)
+ override val pipApp: YouTubeAppHelper = YouTubeAppHelper(instrumentation)
private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
@@ -71,13 +71,13 @@ open class YouTubeEnterPipToOtherOrientationTest(flicker: LegacyFlickerTest) :
override val defaultEnterPip: FlickerBuilder.() -> Unit = {
setup {
- standardAppHelper.launchViaIntent(
+ pipApp.launchViaIntent(
wmHelper,
- YouTubeAppHelper.getYoutubeVideoIntent("HPcEAtoXXLA"),
+ YouTubeAppHelper.getYoutubeVideoIntent("3KtWfp0UopM"),
ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "")
)
- standardAppHelper.enterFullscreen()
- standardAppHelper.waitForVideoPlaying()
+ pipApp.enterFullscreen()
+ pipApp.waitForVideoPlaying()
}
}
@@ -88,7 +88,7 @@ open class YouTubeEnterPipToOtherOrientationTest(flicker: LegacyFlickerTest) :
@Postsubmit
@Test
override fun taskBarLayerIsVisibleAtStartAndEnd() {
- Assume.assumeTrue(flicker.scenario.isTablet)
+ Assume.assumeTrue(usesTaskbar)
// YouTube starts in immersive fullscreen mode, so taskbar bar is not visible at start
flicker.assertLayersStart { this.isInvisible(ComponentNameMatcher.TASK_BAR) }
flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
@@ -101,7 +101,7 @@ open class YouTubeEnterPipToOtherOrientationTest(flicker: LegacyFlickerTest) :
// might go outside of bounds as we resize from landscape fullscreen to destination bounds,
// and once the animation is over we assert that it's fully within the display bounds, at
// which point the device also performs orientation change from landscape to portrait
- flicker.assertWmVisibleRegion(standardAppHelper.packageNameMatcher) {
+ flicker.assertWmVisibleRegion(pipApp.packageNameMatcher) {
regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds)
}
}
@@ -114,7 +114,7 @@ open class YouTubeEnterPipToOtherOrientationTest(flicker: LegacyFlickerTest) :
// and once the animation is over we assert that it's fully within the display bounds, at
// which point the device also performs orientation change from landscape to portrait
// since YouTube uses source rect hint, there is no PiP overlay present
- flicker.assertLayersVisibleRegion(standardAppHelper.packageNameMatcher) {
+ flicker.assertLayersVisibleRegion(pipApp.packageNameMatcher) {
regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds)
}
}
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt
index 8cb81b46cf..f57335c208 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt
@@ -70,6 +70,11 @@ abstract class ClosePipTransition(flicker: LegacyFlickerTest) : PipTransition(fl
}
}
+ @Test
+ override fun pipLayerHasCorrectCornersAtEnd() {
+ // PiP might have completely faded out by this point, so corner radii not applicable.
+ }
+
companion object {
/**
* Creates the test configurations.
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
index 6dd3a175da..a72de0f6da 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
@@ -71,7 +71,9 @@ abstract class EnterPipTransition(flicker: LegacyFlickerTest) : PipTransition(fl
@Presubmit
@Test
open fun pipLayerOrOverlayRemainInsideVisibleBounds() {
- flicker.assertLayersVisibleRegion(pipApp.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)) {
+ flicker.assertLayersVisibleRegion(
+ pipApp.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
+ ) {
coversAtMost(displayBounds)
}
}
@@ -117,7 +119,9 @@ abstract class EnterPipTransition(flicker: LegacyFlickerTest) : PipTransition(fl
@Presubmit
@Test
open fun focusChanges() {
- flicker.assertEventLog { this.focusChanges(pipApp.packageName, "NexusLauncherActivity") }
+ flicker.assertEventLog {
+ this.focusChanges(pipApp.packageName, "NexusLauncherActivity")
+ }
}
companion object {
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt
index 0742cf9c58..ce84eb6440 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.pip.common
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.tools.Rotation
import android.tools.flicker.legacy.LegacyFlickerTest
@@ -123,6 +124,12 @@ abstract class ExitPipToAppTransition(flicker: LegacyFlickerTest) : PipTransitio
}
}
+ @Postsubmit
+ @Test
+ override fun pipLayerHasCorrectCornersAtEnd() {
+ flicker.assertLayersEnd { hasNoRoundedCorners(pipApp) }
+ }
+
/** {@inheritDoc} */
@Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
index 99c1ad2aaa..7b6625ddc4 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
@@ -18,22 +18,29 @@ package com.android.wm.shell.flicker.pip.common
import android.app.Instrumentation
import android.content.Intent
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.tools.Rotation
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import android.tools.helpers.WindowUtils
import android.tools.traces.component.ComponentNameMatcher
+import android.tools.device.apphelpers.PipApp
import com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.wm.shell.flicker.BaseTest
import com.google.common.truth.Truth
+import org.junit.Rule
import org.junit.Test
abstract class PipTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) {
- protected val pipApp = PipAppHelper(instrumentation)
+ @JvmField
+ @Rule
+ val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
protected val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation)
@@ -56,6 +63,11 @@ abstract class PipTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) {
}
}
+ /**
+ * Defines the test app to run PIP flicker test.
+ */
+ protected open val pipApp: PipApp = PipAppHelper(instrumentation)
+
/** Defines the transition used to run the test */
protected open val thisTransition: FlickerBuilder.() -> Unit = {}
@@ -78,10 +90,11 @@ abstract class PipTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) {
/** Defines the default method of entering PiP */
protected open val defaultEnterPip: FlickerBuilder.() -> Unit = {
setup {
- pipApp.launchViaIntentAndWaitForPip(
+ pipApp.launchViaIntent(
wmHelper,
stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true")
)
+ pipApp.waitForPip(wmHelper)
}
}
@@ -105,4 +118,10 @@ abstract class PipTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) {
.doesNotContain(false)
}
}
+
+ @Postsubmit
+ @Test
+ open fun pipLayerHasCorrectCornersAtEnd() {
+ flicker.assertLayersEnd { hasRoundedCorners(pipApp) }
+ }
}
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt
index 8a073abf03..6a84b2803d 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt
@@ -25,7 +25,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
-/** Test Pip Menu on TV. To run this test: `atest WMShellFlickerTests:TvPipBasicTest` */
+/** Test Pip Menu on TV. To run this test: `atest WMShellFlickerTestsPip:TvPipBasicTest` */
@RequiresDevice
@RunWith(Parameterized::class)
class TvPipBasicTest(private val radioButtonId: String, private val pipWindowRatio: Rational?) :
diff --git a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
index d4cd6da4ac..09e8745a02 100644
--- a/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
+++ b/wmshell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
@@ -27,7 +27,7 @@ import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
-/** Test Pip Menu on TV. To run this test: `atest WMShellFlickerTests:TvPipMenuTests` */
+/** Test Pip Menu on TV. To run this test: `atest WMShellFlickerTestsPip:TvPipMenuTests` */
@RequiresDevice
class TvPipMenuTests : TvPipTestBase() {
diff --git a/wmshell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt b/wmshell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
index d03c2f1077..88b1e6adee 100644
--- a/wmshell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
+++ b/wmshell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
@@ -20,8 +20,10 @@ import android.app.Instrumentation
import android.tools.flicker.junit.FlickerBuilderProvider
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.rules.ClearAppCacheRule
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.tapl.LauncherInstrumentation
+import org.junit.ClassRule
abstract class BaseBenchmarkTest
@JvmOverloads
@@ -44,4 +46,8 @@ constructor(
transition()
}
}
+
+ companion object {
+ @ClassRule @JvmField val clearCache = ClearAppCacheRule()
+ }
}
diff --git a/wmshell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt b/wmshell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
index 509f4f202b..770efbc6d5 100644
--- a/wmshell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
+++ b/wmshell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
@@ -99,6 +99,27 @@ fun LegacyFlickerTest.splitScreenDividerIsInvisibleAtStart() {
assertLayersStart { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
+fun LegacyFlickerTest.tilingDividerIsVisibleAtStart() {
+ assertLayersStart { this.isVisible(TILING_SPLIT_DIVIDER) }
+}
+
+fun LegacyFlickerTest.tilingDividerIsVisibleAtEnd() {
+ assertLayersEnd { this.isVisible(TILING_SPLIT_DIVIDER) }
+}
+
+fun LegacyFlickerTest.tilingDividerIsInvisibleAtEnd() {
+ assertLayersEnd { this.isInvisible(TILING_SPLIT_DIVIDER) }
+}
+
+fun LegacyFlickerTest.tilingDividerIsInvisibleAtStart() {
+ assertLayersStart { this.isInvisible(TILING_SPLIT_DIVIDER) }
+}
+
+
+fun LegacyFlickerTest.tilingScreenDividerIsInvisibleAtEnd() {
+ assertLayersEnd { this.isInvisible(TILING_SPLIT_DIVIDER) }
+}
+
fun LegacyFlickerTest.splitScreenDividerIsInvisibleAtEnd() {
assertLayersEnd { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
@@ -254,6 +275,16 @@ fun LayersTraceSubject.splitAppLayerBoundsSnapToDivider(
}
}
+/**
+ * Checks that surfaces are still within the expected region after snapping to a snap point.
+ *
+ * @param component The component we are checking (should be one of the two split apps)
+ * @param landscapePosLeft If [true], and device is in left/right split, app is on the left side of
+ * the screen. Has no meaning if device is in top/bottom split.
+ * @param portraitPosTop If [true], and device is in top/bottom split, app is on the top side of
+ * the screen. Has no meaning if device is in left/right split.
+ * @param rotation The rotation state of the display.
+ */
fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider(
component: IComponentMatcher,
landscapePosLeft: Boolean,
@@ -268,10 +299,12 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider(
visibleRegion(component).isNotEmpty()
visibleRegion(component)
.coversAtMost(
+ // TODO (b/403082705): Should use the new method for determining left/right split.
if (displayBounds.width() > displayBounds.height()) {
if (landscapePosLeft) {
Region(
- 0,
+ // TODO (b/403304310): Check if we're in an offscreen-enabled mode.
+ -displayBounds.right, // the receding app can go offscreen
0,
(dividerRegion.left + dividerRegion.right) / 2,
displayBounds.bottom
@@ -280,7 +313,7 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider(
Region(
(dividerRegion.left + dividerRegion.right) / 2,
0,
- displayBounds.right,
+ displayBounds.right * 2, // the receding app can go offscreen
displayBounds.bottom
)
}
@@ -288,7 +321,7 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider(
if (portraitPosTop) {
Region(
0,
- 0,
+ -displayBounds.bottom, // the receding app can go offscreen
displayBounds.right,
(dividerRegion.top + dividerRegion.bottom) / 2
)
@@ -297,7 +330,7 @@ fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider(
0,
(dividerRegion.top + dividerRegion.bottom) / 2,
displayBounds.right,
- displayBounds.bottom
+ displayBounds.bottom * 2 // the receding app can go offscreen
)
}
}
@@ -337,6 +370,21 @@ fun LegacyFlickerTest.appWindowIsInvisibleAtEnd(component: IComponentMatcher) {
assertWmEnd { this.isAppWindowInvisible(component) }
}
+fun LegacyFlickerTest.appWindowOnTopAtStart(component: IComponentMatcher) {
+ assertWmStart { this.isAppWindowOnTop(component) }
+}
+
+fun LegacyFlickerTest.appWindowOnTopAtEnd(component: IComponentMatcher) {
+ assertWmEnd { this.isAppWindowOnTop(component) }
+}
+
+fun LegacyFlickerTest.appWindowInsideDisplayBoundsAtEnd(component: IComponentMatcher) {
+ assertWmEnd {
+ val displayBounds = WindowUtils.getDisplayBounds(scenario.endRotation)
+ visibleRegion(component).coversAtMost(displayBounds)
+ }
+}
+
fun LegacyFlickerTest.appWindowIsNotContainAtStart(component: IComponentMatcher) {
assertWmStart { this.notContains(component) }
}
diff --git a/wmshell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonConstants.kt b/wmshell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonConstants.kt
index fb21fcceea..da4c5d3e07 100644
--- a/wmshell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonConstants.kt
+++ b/wmshell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonConstants.kt
@@ -25,6 +25,7 @@ const val LAUNCHER_UI_PACKAGE_NAME = "com.google.android.apps.nexuslauncher"
val APP_PAIR_SPLIT_DIVIDER_COMPONENT = ComponentNameMatcher("", "AppPairSplitDivider#")
val DOCKED_STACK_DIVIDER_COMPONENT = ComponentNameMatcher("", "DockedStackDivider#")
val SPLIT_SCREEN_DIVIDER_COMPONENT = ComponentNameMatcher("", "StageCoordinatorSplitDivider#")
+val TILING_SPLIT_DIVIDER = ComponentNameMatcher("", "DesktopTilingWindowDecoration#")
val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
enum class Direction {
diff --git a/wmshell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt b/wmshell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt
index 4465a16a8e..acaf021981 100644
--- a/wmshell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt
+++ b/wmshell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt
@@ -28,12 +28,16 @@ import com.android.server.wm.flicker.statusBarLayerPositionAtStartAndEnd
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd
import com.android.server.wm.flicker.taskBarWindowIsAlwaysVisible
+import com.android.wm.shell.Flags
import org.junit.Assume
import org.junit.Test
interface ICommonAssertions {
val flicker: LegacyFlickerTest
+ val usesTaskbar: Boolean
+ get() = flicker.scenario.isTablet || Flags.enableTaskbarOnPhones()
+
/** Checks that all parts of the screen are covered during the transition */
@Presubmit @Test fun entireScreenCovered() = flicker.entireScreenCovered()
@@ -43,7 +47,7 @@ interface ICommonAssertions {
@Presubmit
@Test
fun navBarLayerIsVisibleAtStartAndEnd() {
- Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(usesTaskbar)
flicker.navBarLayerIsVisibleAtStartAndEnd()
}
@@ -54,7 +58,7 @@ interface ICommonAssertions {
@Presubmit
@Test
fun navBarLayerPositionAtStartAndEnd() {
- Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(usesTaskbar)
flicker.navBarLayerPositionAtStartAndEnd()
}
@@ -66,7 +70,7 @@ interface ICommonAssertions {
@Presubmit
@Test
fun navBarWindowIsAlwaysVisible() {
- Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(usesTaskbar)
flicker.navBarWindowIsAlwaysVisible()
}
@@ -76,7 +80,7 @@ interface ICommonAssertions {
@Presubmit
@Test
fun taskBarLayerIsVisibleAtStartAndEnd() {
- Assume.assumeTrue(flicker.scenario.isTablet)
+ Assume.assumeTrue(usesTaskbar)
flicker.taskBarLayerIsVisibleAtStartAndEnd()
}
@@ -88,7 +92,7 @@ interface ICommonAssertions {
@Presubmit
@Test
fun taskBarWindowIsAlwaysVisible() {
- Assume.assumeTrue(flicker.scenario.isTablet)
+ Assume.assumeTrue(usesTaskbar)
flicker.taskBarWindowIsAlwaysVisible()
}
diff --git a/wmshell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt b/wmshell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
index c4954f9017..e54930d730 100644
--- a/wmshell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
+++ b/wmshell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
@@ -17,11 +17,15 @@
package com.android.wm.shell.flicker.utils
import android.app.Instrumentation
+import android.app.UiAutomation
+import android.content.Context
import android.graphics.Point
import android.os.SystemClock
import android.tools.Rotation
+import android.tools.device.apphelpers.IStandardAppHelper
import android.tools.device.apphelpers.StandardAppHelper
import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.helpers.WindowUtils
import android.tools.traces.component.ComponentNameMatcher
import android.tools.traces.component.IComponentMatcher
import android.tools.traces.component.IComponentNameMatcher
@@ -102,8 +106,8 @@ object SplitScreenUtils {
wmHelper: WindowManagerStateHelper,
tapl: LauncherInstrumentation,
device: UiDevice,
- primaryApp: StandardAppHelper,
- secondaryApp: StandardAppHelper,
+ primaryApp: IStandardAppHelper,
+ secondaryApp: IStandardAppHelper,
rotation: Rotation
) {
primaryApp.launchViaIntent(wmHelper)
@@ -117,8 +121,8 @@ object SplitScreenUtils {
fun enterSplitViaIntent(
wmHelper: WindowManagerStateHelper,
- primaryApp: StandardAppHelper,
- secondaryApp: StandardAppHelper
+ primaryApp: IStandardAppHelper,
+ secondaryApp: IStandardAppHelper
) {
val stringExtras = mapOf(Primary.EXTRA_LAUNCH_ADJACENT to "true")
primaryApp.launchViaIntent(wmHelper, null, null, stringExtras)
@@ -307,12 +311,18 @@ object SplitScreenUtils {
}
}
+ /**
+ * Drags the divider, then releases, making it snap to a new snap point.
+ */
fun dragDividerToResizeAndWait(device: UiDevice, wmHelper: WindowManagerStateHelper) {
+ // Find the first display that is turned on (making the assumption that there is only one).
val displayBounds =
- wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
- ?: error("Display not found")
+ wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual && it.isOn }
+ ?.layerStackSpace ?: error("Display not found")
val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
- dividerBar.drag(Point(displayBounds.width() * 1 / 3, displayBounds.height() * 2 / 3), 200)
+ // Drag to a point on the lower left of the screen -- this will cause the divider to snap
+ // to the left- or bottom-side snap point, shrinking the "primary" test app.
+ dividerBar.drag(Point(displayBounds.width() * 1 / 4, displayBounds.height() * 3 / 4), 200)
wmHelper
.StateSyncBuilder()
@@ -346,13 +356,40 @@ object SplitScreenUtils {
)
}
- fun doubleTapDividerToSwitch(device: UiDevice) {
+ fun doubleTapDividerToSwitch(device: UiDevice, uiAutomation: UiAutomation) {
val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
- val interval =
- (ViewConfiguration.getDoubleTapTimeout() + ViewConfiguration.getDoubleTapMinTime()) / 2
- dividerBar.click()
- SystemClock.sleep(interval.toLong())
- dividerBar.click()
+ val x = dividerBar.visibleCenter.x.toFloat()
+ val y = dividerBar.visibleCenter.y.toFloat()
+
+ // To send a double-tap action, we set a DOWN event, then UP, then DOWN, then, UP.
+ val startTime = SystemClock.uptimeMillis()
+ val timeOfFirstUp = startTime + ViewConfiguration.getTapTimeout()
+ // Between the two taps, we wait an arbitrary amount of time between the min and max times
+ // for a double-tap.
+ val timeOfSecondDown = timeOfFirstUp + ViewConfiguration.getDoubleTapMinTime() +
+ ((ViewConfiguration.getDoubleTapTimeout() -
+ ViewConfiguration.getDoubleTapMinTime()) / 4)
+ val timeOfSecondUp = timeOfSecondDown + ViewConfiguration.getTapTimeout()
+
+ val downEvent = MotionEvent.obtain(startTime, startTime, MotionEvent.ACTION_DOWN, x, y,
+ 0 /* metaState */)
+ downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN)
+ uiAutomation.injectInputEvent(downEvent, true)
+
+ val upEvent = MotionEvent.obtain(startTime, timeOfFirstUp, MotionEvent.ACTION_UP, x, y,
+ 0 /* metaState */)
+ upEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN)
+ uiAutomation.injectInputEvent(upEvent, true)
+
+ val downEvent2 = MotionEvent.obtain(timeOfSecondDown, timeOfSecondDown,
+ MotionEvent.ACTION_DOWN, x, y, 0 /* metaState */)
+ downEvent2.setSource(InputDevice.SOURCE_TOUCHSCREEN)
+ uiAutomation.injectInputEvent(downEvent2, true)
+
+ val upEvent2 = MotionEvent.obtain(timeOfSecondDown, timeOfSecondUp, MotionEvent.ACTION_UP,
+ x, y, 0 /* metaState */)
+ upEvent2.setSource(InputDevice.SOURCE_TOUCHSCREEN)
+ uiAutomation.injectInputEvent(upEvent2, true)
}
fun copyContentInSplit(
@@ -392,4 +429,24 @@ object SplitScreenUtils {
error("Fail to copy content in split")
}
}
+
+ fun isLeftRightSplit(context: Context, rotation: Rotation, displaySizeDp: Point): Boolean {
+ val allowLeftRightSplit = context.resources.getBoolean(
+ com.android.internal.R.bool.config_leftRightSplitInPortrait)
+ val displayBounds = WindowUtils.getDisplayBounds(rotation)
+ val isLandscape = displayBounds.width() > displayBounds.height()
+ if (allowLeftRightSplit && isTablet(displaySizeDp)) {
+ // Certain devices allow left/right split in portrait, so they end up with top/bottom
+ // split in landscape
+ return !isLandscape
+ } else {
+ return isLandscape
+ }
+ }
+
+ fun isTablet(displaySizeDp: Point): Boolean {
+ val LARGE_SCREEN_DP_THRESHOLD = 600
+ return displaySizeDp.x >= LARGE_SCREEN_DP_THRESHOLD
+ && displaySizeDp.y >= LARGE_SCREEN_DP_THRESHOLD
+ }
}
diff --git a/wmshell/tests/unittest/Android.bp b/wmshell/tests/unittest/Android.bp
index 13f95ccea6..ba117c0466 100644
--- a/wmshell/tests/unittest/Android.bp
+++ b/wmshell/tests/unittest/Android.bp
@@ -37,33 +37,42 @@ android_test {
],
static_libs: [
+ "//frameworks/libs/systemui/mechanics:mechanics-testing",
+ "TestParameterInjector",
+ "WMShellTests-utils",
"WindowManager-Shell",
- "junit",
- "flag-junit",
- "androidx.test.runner",
- "androidx.test.rules",
- "androidx.test.ext.junit",
+ "androidx.core_core-animation-testing",
+ "androidx.datastore_datastore",
"androidx.dynamicanimation_dynamicanimation",
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ "androidx.test.runner",
+ "com.android.window.flags.window-aconfig-java",
+ "com_android_launcher3_flags_lib",
+ "com_android_wm_shell_flags_lib",
"dagger2",
+ "flag-junit",
"frameworks-base-testutils",
+ "junit",
+ "kotlin-test",
"kotlinx-coroutines-android",
"kotlinx-coroutines-core",
+ "kotlinx_coroutines_test",
"mockito-kotlin2",
"mockito-target-extended-minus-junit4",
- "truth",
- "testables",
+ "platform-compat-test-rules",
+ "platform-parametric-runner-lib",
+ "platform-test-annotations",
"platform-test-annotations",
"servicestests-utils",
- "com_android_wm_shell_flags_lib",
- "guava-android-testlib",
- "com.android.window.flags.window-aconfig-java",
- "platform-test-annotations",
+ "testables",
+ "truth",
],
libs: [
- "android.test.mock",
- "android.test.base",
- "android.test.runner",
+ "android.test.base.stubs.system",
+ "android.test.mock.stubs.system",
+ "android.test.runner.stubs.system",
],
jni_libs: [
@@ -89,3 +98,10 @@ android_test {
"com.android.wm.shell.tests",
],
}
+
+test_module_config {
+ name: "WMShellUnitTests_shell_back",
+ base: "WMShellUnitTests",
+ test_suites: ["device-tests"],
+ include_filters: ["com.android.wm.shell.back"],
+}
diff --git a/wmshell/tests/unittest/AndroidTest.xml b/wmshell/tests/unittest/AndroidTest.xml
index 21ed2c075d..90363ee457 100644
--- a/wmshell/tests/unittest/AndroidTest.xml
+++ b/wmshell/tests/unittest/AndroidTest.xml
@@ -27,5 +27,7 @@
+
+
diff --git a/wmshell/tests/unittest/res/values/dimen.xml b/wmshell/tests/unittest/res/values/dimen.xml
index aa1b241892..33ea0baa4f 100644
--- a/wmshell/tests/unittest/res/values/dimen.xml
+++ b/wmshell/tests/unittest/res/values/dimen.xml
@@ -18,6 +18,8 @@
32dp
216dp
+ 20dp
+ 16dp
10dp
20dp
30dp
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/wmshell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index f9b4108bc8..4a90c7f7b6 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -16,10 +16,9 @@
package com.android.wm.shell;
-import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
-import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
-import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
-import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
@@ -31,28 +30,31 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP;
-import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
+import static com.android.wm.shell.ShellTaskOrganizer.isHomeTaskOnDefaultDisplay;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.never;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.TaskInfo;
import android.content.LocusId;
import android.content.pm.ParceledListSlice;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.SparseArray;
import android.view.SurfaceControl;
-import android.view.SurfaceSession;
import android.window.ITaskOrganizer;
import android.window.ITaskOrganizerController;
import android.window.TaskAppearedInfo;
@@ -63,6 +65,8 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.compatui.CompatUIController;
+import com.android.wm.shell.compatui.api.CompatUIInfo;
+import com.android.wm.shell.compatui.impl.CompatUIEvents;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
@@ -70,11 +74,13 @@ import com.android.wm.shell.sysui.ShellInit;
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;
import java.util.ArrayList;
import java.util.Optional;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* Tests for the shell task organizer.
@@ -165,19 +171,6 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
verify(mTaskOrganizerController).registerTaskOrganizer(any(ITaskOrganizer.class));
}
- @Test
- public void testTaskLeashReleasedAfterVanished() throws RemoteException {
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
- RunningTaskInfo taskInfo = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_MULTI_WINDOW);
- SurfaceControl taskLeash = new SurfaceControl.Builder(new SurfaceSession())
- .setName("task").build();
- mOrganizer.registerOrganizer();
- mOrganizer.onTaskAppeared(taskInfo, taskLeash);
- assertTrue(taskLeash.isValid());
- mOrganizer.onTaskVanished(taskInfo);
- assertTrue(!taskLeash.isValid());
- }
-
@Test
public void testOneListenerPerType() {
mOrganizer.addListenerForType(new TrackingTaskListener(), TASK_LISTENER_TYPE_MULTI_WINDOW);
@@ -287,6 +280,50 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
assertTrue(pipListener.appeared.contains(taskInfo));
}
+ @Test
+ public void testAddSameListenerForTaskId() {
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_MULTI_WINDOW);
+ TrackingTaskListener task1Listener = new TrackingTaskListener();
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
+
+ // Add task 1 specific listener
+ mOrganizer.addListenerForTaskId(task1Listener, 1);
+ assertTrue(task1Listener.appeared.contains(task1));
+
+ // Add same listener for the task, assert it doesn't throw and also onTaskAppeared() doesn't
+ // get called it again
+ mOrganizer.addListenerForTaskId(task1Listener, 1);
+ assertEquals(1, task1Listener.appeared.size());
+ }
+
+ @Test
+ public void testAddPendingListenerForTaskId() {
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_MULTI_WINDOW);
+ TrackingTaskListener listener = new TrackingTaskListener();
+
+ // Add the listener first, then report the task to the organizer
+ mOrganizer.addListenerForTaskId(listener, 1);
+ assertFalse(mOrganizer.hasTaskListener(1));
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
+
+ // Verify that the listener got notified anyways
+ assertTrue(listener.appeared.contains(task1));
+ }
+
+ @Test
+ public void testRemovePendingListenerForTaskId() {
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_MULTI_WINDOW);
+ TrackingTaskListener listener = new TrackingTaskListener();
+
+ // Add the listener, remove the listener, then report the task to the organizer
+ mOrganizer.addListenerForTaskId(listener, 1);
+ mOrganizer.removeListener(listener);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
+
+ // Verify that the pending listener does not get notified
+ assertFalse(listener.appeared.contains(task1));
+ }
+
@Test
public void testAddListenerForTaskId_afterTypeListener() {
RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_MULTI_WINDOW);
@@ -315,6 +352,24 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
assertFalse(mwListener.appeared.contains(task1));
}
+ @Test
+ public void testMigrateCookieToTaskOnInfoChanged() {
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_MULTI_WINDOW);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
+
+ TrackingTaskListener mwListener = new TrackingTaskListener();
+ mOrganizer.addListenerForType(mwListener, TASK_LISTENER_TYPE_MULTI_WINDOW);
+
+ TrackingTaskListener cookieListener = new TrackingTaskListener();
+ IBinder cookie = new Binder();
+ task1.addLaunchCookie(cookie);
+ mOrganizer.setPendingLaunchCookieListener(cookie, cookieListener);
+
+ mOrganizer.onTaskInfoChanged(task1);
+
+ assertTrue(mOrganizer.hasTaskListener(task1.taskId));
+ }
+
@Test
public void testGetTaskListener() {
RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_MULTI_WINDOW);
@@ -365,37 +420,37 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
final RunningTaskInfo taskInfo1 = createTaskInfo(/* taskId= */ 12,
WINDOWING_MODE_FULLSCREEN);
taskInfo1.displayId = DEFAULT_DISPLAY;
- taskInfo1.appCompatTaskInfo.topActivityInSizeCompat = false;
+ taskInfo1.appCompatTaskInfo.setTopActivityInSizeCompat(false);
final TrackingTaskListener taskListener = new TrackingTaskListener();
mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN);
mOrganizer.onTaskAppeared(taskInfo1, /* leash= */ null);
// sizeCompatActivity is null if top activity is not in size compat.
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */);
// sizeCompatActivity is non-null if top activity is in size compat.
clearInvocations(mCompatUI);
final RunningTaskInfo taskInfo2 =
createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
taskInfo2.displayId = taskInfo1.displayId;
- taskInfo2.appCompatTaskInfo.topActivityInSizeCompat = true;
+ taskInfo2.appCompatTaskInfo.setTopActivityInSizeCompat(true);
taskInfo2.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo2);
- verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo2, taskListener);
// Not show size compat UI if task is not visible.
clearInvocations(mCompatUI);
final RunningTaskInfo taskInfo3 =
createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
taskInfo3.displayId = taskInfo1.displayId;
- taskInfo3.appCompatTaskInfo.topActivityInSizeCompat = true;
+ taskInfo3.appCompatTaskInfo.setTopActivityInSizeCompat(true);
taskInfo3.isVisible = false;
mOrganizer.onTaskInfoChanged(taskInfo3);
- verify(mCompatUI).onCompatInfoChanged(taskInfo3, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo3, null /* taskListener */);
clearInvocations(mCompatUI);
mOrganizer.onTaskVanished(taskInfo1);
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */);
}
@Test
@@ -403,14 +458,14 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
final RunningTaskInfo taskInfo1 = createTaskInfo(/* taskId= */ 12,
WINDOWING_MODE_FULLSCREEN);
taskInfo1.displayId = DEFAULT_DISPLAY;
- taskInfo1.appCompatTaskInfo.topActivityEligibleForLetterboxEducation = false;
+ taskInfo1.appCompatTaskInfo.setEligibleForLetterboxEducation(false);
final TrackingTaskListener taskListener = new TrackingTaskListener();
mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN);
mOrganizer.onTaskAppeared(taskInfo1, /* leash= */ null);
// Task listener sent to compat UI is null if top activity isn't eligible for letterbox
// education.
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */);
// Task listener is non-null if top activity is eligible for letterbox education and task
// is visible.
@@ -418,102 +473,24 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
final RunningTaskInfo taskInfo2 =
createTaskInfo(taskInfo1.taskId, WINDOWING_MODE_FULLSCREEN);
taskInfo2.displayId = taskInfo1.displayId;
- taskInfo2.appCompatTaskInfo.topActivityEligibleForLetterboxEducation = true;
+ taskInfo2.appCompatTaskInfo.setEligibleForLetterboxEducation(true);
taskInfo2.isVisible = true;
mOrganizer.onTaskInfoChanged(taskInfo2);
- verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo2, taskListener);
// Task listener is null if task is invisible.
clearInvocations(mCompatUI);
final RunningTaskInfo taskInfo3 =
createTaskInfo(taskInfo1.taskId, WINDOWING_MODE_FULLSCREEN);
taskInfo3.displayId = taskInfo1.displayId;
- taskInfo3.appCompatTaskInfo.topActivityEligibleForLetterboxEducation = true;
+ taskInfo3.appCompatTaskInfo.setEligibleForLetterboxEducation(true);
taskInfo3.isVisible = false;
mOrganizer.onTaskInfoChanged(taskInfo3);
- verify(mCompatUI).onCompatInfoChanged(taskInfo3, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo3, null /* taskListener */);
clearInvocations(mCompatUI);
mOrganizer.onTaskVanished(taskInfo1);
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
- }
-
- @Test
- public void testOnCameraCompatActivityChanged() {
- final RunningTaskInfo taskInfo1 = createTaskInfo(/* taskId= */ 1,
- WINDOWING_MODE_FULLSCREEN);
- taskInfo1.displayId = DEFAULT_DISPLAY;
- taskInfo1.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState =
- CAMERA_COMPAT_CONTROL_HIDDEN;
- final TrackingTaskListener taskListener = new TrackingTaskListener();
- mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN);
- mOrganizer.onTaskAppeared(taskInfo1, /* leash= */ null);
-
- // Task listener sent to compat UI is null if top activity doesn't request a camera
- // compat control.
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
-
- // Task listener is non-null when request a camera compat control for a visible task.
- clearInvocations(mCompatUI);
- final RunningTaskInfo taskInfo2 =
- createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
- taskInfo2.displayId = taskInfo1.displayId;
- taskInfo2.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState =
- CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
- taskInfo2.isVisible = true;
- mOrganizer.onTaskInfoChanged(taskInfo2);
- verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener);
-
- // CompatUIController#onCompatInfoChanged is called when requested state for a camera
- // compat control changes for a visible task.
- clearInvocations(mCompatUI);
- final RunningTaskInfo taskInfo3 =
- createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
- taskInfo3.displayId = taskInfo1.displayId;
- taskInfo3.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState =
- CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
- taskInfo3.isVisible = true;
- mOrganizer.onTaskInfoChanged(taskInfo3);
- verify(mCompatUI).onCompatInfoChanged(taskInfo3, taskListener);
-
- // CompatUIController#onCompatInfoChanged is called when a top activity goes in size compat
- // mode for a visible task that has a compat control.
- clearInvocations(mCompatUI);
- final RunningTaskInfo taskInfo4 =
- createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
- taskInfo4.displayId = taskInfo1.displayId;
- taskInfo4.appCompatTaskInfo.topActivityInSizeCompat = true;
- taskInfo4.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState =
- CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
- taskInfo4.isVisible = true;
- mOrganizer.onTaskInfoChanged(taskInfo4);
- verify(mCompatUI).onCompatInfoChanged(taskInfo4, taskListener);
-
- // Task linster is null when a camera compat control is dimissed for a visible task.
- clearInvocations(mCompatUI);
- final RunningTaskInfo taskInfo5 =
- createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
- taskInfo5.displayId = taskInfo1.displayId;
- taskInfo5.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState =
- CAMERA_COMPAT_CONTROL_DISMISSED;
- taskInfo5.isVisible = true;
- mOrganizer.onTaskInfoChanged(taskInfo5);
- verify(mCompatUI).onCompatInfoChanged(taskInfo5, null /* taskListener */);
-
- // Task linster is null when request a camera compat control for a invisible task.
- clearInvocations(mCompatUI);
- final RunningTaskInfo taskInfo6 =
- createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
- taskInfo6.displayId = taskInfo1.displayId;
- taskInfo6.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState =
- CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
- taskInfo6.isVisible = false;
- mOrganizer.onTaskInfoChanged(taskInfo6);
- verify(mCompatUI).onCompatInfoChanged(taskInfo6, null /* taskListener */);
-
- clearInvocations(mCompatUI);
- mOrganizer.onTaskVanished(taskInfo1);
- verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */);
+ verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */);
}
@Test
@@ -640,7 +617,8 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
mOrganizer.onTaskAppeared(task1, /* leash= */ null);
- mOrganizer.onSizeCompatRestartButtonClicked(task1.taskId);
+ mOrganizer.onSizeCompatRestartButtonClicked(
+ new CompatUIEvents.SizeCompatRestartButtonClicked(task1.taskId));
verify(mTaskOrganizerController).restartTaskTopActivityProcessIfVisible(task1.token);
}
@@ -666,9 +644,9 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
@Test
public void testRecentTasks_visibilityChanges_shouldNotifyTaskController() {
- RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FREEFORM);
+ RunningTaskInfo task1 = createFreeformTaskInfo(/* taskId= */ 1);
mOrganizer.onTaskAppeared(task1, /* leash= */ null);
- RunningTaskInfo task2 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FREEFORM);
+ RunningTaskInfo task2 = createFreeformTaskInfo(/* taskId= */ 1);
task2.isVisible = false;
mOrganizer.onTaskInfoChanged(task2);
@@ -676,6 +654,42 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
verify(mRecentTasksController).onTaskRunningInfoChanged(task2);
}
+ @Test
+ public void testRecentTasks_sizeChanges_shouldNotifyTaskController() {
+ RunningTaskInfo task1 = createFreeformTaskInfo(/* taskId= */ 1);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
+ RunningTaskInfo task2 = createFreeformTaskInfo(/* taskId= */ 1);
+ task2.configuration.windowConfiguration.setAppBounds(new Rect(0, 0, 300, 400));
+
+ mOrganizer.onTaskInfoChanged(task2);
+
+ verify(mRecentTasksController).onTaskRunningInfoChanged(task2);
+ }
+
+ @Test
+ public void testRecentTasks_positionChanges_shouldNotifyTaskController() {
+ RunningTaskInfo task1 = createFreeformTaskInfo(/* taskId= */ 1);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
+ RunningTaskInfo task2 = createFreeformTaskInfo(/* taskId= */ 1);
+ task2.positionInParent = new Point(200, 200);
+
+ mOrganizer.onTaskInfoChanged(task2);
+
+ verify(mRecentTasksController).onTaskRunningInfoChanged(task2);
+ }
+
+ @Test
+ public void testRecentTasks_visibilityChanges_notFreeForm_shouldNotNotifyTaskController() {
+ RunningTaskInfo task1_visible = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
+ mOrganizer.onTaskAppeared(task1_visible, /* leash= */ null);
+ RunningTaskInfo task1_hidden = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
+ task1_hidden.isVisible = false;
+
+ mOrganizer.onTaskInfoChanged(task1_hidden);
+
+ verify(mRecentTasksController, never()).onTaskRunningInfoChanged(task1_hidden);
+ }
+
@Test
public void testRecentTasks_windowingModeChanges_shouldNotifyTaskController() {
RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
@@ -687,6 +701,141 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
verify(mRecentTasksController).onTaskRunningInfoChanged(task2);
}
+ @Test
+ public void testTaskAppearedListenerCallback() {
+ final RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
+
+ final RunningTaskInfo[] updatedTasks = new RunningTaskInfo[1];
+
+ final ShellTaskOrganizer.TaskAppearedListener listener =
+ new ShellTaskOrganizer.TaskAppearedListener() {
+ @Override
+ public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
+ updatedTasks[0] = taskInfo;
+ }
+ };
+
+ mOrganizer.addTaskAppearedListener(listener);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
+
+ assertEquals(updatedTasks[0], task1);
+ }
+
+ @Test
+ public void testTaskInfoChangedListenerCallback() {
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
+
+ RunningTaskInfo[] vanishedTasks = new RunningTaskInfo[1];
+ ShellTaskOrganizer.TaskInfoChangedListener listener =
+ new ShellTaskOrganizer.TaskInfoChangedListener() {
+ @Override
+ public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+ vanishedTasks[0] = taskInfo;
+ }
+ };
+ mOrganizer.addTaskInfoChangedListener(listener);
+ mOrganizer.onTaskInfoChanged(task1);
+
+ assertEquals(vanishedTasks[0], task1);
+ }
+
+ @Test
+ public void testTaskVanishedCallback() {
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
+
+ RunningTaskInfo[] vanishedTasks = new RunningTaskInfo[1];
+ ShellTaskOrganizer.TaskVanishedListener listener =
+ new ShellTaskOrganizer.TaskVanishedListener() {
+ @Override
+ public void onTaskVanished(RunningTaskInfo taskInfo) {
+ vanishedTasks[0] = taskInfo;
+ }
+ };
+ mOrganizer.addTaskVanishedListener(listener);
+ mOrganizer.onTaskVanished(task1);
+
+ assertEquals(vanishedTasks[0], task1);
+ }
+
+ @Test
+ public void testSelfRemovingVanishedTaskListenersCallback() {
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
+
+ AtomicInteger calledListenerCount = new AtomicInteger(0);
+ ShellTaskOrganizer.TaskVanishedListener listener1 = getSelfRemovingVanishedListener(
+ mOrganizer, calledListenerCount);
+ ShellTaskOrganizer.TaskVanishedListener listener2 = getSelfRemovingVanishedListener(
+ mOrganizer, calledListenerCount);
+ mOrganizer.addTaskVanishedListener(listener1);
+ mOrganizer.addTaskVanishedListener(listener2);
+ mOrganizer.onTaskVanished(task1);
+
+ assertEquals(2, calledListenerCount.get());
+
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
+ mOrganizer.onTaskVanished(task1);
+
+ // Count should remain the same if no new vanished listeners are added.
+ assertEquals(2, calledListenerCount.get());
+ }
+
+ @Test
+ public void testHomeTaskOnDefaultDisplay() {
+ RunningTaskInfo taskInfo = createTaskInfo(
+ /* taskId= */ 1, ACTIVITY_TYPE_HOME, DEFAULT_DISPLAY);
+
+ assertTrue(isHomeTaskOnDefaultDisplay(taskInfo));
+ }
+
+ @Test
+ public void testNonHomeTaskOnDefaultDisplay() {
+ RunningTaskInfo taskInfo = createTaskInfo(
+ /* taskId= */ 1, ACTIVITY_TYPE_DREAM, DEFAULT_DISPLAY);
+
+ assertFalse(isHomeTaskOnDefaultDisplay(taskInfo));
+ }
+
+ @Test
+ public void testHomeTaskOnExternalDisplay() {
+ RunningTaskInfo taskInfo = createTaskInfo(
+ /* taskId= */ 1, ACTIVITY_TYPE_HOME, /* displayId= */ 2);
+
+ assertFalse(isHomeTaskOnDefaultDisplay(taskInfo));
+ }
+
+ @Test
+ public void testRecentTaskOnExternalDisplay() {
+ RunningTaskInfo taskInfo = createTaskInfo(
+ /* taskId= */ 1, ACTIVITY_TYPE_RECENTS, /* displayId= */ 3);
+
+ assertFalse(isHomeTaskOnDefaultDisplay(taskInfo));
+ }
+
+ @Test
+ public void testGetHomeTaskSurface() {
+ RunningTaskInfo taskInfo = createTaskInfo(
+ /* taskId= */ 1, ACTIVITY_TYPE_HOME, /* displayId= */ 2);
+ SurfaceControl taskLeash = new SurfaceControl.Builder()
+ .setName("home_task").build();
+ mOrganizer.onTaskAppeared(taskInfo, taskLeash);
+ assertNull(mOrganizer.getHomeTaskSurface(/* displayId= */ 0));
+ assertEquals(mOrganizer.getHomeTaskSurface(/* displayId= */ 2), taskLeash);
+ }
+
+ private static ShellTaskOrganizer.TaskVanishedListener getSelfRemovingVanishedListener(
+ ShellTaskOrganizer shellTaskOrganizer, AtomicInteger taskVanishedCalls) {
+ return new ShellTaskOrganizer.TaskVanishedListener() {
+ @Override
+ public void onTaskVanished(RunningTaskInfo taskInfo) {
+ shellTaskOrganizer.removeTaskVanishedListener(this);
+ taskVanishedCalls.incrementAndGet();
+ }
+ };
+ }
+
private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
@@ -694,4 +843,28 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
taskInfo.isVisible = true;
return taskInfo;
}
+
+ private static RunningTaskInfo createTaskInfo(int taskId, int activityType, int displayId) {
+ RunningTaskInfo taskInfo = new RunningTaskInfo();
+ taskInfo.taskId = taskId;
+ taskInfo.configuration.windowConfiguration.setActivityType(activityType);
+ taskInfo.displayId = displayId;
+ return taskInfo;
+ }
+
+ private static RunningTaskInfo createFreeformTaskInfo(int taskId) {
+ RunningTaskInfo taskInfo = createTaskInfo(taskId, WINDOWING_MODE_FREEFORM);
+ taskInfo.positionInParent = new Point(100, 100);
+ taskInfo.configuration.windowConfiguration.setAppBounds(new Rect(0, 0, 200, 200));
+ return taskInfo;
+ }
+
+ private void verifyOnCompatInfoChangedInvokedWith(TaskInfo taskInfo,
+ ShellTaskOrganizer.TaskListener listener) {
+ final ArgumentCaptor capture = ArgumentCaptor.forClass(CompatUIInfo.class);
+ verify(mCompatUI).onCompatInfoChanged(capture.capture());
+ final CompatUIInfo captureValue = capture.getValue();
+ assertEquals(captureValue.getTaskInfo(), taskInfo);
+ assertEquals(captureValue.getListener(), listener);
+ }
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java b/wmshell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
index 51a20ee9d0..79f1c4b514 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
@@ -17,19 +17,26 @@
package com.android.wm.shell;
import static android.view.Display.DEFAULT_DISPLAY;
+
import static org.junit.Assume.assumeTrue;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.display.DisplayManager;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.TestableContext;
+import android.testing.TestableLooper;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.protolog.ProtoLog;
import org.junit.After;
import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
import org.mockito.MockitoAnnotations;
/**
@@ -37,6 +44,16 @@ import org.mockito.MockitoAnnotations;
*/
public abstract class ShellTestCase {
+ @ClassRule
+ public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule();
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule();
+
protected TestableContext mContext;
private PackageManager mPm;
@@ -45,6 +62,9 @@ public abstract class ShellTestCase {
// Disable protolog tool when running the tests from studio
ProtoLog.REQUIRE_PROTOLOGTOOL = false;
+ // Make sure ProtoLog is initialized before any logging occurs.
+ ProtoLog.init();
+
MockitoAnnotations.initMocks(this);
final Context context =
InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -61,6 +81,16 @@ public abstract class ShellTestCase {
@After
public void shellTearDown() {
+ // In case someone set up a TestableLooper during the test, get rid of it because
+ // TestableLooper intrinsically leaks all past test method member fields in a
+ // big static list.
+ //
+ // TODO (b/261039202): don't use TestableLooper in WmShell tests at all.
+ if (TestableLooper.get(this) != null) {
+ TestableLooper.get(this).processAllMessages();
+ TestableLooper.remove(this);
+ }
+
InstrumentationRegistry
.getInstrumentation()
.getUiAutomation()
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java b/wmshell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
index 24f4d92af9..443cbf101e 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
@@ -20,42 +20,40 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
+import android.content.ComponentName;
import android.content.Intent;
import android.graphics.Point;
import android.graphics.Rect;
-import android.os.IBinder;
import android.view.Display;
-import android.window.IWindowContainerToken;
import android.window.WindowContainerToken;
public final class TestRunningTaskInfoBuilder {
static int sNextTaskId = 500;
private Rect mBounds = new Rect(0, 0, 100, 100);
- private WindowContainerToken mToken = createMockWCToken();
+ private WindowContainerToken mToken = new MockToken().token();
private int mParentTaskId = INVALID_TASK_ID;
+ private int mUid = INVALID_TASK_ID;
+ private int mTaskId = INVALID_TASK_ID;
+ private int mUserId = -1;
private Intent mBaseIntent = new Intent();
+ private ComponentName mBaseActivity = null;
private @WindowConfiguration.ActivityType int mActivityType = ACTIVITY_TYPE_STANDARD;
private @WindowConfiguration.WindowingMode int mWindowingMode = WINDOWING_MODE_UNDEFINED;
+ private @WindowConfiguration.ActivityType int mTopActivityType = ACTIVITY_TYPE_STANDARD;
private int mDisplayId = Display.DEFAULT_DISPLAY;
private ActivityManager.TaskDescription.Builder mTaskDescriptionBuilder = null;
private final Point mPositionInParent = new Point();
private boolean mIsVisible = false;
+ private boolean mIsVisibleRequested = false;
+ private boolean mIsTopActivityTransparent = false;
+ private boolean mIsActivityStackTransparent = false;
+ private int mNumActivities = 1;
private long mLastActiveTime;
- public static WindowContainerToken createMockWCToken() {
- final IWindowContainerToken itoken = mock(IWindowContainerToken.class);
- final IBinder asBinder = mock(IBinder.class);
- doReturn(asBinder).when(itoken).asBinder();
- return new WindowContainerToken(itoken);
- }
-
public TestRunningTaskInfoBuilder setToken(WindowContainerToken token) {
mToken = token;
return this;
@@ -71,6 +69,24 @@ public final class TestRunningTaskInfoBuilder {
return this;
}
+ /** Sets the task info's effective UID. */
+ public TestRunningTaskInfoBuilder setUid(int uid) {
+ mUid = uid;
+ return this;
+ }
+
+ /** Sets the task info's UID. */
+ public TestRunningTaskInfoBuilder setTaskId(int taskId) {
+ mTaskId = taskId;
+ return this;
+ }
+
+ /** Sets the task info's user id. */
+ public TestRunningTaskInfoBuilder setUserId(int userId) {
+ mUserId = userId;
+ return this;
+ }
+
/**
* Set {@link ActivityManager.RunningTaskInfo#baseIntent} for the task info, by default
* an empty intent is assigned
@@ -80,12 +96,26 @@ public final class TestRunningTaskInfoBuilder {
return this;
}
+ /**
+ * Set {@link ActivityManager.RunningTaskInfo#baseActivity} for the task info.
+ */
+ public TestRunningTaskInfoBuilder setBaseActivity(@NonNull ComponentName activity) {
+ mBaseActivity = activity;
+ return this;
+ }
+
public TestRunningTaskInfoBuilder setActivityType(
@WindowConfiguration.ActivityType int activityType) {
mActivityType = activityType;
return this;
}
+ public TestRunningTaskInfoBuilder setTopActivityType(
+ @WindowConfiguration.ActivityType int activityType) {
+ mTopActivityType = activityType;
+ return this;
+ }
+
public TestRunningTaskInfoBuilder setWindowingMode(
@WindowConfiguration.WindowingMode int windowingMode) {
mWindowingMode = windowingMode;
@@ -113,6 +143,27 @@ public final class TestRunningTaskInfoBuilder {
return this;
}
+ public TestRunningTaskInfoBuilder setVisibleRequested(boolean isVisible) {
+ mIsVisibleRequested = isVisible;
+ return this;
+ }
+
+ public TestRunningTaskInfoBuilder setTopActivityTransparent(boolean isTopActivityTransparent) {
+ mIsTopActivityTransparent = isTopActivityTransparent;
+ return this;
+ }
+
+ public TestRunningTaskInfoBuilder setActivityStackTransparent(
+ boolean isActivityStackTransparent) {
+ mIsActivityStackTransparent = isActivityStackTransparent;
+ return this;
+ }
+
+ public TestRunningTaskInfoBuilder setNumActivities(int numActivities) {
+ mNumActivities = numActivities;
+ return this;
+ }
+
public TestRunningTaskInfoBuilder setLastActiveTime(long lastActiveTime) {
mLastActiveTime = lastActiveTime;
return this;
@@ -120,13 +171,15 @@ public final class TestRunningTaskInfoBuilder {
public ActivityManager.RunningTaskInfo build() {
final ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
- info.taskId = sNextTaskId++;
+ info.taskId = (mTaskId == INVALID_TASK_ID) ? sNextTaskId++ : mTaskId;
+ info.effectiveUid = mUid;
info.baseIntent = mBaseIntent;
info.parentTaskId = mParentTaskId;
info.displayId = mDisplayId;
info.configuration.windowConfiguration.setBounds(mBounds);
info.configuration.windowConfiguration.setActivityType(mActivityType);
info.configuration.windowConfiguration.setWindowingMode(mWindowingMode);
+ info.topActivityType = mTopActivityType;
info.token = mToken;
info.isResizeable = true;
info.supportsMultiWindow = true;
@@ -134,7 +187,13 @@ public final class TestRunningTaskInfoBuilder {
mTaskDescriptionBuilder != null ? mTaskDescriptionBuilder.build() : null;
info.positionInParent = mPositionInParent;
info.isVisible = mIsVisible;
+ info.isVisibleRequested = mIsVisibleRequested;
+ info.isTopActivityTransparent = mIsTopActivityTransparent;
+ info.isActivityStackTransparent = mIsActivityStackTransparent;
+ info.numActivities = mNumActivities;
info.lastActiveTime = mLastActiveTime;
+ info.userId = mUserId;
+ info.baseActivity = mBaseActivity;
return info;
}
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java b/wmshell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
index 4998702201..f31722d3c1 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
@@ -19,6 +19,7 @@ package com.android.wm.shell;
import com.android.wm.shell.common.ShellExecutor;
import java.util.ArrayList;
+import java.util.List;
/**
* Really basic test executor. It just gathers all events in a blob. The only option is to
@@ -52,4 +53,9 @@ public class TestShellExecutor implements ShellExecutor {
mRunnables.remove(0).run();
}
}
+
+ /** Returns the list of callbacks for this executor. */
+ public List getCallbacks() {
+ return mRunnables;
+ }
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/wmshell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index bd20c11432..bc965b9729 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -20,9 +20,13 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
+import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationRunner.calculateParentBounds;
+import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationRunner.shouldUseJumpCutForAnimation;
import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
@@ -32,24 +36,27 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.animation.Animator;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
+import android.annotation.NonNull;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
import android.window.TransitionInfo;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.window.flags.Flags;
import com.android.wm.shell.transition.TransitionInfoBuilder;
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
+import java.util.Arrays;
/**
* Tests for {@link ActivityEmbeddingAnimationRunner}.
@@ -58,19 +65,15 @@ import java.util.ArrayList;
* atest WMShellUnitTests:ActivityEmbeddingAnimationRunnerTests
*/
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(TestParameterInjector.class)
public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnimationTestBase {
- @Rule
- public SetFlagsRule mRule = new SetFlagsRule();
-
@Before
public void setup() {
super.setUp();
doNothing().when(mController).onAnimationFinished(any());
}
- @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testStartAnimation() {
final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
@@ -96,7 +99,6 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim
verify(mController).onAnimationFinished(mTransition);
}
- @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testChangesBehindStartingWindow() {
final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
@@ -111,7 +113,6 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim
assertEquals(0, animator.getDuration());
}
- @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testTransitionTypeDragResize() {
final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TASK_FRAGMENT_DRAG_RESIZE, 0)
@@ -126,33 +127,14 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim
assertEquals(0, animator.getDuration());
}
- @DisableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
- @Test
- public void testInvalidCustomAnimation_disableAnimationOptionsPerChange() {
- final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
- .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY))
- .build();
- info.setAnimationOptions(TransitionInfo.AnimationOptions
- .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */,
- 0 /* backgroundColor */, false /* overrideTaskTransition */));
- final Animator animator = mAnimRunner.createAnimator(
- info, mStartTransaction, mFinishTransaction,
- () -> mFinishCallback.onTransitionFinished(null /* wct */),
- new ArrayList<>());
-
- // An invalid custom animation is equivalent to jump-cut.
- assertEquals(0, animator.getDuration());
- }
-
- @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testInvalidCustomAnimation_enableAnimationOptionsPerChange() {
final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
- .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY))
+ .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, TRANSIT_OPEN))
.build();
info.getChanges().getFirst().setAnimationOptions(TransitionInfo.AnimationOptions
- .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */,
- 0 /* backgroundColor */, false /* overrideTaskTransition */));
+ .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* changeResId */,
+ 0 /* exitResId */, false /* overrideTaskTransition */));
final Animator animator = mAnimRunner.createAnimator(
info, mStartTransaction, mFinishTransaction,
() -> mFinishCallback.onTransitionFinished(null /* wct */),
@@ -161,4 +143,114 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim
// An invalid custom animation is equivalent to jump-cut.
assertEquals(0, animator.getDuration());
}
+
+ @Test
+ public void testCalculateParentBounds_flagEnabled_emptyParentSize() {
+ TransitionInfo.Change change;
+ final Rect actualParentBounds = new Rect();
+ change = prepareChangeForParentBoundsCalculationTest(
+ new Point(0, 0) /* endRelOffset */,
+ new Rect(0, 0, 2000, 2000),
+ new Point() /* endParentSize */
+ );
+
+ calculateParentBounds(change, actualParentBounds);
+
+ assertTrue("Parent bounds must be empty because end parent size is not set.",
+ actualParentBounds.isEmpty());
+ }
+
+ @Test
+ public void testCalculateParentBounds_flagEnabled(
+ @TestParameter ParentBoundsTestParameters params) {
+ final Rect parentBounds = params.getParentBounds();
+ final Rect endAbsBounds = params.getEndAbsBounds();
+ final TransitionInfo.Change change = prepareChangeForParentBoundsCalculationTest(
+ new Point(endAbsBounds.left - parentBounds.left,
+ endAbsBounds.top - parentBounds.top),
+ endAbsBounds, new Point(parentBounds.width(), parentBounds.height()));
+ final Rect actualParentBounds = new Rect();
+
+ calculateParentBounds(change, actualParentBounds);
+
+ assertEquals(parentBounds, actualParentBounds);
+ }
+
+ private enum ParentBoundsTestParameters {
+ PARENT_START_WITH_0_0(
+ new int[]{0, 0, 2000, 2000},
+ new int[]{0, 0, 2000, 2000}),
+ CONTAINER_NOT_START_WITH_0_0(
+ new int[] {0, 0, 2000, 2000},
+ new int[] {1000, 500, 1500, 1500}),
+ PARENT_ON_THE_RIGHT(
+ new int[] {1000, 0, 2000, 2000},
+ new int[] {1000, 500, 1500, 1500}),
+ PARENT_ON_THE_BOTTOM(
+ new int[] {0, 1000, 2000, 2000},
+ new int[] {500, 1500, 1500, 2000}),
+ PARENT_IN_THE_MIDDLE(
+ new int[] {500, 500, 1500, 1500},
+ new int[] {1000, 500, 1500, 1000});
+
+ /**
+ * An int array to present {left, top, right, bottom} of the parent {@link Rect bounds}.
+ */
+ @NonNull
+ private final int[] mParentBounds;
+
+ /**
+ * An int array to present {left, top, right, bottom} of the absolute container
+ * {@link Rect bounds} after the transition finishes.
+ */
+ @NonNull
+ private final int[] mEndAbsBounds;
+
+ ParentBoundsTestParameters(
+ @NonNull int[] parentBounds, @NonNull int[] endAbsBounds) {
+ mParentBounds = parentBounds;
+ mEndAbsBounds = endAbsBounds;
+ }
+
+ @NonNull
+ private Rect getParentBounds() {
+ return asRect(mParentBounds);
+ }
+
+ @NonNull
+ private Rect getEndAbsBounds() {
+ return asRect(mEndAbsBounds);
+ }
+
+ @NonNull
+ private static Rect asRect(@NonNull int[] bounds) {
+ if (bounds.length != 4) {
+ throw new IllegalArgumentException("There must be exactly 4 elements in bounds, "
+ + "but found " + bounds.length + ": " + Arrays.toString(bounds));
+ }
+ return new Rect(bounds[0], bounds[1], bounds[2], bounds[3]);
+ }
+ }
+
+ @Test
+ public void testShouldUseJumpCutForAnimation() {
+ final Animation noopAnimation = new AlphaAnimation(0f, 1f);
+ assertTrue("Animation without duration should use jump cut.",
+ shouldUseJumpCutForAnimation(noopAnimation));
+
+ final Animation alphaAnimation = new AlphaAnimation(0f, 1f);
+ alphaAnimation.setDuration(100);
+ assertFalse("Animation with duration should not use jump cut.",
+ shouldUseJumpCutForAnimation(alphaAnimation));
+ }
+
+ @NonNull
+ private static TransitionInfo.Change prepareChangeForParentBoundsCalculationTest(
+ @NonNull Point endRelOffset, @NonNull Rect endAbsBounds, @NonNull Point endParentSize) {
+ final TransitionInfo.Change change = createChange(0 /* flags */);
+ change.setEndRelOffset(endRelOffset.x, endRelOffset.y);
+ change.setEndAbsBounds(endAbsBounds);
+ change.setEndParentSize(endParentSize.x, endParentSize.y);
+ return change;
+ }
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java b/wmshell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
index 0b2265d4ce..aee88f751b 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
@@ -16,13 +16,13 @@
package com.android.wm.shell.activityembedding;
+import static android.view.WindowManager.TRANSIT_NONE;
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.mock;
import android.animation.Animator;
@@ -31,6 +31,7 @@ import android.annotation.NonNull;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
+import android.view.WindowManager;
import android.window.TransitionInfo;
import android.window.WindowContainerToken;
@@ -67,7 +68,6 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase {
@CallSuper
@Before
public void setUp() {
- assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
MockitoAnnotations.initMocks(this);
mController = ActivityEmbeddingController.create(mContext, mShellInit, mTransitions);
assertNotNull(mController);
@@ -82,11 +82,27 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase {
spyOn(mFinishCallback);
}
- /** Creates a mock {@link TransitionInfo.Change}. */
+ /**
+ * Creates a mock {@link TransitionInfo.Change}.
+ *
+ * @param flags the {@link TransitionInfo.ChangeFlags} of the change
+ */
static TransitionInfo.Change createChange(@TransitionInfo.ChangeFlags int flags) {
+ return createChange(flags, TRANSIT_NONE);
+ }
+
+ /**
+ * Creates a mock {@link TransitionInfo.Change}.
+ *
+ * @param flags the {@link TransitionInfo.ChangeFlags} of the change
+ * @param mode the transition mode of the change
+ */
+ static TransitionInfo.Change createChange(@TransitionInfo.ChangeFlags int flags,
+ @WindowManager.TransitionType int mode) {
TransitionInfo.Change c = new TransitionInfo.Change(mock(WindowContainerToken.class),
mock(SurfaceControl.class));
c.setFlags(flags);
+ c.setMode(mode);
return c;
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/wmshell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
index 39d55079ca..da4543b4d9 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -32,9 +32,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.graphics.Rect;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
@@ -42,11 +39,9 @@ import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.window.flags.Flags;
import com.android.wm.shell.transition.TransitionInfoBuilder;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -64,9 +59,6 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
private static final Rect EMBEDDED_LEFT_BOUNDS = new Rect(0, 0, 500, 500);
private static final Rect EMBEDDED_RIGHT_BOUNDS = new Rect(500, 0, 1000, 500);
- @Rule
- public SetFlagsRule mRule = new SetFlagsRule();
-
@Before
public void setup() {
super.setUp();
@@ -74,13 +66,11 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
any());
}
- @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testInstantiate() {
verify(mShellInit).addInitCallback(any(), any());
}
- @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testOnInit() {
mController.onInit();
@@ -88,7 +78,6 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
verify(mTransitions).addHandler(mController);
}
- @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testSetAnimScaleSetting() {
mController.setAnimScaleSetting(1.0f);
@@ -97,7 +86,6 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
verify(mAnimSpec).setAnimScaleSetting(1.0f);
}
- @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testStartAnimation_containsNonActivityEmbeddingChange() {
final TransitionInfo.Change nonEmbeddedOpen = createChange(0 /* flags */);
@@ -134,7 +122,6 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
assertFalse(info2.getChanges().contains(nonEmbeddedClose));
}
- @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testStartAnimation_containsOnlyFillTaskActivityEmbeddingChange() {
final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
@@ -151,7 +138,6 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
verifyNoMoreInteractions(mFinishCallback);
}
- @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testStartAnimation_containsActivityEmbeddingSplitChange() {
// Change that occupies only part of the Task.
@@ -169,7 +155,6 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
verifyNoMoreInteractions(mFinishTransaction);
}
- @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testStartAnimation_containsChangeEnterActivityEmbeddingSplit() {
// Change that is entering ActivityEmbedding split.
@@ -186,7 +171,6 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
verifyNoMoreInteractions(mFinishTransaction);
}
- @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testStartAnimation_containsChangeExitActivityEmbeddingSplit() {
// Change that is exiting ActivityEmbedding split.
@@ -203,27 +187,6 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
verifyNoMoreInteractions(mFinishTransaction);
}
- @DisableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
- @Test
- public void testShouldAnimate_containsAnimationOptions_disableAnimOptionsPerChange() {
- final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
- .addChange(createEmbeddedChange(EMBEDDED_RIGHT_BOUNDS, TASK_BOUNDS, TASK_BOUNDS))
- .build();
-
- info.setAnimationOptions(TransitionInfo.AnimationOptions
- .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */,
- 0 /* backgroundColor */, false /* overrideTaskTransition */));
- assertTrue(mController.shouldAnimate(info));
-
- info.setAnimationOptions(TransitionInfo.AnimationOptions
- .makeSceneTransitionAnimOptions());
- assertFalse(mController.shouldAnimate(info));
-
- info.setAnimationOptions(TransitionInfo.AnimationOptions.makeCrossProfileAnimOptions());
- assertFalse(mController.shouldAnimate(info));
- }
-
- @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testShouldAnimate_containsAnimationOptions_enableAnimOptionsPerChange() {
final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
@@ -232,8 +195,8 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
final TransitionInfo.Change change = info.getChanges().getFirst();
change.setAnimationOptions(TransitionInfo.AnimationOptions
- .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */,
- 0 /* backgroundColor */, false /* overrideTaskTransition */));
+ .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* changeResId */,
+ 0 /* exitResId */, false /* overrideTaskTransition */));
assertTrue(mController.shouldAnimate(info));
change.setAnimationOptions(TransitionInfo.AnimationOptions
@@ -244,7 +207,6 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
assertFalse(mController.shouldAnimate(info));
}
- @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@UiThreadTest
@Test
public void testMergeAnimation() {
@@ -276,12 +238,13 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
mController.startAnimation(mTransition, info, mStartTransaction,
mFinishTransaction, mFinishCallback);
verify(mFinishCallback, never()).onTransitionFinished(any());
- mController.mergeAnimation(mTransition, info, new SurfaceControl.Transaction(),
+ mController.mergeAnimation(mTransition, info,
+ new SurfaceControl.Transaction(),
+ new SurfaceControl.Transaction(),
mTransition, (wct) -> {});
verify(mFinishCallback).onTransitionFinished(any());
}
- @EnableFlags(Flags.FLAG_MOVE_ANIMATION_OPTIONS_TO_CHANGE)
@Test
public void testOnAnimationFinished() {
// Should not call finish when there is no transition.
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 57e469d5cb..88668ebf73 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -16,15 +16,30 @@
package com.android.wm.shell.back;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
import static android.window.BackNavigationInfo.KEY_NAVIGATION_FINISHED;
+import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
+import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
@@ -32,42 +47,48 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import android.app.ActivityManager;
import android.app.IActivityTaskManager;
import android.app.WindowConfiguration;
import android.content.pm.ApplicationInfo;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.input.InputManager;
+import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteCallback;
import android.os.RemoteException;
-import android.provider.Settings;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
-import android.testing.TestableContentResolver;
import android.testing.TestableLooper;
import android.view.IRemoteAnimationRunner;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
+import android.view.WindowManager;
import android.window.BackEvent;
import android.window.BackMotionEvent;
import android.window.BackNavigationInfo;
import android.window.IBackAnimationFinishedCallback;
import android.window.IOnBackInvokedCallback;
+import android.window.IWindowContainerToken;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
-import com.android.internal.util.test.FakeSettingsProvider;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.sysui.ShellSharedConstants;
+import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
import org.junit.Test;
@@ -84,7 +105,6 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
public class BackAnimationControllerTest extends ShellTestCase {
- private static final String ANIMATION_ENABLED = "1";
private final TestShellExecutor mShellExecutor = new TestShellExecutor();
private ShellInit mShellInit;
@@ -114,53 +134,57 @@ public class BackAnimationControllerTest extends ShellTestCase {
@Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
+ private Transitions mTransitions;
+ @Mock
private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+ @Mock
+ private Handler mHandler;
+ @Mock
+ private Transitions.TransitionHandler mTakeoverHandler;
private BackAnimationController mController;
- private TestableContentResolver mContentResolver;
- private TestableLooper mTestableLooper;
private DefaultCrossActivityBackAnimation mDefaultCrossActivityBackAnimation;
private CrossTaskBackAnimation mCrossTaskBackAnimation;
private ShellBackAnimationRegistry mShellBackAnimationRegistry;
private Rect mTouchableRegion;
+ private BackAnimationController.BackTransitionHandler mBackTransitionHandler;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mContext.addMockSystemService(InputManager.class, mInputManager);
mContext.getApplicationInfo().privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
- mContentResolver = new TestableContentResolver(mContext);
- mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
- Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION,
- ANIMATION_ENABLED);
- mTestableLooper = TestableLooper.get(this);
mShellInit = spy(new ShellInit(mShellExecutor));
mDefaultCrossActivityBackAnimation = new DefaultCrossActivityBackAnimation(mContext,
- mAnimationBackground, mRootTaskDisplayAreaOrganizer);
- mCrossTaskBackAnimation = new CrossTaskBackAnimation(mContext, mAnimationBackground);
+ mAnimationBackground, mRootTaskDisplayAreaOrganizer, mHandler);
+ mCrossTaskBackAnimation = new CrossTaskBackAnimation(mContext, mAnimationBackground,
+ mHandler);
mShellBackAnimationRegistry =
new ShellBackAnimationRegistry(mDefaultCrossActivityBackAnimation,
mCrossTaskBackAnimation, /* dialogCloseAnimation= */ null,
new CustomCrossActivityBackAnimation(mContext, mAnimationBackground,
- mRootTaskDisplayAreaOrganizer),
+ mRootTaskDisplayAreaOrganizer, mHandler),
/* defaultBackToHomeAnimation= */ null);
mController =
new BackAnimationController(
mShellInit,
mShellController,
mShellExecutor,
- new Handler(mTestableLooper.getLooper()),
mActivityTaskManager,
mContext,
- mContentResolver,
mAnimationBackground,
mShellBackAnimationRegistry,
- mShellCommandHandler);
+ mShellCommandHandler,
+ mTransitions,
+ mHandler);
mShellInit.init();
mShellExecutor.flushAll();
mTouchableRegion = new Rect(0, 0, 100, 100);
mController.mTouchableArea.set(mTouchableRegion);
+ mBackTransitionHandler = mController.mBackTransitionHandler;
+ spyOn(mBackTransitionHandler);
}
private void createNavigationInfo(int backType,
@@ -214,7 +238,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
@Test
public void instantiateController_addExternalInterface() {
verify(mShellController, times(1)).addExternalInterface(
- eq(ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION), any(), any());
+ eq(IBackAnimation.DESCRIPTOR), any(), any());
}
@Test
@@ -300,45 +324,6 @@ public class BackAnimationControllerTest extends ShellTestCase {
verify(mAnimatorCallback).onBackInvoked();
}
- @Test
- public void animationDisabledFromSettings() throws RemoteException {
- // Toggle the setting off
- Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0");
- ShellInit shellInit = new ShellInit(mShellExecutor);
- mController =
- new BackAnimationController(
- shellInit,
- mShellController,
- mShellExecutor,
- new Handler(mTestableLooper.getLooper()),
- mActivityTaskManager,
- mContext,
- mContentResolver,
- mAnimationBackground,
- mShellBackAnimationRegistry,
- mShellCommandHandler);
- shellInit.init();
- registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
-
- ArgumentCaptor backEventCaptor =
- ArgumentCaptor.forClass(BackMotionEvent.class);
-
- createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
- /* enableAnimation = */ false,
- /* isAnimationCallback = */ false);
-
- triggerBackGesture();
- releaseBackGesture();
-
- verify(mAppCallback, times(1)).onBackInvoked();
-
- verify(mAnimatorCallback, never()).onBackStarted(any());
- verify(mAnimatorCallback, never()).onBackProgressed(backEventCaptor.capture());
- verify(mAnimatorCallback, never()).onBackInvoked();
- verify(mBackAnimationRunner, never()).onAnimationStart(
- anyInt(), any(), any(), any(), any());
- }
-
@Test
public void gestureQueued_WhenPreviousTransitionHasNotYetEnded() throws RemoteException {
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
@@ -571,6 +556,51 @@ public class BackAnimationControllerTest extends ShellTestCase {
verify(mAnimatorCallback, never()).onBackInvoked();
}
+ @EnableFlags({com.android.systemui.shared.Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ com.android.systemui.shared.Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
+ com.android.window.flags.Flags.FLAG_UNIFY_BACK_NAVIGATION_TRANSITION})
+ @Test
+ public void appCallback_receivesTakeoverHandler_whenAvailable() throws RemoteException {
+ registerAnimation(BackNavigationInfo.TYPE_CROSS_TASK);
+ mBackTransitionHandler.mTakeoverHandler = mTakeoverHandler;
+
+ final int type = BackNavigationInfo.TYPE_CALLBACK;
+ final ResultListener result = new ResultListener();
+ createNavigationInfo(new BackNavigationInfo.Builder()
+ .setType(type)
+ .setOnBackInvokedCallback(mAppCallback)
+ .setOnBackNavigationDone(new RemoteCallback(result))
+ .setTouchableRegion(mTouchableRegion)
+ .setAppProgressAllowed(true));
+
+ triggerBackGesture();
+ mShellExecutor.flushAll();
+ releaseBackGesture();
+ mShellExecutor.flushAll();
+
+ verify(mAppCallback).setHandoffHandler(notNull());
+ }
+
+ @Test
+ public void appCallback_doesNotReceiveTakeoverHandler_whenUnavailable() throws RemoteException {
+ registerAnimation(BackNavigationInfo.TYPE_CROSS_TASK);
+
+ final int type = BackNavigationInfo.TYPE_CALLBACK;
+ final ResultListener result = new ResultListener();
+ createNavigationInfo(new BackNavigationInfo.Builder()
+ .setType(type)
+ .setOnBackInvokedCallback(mAppCallback)
+ .setOnBackNavigationDone(new RemoteCallback(result))
+ .setTouchableRegion(mTouchableRegion)
+ .setAppProgressAllowed(true));
+ triggerBackGesture();
+ mShellExecutor.flushAll();
+ releaseBackGesture();
+ mShellExecutor.flushAll();
+
+ verify(mAppCallback).setHandoffHandler(isNull());
+ }
+
@Test
public void skipsCancelWithoutStart() throws RemoteException {
final int type = BackNavigationInfo.TYPE_CALLBACK;
@@ -601,6 +631,220 @@ public class BackAnimationControllerTest extends ShellTestCase {
mCrossTaskBackAnimation.getRunner());
}
+ @Test
+ public void testCloseAsExpectTransition() {
+ final int openTaskId = 1;
+ final int closeTaskId = 2;
+ mController.mApps = createAppAnimationTargets(openTaskId, closeTaskId);
+ final IBinder mockBinder = mock(IBinder.class);
+ final SurfaceControl.Transaction st = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction ft = mock(SurfaceControl.Transaction.class);
+ // Single close
+ final TransitionInfo.Change open = createAppChange(openTaskId, TRANSIT_OPEN,
+ FLAG_BACK_GESTURE_ANIMATED | FLAG_MOVED_TO_TOP);
+ final TransitionInfo.Change close = createAppChange(closeTaskId, TRANSIT_CLOSE,
+ FLAG_BACK_GESTURE_ANIMATED);
+
+ TransitionInfo tInfo = createTransitionInfo(TRANSIT_CLOSE, open, close);
+ mBackTransitionHandler.mCloseTransitionRequested = true;
+ Transitions.TransitionFinishCallback callback =
+ mock(Transitions.TransitionFinishCallback.class);
+ mBackTransitionHandler.startAnimation(mockBinder, tInfo, st, ft, callback);
+ verify(mBackTransitionHandler).handleCloseTransition(
+ eq(tInfo), eq(st), eq(ft), eq(callback));
+ mBackTransitionHandler.onAnimationFinished();
+ verify(callback).onTransitionFinished(any());
+ mBackTransitionHandler.mCloseTransitionRequested = false;
+
+ // PREPARE + CLOSE
+ tInfo = createTransitionInfo(TRANSIT_PREPARE_BACK_NAVIGATION, open);
+ callback = mock(Transitions.TransitionFinishCallback.class);
+ mBackTransitionHandler.startAnimation(mockBinder, tInfo, st, ft, callback);
+ verify(mBackTransitionHandler).handlePrepareTransition(eq(mockBinder),
+ eq(tInfo), eq(st), eq(ft), eq(callback));
+ mBackTransitionHandler.mCloseTransitionRequested = true;
+ TransitionInfo tInfo2 = createTransitionInfo(TRANSIT_CLOSE, close);
+ Transitions.TransitionFinishCallback mergeCallback =
+ mock(Transitions.TransitionFinishCallback.class);
+ mBackTransitionHandler.mergeAnimation(
+ mock(IBinder.class), tInfo2, st, ft, mock(IBinder.class), mergeCallback);
+ mBackTransitionHandler.onAnimationFinished();
+ verify(callback).onTransitionFinished(any());
+ verify(mergeCallback).onTransitionFinished(any());
+ mBackTransitionHandler.mCloseTransitionRequested = false;
+
+ // PREPARE contains close info
+ tInfo = createTransitionInfo(TRANSIT_PREPARE_BACK_NAVIGATION, open, close);
+ callback = mock(Transitions.TransitionFinishCallback.class);
+ mBackTransitionHandler.mCloseTransitionRequested = true;
+ mBackTransitionHandler.startAnimation(mockBinder, tInfo, st, ft, callback);
+ verify(mBackTransitionHandler).handleCloseTransition(
+ eq(tInfo), eq(st), eq(ft), eq(callback));
+ mBackTransitionHandler.onAnimationFinished();
+ verify(callback).onTransitionFinished(any());
+ mBackTransitionHandler.mCloseTransitionRequested = false;
+
+ // PREPARE then Cancel
+ tInfo = createTransitionInfo(TRANSIT_PREPARE_BACK_NAVIGATION, open);
+ callback = mock(Transitions.TransitionFinishCallback.class);
+ final TransitionRequestInfo requestInfo = new TransitionRequestInfo(
+ TRANSIT_PREPARE_BACK_NAVIGATION, null /* triggerTask */,
+ null /* remoteTransition */);
+ mBackTransitionHandler.handleRequest(mockBinder, requestInfo);
+ mBackTransitionHandler.startAnimation(mockBinder, tInfo, st, ft, callback);
+ verify(mBackTransitionHandler).handlePrepareTransition(eq(mockBinder),
+ eq(tInfo), eq(st), eq(ft), eq(callback));
+
+ mBackTransitionHandler.onAnimationFinished();
+ final TransitionInfo.Change openToClose = createAppChangeFromChange(open, TRANSIT_CLOSE,
+ FLAG_BACK_GESTURE_ANIMATED);
+ tInfo2 = createTransitionInfo(TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION, openToClose);
+ mBackTransitionHandler.mClosePrepareTransition = mock(IBinder.class);
+ mergeCallback = mock(Transitions.TransitionFinishCallback.class);
+ mBackTransitionHandler.mergeAnimation(mBackTransitionHandler.mClosePrepareTransition,
+ tInfo2, st, ft, mock(IBinder.class), mergeCallback);
+ assertTrue("Change should be consumed", tInfo2.getChanges().isEmpty());
+ verify(callback).onTransitionFinished(any());
+ }
+
+ @Test
+ public void testCancelUnexpectedTransition() {
+ final int openTaskId = 1;
+ final int closeTaskId = 2;
+ mController.mApps = createAppAnimationTargets(openTaskId, closeTaskId);
+ final IBinder mockBinder = mock(IBinder.class);
+ final SurfaceControl.Transaction st = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction ft = mock(SurfaceControl.Transaction.class);
+ final TransitionInfo.Change open = createAppChange(openTaskId, TRANSIT_OPEN,
+ FLAG_BACK_GESTURE_ANIMATED | FLAG_MOVED_TO_TOP);
+ final TransitionInfo.Change close = createAppChange(closeTaskId, TRANSIT_CLOSE,
+ FLAG_BACK_GESTURE_ANIMATED);
+
+ // Didn't trigger close transition
+ mBackTransitionHandler.mCloseTransitionRequested = false;
+ TransitionInfo prepareInfo = createTransitionInfo(TRANSIT_PREPARE_BACK_NAVIGATION,
+ open, close);
+ final Transitions.TransitionFinishCallback callback =
+ mock(Transitions.TransitionFinishCallback.class);
+ mBackTransitionHandler.handleRequest(mockBinder, mock(TransitionRequestInfo.class));
+ boolean canHandle = mBackTransitionHandler.startAnimation(
+ mockBinder, prepareInfo, st, ft, callback);
+ assertFalse("Should not handle transition", canHandle);
+ assertNull(mBackTransitionHandler.mOnAnimationFinishCallback);
+
+ // Didn't trigger close transition, but receive close target.
+ final TransitionRequestInfo requestInfo = new TransitionRequestInfo(
+ TRANSIT_PREPARE_BACK_NAVIGATION, null /* triggerTask */,
+ null /* remoteTransition */);
+ prepareInfo = createTransitionInfo(TRANSIT_PREPARE_BACK_NAVIGATION, open);
+ final Transitions.TransitionFinishCallback callback2 =
+ mock(Transitions.TransitionFinishCallback.class);
+ mBackTransitionHandler.handleRequest(mockBinder, requestInfo);
+ canHandle = mBackTransitionHandler.startAnimation(mockBinder,
+ prepareInfo, st, ft, callback2);
+ assertTrue("Handle prepare transition" , canHandle);
+ verify(mBackTransitionHandler).handlePrepareTransition(eq(mockBinder),
+ eq(prepareInfo), eq(st), eq(ft), eq(callback2));
+ final TransitionInfo closeInfo = createTransitionInfo(TRANSIT_CLOSE, close);
+ Transitions.TransitionFinishCallback mergeCallback =
+ mock(Transitions.TransitionFinishCallback.class);
+ mBackTransitionHandler.mergeAnimation(mock(IBinder.class), closeInfo, st, ft,
+ mock(IBinder.class), mergeCallback);
+ verify(callback2).onTransitionFinished(any());
+ verify(mergeCallback, never()).onTransitionFinished(any());
+
+ // Didn't trigger close transition, but contains open target.
+ final int openTaskId2 = 3;
+ final Transitions.TransitionFinishCallback callback3 =
+ mock(Transitions.TransitionFinishCallback.class);
+ mBackTransitionHandler.handleRequest(mockBinder, requestInfo);
+ canHandle = mBackTransitionHandler.startAnimation(
+ mockBinder, prepareInfo, st, ft, callback3);
+ assertTrue("Handle prepare transition" , canHandle);
+ verify(mBackTransitionHandler).handlePrepareTransition(eq(mockBinder),
+ eq(prepareInfo), eq(st), eq(ft), eq(callback3));
+ final TransitionInfo.Change open2 = createAppChange(
+ openTaskId2, TRANSIT_OPEN, FLAG_MOVED_TO_TOP);
+ final TransitionInfo openInfo = createTransitionInfo(TRANSIT_OPEN, open2, close);
+ mergeCallback = mock(Transitions.TransitionFinishCallback.class);
+ mBackTransitionHandler.mergeAnimation(mock(IBinder.class), openInfo, st, ft,
+ mock(IBinder.class), mergeCallback);
+ verify(callback3).onTransitionFinished(any());
+ verify(mergeCallback, never()).onTransitionFinished(any());
+ }
+
+ @Test
+ public void testBackAnimationControllersRecoversFromBadState() throws RemoteException {
+ // put controller into bad state (initial state but mBackGestureStarted=true)
+ mController.mBackGestureStarted = true;
+ verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ mDefaultCrossActivityBackAnimation.getRunner());
+ }
+
+ private RemoteAnimationTarget[] createAppAnimationTargets(int openTaskId, int closeTaskId) {
+ final RemoteAnimationTarget openT = createSingleAnimationTarget(openTaskId,
+ RemoteAnimationTarget.MODE_OPENING);
+ final RemoteAnimationTarget closeT = createSingleAnimationTarget(closeTaskId,
+ RemoteAnimationTarget.MODE_CLOSING);
+ return new RemoteAnimationTarget[]{openT, closeT};
+ }
+
+ private RemoteAnimationTarget createSingleAnimationTarget(int taskId, int mode) {
+ final Rect fakeR = new Rect();
+ final Point fakeP = new Point();
+ final ActivityManager.RunningTaskInfo openTaskInfo = new ActivityManager.RunningTaskInfo();
+ openTaskInfo.taskId = taskId;
+ openTaskInfo.token = new WindowContainerToken(mock(IWindowContainerToken.class));
+ return new RemoteAnimationTarget(
+ taskId, mode, mock(SurfaceControl.class), false, fakeR, fakeR,
+ 0, fakeP, fakeR, fakeR, new WindowConfiguration(), false,
+ mock(SurfaceControl.class), fakeR, openTaskInfo, false);
+ }
+ private TransitionInfo.Change createAppChange(
+ int taskId, @TransitionInfo.TransitionMode int mode,
+ @TransitionInfo.ChangeFlags int flags) {
+ final TransitionInfo.Change change;
+ SurfaceControl.Builder b = new SurfaceControl.Builder()
+ .setName("test task");
+ if (taskId != INVALID_TASK_ID) {
+ final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.taskId = taskId;
+ final IWindowContainerToken mockT = mock(IWindowContainerToken.class);
+ Binder binder = new Binder();
+ doReturn(binder).when(mockT).asBinder();
+ taskInfo.token = new WindowContainerToken(mockT);
+ change = new TransitionInfo.Change(
+ taskInfo.token, b.build());
+ change.setTaskInfo(taskInfo);
+ } else {
+ change = new TransitionInfo.Change(
+ null, b.build());
+
+ }
+ change.setMode(mode);
+ change.setFlags(flags);
+ return change;
+ }
+
+ private TransitionInfo.Change createAppChangeFromChange(
+ TransitionInfo.Change originalChange, @TransitionInfo.TransitionMode int mode,
+ @TransitionInfo.ChangeFlags int flags) {
+ final TransitionInfo.Change change = new TransitionInfo.Change(
+ originalChange.getTaskInfo().token, originalChange.getLeash());
+ change.setMode(mode);
+ change.setFlags(flags);
+ return change;
+ }
+
+ private static TransitionInfo createTransitionInfo(
+ @WindowManager.TransitionType int type, TransitionInfo.Change ... changes) {
+ final TransitionInfo info = new TransitionInfo(type, 0);
+ for (int i = 0; i < changes.length; ++i) {
+ info.addChange(changes[i]);
+ }
+ return info;
+ }
+
private void verifySystemBackBehavior(int type, BackAnimationRunner animation)
throws RemoteException {
final BackAnimationRunner animationRunner = spy(animation);
@@ -634,17 +878,12 @@ public class BackAnimationControllerTest extends ShellTestCase {
}
private void doMotionEvent(int actionDown, int coordinate) {
- doMotionEvent(actionDown, coordinate, 0);
- }
-
- private void doMotionEvent(int actionDown, int coordinate, float velocity) {
mController.onMotionEvent(
/* touchX */ coordinate,
/* touchY */ coordinate,
- /* velocityX = */ velocity,
- /* velocityY = */ velocity,
/* keyAction */ actionDown,
- /* swipeEdge */ BackEvent.EDGE_LEFT);
+ /* swipeEdge */ BackEvent.EDGE_LEFT,
+ /* displayId */ DEFAULT_DISPLAY);
}
/**
@@ -652,8 +891,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
*/
private void doStartEvents(int startX, int moveX) {
doMotionEvent(MotionEvent.ACTION_DOWN, startX);
- mController.onThresholdCrossed();
doMotionEvent(MotionEvent.ACTION_MOVE, moveX);
+ mController.onThresholdCrossed();
}
private void simulateRemoteAnimationStart() throws RemoteException {
@@ -661,7 +900,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
RemoteAnimationTarget[] targets = new RemoteAnimationTarget[]{animationTarget};
if (mController.mBackAnimationAdapter != null) {
mController.mBackAnimationAdapter.getRunner().onAnimationStart(
- targets, null, null, mBackAnimationFinishedCallback);
+ targets, null /* prepareOpenTransition */, mBackAnimationFinishedCallback);
mShellExecutor.flushAll();
}
}
@@ -677,7 +916,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
new BackAnimationRunner(
mAnimatorCallback,
mBackAnimationRunner,
- mContext));
+ mContext,
+ mHandler));
}
private void unregisterAnimation(int type) {
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
index 4d0348b4f4..2ef6c558b0 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -32,6 +32,8 @@ import android.window.BackProgressAnimator;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.wm.shell.ShellTestCase;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -42,7 +44,7 @@ import java.util.concurrent.TimeUnit;
@SmallTest
@TestableLooper.RunWithLooper
@RunWith(AndroidTestingRunner.class)
-public class BackProgressAnimatorTest {
+public class BackProgressAnimatorTest extends ShellTestCase {
private BackProgressAnimator mProgressAnimator;
private BackEvent mReceivedBackEvent;
private float mTargetProgress = 0.5f;
@@ -53,9 +55,8 @@ public class BackProgressAnimatorTest {
return new BackMotionEvent(
/* touchX = */ touchX,
/* touchY = */ 0,
+ /* frameTime = */ 0,
/* progress = */ progress,
- /* velocityX = */ 0,
- /* velocityY = */ 0,
/* triggerBack = */ false,
/* swipeEdge = */ BackEvent.EDGE_LEFT,
/* departingAnimationTarget = */ null);
@@ -63,6 +64,8 @@ public class BackProgressAnimatorTest {
@Before
public void setUp() throws Exception {
+ mTargetProgressCalled = new CountDownLatch(1);
+ mTargetProgress = 0.5f;
mMainThreadHandler = new Handler(Looper.getMainLooper());
final BackMotionEvent backEvent = backMotionEventFrom(0, 0);
mMainThreadHandler.post(
@@ -177,6 +180,31 @@ public class BackProgressAnimatorTest {
assertEquals(1, finishCallbackCalled.getCount());
}
+ @Test
+ public void testOnBackInvokedFinishCallbackNotInvokedWhenRemoved() throws InterruptedException {
+ // Give the animator some progress.
+ final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress);
+ mMainThreadHandler.post(
+ () -> mProgressAnimator.onBackProgressed(backEvent));
+ mTargetProgressCalled.await(1, TimeUnit.SECONDS);
+ assertNotNull(mReceivedBackEvent);
+
+ CountDownLatch finishCallbackCalled = new CountDownLatch(1);
+ // Trigger back invoked animation and remove onBackInvoked finishCallback (while progress
+ // is still animating to 1)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mProgressAnimator.onBackInvoked(finishCallbackCalled::countDown);
+ mProgressAnimator.removeOnBackInvokedFinishCallback();
+ }
+ );
+
+ // call reset (which triggers the finishCallback invocation, if one is present)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> mProgressAnimator.reset());
+
+ // verify that finishCallback is not invoked
+ assertEquals(1, finishCallbackCalled.getCount());
+ }
+
private void onGestureProgress(BackEvent backEvent) {
if (mTargetProgress == backEvent.getProgress()) {
mReceivedBackEvent = backEvent;
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt b/wmshell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
index 080ad901c6..2cc52c5ab9 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt
@@ -22,6 +22,7 @@ import android.app.WindowConfiguration
import android.graphics.Color
import android.graphics.Point
import android.graphics.Rect
+import android.os.Handler
import android.os.RemoteException
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -66,6 +67,7 @@ class CustomCrossActivityBackAnimationTest : ShellTestCase() {
@Mock private lateinit var transitionAnimation: TransitionAnimation
@Mock private lateinit var appCompatTaskInfo: AppCompatTaskInfo
@Mock private lateinit var transaction: Transaction
+ @Mock private lateinit var handler: Handler
private lateinit var customCrossActivityBackAnimation: CustomCrossActivityBackAnimation
private lateinit var customAnimationLoader: CustomAnimationLoader
@@ -80,7 +82,8 @@ class CustomCrossActivityBackAnimationTest : ShellTestCase() {
backAnimationBackground,
rootTaskDisplayAreaOrganizer,
transaction,
- customAnimationLoader
+ customAnimationLoader,
+ handler,
)
whenever(transitionAnimation.loadAppTransitionAnimation(eq(PACKAGE_NAME), eq(OPEN_RES_ID)))
@@ -218,9 +221,8 @@ class CustomCrossActivityBackAnimationTest : ShellTestCase() {
BackMotionEvent(
/* touchX = */ touchX,
/* touchY = */ 0f,
+ /* frameTime = */ 0,
/* progress = */ progress,
- /* velocityX = */ 0f,
- /* velocityY = */ 0f,
/* triggerBack = */ false,
/* swipeEdge = */ BackEvent.EDGE_LEFT,
/* departingAnimationTarget = */ null
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt b/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
index e35995775f..9ec62c965a 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
@@ -124,6 +124,7 @@ class BubbleDataRepositoryTest : ShellTestCase() {
private val testHandler = Handler(Looper.getMainLooper())
private val mainExecutor = HandlerExecutor(testHandler)
+ private val bgExecutor = HandlerExecutor(testHandler)
private val launcherApps = mock()
private val persistedBubbles = SparseArray>()
@@ -134,7 +135,8 @@ class BubbleDataRepositoryTest : ShellTestCase() {
@Before
fun setup() {
persistentRepository = BubblePersistentRepository(mContext)
- dataRepository = spy(BubbleDataRepository(launcherApps, mainExecutor, persistentRepository))
+ dataRepository =
+ spy(BubbleDataRepository(launcherApps, mainExecutor, bgExecutor, persistentRepository))
persistedBubbles.put(0, user0BubbleEntities)
persistedBubbles.put(1, user1BubbleEntities)
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 93e405131a..b3d2db6da6 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -27,7 +27,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.Notification;
@@ -46,11 +46,12 @@ import android.view.WindowManager;
import androidx.test.filters.SmallTest;
+import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.bubbles.BubbleData.TimeSource;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.bubbles.BubbleBarLocation;
-import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.bubbles.BubbleBarUpdate;
import com.google.common.collect.ImmutableList;
@@ -101,6 +102,7 @@ public class BubbleDataTest extends ShellTestCase {
private BubbleData mBubbleData;
private TestableBubblePositioner mPositioner;
+ private UiEventLoggerFake mUiEventLogger;
@Mock
private TimeSource mTimeSource;
@@ -111,11 +113,11 @@ public class BubbleDataTest extends ShellTestCase {
@Mock
private PendingIntent mDeleteIntent;
@Mock
- private BubbleLogger mBubbleLogger;
- @Mock
private BubbleEducationController mEducationController;
@Mock
private ShellExecutor mMainExecutor;
+ @Mock
+ private ShellExecutor mBgExecutor;
@Captor
private ArgumentCaptor mUpdateCaptor;
@@ -143,47 +145,47 @@ public class BubbleDataTest extends ShellTestCase {
when(ranking.isTextChanged()).thenReturn(true);
mEntryInterruptive = createBubbleEntry(1, "interruptive", "package.d", ranking);
mBubbleInterruptive = new Bubble(mEntryInterruptive, mBubbleMetadataFlagListener, null,
- mMainExecutor);
+ mMainExecutor, mBgExecutor);
mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d", null);
mBubbleDismissed = new Bubble(mEntryDismissed, mBubbleMetadataFlagListener, null,
- mMainExecutor);
+ mMainExecutor, mBgExecutor);
mEntryLocusId = createBubbleEntry(1, "keyLocus", "package.e", null,
new LocusId("locusId1"));
mBubbleLocusId = new Bubble(mEntryLocusId,
mBubbleMetadataFlagListener,
null /* pendingIntentCanceledListener */,
- mMainExecutor);
+ mMainExecutor, mBgExecutor);
mBubbleA1 = new Bubble(mEntryA1,
mBubbleMetadataFlagListener,
mPendingIntentCanceledListener,
- mMainExecutor);
+ mMainExecutor, mBgExecutor);
mBubbleA2 = new Bubble(mEntryA2,
mBubbleMetadataFlagListener,
mPendingIntentCanceledListener,
- mMainExecutor);
+ mMainExecutor, mBgExecutor);
mBubbleA3 = new Bubble(mEntryA3,
mBubbleMetadataFlagListener,
mPendingIntentCanceledListener,
- mMainExecutor);
+ mMainExecutor, mBgExecutor);
mBubbleB1 = new Bubble(mEntryB1,
mBubbleMetadataFlagListener,
mPendingIntentCanceledListener,
- mMainExecutor);
+ mMainExecutor, mBgExecutor);
mBubbleB2 = new Bubble(mEntryB2,
mBubbleMetadataFlagListener,
mPendingIntentCanceledListener,
- mMainExecutor);
+ mMainExecutor, mBgExecutor);
mBubbleB3 = new Bubble(mEntryB3,
mBubbleMetadataFlagListener,
mPendingIntentCanceledListener,
- mMainExecutor);
+ mMainExecutor, mBgExecutor);
mBubbleC1 = new Bubble(mEntryC1,
mBubbleMetadataFlagListener,
mPendingIntentCanceledListener,
- mMainExecutor);
+ mMainExecutor, mBgExecutor);
Intent appBubbleIntent = new Intent(mContext, BubblesTestActivity.class);
appBubbleIntent.setPackage(mContext.getPackageName());
@@ -191,12 +193,14 @@ public class BubbleDataTest extends ShellTestCase {
appBubbleIntent,
new UserHandle(1),
mock(Icon.class),
- mMainExecutor);
+ mMainExecutor, mBgExecutor);
+
+ mUiEventLogger = new UiEventLoggerFake();
mPositioner = new TestableBubblePositioner(mContext,
mContext.getSystemService(WindowManager.class));
- mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner, mEducationController,
- mMainExecutor);
+ mBubbleData = new BubbleData(getContext(), new BubbleLogger(mUiEventLogger), mPositioner,
+ mEducationController, mMainExecutor, mBgExecutor);
// Used by BubbleData to set lastAccessedTime
when(mTimeSource.currentTimeMillis()).thenReturn(1000L);
@@ -254,6 +258,121 @@ public class BubbleDataTest extends ShellTestCase {
assertBubbleRemoved(mBubbleA1, Bubbles.DISMISS_USER_GESTURE);
}
+ @Test
+ public void testRemoveBubbleInLauncher_beforeBubbleUpdate_processedAfter_shouldNotBeRemoved() {
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryA2, 2000);
+ mBubbleData.setListener(mListener);
+
+ sendUpdatedEntryAtTime(mEntryA2, 3000);
+
+ verifyUpdateReceived();
+ assertThat(mBubbleData.hasBubbleInStackWithKey(mEntryA2.getKey())).isTrue();
+ assertThat(mBubbleData.getBubbleInStackWithKey(mEntryA2.getKey()).getLastActivity())
+ .isEqualTo(3000);
+
+ // dismiss the bubble with a timestamp in the past
+ mBubbleData.dismissBubbleWithKey(
+ mEntryA2.getKey(), Bubbles.DISMISS_USER_GESTURE_FROM_LAUNCHER, 2500);
+
+ verifyNoMoreInteractions(mListener);
+ assertThat(mBubbleData.hasBubbleInStackWithKey(mEntryA2.getKey())).isTrue();
+ }
+
+ @Test
+ public void testRemoveBubbleInLauncher_isNotSentBackToLauncher() {
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryA2, 2000);
+ mBubbleData.setListener(mListener);
+
+ mBubbleData.dismissBubbleWithKey(
+ mEntryA2.getKey(), Bubbles.DISMISS_USER_GESTURE_FROM_LAUNCHER, 4000);
+ verifyUpdateReceived();
+
+ BubbleData.Update update = mUpdateCaptor.getValue();
+ assertThat(update.removedBubbles).hasSize(1);
+ assertThat(update.removedBubbles.getFirst().first.getKey()).isEqualTo(mBubbleA2.getKey());
+
+ BubbleBarUpdate bubbleBarUpdate = update.toBubbleBarUpdate();
+ assertThat(bubbleBarUpdate.removedBubbles).isEmpty();
+ }
+
+ @Test
+ public void testRemoveBubbleFromBubbleBar_notifCancelled_logEvent() {
+ mPositioner.setShowingInBubbleBar(true);
+
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ mBubbleData.setListener(mListener);
+
+ mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_NOTIF_CANCEL);
+ assertThat(mUiEventLogger.numLogs()).isEqualTo(1);
+ assertThat(mUiEventLogger.eventId(0)).isEqualTo(
+ BubbleLogger.Event.BUBBLE_BAR_BUBBLE_REMOVED_CANCELED.getId());
+ }
+
+ @Test
+ public void testRemoveBubbleFromBubbleBar_taskFinished_logEvent() {
+ mPositioner.setShowingInBubbleBar(true);
+
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ mBubbleData.setListener(mListener);
+
+ mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_TASK_FINISHED);
+ assertThat(mUiEventLogger.numLogs()).isEqualTo(1);
+ assertThat(mUiEventLogger.eventId(0)).isEqualTo(
+ BubbleLogger.Event.BUBBLE_BAR_BUBBLE_ACTIVITY_FINISH.getId());
+ }
+
+ @Test
+ public void testRemoveBubbleFromBubbleBar_notifBlocked_logEvent() {
+ mPositioner.setShowingInBubbleBar(true);
+
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ mBubbleData.setListener(mListener);
+
+ mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_BLOCKED);
+ assertThat(mUiEventLogger.numLogs()).isEqualTo(1);
+ assertThat(mUiEventLogger.eventId(0)).isEqualTo(
+ BubbleLogger.Event.BUBBLE_BAR_BUBBLE_REMOVED_BLOCKED.getId());
+ }
+
+ @Test
+ public void testRemoveBubbleFromBubbleBar_noLongerBubble_logEvent() {
+ mPositioner.setShowingInBubbleBar(true);
+
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ mBubbleData.setListener(mListener);
+
+ mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_NO_LONGER_BUBBLE);
+ assertThat(mUiEventLogger.numLogs()).isEqualTo(1);
+ assertThat(mUiEventLogger.eventId(0)).isEqualTo(
+ BubbleLogger.Event.BUBBLE_BAR_BUBBLE_REMOVED_BLOCKED.getId());
+ }
+
+ @Test
+ public void testRemoveBubbleFromBubbleBar_addToOverflow_logEvent() {
+ mPositioner.setShowingInBubbleBar(true);
+
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ mBubbleData.setListener(mListener);
+
+ mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_AGED);
+ assertThat(mUiEventLogger.numLogs()).isEqualTo(1);
+ assertThat(mUiEventLogger.eventId(0)).isEqualTo(
+ BubbleLogger.Event.BUBBLE_BAR_OVERFLOW_ADD_AGED.getId());
+ }
+
+ @Test
+ public void testRemoveBubble_notifCancelled_noLog() {
+ mPositioner.setShowingInBubbleBar(false);
+
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ mBubbleData.setListener(mListener);
+
+ mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_BLOCKED);
+ assertThat(mUiEventLogger.numLogs()).isEqualTo(0);
+ }
+
@Test
public void ifSuppress_hideFlyout() {
// Setup
@@ -325,6 +444,15 @@ public class BubbleDataTest extends ShellTestCase {
assertThat(update.updatedBubble.showFlyout()).isFalse();
}
+ @Test
+ public void getOrCreateBubble_withIntent_usesCorrectUser() {
+ Intent intent = new Intent();
+ intent.setPackage(mContext.getPackageName());
+ Bubble b = mBubbleData.getOrCreateBubble(intent, UserHandle.of(/* userId= */ 10));
+
+ assertThat(b.getUser().getIdentifier()).isEqualTo(10);
+ }
+
//
// Overflow
//
@@ -443,6 +571,22 @@ public class BubbleDataTest extends ShellTestCase {
assertThat(update.shouldShowEducation).isTrue();
}
+ /** Verifies that the update should contain the bubble bar location. */
+ @Test
+ public void test_shouldUpdateBubbleBarLocation() {
+ // Setup
+ mBubbleData.setListener(mListener);
+
+ // Test
+ mBubbleData.notificationEntryUpdated(mBubbleA1, /* suppressFlyout */ true, /* showInShade */
+ true, BubbleBarLocation.LEFT);
+
+ // Verify
+ verifyUpdateReceived();
+ BubbleData.Update update = mUpdateCaptor.getValue();
+ assertThat(update.mBubbleBarLocation).isEqualTo(BubbleBarLocation.LEFT);
+ }
+
/**
* Verifies that the update shouldn't show the user education, if the education is required but
* the bubble should auto-expand
@@ -659,7 +803,7 @@ public class BubbleDataTest extends ShellTestCase {
mBubbleData.setListener(mListener);
changeExpandedStateAtTime(true, 2000L);
- verifyZeroInteractions(mListener);
+ verifyNoMoreInteractions(mListener);
}
/**
@@ -1237,6 +1381,20 @@ public class BubbleDataTest extends ShellTestCase {
assertExpandedChangedTo(true);
}
+ @Test
+ public void setSelectedBubbleAndExpandStackWithLocation() {
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryA2, 2000);
+ mBubbleData.setListener(mListener);
+
+ mBubbleData.setSelectedBubbleAndExpandStack(mBubbleA1, BubbleBarLocation.LEFT);
+
+ verifyUpdateReceived();
+ assertSelectionChangedTo(mBubbleA1);
+ assertExpandedChangedTo(true);
+ assertLocationChangedTo(BubbleBarLocation.LEFT);
+ }
+
@Test
public void testShowOverflowChanged_hasOverflowBubbles() {
assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
@@ -1321,10 +1479,10 @@ public class BubbleDataTest extends ShellTestCase {
assertWithMessage("selectedBubble").that(update.selectedBubble).isEqualTo(bubble);
}
- private void assertSelectionCleared() {
+ private void assertLocationChangedTo(BubbleBarLocation location) {
BubbleData.Update update = mUpdateCaptor.getValue();
- assertWithMessage("selectionChanged").that(update.selectionChanged).isTrue();
- assertWithMessage("selectedBubble").that(update.selectedBubble).isNull();
+ assertWithMessage("locationChanged").that(update.mBubbleBarLocation)
+ .isEqualTo(location);
}
private void assertExpandedChangedTo(boolean expected) {
@@ -1415,15 +1573,13 @@ public class BubbleDataTest extends ShellTestCase {
sendUpdatedEntryAtTime(entry, postTime, true /* isTextChanged */);
}
- private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime,
- boolean textChanged) {
+ private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime, boolean textChanged) {
setPostTime(entry, postTime);
// BubbleController calls this:
Bubble b = mBubbleData.getOrCreateBubble(entry, null /* persistedBubble */);
b.setTextChangedForTest(textChanged);
// And then this
- mBubbleData.notificationEntryUpdated(b, false /* suppressFlyout*/,
- true /* showInShade */);
+ mBubbleData.notificationEntryUpdated(b, false /* suppressFlyout*/, true /* showInShade */);
}
private void changeExpandedStateAtTime(boolean shouldBeExpanded, long time) {
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
index afec1ee123..6786ba2389 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
@@ -16,24 +16,30 @@
package com.android.wm.shell.bubbles;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Notification;
import android.app.PendingIntent;
+import android.app.TaskInfo;
+import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.content.res.Resources;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -43,7 +49,7 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.bubbles.BubbleInfo;
+import com.android.wm.shell.shared.bubbles.BubbleInfo;
import org.junit.Before;
import org.junit.Test;
@@ -60,11 +66,17 @@ public class BubbleTest extends ShellTestCase {
@Mock
private StatusBarNotification mSbn;
@Mock
+ private NotificationListenerService.Ranking mRanking;
+ @Mock
private ShellExecutor mMainExecutor;
+ @Mock
+ private ShellExecutor mBgExecutor;
- private BubbleEntry mBubbleEntry;
private Bundle mExtras;
- private Bubble mBubble;
+
+ // This entry / bubble are set up with PendingIntent / Icon API for chat
+ private BubbleEntry mBubbleEntry;
+ private Bubble mChatBubble;
@Mock
private Bubbles.BubbleMetadataFlagListener mBubbleMetadataFlagListener;
@@ -81,11 +93,17 @@ public class BubbleTest extends ShellTestCase {
PendingIntent.getActivity(mContext, 0, target, PendingIntent.FLAG_MUTABLE),
Icon.createWithResource(mContext, R.drawable.bubble_ic_create_bubble))
.build();
+ ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext)
+ .setId("shortcutId")
+ .build();
when(mSbn.getNotification()).thenReturn(mNotif);
when(mNotif.getBubbleMetadata()).thenReturn(metadata);
when(mSbn.getKey()).thenReturn("mock");
- mBubbleEntry = new BubbleEntry(mSbn, null, true, false, false, false);
- mBubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor);
+ when(mRanking.getConversationShortcutInfo()).thenReturn(shortcutInfo);
+
+ mBubbleEntry = new BubbleEntry(mSbn, mRanking, true, false, false, false);
+ mChatBubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor,
+ mBgExecutor);
}
@Test
@@ -149,41 +167,115 @@ public class BubbleTest extends ShellTestCase {
@Test
public void testBubbleMetadataFlagListener_change_notified() {
- assertThat(mBubble.showInShade()).isTrue();
+ assertThat(mChatBubble.showInShade()).isTrue();
- mBubble.setSuppressNotification(true);
+ mChatBubble.setSuppressNotification(true);
- assertThat(mBubble.showInShade()).isFalse();
+ assertThat(mChatBubble.showInShade()).isFalse();
- verify(mBubbleMetadataFlagListener).onBubbleMetadataFlagChanged(mBubble);
+ verify(mBubbleMetadataFlagListener).onBubbleMetadataFlagChanged(mChatBubble);
}
@Test
public void testBubbleMetadataFlagListener_noChange_doesntNotify() {
- assertThat(mBubble.showInShade()).isTrue();
+ assertThat(mChatBubble.showInShade()).isTrue();
- mBubble.setSuppressNotification(false);
+ mChatBubble.setSuppressNotification(false);
verify(mBubbleMetadataFlagListener, never()).onBubbleMetadataFlagChanged(any());
}
@Test
- public void testBubbleIsConversation_hasConversationShortcut() {
- Bubble bubble = createBubbleWithShortcut();
- assertThat(bubble.getShortcutInfo()).isNotNull();
- assertThat(bubble.isConversation()).isTrue();
+ public void testBubbleType_conversationShortcut() {
+ Bubble bubble = createChatBubble(true /* useShortcut */);
+ assertThat(bubble.isChat()).isTrue();
}
@Test
- public void testBubbleIsConversation_hasNoShortcut() {
- Bubble bubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor);
- assertThat(bubble.getShortcutInfo()).isNull();
- assertThat(bubble.isConversation()).isFalse();
+ public void testBubbleType_conversationPendingIntent() {
+ Bubble bubble = createChatBubble(false /* useShortcut */);
+ assertThat(bubble.isChat()).isTrue();
+ }
+
+ @Test
+ public void testBubbleType_note() {
+ Bubble bubble = Bubble.createNotesBubble(createIntent(), UserHandle.of(0),
+ mock(Icon.class),
+ mMainExecutor, mBgExecutor);
+ assertThat(bubble.isNote()).isTrue();
+ }
+
+ @Test
+ public void testBubbleType_shortcut() {
+ ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext)
+ .setId("mockShortcutId")
+ .build();
+ Bubble bubble = Bubble.createShortcutBubble(shortcutInfo, mMainExecutor, mBgExecutor);
+ assertThat(bubble.isShortcut()).isTrue();
+ }
+
+ @Test
+ public void testBubbleType_intent() {
+ Bubble bubble = Bubble.createAppBubble(createIntent(), UserHandle.of(0),
+ mock(Icon.class),
+ mMainExecutor, mBgExecutor);
+ assertThat(bubble.isApp()).isTrue();
+ }
+
+ @Test
+ public void testBubbleType_taskId() {
+ TaskInfo info = mock(TaskInfo.class);
+ ComponentName componentName = mock(ComponentName.class);
+ when(componentName.getPackageName()).thenReturn(mContext.getPackageName());
+ info.taskId = 1;
+ info.baseActivity = componentName;
+ info.baseIntent = createIntent();
+ Bubble bubble = Bubble.createTaskBubble(info, UserHandle.of(0),
+ mock(Icon.class),
+ mMainExecutor, mBgExecutor);
+ assertThat(bubble.isApp()).isTrue();
+ assertThat(bubble.getIntent()).isNotNull();
+ }
+
+ @Test
+ public void testShowAppBadge_chat() {
+ Bubble bubble = createChatBubble(true /* useShortcut */);
+ assertThat(bubble.isChat()).isTrue();
+ assertThat(bubble.showAppBadge()).isTrue();
+ }
+
+ @Test
+ public void testShowAppBadge_note() {
+ Bubble bubble = Bubble.createNotesBubble(createIntent(), UserHandle.of(0),
+ mock(Icon.class),
+ mMainExecutor, mBgExecutor);
+ assertThat(bubble.isNote()).isTrue();
+ assertThat(bubble.showAppBadge()).isTrue();
+ }
+
+ @Test
+ public void testShowAppBadge_app() {
+ Bubble bubble = Bubble.createAppBubble(createIntent(), UserHandle.of(0),
+ mock(Icon.class),
+ mMainExecutor, mBgExecutor);
+ assertThat(bubble.isApp()).isTrue();
+ assertThat(bubble.showAppBadge()).isFalse();
+ }
+
+ @Test
+ public void testShowAppBadge_shortcut() {
+ ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext)
+ .setId("mockShortcutId")
+ .build();
+ Bubble bubble = Bubble.createShortcutBubble(shortcutInfo,
+ mMainExecutor, mBgExecutor);
+ assertThat(bubble.isShortcut()).isTrue();
+ assertThat(bubble.showAppBadge()).isTrue();
}
@Test
public void testBubbleAsBubbleBarBubble_withShortcut() {
- Bubble bubble = createBubbleWithShortcut();
+ Bubble bubble = createChatBubble(true /* useShortcut */);
BubbleInfo bubbleInfo = bubble.asBubbleBarBubble();
assertThat(bubble.getShortcutInfo()).isNotNull();
@@ -195,11 +287,11 @@ public class BubbleTest extends ShellTestCase {
}
@Test
- public void testBubbleAsBubbleBarBubble_withoutShortcut() {
+ public void testBubbleAsBubbleBarBubble_withIntent() {
Intent intent = new Intent(mContext, BubblesTestActivity.class);
intent.setPackage(mContext.getPackageName());
Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1 /* userId */),
- null /* icon */, mMainExecutor);
+ null /* icon */, mMainExecutor, mBgExecutor);
BubbleInfo bubbleInfo = bubble.asBubbleBarBubble();
assertThat(bubble.getShortcutInfo()).isNull();
@@ -209,12 +301,37 @@ public class BubbleTest extends ShellTestCase {
assertThat(bubbleInfo.getPackageName()).isEqualTo(bubble.getPackageName());
}
- private Bubble createBubbleWithShortcut() {
- ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext)
- .setId("mockShortcutId")
- .build();
- return new Bubble("mockKey", shortcutInfo, 10, Resources.ID_NULL,
- "mockTitle", 0 /* taskId */, "mockLocus", true /* isDismissible */,
- mMainExecutor, mBubbleMetadataFlagListener);
+ @Test
+ public void testCleanupTaskView() {
+ // Create a bubble with a task id
+ TaskInfo info = mock(TaskInfo.class);
+ info.taskId = 123;
+ info.baseActivity = new ComponentName(mContext, "SomeActivity");
+ Bubble bubble = Bubble.createTaskBubble(info, new UserHandle(1),
+ null /* icon */, mMainExecutor, mBgExecutor);
+ assertThat(bubble.getTaskId()).isEqualTo(123);
+
+ bubble.cleanupTaskView();
+ assertThat(bubble.getTaskId()).isEqualTo(INVALID_TASK_ID);
+ }
+
+ private Intent createIntent() {
+ Intent intent = new Intent(mContext, BubblesTestActivity.class);
+ intent.setPackage(mContext.getPackageName());
+ return intent;
+ }
+
+ private Bubble createChatBubble(boolean useShortcut) {
+ if (useShortcut) {
+ ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext)
+ .setId("mockShortcutId")
+ .build();
+ return new Bubble("mockKey", shortcutInfo, 10, Resources.ID_NULL,
+ "mockTitle", 0 /* taskId */, "mockLocus", true /* isDismissible */,
+ mMainExecutor, mBgExecutor, mBubbleMetadataFlagListener);
+ } else {
+ return new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor,
+ mBgExecutor);
+ }
}
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index 4a4c5e860b..e34c590261 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -33,12 +33,13 @@ import com.android.launcher3.icons.BubbleIconFactory
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestShellExecutor
-import com.android.wm.shell.WindowManagerShellWrapper
+import com.android.wm.shell.bubbles.appinfo.PackageManagerBubbleAppInfoProvider
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView
-import com.android.wm.shell.bubbles.properties.BubbleProperties
import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayImeController
import com.android.wm.shell.common.DisplayInsetsController
import com.android.wm.shell.common.FloatingContentCoordinator
+import com.android.wm.shell.common.HomeIntentProvider
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.common.TaskStackListenerImpl
@@ -57,6 +58,7 @@ import org.mockito.kotlin.doThrow
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
+import java.util.Optional
import java.util.concurrent.Executor
/** Tests for loading / inflating views & icons for a bubble. */
@@ -70,10 +72,11 @@ class BubbleViewInfoTest : ShellTestCase() {
private lateinit var bubble: Bubble
private lateinit var bubbleController: BubbleController
private lateinit var mainExecutor: ShellExecutor
+ private lateinit var bgExecutor: ShellExecutor
private lateinit var bubbleStackView: BubbleStackView
private lateinit var bubbleBarLayerView: BubbleBarLayerView
private lateinit var bubblePositioner: BubblePositioner
- private lateinit var expandedViewManager: BubbleExpandedViewManager
+ private lateinit var bubbleAppInfoProvider: PackageManagerBubbleAppInfoProvider
private val bubbleTaskViewFactory = BubbleTaskViewFactory {
BubbleTaskView(mock(), mock())
@@ -92,21 +95,32 @@ class BubbleViewInfoTest : ShellTestCase() {
)
mainExecutor = TestShellExecutor()
+ bgExecutor = TestShellExecutor()
val windowManager = context.getSystemService(WindowManager::class.java)
val shellInit = ShellInit(mainExecutor)
val shellCommandHandler = ShellCommandHandler()
- val shellController = ShellController(context, shellInit, shellCommandHandler,
- mock(), mainExecutor)
+ val shellController =
+ ShellController(
+ context,
+ shellInit,
+ shellCommandHandler,
+ mock(),
+ mock(),
+ mainExecutor,
+ )
bubblePositioner = BubblePositioner(context, windowManager)
+ val bubbleLogger = mock()
val bubbleData =
BubbleData(
context,
- mock(),
+ bubbleLogger,
bubblePositioner,
BubbleEducationController(context),
- mainExecutor
+ mainExecutor,
+ bgExecutor
)
val surfaceSynchronizer = { obj: Runnable -> obj.run() }
+ bubbleAppInfoProvider = PackageManagerBubbleAppInfoProvider()
bubbleController =
BubbleController(
@@ -118,12 +132,14 @@ class BubbleViewInfoTest : ShellTestCase() {
surfaceSynchronizer,
FloatingContentCoordinator(),
mock(),
+ mock(),
mock(),
windowManager,
- WindowManagerShellWrapper(mainExecutor),
+ mock(),
+ mock(),
mock(),
mock(),
- mock(),
+ bubbleLogger,
mock(),
ShellTaskOrganizer(mainExecutor),
bubblePositioner,
@@ -132,12 +148,15 @@ class BubbleViewInfoTest : ShellTestCase() {
null,
mainExecutor,
mock(),
- mock(),
+ bgExecutor,
mock(),
mock(),
mock(),
mock(),
- mock()
+ BubbleResizabilityChecker(),
+ mock(),
+ bubbleAppInfoProvider,
+ { Optional.empty() },
)
val bubbleStackViewManager = BubbleStackViewManager.fromBubbleController(bubbleController)
@@ -152,8 +171,7 @@ class BubbleViewInfoTest : ShellTestCase() {
bubbleController,
mainExecutor
)
- expandedViewManager = BubbleExpandedViewManager.fromBubbleController(bubbleController)
- bubbleBarLayerView = BubbleBarLayerView(context, bubbleController, bubbleData)
+ bubbleBarLayerView = BubbleBarLayerView(context, bubbleController, bubbleData, bubbleLogger)
}
@Test
@@ -162,12 +180,12 @@ class BubbleViewInfoTest : ShellTestCase() {
val info =
BubbleViewInfoTask.BubbleViewInfo.populate(
context,
- expandedViewManager,
bubbleTaskViewFactory,
bubblePositioner,
bubbleStackView,
iconFactory,
bubble,
+ bubbleAppInfoProvider,
false /* skipInflation */
)
assertThat(info!!).isNotNull()
@@ -190,12 +208,11 @@ class BubbleViewInfoTest : ShellTestCase() {
val info =
BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(
context,
- expandedViewManager,
bubbleTaskViewFactory,
- bubblePositioner,
bubbleBarLayerView,
iconFactory,
bubble,
+ bubbleAppInfoProvider,
false /* skipInflation */
)
assertThat(info!!).isNotNull()
@@ -226,12 +243,11 @@ class BubbleViewInfoTest : ShellTestCase() {
val info =
BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(
context,
- expandedViewManager,
bubbleTaskViewFactory,
- bubblePositioner,
bubbleBarLayerView,
iconFactory,
bubble,
+ bubbleAppInfoProvider,
true /* skipInflation */
)
assertThat(info).isNotNull()
@@ -256,7 +272,7 @@ class BubbleViewInfoTest : ShellTestCase() {
"mockLocus",
true /* isDismissible */,
mainExecutor,
- metadataFlagListener
- )
+ bgExecutor,
+ metadataFlagListener)
}
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java
index c4b9c9ba43..b4b9679129 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java
@@ -25,7 +25,6 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.floatThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import android.os.SystemClock;
import android.testing.AndroidTestingRunner;
@@ -84,7 +83,7 @@ public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase {
verify(mMotionEventListener).onMove(0, -600);
// Check that velocity up is about 5000
verify(mMotionEventListener).onUp(eq(0f), floatThat(f -> Math.round(f) == -5000));
- verifyZeroInteractions(mMotionEventListener);
+ verifyNoMoreInteractions(mMotionEventListener);
verify(mInterceptTouchRunnable).run();
}
@@ -94,8 +93,8 @@ public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase {
mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 100));
mMotionEventHandler.onMotionEvent(newEvent(ACTION_UP, 0, 100));
- verifyZeroInteractions(mMotionEventListener);
- verifyZeroInteractions(mInterceptTouchRunnable);
+ verifyNoMoreInteractions(mMotionEventListener);
+ verifyNoMoreInteractions(mInterceptTouchRunnable);
}
@Test
@@ -107,7 +106,7 @@ public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase {
verify(mMotionEventListener).onDown(0, 990);
verify(mMotionEventListener).onMove(100, 0);
verify(mMotionEventListener).onUp(0, 0);
- verifyZeroInteractions(mMotionEventListener);
+ verifyNoMoreInteractions(mMotionEventListener);
verify(mInterceptTouchRunnable).run();
}
@@ -119,7 +118,7 @@ public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase {
verify(mMotionEventListener).onDown(0, 990);
verifyNoMoreInteractions(mMotionEventListener);
- verifyZeroInteractions(mInterceptTouchRunnable);
+ verifyNoMoreInteractions(mInterceptTouchRunnable);
}
@Test
@@ -129,7 +128,7 @@ public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase {
verify(mMotionEventListener).onDown(0, 990);
verify(mMotionEventListener).onCancel();
verifyNoMoreInteractions(mMotionEventListener);
- verifyZeroInteractions(mInterceptTouchRunnable);
+ verifyNoMoreInteractions(mInterceptTouchRunnable);
}
private MotionEvent newEvent(int actionDown, float x, float y) {
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
index 60f1d271c3..d829c6afb7 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -104,6 +104,7 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
@Test
public void testExpansionAndCollapse() throws Exception {
expand();
+ waitForAnimation();
testBubblesInCorrectExpandedPositions();
waitForMainThread();
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java
index d38b848fbb..bf03834c70 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java
@@ -15,10 +15,12 @@
*/
package com.android.wm.shell.bubbles.bar;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
-import android.graphics.drawable.ColorDrawable;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.graphics.Color;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -47,10 +49,9 @@ public class BubbleBarHandleViewTest extends ShellTestCase {
public void testUpdateHandleColor_lightBg() {
mHandleView.updateHandleColor(false /* isRegionDark */, false /* animated */);
- assertTrue(mHandleView.getClipToOutline());
- assertTrue(mHandleView.getBackground() instanceof ColorDrawable);
- ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground();
- assertEquals(bgDrawable.getColor(),
+ assertFalse(mHandleView.getClipToOutline());
+ int handleColor = mHandleView.mHandlePaint.getColor();
+ assertEquals(handleColor,
ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_dark));
}
@@ -58,10 +59,23 @@ public class BubbleBarHandleViewTest extends ShellTestCase {
public void testUpdateHandleColor_darkBg() {
mHandleView.updateHandleColor(true /* isRegionDark */, false /* animated */);
- assertTrue(mHandleView.getClipToOutline());
- assertTrue(mHandleView.getBackground() instanceof ColorDrawable);
- ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground();
- assertEquals(bgDrawable.getColor(),
+ assertFalse(mHandleView.getClipToOutline());
+ int handleColor = mHandleView.mHandlePaint.getColor();
+ assertEquals(handleColor,
+ ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_light));
+ }
+
+ @Test
+ public void testSetHandleInitialColor_beforeUpdateHandleColor_updatesColor() {
+ mHandleView.setHandleInitialColor(Color.RED);
+ assertThat(mHandleView.getHandleColor()).isEqualTo(Color.RED);
+ }
+
+ @Test
+ public void testSetHandleInitialColor_afterUpdateHandleColor_doesNotUpdateColor() {
+ mHandleView.updateHandleColor(/* isRegionDark= */ true, /* animated= */ false);
+ mHandleView.setHandleInitialColor(Color.RED);
+ assertThat(mHandleView.getHandleColor()).isEqualTo(
ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_light));
}
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java
index f8ee300e41..1472464e81 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java
@@ -22,13 +22,14 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.content.Context;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
@@ -41,7 +42,7 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class DevicePostureControllerTest {
+public class DevicePostureControllerTest extends ShellTestCase {
@Mock
private Context mContext;
@@ -104,6 +105,6 @@ public class DevicePostureControllerTest {
int sameDevicePosture = mDevicePostureCaptor.getValue();
mDevicePostureController.onDevicePostureChanged(sameDevicePosture);
- verifyZeroInteractions(mOnDevicePostureChangedListener);
+ verifyNoMoreInteractions(mOnDevicePostureChangedListener);
}
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/common/DisplayChangeControllerTests.java b/wmshell/tests/unittest/src/com/android/wm/shell/common/DisplayChangeControllerTests.java
index b8aa8e7cbc..84c742626f 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/common/DisplayChangeControllerTests.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/common/DisplayChangeControllerTests.java
@@ -19,10 +19,23 @@ package com.android.wm.shell.common;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.app.WindowConfiguration;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.Size;
+import android.view.IDisplayChangeWindowCallback;
import android.view.IWindowManager;
+import android.view.Surface;
+import android.window.DisplayAreaInfo;
+import android.window.DisplayAreaOrganizer;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -49,16 +62,104 @@ public class DisplayChangeControllerTests extends ShellTestCase {
private @Mock IWindowManager mWM;
private @Mock ShellInit mShellInit;
private @Mock ShellExecutor mMainExecutor;
+ private @Mock DisplayController mDisplayController;
+
+ private @Mock DisplayLayout mMockDisplayLayout;
+ private @Mock DisplayChangeController.OnDisplayChangingListener mMockOnDisplayChangingListener;
+ private @Mock IDisplayChangeWindowCallback mMockDisplayChangeWindowCallback;
private DisplayChangeController mController;
+ private static final int DISPLAY_ID = 0;
+ private static final int START_ROTATION = Surface.ROTATION_0;
+ private static final int END_ROTATION = Surface.ROTATION_90;
+ private static final Size DISPLAY_START_SIZE = new Size(100, 100);
+ private static final Size DISPLAY_END_SIZE = new Size(200, 200);
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mController = spy(new DisplayChangeController(mWM, mShellInit, mMainExecutor));
+ mController = spy(new DisplayChangeController(mDisplayController, mWM, mShellInit,
+ mMainExecutor));
+ mController.addDisplayChangeListener(mMockOnDisplayChangingListener);
}
@Test
public void instantiate_addInitCallback() {
verify(mShellInit, times(1)).addInitCallback(any(), any());
}
+
+ @Test
+ public void onDisplayChange_sizeChange_updateDisplayLayout_thenContinueDisplayChange() throws
+ RemoteException {
+ // set up the init display layout
+ when(mMockDisplayLayout.width()).thenReturn(DISPLAY_START_SIZE.getWidth());
+ when(mMockDisplayLayout.height()).thenReturn(DISPLAY_START_SIZE.getHeight());
+ when(mDisplayController.getDisplayLayout(eq(DISPLAY_ID))).thenReturn(mMockDisplayLayout);
+
+ // set up the new display area info
+ final Rect startBounds = new Rect(0, 0,
+ DISPLAY_START_SIZE.getWidth(), DISPLAY_START_SIZE.getHeight());
+ final Rect endBounds = new Rect(0, 0,
+ DISPLAY_END_SIZE.getWidth(), DISPLAY_END_SIZE.getHeight());
+
+ // create a new display area info for size change
+ final DisplayAreaInfo displayAreaInfo = createDisplayAreaInfo(DISPLAY_ID, endBounds);
+
+ mController.onDisplayChange(DISPLAY_ID, START_ROTATION, START_ROTATION, displayAreaInfo,
+ mMockDisplayChangeWindowCallback);
+
+ // verify that local display layouts are updated
+ verify(mDisplayController, times(1)).updateDisplayLayout(eq(DISPLAY_ID),
+ eq(startBounds), eq(endBounds), eq(START_ROTATION), eq(START_ROTATION));
+
+ // verify that display changing callbacks are dispatched
+ verify(mMockOnDisplayChangingListener, times(1)).onDisplayChange(
+ eq(DISPLAY_ID), eq(START_ROTATION), eq(START_ROTATION), eq(displayAreaInfo),
+ any(WindowContainerTransaction.class));
+
+ verify(mMockDisplayChangeWindowCallback, times(1))
+ .continueDisplayChange(any(WindowContainerTransaction.class));
+ }
+
+ @Test
+ public void onDisplayChange_rotationChange_updateDisplayLayout_thenContinueDisplayChange()
+ throws RemoteException {
+ // set up the init display layout
+ when(mMockDisplayLayout.width()).thenReturn(DISPLAY_START_SIZE.getWidth());
+ when(mMockDisplayLayout.height()).thenReturn(DISPLAY_START_SIZE.getHeight());
+ when(mDisplayController.getDisplayLayout(eq(DISPLAY_ID))).thenReturn(mMockDisplayLayout);
+
+ // set up the new display area info
+ final Rect startBounds = new Rect(0, 0,
+ DISPLAY_START_SIZE.getWidth(), DISPLAY_START_SIZE.getHeight());
+
+ // create a new display area info for size change
+ final DisplayAreaInfo displayAreaInfo = createDisplayAreaInfo(DISPLAY_ID, startBounds);
+
+ mController.onDisplayChange(DISPLAY_ID, START_ROTATION, END_ROTATION, displayAreaInfo,
+ mMockDisplayChangeWindowCallback);
+
+ // verify that local display layouts are updated
+ verify(mDisplayController, times(1)).updateDisplayLayout(eq(DISPLAY_ID),
+ eq(startBounds), eq(startBounds), eq(START_ROTATION), eq(END_ROTATION));
+
+ // verify that display changing callbacks are dispatched
+ verify(mMockOnDisplayChangingListener, times(1)).onDisplayChange(
+ eq(DISPLAY_ID), eq(START_ROTATION), eq(END_ROTATION), eq(displayAreaInfo),
+ any(WindowContainerTransaction.class));
+
+ verify(mMockDisplayChangeWindowCallback, times(1))
+ .continueDisplayChange(any(WindowContainerTransaction.class));
+ }
+
+ private DisplayAreaInfo createDisplayAreaInfo(int displayId, Rect endBounds) {
+ final WindowContainerToken mMockToken = mock(WindowContainerToken.class);
+ final WindowConfiguration windowConfiguration = new WindowConfiguration();
+ windowConfiguration.setBounds(endBounds);
+
+ final DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(mMockToken,
+ displayId, DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER);
+ displayAreaInfo.configuration.windowConfiguration.setTo(windowConfiguration);
+ return displayAreaInfo;
+ }
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java b/wmshell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java
index 1e5e153fdf..3f58da719b 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java
@@ -16,26 +16,54 @@
package com.android.wm.shell.common;
+import static com.android.server.display.feature.flags.Flags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
-import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.RectF;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayTopology;
+import android.os.RemoteException;
+import android.platform.test.annotations.EnableFlags;
+import android.testing.TestableContext;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayAdjustments;
+import android.view.IDisplayWindowListener;
import android.view.IWindowManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestSyncExecutor;
+import com.android.wm.shell.shared.desktopmode.FakeDesktopState;
import com.android.wm.shell.sysui.ShellInit;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.function.Consumer;
+
/**
* Tests for the display controller.
*
@@ -45,21 +73,199 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DisplayControllerTests extends ShellTestCase {
-
- private @Mock Context mContext;
- private @Mock IWindowManager mWM;
- private @Mock ShellInit mShellInit;
- private @Mock ShellExecutor mMainExecutor;
+ @Mock private IWindowManager mWM;
+ @Mock private ShellInit mShellInit;
+ @Mock private DisplayManager mDisplayManager;
+ @Mock private DisplayTopology mMockTopology;
+ @Mock private DisplayController.OnDisplaysChangedListener mListener;
+ private TestSyncExecutor mMainExecutor;
+ private IDisplayWindowListener mDisplayContainerListener;
+ private Consumer mCapturedTopologyListener;
+ private Display mMockDisplay0;
+ private Display mMockDisplay1;
private DisplayController mController;
+ private FakeDesktopState mDesktopState;
+ private static final int DISPLAY_ID_0 = 0;
+ private static final int DISPLAY_ID_1 = 1;
+ private static final RectF DISPLAY_ABS_BOUNDS_0 = new RectF(10, 10, 20, 20);
+ private static final RectF DISPLAY_ABS_BOUNDS_1 = new RectF(11, 11, 22, 22);
+ private AutoCloseable mMocksInit = null;
@Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mController = new DisplayController(mContext, mWM, mShellInit, mMainExecutor);
+ public void setUp() throws RemoteException {
+ mDesktopState = new FakeDesktopState();
+ mMocksInit = MockitoAnnotations.openMocks(this);
+
+ mContext = spy(new TestableContext(
+ androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()
+ .getContext(), null));
+
+ mMainExecutor = new TestSyncExecutor();
+ mController = new DisplayController(
+ mContext, mWM, mShellInit, mMainExecutor, mDisplayManager, mDesktopState);
+
+ mMockDisplay0 = mock(Display.class);
+ when(mMockDisplay0.getDisplayAdjustments()).thenReturn(
+ new DisplayAdjustments(new Configuration()));
+ when(mMockDisplay0.getDisplayId()).thenReturn(DISPLAY_ID_0);
+ when(mDisplayManager.getDisplay(eq(DISPLAY_ID_0))).thenReturn(mMockDisplay0);
+
+ mMockDisplay1 = mock(Display.class);
+ when(mMockDisplay1.getDisplayAdjustments()).thenReturn(
+ new DisplayAdjustments(new Configuration()));
+ when(mMockDisplay1.getDisplayId()).thenReturn(DISPLAY_ID_1);
+ when(mDisplayManager.getDisplay(eq(DISPLAY_ID_1))).thenReturn(mMockDisplay1);
+
+ when(mDisplayManager.getDisplayTopology()).thenReturn(mMockTopology);
+ doAnswer(invocation -> {
+ mDisplayContainerListener = invocation.getArgument(0);
+ return new int[]{DISPLAY_ID_0};
+ }).when(mWM).registerDisplayWindowListener(any());
+ doAnswer(invocation -> {
+ mCapturedTopologyListener = invocation.getArgument(1);
+ return null;
+ }).when(mDisplayManager).registerTopologyListener(any(), any());
+ when(mWM.isEligibleForDesktopMode(anyInt())).thenReturn(false);
+ SparseArray absoluteBounds = new SparseArray<>();
+ absoluteBounds.put(DISPLAY_ID_0, DISPLAY_ABS_BOUNDS_0);
+ absoluteBounds.put(DISPLAY_ID_1, DISPLAY_ABS_BOUNDS_1);
+ when(mMockTopology.getAbsoluteBounds()).thenReturn(absoluteBounds);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mMocksInit != null) {
+ mMocksInit.close();
+ mMocksInit = null;
+ }
}
@Test
public void instantiateController_addInitCallback() {
verify(mShellInit, times(1)).addInitCallback(any(), eq(mController));
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
+ public void onInit_canEnterDesktopMode_registerListeners() throws RemoteException {
+ mDesktopState.setCanEnterDesktopMode(true);
+
+ mController.onInit();
+
+ assertNotNull(mController.getDisplayContext(DISPLAY_ID_0));
+ verify(mWM).registerDisplayWindowListener(any());
+ verify(mDisplayManager).registerTopologyListener(eq(mMainExecutor), any());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
+ public void onInit_canNotEnterDesktopMode_onlyRegisterDisplayWindowListener()
+ throws RemoteException {
+ mDesktopState.setCanEnterDesktopMode(false);
+
+ mController.onInit();
+
+ assertNotNull(mController.getDisplayContext(DISPLAY_ID_0));
+ verify(mWM).registerDisplayWindowListener(any());
+ verify(mDisplayManager, never()).registerTopologyListener(any(), any());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
+ public void addDisplayWindowListener_notifiesExistingDisplaysAndTopology() {
+ mDesktopState.setCanEnterDesktopMode(true);
+
+ mController.onInit();
+ mController.addDisplayWindowListener(mListener);
+
+ verify(mListener).onDisplayAdded(eq(DISPLAY_ID_0));
+ verify(mListener).onTopologyChanged(eq(mMockTopology));
+ }
+
+ @Test
+ public void onDisplayAddedAndRemoved_updatesDisplayContexts() throws RemoteException {
+ mController.onInit();
+ mController.addDisplayWindowListener(mListener);
+
+ mDisplayContainerListener.onDisplayAdded(DISPLAY_ID_1);
+
+ verify(mListener).onDisplayAdded(eq(DISPLAY_ID_0));
+ verify(mListener).onDisplayAdded(eq(DISPLAY_ID_1));
+ assertNotNull(mController.getDisplayContext(DISPLAY_ID_1));
+ verify(mContext).createDisplayContext(eq(mMockDisplay1));
+
+ mDisplayContainerListener.onDisplayRemoved(DISPLAY_ID_1);
+
+ assertNull(mController.getDisplayContext(DISPLAY_ID_1));
+ verify(mListener).onDisplayRemoved(eq(DISPLAY_ID_1));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
+ public void onDisplayTopologyChanged_updateDisplayLayout() throws RemoteException {
+ mDesktopState.setCanEnterDesktopMode(true);
+ mController.onInit();
+ mController.addDisplayWindowListener(mListener);
+ mDisplayContainerListener.onDisplayAdded(DISPLAY_ID_1);
+
+ mCapturedTopologyListener.accept(mMockTopology);
+
+ assertEquals(DISPLAY_ABS_BOUNDS_0, mController.getDisplayLayout(DISPLAY_ID_0)
+ .globalBoundsDp());
+ assertEquals(DISPLAY_ABS_BOUNDS_1, mController.getDisplayLayout(DISPLAY_ID_1)
+ .globalBoundsDp());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
+ public void onDisplayTopologyChanged_topologyBeforeDisplayAdded_appliesBoundsOnAdd()
+ throws RemoteException {
+ mDesktopState.setCanEnterDesktopMode(true);
+ mController.onInit();
+ mController.addDisplayWindowListener(mListener);
+
+ mCapturedTopologyListener.accept(mMockTopology);
+
+ assertNull(mController.getDisplayLayout(DISPLAY_ID_1));
+
+ mDisplayContainerListener.onDisplayAdded(DISPLAY_ID_1);
+
+ assertEquals(DISPLAY_ABS_BOUNDS_0,
+ mController.getDisplayLayout(DISPLAY_ID_0).globalBoundsDp());
+ assertEquals(DISPLAY_ABS_BOUNDS_1,
+ mController.getDisplayLayout(DISPLAY_ID_1).globalBoundsDp());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
+ public void onDisplayConfigurationChanged_reInitDisplayLayout()
+ throws RemoteException {
+ mDesktopState.setCanEnterDesktopMode(true);
+ mController.onInit();
+ mController.addDisplayWindowListener(mListener);
+
+ mCapturedTopologyListener.accept(mMockTopology);
+
+ DisplayLayout displayLayoutBefore = mController.getDisplayLayout(DISPLAY_ID_0);
+ mDisplayContainerListener.onDisplayConfigurationChanged(DISPLAY_ID_0, new Configuration());
+ DisplayLayout displayLayoutAfter = mController.getDisplayLayout(DISPLAY_ID_0);
+
+ assertNotSame(displayLayoutBefore, displayLayoutAfter);
+ assertEquals(DISPLAY_ABS_BOUNDS_0,
+ mController.getDisplayLayout(DISPLAY_ID_0).globalBoundsDp());
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT)
+ public void onEligibleForDesktopModeChanged_recreateLayout() throws RemoteException {
+ mController.onInit();
+ mDesktopState.getOverrideDesktopModeSupportPerDisplay().put(DISPLAY_ID_1, false);
+ mDisplayContainerListener.onDisplayAdded(DISPLAY_ID_1);
+ mDesktopState.getOverrideDesktopModeSupportPerDisplay().put(DISPLAY_ID_1, true);
+ DisplayLayout initialLayout = mController.getDisplayLayout(DISPLAY_ID_1);
+
+ mDisplayContainerListener.onDesktopModeEligibleChanged(DISPLAY_ID_1);
+
+ assertNotSame(initialLayout, mController.getDisplayLayout(DISPLAY_ID_1));
+ }
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 2c0aa12f22..3a378de4f8 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -21,19 +21,21 @@ import static android.view.InsetsSource.ID_IME;
import static android.view.Surface.ROTATION_0;
import static android.view.WindowInsets.Type.ime;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.graphics.Insets;
import android.graphics.Point;
+import android.os.Looper;
+import android.view.IWindowManager;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
@@ -43,6 +45,7 @@ import android.view.inputmethod.ImeTracker;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
@@ -60,11 +63,13 @@ import java.util.concurrent.Executor;
*/
@SmallTest
public class DisplayImeControllerTest extends ShellTestCase {
-
@Mock
private SurfaceControl.Transaction mT;
@Mock
private ShellInit mShellInit;
+ @Mock
+ private IWindowManager mWm;
+ private DisplayImeController mDisplayImeController;
private DisplayImeController.PerDisplay mPerDisplay;
private Executor mExecutor;
@@ -72,7 +77,8 @@ public class DisplayImeControllerTest extends ShellTestCase {
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mExecutor = spy(Runnable::run);
- mPerDisplay = new DisplayImeController(null, mShellInit, null, null, new TransactionPool() {
+ mDisplayImeController = new DisplayImeController(mWm, mShellInit, null, null,
+ new TransactionPool() {
@Override
public SurfaceControl.Transaction acquire() {
return mT;
@@ -83,8 +89,10 @@ public class DisplayImeControllerTest extends ShellTestCase {
}
}, mExecutor) {
@Override
- void removeImeSurface() { }
- }.new PerDisplay(DEFAULT_DISPLAY, ROTATION_0);
+ void removeImeSurface(int displayId) {
+ }
+ };
+ mPerDisplay = mDisplayImeController.new PerDisplay(DEFAULT_DISPLAY, ROTATION_0);
}
@Test
@@ -94,47 +102,33 @@ public class DisplayImeControllerTest extends ShellTestCase {
@Test
public void insetsControlChanged_schedulesNoWorkOnExecutor() {
+ Looper.prepare();
mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
- verifyZeroInteractions(mExecutor);
+ verifyNoMoreInteractions(mExecutor);
}
@Test
public void insetsChanged_schedulesNoWorkOnExecutor() {
+ Looper.prepare();
mPerDisplay.insetsChanged(insetsStateWithIme(false));
- verifyZeroInteractions(mExecutor);
+ verifyNoMoreInteractions(mExecutor);
}
@Test
public void showInsets_schedulesNoWorkOnExecutor() {
- mPerDisplay.showInsets(ime(), true /* fromIme */, ImeTracker.Token.empty());
- verifyZeroInteractions(mExecutor);
+ mPerDisplay.showInsets(ime(), ImeTracker.Token.empty());
+ verifyNoMoreInteractions(mExecutor);
}
@Test
public void hideInsets_schedulesNoWorkOnExecutor() {
- mPerDisplay.hideInsets(ime(), true /* fromIme */, ImeTracker.Token.empty());
- verifyZeroInteractions(mExecutor);
- }
-
- @Test
- public void reappliesVisibilityToChangedLeash() {
- verifyZeroInteractions(mT);
- mPerDisplay.mImeShowing = true;
-
- mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
-
- assertFalse(mPerDisplay.mImeShowing);
- verify(mT).hide(any());
-
- mPerDisplay.mImeShowing = true;
- mPerDisplay.insetsControlChanged(insetsStateWithIme(true), insetsSourceControl());
-
- assertTrue(mPerDisplay.mImeShowing);
- verify(mT).show(any());
+ mPerDisplay.hideInsets(ime(), ImeTracker.Token.empty());
+ verifyNoMoreInteractions(mExecutor);
}
@Test
public void insetsControlChanged_updateImeSourceControl() {
+ Looper.prepare();
mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
assertNotNull(mPerDisplay.mImeSourceControl);
@@ -142,6 +136,18 @@ public class DisplayImeControllerTest extends ShellTestCase {
assertNull(mPerDisplay.mImeSourceControl);
}
+ @Test
+ public void setImeInputTargetRequestedVisibility_invokeOnImeRequested() {
+ var mockPp = mock(DisplayImeController.ImePositionProcessor.class);
+ mDisplayImeController.addPositionProcessor(mockPp);
+
+ mPerDisplay.setImeInputTargetRequestedVisibility(true, null /* statsToken */);
+ verify(mockPp).onImeRequested(anyInt(), eq(true));
+
+ mPerDisplay.setImeInputTargetRequestedVisibility(false, null /* statsToken */);
+ verify(mockPp).onImeRequested(anyInt(), eq(false));
+ }
+
private InsetsSourceControl[] insetsSourceControl() {
return new InsetsSourceControl[]{
new InsetsSourceControl(
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
index 669e433ba3..e35588276a 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.common;
import static android.view.Display.DEFAULT_DISPLAY;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -119,10 +120,8 @@ public class DisplayInsetsControllerTest extends ShellTestCase {
WindowInsets.Type.defaultVisible());
mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsChanged(null);
mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsControlChanged(null, null);
- mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false,
- ImeTracker.Token.empty());
- mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).hideInsets(0, false,
- ImeTracker.Token.empty());
+ mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, ImeTracker.Token.empty());
+ mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).hideInsets(0, ImeTracker.Token.empty());
mExecutor.flushAll();
assertTrue(defaultListener.topFocusedWindowChangedCount == 1);
@@ -141,10 +140,8 @@ public class DisplayInsetsControllerTest extends ShellTestCase {
WindowInsets.Type.defaultVisible());
mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsChanged(null);
mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsControlChanged(null, null);
- mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false,
- ImeTracker.Token.empty());
- mInsetsControllersByDisplayId.get(SECOND_DISPLAY).hideInsets(0, false,
- ImeTracker.Token.empty());
+ mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, ImeTracker.Token.empty());
+ mInsetsControllersByDisplayId.get(SECOND_DISPLAY).hideInsets(0, ImeTracker.Token.empty());
mExecutor.flushAll();
assertTrue(defaultListener.topFocusedWindowChangedCount == 1);
@@ -160,6 +157,19 @@ public class DisplayInsetsControllerTest extends ShellTestCase {
assertTrue(secondListener.hideInsetsCount == 1);
}
+ @Test
+ public void testGlobalListenerCallback() throws RemoteException {
+ TrackedListener globalListener = new TrackedListener();
+ addDisplay(SECOND_DISPLAY);
+ mController.addGlobalInsetsChangedListener(globalListener);
+
+ mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsChanged(null);
+ mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsChanged(null);
+ mExecutor.flushAll();
+
+ assertEquals(2, globalListener.insetsChangedCount);
+ }
+
private void addDisplay(int displayId) throws RemoteException {
mController.onDisplayAdded(displayId);
verify(mWm, times(mInsetsControllersByDisplayId.size() + 1))
@@ -203,12 +213,12 @@ public class DisplayInsetsControllerTest extends ShellTestCase {
}
@Override
- public void showInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {
+ public void showInsets(int types, @Nullable ImeTracker.Token statsToken) {
showInsetsCount++;
}
@Override
- public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {
+ public void hideInsets(int types, @Nullable ImeTracker.Token statsToken) {
hideInsetsCount++;
}
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java
index d467b399eb..b0a455d1bc 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java
@@ -33,7 +33,9 @@ import static org.mockito.Mockito.when;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Insets;
+import android.graphics.PointF;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
@@ -58,6 +60,7 @@ import org.mockito.quality.Strictness;
@SmallTest
public class DisplayLayoutTest extends ShellTestCase {
private MockitoSession mMockitoSession;
+ private static final float DELTA = 0.1f; // Constant for assertion delta
@Before
public void setup() {
@@ -130,6 +133,39 @@ public class DisplayLayoutTest extends ShellTestCase {
assertEquals(new Rect(40, 0, 60, 0), dl.nonDecorInsets());
}
+ @Test
+ public void testDpPxConversion() {
+ int px = 100;
+ float dp = 53.33f;
+ int xPx = 100;
+ int yPx = 200;
+ float xDp = 164.33f;
+ float yDp = 328.66f;
+
+ Resources res = createResources(40, 50, false);
+ DisplayInfo info = createDisplayInfo(1000, 1500, 0, ROTATION_0);
+ DisplayLayout dl = new DisplayLayout(info, res, false, false);
+ dl.setGlobalBoundsDp(new RectF(111f, 222f, 300f, 400f));
+
+ // Test pxToDp
+ float resultDp = dl.pxToDp(px);
+ assertEquals(dp, resultDp, DELTA);
+
+ // Test dpToPx
+ float resultPx = dl.dpToPx(dp);
+ assertEquals(px, resultPx, DELTA);
+
+ // Test localPxToGlobalDp
+ PointF resultGlobalDp = dl.localPxToGlobalDp(xPx, yPx);
+ assertEquals(xDp, resultGlobalDp.x, DELTA);
+ assertEquals(yDp, resultGlobalDp.y, DELTA);
+
+ // Test globalDpToLocalPx
+ PointF resultLocalPx = dl.globalDpToLocalPx(xDp, yDp);
+ assertEquals(xPx, resultLocalPx.x, DELTA);
+ assertEquals(yPx, resultLocalPx.y, DELTA);
+ }
+
private Resources createResources(int navLand, int navPort, boolean navMoves) {
Configuration cfg = new Configuration();
cfg.uiMode = UI_MODE_TYPE_NORMAL;
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt b/wmshell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt
index bec91e910c..6b0c390ac2 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt
@@ -33,8 +33,6 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.eq
import org.mockito.kotlin.any
-import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.doThrow
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
@@ -80,17 +78,19 @@ class MultiInstanceHelperTest : ShellTestCase() {
@Test
fun supportsMultiInstanceSplit_inStaticAllowList() {
val allowList = arrayOf(TEST_PACKAGE)
- val helper = MultiInstanceHelper(mContext, context.packageManager, allowList, true)
+ val helper = MultiInstanceHelper(mContext, context.packageManager, allowList,
+ mock(), mock(), true)
val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY)
- assertEquals(true, helper.supportsMultiInstanceSplit(component))
+ assertEquals(true, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID))
}
@Test
fun supportsMultiInstanceSplit_notInStaticAllowList() {
val allowList = arrayOf(TEST_PACKAGE)
- val helper = MultiInstanceHelper(mContext, context.packageManager, allowList, true)
+ val helper = MultiInstanceHelper(mContext, context.packageManager, allowList,
+ mock(), mock(), true)
val component = ComponentName(TEST_NOT_ALLOWED_PACKAGE, TEST_ACTIVITY)
- assertEquals(false, helper.supportsMultiInstanceSplit(component))
+ assertEquals(false, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID))
}
@Test
@@ -99,17 +99,17 @@ class MultiInstanceHelperTest : ShellTestCase() {
val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY)
val pm = mock()
val activityProp = PackageManager.Property("", true, "", "")
- whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
- eq(component)))
+ whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER_ID)))
.thenReturn(activityProp)
val appProp = PackageManager.Property("", false, "", "")
- whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
- eq(component.packageName)))
+ whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName), eq(null), eq(TEST_OTHER_USER_ID)))
.thenReturn(appProp)
- val helper = MultiInstanceHelper(mContext, pm, emptyArray(), true)
+ val helper = MultiInstanceHelper(mContext, pm, emptyArray(), mock(), mock(), true)
// Expect activity property to override application property
- assertEquals(true, helper.supportsMultiInstanceSplit(component))
+ assertEquals(true, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID))
}
@Test
@@ -118,17 +118,17 @@ class MultiInstanceHelperTest : ShellTestCase() {
val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY)
val pm = mock()
val activityProp = PackageManager.Property("", false, "", "")
- whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
- eq(component)))
+ whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER_ID)))
.thenReturn(activityProp)
val appProp = PackageManager.Property("", true, "", "")
- whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
- eq(component.packageName)))
+ whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName), eq(null), eq(TEST_OTHER_USER_ID)))
.thenReturn(appProp)
- val helper = MultiInstanceHelper(mContext, pm, emptyArray(), true)
+ val helper = MultiInstanceHelper(mContext, pm, emptyArray(), mock(), mock(), true)
// Expect activity property to override application property
- assertEquals(false, helper.supportsMultiInstanceSplit(component))
+ assertEquals(false, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID))
}
@Test
@@ -136,17 +136,17 @@ class MultiInstanceHelperTest : ShellTestCase() {
fun supportsMultiInstanceSplit_noActivityPropertyApplicationPropertyTrue() {
val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY)
val pm = mock()
- whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
- eq(component)))
+ whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER_ID)))
.thenThrow(PackageManager.NameNotFoundException())
val appProp = PackageManager.Property("", true, "", "")
- whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
- eq(component.packageName)))
+ whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName), eq(null), eq(TEST_OTHER_USER_ID)))
.thenReturn(appProp)
- val helper = MultiInstanceHelper(mContext, pm, emptyArray(), true)
+ val helper = MultiInstanceHelper(mContext, pm, emptyArray(), mock(), mock(), true)
// Expect fall through to app property
- assertEquals(true, helper.supportsMultiInstanceSplit(component))
+ assertEquals(true, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID))
}
@Test
@@ -154,15 +154,15 @@ class MultiInstanceHelperTest : ShellTestCase() {
fun supportsMultiInstanceSplit_noActivityOrAppProperty() {
val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY)
val pm = mock()
- whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
- eq(component)))
+ whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER_ID)))
.thenThrow(PackageManager.NameNotFoundException())
- whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
- eq(component.packageName)))
+ whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName), eq(null), eq(TEST_OTHER_USER_ID)))
.thenThrow(PackageManager.NameNotFoundException())
- val helper = MultiInstanceHelper(mContext, pm, emptyArray(), true)
- assertEquals(false, helper.supportsMultiInstanceSplit(component))
+ val helper = MultiInstanceHelper(mContext, pm, emptyArray(), mock(), mock(), true)
+ assertEquals(false, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID))
}
@Test
@@ -171,24 +171,25 @@ class MultiInstanceHelperTest : ShellTestCase() {
val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY)
val pm = mock()
val activityProp = PackageManager.Property("", true, "", "")
- whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
- eq(component)))
+ whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER_ID)))
.thenReturn(activityProp)
val appProp = PackageManager.Property("", true, "", "")
- whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
- eq(component.packageName)))
+ whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+ eq(component.packageName), eq(null), eq(TEST_OTHER_USER_ID)))
.thenReturn(appProp)
- val helper = MultiInstanceHelper(mContext, pm, emptyArray(), false)
+ val helper = MultiInstanceHelper(mContext, pm, emptyArray(), mock(), mock(), false)
// Expect we only check the static list and not the property
- assertEquals(false, helper.supportsMultiInstanceSplit(component))
+ assertEquals(false, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID))
verify(pm, never()).getProperty(any(), any())
}
companion object {
val TEST_PACKAGE = "com.android.wm.shell.common"
- val TEST_NOT_ALLOWED_PACKAGE = "com.android.wm.shell.common.fake";
- val TEST_ACTIVITY = "TestActivity";
+ val TEST_NOT_ALLOWED_PACKAGE = "com.android.wm.shell.common.fake"
+ val TEST_ACTIVITY = "TestActivity"
val TEST_SHORTCUT_ID = "test_shortcut_1"
+ val TEST_OTHER_USER_ID = 1234
}
}
\ No newline at end of file
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java
index 96d202ce3a..7d18669758 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java
@@ -29,7 +29,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -145,7 +145,7 @@ public class TabletopModeControllerTest extends ShellTestCase {
mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
- verifyZeroInteractions(mOnTabletopModeChangedListener);
+ verifyNoMoreInteractions(mOnTabletopModeChangedListener);
}
// Test cases starting from folded state (DEVICE_POSTURE_CLOSED)
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
index 636c6326d2..544c94394d 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
@@ -25,6 +25,7 @@ import static org.mockito.Mockito.verify;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.Handler;
import android.os.SystemClock;
import android.provider.DeviceConfig;
import android.view.InputDevice;
@@ -35,10 +36,15 @@ import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.shared.desktopmode.FakeDesktopState;
+import com.android.wm.shell.splitscreen.SplitStatusBarHider;
+
+import com.google.android.msdl.domain.MSDLPlayer;
import org.junit.Before;
import org.junit.Test;
@@ -55,6 +61,11 @@ public class DividerViewTest extends ShellTestCase {
private @Mock DisplayController mDisplayController;
private @Mock DisplayImeController mDisplayImeController;
private @Mock ShellTaskOrganizer mTaskOrganizer;
+ private @Mock SplitState mSplitState;
+ private @Mock Handler mHandler;
+ private @Mock SplitStatusBarHider mStatusBarHider;
+ private @Mock MSDLPlayer mMSDLPlayer;
+ private FakeDesktopState mDesktopState;
private SplitLayout mSplitLayout;
private DividerView mDividerView;
@@ -62,15 +73,17 @@ public class DividerViewTest extends ShellTestCase {
@UiThreadTest
public void setup() {
MockitoAnnotations.initMocks(this);
+ mDesktopState = new FakeDesktopState();
Configuration configuration = getConfiguration();
- mSplitLayout = new SplitLayout("TestSplitLayout", mContext, configuration,
+ mSplitLayout = spy(new SplitLayout("TestSplitLayout", mContext, configuration,
mSplitLayoutHandler, mCallbacks, mDisplayController, mDisplayImeController,
- mTaskOrganizer, SplitLayout.PARALLAX_NONE);
+ mTaskOrganizer, SplitLayout.PARALLAX_NONE, mSplitState, mHandler, mStatusBarHider,
+ mDesktopState, mMSDLPlayer));
SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager",
- mContext,
- configuration, mCallbacks);
- splitWindowManager.init(mSplitLayout, new InsetsState(), false /* isRestoring */);
- mDividerView = spy((DividerView) splitWindowManager.getDividerView());
+ mContext, configuration, mCallbacks);
+ splitWindowManager.init(mSplitLayout, new InsetsState(), false /* isRestoring */,
+ mDesktopState);
+ mDividerView = spy(splitWindowManager.getDividerView());
}
@Test
@@ -95,6 +108,14 @@ public class DividerViewTest extends ShellTestCase {
"false", false);
}
+ @Test
+ public void swapDividerActionForA11y() {
+ mDividerView.setAccessibilityDelegate(mDividerView.mHandleDelegate);
+ mDividerView.getAccessibilityDelegate().performAccessibilityAction(mDividerView,
+ R.id.action_swap_apps, null);
+ verify(mSplitLayout, times(1)).onDoubleTappedDivider();
+ }
+
private static MotionEvent getMotionEvent(long eventTime, int action, float x, float y) {
MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties();
properties.id = 0;
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/wmshell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index cfe8e07aa6..bc4b50e0cc 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -19,9 +19,7 @@ package com.android.wm.shell.common.split;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import static com.google.common.truth.Truth.assertThat;
@@ -37,6 +35,7 @@ import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.Handler;
import android.window.WindowContainerTransaction;
import androidx.test.annotation.UiThreadTest;
@@ -48,6 +47,10 @@ import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.shared.desktopmode.FakeDesktopState;
+import com.android.wm.shell.splitscreen.SplitStatusBarHider;
+
+import com.google.android.msdl.domain.MSDLPlayer;
import org.junit.Before;
import org.junit.Test;
@@ -67,12 +70,18 @@ public class SplitLayoutTests extends ShellTestCase {
@Mock DisplayImeController mDisplayImeController;
@Mock ShellTaskOrganizer mTaskOrganizer;
@Mock WindowContainerTransaction mWct;
+ @Mock SplitState mSplitState;
+ @Mock SplitStatusBarHider mStatusBarHider;
+ @Mock Handler mHandler;
+ @Mock MSDLPlayer mMSDLPlayer;
@Captor ArgumentCaptor mRunnableCaptor;
private SplitLayout mSplitLayout;
+ private FakeDesktopState mDesktopState;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ mDesktopState = new FakeDesktopState();
mSplitLayout = spy(new SplitLayout(
"TestSplitLayout",
mContext,
@@ -82,7 +91,12 @@ public class SplitLayoutTests extends ShellTestCase {
mDisplayController,
mDisplayImeController,
mTaskOrganizer,
- SplitLayout.PARALLAX_NONE));
+ SplitLayout.PARALLAX_NONE,
+ mSplitState,
+ mHandler,
+ mStatusBarHider,
+ mDesktopState,
+ mMSDLPlayer));
}
@Test
@@ -135,7 +149,7 @@ public class SplitLayoutTests extends ShellTestCase {
@Test
public void testSetDivideRatio() {
mSplitLayout.setDividerPosition(200, false /* applyLayoutChange */);
- mSplitLayout.setDivideRatio(SNAP_TO_50_50);
+ mSplitLayout.setDivideRatio(SNAP_TO_2_50_50);
assertThat(mSplitLayout.getDividerPosition()).isEqualTo(
mSplitLayout.mDividerSnapAlgorithm.getMiddleTarget().position);
}
@@ -150,8 +164,8 @@ public class SplitLayoutTests extends ShellTestCase {
@UiThreadTest
public void testSnapToDismissStart() {
// verify it callbacks properly when the snap target indicates dismissing split.
- DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
- SNAP_TO_START_AND_DISMISS);
+ DividerSnapAlgorithm.SnapTarget snapTarget =
+ mSplitLayout.mDividerSnapAlgorithm.getDismissStartTarget();
mSplitLayout.snapToTarget(mSplitLayout.getDividerPosition(), snapTarget);
waitDividerFlingFinished();
@@ -162,8 +176,8 @@ public class SplitLayoutTests extends ShellTestCase {
@UiThreadTest
public void testSnapToDismissEnd() {
// verify it callbacks properly when the snap target indicates dismissing split.
- DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
- SNAP_TO_END_AND_DISMISS);
+ DividerSnapAlgorithm.SnapTarget snapTarget =
+ mSplitLayout.mDividerSnapAlgorithm.getDismissEndTarget();
mSplitLayout.snapToTarget(mSplitLayout.getDividerPosition(), snapTarget);
waitDividerFlingFinished();
@@ -189,7 +203,7 @@ public class SplitLayoutTests extends ShellTestCase {
}
private void waitDividerFlingFinished() {
- verify(mSplitLayout).flingDividerPosition(anyInt(), anyInt(), anyInt(),
+ verify(mSplitLayout).flingDividerPosition(anyInt(), anyInt(), anyInt(), any(),
mRunnableCaptor.capture());
mRunnableCaptor.getValue().run();
}
@@ -203,9 +217,4 @@ public class SplitLayoutTests extends ShellTestCase {
new Rect(0, 0, 1080, 2160));
return configuration;
}
-
- private static DividerSnapAlgorithm.SnapTarget getSnapTarget(int position, int flag) {
- return new DividerSnapAlgorithm.SnapTarget(
- position /* position */, position /* taskPosition */, flag);
- }
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java b/wmshell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
index 150aa13f2d..6d5272fcc7 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
@@ -29,6 +29,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.shared.desktopmode.FakeDesktopState;
import org.junit.Before;
import org.junit.Test;
@@ -42,6 +43,7 @@ import org.mockito.MockitoAnnotations;
public class SplitWindowManagerTests extends ShellTestCase {
@Mock SplitLayout mSplitLayout;
@Mock SplitWindowManager.ParentContainerCallbacks mCallbacks;
+ private FakeDesktopState mDesktopState;
private SplitWindowManager mSplitWindowManager;
@Before
@@ -51,6 +53,7 @@ public class SplitWindowManagerTests extends ShellTestCase {
configuration.setToDefaults();
mSplitWindowManager = new SplitWindowManager("TestSplitDivider", mContext, configuration,
mCallbacks);
+ mDesktopState = new FakeDesktopState();
when(mSplitLayout.getDividerBounds()).thenReturn(
new Rect(0, 0, configuration.windowConfiguration.getBounds().width(),
configuration.windowConfiguration.getBounds().height()));
@@ -59,7 +62,8 @@ public class SplitWindowManagerTests extends ShellTestCase {
@Test
@UiThreadTest
public void testInitRelease() {
- mSplitWindowManager.init(mSplitLayout, new InsetsState(), false /* isRestoring */);
+ mSplitWindowManager.init(mSplitLayout, new InsetsState(), false /* isRestoring */,
+ mDesktopState);
assertThat(mSplitWindowManager.getSurfaceControl()).isNotNull();
mSplitWindowManager.release(null /* t */);
assertThat(mSplitWindowManager.getSurfaceControl()).isNull();
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 9c00864710..2b833a51c6 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -16,8 +16,6 @@
package com.android.wm.shell.compatui;
-import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
-import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
import static android.view.WindowInsets.Type.navigationBars;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -32,19 +30,24 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.ActivityManager.RunningTaskInfo;
-import android.app.CameraCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
import android.content.Context;
import android.content.res.Configuration;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.testing.AndroidTestingRunner;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.accessibility.AccessibilityManager;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayController;
@@ -55,6 +58,11 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.api.CompatUIInfo;
+import com.android.wm.shell.compatui.impl.CompatUIRequests;
+import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
+import com.android.wm.shell.shared.desktopmode.FakeDesktopState;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -70,17 +78,20 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
/**
* Tests for {@link CompatUIController}.
*
* Build/Install/Run:
- * atest WMShellUnitTests:CompatUIControllerTest
+ * atest WMShellUnitTests:CompatUIControllerTest
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
public class CompatUIControllerTest extends ShellTestCase {
private static final int DISPLAY_ID = 0;
private static final int TASK_ID = 12;
+ private static final int TASK_ID_2 = 18;
private CompatUIController mController;
private ShellInit mShellInit;
@@ -114,17 +125,27 @@ public class CompatUIControllerTest extends ShellTestCase {
private CompatUIConfiguration mCompatUIConfiguration;
@Mock
private CompatUIShellCommandHandler mCompatUIShellCommandHandler;
-
@Mock
private AccessibilityManager mAccessibilityManager;
+ @Mock
+ private DesktopUserRepositories mDesktopUserRepositories;
+ @Mock
+ private DesktopRepository mDesktopRepository;
@Captor
ArgumentCaptor mOnInsetsChangedListenerCaptor;
+ @NonNull
+ private CompatUIStatusManager mCompatUIStatusManager;
+ @NonNull
+ private FakeDesktopState mDesktopState;
+
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mDesktopState = new FakeDesktopState();
doReturn(mMockDisplayLayout).when(mMockDisplayController).getDisplayLayout(anyInt());
doReturn(DISPLAY_ID).when(mMockCompatLayout).getDisplayId();
doReturn(TASK_ID).when(mMockCompatLayout).getTaskId();
@@ -134,17 +155,22 @@ public class CompatUIControllerTest extends ShellTestCase {
doReturn(TASK_ID).when(mMockLetterboxEduLayout).getTaskId();
doReturn(true).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
doReturn(true).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
+ doReturn(mDesktopRepository).when(mDesktopUserRepositories).getCurrent();
+ doReturn(mDesktopRepository).when(mDesktopUserRepositories).getProfile(anyInt());
doReturn(DISPLAY_ID).when(mMockRestartDialogLayout).getDisplayId();
doReturn(TASK_ID).when(mMockRestartDialogLayout).getTaskId();
doReturn(true).when(mMockRestartDialogLayout).createLayout(anyBoolean());
doReturn(true).when(mMockRestartDialogLayout).updateCompatInfo(any(), any(), anyBoolean());
+ mCompatUIStatusManager = new CompatUIStatusManager();
mShellInit = spy(new ShellInit(mMockExecutor));
mController = new CompatUIController(mContext, mShellInit, mMockShellController,
mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader,
- mCompatUIConfiguration, mCompatUIShellCommandHandler, mAccessibilityManager) {
+ mCompatUIConfiguration, mCompatUIShellCommandHandler, mAccessibilityManager,
+ mCompatUIStatusManager, Optional.of(mDesktopUserRepositories),
+ mDesktopState) {
@Override
CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {
@@ -168,28 +194,31 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void instantiateController_addInitCallback() {
verify(mShellInit, times(1)).addInitCallback(any(), any());
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void instantiateController_registerKeyguardChangeListener() {
verify(mMockShellController, times(1)).addKeyguardChangeListener(any());
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testListenerRegistered() {
verify(mMockDisplayController).addDisplayWindowListener(mController);
verify(mMockImeController).addPositionProcessor(mController);
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCompatInfoChanged() {
- TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
- CAMERA_COMPAT_CONTROL_HIDDEN);
+ TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
// Verify that the compat controls are added with non-null task listener.
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
@@ -200,9 +229,8 @@ public class CompatUIControllerTest extends ShellTestCase {
// Verify that the compat controls and letterbox education are updated with new size compat
// info.
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
- taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
- CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
/* canShow= */ true);
@@ -213,9 +241,9 @@ public class CompatUIControllerTest extends ShellTestCase {
// Verify that compat controls and letterbox education are removed with null task listener.
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN),
- /* taskListener= */ null);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true),
+ /* taskListener= */ null));
verify(mMockCompatLayout).release();
verify(mMockLetterboxEduLayout).release();
@@ -223,14 +251,14 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCompatInfoChanged_createLayoutReturnsFalse() {
doReturn(false).when(mMockCompatLayout).createLayout(anyBoolean());
doReturn(false).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
doReturn(false).when(mMockRestartDialogLayout).createLayout(anyBoolean());
- TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
- CAMERA_COMPAT_CONTROL_HIDDEN);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
@@ -240,7 +268,7 @@ public class CompatUIControllerTest extends ShellTestCase {
// Verify that the layout is created again.
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
@@ -253,14 +281,14 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnCompatInfoChanged_updateCompatInfoReturnsFalse() {
doReturn(false).when(mMockCompatLayout).updateCompatInfo(any(), any(), anyBoolean());
doReturn(false).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
doReturn(false).when(mMockRestartDialogLayout).updateCompatInfo(any(), any(), anyBoolean());
- TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
- CAMERA_COMPAT_CONTROL_HIDDEN);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
@@ -270,7 +298,7 @@ public class CompatUIControllerTest extends ShellTestCase {
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout,
mController);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
/* canShow= */ true);
@@ -282,7 +310,7 @@ public class CompatUIControllerTest extends ShellTestCase {
// Verify that the layout is created again.
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout,
mController);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
@@ -296,6 +324,7 @@ public class CompatUIControllerTest extends ShellTestCase {
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDisplayAdded() {
mController.onDisplayAdded(DISPLAY_ID);
mController.onDisplayAdded(DISPLAY_ID + 1);
@@ -305,11 +334,11 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDisplayRemoved() {
mController.onDisplayAdded(DISPLAY_ID);
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN),
- mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true), mMockTaskListener));
mController.onDisplayRemoved(DISPLAY_ID + 1);
@@ -328,9 +357,10 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDisplayConfigurationChanged() {
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true), mMockTaskListener));
mController.onDisplayConfigurationChanged(DISPLAY_ID + 1, new Configuration());
@@ -346,10 +376,11 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testInsetsChanged() {
mController.onDisplayAdded(DISPLAY_ID);
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true), mMockTaskListener));
InsetsState insetsState = new InsetsState();
InsetsSource insetsSource = new InsetsSource(
InsetsSource.createId(null, 0, navigationBars()), navigationBars());
@@ -373,9 +404,10 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testChangeLayoutsVisibilityOnImeShowHide() {
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true), mMockTaskListener));
// Verify that the restart button is hidden after IME is showing.
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
@@ -385,9 +417,8 @@ public class CompatUIControllerTest extends ShellTestCase {
verify(mMockRestartDialogLayout).updateVisibility(false);
// Verify button remains hidden while IME is showing.
- TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
- CAMERA_COMPAT_CONTROL_HIDDEN);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
/* canShow= */ false);
@@ -405,9 +436,10 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testChangeLayoutsVisibilityOnKeyguardShowingChanged() {
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true), mMockTaskListener));
// Verify that the restart button is hidden after keyguard becomes showing.
mController.onKeyguardVisibilityChanged(true, false, false);
@@ -417,9 +449,8 @@ public class CompatUIControllerTest extends ShellTestCase {
verify(mMockRestartDialogLayout).updateVisibility(false);
// Verify button remains hidden while keyguard is showing.
- TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
- CAMERA_COMPAT_CONTROL_HIDDEN);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
/* canShow= */ false);
@@ -437,9 +468,10 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testLayoutsRemainHiddenOnKeyguardShowingFalseWhenImeIsShowing() {
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true), mMockTaskListener));
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
mController.onKeyguardVisibilityChanged(true, false, false);
@@ -466,9 +498,10 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testLayoutsRemainHiddenOnImeHideWhenKeyguardIsShowing() {
- mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID,
+ /* hasSizeCompat= */ true), mMockTaskListener));
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
mController.onKeyguardVisibilityChanged(true, false, false);
@@ -495,45 +528,45 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testRestartLayoutRecreatedIfNeeded() {
- final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
+ final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
doReturn(true).when(mMockRestartDialogLayout)
.needsToBeRecreated(any(TaskInfo.class),
any(ShellTaskOrganizer.TaskListener.class));
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockRestartDialogLayout, times(2))
.createLayout(anyBoolean());
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testRestartLayoutNotRecreatedIfNotNeeded() {
- final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
+ final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
doReturn(false).when(mMockRestartDialogLayout)
.needsToBeRecreated(any(TaskInfo.class),
any(ShellTaskOrganizer.TaskListener.class));
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mMockRestartDialogLayout, times(1))
.createLayout(anyBoolean());
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateActiveTaskInfo_newTask_visibleAndFocused_updated() {
// Simulate user aspect ratio button being shown for previous task
mController.setHasShownUserAspectRatioSettingsButton(true);
Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton());
// Create new task
- final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true,
- /* isFocused */ true);
+ final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
+ /* isVisible */ true, /* isFocused */ true);
// Simulate new task being shown
mController.updateActiveTaskInfo(taskInfo);
@@ -545,11 +578,11 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateActiveTaskInfo_newTask_notVisibleOrFocused_notUpdated() {
// Create new task
- final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true,
- /* isFocused */ true);
+ final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
+ /* isVisible */ true, /* isFocused */ true);
// Simulate task being shown
mController.updateActiveTaskInfo(taskInfo);
@@ -566,9 +599,8 @@ public class CompatUIControllerTest extends ShellTestCase {
final int newTaskId = TASK_ID + 1;
// Create visible but NOT focused task
- final TaskInfo taskInfo1 = createTaskInfo(DISPLAY_ID, newTaskId,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true,
- /* isFocused */ false);
+ final TaskInfo taskInfo1 = createTaskInfo(DISPLAY_ID, newTaskId, /* hasSizeCompat= */ true,
+ /* isVisible */ true, /* isFocused */ false);
// Simulate new task being shown
mController.updateActiveTaskInfo(taskInfo1);
@@ -579,9 +611,8 @@ public class CompatUIControllerTest extends ShellTestCase {
Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton());
// Create focused but NOT visible task
- final TaskInfo taskInfo2 = createTaskInfo(DISPLAY_ID, newTaskId,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ false,
- /* isFocused */ true);
+ final TaskInfo taskInfo2 = createTaskInfo(DISPLAY_ID, newTaskId, /* hasSizeCompat= */ true,
+ /* isVisible */ false, /* isFocused */ true);
// Simulate new task being shown
mController.updateActiveTaskInfo(taskInfo2);
@@ -592,9 +623,8 @@ public class CompatUIControllerTest extends ShellTestCase {
Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton());
// Create NOT focused but NOT visible task
- final TaskInfo taskInfo3 = createTaskInfo(DISPLAY_ID, newTaskId,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ false,
- /* isFocused */ false);
+ final TaskInfo taskInfo3 = createTaskInfo(DISPLAY_ID, newTaskId, /* hasSizeCompat= */ true,
+ /* isVisible */ false, /* isFocused */ false);
// Simulate new task being shown
mController.updateActiveTaskInfo(taskInfo3);
@@ -606,11 +636,11 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateActiveTaskInfo_sameTask_notUpdated() {
// Create new task
- final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true,
- /* isFocused */ true);
+ final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
+ /* isVisible */ true, /* isFocused */ true);
// Simulate new task being shown
mController.updateActiveTaskInfo(taskInfo);
@@ -634,11 +664,11 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateActiveTaskInfo_transparentTask_notUpdated() {
// Create new task
- final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true,
- /* isFocused */ true);
+ final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
+ /* isVisible */ true, /* isFocused */ true);
// Simulate new task being shown
mController.updateActiveTaskInfo(taskInfo);
@@ -655,9 +685,9 @@ public class CompatUIControllerTest extends ShellTestCase {
final int newTaskId = TASK_ID + 1;
// Create transparent task
- final TaskInfo taskInfo1 = createTaskInfo(DISPLAY_ID, newTaskId,
- /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN, /* isVisible */ true,
- /* isFocused */ true, /* isTopActivityTransparent */ true);
+ final TaskInfo taskInfo1 = createTaskInfo(DISPLAY_ID, newTaskId, /* hasSizeCompat= */ true,
+ /* isVisible */ true, /* isFocused */ true, /* isTopActivityTransparent */ true,
+ /* isRestartMenuEnabledForDisplayMove */ true);
// Simulate new task being shown
mController.updateActiveTaskInfo(taskInfo1);
@@ -669,45 +699,129 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testLetterboxEduLayout_notCreatedWhenLetterboxEducationIsDisabled() {
- TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
- CAMERA_COMPAT_CONTROL_HIDDEN);
- taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled = false;
+ TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
+ taskInfo.appCompatTaskInfo.setLetterboxEducationEnabled(false);
- mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mController, never()).createLetterboxEduWindowManager(any(), eq(taskInfo),
eq(mMockTaskListener));
}
- private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat,
- @CameraCompatControlState int cameraCompatControlState) {
- return createTaskInfo(displayId, taskId, hasSizeCompat, cameraCompatControlState,
- /* isVisible */ false, /* isFocused */ false,
- /* isTopActivityTransparent */ false);
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
+ @EnableFlags(Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE)
+ public void testUpdateActiveTaskInfo_removeAllComponentWhenInDesktopModeFlagEnabled() {
+ TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
+ when(mDesktopUserRepositories.getCurrent().isAnyDeskActive(DISPLAY_ID)).thenReturn(false);
+
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+
+ verify(mController, never()).removeLayouts(taskInfo.taskId);
+
+ when(mDesktopUserRepositories.getCurrent().isAnyDeskActive(DISPLAY_ID)).thenReturn(true);
+
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+
+ verify(mController).removeLayouts(taskInfo.taskId);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
+ @EnableFlags(Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE)
+ public void testUpdateActiveTaskInfo_alwaysRemoveLetterboxEdu() {
+ TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
+
+ // When not in Desktop Mode the LetterboxEdu is removed only if the taskId is the one used
+ // when created.
+ when(mDesktopUserRepositories.getCurrent().isAnyDeskActive(DISPLAY_ID)).thenReturn(false);
+
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+ mController.removeLetterboxEdu(TASK_ID_2);
+ verify(mMockLetterboxEduLayout, never()).release();
+
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+ mController.removeLetterboxEdu(TASK_ID);
+ verify(mMockLetterboxEduLayout).release();
+
+ // When in Desktop Mode the LetterboxEdu is always removed
+ when(mDesktopUserRepositories.getCurrent().isAnyDeskActive(DISPLAY_ID)).thenReturn(true);
+
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+ mController.removeLetterboxEdu(TASK_ID);
+ verify(mMockLetterboxEduLayout).release();
+
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+ mController.removeLetterboxEdu(TASK_ID_2);
+ verify(mMockLetterboxEduLayout).release();
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
+ @DisableFlags(Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE)
+ public void testUpdateActiveTaskInfo_removeAllComponentWhenInDesktopModeFlagDisabled() {
+ when(mDesktopUserRepositories.getCurrent().isAnyDeskActive(DISPLAY_ID)).thenReturn(false);
+ TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
+
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+
+ verify(mController, never()).removeLayouts(taskInfo.taskId);
+
+ when(mDesktopUserRepositories.getCurrent().isAnyDeskActive(DISPLAY_ID)).thenReturn(true);
+
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+
+ verify(mController, never()).removeLayouts(taskInfo.taskId);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
+ public void testSendCompatUIRequest_createRestartDialog() {
+ final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
+ /* isVisible */ true, /* isFocused */ true, /* isTopActivityTransparent */ false,
+ /* isRestartMenuEnabledForDisplayMove */ true);
+ doReturn(true).when(mCompatUIConfiguration).isRestartDialogEnabled();
+ doReturn(true).when(mCompatUIConfiguration).shouldShowRestartDialogAgain(eq(taskInfo));
+
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
+ verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo),
+ eq(mMockTaskListener));
+ verify(mMockRestartDialogLayout).setRequestRestartDialog(false);
+
+ mController.sendCompatUIRequest(
+ new CompatUIRequests.DisplayCompatShowRestartDialog(taskInfo.taskId));
+ verify(mMockRestartDialogLayout).setRequestRestartDialog(true);
+ }
+
+ private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat) {
+ return createTaskInfo(displayId, taskId, hasSizeCompat, /* isVisible */ false,
+ /* isFocused */ false, /* isTopActivityTransparent */ false,
+ /* isRestartMenuEnabledForDisplayMove */ false);
}
private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat,
- @CameraCompatControlState int cameraCompatControlState, boolean isVisible,
- boolean isFocused) {
- return createTaskInfo(displayId, taskId, hasSizeCompat, cameraCompatControlState,
- isVisible, isFocused, /* isTopActivityTransparent */ false);
+ boolean isVisible, boolean isFocused) {
+ return createTaskInfo(displayId, taskId, hasSizeCompat,
+ isVisible, isFocused, /* isTopActivityTransparent */ false,
+ /* isRestartMenuEnabledForDisplayMove */ false);
}
private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat,
- @CameraCompatControlState int cameraCompatControlState, boolean isVisible,
- boolean isFocused, boolean isTopActivityTransparent) {
+ boolean isVisible, boolean isFocused, boolean isTopActivityTransparent,
+ boolean isRestartMenuEnabledForDisplayMove) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
taskInfo.displayId = displayId;
- taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat;
- taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState =
- cameraCompatControlState;
+ taskInfo.appCompatTaskInfo.setTopActivityInSizeCompat(hasSizeCompat);
taskInfo.isVisible = isVisible;
taskInfo.isFocused = isFocused;
taskInfo.isTopActivityTransparent = isTopActivityTransparent;
- taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled = true;
- taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed = true;
+ taskInfo.appCompatTaskInfo.setLetterboxEducationEnabled(true);
+ taskInfo.appCompatTaskInfo.setTopActivityLetterboxed(true);
+ taskInfo.appCompatTaskInfo.setRestartMenuEnabledForDisplayMove(
+ isRestartMenuEnabledForDisplayMove);
return taskInfo;
}
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index cd3e8cb0e8..c64d4f1398 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -16,11 +16,6 @@
package com.android.wm.shell.compatui;
-import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
-import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
-import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
-import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
-
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static org.mockito.Mockito.doNothing;
@@ -28,9 +23,9 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
-import android.app.CameraCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
import android.graphics.Rect;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.LayoutInflater;
@@ -40,12 +35,15 @@ import android.widget.LinearLayout;
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
+import com.android.wm.shell.compatui.api.CompatUIEvent;
+import com.android.wm.shell.shared.desktopmode.FakeDesktopState;
import junit.framework.Assert;
@@ -71,7 +69,7 @@ public class CompatUILayoutTest extends ShellTestCase {
private static final int TASK_ID = 1;
@Mock private SyncTransactionQueue mSyncTransactionQueue;
- @Mock private CompatUIController.CompatUICallback mCallback;
+ @Mock private Consumer mCallback;
@Mock private Consumer> mOnRestartButtonClicked;
@Mock private ShellTaskOrganizer.TaskListener mTaskListener;
@Mock private SurfaceControlViewHost mViewHost;
@@ -80,15 +78,17 @@ public class CompatUILayoutTest extends ShellTestCase {
private CompatUIWindowManager mWindowManager;
private CompatUILayout mLayout;
private TaskInfo mTaskInfo;
+ private FakeDesktopState mDesktopState;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mDesktopState = new FakeDesktopState();
doReturn(100).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
- mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
+ mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false);
mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
- mCompatUIConfiguration, mOnRestartButtonClicked);
+ mCompatUIConfiguration, mOnRestartButtonClicked, mDesktopState);
mLayout = (CompatUILayout)
LayoutInflater.from(mContext).inflate(R.layout.compat_ui_layout, null);
@@ -101,6 +101,7 @@ public class CompatUILayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnClickForRestartButton() {
final ImageButton button = mLayout.findViewById(R.id.size_compat_restart_button);
button.performClick();
@@ -117,6 +118,7 @@ public class CompatUILayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnLongClickForRestartButton() {
doNothing().when(mWindowManager).onRestartButtonLongClicked();
@@ -127,6 +129,7 @@ public class CompatUILayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnClickForSizeCompatHint() {
mWindowManager.mHasSizeCompat = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -136,94 +139,10 @@ public class CompatUILayoutTest extends ShellTestCase {
verify(mLayout).setSizeCompatHintVisibility(/* show= */ false);
}
- @Test
- public void testUpdateCameraTreatmentButton_treatmentAppliedByDefault() {
- mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
- mWindowManager.createLayout(/* canShow= */ true);
- final ImageButton button =
- mLayout.findViewById(R.id.camera_compat_treatment_button);
- button.performClick();
-
- verify(mWindowManager).onCameraTreatmentButtonClicked();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
-
- button.performClick();
-
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
- }
-
- @Test
- public void testUpdateCameraTreatmentButton_treatmentSuggestedByDefault() {
- mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
- mWindowManager.createLayout(/* canShow= */ true);
- final ImageButton button =
- mLayout.findViewById(R.id.camera_compat_treatment_button);
- button.performClick();
-
- verify(mWindowManager).onCameraTreatmentButtonClicked();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
-
- button.performClick();
-
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
- }
-
- @Test
- public void testOnCameraDismissButtonClicked() {
- mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
- mWindowManager.createLayout(/* canShow= */ true);
- final ImageButton button =
- mLayout.findViewById(R.id.camera_compat_dismiss_button);
- button.performClick();
-
- verify(mWindowManager).onCameraDismissButtonClicked();
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED);
- verify(mLayout).setCameraControlVisibility(/* show */ false);
- }
-
- @Test
- public void testOnLongClickForCameraTreatmentButton() {
- doNothing().when(mWindowManager).onCameraButtonLongClicked();
-
- final ImageButton button =
- mLayout.findViewById(R.id.camera_compat_treatment_button);
- button.performLongClick();
-
- verify(mWindowManager).onCameraButtonLongClicked();
- }
-
- @Test
- public void testOnLongClickForCameraDismissButton() {
- doNothing().when(mWindowManager).onCameraButtonLongClicked();
-
- final ImageButton button = mLayout.findViewById(R.id.camera_compat_dismiss_button);
- button.performLongClick();
-
- verify(mWindowManager).onCameraButtonLongClicked();
- }
-
- @Test
- public void testOnClickForCameraCompatHint() {
- mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
- mWindowManager.createLayout(/* canShow= */ true);
- final LinearLayout hint = mLayout.findViewById(R.id.camera_compat_hint);
- hint.performClick();
-
- verify(mLayout).setCameraCompatHintVisibility(/* show= */ false);
- }
-
- private static TaskInfo createTaskInfo(boolean hasSizeCompat,
- @CameraCompatControlState int cameraCompatControlState) {
+ private static TaskInfo createTaskInfo(boolean hasSizeCompat) {
ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.taskId = TASK_ID;
- taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat;
- taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState =
- cameraCompatControlState;
+ taskInfo.appCompatTaskInfo.setTopActivityInSizeCompat(hasSizeCompat);
taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 1000;
taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000));
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index 41a81c1a99..aa1e3d360a 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -16,15 +16,11 @@
package com.android.wm.shell.compatui;
-import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
-import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
-import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
-import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
-import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -35,14 +31,15 @@ import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
-import android.app.CameraCompatTaskInfo;
import android.app.TaskInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
-import android.platform.test.flag.junit.SetFlagsRule;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.DisplayInfo;
@@ -60,11 +57,12 @@ import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
+import com.android.wm.shell.compatui.api.CompatUIEvent;
+import com.android.wm.shell.shared.desktopmode.FakeDesktopState;
import junit.framework.Assert;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -82,15 +80,13 @@ import java.util.function.Consumer;
@RunWith(AndroidTestingRunner.class)
@SmallTest
public class CompatUIWindowManagerTest extends ShellTestCase {
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
private static final int TASK_ID = 1;
private static final int TASK_WIDTH = 2000;
private static final int TASK_HEIGHT = 2000;
@Mock private SyncTransactionQueue mSyncTransactionQueue;
- @Mock private CompatUIController.CompatUICallback mCallback;
+ @Mock private Consumer mCallback;
@Mock private Consumer> mOnRestartButtonClicked;
@Mock private ShellTaskOrganizer.TaskListener mTaskListener;
@Mock private CompatUILayout mLayout;
@@ -100,12 +96,14 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
private CompatUIWindowManager mWindowManager;
private TaskInfo mTaskInfo;
private DisplayLayout mDisplayLayout;
+ private FakeDesktopState mDesktopState;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mDesktopState = new FakeDesktopState();
doReturn(100).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
- mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
+ mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false);
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = TASK_WIDTH;
@@ -121,7 +119,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
mDisplayLayout.setInsets(mContext.getResources(), insetsState);
mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
mCallback, mTaskListener, mDisplayLayout, new CompatUIHintsState(),
- mCompatUIConfiguration, mOnRestartButtonClicked);
+ mCompatUIConfiguration, mOnRestartButtonClicked, mDesktopState);
spyOn(mWindowManager);
doReturn(mLayout).when(mWindowManager).inflateLayout();
@@ -129,6 +127,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateSizeCompatButton() {
// Doesn't create layout if show is false.
mWindowManager.mHasSizeCompat = true;
@@ -174,44 +173,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
- public void testCreateCameraCompatControl() {
- // Doesn't create layout if show is false.
- mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
- assertTrue(mWindowManager.createLayout(/* canShow= */ false));
-
- verify(mWindowManager, never()).inflateLayout();
-
- // Doesn't create hint popup.
- mWindowManager.mCompatUIHintsState.mHasShownCameraCompatHint = true;
- assertTrue(mWindowManager.createLayout(/* canShow= */ true));
-
- verify(mWindowManager).inflateLayout();
- verify(mLayout).setCameraControlVisibility(/* show= */ true);
- verify(mLayout, never()).setCameraCompatHintVisibility(/* show= */ true);
-
- // Creates hint popup.
- clearInvocations(mWindowManager);
- clearInvocations(mLayout);
- mWindowManager.release();
- mWindowManager.mCompatUIHintsState.mHasShownCameraCompatHint = false;
- assertTrue(mWindowManager.createLayout(/* canShow= */ true));
-
- verify(mWindowManager).inflateLayout();
- assertNotNull(mLayout);
- verify(mLayout).setCameraControlVisibility(/* show= */ true);
- verify(mLayout).setCameraCompatHintVisibility(/* show= */ true);
- assertTrue(mWindowManager.mCompatUIHintsState.mHasShownCameraCompatHint);
-
- // Returns false and doesn't create layout if Camera Compat state is hidden
- clearInvocations(mWindowManager);
- mWindowManager.release();
- mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN;
- assertFalse(mWindowManager.createLayout(/* canShow= */ true));
-
- verify(mWindowManager, never()).inflateLayout();
- }
-
- @Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testRelease() {
mWindowManager.mHasSizeCompat = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -224,13 +186,15 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfo() {
mWindowManager.mHasSizeCompat = true;
mWindowManager.createLayout(/* canShow= */ true);
+ verify(mLayout).setRestartButtonVisibility(/* show= */ true);
// No diff
clearInvocations(mWindowManager);
- TaskInfo taskInfo = createTaskInfo(/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
+ TaskInfo taskInfo = createTaskInfo(/* hasSizeCompat= */ true);
doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(any());
assertTrue(mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true));
@@ -247,58 +211,25 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
verify(mWindowManager).release();
verify(mWindowManager).createLayout(/* canShow= */ true);
- // Change Camera Compat state, show a control.
+ // Change has Size Compat to false, no more CompatIU.
clearInvocations(mWindowManager);
clearInvocations(mLayout);
- taskInfo = createTaskInfo(/* hasSizeCompat= */ true,
- CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
- assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
-
- verify(mLayout).setCameraControlVisibility(/* show= */ true);
- verify(mLayout).updateCameraTreatmentButton(
- CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
-
- // Change Camera Compat state, update a control.
- clearInvocations(mWindowManager);
- clearInvocations(mLayout);
- taskInfo = createTaskInfo(/* hasSizeCompat= */ true,
- CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
- assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
-
- verify(mLayout).setCameraControlVisibility(/* show= */ true);
- verify(mLayout).updateCameraTreatmentButton(
- CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
-
- // Change has Size Compat to false, hides restart button.
- clearInvocations(mWindowManager);
- clearInvocations(mLayout);
- taskInfo = createTaskInfo(/* hasSizeCompat= */ false,
- CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
- assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
-
- verify(mLayout).setRestartButtonVisibility(/* show= */ false);
+ taskInfo = createTaskInfo(/* hasSizeCompat= */ false);
+ assertFalse(mWindowManager.updateCompatInfo(taskInfo, newTaskListener,
+ /* canShow= */ true));
// Change has Size Compat to true, shows restart button.
clearInvocations(mWindowManager);
clearInvocations(mLayout);
- taskInfo = createTaskInfo(/* hasSizeCompat= */ true,
- CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
+ taskInfo = createTaskInfo(/* hasSizeCompat= */ true);
assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
- verify(mLayout).setRestartButtonVisibility(/* show= */ true);
-
- // Change Camera Compat state to dismissed, hide a control.
- clearInvocations(mWindowManager);
- clearInvocations(mLayout);
- taskInfo = createTaskInfo(/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_DISMISSED);
- assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
-
- verify(mLayout).setCameraControlVisibility(/* show= */ false);
+ verify(mLayout, times(2)).setRestartButtonVisibility(/* show= */ true);
// Change task bounds, update position.
clearInvocations(mWindowManager);
clearInvocations(mLayout);
- taskInfo = createTaskInfo(/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
+ taskInfo = createTaskInfo(/* hasSizeCompat= */ true);
taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 1000, 0, 2000));
assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
@@ -307,7 +238,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
// Change has Size Compat to false, release layout.
clearInvocations(mWindowManager);
clearInvocations(mLayout);
- taskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
+ taskInfo = createTaskInfo(/* hasSizeCompat= */ false);
assertFalse(
mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
@@ -315,6 +246,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfoLayoutNotInflatedYet() {
mWindowManager.createLayout(/* canShow= */ false);
@@ -323,15 +255,14 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
// Change topActivityInSizeCompat to false and pass canShow true, layout shouldn't be
// inflated
clearInvocations(mWindowManager);
- TaskInfo taskInfo = createTaskInfo(/* hasSizeCompat= */ false,
- CAMERA_COMPAT_CONTROL_HIDDEN);
+ TaskInfo taskInfo = createTaskInfo(/* hasSizeCompat= */ false);
mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
verify(mWindowManager, never()).inflateLayout();
// Change topActivityInSizeCompat to true and pass canShow true, layout should be inflated.
clearInvocations(mWindowManager);
- taskInfo = createTaskInfo(/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
+ taskInfo = createTaskInfo(/* hasSizeCompat= */ true);
mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
verify(mWindowManager).inflateLayout();
@@ -347,6 +278,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateDisplayLayout() {
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 1000;
@@ -366,6 +298,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateDisplayLayoutInsets() {
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 1000;
@@ -390,6 +323,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateVisibility() {
// Create button if it is not created.
mWindowManager.mLayout = null;
@@ -415,6 +349,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testAttachToParentSurface() {
final SurfaceControl.Builder b = new SurfaceControl.Builder();
mWindowManager.attachToParentSurface(b);
@@ -423,37 +358,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
- public void testOnCameraDismissButtonClicked() {
- mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
- mWindowManager.createLayout(/* canShow= */ true);
- clearInvocations(mLayout);
- mWindowManager.onCameraDismissButtonClicked();
-
- verify(mCallback).onCameraControlStateUpdated(TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED);
- verify(mLayout).setCameraControlVisibility(/* show= */ false);
- }
-
- @Test
- public void testOnCameraTreatmentButtonClicked() {
- mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
- mWindowManager.createLayout(/* canShow= */ true);
- clearInvocations(mLayout);
- mWindowManager.onCameraTreatmentButtonClicked();
-
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
- verify(mLayout).updateCameraTreatmentButton(
- CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
-
- mWindowManager.onCameraTreatmentButtonClicked();
-
- verify(mCallback).onCameraControlStateUpdated(
- TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
- verify(mLayout).updateCameraTreatmentButton(
- CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED);
- }
-
- @Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnRestartButtonClicked() {
mWindowManager.onRestartButtonClicked();
@@ -468,8 +373,9 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnRestartButtonLongClicked_showHint() {
- // Not create hint popup.
+ // Not create hint popup.
mWindowManager.mHasSizeCompat = true;
mWindowManager.mCompatUIHintsState.mHasShownSizeCompatHint = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -483,21 +389,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
- public void testOnCameraControlLongClicked_showHint() {
- // Not create hint popup.
- mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
- mWindowManager.mCompatUIHintsState.mHasShownCameraCompatHint = true;
- mWindowManager.createLayout(/* canShow= */ true);
-
- verify(mWindowManager).inflateLayout();
- verify(mLayout, never()).setCameraCompatHintVisibility(/* show= */ true);
-
- mWindowManager.onCameraButtonLongClicked();
-
- verify(mLayout).setCameraCompatHintVisibility(/* show= */ true);
- }
-
- @Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDockedStateHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
newTaskInfo.configuration.uiMode |= Configuration.UI_MODE_TYPE_DESK;
@@ -506,15 +398,15 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testShouldShowSizeCompatRestartButton() {
- mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON);
doReturn(85).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
mCallback, mTaskListener, mDisplayLayout, new CompatUIHintsState(),
- mCompatUIConfiguration, mOnRestartButtonClicked);
+ mCompatUIConfiguration, mOnRestartButtonClicked, mDesktopState);
// Simulate rotation of activity in square display
- TaskInfo taskInfo = createTaskInfo(true, CAMERA_COMPAT_CONTROL_HIDDEN);
+ TaskInfo taskInfo = createTaskInfo(true);
taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = TASK_HEIGHT;
taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1850;
@@ -543,13 +435,10 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
}
- private static TaskInfo createTaskInfo(boolean hasSizeCompat,
- @CameraCompatTaskInfo.CameraCompatControlState int cameraCompatControlState) {
+ private static TaskInfo createTaskInfo(boolean hasSizeCompat) {
ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.taskId = TASK_ID;
- taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat;
- taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState =
- cameraCompatControlState;
+ taskInfo.appCompatTaskInfo.setTopActivityInSizeCompat(hasSizeCompat);
taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK;
// Letterboxed activity that takes half the screen should show size compat restart button
taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 1000;
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java
index 172c263ab0..c6884ea173 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java
@@ -16,12 +16,15 @@
package com.android.wm.shell.compatui;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.testing.AndroidTestingRunner;
import android.view.LayoutInflater;
import android.view.View;
@@ -66,6 +69,7 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnFinishInflate() {
assertEquals(mLayout.getDialogContainerView(),
mLayout.findViewById(R.id.letterbox_education_dialog_container));
@@ -76,6 +80,7 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDismissButtonClicked() {
assertTrue(mDismissButton.performClick());
@@ -83,6 +88,7 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnBackgroundClicked() {
assertTrue(mLayout.performClick());
@@ -90,6 +96,7 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDialogContainerClicked() {
assertTrue(mDialogContainer.performClick());
@@ -97,6 +104,7 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testSetDismissOnClickListenerNull() {
mLayout.setDismissOnClickListener(null);
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
index a60a1cbb43..cbf5d1bb65 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
@@ -19,9 +19,15 @@ package com.android.wm.shell.compatui;
import static android.content.res.Configuration.UI_MODE_NIGHT_YES;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_ASYNC_RELAYOUT;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+import static com.android.wm.shell.compatui.CompatUIStatusManager.COMPAT_UI_EDUCATION_HIDDEN;
+import static com.android.wm.shell.compatui.CompatUIStatusManager.COMPAT_UI_EDUCATION_VISIBLE;
import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.Assert.assertEquals;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -37,6 +43,9 @@ import android.app.ActivityManager;
import android.app.TaskInfo;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.DisplayCutout;
@@ -50,6 +59,7 @@ import android.view.accessibility.AccessibilityEvent;
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
@@ -57,6 +67,7 @@ import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIStatusManagerTest.FakeCompatUIStatusManagerTest;
import com.android.wm.shell.transition.Transitions;
import org.junit.After;
@@ -115,11 +126,16 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
private CompatUIConfiguration mCompatUIConfiguration;
private TestShellExecutor mExecutor;
+ private FakeCompatUIStatusManagerTest mCompatUIStatus;
+ private CompatUIStatusManager mCompatUIStatusManager;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mExecutor = new TestShellExecutor();
+ mCompatUIStatus = new FakeCompatUIStatusManagerTest();
+ mCompatUIStatusManager = new CompatUIStatusManager(mCompatUIStatus.mWriter,
+ mCompatUIStatus.mReader);
mCompatUIConfiguration = new CompatUIConfiguration(mContext, mExecutor) {
final Set mHasSeenSet = new HashSet<>();
@@ -153,6 +169,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_notEligible_doesNotCreateLayout() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ false);
@@ -162,6 +179,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_eligibleAndDocked_doesNotCreateLayout() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */
true, /* isDocked */ true);
@@ -172,6 +190,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_taskBarEducationIsShowing_doesNotCreateLayout() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true,
USER_ID_1, /* isTaskbarEduShowing= */ true);
@@ -182,6 +201,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_canShowFalse_returnsTrueButDoesNotCreateLayout() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -192,6 +212,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_canShowTrue_createsLayoutCorrectly() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -238,6 +259,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_alreadyShownToUser_createsLayoutForOtherUserOnly() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true,
USER_ID_1, /* isTaskbarEduShowing= */ false);
@@ -271,6 +293,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_windowManagerReleasedBeforeTransitionsIsIdle_doesNotStartAnim() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -288,6 +311,8 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
+ @DisableFlags(FLAG_APP_COMPAT_ASYNC_RELAYOUT)
public void testUpdateCompatInfo_updatesLayoutCorrectly() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -316,6 +341,37 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
+ @EnableFlags(FLAG_APP_COMPAT_ASYNC_RELAYOUT)
+ public void testUpdateCompatInfo_updatesLayoutCorrectlyAsync() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
+
+ assertTrue(windowManager.createLayout(/* canShow= */ true));
+ LetterboxEduDialogLayout layout = windowManager.mLayout;
+ assertNotNull(layout);
+
+ assertTrue(windowManager.updateCompatInfo(
+ createTaskInfo(/* eligible= */ true, USER_ID_1, new Rect(50, 25, 150, 75)),
+ mTaskListener, /* canShow= */ true));
+
+ verifyLayout(layout, layout.getLayoutParams(), /* expectedWidth= */ 100,
+ /* expectedHeight= */ 50, /* expectedExtraTopMargin= */ 0,
+ /* expectedExtraBottomMargin= */ 0);
+ verify(mViewHost).relayout(mWindowAttrsCaptor.capture(), any());
+ assertThat(mWindowAttrsCaptor.getValue()).isEqualTo(layout.getLayoutParams());
+
+ // Window manager should be released (without animation) when eligible becomes false.
+ assertFalse(windowManager.updateCompatInfo(createTaskInfo(/* eligible= */ false),
+ mTaskListener, /* canShow= */ true));
+
+ verify(windowManager).release();
+ verify(mOnDismissCallback, never()).accept(any());
+ verify(mAnimationController, never()).startExitAnimation(any(), any());
+ assertNull(windowManager.mLayout);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfo_notEligibleUntilUpdate_createsLayoutAfterUpdate() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ false);
@@ -329,6 +385,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfo_canShowFalse_doesNothing() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -343,6 +400,8 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
+ @DisableFlags(FLAG_APP_COMPAT_ASYNC_RELAYOUT)
public void testUpdateDisplayLayout_updatesLayoutCorrectly() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -364,6 +423,30 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
+ @EnableFlags(FLAG_APP_COMPAT_ASYNC_RELAYOUT)
+ public void testUpdateDisplayLayout_updatesLayoutCorrectlyAsync() {
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
+
+ assertTrue(windowManager.createLayout(/* canShow= */ true));
+ LetterboxEduDialogLayout layout = windowManager.mLayout;
+ assertNotNull(layout);
+
+ int newDisplayCutoutTop = DISPLAY_CUTOUT_TOP + 7;
+ int newDisplayCutoutBottom = DISPLAY_CUTOUT_BOTTOM + 9;
+ windowManager.updateDisplayLayout(createDisplayLayout(
+ Insets.of(DISPLAY_CUTOUT_HORIZONTAL, newDisplayCutoutTop,
+ DISPLAY_CUTOUT_HORIZONTAL, newDisplayCutoutBottom)));
+
+ verifyLayout(layout, layout.getLayoutParams(), /* expectedWidth= */ TASK_WIDTH,
+ /* expectedHeight= */ TASK_HEIGHT, /* expectedExtraTopMargin= */
+ newDisplayCutoutTop, /* expectedExtraBottomMargin= */ newDisplayCutoutBottom);
+ verify(mViewHost).relayout(mWindowAttrsCaptor.capture(), any());
+ assertThat(mWindowAttrsCaptor.getValue()).isEqualTo(layout.getLayoutParams());
+ }
+
+ @Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testRelease_animationIsCancelled() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -374,6 +457,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testDeviceThemeChange_educationDialogUnseen_recreated() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
@@ -392,6 +476,21 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
assertFalse(windowManager.needsToBeRecreated(newTaskInfo, mTaskListener));
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_COMPAT_UI_VISIBILITY_STATUS)
+ public void testCompatUIStatus_dialogIsShown() {
+ // We display the dialog
+ LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true,
+ USER_ID_1, /* isTaskbarEduShowing= */ false);
+ assertTrue(windowManager.createLayout(/* canShow= */ true));
+ assertNotNull(windowManager.mLayout);
+ assertEquals(/* expected= */ COMPAT_UI_EDUCATION_VISIBLE, mCompatUIStatus.mCurrentStatus);
+
+ // We dismiss
+ windowManager.release();
+ assertEquals(/* expected= */ COMPAT_UI_EDUCATION_HIDDEN, mCompatUIStatus.mCurrentStatus);
+ }
+
private void verifyLayout(LetterboxEduDialogLayout layout, ViewGroup.LayoutParams params,
int expectedWidth, int expectedHeight, int expectedExtraTopMargin,
int expectedExtraBottomMargin) {
@@ -442,7 +541,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
windowManager = new LetterboxEduWindowManager(mContext,
createTaskInfo(eligible, userId), mSyncTransactionQueue, mTaskListener,
createDisplayLayout(), mTransitions, mOnDismissCallback, mAnimationController,
- mDockStateReader, mCompatUIConfiguration);
+ mDockStateReader, mCompatUIConfiguration, mCompatUIStatusManager);
spyOn(windowManager);
doReturn(mViewHost).when(windowManager).createSurfaceViewHost();
doReturn(isTaskbarEduShowing).when(windowManager).isTaskbarEduShowing();
@@ -477,7 +576,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.userId = userId;
taskInfo.taskId = TASK_ID;
- taskInfo.appCompatTaskInfo.topActivityEligibleForLetterboxEducation = eligible;
+ taskInfo.appCompatTaskInfo.setEligibleForLetterboxEducation(eligible);
taskInfo.configuration.windowConfiguration.setBounds(bounds);
return taskInfo;
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java
index 4f71b83179..31ea8f7635 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.compatui;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
@@ -23,6 +25,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.app.TaskInfo;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
@@ -74,6 +77,7 @@ public class ReachabilityEduLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnFinishInflate() {
assertNotNull(mMoveUpButton);
assertNotNull(mMoveDownButton);
@@ -82,6 +86,7 @@ public class ReachabilityEduLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void handleVisibility_educationNotEnabled_buttonsAreHidden() {
mLayout.handleVisibility(/* horizontalEnabled */ false, /* verticalEnabled */
false, /* letterboxVerticalPosition */
@@ -94,6 +99,7 @@ public class ReachabilityEduLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void handleVisibility_horizontalEducationEnableduiConfigurationIsUpdated() {
mLayout.handleVisibility(/* horizontalEnabled */ true, /* verticalEnabled */
false, /* letterboxVerticalPosition */ -1, /* letterboxHorizontalPosition */
@@ -106,6 +112,7 @@ public class ReachabilityEduLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void handleVisibility_verticalEducationEnabled_uiConfigurationIsUpdated() {
mLayout.handleVisibility(/* horizontalEnabled */ false, /* verticalEnabled */
true, /* letterboxVerticalPosition */ 0, /* letterboxHorizontalPosition */
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
index 5867a8553d..1b2c094477 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
@@ -16,12 +16,15 @@
package com.android.wm.shell.compatui;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.res.Configuration;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
@@ -80,6 +83,7 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateLayout_notEligible_doesNotCreateLayout() {
assertFalse(mWindowManager.createLayout(/* canShow= */ true));
@@ -87,6 +91,7 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDockedStateHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
newTaskInfo.configuration.uiMode =
@@ -97,6 +102,7 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDarkLightThemeHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
mTaskInfo.configuration.uiMode =
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java
index e2dcdb0e91..5075453d8c 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.compatui;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -23,6 +25,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.testing.AndroidTestingRunner;
import android.view.LayoutInflater;
import android.view.View;
@@ -49,7 +52,7 @@ import java.util.function.Consumer;
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class RestartDialogLayoutTest extends ShellTestCase {
+public class RestartDialogLayoutTest extends ShellTestCase {
@Mock private Runnable mDismissCallback;
@Mock private Consumer mRestartCallback;
@@ -76,6 +79,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnFinishInflate() {
assertEquals(mLayout.getDialogContainerView(),
mLayout.findViewById(R.id.letterbox_restart_dialog_container));
@@ -86,6 +90,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDismissButtonClicked() {
assertTrue(mDismissButton.performClick());
@@ -93,6 +98,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnRestartButtonClickedWithoutCheckbox() {
mDontRepeatCheckBox.setChecked(false);
assertTrue(mRestartButton.performClick());
@@ -101,6 +107,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnRestartButtonClickedWithCheckbox() {
mDontRepeatCheckBox.setChecked(true);
assertTrue(mRestartButton.performClick());
@@ -109,6 +116,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnBackgroundClickedDoesntDismiss() {
assertFalse(mLayout.performClick());
@@ -116,6 +124,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnDialogContainerClicked() {
assertTrue(mDialogContainer.performClick());
@@ -124,6 +133,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testSetDismissOnClickListenerNull() {
mLayout.setDismissOnClickListener(null);
@@ -135,6 +145,7 @@ public class RestartDialogLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testSetRestartOnClickListenerNull() {
mLayout.setRestartOnClickListener(null);
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java
index 9f109a1d0f..779a5ca106 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java
@@ -16,9 +16,12 @@
package com.android.wm.shell.compatui;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
+
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.res.Configuration;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
@@ -76,6 +79,7 @@ public class RestartDialogWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDockedStateHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
newTaskInfo.configuration.uiMode =
@@ -86,6 +90,7 @@ public class RestartDialogWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDarkLightThemeHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
mTaskInfo.configuration.uiMode =
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
index 02316125bc..2b4d5f1257 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
@@ -16,18 +16,17 @@
package com.android.wm.shell.compatui;
-import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
-
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
-import android.app.CameraCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
import android.content.ComponentName;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.LayoutInflater;
@@ -89,7 +88,7 @@ public class UserAspectRatioSettingsLayoutTest extends ShellTestCase {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
+ mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false);
mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo,
mSyncTransactionQueue, mTaskListener, new DisplayLayout(),
new CompatUIController.CompatUIHintsState(),
@@ -107,6 +106,7 @@ public class UserAspectRatioSettingsLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnClickForUserAspectRatioSettingsButton() {
final ImageButton button = mLayout.findViewById(R.id.user_aspect_ratio_settings_button);
button.performClick();
@@ -123,6 +123,7 @@ public class UserAspectRatioSettingsLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnLongClickForUserAspectRatioButton() {
doNothing().when(mWindowManager).onUserAspectRatioSettingsButtonLongClicked();
@@ -133,6 +134,7 @@ public class UserAspectRatioSettingsLayoutTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnClickForUserAspectRatioSettingsHint() {
mWindowManager.mHasUserAspectRatioSettingsButton = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -143,13 +145,10 @@ public class UserAspectRatioSettingsLayoutTest extends ShellTestCase {
verify(mLayout).setUserAspectRatioSettingsHintVisibility(/* show= */ false);
}
- private static TaskInfo createTaskInfo(boolean hasSizeCompat,
- @CameraCompatControlState int cameraCompatControlState) {
+ private static TaskInfo createTaskInfo(boolean hasSizeCompat) {
ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.taskId = TASK_ID;
- taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat;
- taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState =
- cameraCompatControlState;
+ taskInfo.appCompatTaskInfo.setTopActivityInSizeCompat(hasSizeCompat);
taskInfo.realActivity = new ComponentName("com.mypackage.test", "TestActivity");
return taskInfo;
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
index 94e168ed70..af7c1f5d76 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
@@ -22,6 +22,7 @@ import static android.hardware.usb.UsbManager.ACTION_USB_STATE;
import static android.view.WindowInsets.Type.navigationBars;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -39,6 +40,7 @@ import android.content.ComponentName;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.util.Pair;
@@ -138,6 +140,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testCreateUserAspectRatioButton() {
// Doesn't create layout if show is false.
mWindowManager.mHasUserAspectRatioSettingsButton = true;
@@ -178,6 +181,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testRelease() {
mWindowManager.mHasUserAspectRatioSettingsButton = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -190,6 +194,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfo() {
mWindowManager.mHasUserAspectRatioSettingsButton = true;
mWindowManager.createLayout(/* canShow= */ true);
@@ -242,6 +247,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateCompatInfoLayoutNotInflatedYet() {
mWindowManager.mHasUserAspectRatioSettingsButton = true;
mWindowManager.createLayout(/* canShow= */ false);
@@ -267,6 +273,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testEligibleButtonHiddenIfLetterboxBoundsEqualToStableBounds() {
TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
@@ -292,6 +299,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUserFullscreenOverrideEnabled_buttonAlwaysShown() {
TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
@@ -302,7 +310,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
// layout should be inflated
taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = stableBounds.height();
taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = stableBounds.width();
- taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled = true;
+ taskInfo.appCompatTaskInfo.setUserFullscreenOverrideEnabled(true);
mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
@@ -310,6 +318,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateDisplayLayout() {
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 1000;
@@ -329,6 +338,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateDisplayLayoutInsets() {
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 1000;
@@ -353,6 +363,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testUpdateVisibility() {
// Create button if it is not created.
mWindowManager.removeLayout();
@@ -378,6 +389,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testLayoutHasUserAspectRatioSettingsButton() {
clearInvocations(mWindowManager);
spyOn(mWindowManager);
@@ -411,6 +423,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testAttachToParentSurface() {
final SurfaceControl.Builder b = new SurfaceControl.Builder();
mWindowManager.attachToParentSurface(b);
@@ -419,6 +432,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnUserAspectRatioButtonClicked() {
mWindowManager.onUserAspectRatioSettingsButtonClicked();
@@ -433,6 +447,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testOnUserAspectRatioButtonLongClicked_showHint() {
// Not create hint popup.
mWindowManager.mHasUserAspectRatioSettingsButton = true;
@@ -448,6 +463,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testWhenDockedStateHasChanged_needsToBeRecreated() {
ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
newTaskInfo.configuration.uiMode |= Configuration.UI_MODE_TYPE_DESK;
@@ -459,9 +475,9 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
boolean topActivityBoundsLetterboxed, String action, String category) {
ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.taskId = TASK_ID;
- taskInfo.appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton =
- eligibleForUserAspectRatioButton;
- taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed = topActivityBoundsLetterboxed;
+ taskInfo.appCompatTaskInfo.setEligibleForUserAspectRatioButton(
+ eligibleForUserAspectRatioButton);
+ taskInfo.appCompatTaskInfo.setTopActivityLetterboxed(topActivityBoundsLetterboxed);
taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK;
taskInfo.realActivity = new ComponentName("com.mypackage.test", "TestActivity");
taskInfo.baseIntent = new Intent(action).addCategory(category);
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
index 4548fcb06c..695cf600b3 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -16,154 +16,772 @@
package com.android.wm.shell.desktopmode
-import com.android.dx.mockito.inline.extended.ExtendedMockito
+import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.Rect
+import android.platform.test.annotations.EnableFlags
+import com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker
+import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
+import com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions
import com.android.internal.util.FrameworkStatsLog
import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.window.flags.Flags
+import com.android.wm.shell.EventLogTags
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.FocusReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.NO_SESSION_ID
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskSizeUpdate
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate
-import kotlinx.coroutines.runBlocking
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_FOCUS_REASON
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_MINIMIZE_REASON
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_UNMINIMIZE_REASON
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.whenever
-/**
- * Tests for [DesktopModeEventLogger].
- */
-class DesktopModeEventLoggerTest {
+/** Tests for [DesktopModeEventLogger]. */
+class DesktopModeEventLoggerTest : ShellTestCase() {
- private val desktopModeEventLogger = DesktopModeEventLogger()
+ private val desktopModeEventLogger = DesktopModeEventLogger()
+ val displayController = mock()
+ val displayLayout = mock()
@JvmField
- @Rule
- val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
- .mockStatic(FrameworkStatsLog::class.java).build()!!
+ @Rule()
+ val extendedMockitoRule =
+ ExtendedMockitoRule.Builder(this)
+ .mockStatic(FrameworkStatsLog::class.java)
+ .mockStatic(EventLogTags::class.java)
+ .build()!!
- @Test
- fun logSessionEnter_enterReason() = runBlocking {
- desktopModeEventLogger.logSessionEnter(sessionId = SESSION_ID, EnterReason.UNKNOWN_ENTER)
+ @Before
+ fun setUp() {
+ doReturn(displayLayout).whenever(displayController).getDisplayLayout(anyInt())
+ doReturn(DISPLAY_WIDTH).whenever(displayLayout).width()
+ doReturn(DISPLAY_HEIGHT).whenever(displayLayout).height()
+ }
- ExtendedMockito.verify {
- FrameworkStatsLog.write(
- eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
- /* event */
- eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER),
- /* enter_reason */
- eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__UNKNOWN_ENTER),
- /* exit_reason */
- eq(0),
- /* sessionId */
- eq(SESSION_ID)
- )
- }
+ @After
+ fun tearDown() {
+ clearInvocations(staticMockMarker(FrameworkStatsLog::class.java))
+ clearInvocations(staticMockMarker(EventLogTags::class.java))
}
@Test
- fun logSessionExit_exitReason() = runBlocking {
- desktopModeEventLogger.logSessionExit(sessionId = SESSION_ID, ExitReason.UNKNOWN_EXIT)
+ fun logSessionEnter_logsEnterReasonWithNewSessionId() {
+ desktopModeEventLogger.logSessionEnter(EnterReason.KEYBOARD_SHORTCUT_ENTER)
- ExtendedMockito.verify {
- FrameworkStatsLog.write(
- eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
- /* event */
- eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__EXIT),
- /* enter_reason */
- eq(0),
- /* exit_reason */
- eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__UNKNOWN_EXIT),
- /* sessionId */
- eq(SESSION_ID)
+ val sessionId = desktopModeEventLogger.currentSessionId.get()
+ assertThat(sessionId).isNotEqualTo(NO_SESSION_ID)
+ verifyOnlyOneUiChangedLogging(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER,
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__KEYBOARD_SHORTCUT_ENTER,
+ 0,
+ sessionId,
+ )
+ verify {
+ EventLogTags.writeWmShellEnterDesktopMode(
+ eq(EnterReason.KEYBOARD_SHORTCUT_ENTER.reason),
+ eq(sessionId),
)
}
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
- fun logTaskAdded_taskUpdate() = runBlocking {
- desktopModeEventLogger.logTaskAdded(sessionId = SESSION_ID, TASK_UPDATE)
+ fun logSessionEnter_ongoingSession_logsEnterReasonWithNewSessionId() {
+ val previousSessionId = startDesktopModeSession()
- ExtendedMockito.verify {
- FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
- /* task_event */
+ desktopModeEventLogger.logSessionEnter(EnterReason.KEYBOARD_SHORTCUT_ENTER)
+
+ val sessionId = desktopModeEventLogger.currentSessionId.get()
+ assertThat(sessionId).isNotEqualTo(NO_SESSION_ID)
+ assertThat(sessionId).isNotEqualTo(previousSessionId)
+ verifyOnlyOneUiChangedLogging(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER,
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__KEYBOARD_SHORTCUT_ENTER,
+ /* exit_reason */
+ 0,
+ sessionId,
+ )
+ verify {
+ EventLogTags.writeWmShellEnterDesktopMode(
+ eq(EnterReason.KEYBOARD_SHORTCUT_ENTER.reason),
+ eq(sessionId),
+ )
+ }
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
+ }
+
+ @Test
+ fun logSessionExit_noOngoingSession_doesNotLog() {
+ desktopModeEventLogger.logSessionExit(ExitReason.DRAG_TO_EXIT)
+
+ verifyNoLogging()
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
+ }
+
+ @Test
+ fun logSessionExit_logsExitReasonAndClearsSessionId() {
+ val sessionId = startDesktopModeSession()
+
+ desktopModeEventLogger.logSessionExit(ExitReason.DRAG_TO_EXIT)
+
+ verifyOnlyOneUiChangedLogging(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__EXIT,
+ /* enter_reason */
+ 0,
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__DRAG_TO_EXIT,
+ sessionId,
+ )
+ verify {
+ EventLogTags.writeWmShellExitDesktopMode(
+ eq(ExitReason.DRAG_TO_EXIT.reason),
+ eq(sessionId),
+ )
+ }
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
+ assertThat(desktopModeEventLogger.currentSessionId.get()).isEqualTo(NO_SESSION_ID)
+ }
+
+ @Test
+ fun logTaskAdded_noOngoingSession_doesNotLog() {
+ desktopModeEventLogger.logTaskAdded(TASK_UPDATE)
+
+ verifyNoLogging()
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
+ }
+
+ @Test
+ fun logTaskAdded_logsTaskUpdate() {
+ val sessionId = startDesktopModeSession()
+
+ desktopModeEventLogger.logTaskAdded(TASK_UPDATE)
+
+ verifyOnlyOneTaskUpdateLogging(
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED,
+ TASK_UPDATE.instanceId,
+ TASK_UPDATE.uid,
+ TASK_UPDATE.taskHeight,
+ TASK_UPDATE.taskWidth,
+ TASK_UPDATE.taskX,
+ TASK_UPDATE.taskY,
+ sessionId,
+ UNSET_MINIMIZE_REASON,
+ UNSET_UNMINIMIZE_REASON,
+ TASK_COUNT,
+ UNSET_FOCUS_REASON,
+ )
+ verify {
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED),
- /* instance_id */
eq(TASK_UPDATE.instanceId),
- /* uid */
eq(TASK_UPDATE.uid),
- /* task_height */
eq(TASK_UPDATE.taskHeight),
- /* task_width */
eq(TASK_UPDATE.taskWidth),
- /* task_x */
eq(TASK_UPDATE.taskX),
- /* task_y */
eq(TASK_UPDATE.taskY),
- /* session_id */
- eq(SESSION_ID))
+ eq(sessionId),
+ eq(UNSET_MINIMIZE_REASON),
+ eq(UNSET_UNMINIMIZE_REASON),
+ eq(TASK_COUNT),
+ eq(UNSET_FOCUS_REASON),
+ )
}
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
- fun logTaskRemoved_taskUpdate() = runBlocking {
- desktopModeEventLogger.logTaskRemoved(sessionId = SESSION_ID, TASK_UPDATE)
+ fun logTaskRemoved_noOngoingSession_doesNotLog() {
+ desktopModeEventLogger.logTaskRemoved(TASK_UPDATE)
- ExtendedMockito.verify {
- FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
- /* task_event */
+ verifyNoLogging()
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
+ }
+
+ @Test
+ fun logTaskRemoved_taskUpdate() {
+ val sessionId = startDesktopModeSession()
+
+ desktopModeEventLogger.logTaskRemoved(TASK_UPDATE)
+
+ verifyOnlyOneTaskUpdateLogging(
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED,
+ TASK_UPDATE.instanceId,
+ TASK_UPDATE.uid,
+ TASK_UPDATE.taskHeight,
+ TASK_UPDATE.taskWidth,
+ TASK_UPDATE.taskX,
+ TASK_UPDATE.taskY,
+ sessionId,
+ UNSET_MINIMIZE_REASON,
+ UNSET_UNMINIMIZE_REASON,
+ TASK_COUNT,
+ UNSET_FOCUS_REASON,
+ )
+ verify {
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED),
- /* instance_id */
eq(TASK_UPDATE.instanceId),
- /* uid */
eq(TASK_UPDATE.uid),
- /* task_height */
eq(TASK_UPDATE.taskHeight),
- /* task_width */
eq(TASK_UPDATE.taskWidth),
- /* task_x */
eq(TASK_UPDATE.taskX),
- /* task_y */
eq(TASK_UPDATE.taskY),
- /* session_id */
- eq(SESSION_ID))
+ eq(sessionId),
+ eq(UNSET_MINIMIZE_REASON),
+ eq(UNSET_UNMINIMIZE_REASON),
+ eq(TASK_COUNT),
+ eq(UNSET_FOCUS_REASON),
+ )
}
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
}
@Test
- fun logTaskInfoChanged_taskUpdate() = runBlocking {
- desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID, TASK_UPDATE)
+ fun logTaskInfoChanged_noOngoingSession_doesNotLog() {
+ desktopModeEventLogger.logTaskInfoChanged(TASK_UPDATE)
- ExtendedMockito.verify {
- FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
- /* task_event */
- eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
- /* instance_id */
+ verifyNoLogging()
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
+ }
+
+ @Test
+ fun logTaskInfoChanged_taskUpdate() {
+ val sessionId = startDesktopModeSession()
+
+ desktopModeEventLogger.logTaskInfoChanged(TASK_UPDATE)
+
+ verifyOnlyOneTaskUpdateLogging(
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED,
+ TASK_UPDATE.instanceId,
+ TASK_UPDATE.uid,
+ TASK_UPDATE.taskHeight,
+ TASK_UPDATE.taskWidth,
+ TASK_UPDATE.taskX,
+ TASK_UPDATE.taskY,
+ sessionId,
+ UNSET_MINIMIZE_REASON,
+ UNSET_UNMINIMIZE_REASON,
+ TASK_COUNT,
+ UNSET_FOCUS_REASON,
+ )
+ verify {
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
+ eq(
+ FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED
+ ),
eq(TASK_UPDATE.instanceId),
- /* uid */
eq(TASK_UPDATE.uid),
- /* task_height */
eq(TASK_UPDATE.taskHeight),
- /* task_width */
eq(TASK_UPDATE.taskWidth),
- /* task_x */
eq(TASK_UPDATE.taskX),
- /* task_y */
eq(TASK_UPDATE.taskY),
+ eq(sessionId),
+ eq(UNSET_MINIMIZE_REASON),
+ eq(UNSET_UNMINIMIZE_REASON),
+ eq(TASK_COUNT),
+ eq(UNSET_FOCUS_REASON),
+ )
+ }
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
+ }
+
+ @Test
+ fun logTaskInfoChanged_logsTaskUpdateWithMinimizeReason() {
+ val sessionId = startDesktopModeSession()
+
+ desktopModeEventLogger.logTaskInfoChanged(
+ createTaskUpdate(minimizeReason = MinimizeReason.TASK_LIMIT)
+ )
+
+ verifyOnlyOneTaskUpdateLogging(
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED,
+ TASK_UPDATE.instanceId,
+ TASK_UPDATE.uid,
+ TASK_UPDATE.taskHeight,
+ TASK_UPDATE.taskWidth,
+ TASK_UPDATE.taskX,
+ TASK_UPDATE.taskY,
+ sessionId,
+ MinimizeReason.TASK_LIMIT.reason,
+ UNSET_UNMINIMIZE_REASON,
+ TASK_COUNT,
+ UNSET_FOCUS_REASON,
+ )
+ verify {
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
+ eq(
+ FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED
+ ),
+ eq(TASK_UPDATE.instanceId),
+ eq(TASK_UPDATE.uid),
+ eq(TASK_UPDATE.taskHeight),
+ eq(TASK_UPDATE.taskWidth),
+ eq(TASK_UPDATE.taskX),
+ eq(TASK_UPDATE.taskY),
+ eq(sessionId),
+ eq(MinimizeReason.TASK_LIMIT.reason),
+ eq(UNSET_UNMINIMIZE_REASON),
+ eq(TASK_COUNT),
+ eq(UNSET_FOCUS_REASON),
+ )
+ }
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
+ }
+
+ @Test
+ fun logTaskInfoChanged_logsTaskUpdateWithUnminimizeReason() {
+ val sessionId = startDesktopModeSession()
+
+ desktopModeEventLogger.logTaskInfoChanged(
+ createTaskUpdate(unminimizeReason = UnminimizeReason.TASKBAR_TAP)
+ )
+
+ verifyOnlyOneTaskUpdateLogging(
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED,
+ TASK_UPDATE.instanceId,
+ TASK_UPDATE.uid,
+ TASK_UPDATE.taskHeight,
+ TASK_UPDATE.taskWidth,
+ TASK_UPDATE.taskX,
+ TASK_UPDATE.taskY,
+ sessionId,
+ UNSET_MINIMIZE_REASON,
+ UnminimizeReason.TASKBAR_TAP.reason,
+ TASK_COUNT,
+ UNSET_FOCUS_REASON,
+ )
+ verify {
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
+ eq(
+ FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED
+ ),
+ eq(TASK_UPDATE.instanceId),
+ eq(TASK_UPDATE.uid),
+ eq(TASK_UPDATE.taskHeight),
+ eq(TASK_UPDATE.taskWidth),
+ eq(TASK_UPDATE.taskX),
+ eq(TASK_UPDATE.taskY),
+ eq(sessionId),
+ eq(UNSET_MINIMIZE_REASON),
+ eq(UnminimizeReason.TASKBAR_TAP.reason),
+ eq(TASK_COUNT),
+ eq(UNSET_FOCUS_REASON),
+ )
+ }
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
+ }
+
+ @Test
+ fun logTaskInfoChanged_logsTaskUpdateWithFocusReason() {
+ val sessionId = startDesktopModeSession()
+
+ desktopModeEventLogger.logTaskInfoChanged(
+ createTaskUpdate(focusChangesReason = FocusReason.UNKNOWN)
+ )
+
+ verifyOnlyOneTaskUpdateLogging(
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED,
+ TASK_UPDATE.instanceId,
+ TASK_UPDATE.uid,
+ TASK_UPDATE.taskHeight,
+ TASK_UPDATE.taskWidth,
+ TASK_UPDATE.taskX,
+ TASK_UPDATE.taskY,
+ sessionId,
+ UNSET_MINIMIZE_REASON,
+ UNSET_UNMINIMIZE_REASON,
+ TASK_COUNT,
+ FocusReason.UNKNOWN.reason,
+ )
+ verify {
+ EventLogTags.writeWmShellDesktopModeTaskUpdate(
+ eq(
+ FrameworkStatsLog
+ .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED
+ ),
+ eq(TASK_UPDATE.instanceId),
+ eq(TASK_UPDATE.uid),
+ eq(TASK_UPDATE.taskHeight),
+ eq(TASK_UPDATE.taskWidth),
+ eq(TASK_UPDATE.taskX),
+ eq(TASK_UPDATE.taskY),
+ eq(sessionId),
+ eq(UNSET_MINIMIZE_REASON),
+ eq(UNSET_UNMINIMIZE_REASON),
+ eq(TASK_COUNT),
+ eq(FocusReason.UNKNOWN.reason),
+ )
+ }
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
+ }
+
+ @Test
+ fun logTaskResizingStarted_noOngoingSession_doesNotLog() {
+ desktopModeEventLogger.logTaskResizingStarted(
+ ResizeTrigger.CORNER,
+ InputMethod.UNKNOWN_INPUT_METHOD,
+ createTaskInfo(),
+ )
+
+ verifyNoLogging()
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESIZING_METRICS)
+ fun logTaskResizingStarted_logsTaskSizeUpdatedWithStartResizingStage() {
+ val sessionId = startDesktopModeSession()
+
+ desktopModeEventLogger.logTaskResizingStarted(
+ ResizeTrigger.CORNER,
+ InputMethod.UNKNOWN_INPUT_METHOD,
+ createTaskInfo(),
+ TASK_SIZE_UPDATE.taskWidth,
+ TASK_SIZE_UPDATE.taskHeight,
+ displayController,
+ )
+
+ verifyOnlyOneTaskSizeUpdatedLogging(
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER,
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE,
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD,
+ sessionId,
+ TASK_SIZE_UPDATE.instanceId,
+ TASK_SIZE_UPDATE.uid,
+ TASK_SIZE_UPDATE.taskWidth,
+ TASK_SIZE_UPDATE.taskHeight,
+ DISPLAY_AREA,
+ )
+ }
+
+ @Test
+ fun logTaskResizingEnded_noOngoingSession_doesNotLog() {
+ desktopModeEventLogger.logTaskResizingEnded(
+ ResizeTrigger.CORNER,
+ InputMethod.UNKNOWN_INPUT_METHOD,
+ createTaskInfo(),
+ )
+
+ verifyNoLogging()
+ verifyNoMoreInteractions(staticMockMarker(EventLogTags::class.java))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESIZING_METRICS)
+ fun logTaskResizingEnded_logsTaskSizeUpdatedWithEndResizingStage() {
+ val sessionId = startDesktopModeSession()
+
+ desktopModeEventLogger.logTaskResizingEnded(
+ ResizeTrigger.CORNER,
+ InputMethod.UNKNOWN_INPUT_METHOD,
+ createTaskInfo(),
+ displayController = displayController,
+ )
+
+ verifyOnlyOneTaskSizeUpdatedLogging(
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER,
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE,
+ FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD,
+ sessionId,
+ TASK_SIZE_UPDATE.instanceId,
+ TASK_SIZE_UPDATE.uid,
+ TASK_SIZE_UPDATE.taskWidth,
+ TASK_SIZE_UPDATE.taskHeight,
+ DISPLAY_AREA,
+ )
+ }
+
+ private fun startDesktopModeSession(): Int {
+ desktopModeEventLogger.logSessionEnter(EnterReason.KEYBOARD_SHORTCUT_ENTER)
+ clearInvocations(staticMockMarker(FrameworkStatsLog::class.java))
+ clearInvocations(staticMockMarker(EventLogTags::class.java))
+ return desktopModeEventLogger.currentSessionId.get()
+ }
+
+ @Test
+ fun logTaskInfoStateInit_logsTaskInfoChangedStateInit() {
+ desktopModeEventLogger.logTaskInfoStateInit()
+ verify {
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+ /* task_event */
+ eq(
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INIT_STATSD
+ ),
+ /* instance_id */
+ eq(0),
+ /* uid */
+ eq(0),
+ /* task_height */
+ eq(0),
+ /* task_width */
+ eq(0),
+ /* task_x */
+ eq(0),
+ /* task_y */
+ eq(0),
/* session_id */
- eq(SESSION_ID))
+ eq(0),
+ /* minimize_reason */
+ eq(UNSET_MINIMIZE_REASON),
+ /* unminimize_reason */
+ eq(UNSET_UNMINIMIZE_REASON),
+ /* visible_task_count */
+ eq(0),
+ /* focus_reason */
+ eq(UNSET_FOCUS_REASON),
+ )
}
}
- companion object {
- private const val SESSION_ID = 1
+ private fun createTaskInfo(): RunningTaskInfo =
+ TestRunningTaskInfoBuilder()
+ .setTaskId(TASK_ID)
+ .setUid(TASK_UID)
+ .setBounds(Rect(TASK_X, TASK_Y, TASK_WIDTH, TASK_HEIGHT))
+ .build()
+
+ private fun verifyNoLogging() {
+ verify(
+ {
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ )
+ },
+ never(),
+ )
+ verify(
+ {
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ )
+ },
+ never(),
+ )
+ verify(
+ {
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ )
+ },
+ never(),
+ )
+ }
+
+ private fun verifyOnlyOneUiChangedLogging(
+ event: Int,
+ enterReason: Int,
+ exitReason: Int,
+ sessionId: Int,
+ ) {
+ verify({
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
+ eq(event),
+ eq(enterReason),
+ eq(exitReason),
+ eq(sessionId),
+ )
+ })
+ verify({
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ )
+ })
+ }
+
+ private fun verifyOnlyOneTaskUpdateLogging(
+ taskEvent: Int,
+ instanceId: Int,
+ uid: Int,
+ taskHeight: Int,
+ taskWidth: Int,
+ taskX: Int,
+ taskY: Int,
+ sessionId: Int,
+ minimizeReason: Int,
+ unminimizeReason: Int,
+ visibleTaskCount: Int,
+ focusChangedReason: Int,
+ ) {
+ verify({
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+ eq(taskEvent),
+ eq(instanceId),
+ eq(uid),
+ eq(taskHeight),
+ eq(taskWidth),
+ eq(taskX),
+ eq(taskY),
+ eq(sessionId),
+ eq(minimizeReason),
+ eq(unminimizeReason),
+ eq(visibleTaskCount),
+ eq(focusChangedReason),
+ )
+ })
+ verify({
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ )
+ })
+ }
+
+ private fun verifyOnlyOneTaskSizeUpdatedLogging(
+ resizeTrigger: Int,
+ resizingStage: Int,
+ inputMethod: Int,
+ sessionId: Int,
+ instanceId: Int,
+ uid: Int,
+ taskWidth: Int,
+ taskHeight: Int,
+ displayArea: Int,
+ ) {
+ verify({
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
+ eq(resizeTrigger),
+ eq(resizingStage),
+ eq(inputMethod),
+ eq(sessionId),
+ eq(instanceId),
+ eq(uid),
+ eq(taskWidth),
+ eq(taskHeight),
+ eq(displayArea),
+ )
+ })
+ verify({
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ )
+ })
+ }
+
+ private companion object {
private const val TASK_ID = 1
private const val TASK_UID = 1
private const val TASK_X = 0
private const val TASK_Y = 0
private const val TASK_HEIGHT = 100
private const val TASK_WIDTH = 100
+ private const val TASK_COUNT = 1
+ private const val DISPLAY_WIDTH = 500
+ private const val DISPLAY_HEIGHT = 500
+ private const val DISPLAY_AREA = DISPLAY_HEIGHT * DISPLAY_WIDTH
- private val TASK_UPDATE = TaskUpdate(
- TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y
- )
+ private val TASK_UPDATE =
+ TaskUpdate(
+ TASK_ID,
+ TASK_UID,
+ TASK_HEIGHT,
+ TASK_WIDTH,
+ TASK_X,
+ TASK_Y,
+ visibleTaskCount = TASK_COUNT,
+ )
+
+ private val TASK_SIZE_UPDATE =
+ TaskSizeUpdate(
+ resizeTrigger = ResizeTrigger.UNKNOWN_RESIZE_TRIGGER,
+ inputMethod = InputMethod.UNKNOWN_INPUT_METHOD,
+ TASK_ID,
+ TASK_UID,
+ TASK_HEIGHT,
+ TASK_WIDTH,
+ DISPLAY_AREA,
+ )
+
+ private fun createTaskUpdate(
+ minimizeReason: MinimizeReason? = null,
+ unminimizeReason: UnminimizeReason? = null,
+ focusChangesReason: FocusReason? = null,
+ ) =
+ TaskUpdate(
+ TASK_ID,
+ TASK_UID,
+ TASK_HEIGHT,
+ TASK_WIDTH,
+ TASK_X,
+ TASK_Y,
+ minimizeReason,
+ unminimizeReason,
+ TASK_COUNT,
+ focusChangesReason,
+ )
}
-}
\ No newline at end of file
+}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
index fb03f20f93..4a5431c79c 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -18,8 +18,11 @@ package com.android.wm.shell.desktopmode
import android.app.ActivityManager
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
-import android.content.Context
+import android.graphics.Point
+import android.graphics.Rect
import android.os.IBinder
+import android.os.SystemProperties
+import android.os.Trace
import android.testing.AndroidTestingRunner
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
@@ -34,13 +37,21 @@ import android.view.WindowManager.TRANSIT_WAKE
import android.window.IWindowContainerToken
import android.window.TransitionInfo
import android.window.TransitionInfo.Change
+import android.window.TransitionInfo.FLAG_MOVED_TO_TOP
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
-import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.FocusReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason
+import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP
+import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_TASK_LIMIT_MINIMIZE
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT
@@ -49,26 +60,29 @@ import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.FakeDesktopState
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.android.wm.shell.transition.Transitions
-import com.google.common.truth.Truth.assertThat
+import com.android.wm.shell.transition.Transitions.TRANSIT_MINIMIZE
+import java.util.Optional
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.same
+import org.mockito.kotlin.spy
import org.mockito.kotlin.times
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyNoMoreInteractions
/**
* Test class for {@link DesktopModeLoggerTransitionObserver}
@@ -77,561 +91,883 @@ import org.mockito.kotlin.verifyZeroInteractions
*/
@SmallTest
@RunWith(AndroidTestingRunner::class)
-class DesktopModeLoggerTransitionObserverTest {
-
- @JvmField
- @Rule
- val extendedMockitoRule =
- ExtendedMockitoRule.Builder(this)
- .mockStatic(DesktopModeEventLogger::class.java)
- .mockStatic(DesktopModeStatus::class.java)
- .build()!!
-
- @Mock lateinit var testExecutor: ShellExecutor
- @Mock private lateinit var mockShellInit: ShellInit
- @Mock private lateinit var transitions: Transitions
- @Mock private lateinit var context: Context
-
- private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver
- private lateinit var shellInit: ShellInit
- private lateinit var desktopModeEventLogger: DesktopModeEventLogger
-
- @Before
- fun setup() {
- doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) }
- shellInit = Mockito.spy(ShellInit(testExecutor))
- desktopModeEventLogger = mock(DesktopModeEventLogger::class.java)
-
- transitionObserver =
- DesktopModeLoggerTransitionObserver(
- context, mockShellInit, transitions, desktopModeEventLogger)
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- val initRunnableCaptor = ArgumentCaptor.forClass(Runnable::class.java)
- verify(mockShellInit).addInitCallback(initRunnableCaptor.capture(), same(transitionObserver))
- initRunnableCaptor.value.run()
- } else {
- transitionObserver.onInit()
- }
- }
-
- @Test
- fun testRegistersObserverAtInit() {
- verify(transitions).registerObserver(same(transitionObserver))
- }
-
- @Test
- fun transitOpen_notFreeformWindow_doesNotLogTaskAddedOrSessionEnter() {
- val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
- val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
-
- callOnTransitionReady(transitionInfo)
-
- verify(desktopModeEventLogger, never()).logSessionEnter(any(), any())
- verify(desktopModeEventLogger, never()).logTaskAdded(any(), any())
- }
-
- @Test
- fun transitOpen_logTaskAddedAndEnterReasonAppFreeformIntent() {
- val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
-
- callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
-
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_FREEFORM_INTENT))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
- }
-
- @Test
- fun transitEndDragToDesktop_logTaskAddedAndEnterReasonAppHandleDrag() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- // task change is finalised when drag ends
- val transitionInfo =
- TransitionInfoBuilder(Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, 0)
- .addChange(change)
- .build()
-
- callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
-
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_HANDLE_DRAG))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
- }
-
- @Test
- fun transitEnterDesktopByButtonTap_logTaskAddedAndEnterReasonButtonTap() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- val transitionInfo =
- TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON, 0)
- .addChange(change)
- .build()
-
- callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
-
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_HANDLE_MENU_BUTTON))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
- }
-
- @Test
- fun transitEnterDesktopFromAppFromOverview_logTaskAddedAndEnterReasonAppFromOverview() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- val transitionInfo =
- TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0)
- .addChange(change)
- .build()
-
- callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
-
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_FROM_OVERVIEW))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
- }
-
- @Test
- fun transitEnterDesktopFromKeyboardShortcut_logTaskAddedAndEnterReasonKeyboardShortcut() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- val transitionInfo =
- TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT, 0)
- .addChange(change)
- .build()
-
- callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
-
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.KEYBOARD_SHORTCUT_ENTER))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
- }
-
- @Test
- fun transitToFront_logTaskAddedAndEnterReasonOverview() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, 0).addChange(change).build()
-
- callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
-
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
- }
-
- @Test
- fun transitToFront_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() {
- // previous exit to overview transition
- val previousSessionId = 1
- // add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- transitionObserver.setLoggerSessionId(previousSessionId)
- val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- val previousTransitionInfo =
- TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
- .addChange(previousChange)
- .build()
-
- callOnTransitionReady(previousTransitionInfo)
-
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
-
- // Enter desktop mode from cancelled recents has no transition. Enter is detected on the
- // next transition involving freeform windows
-
- // TRANSIT_TO_FRONT
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, 0).addChange(change).build()
-
- callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
-
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
- }
-
- @Test
- fun transitChange_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() {
- // previous exit to overview transition
- val previousSessionId = 1
- // add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- transitionObserver.setLoggerSessionId(previousSessionId)
- val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- val previousTransitionInfo =
- TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
- .addChange(previousChange)
- .build()
-
- callOnTransitionReady(previousTransitionInfo)
-
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
-
- // Enter desktop mode from cancelled recents has no transition. Enter is detected on the
- // next transition involving freeform windows
-
- // TRANSIT_CHANGE
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- val transitionInfo = TransitionInfoBuilder(TRANSIT_CHANGE, 0).addChange(change).build()
-
- callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
-
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
- }
-
- @Test
- fun transitOpen_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() {
- // previous exit to overview transition
- val previousSessionId = 1
- // add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- transitionObserver.setLoggerSessionId(previousSessionId)
- val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- val previousTransitionInfo =
- TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
- .addChange(previousChange)
- .build()
-
- callOnTransitionReady(previousTransitionInfo)
-
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
-
- // Enter desktop mode from cancelled recents has no transition. Enter is detected on the
- // next transition involving freeform windows
-
- // TRANSIT_OPEN
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
-
- callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
-
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
- }
-
- @Test
- @Suppress("ktlint:standard:max-line-length")
- fun transitEnterDesktopFromAppFromOverview_previousTransitionExitToOverview_logTaskAddedAndEnterReasonAppFromOverview() {
- // Tests for AppFromOverview precedence in compared to cancelled Overview
-
- // previous exit to overview transition
- val previousSessionId = 1
- // add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- transitionObserver.setLoggerSessionId(previousSessionId)
- val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- val previousTransitionInfo =
- TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
- .addChange(previousChange)
- .build()
-
- callOnTransitionReady(previousTransitionInfo)
-
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
-
- // TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- val transitionInfo =
- TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0)
- .addChange(change)
- .build()
-
- callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
-
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_FROM_OVERVIEW))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
- }
-
- @Test
- fun transitEnterDesktopFromUnknown_logTaskAddedAndEnterReasonUnknown() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- val transitionInfo =
- TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN, 0).addChange(change).build()
-
- callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
-
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.UNKNOWN_ENTER))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
- }
-
- @Test
- fun transitWake_logTaskAddedAndEnterReasonScreenOn() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- val transitionInfo = TransitionInfoBuilder(TRANSIT_WAKE, 0).addChange(change).build()
-
- callOnTransitionReady(transitionInfo)
- val sessionId = transitionObserver.getLoggerSessionId()
-
- assertThat(sessionId).isNotNull()
- verify(desktopModeEventLogger, times(1))
- .logSessionEnter(eq(sessionId!!), eq(EnterReason.SCREEN_ON))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verifyZeroInteractions(desktopModeEventLogger)
- }
-
- @Test
- fun transitSleep_logTaskAddedAndExitReasonScreenOff_sessionIdNull() {
- val sessionId = 1
- // add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- transitionObserver.setLoggerSessionId(sessionId)
-
- val transitionInfo = TransitionInfoBuilder(TRANSIT_SLEEP).build()
- callOnTransitionReady(transitionInfo)
-
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.SCREEN_OFF))
- verifyZeroInteractions(desktopModeEventLogger)
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
- }
-
- @Test
- fun transitExitDesktopTaskDrag_logTaskRemovedAndExitReasonDragToExit_sessionIdNull() {
- val sessionId = 1
- // add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- transitionObserver.setLoggerSessionId(sessionId)
-
- // window mode changing from FREEFORM to FULLSCREEN
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
- val transitionInfo =
- TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG).addChange(change).build()
- callOnTransitionReady(transitionInfo)
-
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.DRAG_TO_EXIT))
- verifyZeroInteractions(desktopModeEventLogger)
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
- }
-
- @Test
- fun transitExitDesktopAppHandleButton_logTaskRemovedAndExitReasonButton_sessionIdNull() {
- val sessionId = 1
- // add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- transitionObserver.setLoggerSessionId(sessionId)
-
- // window mode changing from FREEFORM to FULLSCREEN
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
- val transitionInfo =
- TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON)
- .addChange(change)
- .build()
- callOnTransitionReady(transitionInfo)
-
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.APP_HANDLE_MENU_BUTTON_EXIT))
- verifyZeroInteractions(desktopModeEventLogger)
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
- }
-
- @Test
- fun transitExitDesktopUsingKeyboard_logTaskRemovedAndExitReasonKeyboard_sessionIdNull() {
- val sessionId = 1
- // add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- transitionObserver.setLoggerSessionId(sessionId)
-
- // window mode changing from FREEFORM to FULLSCREEN
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
- val transitionInfo =
- TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT).addChange(change).build()
- callOnTransitionReady(transitionInfo)
-
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.KEYBOARD_SHORTCUT_EXIT))
- verifyZeroInteractions(desktopModeEventLogger)
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
- }
-
- @Test
- fun transitExitDesktopUnknown_logTaskRemovedAndExitReasonUnknown_sessionIdNull() {
- val sessionId = 1
- // add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- transitionObserver.setLoggerSessionId(sessionId)
-
- // window mode changing from FREEFORM to FULLSCREEN
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
- val transitionInfo =
- TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN).addChange(change).build()
- callOnTransitionReady(transitionInfo)
-
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.UNKNOWN_EXIT))
- verifyZeroInteractions(desktopModeEventLogger)
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
- }
-
- @Test
- fun transitToFrontWithFlagRecents_logTaskRemovedAndExitReasonOverview_sessionIdNull() {
- val sessionId = 1
- // add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- transitionObserver.setLoggerSessionId(sessionId)
-
- // recents transition
- val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- val transitionInfo =
- TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS).addChange(change).build()
- callOnTransitionReady(transitionInfo)
-
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
- verifyZeroInteractions(desktopModeEventLogger)
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
- }
-
- @Test
- fun transitClose_logTaskRemovedAndExitReasonTaskFinished_sessionIdNull() {
- val sessionId = 1
- // add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- transitionObserver.setLoggerSessionId(sessionId)
-
- // task closing
- val change = createChange(TRANSIT_CLOSE, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
- val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE).addChange(change).build()
- callOnTransitionReady(transitionInfo)
-
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.TASK_FINISHED))
- verifyZeroInteractions(desktopModeEventLogger)
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
- }
-
- @Test
- fun sessionExitByRecents_cancelledAnimation_sessionRestored() {
- val sessionId = 1
- // add a freeform task to an existing session
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- transitionObserver.setLoggerSessionId(sessionId)
-
- // recents transition sent freeform window to back
- val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- val transitionInfo1 =
- TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS).addChange(change).build()
- callOnTransitionReady(transitionInfo1)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, times(1))
- .logSessionExit(eq(sessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
- assertThat(transitionObserver.getLoggerSessionId()).isNull()
-
- val transitionInfo2 = TransitionInfoBuilder(TRANSIT_NONE).build()
- callOnTransitionReady(transitionInfo2)
-
- verify(desktopModeEventLogger, times(1)).logSessionEnter(any(), any())
- verify(desktopModeEventLogger, times(1)).logTaskAdded(any(), any())
- }
-
- @Test
- fun sessionAlreadyStarted_newFreeformTaskAdded_logsTaskAdded() {
- val sessionId = 1
- // add an existing freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- transitionObserver.setLoggerSessionId(sessionId)
-
- // new freeform task added
- val change = createChange(TRANSIT_OPEN, createTaskInfo(2, WINDOWING_MODE_FREEFORM))
- val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
- callOnTransitionReady(transitionInfo)
-
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
- verify(desktopModeEventLogger, never()).logSessionEnter(any(), any())
- }
-
- @Test
- fun sessionAlreadyStarted_freeformTaskRemoved_logsTaskRemoved() {
- val sessionId = 1
- // add two existing freeform tasks
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(2, WINDOWING_MODE_FREEFORM))
- transitionObserver.setLoggerSessionId(sessionId)
-
- // new freeform task added
- val change = createChange(TRANSIT_CLOSE, createTaskInfo(2, WINDOWING_MODE_FREEFORM))
- val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build()
- callOnTransitionReady(transitionInfo)
-
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
- verify(desktopModeEventLogger, never()).logSessionExit(any(), any())
- }
-
- /** Simulate calling the onTransitionReady() method */
- private fun callOnTransitionReady(transitionInfo: TransitionInfo) {
- val transition = mock(IBinder::class.java)
- val startT = mock(SurfaceControl.Transaction::class.java)
- val finishT = mock(SurfaceControl.Transaction::class.java)
-
- transitionObserver.onTransitionReady(transition, transitionInfo, startT, finishT)
- }
-
- companion object {
- fun createTaskInfo(taskId: Int, windowMode: Int): ActivityManager.RunningTaskInfo {
- val taskInfo = ActivityManager.RunningTaskInfo()
- taskInfo.taskId = taskId
- taskInfo.configuration.windowConfiguration.windowingMode = windowMode
-
- return taskInfo
+class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
+
+ @JvmField
+ @Rule
+ val extendedMockitoRule =
+ ExtendedMockitoRule.Builder(this)
+ .mockStatic(SystemProperties::class.java)
+ .mockStatic(Trace::class.java)
+ .build()!!
+
+ private val testExecutor = mock()
+ private val mockShellInit = mock()
+ private val transitions = mock()
+ private val desktopTasksLimiter = mock()
+ private val desktopState = FakeDesktopState()
+
+ private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver
+ private lateinit var shellInit: ShellInit
+ private lateinit var desktopModeEventLogger: DesktopModeEventLogger
+
+ @Before
+ fun setup() {
+ desktopState.canEnterDesktopMode = true
+ shellInit = spy(ShellInit(testExecutor))
+ desktopModeEventLogger = mock()
+
+ transitionObserver =
+ DesktopModeLoggerTransitionObserver(
+ mockShellInit,
+ transitions,
+ desktopModeEventLogger,
+ Optional.of(desktopTasksLimiter),
+ desktopState,
+ )
+ val initRunnableCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+ verify(mockShellInit)
+ .addInitCallback(initRunnableCaptor.capture(), same(transitionObserver))
+ initRunnableCaptor.value.run()
+ // verify this initialisation interaction to leave the desktopmodeEventLogger mock in a
+ // consistent state with no outstanding interactions when test cases start executing.
+ verify(desktopModeEventLogger).logTaskInfoStateInit()
}
- fun createChange(mode: Int, taskInfo: ActivityManager.RunningTaskInfo): Change {
- val change =
- Change(
- WindowContainerToken(mock(IWindowContainerToken::class.java)),
- mock(SurfaceControl::class.java))
- change.mode = mode
- change.taskInfo = taskInfo
- return change
+ @Test
+ fun testInitialiseVisibleTasksSystemProperty() {
+ ExtendedMockito.verify {
+ SystemProperties.set(
+ eq(DesktopModeLoggerTransitionObserver.VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY),
+ eq(
+ DesktopModeLoggerTransitionObserver
+ .VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY_DEFAULT_VALUE
+ ),
+ )
+ }
+ }
+
+ @Test
+ fun testRegistersObserverAtInit() {
+ verify(transitions).registerObserver(same(transitionObserver))
+ }
+
+ @Test
+ fun transitOpen_notFreeformWindow_doesNotLogTaskAddedOrSessionEnter() {
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, never()).logSessionEnter(any())
+ verify(desktopModeEventLogger, never()).logTaskAdded(any())
+ }
+
+ @Test
+ fun transitOpen_logTaskAddedAndEnterReasonAppFreeformIntent() {
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.APP_FREEFORM_INTENT,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1, focusReason = FocusReason.UNKNOWN),
+ )
+ }
+
+ @Test
+ fun transitEndDragToDesktop_logTaskAddedAndEnterReasonAppHandleDrag() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ // task change is finalised when drag ends
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, 0)
+ .addChange(change)
+ .build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.APP_HANDLE_DRAG,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ fun transitEnterDesktopByButtonTap_logTaskAddedAndEnterReasonButtonTap() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON, 0)
+ .addChange(change)
+ .build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.APP_HANDLE_MENU_BUTTON,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ fun transitEnterDesktopFromAppFromOverview_logTaskAddedAndEnterReasonAppFromOverview() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0)
+ .addChange(change)
+ .build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.APP_FROM_OVERVIEW,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ fun transitEnterDesktopFromKeyboardShortcut_logTaskAddedAndEnterReasonKeyboardShortcut() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT, 0)
+ .addChange(change)
+ .build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.KEYBOARD_SHORTCUT_ENTER,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ fun transitToFront_logTaskAddedAndEnterReasonOverview() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.OVERVIEW,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ fun transitToFront_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() {
+ // previous exit to overview transition
+ // add a freeform task
+ val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(previousTaskInfo)
+ transitionObserver.isSessionActive = true
+ val previousTransitionInfo =
+ TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
+ .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo))
+ .build()
+
+ callOnTransitionReady(previousTransitionInfo)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
+
+ // Enter desktop mode from cancelled recents has no transition. Enter is detected on the
+ // next transition involving freeform windows
+
+ // TRANSIT_TO_FRONT
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.OVERVIEW,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ fun transitChange_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() {
+ // previous exit to overview transition
+ // add a freeform task
+ val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(previousTaskInfo)
+ transitionObserver.isSessionActive = true
+ val previousTransitionInfo =
+ TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
+ .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo))
+ .build()
+
+ callOnTransitionReady(previousTransitionInfo)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
+
+ // Enter desktop mode from cancelled recents has no transition. Enter is detected on the
+ // next transition involving freeform windows
+
+ // TRANSIT_CHANGE
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_CHANGE, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.OVERVIEW,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ fun transitOpen_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() {
+ // previous exit to overview transition
+ // add a freeform task
+ val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(previousTaskInfo)
+ transitionObserver.isSessionActive = true
+ val previousTransitionInfo =
+ TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
+ .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo))
+ .build()
+
+ callOnTransitionReady(previousTransitionInfo)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
+
+ // Enter desktop mode from cancelled recents has no transition. Enter is detected on the
+ // next transition involving freeform windows
+
+ // TRANSIT_OPEN
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.OVERVIEW,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ @Suppress("ktlint:standard:max-line-length")
+ fun transitEnterDesktopFromAppFromOverview_previousTransitionExitToOverview_logTaskAddedAndEnterReasonAppFromOverview() {
+ // Tests for AppFromOverview precedence in compared to cancelled Overview
+
+ // previous exit to overview transition
+ // add a freeform task
+ val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(previousTaskInfo)
+ transitionObserver.isSessionActive = true
+ val previousTransitionInfo =
+ TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
+ .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo))
+ .build()
+
+ callOnTransitionReady(previousTransitionInfo)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
+
+ // TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0)
+ .addChange(change)
+ .build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.APP_FROM_OVERVIEW,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ fun transitEnterDesktopFromUnknown_logTaskAddedAndEnterReasonUnknown() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.UNKNOWN_ENTER,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ fun transitWake_logTaskAddedAndEnterReasonScreenOn() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_WAKE, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.SCREEN_ON,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ fun transitBack_previousExitReasonScreenOff_logTaskAddedAndEnterReasonScreenOn() {
+ val freeformTask = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ // Previous Exit reason recorded as Screen Off
+ transitionObserver.addTaskInfosToCachedMap(freeformTask)
+ transitionObserver.isSessionActive = true
+ callOnTransitionReady(TransitionInfoBuilder(TRANSIT_SLEEP).build())
+ verifyTaskRemovedAndExitLogging(ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE)
+ // Enter desktop through back transition, this happens when user enters after dismissing
+ // keyguard
+ val change = createChange(TRANSIT_TO_FRONT, freeformTask)
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_BACK, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.SCREEN_ON,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ fun transitEndDragToDesktop_previousExitReasonScreenOff_logTaskAddedAndEnterReasonAppDrag() {
+ val freeformTask = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ // Previous Exit reason recorded as Screen Off
+ transitionObserver.addTaskInfosToCachedMap(freeformTask)
+ transitionObserver.isSessionActive = true
+ callOnTransitionReady(TransitionInfoBuilder(TRANSIT_SLEEP).build())
+ verifyTaskRemovedAndExitLogging(ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE)
+
+ // Enter desktop through app handle drag. This represents cases where instead of moving to
+ // desktop right after turning the screen on, we move to fullscreen then move another task
+ // to desktop
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, 0)
+ .addChange(createChange(TRANSIT_TO_FRONT, freeformTask))
+ .build()
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.APP_HANDLE_DRAG,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ fun transitSleep_logTaskRemovedAndExitReasonScreenOff() {
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.isSessionActive = true
+
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_SLEEP).build()
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE)
+ }
+
+ @Test
+ fun transitExitDesktopTaskDrag_logTaskRemovedAndExitReasonDragToExit() {
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.isSessionActive = true
+
+ // window mode changing from FREEFORM to FULLSCREEN
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.DRAG_TO_EXIT, DEFAULT_TASK_UPDATE)
+ }
+
+ @Test
+ fun transitExitDesktopAppHandleButton_logTaskRemovedAndExitReasonButton() {
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.isSessionActive = true
+
+ // window mode changing from FREEFORM to FULLSCREEN
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON)
+ .addChange(change)
+ .build()
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.APP_HANDLE_MENU_BUTTON_EXIT, DEFAULT_TASK_UPDATE)
+ }
+
+ @Test
+ fun transitExitDesktopUsingKeyboard_logTaskRemovedAndExitReasonKeyboard() {
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.isSessionActive = true
+
+ // window mode changing from FREEFORM to FULLSCREEN
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT)
+ .addChange(change)
+ .build()
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.KEYBOARD_SHORTCUT_EXIT, DEFAULT_TASK_UPDATE)
+ }
+
+ @Test
+ fun transitExitBackGesture_logTaskRemovedAndExitReasonTaskMovedToBack() {
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.isSessionActive = true
+
+ // task moved to back
+ val change = createChange(TRANSIT_TO_BACK, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_BACK).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.TASK_MOVED_TO_BACK, DEFAULT_TASK_UPDATE)
+ }
+
+ @Test
+ fun transitExitDesktopUnknown_logTaskRemovedAndExitReasonUnknown() {
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.isSessionActive = true
+
+ // window mode changing from FREEFORM to FULLSCREEN
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.UNKNOWN_EXIT, DEFAULT_TASK_UPDATE)
+ }
+
+ @Test
+ fun transitToFrontWithFlagRecents_logTaskRemovedAndExitReasonOverview() {
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.isSessionActive = true
+
+ // recents transition
+ val change = createChange(TRANSIT_TO_BACK, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
+ .addChange(change)
+ .build()
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
+ }
+
+ @Test
+ fun transitClose_logTaskRemovedAndExitReasonTaskFinished() {
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.isSessionActive = true
+
+ // task closing
+ val change = createChange(TRANSIT_CLOSE, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.TASK_FINISHED, DEFAULT_TASK_UPDATE)
+ }
+
+ @Test
+ fun transitMinimize_logExitReasongMinimized() {
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.isSessionActive = true
+
+ // minimize the task
+ val change = createChange(TRANSIT_MINIMIZE, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_MINIMIZE).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ assertFalse(transitionObserver.isSessionActive)
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(ExitReason.TASK_MINIMIZED))
+ verify(desktopModeEventLogger, times(1))
+ .logTaskRemoved(
+ eq(DEFAULT_TASK_UPDATE.copy(minimizeReason = MinimizeReason.MINIMIZE_BUTTON))
+ )
+ verifyNoMoreInteractions(desktopModeEventLogger)
+ }
+
+ @Test
+ fun sessionExitByRecents_cancelledAnimation_sessionRestored() {
+ // add a freeform task to an existing session
+ val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(taskInfo)
+ transitionObserver.isSessionActive = true
+
+ // recents transition sent freeform window to back
+ val change = createChange(TRANSIT_TO_BACK, taskInfo)
+ val transitionInfo1 =
+ TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
+ .addChange(change)
+ .build()
+ callOnTransitionReady(transitionInfo1)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
+
+ val transitionInfo2 = TransitionInfoBuilder(TRANSIT_NONE).build()
+ callOnTransitionReady(transitionInfo2)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.OVERVIEW,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ fun sessionAlreadyStarted_newFreeformTaskAdded_logsTaskAdded() {
+ // add an existing freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.isSessionActive = true
+
+ // new freeform task added
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1))
+ .logTaskAdded(
+ eq(
+ DEFAULT_TASK_UPDATE.copy(
+ instanceId = 2,
+ visibleTaskCount = 2,
+ focusReason = FocusReason.UNKNOWN,
+ )
+ )
+ )
+ verify(desktopModeEventLogger, never()).logSessionEnter(any())
+ }
+
+ @Test
+ fun sessionAlreadyStarted_taskPositionChanged_logsTaskUpdate() {
+ // add an existing freeform task
+ val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(taskInfo)
+ transitionObserver.isSessionActive = true
+
+ // task position changed
+ val newTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM, taskX = DEFAULT_TASK_X + 100)
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_CHANGE, 0)
+ .addChange(createChange(TRANSIT_CHANGE, newTaskInfo))
+ .build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1))
+ .logTaskInfoChanged(
+ eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 1))
+ )
+ verifyNoMoreInteractions(desktopModeEventLogger)
+ }
+
+ @Test
+ fun sessionAlreadyStarted_taskResized_logsTaskUpdate() {
+ // add an existing freeform task
+ val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(taskInfo)
+ transitionObserver.isSessionActive = true
+
+ // task resized
+ val newTaskInfo =
+ createTaskInfo(
+ WINDOWING_MODE_FREEFORM,
+ taskWidth = DEFAULT_TASK_WIDTH + 100,
+ taskHeight = DEFAULT_TASK_HEIGHT - 100,
+ )
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_CHANGE, 0)
+ .addChange(createChange(TRANSIT_CHANGE, newTaskInfo))
+ .build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1))
+ .logTaskInfoChanged(
+ eq(
+ DEFAULT_TASK_UPDATE.copy(
+ taskWidth = DEFAULT_TASK_WIDTH + 100,
+ taskHeight = DEFAULT_TASK_HEIGHT - 100,
+ visibleTaskCount = 1,
+ )
+ )
+ )
+ verifyNoMoreInteractions(desktopModeEventLogger)
+ }
+
+ @Test
+ fun sessionAlreadyStarted_taskFocusChanged_logsTaskUpdate() {
+ val taskInfo1 = createTaskInfo(WINDOWING_MODE_FREEFORM, id = 1)
+ val taskInfo2 = createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)
+ transitionObserver.addTaskInfosToCachedMap(taskInfo1)
+ transitionObserver.addTaskInfosToCachedMap(taskInfo2)
+ transitionObserver.isSessionActive = true
+ transitionObserver.setFocusedTaskForTesting(taskInfo1)
+
+ val task2FocusedChange = createChange(TRANSIT_CHANGE, taskInfo2)
+ task2FocusedChange.flags = FLAG_MOVED_TO_TOP
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_CHANGE, 0).addChange(task2FocusedChange).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1))
+ .logTaskInfoChanged(
+ eq(
+ DEFAULT_TASK_UPDATE.copy(
+ instanceId = 2,
+ visibleTaskCount = 2,
+ focusReason = FocusReason.UNKNOWN,
+ )
+ )
+ )
+ verifyNoMoreInteractions(desktopModeEventLogger)
+ }
+
+ @Test
+ fun sessionAlreadyStarted_multipleTasksUpdated_logsTaskUpdateForCorrectTask() {
+ // add 2 existing freeform task
+ val taskInfo1 = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ val taskInfo2 = createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)
+ transitionObserver.addTaskInfosToCachedMap(taskInfo1)
+ transitionObserver.addTaskInfosToCachedMap(taskInfo2)
+ transitionObserver.isSessionActive = true
+
+ // task 1 position update
+ val newTaskInfo1 = createTaskInfo(WINDOWING_MODE_FREEFORM, taskX = DEFAULT_TASK_X + 100)
+ val transitionInfo1 =
+ TransitionInfoBuilder(TRANSIT_CHANGE, 0)
+ .addChange(createChange(TRANSIT_CHANGE, newTaskInfo1))
+ .build()
+ callOnTransitionReady(transitionInfo1)
+
+ verify(desktopModeEventLogger, times(1))
+ .logTaskInfoChanged(
+ eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 2))
+ )
+ verifyNoMoreInteractions(desktopModeEventLogger)
+
+ // task 2 resize
+ val newTaskInfo2 =
+ createTaskInfo(
+ WINDOWING_MODE_FREEFORM,
+ id = 2,
+ taskWidth = DEFAULT_TASK_WIDTH + 100,
+ taskHeight = DEFAULT_TASK_HEIGHT - 100,
+ )
+ val transitionInfo2 =
+ TransitionInfoBuilder(TRANSIT_CHANGE, 0)
+ .addChange(createChange(TRANSIT_CHANGE, newTaskInfo2))
+ .build()
+
+ callOnTransitionReady(transitionInfo2)
+
+ verify(desktopModeEventLogger, times(1))
+ .logTaskInfoChanged(
+ eq(
+ DEFAULT_TASK_UPDATE.copy(
+ instanceId = 2,
+ taskWidth = DEFAULT_TASK_WIDTH + 100,
+ taskHeight = DEFAULT_TASK_HEIGHT - 100,
+ visibleTaskCount = 2,
+ )
+ )
+ )
+ verifyNoMoreInteractions(desktopModeEventLogger)
+ }
+
+ @Test
+ fun sessionAlreadyStarted_freeformTaskRemoved_logsTaskRemoved() {
+ // add two existing freeform tasks
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2))
+ transitionObserver.isSessionActive = true
+
+ // new freeform task closed
+ val change = createChange(TRANSIT_CLOSE, createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1))
+ .logTaskRemoved(eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2, visibleTaskCount = 1)))
+ verify(desktopModeEventLogger, never()).logSessionExit(any())
+ }
+
+ @Test
+ fun onTransitionReady_taskIsBeingMinimized_logsTaskMinimized() {
+ transitionObserver.isSessionActive = true
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM, id = 1))
+ val taskInfo2 = createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)
+ transitionObserver.addTaskInfosToCachedMap(taskInfo2)
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_TO_BACK, 0)
+ .addChange(createChange(TRANSIT_TO_BACK, taskInfo2))
+ .build()
+ `when`(desktopTasksLimiter.getMinimizingTask(any()))
+ .thenReturn(
+ DesktopTasksLimiter.TaskDetails(
+ taskInfo2.displayId,
+ taskInfo2.taskId,
+ minimizeReason = MinimizeReason.TASK_LIMIT,
+ )
+ )
+
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1))
+ .logTaskRemoved(
+ eq(
+ DEFAULT_TASK_UPDATE.copy(
+ instanceId = 2,
+ visibleTaskCount = 1,
+ minimizeReason = MinimizeReason.TASK_LIMIT,
+ )
+ )
+ )
+ }
+
+ @Test
+ fun onTransitionReady_taskIsBeingUnminimized_logsTaskUnminimized() {
+ transitionObserver.isSessionActive = true
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM, id = 1))
+ val taskInfo2 = createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_TO_FRONT, /* flags= */ 0)
+ .addChange(createChange(TRANSIT_TO_FRONT, taskInfo2))
+ .build()
+ `when`(desktopTasksLimiter.getUnminimizingTask(any()))
+ .thenReturn(
+ DesktopTasksLimiter.TaskDetails(
+ taskInfo2.displayId,
+ taskInfo2.taskId,
+ unminimizeReason = UnminimizeReason.TASKBAR_MANAGE_WINDOW,
+ )
+ )
+
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1))
+ .logTaskAdded(
+ eq(
+ DEFAULT_TASK_UPDATE.copy(
+ instanceId = 2,
+ visibleTaskCount = 2,
+ unminimizeReason = UnminimizeReason.TASKBAR_MANAGE_WINDOW,
+ )
+ )
+ )
+ }
+
+ @Test
+ fun onTransitionReady_taskLimitMinimizeTransitionType_logsTaskMinimized() {
+ val taskInfo2 = createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)
+ transitionObserver.apply {
+ isSessionActive = true
+ addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM, id = 1))
+ addTaskInfosToCachedMap(taskInfo2)
+ }
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_DESKTOP_MODE_TASK_LIMIT_MINIMIZE, /* flags= */ 0)
+ .addChange(createChange(TRANSIT_TO_BACK, taskInfo2))
+ .build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1))
+ .logTaskRemoved(
+ eq(
+ DEFAULT_TASK_UPDATE.copy(
+ instanceId = 2,
+ visibleTaskCount = 1,
+ minimizeReason = MinimizeReason.TASK_LIMIT,
+ )
+ )
+ )
+ }
+
+ /** Simulate calling the onTransitionReady() method */
+ private fun callOnTransitionReady(transitionInfo: TransitionInfo) {
+ val transition = mock()
+ val startT = mock()
+ val finishT = mock()
+
+ transitionObserver.onTransitionReady(transition, transitionInfo, startT, finishT)
+ }
+
+ private fun verifyTaskAddedAndEnterLogging(enterReason: EnterReason, taskUpdate: TaskUpdate) {
+ assertTrue(transitionObserver.isSessionActive)
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(enterReason))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(taskUpdate))
+ ExtendedMockito.verify {
+ Trace.setCounter(
+ eq(Trace.TRACE_TAG_WINDOW_MANAGER),
+ eq(DesktopModeLoggerTransitionObserver.VISIBLE_TASKS_COUNTER_NAME),
+ eq(taskUpdate.visibleTaskCount.toLong()),
+ )
+ }
+ ExtendedMockito.verify {
+ SystemProperties.set(
+ eq(DesktopModeLoggerTransitionObserver.VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY),
+ eq(taskUpdate.visibleTaskCount.toString()),
+ )
+ }
+ verifyNoMoreInteractions(desktopModeEventLogger)
+ }
+
+ private fun verifyTaskRemovedAndExitLogging(exitReason: ExitReason, taskUpdate: TaskUpdate) {
+ assertFalse(transitionObserver.isSessionActive)
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(taskUpdate))
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(exitReason))
+ verifyNoMoreInteractions(desktopModeEventLogger)
+ }
+
+ private companion object {
+ const val DEFAULT_TASK_ID = 1
+ const val DEFAULT_TASK_UID = 2
+ const val DEFAULT_TASK_HEIGHT = 100
+ const val DEFAULT_TASK_WIDTH = 200
+ const val DEFAULT_TASK_X = 30
+ const val DEFAULT_TASK_Y = 70
+ const val DEFAULT_VISIBLE_TASK_COUNT = 0
+ val DEFAULT_TASK_UPDATE =
+ TaskUpdate(
+ DEFAULT_TASK_ID,
+ DEFAULT_TASK_UID,
+ DEFAULT_TASK_HEIGHT,
+ DEFAULT_TASK_WIDTH,
+ DEFAULT_TASK_X,
+ DEFAULT_TASK_Y,
+ visibleTaskCount = DEFAULT_VISIBLE_TASK_COUNT,
+ )
+
+ fun createTaskInfo(
+ windowMode: Int,
+ id: Int = DEFAULT_TASK_ID,
+ uid: Int = DEFAULT_TASK_UID,
+ taskHeight: Int = DEFAULT_TASK_HEIGHT,
+ taskWidth: Int = DEFAULT_TASK_WIDTH,
+ taskX: Int = DEFAULT_TASK_X,
+ taskY: Int = DEFAULT_TASK_Y,
+ ) =
+ ActivityManager.RunningTaskInfo().apply {
+ taskId = id
+ effectiveUid = uid
+ configuration.windowConfiguration.apply {
+ windowingMode = windowMode
+ positionInParent = Point(taskX, taskY)
+ bounds.set(Rect(taskX, taskY, taskX + taskWidth, taskY + taskHeight))
+ }
+ }
+
+ fun createChange(mode: Int, taskInfo: ActivityManager.RunningTaskInfo): Change {
+ val change =
+ Change(WindowContainerToken(mock()), mock())
+ change.mode = mode
+ change.taskInfo = taskInfo
+ return change
+ }
}
- }
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt b/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt
index 3a04ddbba0..f6eed5da6c 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt
@@ -18,21 +18,21 @@ package com.android.wm.shell.desktopmode
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON
-import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_FROM_OVERVIEW
-import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.KEYBOARD_SHORTCUT
-import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.TASK_DRAG
-import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
-import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON
-import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT
-import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
-import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN
+import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON
+import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT
+import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
+import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.getEnterTransitionType
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.getExitTransitionType
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_FROM_OVERVIEW
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.KEYBOARD_SHORTCUT
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.TASK_DRAG
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -53,8 +53,7 @@ class DesktopModeTransitionTypesTest {
.isEqualTo(TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON)
assertThat(APP_FROM_OVERVIEW.getEnterTransitionType())
.isEqualTo(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW)
- assertThat(TASK_DRAG.getEnterTransitionType())
- .isEqualTo(TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN)
+ assertThat(TASK_DRAG.getEnterTransitionType()).isEqualTo(TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN)
assertThat(KEYBOARD_SHORTCUT.getEnterTransitionType())
.isEqualTo(TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT)
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt b/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt
index 51b291c0b7..72b1fd9af1 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt
@@ -16,18 +16,22 @@
package com.android.wm.shell.desktopmode
-
+import android.content.ComponentName
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
-import com.android.internal.logging.InstanceIdSequence
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.Companion.DesktopUiEventEnum.DESKTOP_WINDOW_EDGE_DRAG_RESIZE
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum.DESKTOP_WINDOW_EDGE_DRAG_RESIZE
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
/**
* Test class for [DesktopModeUiEventLogger]
@@ -39,13 +43,13 @@ import org.junit.runner.RunWith
class DesktopModeUiEventLoggerTest : ShellTestCase() {
private lateinit var uiEventLoggerFake: UiEventLoggerFake
private lateinit var logger: DesktopModeUiEventLogger
- private val instanceIdSequence = InstanceIdSequence(/* instanceIdMax */ 1 shl 20)
+ private val mockPackageManager: PackageManager = mock()
@Before
fun setUp() {
uiEventLoggerFake = UiEventLoggerFake()
- logger = DesktopModeUiEventLogger(uiEventLoggerFake, instanceIdSequence)
+ logger = DesktopModeUiEventLogger(uiEventLoggerFake, mockPackageManager)
}
@Test
@@ -62,8 +66,7 @@ class DesktopModeUiEventLoggerTest : ShellTestCase() {
@Test
fun log_eventLogged() {
- val event =
- DESKTOP_WINDOW_EDGE_DRAG_RESIZE
+ val event = DESKTOP_WINDOW_EDGE_DRAG_RESIZE
logger.log(UID, PACKAGE_NAME, event)
assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(event.id)
@@ -92,8 +95,7 @@ class DesktopModeUiEventLoggerTest : ShellTestCase() {
@Test
fun logWithInstanceId_eventLogged() {
- val event =
- DESKTOP_WINDOW_EDGE_DRAG_RESIZE
+ val event = DESKTOP_WINDOW_EDGE_DRAG_RESIZE
logger.logWithInstanceId(INSTANCE_ID, UID, PACKAGE_NAME, event)
assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(event.id)
@@ -102,10 +104,28 @@ class DesktopModeUiEventLoggerTest : ShellTestCase() {
assertThat(uiEventLoggerFake[0].packageName).isEqualTo(PACKAGE_NAME)
}
+ @Test
+ fun logWithTaskInfo_eventLogged() {
+ val event = DESKTOP_WINDOW_EDGE_DRAG_RESIZE
+ val taskInfo =
+ TestRunningTaskInfoBuilder()
+ .setUserId(USER_ID)
+ .setBaseActivity(ComponentName(PACKAGE_NAME, "test"))
+ .build()
+ whenever(mockPackageManager.getApplicationInfoAsUser(PACKAGE_NAME, /* flags= */ 0, USER_ID))
+ .thenReturn(ApplicationInfo().apply { uid = UID })
+ logger.log(taskInfo, event)
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+ assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(event.id)
+ assertThat(uiEventLoggerFake[0].instanceId).isNull()
+ assertThat(uiEventLoggerFake[0].uid).isEqualTo(UID)
+ assertThat(uiEventLoggerFake[0].packageName).isEqualTo(PACKAGE_NAME)
+ }
companion object {
private val INSTANCE_ID = InstanceId.fakeInstanceId(0)
private const val UID = 10
+ private const val USER_ID = 2
private const val PACKAGE_NAME = "com.foo"
}
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index bd39aa6ace..12cee62a6c 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -16,129 +16,585 @@
package com.android.wm.shell.desktopmode
+import android.animation.AnimatorTestRule
import android.app.ActivityManager.RunningTaskInfo
-import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
-import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
-import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.graphics.PointF
import android.graphics.Rect
-import android.graphics.Region
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.Display.DEFAULT_DISPLAY
import android.view.SurfaceControl
import androidx.test.filters.SmallTest
+import com.android.internal.policy.SystemBarUtils
+import com.android.window.flags.Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
+import com.android.window.flags.Flags.FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.shared.R as sharedR
+import com.android.wm.shell.shared.bubbles.BubbleDropTargetBoundsProvider
+import com.android.wm.shell.windowdecor.tiling.SnapEventHandler
import com.google.common.truth.Truth.assertThat
import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
+import org.mockito.kotlin.any
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
+/**
+ * Test class for [DesktopModeVisualIndicator]
+ *
+ * Usage: atest WMShellUnitTests:DesktopModeVisualIndicatorTest
+ */
@SmallTest
+@RunWithLooper
@RunWith(AndroidTestingRunner::class)
+@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+@Ignore("Non-hermetic test - b/419771302")
class DesktopModeVisualIndicatorTest : ShellTestCase() {
- @Mock private lateinit var taskInfo: RunningTaskInfo
+
+ @JvmField @Rule val animatorTestRule = AnimatorTestRule(this)
+
+ private lateinit var taskInfo: RunningTaskInfo
@Mock private lateinit var syncQueue: SyncTransactionQueue
@Mock private lateinit var displayController: DisplayController
@Mock private lateinit var taskSurface: SurfaceControl
@Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@Mock private lateinit var displayLayout: DisplayLayout
+ @Mock private lateinit var bubbleBoundsProvider: BubbleDropTargetBoundsProvider
+ @Mock private lateinit var snapEventHandler: SnapEventHandler
private lateinit var visualIndicator: DesktopModeVisualIndicator
+ private val desktopExecutor = TestShellExecutor()
+ private val mainExecutor = TestShellExecutor()
@Before
fun setUp() {
- visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo, displayController,
- context, taskSurface, taskDisplayAreaOrganizer)
- whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
- whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height())
- whenever(displayLayout.stableInsets()).thenReturn(STABLE_INSETS)
+ setUpDisplayBoundsTablet()
+ whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
+ whenever(displayController.getDisplay(anyInt())).thenReturn(mContext.display)
+ whenever(bubbleBoundsProvider.getBubbleBarExpandedViewDropTargetBounds(any()))
+ .thenReturn(Rect())
+ taskInfo = DesktopTestHelpers.createFullscreenTask()
+
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_isDesktopModeSupported,
+ true,
+ )
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_canInternalDisplayHostDesktops,
+ true,
+ )
+ }
+
+ private fun setUpDisplayBoundsTablet() {
+ whenever(displayLayout.width()).thenReturn(TABLET_DISPLAY_BOUNDS.width())
+ whenever(displayLayout.height()).thenReturn(TABLET_DISPLAY_BOUNDS.height())
+ whenever(displayLayout.stableInsets()).thenReturn(TABLET_STABLE_INSETS)
+ }
+
+ private fun setUpDisplayBoundsFoldable() {
+ whenever(displayLayout.width()).thenReturn(FOLDABLE_DISPLAY_BOUNDS.width())
+ whenever(displayLayout.height()).thenReturn(FOLDABLE_DISPLAY_BOUNDS.height())
+ whenever(displayLayout.stableInsets()).thenReturn(FOLDABLE_STABLE_INSETS)
+ }
+
+ private fun disableDesktop() {
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_canInternalDisplayHostDesktops,
+ false,
+ )
+ }
+
+ private fun setUpFoldable() {
+ setUpDisplayBoundsFoldable()
+ disableDesktop()
}
@Test
fun testFullscreenRegionCalculation() {
- val transitionHeight = context.resources.getDimensionPixelSize(
- R.dimen.desktop_mode_fullscreen_from_desktop_height)
- val fromFreeformWidth = mContext.resources.getDimensionPixelSize(
- R.dimen.desktop_mode_fullscreen_from_desktop_width
- )
- var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
- WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top))
- testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
- WINDOWING_MODE_FREEFORM, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(
- DISPLAY_BOUNDS.width() / 2 - fromFreeformWidth / 2,
- -50,
- DISPLAY_BOUNDS.width() / 2 + fromFreeformWidth / 2,
- transitionHeight))
- testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
- WINDOWING_MODE_MULTI_WINDOW, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top))
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
+ var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
+ assertThat(testRegion.bounds)
+ .isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400, 2 * TABLET_STABLE_INSETS.top))
+
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
+ testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
+ val transitionHeight = SystemBarUtils.getStatusBarHeight(context)
+ val toFullscreenScale =
+ mContext.resources.getFloat(R.dimen.desktop_mode_fullscreen_region_scale)
+ val toFullscreenWidth = displayLayout.width() * toFullscreenScale
+ assertThat(testRegion.bounds)
+ .isEqualTo(
+ Rect(
+ (TABLET_DISPLAY_BOUNDS.width() / 2f - toFullscreenWidth / 2f).toInt(),
+ Short.MIN_VALUE.toInt(),
+ (TABLET_DISPLAY_BOUNDS.width() / 2f + toFullscreenWidth / 2f).toInt(),
+ transitionHeight,
+ )
+ )
+
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
+ testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
+ assertThat(testRegion.bounds)
+ .isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400, 2 * TABLET_STABLE_INSETS.top))
+
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT)
+ testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
+ assertThat(testRegion.bounds)
+ .isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400, transitionHeight))
}
@Test
fun testSplitLeftRegionCalculation() {
- val transitionHeight = context.resources.getDimensionPixelSize(
- R.dimen.desktop_mode_split_from_desktop_height)
- var testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
- WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 32, 1600))
- testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
- WINDOWING_MODE_FREEFORM, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, transitionHeight, 32, 1600))
- testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
- WINDOWING_MODE_MULTI_WINDOW, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 32, 1600))
+ val transitionHeight =
+ context.resources.getDimensionPixelSize(R.dimen.desktop_mode_split_from_desktop_height)
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
+ var testRegion =
+ visualIndicator.calculateSplitLeftRegion(
+ displayLayout,
+ TRANSITION_AREA_WIDTH,
+ CAPTION_HEIGHT,
+ )
+ assertThat(testRegion).isEqualTo(Rect(0, -50, 32, 1600))
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
+ testRegion =
+ visualIndicator.calculateSplitLeftRegion(
+ displayLayout,
+ TRANSITION_AREA_WIDTH,
+ CAPTION_HEIGHT,
+ )
+ assertThat(testRegion).isEqualTo(Rect(0, transitionHeight, 32, 1600))
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
+ testRegion =
+ visualIndicator.calculateSplitLeftRegion(
+ displayLayout,
+ TRANSITION_AREA_WIDTH,
+ CAPTION_HEIGHT,
+ )
+ assertThat(testRegion).isEqualTo(Rect(0, -50, 32, 1600))
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT)
+ testRegion =
+ visualIndicator.calculateSplitLeftRegion(
+ displayLayout,
+ TRANSITION_AREA_WIDTH,
+ CAPTION_HEIGHT,
+ )
+ assertThat(testRegion).isEqualTo(Rect(0, -50, 32, 1600))
}
@Test
fun testSplitRightRegionCalculation() {
- val transitionHeight = context.resources.getDimensionPixelSize(
- R.dimen.desktop_mode_split_from_desktop_height)
- var testRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
- WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(2368, -50, 2400, 1600))
- testRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
- WINDOWING_MODE_FREEFORM, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(2368, transitionHeight, 2400, 1600))
- testRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
- WINDOWING_MODE_MULTI_WINDOW, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(2368, -50, 2400, 1600))
+ val transitionHeight =
+ context.resources.getDimensionPixelSize(R.dimen.desktop_mode_split_from_desktop_height)
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
+ var testRegion =
+ visualIndicator.calculateSplitRightRegion(
+ displayLayout,
+ TRANSITION_AREA_WIDTH,
+ CAPTION_HEIGHT,
+ )
+ assertThat(testRegion).isEqualTo(Rect(2368, -50, 2400, 1600))
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
+ testRegion =
+ visualIndicator.calculateSplitRightRegion(
+ displayLayout,
+ TRANSITION_AREA_WIDTH,
+ CAPTION_HEIGHT,
+ )
+ assertThat(testRegion).isEqualTo(Rect(2368, transitionHeight, 2400, 1600))
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
+ testRegion =
+ visualIndicator.calculateSplitRightRegion(
+ displayLayout,
+ TRANSITION_AREA_WIDTH,
+ CAPTION_HEIGHT,
+ )
+ assertThat(testRegion).isEqualTo(Rect(2368, -50, 2400, 1600))
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT)
+ testRegion =
+ visualIndicator.calculateSplitRightRegion(
+ displayLayout,
+ TRANSITION_AREA_WIDTH,
+ CAPTION_HEIGHT,
+ )
+ assertThat(testRegion).isEqualTo(Rect(2368, -50, 2400, 1600))
}
@Test
- fun testToDesktopRegionCalculation() {
- val fullscreenRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
- WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT)
- val splitLeftRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
- WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
- val splitRightRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
- WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
- val desktopRegion = visualIndicator.calculateToDesktopRegion(displayLayout,
- WINDOWING_MODE_FULLSCREEN, splitLeftRegion, splitRightRegion, fullscreenRegion)
- var testRegion = Region()
- testRegion.union(DISPLAY_BOUNDS)
- testRegion.op(splitLeftRegion, Region.Op.DIFFERENCE)
- testRegion.op(splitRightRegion, Region.Op.DIFFERENCE)
- testRegion.op(fullscreenRegion, Region.Op.DIFFERENCE)
- assertThat(desktopRegion).isEqualTo(testRegion)
+ fun testBubbleLeftRegionCalculation() {
+ val bubbleRegionSize =
+ context.resources.getDimensionPixelSize(sharedR.dimen.drag_zone_bubble_tablet)
+ val expectedRect = Rect(0, 1600 - bubbleRegionSize, bubbleRegionSize, 1600)
+
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
+ var testRegion = visualIndicator.calculateBubbleLeftRegion(displayLayout)
+ assertThat(testRegion).isEqualTo(expectedRect)
+
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
+ testRegion = visualIndicator.calculateBubbleLeftRegion(displayLayout)
+ assertThat(testRegion).isEqualTo(expectedRect)
+ }
+
+ @Test
+ fun testBubbleRightRegionCalculation() {
+ val bubbleRegionSize =
+ context.resources.getDimensionPixelSize(sharedR.dimen.drag_zone_bubble_tablet)
+ val expectedRect = Rect(2400 - bubbleRegionSize, 1600 - bubbleRegionSize, 2400, 1600)
+
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
+ var testRegion = visualIndicator.calculateBubbleRightRegion(displayLayout)
+ assertThat(testRegion).isEqualTo(expectedRect)
+
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
+ testRegion = visualIndicator.calculateBubbleRightRegion(displayLayout)
+ assertThat(testRegion).isEqualTo(expectedRect)
+ }
+
+ @Test
+ fun testDefaultIndicators() {
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
+ var result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(-10000f, 500f))
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR)
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
+ result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(10000f, 500f))
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR)
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT)
+ result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(500f, 10000f))
+ assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
+ result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(500f, 10000f))
+ assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
+ }
+
+ @Test
+ @EnableFlags(
+ com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN,
+ com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE,
+ )
+ fun testDefaultIndicators_enableBubbleToFullscreen() {
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
+ var result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(10f, 1500f))
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_LEFT_INDICATOR)
+ result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(2390f, 1500f))
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_RIGHT_INDICATOR)
+
+ // Check that bubble zones are not available from split
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
+ result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(10f, 1500f))
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR)
+ result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(2390f, 1500f))
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR)
+
+ // Check that bubble zones are not available from desktop
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
+ result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(10f, 1500f))
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR)
+ result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(2390f, 1500f))
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR)
+ }
+
+ @Test
+ @EnableFlags(
+ com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN,
+ com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE,
+ )
+ fun testDefaultIndicators_foldable_enableBubbleToFullscreen_dragFromFullscreen() {
+ setUpFoldable()
+
+ createVisualIndicator(
+ DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN,
+ isSmallTablet = true,
+ isLeftRightSplit = true,
+ )
+ var result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldCenter())
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
+
+ result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldLeftEdge())
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR)
+
+ result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldRightEdge())
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR)
+
+ result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldLeftBottom())
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_LEFT_INDICATOR)
+
+ result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldRightBottom())
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_RIGHT_INDICATOR)
+ }
+
+ @Test
+ @EnableFlags(
+ com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN,
+ com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE,
+ )
+ @DisableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_ANYTHING)
+ fun testDefaultIndicators_foldable_enableBubbleToFullscreen_dragFromSplit() {
+ setUpFoldable()
+
+ createVisualIndicator(
+ DesktopModeVisualIndicator.DragStartState.FROM_SPLIT,
+ isSmallTablet = true,
+ isLeftRightSplit = true,
+ )
+ var result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldCenter())
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
+
+ // Check that bubbles are not available from split
+ result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldLeftBottom())
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR)
+
+ result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldRightBottom())
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR)
+ }
+
+ @Test
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_ANYTHING)
+ fun testDefaultIndicators_foldable_enableBubbleAnything_dragFromSplit() {
+ setUpFoldable()
+
+ createVisualIndicator(
+ DesktopModeVisualIndicator.DragStartState.FROM_SPLIT,
+ isSmallTablet = true,
+ isLeftRightSplit = true,
+ )
+ var result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldCenter())
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
+
+ result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldLeftBottom())
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_LEFT_INDICATOR)
+
+ result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldRightBottom())
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_RIGHT_INDICATOR)
+ }
+
+ @Test
+ @EnableFlags(
+ com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN,
+ com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE,
+ )
+ fun testDefaultIndicators_foldable_topBottomSplit() {
+ setUpFoldable()
+
+ createVisualIndicator(
+ DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN,
+ isSmallTablet = true,
+ isLeftRightSplit = false,
+ )
+ var result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldCenter())
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
+
+ result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldLeftEdge())
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
+
+ result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldLeftBottom())
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_LEFT_INDICATOR)
+
+ result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldRightBottom())
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_RIGHT_INDICATOR)
+
+ createVisualIndicator(
+ DesktopModeVisualIndicator.DragStartState.FROM_SPLIT,
+ isSmallTablet = true,
+ isLeftRightSplit = false,
+ )
+ // No indicator as top/bottom split apps should not be dragged
+ result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldCenter())
+ assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
+ result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldLeftBottom())
+ assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
+ result = visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, foldRightBottom())
+ assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
+ fun testDefaultIndicators_crossDisplayDrag_noIndicator() {
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
+
+ // Simulate dragging to a point on a different display.
+ // Even though this point (-10000f, 500f) would trigger TO_SPLIT_LEFT_INDICATOR
+ // on the original display, dragging across displays should show no indicator.
+ val result = visualIndicator.updateIndicatorType(/* displayId= */ 10, PointF(-10000f, 500f))
+
+ assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
+ }
+
+ @Test
+ @EnableFlags(
+ com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN,
+ com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE,
+ )
+ fun testBubbleLeftVisualIndicatorSize() {
+ val dropTargetBounds = Rect(100, 100, 500, 1500)
+ whenever(bubbleBoundsProvider.getBubbleBarExpandedViewDropTargetBounds(/* onLeft= */ true))
+ .thenReturn(dropTargetBounds)
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
+ desktopExecutor.flushAll()
+ mainExecutor.flushAll()
+ visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(100f, 1500f))
+ desktopExecutor.flushAll()
+ mainExecutor.flushAll()
+
+ animatorTestRule.advanceTimeBy(200)
+
+ assertThat(visualIndicator.indicatorBounds).isEqualTo(dropTargetBounds)
+ }
+
+ @Test
+ @EnableFlags(
+ com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN,
+ com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE,
+ )
+ fun testBubbleRightVisualIndicatorSize() {
+ val dropTargetBounds = Rect(1900, 100, 2300, 1500)
+ whenever(bubbleBoundsProvider.getBubbleBarExpandedViewDropTargetBounds(/* onLeft= */ false))
+ .thenReturn(dropTargetBounds)
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
+ desktopExecutor.flushAll()
+ mainExecutor.flushAll()
+ visualIndicator.updateIndicatorType(DEFAULT_DISPLAY, PointF(2300f, 1500f))
+ desktopExecutor.flushAll()
+ mainExecutor.flushAll()
+
+ animatorTestRule.advanceTimeBy(200)
+
+ assertThat(visualIndicator.indicatorBounds).isEqualTo(dropTargetBounds)
+ }
+
+ @Test
+ @DisableFlags(FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX)
+ fun createIndicator_inTransitionFlagDisabled_isAttachedToDisplayArea() {
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
+
+ verify(taskDisplayAreaOrganizer).attachToDisplayArea(anyInt(), any())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX)
+ fun createIndicator_fromFreeform_inTransitionFlagEnabled_isAttachedToDisplayArea() {
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
+
+ verify(taskDisplayAreaOrganizer).attachToDisplayArea(anyInt(), any())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX)
+ fun createIndicator_fromFullscreen_inTransitionFlagEnabled_notAttachedToDisplayArea() {
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
+
+ verify(taskDisplayAreaOrganizer, never()).attachToDisplayArea(anyInt(), any())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX)
+ fun createIndicator_fromSplit_inTransitionFlagEnabled_notAttachedToDisplayArea() {
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
+
+ verify(taskDisplayAreaOrganizer, never()).attachToDisplayArea(anyInt(), any())
+ }
+
+ private fun createVisualIndicator(
+ dragStartState: DesktopModeVisualIndicator.DragStartState,
+ isSmallTablet: Boolean = false,
+ isLeftRightSplit: Boolean = true,
+ ) {
+ visualIndicator =
+ DesktopModeVisualIndicator(
+ desktopExecutor,
+ mainExecutor,
+ syncQueue,
+ taskInfo,
+ displayController,
+ context,
+ taskSurface,
+ taskDisplayAreaOrganizer,
+ dragStartState,
+ bubbleBoundsProvider,
+ snapEventHandler,
+ isSmallTablet,
+ isLeftRightSplit,
+ )
+ }
+
+ private fun foldCenter(): PointF {
+ return PointF(
+ FOLDABLE_DISPLAY_BOUNDS.centerX().toFloat(),
+ FOLDABLE_DISPLAY_BOUNDS.centerY().toFloat(),
+ )
+ }
+
+ private fun foldLeftEdge(): PointF {
+ return PointF(0f, 50f)
+ }
+
+ private fun foldRightEdge(): PointF {
+ return PointF(750f, 50f)
+ }
+
+ private fun foldLeftBottom(): PointF {
+ return PointF(0f, 650f)
+ }
+
+ private fun foldRightBottom(): PointF {
+ return PointF(750f, 650f)
}
companion object {
private const val TRANSITION_AREA_WIDTH = 32
private const val CAPTION_HEIGHT = 50
- private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
private const val NAVBAR_HEIGHT = 50
- private val STABLE_INSETS = Rect(
- DISPLAY_BOUNDS.left,
- DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
- DISPLAY_BOUNDS.right,
- DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
- )
+ private val TABLET_DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
+ private val TABLET_STABLE_INSETS =
+ Rect(
+ TABLET_DISPLAY_BOUNDS.left,
+ TABLET_DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
+ TABLET_DISPLAY_BOUNDS.right,
+ TABLET_DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
+ )
+ private val FOLDABLE_DISPLAY_BOUNDS = Rect(0, 0, 800, 700)
+ private val FOLDABLE_STABLE_INSETS =
+ Rect(
+ FOLDABLE_DISPLAY_BOUNDS.left,
+ FOLDABLE_DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
+ FOLDABLE_DISPLAY_BOUNDS.right,
+ FOLDABLE_DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
+ )
}
-}
\ No newline at end of file
+}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 9e764a9261..e7a6112eeb 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -18,6 +18,13 @@ package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RecentTaskInfo
import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityOptions
+import android.app.ActivityTaskManager.INVALID_TASK_ID
+import android.app.AppOpsManager
+import android.app.KeyguardManager
+import android.app.PendingIntent
+import android.app.PictureInPictureParams
+import android.app.WindowConfiguration
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -25,32 +32,51 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.content.ComponentName
+import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.ActivityInfo.CONFIG_DENSITY
+import android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
import android.content.res.Configuration.ORIENTATION_LANDSCAPE
import android.content.res.Configuration.ORIENTATION_PORTRAIT
+import android.content.res.Resources
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
import android.os.Binder
+import android.os.Bundle
+import android.os.Handler
+import android.os.IBinder
+import android.os.UserHandle
+import android.os.UserManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
-import android.testing.AndroidTestingRunner
+import android.platform.test.flag.junit.FlagsParameterization
+import android.testing.TestableContext
+import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
+import android.view.Display.INVALID_DISPLAY
+import android.view.DragEvent
+import android.view.Gravity
+import android.view.MotionEvent
import android.view.SurfaceControl
+import android.view.WindowInsets
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.widget.Toast
import android.window.DisplayAreaInfo
import android.window.IWindowContainerToken
import android.window.RemoteTransition
+import android.window.TransitionInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
@@ -64,67 +90,128 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.policy.SystemBarUtils.getDesktopViewAppHeaderHeightPx
import com.android.window.flags.Flags
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
+import com.android.window.flags.Flags.FLAG_ENABLE_DISPLAY_DISCONNECT_INTERACTION
+import com.android.window.flags.Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS
+import com.android.window.flags.Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP
+import com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT
+import com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND
+import com.android.window.flags.Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY
import com.android.wm.shell.MockToken
+import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.bubbles.BubbleController
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
-import com.android.wm.shell.common.LaunchAdjacentController
+import com.android.wm.shell.common.HomeIntentProvider
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
-import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
-import com.android.wm.shell.common.split.SplitScreenConstants
-import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
-import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
-import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
-import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSplitScreenTask
+import com.android.wm.shell.common.UserProfileContexts
+import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitResult
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason
+import com.android.wm.shell.desktopmode.DesktopTasksController.DesktopModeEntryExitTransitionListener
+import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
+import com.android.wm.shell.desktopmode.DesktopTasksController.TaskbarDesktopTaskListener
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createHomeTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createSplitScreenTask
+import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCREEN_ANIMATION_DURATION
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
+import com.android.wm.shell.desktopmode.desktopfirst.DesktopFirstListenerManager
+import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
+import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
+import com.android.wm.shell.desktopmode.multidesks.DeskTransition
+import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
+import com.android.wm.shell.desktopmode.multidesks.PreserveDisplayRequestHandler
+import com.android.wm.shell.desktopmode.persistence.Desktop
+import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
import com.android.wm.shell.draganddrop.DragAndDropController
+import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
import com.android.wm.shell.recents.RecentTasksController
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_ANIMATING
+import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED
+import com.android.wm.shell.shared.R as SharedR
+import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.ADB_COMMAND
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_FROM_OVERVIEW
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.KEYBOARD_SHORTCUT
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.TASK_DRAG
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
+import com.android.wm.shell.shared.desktopmode.FakeDesktopConfig
+import com.android.wm.shell.shared.desktopmode.FakeDesktopState
+import com.android.wm.shell.shared.split.SplitScreenConstants
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.FocusTransitionObserver
import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.TestRemoteTransition
import com.android.wm.shell.transition.Transitions
-import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
import com.android.wm.shell.transition.Transitions.TransitionHandler
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModelTestsBase.Companion.HOME_LAUNCHER_PACKAGE_NAME
+import com.android.wm.shell.windowdecor.tiling.SnapEventHandler
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import java.util.Optional
+import java.util.function.Consumer
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
+import kotlin.test.assertIs
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assume.assumeTrue
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.eq
import org.mockito.ArgumentMatchers.isA
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mock
import org.mockito.Mockito
-import org.mockito.Mockito.any
+import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argThat
+import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.atLeastOnce
-import org.mockito.kotlin.capture
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.verifyNoInteractions
+import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
/**
* Test class for {@link DesktopTasksController}
@@ -132,1739 +219,10876 @@ import org.mockito.quality.Strictness
* Usage: atest WMShellUnitTests:DesktopTasksControllerTest
*/
@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class DesktopTasksControllerTest : ShellTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+@ExperimentalCoroutinesApi
+@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() {
- @JvmField @Rule val setFlagsRule = SetFlagsRule()
+ @Mock lateinit var testExecutor: ShellExecutor
+ @Mock lateinit var shellCommandHandler: ShellCommandHandler
+ @Mock lateinit var shellController: ShellController
+ @Mock lateinit var displayController: DisplayController
+ @Mock lateinit var displayLayout: DisplayLayout
+ @Mock lateinit var display: Display
+ @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
+ @Mock lateinit var syncQueue: SyncTransactionQueue
+ @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ @Mock lateinit var transitions: Transitions
+ @Mock lateinit var keyguardManager: KeyguardManager
+ @Mock lateinit var mReturnToDragStartAnimator: ReturnToDragStartAnimator
+ @Mock lateinit var desktopMixedTransitionHandler: DesktopMixedTransitionHandler
+ @Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
+ @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
+ @Mock lateinit var dragAndDropTransitionHandler: DesktopModeDragAndDropTransitionHandler
+ @Mock
+ lateinit var toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler
+ @Mock lateinit var dragToDesktopTransitionHandler: DragToDesktopTransitionHandler
+ @Mock lateinit var mMockDesktopImmersiveController: DesktopImmersiveController
+ @Mock lateinit var splitScreenController: SplitScreenController
+ @Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler
+ @Mock lateinit var dragAndDropController: DragAndDropController
+ @Mock lateinit var multiInstanceHelper: MultiInstanceHelper
+ @Mock lateinit var desktopModeVisualIndicator: DesktopModeVisualIndicator
+ @Mock lateinit var recentTasksController: RecentTasksController
+ @Mock lateinit var snapEventHandler: SnapEventHandler
+ @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+ @Mock private lateinit var mockSurface: SurfaceControl
+ @Mock private lateinit var taskbarDesktopTaskListener: TaskbarDesktopTaskListener
+ @Mock private lateinit var freeformTaskTransitionStarter: FreeformTaskTransitionStarter
+ @Mock private lateinit var mockHandler: Handler
+ @Mock private lateinit var focusTransitionObserver: FocusTransitionObserver
+ @Mock private lateinit var desktopModeEventLogger: DesktopModeEventLogger
+ @Mock private lateinit var desktopModeUiEventLogger: DesktopModeUiEventLogger
+ @Mock lateinit var persistentRepository: DesktopPersistentRepository
+ @Mock lateinit var motionEvent: MotionEvent
+ @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer
+ @Mock private lateinit var mockToast: Toast
+ private lateinit var mockitoSession: StaticMockitoSession
+ @Mock private lateinit var bubbleController: BubbleController
+ @Mock private lateinit var resources: Resources
+ @Mock
+ lateinit var desktopModeEnterExitTransitionListener: DesktopModeEntryExitTransitionListener
+ @Mock private lateinit var userManager: UserManager
+ @Mock
+ private lateinit var desktopWallpaperActivityTokenProvider:
+ DesktopWallpaperActivityTokenProvider
+ @Mock
+ private lateinit var overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver
+ @Mock private lateinit var desksOrganizer: DesksOrganizer
+ @Mock private lateinit var userProfileContexts: UserProfileContexts
+ @Mock private lateinit var desksTransitionsObserver: DesksTransitionObserver
+ @Mock private lateinit var packageManager: PackageManager
+ @Mock private lateinit var mockDisplayContext: Context
+ @Mock private lateinit var dragToDisplayTransitionHandler: DragToDisplayTransitionHandler
+ @Mock
+ private lateinit var moveToDisplayTransitionHandler: DesktopModeMoveToDisplayTransitionHandler
+ @Mock private lateinit var mockAppOpsManager: AppOpsManager
+ @Mock private lateinit var visualIndicatorUpdateScheduler: VisualIndicatorUpdateScheduler
+ @Mock private lateinit var desktopFirstListenerManager: DesktopFirstListenerManager
- @Mock lateinit var testExecutor: ShellExecutor
- @Mock lateinit var shellCommandHandler: ShellCommandHandler
- @Mock lateinit var shellController: ShellController
- @Mock lateinit var displayController: DisplayController
- @Mock lateinit var displayLayout: DisplayLayout
- @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
- @Mock lateinit var syncQueue: SyncTransactionQueue
- @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
- @Mock lateinit var transitions: Transitions
- @Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
- @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
- @Mock
- lateinit var toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler
- @Mock lateinit var dragToDesktopTransitionHandler: DragToDesktopTransitionHandler
- @Mock lateinit var launchAdjacentController: LaunchAdjacentController
- @Mock lateinit var splitScreenController: SplitScreenController
- @Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler
- @Mock lateinit var dragAndDropController: DragAndDropController
- @Mock lateinit var multiInstanceHelper: MultiInstanceHelper
- @Mock lateinit var desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver
- @Mock lateinit var desktopModeVisualIndicator: DesktopModeVisualIndicator
- @Mock lateinit var recentTasksController: RecentTasksController
+ private lateinit var controller: DesktopTasksController
+ private lateinit var shellInit: ShellInit
+ private lateinit var taskRepository: DesktopRepository
+ private lateinit var userRepositories: DesktopUserRepositories
+ private lateinit var desktopTasksLimiter: DesktopTasksLimiter
+ private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener
+ private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy
+ private lateinit var spyContext: TestableContext
+ private lateinit var homeIntentProvider: HomeIntentProvider
+ private lateinit var desktopState: FakeDesktopState
+ private lateinit var desktopConfig: FakeDesktopConfig
- private lateinit var mockitoSession: StaticMockitoSession
- private lateinit var controller: DesktopTasksController
- private lateinit var shellInit: ShellInit
- private lateinit var desktopModeTaskRepository: DesktopModeTaskRepository
- private lateinit var desktopTasksLimiter: DesktopTasksLimiter
- private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener
+ private val shellExecutor = TestShellExecutor()
+ private val bgExecutor = TestShellExecutor()
+ private val testScope = TestScope()
- private val shellExecutor = TestShellExecutor()
+ // Mock running tasks are registered here so we can get the list from mock shell task organizer
+ private val runningTasks = mutableListOf()
- // Mock running tasks are registered here so we can get the list from mock shell task organizer
- private val runningTasks = mutableListOf()
+ private val SECONDARY_DISPLAY_ID = 1
+ private val DISPLAY_DIMENSION_SHORT = 1600
+ private val DISPLAY_DIMENSION_LONG = 2560
+ private val DEFAULT_LANDSCAPE_BOUNDS = Rect(320, 75, 2240, 1275)
+ private val DEFAULT_PORTRAIT_BOUNDS = Rect(200, 165, 1400, 2085)
+ private val RESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 435, 1575, 1635)
+ private val RESIZABLE_PORTRAIT_BOUNDS = Rect(680, 75, 1880, 1275)
+ private val UNRESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 448, 1575, 1611)
+ private val UNRESIZABLE_PORTRAIT_BOUNDS = Rect(830, 75, 1730, 1275)
+ private val wallpaperToken = MockToken().token()
+ private val homeComponentName = ComponentName(HOME_LAUNCHER_PACKAGE_NAME, /* class */ "")
+ private val secondDisplayArea =
+ DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, /* featureId= */ 0)
- private val DISPLAY_DIMENSION_SHORT = 1600
- private val DISPLAY_DIMENSION_LONG = 2560
- private val DEFAULT_LANDSCAPE_BOUNDS = Rect(320, 200, 2240, 1400)
- private val DEFAULT_PORTRAIT_BOUNDS = Rect(200, 320, 1400, 2240)
- private val RESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 680, 1575, 1880)
- private val RESIZABLE_PORTRAIT_BOUNDS = Rect(680, 200, 1880, 1400)
- private val UNRESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 699, 1575, 1861)
- private val UNRESIZABLE_PORTRAIT_BOUNDS = Rect(830, 200, 1730, 1400)
-
- @Before
- fun setUp() {
- mockitoSession =
- mockitoSession()
- .strictness(Strictness.LENIENT)
- .spyStatic(DesktopModeStatus::class.java)
- .startMocking()
- whenever(DesktopModeStatus.isEnabled()).thenReturn(true)
- doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
-
- shellInit = spy(ShellInit(testExecutor))
- desktopModeTaskRepository = DesktopModeTaskRepository()
- desktopTasksLimiter =
- DesktopTasksLimiter(transitions, desktopModeTaskRepository, shellTaskOrganizer)
-
- whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
- whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
- whenever(enterDesktopTransitionHandler.moveToDesktop(any(), any())).thenAnswer { Binder() }
- whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
- whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
- (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
}
- val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
- tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
- whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
-
- controller = createController()
- controller.setSplitScreenController(splitScreenController)
-
- shellInit.init()
-
- val captor = ArgumentCaptor.forClass(RecentsTransitionStateListener::class.java)
- verify(recentsTransitionHandler).addTransitionStateListener(captor.capture())
- recentsTransitionStateListener = captor.value
- }
-
- private fun createController(): DesktopTasksController {
- return DesktopTasksController(
- context,
- shellInit,
- shellCommandHandler,
- shellController,
- displayController,
- shellTaskOrganizer,
- syncQueue,
- rootTaskDisplayAreaOrganizer,
- dragAndDropController,
- transitions,
- enterDesktopTransitionHandler,
- exitDesktopTransitionHandler,
- toggleResizeDesktopTaskTransitionHandler,
- dragToDesktopTransitionHandler,
- desktopModeTaskRepository,
- desktopModeLoggerTransitionObserver,
- launchAdjacentController,
- recentsTransitionHandler,
- multiInstanceHelper,
- shellExecutor,
- Optional.of(desktopTasksLimiter),
- recentTasksController)
- }
-
- @After
- fun tearDown() {
- mockitoSession.finishMocking()
-
- runningTasks.clear()
- }
-
- @Test
- fun instantiate_addInitCallback() {
- verify(shellInit).addInitCallback(any(), any())
- }
-
- @Test
- fun instantiate_flagOff_doNotAddInitCallback() {
- whenever(DesktopModeStatus.isEnabled()).thenReturn(false)
- clearInvocations(shellInit)
-
- createController()
-
- verify(shellInit, never()).addInitCallback(any(), any())
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperDisabled() {
- val homeTask = setUpHomeTask()
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- markTaskHidden(task1)
- markTaskHidden(task2)
-
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(3)
- // Expect order to be from bottom: home, task1, task2
- wct.assertReorderAt(index = 0, homeTask)
- wct.assertReorderAt(index = 1, task1)
- wct.assertReorderAt(index = 2, task2)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperEnabled() {
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- markTaskHidden(task1)
- markTaskHidden(task2)
-
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(3)
- // Expect order to be from bottom: wallpaper intent, task1, task2
- wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
- wct.assertReorderAt(index = 1, task1)
- wct.assertReorderAt(index = 2, task2)
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperDisabled() {
- val homeTask = setUpHomeTask()
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- markTaskVisible(task1)
- markTaskVisible(task2)
-
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(3)
- // Expect order to be from bottom: home, task1, task2
- wct.assertReorderAt(index = 0, homeTask)
- wct.assertReorderAt(index = 1, task1)
- wct.assertReorderAt(index = 2, task2)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperEnabled() {
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- markTaskVisible(task1)
- markTaskVisible(task2)
-
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(3)
- // Expect order to be from bottom: wallpaper intent, task1, task2
- wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
- wct.assertReorderAt(index = 1, task1)
- wct.assertReorderAt(index = 2, task2)
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_someAppsInvisible_reordersAll_desktopWallpaperDisabled() {
- val homeTask = setUpHomeTask()
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- markTaskHidden(task1)
- markTaskVisible(task2)
-
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(3)
- // Expect order to be from bottom: home, task1, task2
- wct.assertReorderAt(index = 0, homeTask)
- wct.assertReorderAt(index = 1, task1)
- wct.assertReorderAt(index = 2, task2)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_someAppsInvisible_reordersAll_desktopWallpaperEnabled() {
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- markTaskHidden(task1)
- markTaskVisible(task2)
-
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(3)
- // Expect order to be from bottom: wallpaper intent, task1, task2
- wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
- wct.assertReorderAt(index = 1, task1)
- wct.assertReorderAt(index = 2, task2)
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_noActiveTasks_reorderHomeToTop_desktopWallpaperDisabled() {
- val homeTask = setUpHomeTask()
-
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(1)
- wct.assertReorderAt(index = 0, homeTask)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_noActiveTasks_addDesktopWallpaper_desktopWallpaperEnabled() {
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperDisabled() {
- val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
- val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
- setUpHomeTask(SECOND_DISPLAY)
- val taskSecondDisplay = setUpFreeformTask(SECOND_DISPLAY)
- markTaskHidden(taskDefaultDisplay)
- markTaskHidden(taskSecondDisplay)
-
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(2)
- // Expect order to be from bottom: home, task
- wct.assertReorderAt(index = 0, homeTaskDefaultDisplay)
- wct.assertReorderAt(index = 1, taskDefaultDisplay)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperEnabled() {
- val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
- setUpHomeTask(SECOND_DISPLAY)
- val taskSecondDisplay = setUpFreeformTask(SECOND_DISPLAY)
- markTaskHidden(taskDefaultDisplay)
- markTaskHidden(taskSecondDisplay)
-
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(2)
- // Expect order to be from bottom: wallpaper intent, task
- wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
- wct.assertReorderAt(index = 1, taskDefaultDisplay)
- }
-
- @Test
- fun showDesktopApps_dontReorderMinimizedTask() {
- val homeTask = setUpHomeTask()
- val freeformTask = setUpFreeformTask()
- val minimizedTask = setUpFreeformTask()
- markTaskHidden(freeformTask)
- markTaskHidden(minimizedTask)
- desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, minimizedTask.taskId)
-
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(2)
- // Reorder home and freeform task to top, don't reorder the minimized task
- wct.assertReorderAt(index = 0, homeTask, toTop = true)
- wct.assertReorderAt(index = 1, freeformTask, toTop = true)
- }
-
- @Test
- fun getVisibleTaskCount_noTasks_returnsZero() {
- assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
- }
-
- @Test
- fun getVisibleTaskCount_twoTasks_bothVisible_returnsTwo() {
- setUpHomeTask()
- setUpFreeformTask().also(::markTaskVisible)
- setUpFreeformTask().also(::markTaskVisible)
- assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
- }
-
- @Test
- fun getVisibleTaskCount_twoTasks_oneVisible_returnsOne() {
- setUpHomeTask()
- setUpFreeformTask().also(::markTaskVisible)
- setUpFreeformTask().also(::markTaskHidden)
- assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
- }
-
- @Test
- fun getVisibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() {
- setUpHomeTask()
- setUpFreeformTask(DEFAULT_DISPLAY).also(::markTaskVisible)
- setUpFreeformTask(SECOND_DISPLAY).also(::markTaskVisible)
- assertThat(controller.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun moveToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
- val task = setUpFullscreenTask()
- setUpLandscapeDisplay()
-
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun moveToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() {
- val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
- setUpLandscapeDisplay()
-
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun moveToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() {
- val task =
- setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true)
- setUpLandscapeDisplay()
-
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun moveToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() {
- val task =
- setUpFullscreenTask(isResizable = false, screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
- setUpLandscapeDisplay()
-
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun moveToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() {
- val task =
- setUpFullscreenTask(
- isResizable = false,
- screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
- shouldLetterbox = true)
- setUpLandscapeDisplay()
-
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun moveToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() {
- val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT)
- setUpPortraitDisplay()
-
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun moveToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() {
- val task =
- setUpFullscreenTask(
- deviceOrientation = ORIENTATION_PORTRAIT,
- screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
- setUpPortraitDisplay()
-
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun moveToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() {
- val task =
- setUpFullscreenTask(
- deviceOrientation = ORIENTATION_PORTRAIT,
- screenOrientation = SCREEN_ORIENTATION_LANDSCAPE,
- shouldLetterbox = true)
- setUpPortraitDisplay()
-
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun moveToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() {
- val task =
- setUpFullscreenTask(
- isResizable = false,
- deviceOrientation = ORIENTATION_PORTRAIT,
- screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
- setUpPortraitDisplay()
-
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun moveToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() {
- val task =
- setUpFullscreenTask(
- isResizable = false,
- deviceOrientation = ORIENTATION_PORTRAIT,
- screenOrientation = SCREEN_ORIENTATION_LANDSCAPE,
- shouldLetterbox = true)
- setUpPortraitDisplay()
-
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
- }
-
- @Test
- fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() {
- val task = setUpFullscreenTask()
- val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
- tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
- }
-
- @Test
- fun moveToDesktop_tdaFreeform_windowingModeSetToUndefined() {
- val task = setUpFullscreenTask()
- val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
- tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED)
- }
-
- @Test
- fun moveToDesktop_nonExistentTask_doesNothing() {
- controller.moveToDesktop(999, transitionSource = UNKNOWN)
- verifyEnterDesktopWCTNotExecuted()
- }
-
- @Test
- fun moveToDesktop_nonRunningTask_launchesInFreeform() {
- whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
-
- val task = createTaskInfo(1)
-
- whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
-
- controller.moveToDesktop(task.taskId, transitionSource = UNKNOWN)
- with(getLatestEnterDesktopWct()) {
- assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
+ @Before
+ fun setUp() {
+ mockitoSession =
+ mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(Toast::class.java)
+ .startMocking()
+
+ desktopState = FakeDesktopState()
+ desktopConfig = FakeDesktopConfig()
+ desktopState.canEnterDesktopMode = true
+
+ spyContext = spy(mContext)
+ shellInit = spy(ShellInit(testExecutor))
+ userRepositories =
+ DesktopUserRepositories(
+ shellInit,
+ shellController,
+ persistentRepository,
+ repositoryInitializer,
+ testScope.backgroundScope,
+ userManager,
+ desktopState,
+ desktopConfig,
+ )
+ desktopTasksLimiter =
+ DesktopTasksLimiter(
+ transitions,
+ userRepositories,
+ shellTaskOrganizer,
+ desksOrganizer,
+ desktopMixedTransitionHandler,
+ MAX_TASK_LIMIT,
+ )
+ desktopModeCompatPolicy = spy(DesktopModeCompatPolicy(spyContext))
+ homeIntentProvider = HomeIntentProvider(context)
+
+ mContext
+ .getOrCreateTestableResources()
+ .addOverride(SharedR.integer.to_desktop_animation_duration_ms, TO_DESKTOP_ANIM_DURATION)
+
+ whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
+ whenever(transitions.startTransition(anyInt(), any(), anyOrNull())).thenAnswer { Binder() }
+ whenever(enterDesktopTransitionHandler.moveToDesktop(any(), any())).thenAnswer { Binder() }
+ whenever(exitDesktopTransitionHandler.startTransition(any(), any(), any(), any()))
+ .thenReturn(Binder())
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ any(),
+ any(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+ whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
+ whenever(displayController.getDisplayContext(anyInt())).thenReturn(mockDisplayContext)
+ whenever(mockDisplayContext.resources).thenReturn(resources)
+ whenever(displayController.getDisplay(anyInt())).thenReturn(display)
+ whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ }
+ whenever(displayLayout.densityDpi()).thenReturn(160)
+ whenever(runBlocking { persistentRepository.readDesktop(any(), any()) })
+ .thenReturn(Desktop.getDefaultInstance())
+ whenever(display.type).thenReturn(Display.TYPE_INTERNAL)
+ doReturn(mockToast).`when` { Toast.makeText(any(), anyInt(), anyInt()) }
+
+ val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECONDARY_DISPLAY_ID))
+ .thenReturn(tda)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY))
+ .thenReturn(secondDisplayArea)
+ whenever(
+ mMockDesktopImmersiveController.exitImmersiveIfApplicable(
+ any(),
+ any(),
+ any(),
+ )
+ )
+ .thenReturn(ExitResult.NoExit)
+ whenever(
+ mMockDesktopImmersiveController.exitImmersiveIfApplicable(
+ any(),
+ anyInt(),
+ anyOrNull(),
+ any(),
+ )
+ )
+ .thenReturn(ExitResult.NoExit)
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken)
+ whenever(userProfileContexts[anyInt()]).thenReturn(context)
+ whenever(userProfileContexts.getOrCreate(anyInt())).thenReturn(context)
+ whenever(freeformTaskTransitionStarter.startPipTransition(any())).thenReturn(Binder())
+ whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY))
+
+ controller = createController()
+ controller.setSplitScreenController(splitScreenController)
+ controller.freeformTaskTransitionStarter = freeformTaskTransitionStarter
+ controller.desktopModeEnterExitTransitionListener = desktopModeEnterExitTransitionListener
+
+ shellInit.init()
+
+ val captor = argumentCaptor()
+ verify(recentsTransitionHandler).addTransitionStateListener(captor.capture())
+ recentsTransitionStateListener = captor.firstValue
+
+ controller.taskbarDesktopTaskListener = taskbarDesktopTaskListener
+ controller.setSnapEventHandler(snapEventHandler)
+
+ taskRepository = userRepositories.current
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = DEFAULT_DISPLAY)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = DEFAULT_DISPLAY)
+
+ spyContext.setMockPackageManager(packageManager)
+ whenever(packageManager.getHomeActivities(ArrayList())).thenReturn(homeComponentName)
}
- }
- @Test
- fun moveToDesktop_topActivityTranslucent_doesNothing() {
- setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- val task =
- setUpFullscreenTask().apply {
- isTopActivityTransparent = true
- numActivities = 1
+ private fun createController() =
+ DesktopTasksController(
+ context,
+ shellInit,
+ shellCommandHandler,
+ shellController,
+ displayController,
+ shellTaskOrganizer,
+ syncQueue,
+ rootTaskDisplayAreaOrganizer,
+ dragAndDropController,
+ transitions,
+ keyguardManager,
+ mReturnToDragStartAnimator,
+ desktopMixedTransitionHandler,
+ enterDesktopTransitionHandler,
+ exitDesktopTransitionHandler,
+ dragAndDropTransitionHandler,
+ toggleResizeDesktopTaskTransitionHandler,
+ dragToDesktopTransitionHandler,
+ mMockDesktopImmersiveController,
+ userRepositories,
+ repositoryInitializer,
+ recentsTransitionHandler,
+ multiInstanceHelper,
+ shellExecutor,
+ testScope.backgroundScope,
+ bgExecutor,
+ Optional.of(desktopTasksLimiter),
+ recentTasksController,
+ mockInteractionJankMonitor,
+ mockHandler,
+ focusTransitionObserver,
+ desktopModeEventLogger,
+ desktopModeUiEventLogger,
+ desktopWallpaperActivityTokenProvider,
+ Optional.of(bubbleController),
+ overviewToDesktopTransitionObserver,
+ desksOrganizer,
+ desksTransitionsObserver,
+ userProfileContexts,
+ desktopModeCompatPolicy,
+ dragToDisplayTransitionHandler,
+ moveToDisplayTransitionHandler,
+ homeIntentProvider,
+ desktopState,
+ desktopConfig,
+ visualIndicatorUpdateScheduler,
+ Optional.of(desktopFirstListenerManager),
+ )
+
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
+
+ runningTasks.clear()
+ testScope.cancel()
+ }
+
+ @Test
+ fun instantiate_addInitCallback() {
+ verify(shellInit).addInitCallback(any(), any())
+ }
+
+ @Test
+ fun doesAnyTaskRequireTaskbarRounding_onlyFreeFormTaskIsRunning_returnFalse() {
+ setUpFreeformTask()
+
+ assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isFalse()
+ }
+
+ @Test
+ fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfFreeFormTask_returnTrue() {
+ val task1 = setUpFreeformTask()
+
+ val argumentCaptor = argumentCaptor()
+ controller.toggleDesktopTaskSize(
+ task1,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH,
+ ),
+ )
+
+ verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ ResizeTrigger.MAXIMIZE_BUTTON,
+ InputMethod.TOUCH,
+ task1,
+ STABLE_BOUNDS.width(),
+ STABLE_BOUNDS.height(),
+ displayController,
+ )
+ assertThat(argumentCaptor.firstValue).isTrue()
+ }
+
+ @Test
+ fun doesAnyTaskRequireTaskbarRounding_fullScreenTaskIsRunning_returnTrue() {
+ val stableBounds = Rect().also { displayLayout.getStableBounds(it) }
+ setUpFreeformTask(bounds = stableBounds, active = true)
+ assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isTrue()
+ }
+
+ @Test
+ fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfMaximizedTask_returnFalse() {
+ val stableBounds = Rect().also { displayLayout.getStableBounds(it) }
+ val task1 = setUpFreeformTask(bounds = stableBounds, active = true)
+
+ val argumentCaptor = argumentCaptor()
+ controller.toggleDesktopTaskSize(
+ task1,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.RESTORE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+ InputMethod.TOUCH,
+ ),
+ )
+
+ verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ eq(ResizeTrigger.MAXIMIZE_BUTTON),
+ eq(InputMethod.TOUCH),
+ eq(task1),
+ anyOrNull(),
+ anyOrNull(),
+ eq(displayController),
+ anyOrNull(),
+ )
+ assertThat(argumentCaptor.firstValue).isFalse()
+ }
+
+ @Test
+ fun doesAnyTaskRequireTaskbarRounding_splitScreenTaskIsRunning_returnTrue() {
+ val stableBounds = Rect().also { displayLayout.getStableBounds(it) }
+ setUpFreeformTask(
+ bounds = Rect(stableBounds.left, stableBounds.top, 500, stableBounds.bottom)
+ )
+
+ assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isTrue()
+ }
+
+ @Test
+ fun getNextFocusedTask_onlyClosingTask_returnInvalidId() {
+ val closingTask = setUpFreeformTask()
+ assertThat(controller.getNextFocusedTask(closingTask)).isEqualTo(INVALID_TASK_ID)
+ }
+
+ @Test
+ fun getNextFocusedTask_oneNonClosingTask_returnNextFocusedTask() {
+ val otherTask = setUpFreeformTask()
+ val closingTask = setUpFreeformTask()
+ assertThat(controller.getNextFocusedTask(closingTask)).isEqualTo(otherTask.taskId)
+ }
+
+ @Test
+ fun getNextFocusedTask_multipleNonClosingTask_returnNextFocusedTask() {
+ val otherTask = setUpFreeformTask()
+ val otherTask2 = setUpFreeformTask()
+ val otherTask3 = setUpFreeformTask()
+ val closingTask = setUpFreeformTask()
+ assertThat(controller.getNextFocusedTask(closingTask)).isNotEqualTo(otherTask.taskId)
+ assertThat(controller.getNextFocusedTask(closingTask)).isNotEqualTo(otherTask2.taskId)
+ assertThat(controller.getNextFocusedTask(closingTask)).isEqualTo(otherTask3.taskId)
+ }
+
+ @Test
+ fun instantiate_cannotEnterDesktopMode_doNotAddInitCallback() {
+ desktopState.canEnterDesktopMode = false
+ clearInvocations(shellInit)
+
+ createController()
+
+ verify(shellInit, never()).addInitCallback(any(), any())
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperDisabled() {
+ val homeTask = setUpHomeTask()
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskHidden(task2)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: home, task1, task2
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperEnabled() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskHidden(task2)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: wallpaper intent, task1, task2
+ wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ /** TODO: b/353948437 - add a same test for when the multi-desk flag is enabled. */
+ fun showDesktopApps_allAppsInvisible_bringNewTaskInFront_tasksAreInCorrectOrder() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskHidden(task2)
+
+ controller.showDesktopApps(
+ DEFAULT_DISPLAY,
+ RemoteTransition(TestRemoteTransition()),
+ task1.taskId,
+ )
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(4)
+ // Expect order to be from bottom: wallpaper intent, task2, task1.
+ // Note task1 is reordered twice, once to bring all apps to the front, and once to reoder it
+ // to top.
+ wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ wct.assertReorderAt(index = 3, task1)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION,
+ )
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ /** TODO: b/353948437 - add a same test for when the multi-desk flag is enabled. */
+ fun showDesktopApps_allAppsInvisible_bringNewTaskInFront_ExceedLimit_tasksAreInCorrectOrder() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+ val task1 = setUpFreeformTask()
+ markTaskHidden(task1)
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+ freeformTasks.forEach { markTaskHidden(it) }
+
+ controller.showDesktopApps(
+ DEFAULT_DISPLAY,
+ RemoteTransition(TestRemoteTransition()),
+ task1.taskId,
+ )
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(MAX_TASK_LIMIT + 3)
+ // Expect order to be from bottom: wallpaper intent, freeformTasks[1], ...,
+ // freeformTasks[MAX_TASK_LIMIT -1], task1.
+ // Note task1 is reordered twice, once to bring all apps to the front, and once to reoder it
+ // to top.
+ wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = MAX_TASK_LIMIT + 2, task1)
+
+ val taskToMinimize = freeformTasks[0]
+ wct.assertReorder(taskToMinimize.token, toTop = true)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun showDesktopApps_deskInactive_bringsToFront_multipleDesksEnabled() {
+ whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
+ .thenReturn(Binder())
+ val deskId = 0
+ // Make desk inactive by activating another desk.
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 1)
+ taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId = 1)
+
+ controller.activateDesk(deskId, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ // Wallpaper is moved to front.
+ wct.assertReorderAt(index = 0, wallpaperToken)
+ // Desk is activated.
+ verify(desksOrganizer).activateDesk(wct, deskId)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun isAnyDeskActive_noTasksVisible_returnsFalse() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskHidden(task2)
+
+ assertThat(controller.isAnyDeskActive(displayId = 0)).isFalse()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun isAnyDeskActive_tasksActiveAndVisible_returnsTrue() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskVisible(task1)
+ markTaskHidden(task2)
+
+ assertThat(controller.isAnyDeskActive(displayId = 0)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+ )
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun isAnyDeskActive_topTransparentFullscreenTask_returnsTrue() {
+ val topTransparentTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+ taskRepository.setTopTransparentFullscreenTaskData(DEFAULT_DISPLAY, topTransparentTask)
+
+ assertThat(controller.isAnyDeskActive(displayId = DEFAULT_DISPLAY)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ )
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_perDisplayWallpaperEnabled_bringsTasksToFront() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ setUpHomeTask(SECOND_DISPLAY)
+ val task1 = setUpFreeformTask(SECOND_DISPLAY)
+ val task2 = setUpFreeformTask(SECOND_DISPLAY)
+ markTaskHidden(task1)
+ markTaskHidden(task2)
+
+ assertThat(taskRepository.getExpandedTasksOrdered(SECOND_DISPLAY)).contains(task1.taskId)
+ assertThat(taskRepository.getExpandedTasksOrdered(SECOND_DISPLAY)).contains(task2.taskId)
+ controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ wct.assertReorder(task1)
+ wct.assertReorder(task2)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_perDisplayWallpaperEnabled_multipleDesksEnabled_bringsDeskToFront() {
+ whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
+ .thenReturn(Binder())
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = 2)
+ setUpHomeTask(SECOND_DISPLAY)
+
+ controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ verify(desksOrganizer).activateDesk(wct, deskId = 2)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ )
+ fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_perDisplayWallpaperEnabled_shouldShowWallpaper() {
+ whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
+ .thenReturn(Binder())
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ setUpHomeTask(SECOND_DISPLAY)
+
+ controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ wct.assertPendingIntent(desktopWallpaperIntent)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() {
+ whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
+ .thenReturn(Binder())
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ val homeTask = setUpHomeTask(SECOND_DISPLAY)
+
+ controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ wct.assertWithoutPendingIntent(desktopWallpaperIntent)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperDisabled() {
+ val homeTask = setUpHomeTask()
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskVisible(task1)
+ markTaskVisible(task2)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: home, task1, task2
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun showDesktopApps_onSecondaryDisplay_desktopWallpaperDisabled_shouldNotMoveLauncher() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ 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(3)
+ // Expect order to be from bottom: home, task1, task2
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperEnabled() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskVisible(task1)
+ markTaskVisible(task2)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: wallpaper intent, task1, task2
+ wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun showDesktopApps_someAppsInvisible_reordersAll_desktopWallpaperDisabled() {
+ val homeTask = setUpHomeTask()
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskVisible(task2)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: home, task1, task2
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun showDesktopApps_someAppsInvisible_desktopWallpaperEnabled_reordersOnlyFreeformTasks() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskVisible(task2)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: wallpaper intent, task1, task2
+ wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_someAppsInvisible_desktopWallpaperEnabled_reordersAll() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskVisible(task2)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: wallpaper intent, task1, task2
+ wct.assertReorderAt(index = 0, wallpaperToken)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun showDesktopApps_noActiveTasks_reorderHomeToTop_desktopWallpaperDisabled() {
+ val homeTask = setUpHomeTask()
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(1)
+ wct.assertReorderAt(index = 0, homeTask)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_noActiveTasks_desktopWallpaperEnabled_addsDesktopWallpaper() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_noActiveTasks_desktopWallpaperEnabled_reordersDesktopWallpaper() {
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ wct.assertReorderAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperDisabled() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
+ val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
+ setUpHomeTask(SECOND_DISPLAY)
+ val taskSecondDisplay = setUpFreeformTask(SECOND_DISPLAY)
+ markTaskHidden(taskDefaultDisplay)
+ markTaskHidden(taskSecondDisplay)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(2)
+ // Expect order to be from bottom: home, task
+ wct.assertReorderAt(index = 0, homeTaskDefaultDisplay)
+ wct.assertReorderAt(index = 1, taskDefaultDisplay)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperEnabled() {
+ whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
+ .thenReturn(Binder())
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
+ val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
+ setUpHomeTask(SECOND_DISPLAY)
+ val taskSecondDisplay = setUpFreeformTask(SECOND_DISPLAY)
+ markTaskHidden(taskDefaultDisplay)
+ markTaskHidden(taskSecondDisplay)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ // Move home to front
+ wct.assertReorderAt(index = 0, homeTaskDefaultDisplay)
+ // Add desktop wallpaper activity
+ wct.assertPendingIntentAt(index = 1, desktopWallpaperIntent)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplayTasks_desktopWallpaperEnabled_multiDesksDisabled() {
+ whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
+ .thenReturn(Binder())
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
+ val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
+ setUpHomeTask(SECOND_DISPLAY)
+ val taskSecondDisplay = setUpFreeformTask(SECOND_DISPLAY)
+ markTaskHidden(taskDefaultDisplay)
+ markTaskHidden(taskSecondDisplay)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ // Move freeform task to front
+ wct.assertReorderAt(index = 2, taskDefaultDisplay)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplayTasks_desktopWallpaperEnabled_multiDesksEnabled() {
+ whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
+ .thenReturn(Binder())
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
+ val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
+ setUpHomeTask(SECOND_DISPLAY)
+ val taskSecondDisplay = setUpFreeformTask(SECOND_DISPLAY)
+ markTaskHidden(taskDefaultDisplay)
+ markTaskHidden(taskSecondDisplay)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ // Move desktop tasks to front
+ verify(desksOrganizer).activateDesk(wct, deskId = DEFAULT_DISPLAY)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun showDesktopApps_desktopWallpaperDisabled_dontReorderMinimizedTask() {
+ val homeTask = setUpHomeTask()
+ val freeformTask = setUpFreeformTask()
+ val minimizedTask = setUpFreeformTask()
+
+ markTaskHidden(freeformTask)
+ markTaskHidden(minimizedTask)
+ taskRepository.minimizeTask(DEFAULT_DISPLAY, minimizedTask.taskId)
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(2)
+ // Reorder home and freeform task to top, don't reorder the minimized task
+ wct.assertReorderAt(index = 0, homeTask, toTop = true)
+ wct.assertReorderAt(index = 1, freeformTask, toTop = true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ /** TODO: b/362720497 - add multi-desk version when minimization is implemented. */
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun showDesktopApps_desktopWallpaperEnabled_dontReorderMinimizedTask() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+ val homeTask = setUpHomeTask()
+ val freeformTask = setUpFreeformTask()
+ val minimizedTask = setUpFreeformTask()
+
+ markTaskHidden(freeformTask)
+ markTaskHidden(minimizedTask)
+ taskRepository.minimizeTask(DEFAULT_DISPLAY, minimizedTask.taskId)
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Move home to front
+ wct.assertReorderAt(index = 0, homeTask, toTop = true)
+ // Add desktop wallpaper activity
+ wct.assertPendingIntentAt(index = 1, desktopWallpaperIntent)
+ // Reorder freeform task to top, don't reorder the minimized task
+ wct.assertReorderAt(index = 2, freeformTask, toTop = true)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun isAnyDeskActive_noTasks_returnsFalse() {
+ assertThat(controller.isAnyDeskActive(DEFAULT_DISPLAY)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun isAnyDeskActive_noActiveDesk_returnsFalse() {
+ taskRepository.setDeskInactive(deskId = 0)
+
+ assertThat(controller.isAnyDeskActive(DEFAULT_DISPLAY)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun isAnyDeskActive_withActiveDesk_returnsTrue() {
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+
+ assertThat(controller.isAnyDeskActive(DEFAULT_DISPLAY)).isTrue()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun isAnyDeskActive_twoTasks_bothVisible_returnsTrue() {
+ setUpHomeTask()
+
+ setUpFreeformTask().also(::markTaskVisible)
+ setUpFreeformTask().also(::markTaskVisible)
+
+ assertThat(controller.isAnyDeskActive(DEFAULT_DISPLAY)).isTrue()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun isInDesktop_twoTasks_oneVisible_returnsTrue() {
+ setUpHomeTask()
+
+ setUpFreeformTask().also(::markTaskVisible)
+ setUpFreeformTask().also(::markTaskHidden)
+
+ assertThat(controller.isAnyDeskActive(DEFAULT_DISPLAY)).isTrue()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun isAnyDeskActive_twoTasksVisibleOnDifferentDisplays_returnsTrue() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ setUpHomeTask()
+
+ setUpFreeformTask(DEFAULT_DISPLAY).also(::markTaskVisible)
+ setUpFreeformTask(SECOND_DISPLAY).also(::markTaskVisible)
+
+ assertThat(controller.isAnyDeskActive(SECOND_DISPLAY)).isTrue()
+ }
+
+ @Test
+ fun addMoveToDeskTaskChanges_gravityLeft_noBoundsApplied() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask(gravity = Gravity.LEFT)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(finalBounds).isNull()
+ }
+
+ @Test
+ fun addMoveToDeskTaskChanges_gravityRight_noBoundsApplied() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask(gravity = Gravity.RIGHT)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(finalBounds).isNull()
+ }
+
+ @Test
+ fun addMoveToDeskTaskChanges_gravityTop_noBoundsApplied() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask(gravity = Gravity.TOP)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(finalBounds).isNull()
+ }
+
+ @Test
+ fun addMoveToDeskTaskChanges_gravityBottom_noBoundsApplied() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask(gravity = Gravity.BOTTOM)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(finalBounds).isNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES)
+ fun addMoveToDeskTaskChanges_newTaskInstance_inheritsClosingInstanceBounds() {
+ // Setup existing task.
+ val existingTask = setUpFreeformTask(active = true)
+ val testComponent = ComponentName(/* package */ "test.package", /* class */ "test.class")
+ existingTask.topActivity = testComponent
+ existingTask.configuration.windowConfiguration.setBounds(Rect(0, 0, 500, 500))
+ // Set up new instance of already existing task.
+ val launchingTask =
+ setUpFullscreenTask().apply {
+ topActivityInfo = ActivityInfo().apply { launchMode = LAUNCH_SINGLE_INSTANCE }
+ }
+ launchingTask.topActivity = testComponent
+
+ // Move new instance to desktop. By default multi instance is not supported so first
+ // instance will close.
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, launchingTask, deskId = 0)
+
+ // New instance should inherit task bounds of old instance.
+ assertThat(findBoundsChange(wct, launchingTask))
+ .isEqualTo(existingTask.configuration.windowConfiguration.bounds)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES)
+ fun handleRequest_newTaskInstance_inheritsClosingInstanceBounds() {
+ setUpLandscapeDisplay()
+ // Setup existing task.
+ val existingTask = setUpFreeformTask(active = true)
+ val testComponent = ComponentName(/* package */ "test.package", /* class */ "test.class")
+ existingTask.topActivity = testComponent
+ existingTask.configuration.windowConfiguration.setBounds(Rect(0, 0, 500, 500))
+ // Set up new instance of already existing task.
+ val launchingTask =
+ setUpFreeformTask(active = false).apply {
+ topActivityInfo = ActivityInfo().apply { launchMode = LAUNCH_SINGLE_INSTANCE }
+ }
+ taskRepository.removeTask(launchingTask.taskId)
+ launchingTask.topActivity = testComponent
+
+ // Move new instance to desktop. By default multi instance is not supported so first
+ // instance will close.
+ val wct = controller.handleRequest(Binder(), createTransition(launchingTask))
+
+ assertNotNull(wct, "should handle request")
+ val finalBounds = findBoundsChange(wct, launchingTask)
+ // New instance should inherit task bounds of old instance.
+ assertThat(finalBounds).isEqualTo(existingTask.configuration.windowConfiguration.bounds)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun handleRequest_newFreeformTaskLaunch_cascadeApplied() {
+ val stableBounds =
+ Rect(0, 0, DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT - TASKBAR_FRAME_HEIGHT)
+ whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(stableBounds)
+ }
+ setUpLandscapeDisplay()
+
+ setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS)
+ val freeformTask = setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS, active = false)
+
+ val wct = controller.handleRequest(Binder(), createTransition(freeformTask))
+
+ assertNotNull(wct, "should handle request")
+ val finalBounds = findBoundsChange(wct, freeformTask)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.BottomRight)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS, Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_newFreeformTaskLaunch_newDesk_desksCascadeIndependently() {
+ setUpLandscapeDisplay()
+ val stableBounds =
+ Rect(0, 0, DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT - TASKBAR_FRAME_HEIGHT)
+ whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(stableBounds)
}
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- verifyEnterDesktopWCTNotExecuted()
- }
+ // Launch freeform tasks in default desk.
+ setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS)
+ val freeformTask = setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS, active = false)
+ controller.handleRequest(Binder(), createTransition(freeformTask))
- @Test
- fun moveToDesktop_systemUIActivity_doesNothing() {
- val task = setUpFullscreenTask()
+ // Create new active desk and launch new task.
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 2)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 2)
+ val newDeskTask = createFullscreenTask(displayId = DEFAULT_DISPLAY)
+ val wct = controller.handleRequest(Binder(), createTransition(newDeskTask))
- // Set task as systemUI package
- val systemUIPackageName = context.resources.getString(
- com.android.internal.R.string.config_systemUi)
- val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
- task.baseActivity = baseComponent
-
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- verifyEnterDesktopWCTNotExecuted()
- }
-
- @Test
- fun moveToDesktop_deviceSupported_taskIsMovedToDesktop() {
- val task = setUpFullscreenTask()
-
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
-
- val wct = getLatestEnterDesktopWct()
- assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun moveToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperDisabled() {
- val homeTask = setUpHomeTask()
- val freeformTask = setUpFreeformTask()
- val fullscreenTask = setUpFullscreenTask()
- markTaskHidden(freeformTask)
-
- controller.moveToDesktop(fullscreenTask, transitionSource = UNKNOWN)
-
- with(getLatestEnterDesktopWct()) {
- // Operations should include home task, freeform task
- assertThat(hierarchyOps).hasSize(3)
- assertReorderSequence(homeTask, freeformTask, fullscreenTask)
- assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FREEFORM)
+ // New task should be cascaded independently of tasks in other desks.
+ assertNotNull(wct, "should handle request")
+ val finalBounds = findBoundsChange(wct, newDeskTask)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.Center)
}
- }
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun moveToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperEnabled() {
- val freeformTask = setUpFreeformTask()
- val fullscreenTask = setUpFullscreenTask()
- markTaskHidden(freeformTask)
-
- controller.moveToDesktop(fullscreenTask, transitionSource = UNKNOWN)
-
- with(getLatestEnterDesktopWct()) {
- // Operations should include wallpaper intent, freeform task, fullscreen task
- assertThat(hierarchyOps).hasSize(3)
- assertPendingIntentAt(index = 0, desktopWallpaperIntent)
- assertReorderAt(index = 1, freeformTask)
- assertReorderAt(index = 2, fullscreenTask)
- assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FREEFORM)
- }
- }
-
- @Test
- fun moveToDesktop_onlyFreeformTasksFromCurrentDisplayBroughtToFront() {
- setUpHomeTask(displayId = DEFAULT_DISPLAY)
- val freeformTaskDefault = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val fullscreenTaskDefault = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
- markTaskHidden(freeformTaskDefault)
-
- val homeTaskSecond = setUpHomeTask(displayId = SECOND_DISPLAY)
- val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY)
- markTaskHidden(freeformTaskSecond)
-
- controller.moveToDesktop(fullscreenTaskDefault, transitionSource = UNKNOWN)
-
- with(getLatestEnterDesktopWct()) {
- // Check that hierarchy operations do not include tasks from second display
- assertThat(hierarchyOps.map { it.container }).doesNotContain(homeTaskSecond.token.asBinder())
- assertThat(hierarchyOps.map { it.container })
- .doesNotContain(freeformTaskSecond.token.asBinder())
- }
- }
-
- @Test
- fun moveToDesktop_splitTaskExitsSplit() {
- val task = setUpSplitScreenTask()
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
- verify(splitScreenController)
- .prepareExitSplitScreen(any(), anyInt(), eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE))
- }
-
- @Test
- fun moveToDesktop_fullscreenTaskDoesNotExitSplit() {
- val task = setUpFullscreenTask()
- controller.moveToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
- verify(splitScreenController, never())
- .prepareExitSplitScreen(any(), anyInt(), eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE))
- }
-
- @Test
- fun moveToDesktop_bringsTasksOverLimit_dontShowBackTask() {
- val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
- val homeTask = setUpHomeTask()
- val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() }
- val newTask = setUpFullscreenTask()
-
- controller.moveToDesktop(newTask, transitionSource = UNKNOWN)
-
- val wct = getLatestEnterDesktopWct()
- assertThat(wct.hierarchyOps.size).isEqualTo(taskLimit + 1) // visible tasks + home
- wct.assertReorderAt(0, homeTask)
- for (i in 1.. setUpFreeformTask() }
-
- controller.moveTaskToFront(freeformTasks[0])
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT)
- assertThat(wct.hierarchyOps.size).isEqualTo(2) // move-to-front + minimize
- wct.assertReorderAt(0, freeformTasks[0], toTop = true)
- wct.assertReorderAt(1, freeformTasks[1], toTop = false)
- }
-
- @Test
- fun moveToNextDisplay_noOtherDisplays() {
- whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY))
- val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- controller.moveToNextDisplay(task.taskId)
- verifyWCTNotExecuted()
- }
-
- @Test
- fun moveToNextDisplay_moveFromFirstToSecondDisplay() {
- // 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)
-
- val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- controller.moveToNextDisplay(task.taskId)
- with(getLatestWct(type = TRANSIT_CHANGE)) {
- assertThat(hierarchyOps).hasSize(1)
- assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder())
- assertThat(hierarchyOps[0].isReparent).isTrue()
- assertThat(hierarchyOps[0].newParent).isEqualTo(secondDisplayArea.token.asBinder())
- assertThat(hierarchyOps[0].toTop).isTrue()
- }
- }
-
- @Test
- fun moveToNextDisplay_moveFromSecondToFirstDisplay() {
- // Set up two display ids
- whenever(rootTaskDisplayAreaOrganizer.displayIds)
- .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
- // Create a mock for the target display area: default display
- val defaultDisplayArea = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
- whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
- .thenReturn(defaultDisplayArea)
-
- val task = setUpFreeformTask(displayId = SECOND_DISPLAY)
- controller.moveToNextDisplay(task.taskId)
-
- with(getLatestWct(type = TRANSIT_CHANGE)) {
- assertThat(hierarchyOps).hasSize(1)
- assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder())
- assertThat(hierarchyOps[0].isReparent).isTrue()
- assertThat(hierarchyOps[0].newParent).isEqualTo(defaultDisplayArea.token.asBinder())
- assertThat(hierarchyOps[0].toTop).isTrue()
- }
- }
-
- @Test
- fun getTaskWindowingMode() {
- val fullscreenTask = setUpFullscreenTask()
- val freeformTask = setUpFreeformTask()
-
- assertThat(controller.getTaskWindowingMode(fullscreenTask.taskId))
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
- assertThat(controller.getTaskWindowingMode(freeformTask.taskId))
- .isEqualTo(WINDOWING_MODE_FREEFORM)
- assertThat(controller.getTaskWindowingMode(999)).isEqualTo(WINDOWING_MODE_UNDEFINED)
- }
-
- @Test
- fun onDesktopWindowClose_noActiveTasks() {
- val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, 1 /* taskId */)
- // Doesn't modify transaction
- assertThat(wct.hierarchyOps).isEmpty()
- }
-
- @Test
- fun onDesktopWindowClose_singleActiveTask_noWallpaperActivityToken() {
- val task = setUpFreeformTask()
- val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, task.taskId)
- // Doesn't modify transaction
- assertThat(wct.hierarchyOps).isEmpty()
- }
-
- @Test
- fun onDesktopWindowClose_singleActiveTask_hasWallpaperActivityToken() {
- val task = setUpFreeformTask()
- val wallpaperToken = MockToken().token()
- desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
-
- val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, task.taskId)
- // Adds remove wallpaper operation
- wct.assertRemoveAt(index = 0, wallpaperToken)
- }
-
- @Test
- fun onDesktopWindowClose_multipleActiveTasks() {
- val task1 = setUpFreeformTask()
- setUpFreeformTask()
- val wallpaperToken = MockToken().token()
- desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
-
- val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, task1.taskId)
- // Doesn't modify transaction
- assertThat(wct.hierarchyOps).isEmpty()
- }
-
- @Test
- fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
- val freeformTask = setUpFreeformTask()
- markTaskVisible(freeformTask)
- val fullscreenTask = createFullscreenTask()
-
- val result = controller.handleRequest(Binder(), createTransition(fullscreenTask))
- assertThat(result?.changes?.get(fullscreenTask.token.asBinder())?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FREEFORM)
- }
-
- @Test
- fun handleRequest_fullscreenTaskToFreeform_underTaskLimit_dontMinimize() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
- val freeformTask = setUpFreeformTask()
- markTaskVisible(freeformTask)
- val fullscreenTask = createFullscreenTask()
-
- val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
-
- // Make sure we only reorder the new task to top (we don't reorder the old task to bottom)
- assertThat(wct?.hierarchyOps?.size).isEqualTo(1)
- wct!!.assertReorderAt(0, fullscreenTask, toTop = true)
- }
-
- @Test
- fun handleRequest_fullscreenTaskToFreeform_bringsTasksOverLimit_otherTaskIsMinimized() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
- val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
- val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() }
- freeformTasks.forEach { markTaskVisible(it) }
- val fullscreenTask = createFullscreenTask()
-
- val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
-
- // Make sure we reorder the new task to top, and the back task to the bottom
- assertThat(wct!!.hierarchyOps.size).isEqualTo(2)
- wct.assertReorderAt(0, fullscreenTask, toTop = true)
- wct.assertReorderAt(1, freeformTasks[0], toTop = false)
- }
-
- @Test
- fun handleRequest_fullscreenTask_freeformNotVisible_returnNull() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
- val freeformTask = setUpFreeformTask()
- markTaskHidden(freeformTask)
- val fullscreenTask = createFullscreenTask()
- assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
- }
-
- @Test
- fun handleRequest_fullscreenTask_noOtherTasks_returnNull() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
- val fullscreenTask = createFullscreenTask()
- assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
- }
-
- @Test
- fun handleRequest_fullscreenTask_freeformTaskOnOtherDisplay_returnNull() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
- val fullscreenTaskDefaultDisplay = createFullscreenTask(displayId = DEFAULT_DISPLAY)
- createFreeformTask(displayId = SECOND_DISPLAY)
-
- val result = controller.handleRequest(Binder(), createTransition(fullscreenTaskDefaultDisplay))
- assertThat(result).isNull()
- }
-
- @Test
- fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_minimize() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
- val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
- val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() }
- freeformTasks.forEach { markTaskVisible(it) }
- val newFreeformTask = createFreeformTask()
-
- val wct = controller.handleRequest(Binder(), createTransition(newFreeformTask, TRANSIT_OPEN))
-
- assertThat(wct?.hierarchyOps?.size).isEqualTo(1)
- wct!!.assertReorderAt(0, freeformTasks[0], toTop = false) // Reorder to the bottom
- }
-
- @Test
- fun handleRequest_freeformTask_freeformNotVisible_reorderedToTop() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
- val freeformTask1 = setUpFreeformTask()
- markTaskHidden(freeformTask1)
-
- val freeformTask2 = createFreeformTask()
- val result =
- controller.handleRequest(Binder(), createTransition(freeformTask2, type = TRANSIT_TO_FRONT))
-
- assertThat(result?.hierarchyOps?.size).isEqualTo(2)
- result!!.assertReorderAt(1, freeformTask2, toTop = true)
- }
-
- @Test
- fun handleRequest_freeformTask_noOtherTasks_reorderedToTop() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
- val task = createFreeformTask()
- val result = controller.handleRequest(Binder(), createTransition(task))
-
- assertThat(result?.hierarchyOps?.size).isEqualTo(1)
- result!!.assertReorderAt(0, task, toTop = true)
- }
-
- @Test
- fun handleRequest_freeformTask_freeformOnOtherDisplayOnly_reorderedToTop() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
- val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
- val taskSecondDisplay = createFreeformTask(displayId = SECOND_DISPLAY)
-
- val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay))
- assertThat(result?.hierarchyOps?.size).isEqualTo(1)
- result!!.assertReorderAt(0, taskDefaultDisplay, toTop = true)
- }
-
- @Test
- fun handleRequest_freeformTask_alreadyInDesktop_noOverrideDensity_noConfigDensityChange() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
- whenever(DesktopModeStatus.useDesktopOverrideDensity()).thenReturn(false)
-
- val freeformTask1 = setUpFreeformTask()
- markTaskVisible(freeformTask1)
-
- val freeformTask2 = createFreeformTask()
- val result =
- controller.handleRequest(freeformTask2.token.asBinder(), createTransition(freeformTask2))
- assertFalse(result.anyDensityConfigChange(freeformTask2.token))
- }
-
- @Test
- fun handleRequest_freeformTask_alreadyInDesktop_overrideDensity_hasConfigDensityChange() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
- whenever(DesktopModeStatus.useDesktopOverrideDensity()).thenReturn(true)
-
- val freeformTask1 = setUpFreeformTask()
- markTaskVisible(freeformTask1)
-
- val freeformTask2 = createFreeformTask()
- val result =
- controller.handleRequest(freeformTask2.token.asBinder(), createTransition(freeformTask2))
- assertTrue(result.anyDensityConfigChange(freeformTask2.token))
- }
-
- @Test
- fun handleRequest_notOpenOrToFrontTransition_returnNull() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
- val task =
- TestRunningTaskInfoBuilder()
- .setActivityType(ACTIVITY_TYPE_STANDARD)
- .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
- .build()
- val transition = createTransition(task = task, type = WindowManager.TRANSIT_CLOSE)
- val result = controller.handleRequest(Binder(), transition)
- assertThat(result).isNull()
- }
-
- @Test
- fun handleRequest_noTriggerTask_returnNull() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
- assertThat(controller.handleRequest(Binder(), createTransition(task = null))).isNull()
- }
-
- @Test
- fun handleRequest_triggerTaskNotStandard_returnNull() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
- val task = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
- assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
- }
-
- @Test
- fun handleRequest_triggerTaskNotFullscreenOrFreeform_returnNull() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
- val task =
- TestRunningTaskInfoBuilder()
- .setActivityType(ACTIVITY_TYPE_STANDARD)
- .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
- .build()
- assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
- }
-
- @Test
- fun handleRequest_recentsAnimationRunning_returnNull() {
- // Set up a visible freeform task so a fullscreen task should be converted to freeform
- val freeformTask = setUpFreeformTask()
- markTaskVisible(freeformTask)
-
- // Mark recents animation running
- recentsTransitionStateListener.onAnimationStateChanged(true)
-
- // Open a fullscreen task, check that it does not result in a WCT with changes to it
- val fullscreenTask = createFullscreenTask()
- assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
- }
-
- @Test
- fun handleRequest_shouldLaunchAsModal_returnSwitchToFullscreenWCT() {
- setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- val task =
- setUpFreeformTask().apply {
- isTopActivityTransparent = true
- numActivities = 1
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun handleRequest_freeformTaskAlreadyExistsInDesktopMode_cascadeNotApplied() {
+ setUpLandscapeDisplay()
+ val stableBounds =
+ Rect(0, 0, DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT - TASKBAR_FRAME_HEIGHT)
+ whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(stableBounds)
}
- val result = controller.handleRequest(Binder(), createTransition(task))
- assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
- }
+ setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS)
+ val freeformTask = setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS)
- @Test
- fun handleRequest_systemUIActivity_returnSwitchToFullscreenWCT() {
- val task = setUpFreeformTask()
+ val wct = controller.handleRequest(Binder(), createTransition(freeformTask))
- // Set task as systemUI package
- val systemUIPackageName = context.resources.getString(
- com.android.internal.R.string.config_systemUi)
- val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
- task.baseActivity = baseComponent
+ assertNull(wct, "should not handle request")
+ }
- val result = controller.handleRequest(Binder(), createTransition(task))
- assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDeskTaskChanges_activeButClosingTask_cascadeNotApplied() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect().also { displayLayout.getStableBoundsForDesktopMode(it) }
+
+ val closingTask = setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS)
+ taskRepository.addClosingTask(
+ displayId = DEFAULT_DISPLAY,
+ deskId = 0,
+ taskId = closingTask.taskId,
+ )
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.Center)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDeskTaskChanges_positionBottomRight() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect().also { displayLayout.getStableBoundsForDesktopMode(it) }
+
+ setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS)
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.BottomRight)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDeskTaskChanges_positionTopLeft() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect().also { displayLayout.getStableBoundsForDesktopMode(it) }
+
+ addFreeformTaskAtPosition(DesktopTaskPosition.BottomRight, stableBounds)
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.TopLeft)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDeskTaskChanges_positionBottomLeft() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect().also { displayLayout.getStableBoundsForDesktopMode(it) }
+
+ addFreeformTaskAtPosition(DesktopTaskPosition.TopLeft, stableBounds)
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.BottomLeft)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDeskTaskChanges_positionTopRight() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect().also { displayLayout.getStableBoundsForDesktopMode(it) }
+
+ addFreeformTaskAtPosition(DesktopTaskPosition.BottomLeft, stableBounds)
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.TopRight)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDeskTaskChanges_positionResetsToCenter() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect().also { displayLayout.getStableBoundsForDesktopMode(it) }
+
+ addFreeformTaskAtPosition(DesktopTaskPosition.TopRight, stableBounds)
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.Center)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDeskTaskChanges_lastWindowSnapLeft_positionResetsToCenter() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect().also { displayLayout.getStableBoundsForDesktopMode(it) }
+
+ // Add freeform task with half display size snap bounds at left side.
+ setUpFreeformTask(
+ bounds = Rect(stableBounds.left, stableBounds.top, 500, stableBounds.bottom)
+ )
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.Center)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDeskTaskChanges_lastWindowSnapRight_positionResetsToCenter() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect().also { displayLayout.getStableBoundsForDesktopMode(it) }
+
+ // Add freeform task with half display size snap bounds at right side.
+ setUpFreeformTask(
+ bounds =
+ Rect(
+ stableBounds.right - 500,
+ stableBounds.top,
+ stableBounds.right,
+ stableBounds.bottom,
+ )
+ )
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.Center)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDeskTaskChanges_lastWindowMaximised_positionResetsToCenter() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect().also { displayLayout.getStableBoundsForDesktopMode(it) }
+
+ // Add maximised freeform task.
+ setUpFreeformTask(bounds = Rect(stableBounds))
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.Center)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDeskTaskChanges_defaultToCenterIfFree() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect().also { displayLayout.getStableBoundsForDesktopMode(it) }
+
+ val minTouchTarget =
+ context.resources.getDimensionPixelSize(
+ R.dimen.freeform_required_visible_empty_space_in_header
+ )
+ addFreeformTaskAtPosition(
+ DesktopTaskPosition.Center,
+ stableBounds,
+ Rect(0, 0, 1600, 1200),
+ Point(0, minTouchTarget + 1),
+ )
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.Center)
+ }
+
+ @Test
+ fun addMoveToDeskTaskChanges_excludeCaptionFromAppBounds_nonResizableLandscape() {
+ setUpLandscapeDisplay()
+ val task =
+ setUpFullscreenTask(
+ isResizable = false,
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE,
+ )
+ whenever(desktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(task)).thenReturn(true)
+ val initialAspectRatio = calculateAspectRatio(task)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
+
+ val finalBounds = findBoundsChange(wct, task)
+ val displayId = taskRepository.getDisplayForDesk(deskId = 0)
+ val displayContext = displayController.getDisplayContext(displayId) ?: context
+ val captionInsets = getDesktopViewAppHeaderHeightPx(displayContext)
+ finalBounds!!.top += captionInsets
+ val finalAspectRatio =
+ maxOf(finalBounds.height(), finalBounds.width()) /
+ minOf(finalBounds.height(), finalBounds.width()).toFloat()
+ assertThat(finalAspectRatio).isWithin(FLOAT_TOLERANCE).of(initialAspectRatio)
+ }
+
+ @Test
+ fun addMoveToDeskTaskChanges_excludeCaptionFromAppBounds_nonResizablePortrait() {
+ setUpLandscapeDisplay()
+ val task =
+ setUpFullscreenTask(
+ isResizable = false,
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
+ )
+ whenever(desktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(task)).thenReturn(true)
+ val initialAspectRatio = calculateAspectRatio(task)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
+
+ val finalBounds = findBoundsChange(wct, task)
+ val displayId = taskRepository.getDisplayForDesk(deskId = 0)
+ val displayContext = displayController.getDisplayContext(displayId) ?: context
+ val captionInsets = getDesktopViewAppHeaderHeightPx(displayContext)
+ finalBounds!!.top += captionInsets
+ val finalAspectRatio =
+ maxOf(finalBounds.height(), finalBounds.width()) /
+ minOf(finalBounds.height(), finalBounds.width()).toFloat()
+ assertThat(finalAspectRatio).isWithin(FLOAT_TOLERANCE).of(initialAspectRatio)
+ }
+
+ @Test
+ fun launchIntent_taskInDesktopMode_transitionStarted() {
+ setUpLandscapeDisplay()
+ val freeformTask = setUpFreeformTask()
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_OPEN),
+ any(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+
+ controller.startLaunchIntentTransition(
+ freeformTask.baseIntent,
+ Bundle.EMPTY,
+ DEFAULT_DISPLAY,
+ )
+
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
+ assertThat(wct.hierarchyOps).hasSize(1)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ )
+ fun launchIntent_taskInDesktopMode_onSecondaryDisplayNotInDesktopMode_transitionStarted() {
+ setUpLandscapeDisplay()
+ taskRepository.addDesk(SECOND_DISPLAY, deskId = 2) // Inactive desk.
+ val intent = Intent().setComponent(homeComponentName)
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_OPEN),
+ any(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+
+ controller.startLaunchIntentTransition(intent, Bundle.EMPTY, SECOND_DISPLAY)
+
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
+ // We expect two actions: open the app and open the wallpaper.
+ assertThat(wct.hierarchyOps).hasSize(2)
+ val hOps0 = wct.hierarchyOps[0]
+ val hOps1 = wct.hierarchyOps[1]
+ assertThat(hOps0.type).isEqualTo(HIERARCHY_OP_TYPE_PENDING_INTENT)
+ val activityOptions0 = ActivityOptions.fromBundle(hOps0.launchOptions)
+ assertThat(activityOptions0.launchDisplayId).isEqualTo(SECOND_DISPLAY)
+ assertThat(hOps1.type).isEqualTo(HIERARCHY_OP_TYPE_PENDING_INTENT)
+ val activityOptions1 = ActivityOptions.fromBundle(hOps1.launchOptions)
+ assertThat(activityOptions1.launchDisplayId).isEqualTo(SECOND_DISPLAY)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_FORCE_CLOSE_TOP_TRANSPARENT_FULLSCREEN_TASK)
+ fun launchNewTask_topTransparentFullscreenTaskIdPassedToClear() {
+ setUpLandscapeDisplay()
+ val topTransparentTask =
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = DEFAULT_DISPLAY)
+ taskRepository.setTopTransparentFullscreenTaskData(DEFAULT_DISPLAY, topTransparentTask)
+
+ val task = setUpFreeformTask()
+ controller.startLaunchIntentTransition(task.baseIntent, Bundle.EMPTY, DEFAULT_DISPLAY)
+
+ verify(desktopMixedTransitionHandler)
+ .startLaunchTransition(
+ eq(TRANSIT_OPEN),
+ any(),
+ anyOrNull(),
+ anyOrNull(),
+ eq(topTransparentTask.taskId),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun addMoveToDeskTaskChanges_landscapeDevice_userFullscreenOverride_defaultPortraitBounds() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask(enableUserFullscreenOverride = true)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
+
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun addMoveToDeskTaskChanges_landscapeDevice_systemFullscreenOverride_defaultPortraitBounds() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask(enableSystemFullscreenOverride = true)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
+
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun addMoveToDeskTaskChanges_landscapeDevice_portraitResizableApp_aspectRatioOverridden() {
+ setUpLandscapeDisplay()
+ val task =
+ setUpFullscreenTask(
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
+ shouldLetterbox = true,
+ aspectRatioOverrideApplied = true,
+ )
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
+
+ assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun addMoveToDeskTaskChanges_portraitDevice_userFullscreenOverride_defaultPortraitBounds() {
+ setUpPortraitDisplay()
+ val task = setUpFullscreenTask(enableUserFullscreenOverride = true)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
+
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun addMoveToDeskTaskChanges_portraitDevice_systemFullscreenOverride_defaultPortraitBounds() {
+ setUpPortraitDisplay()
+ val task = setUpFullscreenTask(enableSystemFullscreenOverride = true)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
+
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun addMoveToDeskTaskChanges_portraitDevice_landscapeResizableApp_aspectRatioOverridden() {
+ setUpPortraitDisplay()
+ val task =
+ setUpFullscreenTask(
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE,
+ deviceOrientation = ORIENTATION_PORTRAIT,
+ shouldLetterbox = true,
+ aspectRatioOverrideApplied = true,
+ )
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
+
+ assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ fun addMoveToDeskTaskChanges_inSizeCompatMode_originalAspectRatioMaintained() {
+ setUpLandscapeDisplay()
+ val task =
+ setUpFullscreenTask(
+ isResizable = false,
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
+ deviceOrientation = ORIENTATION_PORTRAIT,
+ )
+ // Simulate floating size compat mode bounds (same aspect ratio as display without insets).
+ task.appCompatTaskInfo.topActivityAppBounds.set(
+ 0,
+ 0,
+ DISPLAY_DIMENSION_LONG / 2,
+ DISPLAY_DIMENSION_SHORT / 2,
+ )
+ val originalAspectRatio = 1.5f
+ task.appCompatTaskInfo.topNonResizableActivityAspectRatio = originalAspectRatio
+
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertNotNull(finalBounds, "finalBounds should be resolved")
+ val finalAspectRatio =
+ maxOf(finalBounds.height(), finalBounds.width()) /
+ minOf(finalBounds.height(), finalBounds.width()).toFloat()
+ assertThat(finalAspectRatio).isWithin(FLOAT_TOLERANCE).of(originalAspectRatio)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToDesktop_displayNotSupported_withOverButtonOrAdb_movesToDesk() {
+ val spyController = spy(controller)
+ desktopState.overrideDesktopModeSupportPerDisplay[DEFAULT_DISPLAY] = false
+ val task = setUpFullscreenTask()
+ spyController.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = ADB_COMMAND)
+ verify(spyController, times(1))
+ .moveTaskToDesk(anyInt(), anyInt(), any(), eq(ADB_COMMAND), eq(null), eq(null))
+
+ clearInvocations(desksOrganizer)
+
+ spyController.moveTaskToDefaultDeskAndActivate(
+ task.taskId,
+ transitionSource = APP_FROM_OVERVIEW,
+ )
+ verify(spyController, times(1))
+ .moveTaskToDesk(anyInt(), anyInt(), any(), eq(APP_FROM_OVERVIEW), eq(null), eq(null))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToDesktop_displayNotSupported_doesNothing() {
+ val spyController = spy(controller)
+ desktopState.overrideDesktopModeSupportPerDisplay[DEFAULT_DISPLAY] = false
+ val task = setUpFullscreenTask()
+ spyController.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
+ verify(spyController, times(0))
+ .moveTaskToDesk(anyInt(), anyInt(), any(), eq(UNKNOWN), eq(null), eq(null))
+
+ spyController.moveTaskToDefaultDeskAndActivate(
+ task.taskId,
+ transitionSource = KEYBOARD_SHORTCUT,
+ )
+ verify(spyController, times(0))
+ .moveTaskToDesk(anyInt(), anyInt(), any(), eq(KEYBOARD_SHORTCUT), eq(null), eq(null))
+
+ spyController.moveTaskToDefaultDeskAndActivate(
+ task.taskId,
+ transitionSource = APP_HANDLE_MENU_BUTTON,
+ )
+ verify(spyController, times(0))
+ .moveTaskToDesk(
+ anyInt(),
+ anyInt(),
+ any(),
+ eq(APP_HANDLE_MENU_BUTTON),
+ eq(null),
+ eq(null),
+ )
+
+ spyController.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = TASK_DRAG)
+ verify(spyController, times(0))
+ .moveTaskToDesk(anyInt(), anyInt(), any(), eq(TASK_DRAG), eq(null), eq(null))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() {
+ val task = setUpFullscreenTask()
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
+ val wct = getLatestEnterDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(TO_DESKTOP_ANIM_DURATION)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveRunningTaskToDesktop_tdaFreeform_windowingModeSetToUndefined() {
+ val task = setUpFullscreenTask()
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
+ val wct = getLatestEnterDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(TO_DESKTOP_ANIM_DURATION)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveRunningTaskToDesktop_movesTaskToDefaultDesk() =
+ testScope.runTest {
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
+ runCurrent()
+
+ val wct = getLatestEnterDesktopWct()
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId = 0, task)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveRunningTaskToDesktop_activatesDesk() =
+ testScope.runTest {
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
+ runCurrent()
+
+ val wct = getLatestEnterDesktopWct()
+ verify(desksOrganizer).activateDesk(wct, deskId = 0)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveRunningTaskToDesktop_triggersEnterDesktopListener() =
+ testScope.runTest {
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
+ runCurrent()
+
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(TO_DESKTOP_ANIM_DURATION)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveTaskToDesk_nonDefaultDesk_movesTaskToDesk() {
+ val transition = Binder()
+ whenever(enterDesktopTransitionHandler.moveToDesktop(any(), any())).thenReturn(transition)
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 3)
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+ task.isVisible = true
+
+ controller.moveTaskToDesk(taskId = task.taskId, deskId = 3, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId = 3, task)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveTaskToDesk_nonDefaultDesk_activatesDesk() {
+ val transition = Binder()
+ whenever(enterDesktopTransitionHandler.moveToDesktop(any(), any())).thenReturn(transition)
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 3)
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+ task.isVisible = true
+
+ controller.moveTaskToDesk(taskId = task.taskId, deskId = 3, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ verify(desksOrganizer).activateDesk(wct, deskId = 3)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveTaskToDesk_nonDefaultDesk_triggersEnterDesktopListener() {
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 3)
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+
+ controller.moveTaskToDesk(taskId = task.taskId, deskId = 3, transitionSource = UNKNOWN)
+
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(TO_DESKTOP_ANIM_DURATION)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun moveTaskToDesktop_desktopWallpaperDisabled_nonRunningTask_launchesInFreeform() =
+ testScope.runTest {
+ val task = createRecentTaskInfo(1)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
+ whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
+
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
+ runCurrent()
+
+ with(getLatestEnterDesktopWct()) {
+ assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun moveTaskToDesktop_desktopWallpaperEnabled_nonRunningTask_launchesInFreeform() =
+ testScope.runTest {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+ val task = createRecentTaskInfo(1)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
+ whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
+
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
+ runCurrent()
+
+ with(getLatestEnterDesktopWct()) {
+ // Add desktop wallpaper activity
+ assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ // Launch task
+ assertLaunchTaskAt(index = 1, task.taskId, WINDOWING_MODE_FREEFORM)
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun moveBackgroundTaskToDesktop_remoteTransition_usesOneShotHandler() =
+ testScope.runTest {
+ val transitionHandlerArgCaptor = argumentCaptor()
+ whenever(
+ transitions.startTransition(
+ anyInt(),
+ any(),
+ transitionHandlerArgCaptor.capture(),
+ )
+ )
+ .thenReturn(Binder())
+
+ val task = createRecentTaskInfo(1)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
+ whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
+ controller.moveTaskToDefaultDeskAndActivate(
+ taskId = task.taskId,
+ transitionSource = UNKNOWN,
+ remoteTransition = RemoteTransition(spy(TestRemoteTransition())),
+ )
+ runCurrent()
+
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(TO_DESKTOP_ANIM_DURATION)
+ assertIs(transitionHandlerArgCaptor.firstValue)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ )
+ fun moveBackgroundTaskToDesktop_nonDefaultDisplay_reordersHomeAndWallpaperOfNonDefaultDisplay() =
+ testScope.runTest {
+ val homeTask = setUpHomeTask(displayId = SECOND_DISPLAY)
+ val wallpaperToken = MockToken().token()
+ whenever(desktopWallpaperActivityTokenProvider.getToken(SECOND_DISPLAY))
+ .thenReturn(wallpaperToken)
+ taskRepository.addDesk(SECOND_DISPLAY, deskId = 2)
+ val task = setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = 2, background = true)
+
+ controller.moveTaskToDefaultDeskAndActivate(
+ taskId = task.taskId,
+ transitionSource = UNKNOWN,
+ remoteTransition = RemoteTransition(spy(TestRemoteTransition())),
+ )
+ runCurrent()
+
+ val wct = getLatestTransition()
+ val homeReorderIndex = wct.indexOfReorder(homeTask, toTop = true)
+ val wallpaperReorderIndex = wct.indexOfReorder(wallpaperToken, toTop = true)
+ assertThat(homeReorderIndex).isNotEqualTo(-1)
+ assertThat(wallpaperReorderIndex).isNotEqualTo(-1)
+ // Wallpaper last, to be in front of Home.
+ assertThat(wallpaperReorderIndex).isGreaterThan(homeReorderIndex)
+ }
+
+ @Test
+ fun moveBackgroundTaskToDesktop_invalidDisplay_invalidFocusedDisplay_reordersHomeAndWallpaperInDefaultDisplay() =
+ testScope.runTest {
+ val task = createRecentTaskInfo(1, INVALID_DISPLAY)
+ val homeTask = setUpHomeTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+ whenever(focusTransitionObserver.globallyFocusedDisplayId).thenReturn(INVALID_DISPLAY)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
+ whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
+ whenever(desktopWallpaperActivityTokenProvider.getToken(DEFAULT_DISPLAY))
+ .thenReturn(wallpaperToken)
+
+ controller.moveTaskToDefaultDeskAndActivate(
+ taskId = task.taskId,
+ transitionSource = UNKNOWN,
+ remoteTransition = RemoteTransition(TestRemoteTransition()),
+ )
+ runCurrent()
+
+ val wct = getLatestTransition()
+ wct.assertReorder(homeTask)
+ wct.assertReorder(wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY)
+ fun moveBackgroundTaskToDesktop_invalidDisplay_validFocusedDisplay_reordersHomeAndWallpaperInFocusedDisplay() =
+ testScope.runTest {
+ val task = createRecentTaskInfo(1, INVALID_DISPLAY)
+ val focusedDisplayId = 5
+ val homeTask = setUpHomeTask(displayId = focusedDisplayId)
+ val wallpaperToken = MockToken().token()
+ taskRepository.addDesk(displayId = focusedDisplayId, deskId = 5)
+ whenever(focusTransitionObserver.globallyFocusedDisplayId).thenReturn(focusedDisplayId)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
+ whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
+ whenever(desktopWallpaperActivityTokenProvider.getToken(focusedDisplayId))
+ .thenReturn(wallpaperToken)
+
+ controller.moveTaskToDefaultDeskAndActivate(
+ taskId = task.taskId,
+ transitionSource = UNKNOWN,
+ remoteTransition = RemoteTransition(TestRemoteTransition()),
+ )
+ runCurrent()
+
+ val wct = getLatestTransition()
+ wct.assertReorder(homeTask)
+ wct.assertReorder(wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveBackgroundTaskToDesktop_invalidDisplay_invalidFocusedDisplay_activatesDeskInDefaultDisplay() =
+ testScope.runTest {
+ val task = createRecentTaskInfo(1, INVALID_DISPLAY)
+ val deskId = 2
+ whenever(focusTransitionObserver.globallyFocusedDisplayId).thenReturn(INVALID_DISPLAY)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
+ whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+
+ controller.moveTaskToDefaultDeskAndActivate(
+ taskId = task.taskId,
+ transitionSource = UNKNOWN,
+ remoteTransition = RemoteTransition(TestRemoteTransition()),
+ )
+ runCurrent()
+
+ val wct = getLatestTransition()
+ verify(desksOrganizer).activateDesk(wct, deskId = deskId)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveBackgroundTaskToDesktop_invalidDisplay_validFocusedDisplay_activatesDeskInFocusedDisplay() =
+ testScope.runTest {
+ val task = createRecentTaskInfo(1, INVALID_DISPLAY)
+ val focusedDisplayId = 5
+ val deskId = 2
+ whenever(focusTransitionObserver.globallyFocusedDisplayId).thenReturn(focusedDisplayId)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
+ whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
+ taskRepository.addDesk(displayId = focusedDisplayId, deskId = deskId)
+
+ controller.moveTaskToDefaultDeskAndActivate(
+ taskId = task.taskId,
+ transitionSource = UNKNOWN,
+ remoteTransition = RemoteTransition(TestRemoteTransition()),
+ )
+ runCurrent()
+
+ val wct = getLatestTransition()
+ verify(desksOrganizer).activateDesk(wct, deskId = deskId)
+ }
+
+ @Test
+ fun moveRunningTaskToDesktop_remoteTransition_usesOneShotHandler() =
+ testScope.runTest {
+ val transitionHandlerArgCaptor = argumentCaptor()
+ whenever(
+ transitions.startTransition(
+ anyInt(),
+ any(),
+ transitionHandlerArgCaptor.capture(),
+ )
+ )
+ .thenReturn(Binder())
+
+ controller.moveTaskToDefaultDeskAndActivate(
+ taskId = setUpFullscreenTask().taskId,
+ transitionSource = UNKNOWN,
+ remoteTransition = RemoteTransition(spy(TestRemoteTransition())),
+ )
+ runCurrent()
+
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(TO_DESKTOP_ANIM_DURATION)
+ assertIs(transitionHandlerArgCaptor.firstValue)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun moveRunningTaskToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperDisabled() {
+ val homeTask = setUpHomeTask()
+ val freeformTask = setUpFreeformTask()
+ val fullscreenTask = setUpFullscreenTask()
+ markTaskHidden(freeformTask)
+
+ controller.moveTaskToDefaultDeskAndActivate(
+ fullscreenTask.taskId,
+ transitionSource = UNKNOWN,
+ )
+
+ with(getLatestEnterDesktopWct()) {
+ // Operations should include home task, freeform task
+ assertThat(hierarchyOps).hasSize(3)
+ assertReorderSequence(homeTask, freeformTask, fullscreenTask)
+ assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(TO_DESKTOP_ANIM_DURATION)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveRunningTaskToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperEnabled() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+ val freeformTask = setUpFreeformTask()
+ val fullscreenTask = setUpFullscreenTask()
+ markTaskHidden(freeformTask)
+
+ controller.moveTaskToDefaultDeskAndActivate(
+ fullscreenTask.taskId,
+ transitionSource = UNKNOWN,
+ )
+
+ with(getLatestEnterDesktopWct()) {
+ // Operations should include wallpaper intent, freeform task, fullscreen task
+ assertThat(hierarchyOps).hasSize(3)
+ assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ assertReorderAt(index = 1, freeformTask)
+ assertReorderAt(index = 2, fullscreenTask)
+ assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(TO_DESKTOP_ANIM_DURATION)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ )
+ fun moveRunningTaskToDesktop_desktopWallpaperEnabled_multiDesksEnabled() =
+ testScope.runTest {
+ val freeformTask = setUpFreeformTask()
+ val fullscreenTask = setUpFullscreenTask()
+ markTaskHidden(freeformTask)
+
+ controller.moveTaskToDefaultDeskAndActivate(
+ fullscreenTask.taskId,
+ transitionSource = UNKNOWN,
+ )
+ runCurrent()
+
+ val wct = getLatestEnterDesktopWct()
+ wct.assertReorderAt(index = 0, wallpaperToken)
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId = 0, fullscreenTask)
+ verify(desksOrganizer).activateDesk(wct, deskId = 0)
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(TO_DESKTOP_ANIM_DURATION)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveRunningTaskToDesktop_activatesDesk_desktopWallpaperEnabled_multiDesksDisabled() {
+ val fullscreenTask = setUpFullscreenTask()
+
+ controller.moveTaskToDefaultDeskAndActivate(
+ fullscreenTask.taskId,
+ transitionSource = UNKNOWN,
+ )
+
+ assertThat(taskRepository.getActiveDeskId(DEFAULT_DISPLAY)).isEqualTo(DEFAULT_DISPLAY)
+ }
+
+ @Test
+ fun moveRunningTaskToDesktop_onlyFreeformTasksFromCurrentDisplayBroughtToFront() =
+ testScope.runTest {
+ setUpHomeTask(displayId = DEFAULT_DISPLAY)
+ val freeformTaskDefault = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val fullscreenTaskDefault = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+ markTaskHidden(freeformTaskDefault)
+
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ val homeTaskSecond = setUpHomeTask(displayId = SECOND_DISPLAY)
+ val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY)
+ markTaskHidden(freeformTaskSecond)
+
+ controller.moveTaskToDefaultDeskAndActivate(
+ fullscreenTaskDefault.taskId,
+ transitionSource = UNKNOWN,
+ )
+ runCurrent()
+
+ with(getLatestEnterDesktopWct()) {
+ // Check that hierarchy operations do not include tasks from second display
+ assertThat(hierarchyOps.map { it.container })
+ .doesNotContain(homeTaskSecond.token.asBinder())
+ assertThat(hierarchyOps.map { it.container })
+ .doesNotContain(freeformTaskSecond.token.asBinder())
+ }
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(TO_DESKTOP_ANIM_DURATION)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveRunningTaskToDesktop_splitTaskExitsSplit_multiDesksDisabled() =
+ testScope.runTest {
+ val task = setUpSplitScreenTask()
+
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
+ runCurrent()
+
+ val wct = getLatestEnterDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(TO_DESKTOP_ANIM_DURATION)
+ verify(splitScreenController)
+ .prepareExitSplitScreen(
+ any(),
+ anyInt(),
+ eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE),
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveRunningTaskToDesktop_splitTaskExitsSplit_multiDesksEnabled() =
+ testScope.runTest {
+ val task = setUpSplitScreenTask()
+
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
+ runCurrent()
+
+ val wct = getLatestEnterDesktopWct()
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId = 0, task)
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(TO_DESKTOP_ANIM_DURATION)
+ verify(splitScreenController)
+ .prepareExitSplitScreen(
+ any(),
+ anyInt(),
+ eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE),
+ )
+ }
+
+ @Test
+ fun moveRunningTaskToDesktop_fullscreenTaskDoesNotExitSplit() =
+ testScope.runTest {
+ val task = setUpFullscreenTask()
+
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
+ runCurrent()
+
+ val wct = getLatestEnterDesktopWct()
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(TO_DESKTOP_ANIM_DURATION)
+ verify(splitScreenController, never())
+ .prepareExitSplitScreen(
+ any(),
+ anyInt(),
+ eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE),
+ )
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION,
+ )
+ fun moveRunningTaskToDesktop_desktopWallpaperDisabled_bringsTasksOver_dontShowBackTask() {
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+ val newTask = setUpFullscreenTask()
+ val homeTask = setUpHomeTask()
+
+ controller.moveTaskToDefaultDeskAndActivate(newTask.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(TO_DESKTOP_ANIM_DURATION)
+ assertThat(wct.hierarchyOps.size).isEqualTo(MAX_TASK_LIMIT + 1) // visible tasks + home
+ wct.assertReorderAt(0, homeTask)
+ wct.assertReorderSequenceInRange(
+ range = 1..<(MAX_TASK_LIMIT + 1),
+ *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
+ newTask,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION,
+ )
+ fun moveRunningTaskToDesktop_desktopWallpaperEnabled_bringsTasksOverLimit_dontShowBackTask() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+ val newTask = setUpFullscreenTask()
+ val homeTask = setUpHomeTask()
+
+ controller.moveTaskToDefaultDeskAndActivate(newTask.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(TO_DESKTOP_ANIM_DURATION)
+ assertThat(wct.hierarchyOps.size).isEqualTo(MAX_TASK_LIMIT + 2) // tasks + home + wallpaper
+ // Move home to front
+ wct.assertReorderAt(0, homeTask)
+ // Add desktop wallpaper activity
+ wct.assertPendingIntentAt(1, desktopWallpaperIntent)
+ // Bring freeform tasks to front
+ wct.assertReorderSequenceInRange(
+ range = 2..<(MAX_TASK_LIMIT + 2),
+ *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
+ newTask,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToFullscreen_fromDesk_reparentsToTaskDisplayArea() {
+ val task = setUpFreeformTask()
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ wct.assertHop(ReparentPredicate(token = task.token, parentToken = tda.token, toTop = true))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToFullscreen_fromDesk_deactivatesDesk() {
+ val task = setUpFreeformTask()
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ verify(desksOrganizer).deactivateDesk(wct, deskId = 0)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToFullscreen_fromDeskWithMultipleTasks_deactivatesDesk() {
+ val deskId = 1
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+
+ controller.moveToFullscreen(task1.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ verify(desksOrganizer).deactivateDesk(wct, deskId = deskId)
+ }
+
+ @Test
+ fun moveToFullscreen_tdaFullscreen_windowingModeSetToUndefined() {
+ val task = setUpFreeformTask()
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+ val wct = getLatestExitDesktopWct()
+ verify(desktopModeEnterExitTransitionListener, times(1))
+ .onExitDesktopModeTransitionStarted(
+ FULLSCREEN_ANIMATION_DURATION,
+ shouldEndUpAtHome = false,
+ )
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ com.android.launcher3.Flags.FLAG_ENABLE_ALT_TAB_KQS_FLATENNING,
+ )
+ fun moveToFullscreen_tdaFullscreen_windowingModeUndefined_removesWallpaperActivity() {
+ desktopState.enterDesktopByDefaultOnFreeformDisplay = true
+ val homeTask = setUpHomeTask()
+ val task = setUpFreeformTask()
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration
+ .windowConfiguration
+ .windowingMode = WINDOWING_MODE_FULLSCREEN
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task.token.asBinder()])
+ verify(desktopModeEnterExitTransitionListener)
+ .onExitDesktopModeTransitionStarted(
+ FULLSCREEN_ANIMATION_DURATION,
+ shouldEndUpAtHome = false,
+ )
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Removes wallpaper activity when leaving desktop
+ wct.assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ // Moves home task behind the fullscreen task
+ wct.assertReorderAt(index = 1, homeTask.getToken(), toTop = true)
+ wct.assertReorderAt(index = 2, task.getToken(), toTop = true)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun moveToFullscreen_tdaFullscreen_windowingModeUndefined_removesWallpaperActivity_multiDesksEnabled() {
+ desktopState.enterDesktopByDefaultOnFreeformDisplay = true
+ setUpHomeTask()
+ val task = setUpFreeformTask()
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration
+ .windowConfiguration
+ .windowingMode = WINDOWING_MODE_FULLSCREEN
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task.token.asBinder()])
+ verify(desktopModeEnterExitTransitionListener)
+ .onExitDesktopModeTransitionStarted(
+ FULLSCREEN_ANIMATION_DURATION,
+ shouldEndUpAtHome = false,
+ )
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED)
+ // Removes wallpaper activity when leaving desktop
+ wct.assertReorder(wallpaperToken, toTop = false)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun moveToFullscreen_tdaFullscreen_windowingModeUndefined_homeBehindFullscreen_multiDesksEnabled() {
+ desktopState.enterDesktopByDefaultOnFreeformDisplay = true
+ val homeTask = setUpHomeTask()
+ val task = setUpFreeformTask()
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration
+ .windowConfiguration
+ .windowingMode = WINDOWING_MODE_FULLSCREEN
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task.token.asBinder()])
+ verify(desktopModeEnterExitTransitionListener)
+ .onExitDesktopModeTransitionStarted(
+ FULLSCREEN_ANIMATION_DURATION,
+ shouldEndUpAtHome = false,
+ )
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED)
+ // Moves home task behind the fullscreen task
+ val homeReorderIndex = wct.indexOfReorder(homeTask, toTop = true)
+ val fullscreenReorderIndex = wct.indexOfReorder(task, toTop = true)
+ assertThat(homeReorderIndex).isNotEqualTo(-1)
+ assertThat(fullscreenReorderIndex).isNotEqualTo(-1)
+ assertThat(fullscreenReorderIndex).isGreaterThan(homeReorderIndex)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
+ fun moveToFullscreen_tdaFreeform_enforcedDesktop_doesNotReorderHome() {
+ desktopState.enterDesktopByDefaultOnFreeformDisplay = true
+ val homeTask = setUpHomeTask()
+ val task = setUpFreeformTask()
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration
+ .windowConfiguration
+ .windowingMode = WINDOWING_MODE_FREEFORM
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ verify(desktopModeEnterExitTransitionListener)
+ .onExitDesktopModeTransitionStarted(
+ FULLSCREEN_ANIMATION_DURATION,
+ shouldEndUpAtHome = false,
+ )
+ // Removes wallpaper activity when leaving desktop but doesn't reorder home or the task
+ wct.assertReorder(wallpaperToken, toTop = false)
+ wct.assertWithoutHop(ReorderPredicate(homeTask.token, toTop = null))
+ }
+
+ @Test
+ fun moveToFullscreen_tdaFreeform_windowingModeSetToFullscreen() {
+ val task = setUpFreeformTask()
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+ val wct = getLatestExitDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ verify(desktopModeEnterExitTransitionListener)
+ .onExitDesktopModeTransitionStarted(
+ FULLSCREEN_ANIMATION_DURATION,
+ shouldEndUpAtHome = false,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ com.android.launcher3.Flags.FLAG_ENABLE_ALT_TAB_KQS_FLATENNING,
+ Flags.FLAG_ENABLE_DESKTOP_FIRST_BASED_DEFAULT_TO_DESKTOP_BUGFIX,
+ )
+ fun moveToFullscreen_tdaFreeform_windowingModeFullscreen_removesWallpaperActivity() {
+ desktopState.enterDesktopByDefaultOnFreeformDisplay = false
+ val homeTask = setUpHomeTask()
+ val task = setUpFreeformTask()
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration
+ .windowConfiguration
+ .windowingMode = WINDOWING_MODE_FREEFORM
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task.token.asBinder()])
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ verify(desktopModeEnterExitTransitionListener)
+ .onExitDesktopModeTransitionStarted(
+ FULLSCREEN_ANIMATION_DURATION,
+ shouldEndUpAtHome = false,
+ )
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Removes wallpaper activity when leaving desktop
+ wct.assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ // Moves home task behind the fullscreen task
+ wct.assertReorderAt(index = 1, homeTask.getToken(), toTop = true)
+ wct.assertReorderAt(index = 2, task.getToken(), toTop = true)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun moveToFullscreen_tdaFreeform_windowingModeFullscreen_removesWallpaperActivity_multiDesksEnabled() {
+ val homeTask = setUpHomeTask()
+ val task = setUpFreeformTask()
+
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration
+ .windowConfiguration
+ .windowingMode = WINDOWING_MODE_FREEFORM
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task.token.asBinder()])
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ verify(desktopModeEnterExitTransitionListener)
+ .onExitDesktopModeTransitionStarted(
+ FULLSCREEN_ANIMATION_DURATION,
+ shouldEndUpAtHome = false,
+ )
+ // Removes wallpaper activity when leaving desktop
+ wct.assertReorder(wallpaperToken, toTop = false)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_FIRST_BASED_DEFAULT_TO_DESKTOP_BUGFIX)
+ fun moveToFullscreen_tdaFreeform_windowingModeFullscreen_homeBehindFullscreen_multiDesksEnabled() {
+ desktopState.enterDesktopByDefaultOnFreeformDisplay = false
+ val homeTask = setUpHomeTask()
+ val task = setUpFreeformTask()
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration
+ .windowConfiguration
+ .windowingMode = WINDOWING_MODE_FREEFORM
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task.token.asBinder()])
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ verify(desktopModeEnterExitTransitionListener)
+ .onExitDesktopModeTransitionStarted(
+ FULLSCREEN_ANIMATION_DURATION,
+ shouldEndUpAtHome = false,
+ )
+ // Moves home task behind the fullscreen task
+ val homeReorderIndex = wct.indexOfReorder(homeTask, toTop = true)
+ val fullscreenReorderIndex = wct.indexOfReorder(task, toTop = true)
+ assertThat(homeReorderIndex).isNotEqualTo(-1)
+ assertThat(fullscreenReorderIndex).isNotEqualTo(-1)
+ assertThat(fullscreenReorderIndex).isGreaterThan(homeReorderIndex)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
+ com.android.launcher3.Flags.FLAG_ENABLE_ALT_TAB_KQS_FLATENNING,
+ )
+ fun moveToFullscreen_multipleVisibleNonMinimizedTasks_doesNotRemoveWallpaperActivity() {
+ val homeTask = setUpHomeTask()
+ val task1 = setUpFreeformTask()
+ // Setup task2
+ setUpFreeformTask()
+
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration
+ .windowConfiguration
+ .windowingMode = WINDOWING_MODE_FULLSCREEN
+
+ controller.moveToFullscreen(task1.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val task1Change = assertNotNull(wct.changes[task1.token.asBinder()])
+ assertThat(task1Change.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED)
+ verify(desktopModeEnterExitTransitionListener)
+ .onExitDesktopModeTransitionStarted(
+ FULLSCREEN_ANIMATION_DURATION,
+ shouldEndUpAtHome = false,
+ )
+ // Does not remove wallpaper activity, as desktop still has a visible desktop task
+ assertThat(wct.hierarchyOps).hasSize(2)
+ // Moves home task behind the fullscreen task
+ wct.assertReorderAt(index = 0, homeTask.getToken(), toTop = true)
+ wct.assertReorderAt(index = 1, task1.getToken(), toTop = true)
+ }
+
+ @Test
+ fun moveToFullscreen_nonExistentTask_doesNothing() {
+ controller.moveToFullscreen(999, transitionSource = UNKNOWN)
+ verifyExitDesktopWCTNotExecuted()
+ }
+
+ @Test
+ fun moveToFullscreen_secondDisplayTaskHasFreeform_secondDisplayNotAffected() {
+ val taskDefaultDisplay = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ val taskSecondDisplay = setUpFreeformTask(displayId = SECOND_DISPLAY)
+ controller.moveToFullscreen(taskDefaultDisplay.taskId, transitionSource = UNKNOWN)
+
+ with(getLatestExitDesktopWct()) {
+ assertThat(changes.keys).contains(taskDefaultDisplay.token.asBinder())
+ assertThat(changes.keys).doesNotContain(taskSecondDisplay.token.asBinder())
+ }
+ verify(desktopModeEnterExitTransitionListener)
+ .onExitDesktopModeTransitionStarted(
+ FULLSCREEN_ANIMATION_DURATION,
+ shouldEndUpAtHome = false,
+ )
+ }
+
+ @Test
+ @EnableFlags(com.android.launcher3.Flags.FLAG_ENABLE_ALT_TAB_KQS_FLATENNING)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToFullscreen_enforceDesktopWithMultipleDesktopDisabled_taskReorderToTop() {
+ desktopState.enterDesktopByDefaultOnFreeformDisplay = true
+ val transitionHandlerArgCaptor = argumentCaptor()
+ whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
+ .thenReturn(Binder())
+
+ val task = setUpFullscreenTask()
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration
+ .windowConfiguration
+ .windowingMode = WINDOWING_MODE_FREEFORM
+
+ controller.moveToFullscreen(
+ task.taskId,
+ transitionSource = UNKNOWN,
+ remoteTransition = RemoteTransition(spy(TestRemoteTransition())),
+ )
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ wct.assertReorderAt(index = 0, task)
+ verify(desktopModeEnterExitTransitionListener, never())
+ .onEnterDesktopModeTransitionStarted(anyInt())
+ assertIs(transitionHandlerArgCaptor.firstValue)
+ }
+
+ @Test
+ @EnableFlags(com.android.launcher3.Flags.FLAG_ENABLE_ALT_TAB_KQS_FLATENNING)
+ fun moveToFullscreen_fullscreenTaskWithRemoteTransition_transitToFrontUsesRemoteTransition() {
+ val transitionHandlerArgCaptor = argumentCaptor()
+ whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
+ .thenReturn(Binder())
+
+ val task = setUpFullscreenTask()
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration
+ .windowConfiguration
+ .windowingMode = WINDOWING_MODE_FREEFORM
+
+ controller.moveToFullscreen(
+ task.taskId,
+ transitionSource = UNKNOWN,
+ remoteTransition = RemoteTransition(spy(TestRemoteTransition())),
+ )
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ verify(desktopModeEnterExitTransitionListener, never())
+ .onEnterDesktopModeTransitionStarted(anyInt())
+ assertIs(transitionHandlerArgCaptor.firstValue)
+ }
+
+ @Test
+ @EnableFlags(com.android.launcher3.Flags.FLAG_ENABLE_ALT_TAB_KQS_FLATENNING)
+ fun moveToFullscreen_backgroundFullscreenTask_launchesFullscreenTask() {
+ val task = createRecentTaskInfo(1, INVALID_DISPLAY)
+ whenever(recentTasksController.findTaskInBackground(task.taskId)).thenReturn(task)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
+
+ whenever(focusTransitionObserver.globallyFocusedDisplayId).thenReturn(SECOND_DISPLAY)
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+
+ val transitionHandlerArgCaptor = argumentCaptor()
+ whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
+ .thenReturn(Binder())
+
+ controller.moveToFullscreen(
+ task.taskId,
+ transitionSource = UNKNOWN,
+ remoteTransition = RemoteTransition(spy(TestRemoteTransition())),
+ )
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ wct.assertLaunchTask(task.taskId, WINDOWING_MODE_FULLSCREEN)
+ verify(desktopModeEnterExitTransitionListener, never())
+ .onEnterDesktopModeTransitionStarted(anyInt())
+ assertIs(transitionHandlerArgCaptor.firstValue)
+ }
+
+ @Test
+ @DisableFlags(com.android.launcher3.Flags.FLAG_ENABLE_ALT_TAB_KQS_FLATENNING)
+ fun moveToFullscreen_backgroundFullscreenTask_ignoredWhenFlagOff() {
+ val task = createRecentTaskInfo(1, INVALID_DISPLAY)
+ whenever(recentTasksController.findTaskInBackground(task.taskId)).thenReturn(task)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ verify(snapEventHandler, never()).removeTaskIfTiled(anyInt(), anyInt())
+ verify(transitions, never()).startTransition(anyInt(), any(), any())
+ verify(desktopModeEnterExitTransitionListener, never())
+ .onEnterDesktopModeTransitionStarted(anyInt())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveTaskToFront_postsWctWithReorderOp() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_TO_FRONT),
+ any(),
+ eq(task1.taskId),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+
+ controller.moveTaskToFront(task1, remoteTransition = null)
+
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT)
+ assertThat(wct.hierarchyOps).hasSize(1)
+ wct.assertReorderAt(index = 0, task1)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveTaskToFront_desktopTask_reordersToFront() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_TO_FRONT),
+ any(),
+ eq(task1.taskId),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+
+ controller.moveTaskToFront(task1, remoteTransition = null)
+
+ verify(desksOrganizer).reorderTaskToFront(any(), eq(0), eq(task1))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveTaskToFront_nonDesktopTask_reordersToFront() {
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_TO_FRONT),
+ any(),
+ eq(task.taskId),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+
+ controller.moveTaskToFront(task, remoteTransition = null)
+
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT)
+ assertNotNull(wct)
+ wct.assertReorder(task = task, toTop = true, includingParents = true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveTaskToFront_nonDesktopTask_doesNotActivateDesk() {
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_TO_FRONT),
+ any(),
+ eq(task.taskId),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+
+ controller.moveTaskToFront(task, remoteTransition = null)
+
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT)
+ assertNotNull(wct)
+ verify(desksOrganizer, never()).activateDesk(eq(wct), any(), any())
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION,
+ )
+ fun moveTaskToFront_bringsTasksOverLimit_multiDesksDisabled_minimizesBackTask() {
+ setUpHomeTask()
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT + 1).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+ }
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_TO_FRONT),
+ any(),
+ eq(freeformTasks[0].taskId),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+
+ controller.moveTaskToFront(freeformTasks[0], remoteTransition = null)
+
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT)
+ assertThat(wct.hierarchyOps.size).isEqualTo(2) // move-to-front + minimize
+ wct.assertReorderAt(0, freeformTasks[0], toTop = true)
+ wct.assertReorderAt(1, freeformTasks[1], toTop = false)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
+ fun moveTaskToFront_bringsTasksOverLimit_multiDesksEnabled_minimizesBackTask() {
+ val deskId = 0
+ setUpHomeTask()
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT + 1).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ }
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_TO_FRONT),
+ any(),
+ eq(freeformTasks[0].taskId),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+
+ controller.moveTaskToFront(freeformTasks[0], remoteTransition = null)
+
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT)
+ verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[1])
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
+ fun moveTaskToFront_bringsTasksOverLimit_separateTaskLimitTransition_minimizeSeparately() {
+ val deskId = 0
+ setUpHomeTask()
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT + 1).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ }
+ val transition = Binder()
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_TO_FRONT),
+ any(),
+ eq(freeformTasks[0].taskId),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(transition)
+
+ controller.moveTaskToFront(freeformTasks[0], remoteTransition = null)
+
+ verify(desksOrganizer, never()).minimizeTask(any(), any(), any())
+ assertThat(desktopTasksLimiter.getMinimizingTask(transition)).isNull()
+ assertThat(desktopTasksLimiter.hasTaskLimitTransitionForTesting(transition)).isTrue()
+ }
+
+ @Test
+ fun moveTaskToFront_minimizedTask_marksTaskAsUnminimized() {
+ val transition = Binder()
+ val freeformTask = setUpFreeformTask()
+ taskRepository.minimizeTask(DEFAULT_DISPLAY, freeformTask.taskId)
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_TO_FRONT),
+ any(),
+ eq(freeformTask.taskId),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(transition)
+
+ controller.moveTaskToFront(freeformTask, unminimizeReason = UnminimizeReason.ALT_TAB)
+
+ val task = desktopTasksLimiter.getUnminimizingTask(transition)
+ assertThat(task).isNotNull()
+ assertThat(task?.taskId).isEqualTo(freeformTask.taskId)
+ assertThat(task?.unminimizeReason).isEqualTo(UnminimizeReason.ALT_TAB)
+ }
+
+ @Test
+ fun handleRequest_minimizedFreeformTask_marksTaskAsUnminimized() {
+ val transition = Binder()
+ // Create a visible task so we stay in Desktop Mode when minimizing task under test.
+ setUpFreeformTask().also { markTaskVisible(it) }
+ val freeformTask = setUpFreeformTask()
+ taskRepository.minimizeTask(DEFAULT_DISPLAY, freeformTask.taskId)
+
+ controller.handleRequest(transition, createTransition(freeformTask, TRANSIT_OPEN))
+
+ val task = desktopTasksLimiter.getUnminimizingTask(transition)
+ assertThat(task).isNotNull()
+ assertThat(task?.taskId).isEqualTo(freeformTask.taskId)
+ assertThat(task?.unminimizeReason).isEqualTo(UnminimizeReason.TASK_LAUNCH)
+ }
+
+ @Test
+ fun moveTaskToFront_remoteTransition_usesOneshotHandler() {
+ setUpHomeTask()
+ val freeformTasks = List(MAX_TASK_LIMIT) { setUpFreeformTask() }
+ val transitionHandlerArgCaptor = argumentCaptor()
+ whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
+ .thenReturn(Binder())
+
+ controller.moveTaskToFront(freeformTasks[0], RemoteTransition(TestRemoteTransition()))
+
+ assertIs(transitionHandlerArgCaptor.firstValue)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
+ fun moveTaskToFront_bringsTasksOverLimit_remoteTransition_usesWindowLimitHandler() {
+ setUpHomeTask()
+ val freeformTasks = List(MAX_TASK_LIMIT + 1) { setUpFreeformTask() }
+ val transitionHandlerArgCaptor = argumentCaptor()
+ whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
+ .thenReturn(Binder())
+
+ controller.moveTaskToFront(freeformTasks[0], RemoteTransition(TestRemoteTransition()))
+
+ assertThat(transitionHandlerArgCaptor.firstValue)
+ .isInstanceOf(DesktopWindowLimitRemoteHandler::class.java)
+ }
+
+ @Test
+ fun moveTaskToFront_backgroundTask_launchesTask() {
+ val task = createRecentTaskInfo(1)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_OPEN),
+ any(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+
+ controller.moveTaskToFront(task.taskId, unminimizeReason = UnminimizeReason.UNKNOWN)
+
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
+ wct.assertLaunchTask(task.taskId, WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY,
+ )
+ fun moveTaskToFront_backgroundTask_launchesTask_launchesToExistingDisplay() {
+ val deskId = 2
+ val taskId = 1
+ val task = createRecentTaskInfo(taskId, displayId = SECOND_DISPLAY)
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = deskId)
+ taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = deskId)
+ taskRepository.addTaskToDesk(
+ displayId = SECOND_DISPLAY,
+ deskId = deskId,
+ taskId = task.taskId,
+ isVisible = true,
+ taskBounds = TASK_BOUNDS,
+ )
+ whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_OPEN),
+ any(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+
+ controller.moveTaskToFront(task.taskId, unminimizeReason = UnminimizeReason.UNKNOWN)
+
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
+ wct.assertLaunchTaskOnDisplay(SECOND_DISPLAY)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY,
+ )
+ fun moveTaskToFront_backgroundTask_notInDesk_launchesInAssociatedDisplay() {
+ val deskId = 2
+ val taskId = 1
+ val task = createRecentTaskInfo(taskId, displayId = SECOND_DISPLAY)
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = deskId)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(taskId)).thenReturn(null)
+ whenever(recentTasksController.findTaskInBackground(taskId)).thenReturn(task)
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_OPEN),
+ any(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+ desktopState.overrideDesktopModeSupportPerDisplay[SECOND_DISPLAY] = true
+
+ controller.moveTaskToFront(task.taskId, unminimizeReason = UnminimizeReason.UNKNOWN)
+
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
+ wct.assertLaunchTaskOnDisplay(SECOND_DISPLAY)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY,
+ )
+ fun moveTaskToFront_backgroundTask_notInDesk_unsupportedAssociatedDisplay_launchesInFocused() {
+ val focusedDisplayId = 10
+ val deskId = 2
+ val taskId = 1
+ val task = createRecentTaskInfo(taskId, displayId = SECOND_DISPLAY)
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = deskId)
+ taskRepository.addDesk(displayId = focusedDisplayId, deskId = focusedDisplayId)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(taskId)).thenReturn(null)
+ whenever(recentTasksController.findTaskInBackground(taskId)).thenReturn(task)
+ whenever(focusTransitionObserver.globallyFocusedDisplayId).thenReturn(focusedDisplayId)
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_OPEN),
+ any(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+ desktopState.overrideDesktopModeSupportPerDisplay[SECOND_DISPLAY] = false
+ desktopState.overrideDesktopModeSupportPerDisplay[focusedDisplayId] = true
+
+ controller.moveTaskToFront(task.taskId, unminimizeReason = UnminimizeReason.UNKNOWN)
+
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
+ wct.assertLaunchTaskOnDisplay(focusedDisplayId)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY,
+ )
+ fun moveTaskToFront_backgroundTask_notInDesk_unsupportedAssociatedAndFocusedDisplay_launchesInSupported() {
+ val supportedDisplayId = 11
+ val focusedDisplayId = 10
+ val deskId = 2
+ val taskId = 1
+ val task = createRecentTaskInfo(taskId, displayId = SECOND_DISPLAY)
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = deskId)
+ taskRepository.addDesk(displayId = focusedDisplayId, deskId = focusedDisplayId)
+ taskRepository.addDesk(displayId = supportedDisplayId, deskId = supportedDisplayId)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(taskId)).thenReturn(null)
+ whenever(recentTasksController.findTaskInBackground(taskId)).thenReturn(task)
+ whenever(focusTransitionObserver.globallyFocusedDisplayId).thenReturn(focusedDisplayId)
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(SECOND_DISPLAY, focusedDisplayId, supportedDisplayId))
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_OPEN),
+ any(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+ desktopState.overrideDesktopModeSupportPerDisplay[SECOND_DISPLAY] = false
+ desktopState.overrideDesktopModeSupportPerDisplay[focusedDisplayId] = false
+ desktopState.overrideDesktopModeSupportPerDisplay[supportedDisplayId] = true
+
+ controller.moveTaskToFront(task.taskId, unminimizeReason = UnminimizeReason.UNKNOWN)
+
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
+ wct.assertLaunchTaskOnDisplay(supportedDisplayId)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION,
+ )
+ fun moveTaskToFront_backgroundTaskBringsTasksOverLimit_multiDesksDisabled_minimizesBackTask() {
+ val deskId = 0
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ }
+ val task = createRecentTaskInfo(1001)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null)
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_OPEN),
+ any(),
+ eq(task.taskId),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+
+ controller.moveTaskToFront(task.taskId, unminimizeReason = UnminimizeReason.UNKNOWN)
+
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
+ assertThat(wct.hierarchyOps.size).isEqualTo(2) // launch + minimize
+ wct.assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
+ wct.assertReorderAt(1, freeformTasks[0], toTop = false)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
+ fun moveTaskToFront_backgroundTaskBringsTasksOverLimit_multiDesksDisabled_separateMinimize() {
+ val deskId = 0
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ }
+ val task = createRecentTaskInfo(1001)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null)
+ val transition = Binder()
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_OPEN),
+ any(),
+ eq(task.taskId),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(transition)
+
+ controller.moveTaskToFront(task.taskId, unminimizeReason = UnminimizeReason.UNKNOWN)
+
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
+ assertThat(wct.hierarchyOps.size).isEqualTo(1)
+ wct.assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
+ assertThat(desktopTasksLimiter.hasTaskLimitTransitionForTesting(transition)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
+ fun moveTaskToFront_backgroundTaskBringsTasksOverLimit_multiDesksEnabled_minimizesBackTask() {
+ val deskId = 0
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ }
+ val task = createRecentTaskInfo(freeformTasks.last().taskId + 10)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null)
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_OPEN),
+ any(),
+ eq(task.taskId),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+
+ controller.moveTaskToFront(task.taskId, unminimizeReason = UnminimizeReason.UNKNOWN)
+
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
+ verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[0])
+ }
+
+ @Test
+ fun moveToNextDisplay_noOtherDisplays() {
+ whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY))
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ controller.moveToNextDisplay(task.taskId)
+ verify(transitions, never()).startTransition(anyInt(), any(), anyOrNull())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToNextDisplay_moveFromFirstToSecondDisplay_multiDesksDisabled() {
+ // Set up two display ids
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ controller.moveToNextDisplay(task.taskId)
+
+ val taskChange =
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ .hierarchyOps
+ .find { it.container == task.token.asBinder() && it.isReparent }
+ assertNotNull(taskChange)
+ assertThat(taskChange.newParent).isEqualTo(secondDisplayArea.token.asBinder())
+ assertThat(taskChange.toTop).isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToNextDisplay_moveFromFirstToSecondDisplay_multiDesksEnabled() {
+ // Set up two display ids
+ val targetDeskId = 2
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ controller.moveToNextDisplay(task.taskId)
+
+ verify(desksOrganizer).moveTaskToDesk(any(), eq(targetDeskId), eq(task), eq(false))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToNextDisplay_moveFromSecondToFirstDisplay_multiDesksDisabled() {
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Create a mock for the target display area: default display
+ val defaultDisplayArea = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .thenReturn(defaultDisplayArea)
+
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ val task = setUpFreeformTask(displayId = SECOND_DISPLAY)
+ controller.moveToNextDisplay(task.taskId)
+
+ val taskChange =
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ .hierarchyOps
+ .find { it.container == task.token.asBinder() && it.isReparent }
+ assertNotNull(taskChange)
+ assertThat(taskChange.newParent).isEqualTo(defaultDisplayArea.token.asBinder())
+ assertThat(taskChange.toTop).isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToNextDisplay_moveFromSecondToFirstDisplay_multiDesksEnabled() {
+ // Set up two display ids
+ val targetDeskId = 0
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Create a mock for the target display area: default display
+ val defaultDisplayArea = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .thenReturn(defaultDisplayArea)
+
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ val task = setUpFreeformTask(displayId = SECOND_DISPLAY)
+ controller.moveToNextDisplay(task.taskId)
+
+ verify(desksOrganizer).moveTaskToDesk(any(), eq(targetDeskId), eq(task), eq(false))
+ }
+
+ @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
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Add a task and a wallpaper
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+
+ controller.moveToNextDisplay(task.taskId)
+
+ with(
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ ) {
+ 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)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
+ fun moveToNextDisplay_wallpaperNotOnSystemUser_removeWallpaper() {
+ // Set up two display ids
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Add a task and a wallpaper
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+
+ controller.moveToNextDisplay(task.taskId)
+
+ val wallpaperChange =
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ .hierarchyOps
+ .find { op -> op.container == wallpaperToken.asBinder() }
+ assertNotNull(wallpaperChange)
+ assertThat(wallpaperChange.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT)
+ fun moveToNextDisplay_sizeInDpPreserved() {
+ // Set up two display ids
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = 2)
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Two displays have different density
+ whenever(displayLayout.densityDpi()).thenReturn(320)
+ whenever(displayLayout.width()).thenReturn(2400)
+ whenever(displayLayout.height()).thenReturn(1600)
+ val secondaryLayout = mock(DisplayLayout::class.java)
+ whenever(displayController.getDisplayLayout(SECOND_DISPLAY)).thenReturn(secondaryLayout)
+ whenever(secondaryLayout.densityDpi()).thenReturn(160)
+ whenever(secondaryLayout.width()).thenReturn(1280)
+ whenever(secondaryLayout.height()).thenReturn(720)
+
+ // Place a task with a size of 640x480 at a position where the ratio of the left margin to
+ // the right margin is 1:3 and the ratio of top margin to the bottom margin is 1:2.
+ val task =
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, bounds = Rect(440, 374, 1080, 854))
+
+ controller.moveToNextDisplay(task.taskId)
+
+ val taskChange =
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ .changes[task.token.asBinder()]
+ assertNotNull(taskChange)
+ // To preserve DP size, pixel size is changed to 320x240. The ratio of the left margin
+ // to the right margin and the ratio of the top margin to bottom margin are also
+ // preserved.
+ assertThat(taskChange.configuration.windowConfiguration.bounds)
+ .isEqualTo(Rect(240, 160, 560, 400))
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT)
+ fun moveToNextDisplay_shiftWithinDestinationDisplayBounds() {
+ // Set up two display ids
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = 2)
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Two displays have different density
+ whenever(displayLayout.densityDpi()).thenReturn(320)
+ whenever(displayLayout.width()).thenReturn(2400)
+ whenever(displayLayout.height()).thenReturn(1600)
+ val secondaryLayout = mock(DisplayLayout::class.java)
+ whenever(displayController.getDisplayLayout(SECOND_DISPLAY)).thenReturn(secondaryLayout)
+ whenever(secondaryLayout.densityDpi()).thenReturn(160)
+ whenever(secondaryLayout.width()).thenReturn(1280)
+ whenever(secondaryLayout.height()).thenReturn(720)
+
+ // Place a task with a size of 640x480 at a position where the bottom-right corner of the
+ // window is outside the source display bounds. The destination display still has enough
+ // space to place the window within its bounds.
+ val task =
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, bounds = Rect(2000, 1200, 2640, 1680))
+
+ controller.moveToNextDisplay(task.taskId)
+
+ val taskChange =
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ .changes[task.token.asBinder()]
+ assertNotNull(taskChange)
+ assertThat(taskChange.configuration.windowConfiguration.bounds)
+ .isEqualTo(Rect(960, 480, 1280, 720))
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT)
+ fun moveToNextDisplay_maximizedTask() {
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = 2)
+ // Two displays have different density
+ whenever(displayLayout.densityDpi()).thenReturn(320)
+ whenever(displayLayout.width()).thenReturn(1280)
+ whenever(displayLayout.height()).thenReturn(960)
+ val secondaryLayout = mock(DisplayLayout::class.java)
+ whenever(displayController.getDisplayLayout(SECOND_DISPLAY)).thenReturn(secondaryLayout)
+ whenever(secondaryLayout.densityDpi()).thenReturn(160)
+ whenever(secondaryLayout.width()).thenReturn(1280)
+ whenever(secondaryLayout.height()).thenReturn(720)
+
+ // Place a task with a size equals to display size.
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, bounds = Rect(0, 0, 1280, 960))
+
+ controller.moveToNextDisplay(task.taskId)
+
+ val taskChange =
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ .changes[task.token.asBinder()]
+ assertNotNull(taskChange)
+ // DP size is preserved. The window is centered in the destination display.
+ assertThat(taskChange.configuration.windowConfiguration.bounds)
+ .isEqualTo(Rect(320, 120, 960, 600))
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT)
+ fun moveToNextDisplay_defaultBoundsWhenDestinationTooSmall() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Two displays have different density
+ whenever(displayLayout.densityDpi()).thenReturn(320)
+ whenever(displayLayout.width()).thenReturn(2400)
+ whenever(displayLayout.height()).thenReturn(1600)
+ val secondaryLayout = mock(DisplayLayout::class.java)
+ whenever(displayController.getDisplayLayout(SECOND_DISPLAY)).thenReturn(secondaryLayout)
+ whenever(secondaryLayout.densityDpi()).thenReturn(160)
+ whenever(secondaryLayout.width()).thenReturn(640)
+ whenever(secondaryLayout.height()).thenReturn(480)
+ whenever(secondaryLayout.getStableBoundsForDesktopMode(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(0, 0, 640, 480)
+ }
+
+ // A task with a size of 1800x1200 is being placed. To preserve DP size,
+ // 900x600 pixels are needed, which does not fit in the destination display.
+ val task =
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, bounds = Rect(300, 200, 2100, 1400))
+
+ controller.moveToNextDisplay(task.taskId)
+
+ val taskChange =
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ .changes[task.token.asBinder()]
+ assertNotNull(taskChange)
+ assertThat(taskChange.configuration.windowConfiguration.bounds.left).isAtLeast(0)
+ assertThat(taskChange.configuration.windowConfiguration.bounds.top).isAtLeast(0)
+ assertThat(taskChange.configuration.windowConfiguration.bounds.right).isAtMost(640)
+ assertThat(taskChange.configuration.windowConfiguration.bounds.bottom).isAtMost(480)
+ }
+
+ @Test
+ @EnableFlags(
+ FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS,
+ FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
+ )
+ fun moveToNextDisplay_destinationGainGlobalFocus() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ controller.moveToNextDisplay(task.taskId)
+
+ val wct =
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ wct.assertReorderAt(
+ // Reorder should be the last change so that other hierarchyOps do not change the
+ // display focus after moving the destination display top.
+ index = wct.hierarchyOps.size - 1,
+ task,
+ toTop = true,
+ includingParents = true,
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToNextDisplay_toDesktopInOtherDisplay_multiDesksDisabled_bringsExistingTasksToFront() {
+ val transition = Binder()
+ val sourceDeskId = 0
+ val targetDeskId = 2
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+ taskRepository.setDeskInactive(deskId = targetDeskId)
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ whenever(transitions.startTransition(eq(TRANSIT_CHANGE), any(), anyOrNull()))
+ .thenReturn(transition)
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = sourceDeskId)
+ val task2 = setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+
+ controller.moveToNextDisplay(task1.taskId)
+
+ // Existing desktop task in the target display is moved to front.
+ val wct = getLatestTransition()
+ wct.assertReorder(task2.token, /* toTop= */ true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToNextDisplay_toDesktopInOtherDisplay_multiDesksEnabled_bringsExistingTasksToFront() {
+ val transition = Binder()
+ val sourceDeskId = 0
+ val targetDeskId = 2
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+ taskRepository.setDeskInactive(deskId = targetDeskId)
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ whenever(transitions.startTransition(eq(TRANSIT_CHANGE), any(), anyOrNull()))
+ .thenReturn(transition)
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = sourceDeskId)
+ val task2 = setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+
+ controller.moveToNextDisplay(task1.taskId)
+
+ // Existing desktop task in the target display is moved to front.
+ val wct = getLatestTransition()
+ assertNotNull(wct)
+ verify(desksOrganizer).reorderTaskToFront(wct, targetDeskId, task2)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
+ fun moveToNextDisplay_toDesktopInOtherDisplay_appliesTaskLimit() {
+ val transition = Binder()
+ val sourceDeskId = 0
+ val targetDeskId = 2
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+ taskRepository.setDeskInactive(deskId = targetDeskId)
+ val targetDeskTasks =
+ (1..MAX_TASK_LIMIT + 1).map { _ ->
+ setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+ }
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ whenever(transitions.startTransition(eq(TRANSIT_CHANGE), any(), anyOrNull()))
+ .thenReturn(transition)
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = sourceDeskId)
+
+ controller.moveToNextDisplay(task.taskId)
+
+ val wct = getLatestTransition()
+ assertNotNull(wct)
+ verify(desksOrganizer).minimizeTask(wct, targetDeskId, targetDeskTasks[0])
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION,
+ )
+ fun moveToNextDisplay_toDesktopInOtherDisplay_appliesTaskLimitSeparate() {
+ val transition = Binder()
+ val sourceDeskId = 0
+ val targetDeskId = 2
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+ taskRepository.setDeskInactive(deskId = targetDeskId)
+ val targetDeskTasks =
+ (1..MAX_TASK_LIMIT + 1).map { _ ->
+ setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+ }
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ whenever(transitions.startTransition(eq(TRANSIT_CHANGE), any(), anyOrNull()))
+ .thenReturn(transition)
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = sourceDeskId)
+
+ controller.moveToNextDisplay(task.taskId)
+
+ val wct = getLatestTransition()
+ assertNotNull(wct)
+ assertThat(desktopTasksLimiter.hasTaskLimitTransitionForTesting(transition)).isTrue()
+ verify(desksOrganizer, never()).minimizeTask(wct, targetDeskId, targetDeskTasks[0])
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ )
+ fun moveToNextDisplay_toDesktopInOtherDisplay_movesHomeAndWallpaperToFront() {
+ val homeTask = setUpHomeTask(displayId = SECOND_DISPLAY)
+ whenever(desktopWallpaperActivityTokenProvider.getToken(SECOND_DISPLAY))
+ .thenReturn(wallpaperToken)
+ val transition = Binder()
+ val sourceDeskId = 0
+ val targetDeskId = 2
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+ taskRepository.setDeskInactive(deskId = targetDeskId)
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ whenever(transitions.startTransition(eq(TRANSIT_CHANGE), any(), anyOrNull()))
+ .thenReturn(transition)
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = sourceDeskId)
+
+ controller.moveToNextDisplay(task1.taskId)
+
+ // Home / Wallpaper should be moved to front as the background of desktop tasks, otherwise
+ // fullscreen (non-desktop) tasks could remain visible.
+ val wct = getLatestTransition()
+ val homeReorderIndex = wct.indexOfReorder(homeTask, toTop = true)
+ val wallpaperReorderIndex = wct.indexOfReorder(wallpaperToken, toTop = true)
+ assertThat(homeReorderIndex).isNotEqualTo(-1)
+ assertThat(wallpaperReorderIndex).isNotEqualTo(-1)
+ // Wallpaper last, to be in front of Home.
+ assertThat(wallpaperReorderIndex).isGreaterThan(homeReorderIndex)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToNextDisplay_toDeskInOtherDisplay_movesToDeskAndActivates() {
+ val transition = Binder()
+ val targetDeskId = 4
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+ taskRepository.setDeskInactive(deskId = targetDeskId)
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ whenever(transitions.startTransition(eq(TRANSIT_CHANGE), any(), anyOrNull()))
+ .thenReturn(transition)
+
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ taskRepository.addTaskToDesk(
+ displayId = DEFAULT_DISPLAY,
+ deskId = 0,
+ taskId = task.taskId,
+ isVisible = true,
+ taskBounds = TASK_BOUNDS,
+ )
+ controller.moveToNextDisplay(task.taskId)
+
+ verify(desksOrganizer).moveTaskToDesk(any(), eq(targetDeskId), eq(task), eq(false))
+ verify(desksOrganizer).activateDesk(any(), eq(targetDeskId), skipReorder = eq(false))
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ DeskTransition.ActivateDeskWithTask(
+ token = transition,
+ displayId = SECOND_DISPLAY,
+ deskId = targetDeskId,
+ enterTaskId = task.taskId,
+ )
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToNextDisplay_wasLastTaskInSourceDesk_deactivates() {
+ val transition = Binder()
+ val sourceDeskId = 0
+ val targetDeskId = 4
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+ taskRepository.setDeskInactive(deskId = targetDeskId)
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ whenever(transitions.startTransition(eq(TRANSIT_CHANGE), any(), anyOrNull()))
+ .thenReturn(transition)
+
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ taskRepository.addTaskToDesk(
+ displayId = DEFAULT_DISPLAY,
+ deskId = sourceDeskId,
+ taskId = task.taskId,
+ isVisible = true,
+ taskBounds = TASK_BOUNDS,
+ )
+ controller.moveToNextDisplay(task.taskId)
+
+ verify(desksOrganizer).deactivateDesk(any(), eq(sourceDeskId), skipReorder = eq(false))
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ DeskTransition.DeactivateDesk(token = transition, deskId = sourceDeskId)
+ )
+ }
+
+ @Test
+ @EnableFlags(
+ FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS,
+ FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
+ )
+ fun moveToNextDisplay_resetLauncherOnSourceDisplay() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ controller.moveToNextDisplay(task.taskId)
+
+ val wct =
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ wct.assertPendingIntent(launchHomeIntent(DEFAULT_DISPLAY))
+ wct.assertPendingIntentActivityOptionsLaunchDisplayId(DEFAULT_DISPLAY)
+ }
+
+ @Test
+ fun moveToNextDisplay_movingToDesktop_sendsTaskbarRoundingUpdate() {
+ val transition = Binder()
+ val sourceDeskId = 0
+ val targetDeskId = 2
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+ taskRepository.setDeskInactive(deskId = targetDeskId)
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ whenever(transitions.startTransition(eq(TRANSIT_CHANGE), any(), anyOrNull()))
+ .thenReturn(transition)
+
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = sourceDeskId)
+ taskRepository.addTaskToDesk(
+ displayId = DEFAULT_DISPLAY,
+ deskId = sourceDeskId,
+ taskId = task.taskId,
+ isVisible = true,
+ taskBounds = TASK_BOUNDS,
+ )
+ controller.moveToNextDisplay(task.taskId)
+
+ verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(anyBoolean())
+ }
+
+ private fun moveToNextDesktopDisplay_moveIifDesktopModeSupportedOnDestination(
+ isDesktopModeSupportedOnDestination: Boolean
+ ) {
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+
+ // Add desk if destination support desktop
+ if (isDesktopModeSupportedOnDestination) {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = 2)
+ }
+
+ // 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)
+
+ // Set up external display content
+ val secondaryDisplay = mock(Display::class.java)
+ whenever(displayController.getDisplay(SECOND_DISPLAY)).thenReturn(secondaryDisplay)
+
+ desktopState.overrideDesktopModeSupportPerDisplay[SECOND_DISPLAY] =
+ isDesktopModeSupportedOnDestination
+
+ // Set up a task on the default display
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+
+ controller.moveToNextDesktopDisplay(task.taskId)
+
+ val verificationMode =
+ if (isDesktopModeSupportedOnDestination) {
+ times(1)
+ } else {
+ never()
+ }
+ verify(transitions, verificationMode)
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ any(),
+ isA(DesktopModeMoveToDisplayTransitionHandler::class.java),
+ )
+ verify(snapEventHandler, verificationMode).removeTaskIfTiled(task.displayId, task.taskId)
+ }
+
+ @Test
+ fun moveToNextDesktopDisplay_moveIfDesktopModeSupportedOnDestination() {
+ moveToNextDesktopDisplay_moveIifDesktopModeSupportedOnDestination(true)
+ }
+
+ @Test
+ fun moveToNextDesktopDisplay_dontMoveIfDesktopModeNotSupportedOnDestination() {
+ moveToNextDesktopDisplay_moveIifDesktopModeSupportedOnDestination(false)
+ }
+
+ @Test
+ fun getTaskWindowingMode() {
+ val fullscreenTask = setUpFullscreenTask()
+ val freeformTask = setUpFreeformTask()
+
+ assertThat(controller.getTaskWindowingMode(fullscreenTask.taskId))
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ assertThat(controller.getTaskWindowingMode(freeformTask.taskId))
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ assertThat(controller.getTaskWindowingMode(999)).isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
+ fun onDesktopWindowClose_noActiveTasks() {
+ val task = setUpFreeformTask(active = false)
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
+ // Doesn't modify transaction
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY)
+ fun onDesktopWindowClose_singleActiveTask_noWallpaperActivityToken_launchesHome() {
+ val task = setUpFreeformTask()
+ val wct = WindowContainerTransaction()
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
+
+ // Should launch home
+ wct.assertPendingIntentAt(0, launchHomeIntent(DEFAULT_DISPLAY))
+ wct.assertPendingIntentActivityOptionsLaunchDisplayIdAt(0, DEFAULT_DISPLAY)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
+ fun onDesktopWindowClose_singleActiveTask_hasWallpaperActivityToken() {
+ val task = setUpFreeformTask()
+
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
+ // Adds remove wallpaper operation
+ wct.assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ }
+
+ @Test
+ fun onDesktopWindowClose_singleActiveTask_isClosing() {
+ val task = setUpFreeformTask()
+
+ taskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, deskId = 0, taskId = task.taskId)
+
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
+ // Doesn't modify transaction
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
+ fun onDesktopWindowClose_singleActiveTask_isMinimized() {
+ val task = setUpFreeformTask()
+
+ taskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId)
+
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
+ // Doesn't modify transaction
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
+ fun tilingBroken_onTaskMinimised() {
+ val task = setUpFreeformTask()
+ val transition = Binder()
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(transition)
+
+ controller.minimizeTask(task, MinimizeReason.TASK_LIMIT)
+
+ verify(snapEventHandler, times(1)).removeTaskIfTiled(task.displayId, task.taskId)
+ }
+
+ @Test
+ fun onDesktopWindowClose_multipleActiveTasks() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task1)
+ // Doesn't modify transaction
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
+ fun onDesktopWindowClose_multipleActiveTasks_isOnlyNonClosingTask() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+
+ taskRepository.addClosingTask(
+ displayId = DEFAULT_DISPLAY,
+ deskId = 0,
+ taskId = task2.taskId,
+ )
+
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task1)
+ // Adds remove wallpaper operation
+ wct.assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
+ fun onDesktopWindowClose_multipleActiveTasks_hasMinimized() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+
+ taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
+
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task1)
+ // Adds remove wallpaper operation
+ wct.assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onDesktopWindowClose_lastWindow_deactivatesDesk() {
+ val task = setUpFreeformTask()
+ val wct = WindowContainerTransaction()
+
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
+
+ verify(desksOrganizer).deactivateDesk(wct, deskId = 0)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onDesktopWindowClose_lastWindow_addsPendingDeactivateTransition() {
+ val task = setUpFreeformTask()
+ val wct = WindowContainerTransaction()
+
+ val transition = Binder()
+ val runOnTransitStart =
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
+ runOnTransitStart(transition)
+
+ verify(desksTransitionsObserver)
+ .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId = 0))
+ }
+
+ @Test
+ fun onDesktopWindowMinimize_noActiveTask_doesntRemoveWallpaper() {
+ val task = setUpFreeformTask(active = false)
+ val transition = Binder()
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(transition)
+
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
+
+ val captor = argumentCaptor()
+ verify(freeformTaskTransitionStarter)
+ .startMinimizedModeTransition(captor.capture(), eq(task.taskId), eq(false))
+ assertThat(
+ captor.firstValue.hierarchyOps.none { hop ->
+ hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK &&
+ hop.container == wallpaperToken.asBinder()
+ }
+ )
+ .isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ @DisableFlags(Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE)
+ fun onDesktopWindowMinimize_lastWindow_deactivatesDesk() {
+ val task = setUpFreeformTask()
+ val transition = Binder()
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(transition)
+
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
+
+ val captor = argumentCaptor()
+ verify(freeformTaskTransitionStarter)
+ .startMinimizedModeTransition(captor.capture(), eq(task.taskId), eq(true))
+ verify(desksOrganizer).deactivateDesk(captor.firstValue, deskId = 0)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE,
+ )
+ fun onDesktopWindowMinimize_lastWindow_dontDeactivateDesk() {
+ val task = setUpFreeformTask()
+ val transition = Binder()
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(transition)
+
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
+
+ val captor = argumentCaptor()
+ verify(freeformTaskTransitionStarter)
+ .startMinimizedModeTransition(captor.capture(), eq(task.taskId), eq(true))
+
+ assertTrue(captor.firstValue.isEmpty)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ @DisableFlags(Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE)
+ fun onDesktopWindowMinimize_lastWindow_addsPendingDeactivateTransition() {
+ val task = setUpFreeformTask()
+ val transition = Binder()
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(transition)
+
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
+
+ verify(desksTransitionsObserver)
+ .addPendingTransition(DeskTransition.DeactivateDesk(token = transition, deskId = 0))
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE,
+ )
+ fun onDesktopWindowMinimize_lastWindow_dontAddPendingDeactivateTransition() {
+ val task = setUpFreeformTask()
+ val transition = Binder()
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(transition)
+
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
+
+ verifyNoInteractions(desksTransitionsObserver)
+ }
+
+ private fun minimizePipTask(task: RunningTaskInfo, appOpsAllowed: Boolean = true) {
+ val handler = mock(TransitionHandler::class.java)
+ whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
+ .thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
+ mContext.addMockSystemService(Context.APP_OPS_SERVICE, mockAppOpsManager)
+ mContext.setMockPackageManager(packageManager)
+
+ whenever(
+ mockAppOpsManager.checkOpNoThrow(
+ eq(AppOpsManager.OP_PICTURE_IN_PICTURE),
+ any(),
+ any(),
+ )
+ )
+ .thenReturn(
+ if (appOpsAllowed) AppOpsManager.MODE_ALLOWED else AppOpsManager.MODE_IGNORED
+ )
+
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun onPipTaskMinimize_autoEnterEnabled_startPipTransition() {
+ val task = setUpPipTask(autoEnterEnabled = true)
+
+ minimizePipTask(task)
+
+ verify(freeformTaskTransitionStarter).startPipTransition(any())
+ verify(freeformTaskTransitionStarter, never())
+ .startMinimizedModeTransition(any(), anyInt(), anyBoolean())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun onPipTaskMinimize_autoEnterDisabled_startMinimizeTransition() {
+ val task = setUpPipTask(autoEnterEnabled = false)
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(Binder())
+
+ minimizePipTask(task)
+
+ verify(freeformTaskTransitionStarter)
+ .startMinimizedModeTransition(any(), eq(task.taskId), anyBoolean())
+ verify(freeformTaskTransitionStarter, never()).startPipTransition(any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun onPipTaskMinimize_pipNotAllowedInAppOps_startMinimizeTransition() {
+ val task = setUpPipTask(autoEnterEnabled = true)
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(Binder())
+ whenever(
+ mockAppOpsManager.checkOpNoThrow(
+ eq(AppOpsManager.OP_PICTURE_IN_PICTURE),
+ any(),
+ any(),
+ )
+ )
+ .thenReturn(AppOpsManager.MODE_IGNORED)
+
+ minimizePipTask(task, appOpsAllowed = false)
+
+ verify(freeformTaskTransitionStarter)
+ .startMinimizedModeTransition(any(), eq(task.taskId), anyBoolean())
+ verify(freeformTaskTransitionStarter, never()).startPipTransition(any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun onPipTaskMinimize_autoEnterEnabled_sendsTaskbarRoundingUpdate() {
+ val task = setUpPipTask(autoEnterEnabled = true)
+
+ minimizePipTask(task)
+
+ verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(anyBoolean())
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun onPipTaskMinimize_isLastTask_deactivatesDesk() {
+ val deskId = DEFAULT_DISPLAY
+ val task = setUpPipTask(autoEnterEnabled = true, deskId = deskId)
+ val transition = Binder()
+ whenever(freeformTaskTransitionStarter.startPipTransition(any())).thenReturn(transition)
+
+ minimizePipTask(task)
+
+ verify(desksOrganizer).deactivateDesk(any(), eq(deskId), skipReorder = eq(false))
+ verify(desksTransitionsObserver)
+ .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId))
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ )
+ fun onPipTaskMinimize_isLastTask_removesWallpaper() {
+ val task = setUpPipTask(autoEnterEnabled = true)
+
+ minimizePipTask(task)
+
+ val arg = argumentCaptor()
+ verify(freeformTaskTransitionStarter).startPipTransition(arg.capture())
+ // Wallpaper is moved to the back
+ arg.lastValue.assertReorder(wallpaperToken, /* toTop= */ false)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun onPipTaskMinimize_isLastTask_launchesHome() {
+ val task = setUpPipTask(autoEnterEnabled = true)
+
+ minimizePipTask(task)
+
+ val arg = argumentCaptor()
+ verify(freeformTaskTransitionStarter).startPipTransition(arg.capture())
+ arg.lastValue.assertPendingIntent(launchHomeIntent(DEFAULT_DISPLAY))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onPipTaskMinimize_multiActivity_reordersParentToBack() {
+ val task = setUpPipTask(autoEnterEnabled = true).apply { numActivities = 2 }
+ // Add a second task so that entering PiP does not trigger Desktop cleanup
+ setUpFreeformTask(deskId = DEFAULT_DISPLAY)
+
+ minimizePipTask(task)
+
+ val arg = argumentCaptor()
+ verify(freeformTaskTransitionStarter).startPipTransition(arg.capture())
+ assertThat(arg.lastValue.hierarchyOps.size).isEqualTo(1)
+ arg.lastValue.assertReorderAt(index = 0, task, toTop = false)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun onPipTaskMinimize_multiActivity_minimizeParent() {
+ val task = setUpPipTask(autoEnterEnabled = true).apply { numActivities = 2 }
+ // Add a second task so that entering PiP does not trigger Desktop cleanup
+ setUpFreeformTask(deskId = DEFAULT_DISPLAY)
+
+ minimizePipTask(task)
+
+ val arg = argumentCaptor()
+ verify(freeformTaskTransitionStarter).startPipTransition(arg.capture())
+ verify(desksOrganizer)
+ .minimizeTask(wct = arg.lastValue, deskId = DEFAULT_DISPLAY, task = task)
+ }
+
+ @Test
+ fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntRemoveWallpaper() {
+ val task = setUpFreeformTask(active = true)
+ val transition = Binder()
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(transition)
+
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
+
+ val captor = argumentCaptor()
+ verify(freeformTaskTransitionStarter)
+ .startMinimizedModeTransition(captor.capture(), eq(task.taskId), eq(true))
+ assertThat(
+ captor.firstValue.hierarchyOps.none { hop ->
+ hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK
+ }
+ )
+ .isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
+ @DisableFlags(Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE)
+ fun onTaskMinimize_singleActiveTask_hasWallpaperActivityToken_removesWallpaper() {
+ val task = setUpFreeformTask()
+ val transition = Binder()
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(transition)
+
+ // The only active task is being minimized.
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
+
+ val captor = argumentCaptor()
+ verify(freeformTaskTransitionStarter)
+ .startMinimizedModeTransition(captor.capture(), eq(task.taskId), eq(true))
+ // Adds remove wallpaper operation
+ captor.firstValue.assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE,
+ )
+ fun onTaskMinimize_singleActiveTask_hasWallpaperActivityToken_dontRemoveWallpaper() {
+ val task = setUpFreeformTask()
+ val transition = Binder()
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(transition)
+
+ // The only active task is being minimized.
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
+
+ val captor = argumentCaptor()
+ verify(freeformTaskTransitionStarter)
+ .startMinimizedModeTransition(captor.capture(), eq(task.taskId), eq(true))
+
+ assertThat(captor.firstValue.changes).isEmpty()
+ }
+
+ @Test
+ fun onDesktopWindowMinimize_singleActiveTask_alreadyMinimized_doesntRemoveWallpaper() {
+ val task = setUpFreeformTask()
+ val transition = Binder()
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(transition)
+ taskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId)
+
+ // The only active task is already minimized.
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
+
+ val captor = argumentCaptor()
+ verify(freeformTaskTransitionStarter)
+ .startMinimizedModeTransition(captor.capture(), eq(task.taskId), eq(false))
+ assertThat(
+ captor.firstValue.hierarchyOps.none { hop ->
+ hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK &&
+ hop.container == wallpaperToken.asBinder()
+ }
+ )
+ .isTrue()
+ }
+
+ @Test
+ fun onDesktopWindowMinimize_multipleActiveTasks_doesntRemoveWallpaper() {
+ val task1 = setUpFreeformTask(active = true)
+ setUpFreeformTask(active = true)
+ val transition = Binder()
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(transition)
+
+ controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON)
+
+ val captor = argumentCaptor()
+ verify(freeformTaskTransitionStarter)
+ .startMinimizedModeTransition(captor.capture(), eq(task1.taskId), eq(false))
+ assertThat(
+ captor.firstValue.hierarchyOps.none { hop ->
+ hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK &&
+ hop.container == wallpaperToken.asBinder()
+ }
+ )
+ .isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
+ @DisableFlags(Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE)
+ fun onDesktopWindowMinimize_multipleActiveTasks_minimizesTheOnlyVisibleTask_removesWallpaper() {
+ val task1 = setUpFreeformTask(active = true)
+ val task2 = setUpFreeformTask(active = true)
+ val transition = Binder()
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(transition)
+ taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
+
+ // task1 is the only visible task as task2 is minimized.
+ controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON)
+ // Adds remove wallpaper operation
+ val captor = argumentCaptor()
+ verify(freeformTaskTransitionStarter)
+ .startMinimizedModeTransition(captor.capture(), eq(task1.taskId), eq(true))
+ // Adds remove wallpaper operation
+ captor.firstValue.assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun onDesktopWindowMinimize_multipleActiveTasks_minimizesTheOnlyVisibleTask_dontRemoveWallpaper() {
+ val task1 = setUpFreeformTask(active = true)
+ val task2 = setUpFreeformTask(active = true)
+ val transition = Binder()
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(transition)
+ taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
+
+ // task1 is the only visible task as task2 is minimized.
+ controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON)
+
+ val captor = argumentCaptor()
+ verify(freeformTaskTransitionStarter)
+ .startMinimizedModeTransition(captor.capture(), eq(task1.taskId), eq(true))
+
+ assertTrue(captor.firstValue.isEmpty)
+ }
+
+ @Test
+ fun onDesktopWindowMinimize_triesToExitImmersive() {
+ val task = setUpFreeformTask()
+ val transition = Binder()
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(transition)
+
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
+
+ verify(mMockDesktopImmersiveController).exitImmersiveIfApplicable(any(), eq(task), any())
+ }
+
+ @Test
+ fun onDesktopWindowMinimize_invokesImmersiveTransitionStartCallback() {
+ val task = setUpFreeformTask()
+ val transition = Binder()
+ val runOnTransit = RunOnStartTransitionCallback()
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(transition)
+ whenever(mMockDesktopImmersiveController.exitImmersiveIfApplicable(any(), eq(task), any()))
+ .thenReturn(
+ ExitResult.Exit(exitingTask = task.taskId, runOnTransitionStart = runOnTransit)
+ )
+
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
+
+ assertThat(runOnTransit.invocations).isEqualTo(1)
+ assertThat(runOnTransit.lastInvoked).isEqualTo(transition)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onDesktopWindowMinimize_minimizesTask() {
+ val task = setUpFreeformTask()
+ val transition = Binder()
+ val runOnTransit = RunOnStartTransitionCallback()
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(transition)
+ whenever(mMockDesktopImmersiveController.exitImmersiveIfApplicable(any(), eq(task), any()))
+ .thenReturn(
+ ExitResult.Exit(exitingTask = task.taskId, runOnTransitionStart = runOnTransit)
+ )
+
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
+
+ verify(desksOrganizer).minimizeTask(any(), /* deskId= */ eq(0), eq(task))
+ }
+
+ @Test
+ fun onDesktopWindowMinimize_triesToStopTiling() {
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val transition = Binder()
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(transition)
+
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
+
+ verify(snapEventHandler).removeTaskIfTiled(eq(DEFAULT_DISPLAY), eq(task.taskId))
+ }
+
+ @Test
+ fun onDesktopWindowMinimize_sendsTaskbarRoundingUpdate() {
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val transition = Binder()
+ whenever(
+ freeformTaskTransitionStarter.startMinimizedModeTransition(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(transition)
+
+ controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
+
+ verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(anyBoolean())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTask_switchToDesktop_movesTaskToDesk() {
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = 5)
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 5)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 5)
+
+ val fullscreenTask = createFullscreenTask()
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct, "should handle request")
+ verify(desksOrganizer).moveTaskToDesk(wct = wct, deskId = 5, task = fullscreenTask)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTaskThatWasInactiveInDesk_tracksDeskDeactivation() {
+ // Set up and existing desktop task in an active desk.
+ val inactiveInDeskTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+ taskRepository.setDeskInactive(deskId = 0)
+
+ // Now the task is launching as fullscreen.
+ inactiveInDeskTask.configuration.windowConfiguration.windowingMode =
+ WINDOWING_MODE_FULLSCREEN
+ val transition = Binder()
+ val wct = controller.handleRequest(transition, createTransition(inactiveInDeskTask))
+
+ // Desk is deactivated.
+ assertNotNull(wct, "should handle request")
+ verify(desksTransitionsObserver)
+ .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId = 0))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTask_freeformVisible_multiDesksDisabled_returnSwitchToFreeformWCT() {
+ val homeTask = setUpHomeTask()
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+ val fullscreenTask = createFullscreenTask()
+
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct, "should handle request")
+ assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+
+ assertThat(wct.hierarchyOps).hasSize(1)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTask_deskActive_multiDesksEnabled_movesToDesk() {
+ val deskId = 0
+ taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId = deskId)
+ setUpHomeTask()
+ val fullscreenTask = createFullscreenTask()
+
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct, "should handle request")
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId, fullscreenTask)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTaskWithTaskOnHome_freeformVisible_multiDesksDisabled_returnSwitchToFreeformWCT() {
+ val homeTask = setUpHomeTask()
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+ val fullscreenTask = createFullscreenTask()
+ fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct, "should handle request")
+ assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+
+ // There are 5 hops that are happening in this case:
+ // 1. Moving the fullscreen task to top as we add moveToDesktop() changes
+ // 2. Bringing home task to front
+ // 3. Pending intent for the wallpaper
+ // 4. Bringing the existing freeform task to top
+ // 5. Bringing the fullscreen task back at the top
+ assertThat(wct.hierarchyOps).hasSize(5)
+ wct.assertReorderAt(1, homeTask, toTop = true)
+ wct.assertReorderAt(4, fullscreenTask, toTop = true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTaskWithTaskOnHome_activeDesk_multiDesksEnabled_movesToDesk() {
+ val deskId = 0
+ setUpHomeTask()
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ val fullscreenTask = createFullscreenTask()
+ fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct, "should handle request")
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId, fullscreenTask)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTaskToDesk_underTaskLimit_multiDesksDisabled_dontMinimize() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+ val fullscreenTask = createFullscreenTask()
+
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ // Make sure we only reorder the new task to top (we don't reorder the old task to bottom)
+ assertThat(wct?.hierarchyOps?.size).isEqualTo(1)
+ wct!!.assertReorderAt(0, fullscreenTask, toTop = true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTaskToDesk_underTaskLimit_multiDesksEnabled_dontMinimize() {
+ val deskId = 5
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ val freeformTask =
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId).also {
+ markTaskVisible(it)
+ }
+
+ // Launch a fullscreen task while in the desk.
+ val fullscreenTask = createFullscreenTask()
+ val transition = Binder()
+ val wct = controller.handleRequest(transition, createTransition(fullscreenTask))
+
+ assertNotNull(wct)
+ verify(desksOrganizer, never()).minimizeTask(eq(wct), eq(deskId), any())
+ assertNull(desktopTasksLimiter.getMinimizingTask(transition))
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION,
+ )
+ fun handleRequest_fullscreenTaskToDesk_bringsTasksOverLimit_multiDesksDisabled_otherTaskIsMinimized() {
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+ freeformTasks.forEach { markTaskVisible(it) }
+ val fullscreenTask = createFullscreenTask()
+
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ // Make sure we reorder the new task to top, and the back task to the bottom
+ assertThat(wct!!.hierarchyOps.size).isEqualTo(2)
+ wct.assertReorderAt(0, fullscreenTask, toTop = true)
+ wct.assertReorderAt(1, freeformTasks[0], toTop = false)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
+ fun handleRequest_fullscreenTaskToDesk_bringsTasksOverLimit_multiDesksDisabled_separateMinimize() {
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+ freeformTasks.forEach { markTaskVisible(it) }
+ val fullscreenTask = createFullscreenTask()
+ val transition = Binder()
+
+ val wct = controller.handleRequest(transition, createTransition(fullscreenTask))
+
+ // Make sure we reorder the new task to top, and the back task to the bottom
+ assertThat(wct!!.hierarchyOps.size).isEqualTo(1)
+ wct.assertReorderAt(0, fullscreenTask, toTop = true)
+ assertThat(desktopTasksLimiter.hasTaskLimitTransitionForTesting(transition)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
+ fun handleRequest_fullscreenTaskToDesk_bringsTasksOverLimit_multiDesksEnabled_otherTaskIsMinimized() {
+ val deskId = 5
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId).also {
+ markTaskVisible(it)
+ }
+ }
+
+ // Launch a fullscreen task while in the desk.
+ setUpHomeTask()
+ val fullscreenTask = createFullscreenTask()
+ val transition = Binder()
+ val wct = controller.handleRequest(transition, createTransition(fullscreenTask))
+
+ assertNotNull(wct)
+ verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[0])
+ val minimizingTask =
+ assertNotNull(desktopTasksLimiter.getMinimizingTask(transition)?.taskId)
+ assertThat(minimizingTask).isEqualTo(freeformTasks[0].taskId)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION,
+ )
+ fun handleRequest_fullscreenTaskWithTaskOnHome_bringsTasksOverLimit_multiDesksDisabled_otherTaskIsMinimized() {
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+ freeformTasks.forEach { markTaskVisible(it) }
+ val fullscreenTask = createFullscreenTask()
+ fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ // Make sure we reorder the new task to top, and the back task to the bottom
+ assertThat(wct!!.hierarchyOps.size).isEqualTo(8)
+ wct.assertReorderAt(0, fullscreenTask, toTop = true)
+ // Oldest task that needs to minimized is never reordered to top over Home.
+ val taskToMinimize = freeformTasks[0]
+ wct.assertWithoutHop { hop ->
+ hop.container == taskToMinimize.token &&
+ hop.type == HIERARCHY_OP_TYPE_REORDER &&
+ hop.toTop == true
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
+ fun handleRequest_fullscreenTaskWithTaskOnHome_bringsTasksOverLimit_multiDesksEnabled_otherTaskIsMinimized() {
+ val deskId = 5
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ }
+ freeformTasks.forEach { markTaskVisible(it) }
+ val fullscreenTask = createFullscreenTask()
+ fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct)
+ // The launching task is moved to the desk.
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId, fullscreenTask)
+ // The bottom-most task is minimized.
+ verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[0])
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
+ fun handleRequest_fullscreenTaskToDesk_bringsTasksOverLimit_separateMinimizeFlagEnabled_minimizeSeparately() {
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+ freeformTasks.forEach { markTaskVisible(it) }
+ val fullscreenTask = createFullscreenTask()
+ val transition = Binder()
+
+ val wct = controller.handleRequest(transition, createTransition(fullscreenTask))
+
+ assertThat(wct!!.hierarchyOps.size).isAtMost(1)
+ assertThat(desktopTasksLimiter.hasTaskLimitTransitionForTesting(transition)).isTrue()
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION,
+ )
+ fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_multiDesksDisabled_existingAndNewTasksAreMinimized() {
+ val minimizedTask = setUpFreeformTask()
+ taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = minimizedTask.taskId)
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+ freeformTasks.forEach { markTaskVisible(it) }
+ val homeTask = setUpHomeTask()
+ val fullscreenTask = createFullscreenTask()
+ fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertThat(wct!!.hierarchyOps.size).isEqualTo(9)
+ wct.assertReorderAt(0, fullscreenTask, toTop = true)
+ // Make sure we reorder the home task to the top, desktop tasks to top of them and minimized
+ // task is under the home task.
+ wct.assertReorderAt(1, homeTask, toTop = true)
+ // Oldest task that needs to minimized is never reordered to top over Home.
+ val taskToMinimize = freeformTasks[0]
+ wct.assertWithoutHop { hop ->
+ hop.container == taskToMinimize.token &&
+ hop.type == HIERARCHY_OP_TYPE_REORDER &&
+ hop.toTop == true
+ }
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
+ fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_separateMinFlagEnabled_minimizeSeparately() {
+ val minimizedTask = setUpFreeformTask()
+ taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = minimizedTask.taskId)
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+ freeformTasks.forEach { markTaskVisible(it) }
+ val homeTask = setUpHomeTask()
+ val fullscreenTask = createFullscreenTask()
+ fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertThat(wct!!.hierarchyOps.size).isEqualTo(10)
+ wct.assertReorderAt(0, fullscreenTask, toTop = true)
+ // Make sure we reorder the home task to the top, desktop tasks to top of them and minimized
+ // task is under the home task.
+ wct.assertReorderAt(1, homeTask, toTop = true)
+ val taskToMinimize = freeformTasks[0]
+ wct.assertReorder(taskToMinimize.token, toTop = true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
+ fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_multiDesksEnabled_existingAndNewTasksAreMinimized() {
+ // A desk with a minimized tasks, and non-minimized tasks already at the task limit.
+ val deskId = 5
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ val minimizedTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ taskRepository.minimizeTaskInDesk(
+ displayId = DEFAULT_DISPLAY,
+ deskId = deskId,
+ taskId = minimizedTask.taskId,
+ )
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId).also {
+ markTaskVisible(it)
+ }
+ }
+
+ // Launch a fullscreen task that brings Home to front with it.
+ setUpHomeTask()
+ val fullscreenTask = createFullscreenTask()
+ fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct)
+ verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[0])
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTaskWithTaskOnHome_taskAddedToDesk() {
+ // A desk with a minimized tasks, and non-minimized tasks already at the task limit.
+ val deskId = 5
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+
+ // Launch a fullscreen task that brings Home to front with it.
+ setUpHomeTask()
+ val fullscreenTask = createFullscreenTask()
+ fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct)
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId, fullscreenTask)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ )
+ fun handleRequest_fullscreenTaskWithTaskOnHome_activatesDesk() {
+ val deskId = 5
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+
+ // Launch a fullscreen task that brings Home to front with it.
+ val homeTask = setUpHomeTask()
+ val fullscreenTask = createFullscreenTask()
+ fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+ val transition = Binder()
+ val wct = controller.handleRequest(transition, createTransition(fullscreenTask))
+
+ assertNotNull(wct)
+ wct.assertReorder(homeTask, toTop = true)
+ wct.assertReorder(wallpaperToken, toTop = true)
+ verify(desksOrganizer).activateDesk(wct, deskId)
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ DeskTransition.ActivateDeskWithTask(
+ token = transition,
+ displayId = DEFAULT_DISPLAY,
+ deskId = deskId,
+ enterTaskId = fullscreenTask.taskId,
+ )
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTask_noTasks_enforceDesktop_freeformDisplay_returnFreeformWCT() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+ desktopState.enterDesktopByDefaultOnFreeformDisplay = true
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+
+ val fullscreenTask = createFullscreenTask()
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct, "should handle request")
+ assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // There are 3 hops that are happening in this case:
+ // 1. Moving the fullscreen task to top as we add moveToDesktop() changes
+ // 2. Pending intent for the wallpaper
+ // 3. Bringing the fullscreen task back at the top
+ wct.assertPendingIntentAt(1, desktopWallpaperIntent)
+ wct.assertReorderAt(2, fullscreenTask, toTop = true)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun handleRequest_fullscreenTask_noInDesk_enforceDesktop_freeformDisplay_movesToDesk() {
+ val deskId = 0
+ taskRepository.setDeskInactive(deskId)
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+ desktopState.enterDesktopByDefaultOnFreeformDisplay = true
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+
+ val fullscreenTask = createFullscreenTask()
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct, "should handle request")
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId, fullscreenTask)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_FIRST_BASED_DEFAULT_TO_DESKTOP_BUGFIX,
+ )
+ fun handleRequest_fullscreenTask_noInDesk_enforceDesktop_freeformDisplay_movesToDesk_desktopFirst() {
+ // Ensure the force enter desktop works when the deprecated flag is off.
+ desktopState.enterDesktopByDefaultOnFreeformDisplay = false
+ val deskId = 0
+ taskRepository.setDeskInactive(deskId)
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+ desktopState.enterDesktopByDefaultOnFreeformDisplay = true
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+
+ val fullscreenTask = createFullscreenTask()
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct, "should handle request")
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId, fullscreenTask)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun handleRequest_fullscreenTask_noInDesk_enforceDesktop_secondaryDisplay_movesToDesk() {
+ val deskId = 5
+ taskRepository.addDesk(displayId = SECONDARY_DISPLAY_ID, deskId = deskId)
+ taskRepository.setDeskInactive(deskId)
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+ desktopState.enterDesktopByDefaultOnFreeformDisplay = true
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration
+ .windowConfiguration
+ .windowingMode = WINDOWING_MODE_FREEFORM
+
+ val fullscreenTask = createFullscreenTask(displayId = SECONDARY_DISPLAY_ID)
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct, "should handle request")
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId, fullscreenTask)
+ }
+
+ @Test
+ fun handleRequest_fullscreenTask_notInDesk_enforceDesktop_fullscreenDisplay_returnNull() {
+ taskRepository.setDeskInactive(deskId = 0)
+ desktopState.enterDesktopByDefaultOnFreeformDisplay = true
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+
+ val fullscreenTask = createFullscreenTask()
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertThat(wct).isNull()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTask_freeformNotVisible_returnNull() {
+ val freeformTask = setUpFreeformTask()
+ markTaskHidden(freeformTask)
+ val fullscreenTask = createFullscreenTask()
+ assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTask_noOtherTasks_returnNull() {
+ val fullscreenTask = createFullscreenTask()
+ assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTask_notInDesk_returnNull() {
+ taskRepository.setDeskInactive(deskId = 0)
+ val fullscreenTask = createFullscreenTask()
+
+ assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTask_freeformTaskOnOtherDisplay_returnNull() {
+ val fullscreenTaskDefaultDisplay = createFullscreenTask(displayId = DEFAULT_DISPLAY)
+ createFreeformTask(displayId = SECOND_DISPLAY)
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(fullscreenTaskDefaultDisplay))
+ assertThat(result).isNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTask_deskInOtherDisplayActive_returnNull() {
+ taskRepository.setDeskInactive(deskId = 0)
+ val fullscreenTaskDefaultDisplay = createFullscreenTask(displayId = DEFAULT_DISPLAY)
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = 2)
+ taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = 2)
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(fullscreenTaskDefaultDisplay))
+
+ assertThat(result).isNull()
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION,
+ )
+ fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_multiDesksDisabled_minimize() {
+ val deskId = 0
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ }
+ freeformTasks.forEach { markTaskVisible(it) }
+ val newFreeformTask = createFreeformTask()
+
+ val wct =
+ controller.handleRequest(Binder(), createTransition(newFreeformTask, TRANSIT_OPEN))
+
+ assertThat(wct?.hierarchyOps?.size).isEqualTo(1)
+ wct!!.assertReorderAt(0, freeformTasks[0], toTop = false) // Reorder to the bottom
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
+ fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_multiDesksEnabled_minimize() {
+ val deskId = 0
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ }
+ freeformTasks.forEach { markTaskVisible(it) }
+ val newFreeformTask = createFreeformTask()
+
+ val wct =
+ controller.handleRequest(Binder(), createTransition(newFreeformTask, TRANSIT_OPEN))
+
+ assertNotNull(wct)
+ verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[0])
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
+ fun handleRequest_freeform_aboveTaskLimit_separateMinimizeFlagEnabled_minimizeSeparately() {
+ val deskId = 0
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ }
+ freeformTasks.forEach { markTaskVisible(it) }
+ val newFreeformTask = createFreeformTask()
+ val transition = Binder()
+
+ val wct =
+ controller.handleRequest(transition, createTransition(newFreeformTask, TRANSIT_OPEN))
+
+ assertNotNull(wct)
+ assertThat(wct.hierarchyOps).isEmpty()
+ assertThat(desktopTasksLimiter.hasTaskLimitTransitionForTesting(transition)).isTrue()
+ }
+
+ @Test
+ fun handleRequest_freeformTask_relaunchActiveTask_taskBecomesUndefined() {
+ taskRepository.setDeskInactive(deskId = 0)
+ val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+ markTaskHidden(freeformTask)
+
+ val wct = controller.handleRequest(Binder(), createTransition(freeformTask))
+
+ // Should become undefined as the TDA is set to fullscreen. It will inherit from the TDA.
+ assertNotNull(wct, "should handle request")
+ assertThat(wct.changes[freeformTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
+ fun handleRequest_freeformTask_relaunchTask_enforceDesktop_freeformDisplay_noWinModeChange() {
+ desktopState.enterDesktopByDefaultOnFreeformDisplay = true
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+
+ val freeformTask = setUpFreeformTask()
+ markTaskHidden(freeformTask)
+ val wct = controller.handleRequest(Binder(), createTransition(freeformTask))
+
+ assertNotNull(wct, "should handle request")
+ assertFalse(wct.anyWindowingModeChange(freeformTask.token))
+ }
+
+ @Test
+ fun handleRequest_freeformTask_relaunchTask_enforceDesktop_fullscreenDisplay_becomesUndefined() {
+ desktopState.enterDesktopByDefaultOnFreeformDisplay = true
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ taskRepository.setDeskInactive(deskId = 0)
+ val freeformTask = setUpFreeformTask(DEFAULT_DISPLAY, deskId = 0)
+ markTaskHidden(freeformTask)
+
+ val wct = controller.handleRequest(Binder(), createTransition(freeformTask))
+
+ assertNotNull(wct, "should handle request")
+ assertThat(wct.changes[freeformTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun handleRequest_freeformTask_desktopWallpaperDisabled_freeformNotVisible_reorderedToTop() {
+ val freeformTask1 = setUpFreeformTask()
+ val freeformTask2 = createFreeformTask()
+
+ markTaskHidden(freeformTask1)
+ val result =
+ controller.handleRequest(
+ Binder(),
+ createTransition(freeformTask2, type = TRANSIT_TO_FRONT),
+ )
+
+ assertNotNull(result, "Should handle request")
+ assertThat(result.hierarchyOps?.size).isEqualTo(2)
+ result.assertReorderAt(1, freeformTask2, toTop = true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_multiDesksDisabled_reorderedToTop() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+ val freeformTask1 = setUpFreeformTask()
+ val freeformTask2 = createFreeformTask()
+
+ markTaskHidden(freeformTask1)
+ val result =
+ controller.handleRequest(
+ Binder(),
+ createTransition(freeformTask2, type = TRANSIT_TO_FRONT),
+ )
+
+ assertNotNull(result, "Should handle request")
+ assertThat(result.hierarchyOps?.size).isEqualTo(3)
+ // Add desktop wallpaper activity
+ result.assertPendingIntentAt(0, desktopWallpaperIntent)
+ // Bring active desktop tasks to front
+ result.assertReorderAt(1, freeformTask1, toTop = true)
+ // Bring new task to front
+ result.assertReorderAt(2, freeformTask2, toTop = true)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun handleRequest_freeformTask_desktopWallpaperEnabled_notInDesk_reorderedToTop() {
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+ val deskId = 0
+ taskRepository.setDeskInactive(deskId)
+ val freeformTask1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ val freeformTask2 = createFreeformTask()
+
+ val wct =
+ controller.handleRequest(
+ Binder(),
+ createTransition(freeformTask2, type = TRANSIT_TO_FRONT),
+ )
+
+ assertNotNull(wct, "Should handle request")
+ verify(desksOrganizer).reorderTaskToFront(wct, deskId, freeformTask1)
+ wct.assertReorder(freeformTask2, toTop = true)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun handleRequest_freeformTask_desktopWallpaperDisabled_noOtherTasks_reorderedToTop() {
+ val task = createFreeformTask()
+ val result = controller.handleRequest(Binder(), createTransition(task))
+
+ assertNotNull(result, "Should handle request")
+ assertThat(result.hierarchyOps?.size).isEqualTo(1)
+ result.assertReorderAt(0, task, toTop = true)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun handleRequest_freeformTask_dskWallpaperDisabled_freeformOnOtherDisplayOnly_reorderedToTop() {
+ val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
+ // Second display task
+ createFreeformTask(displayId = SECOND_DISPLAY)
+
+ val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay))
+
+ assertNotNull(result, "Should handle request")
+ assertThat(result.hierarchyOps?.size).isEqualTo(1)
+ result.assertReorderAt(0, taskDefaultDisplay, toTop = true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_freeformTask_dskWallpaperEnabled_freeformOnOtherDisplayOnly_reorderedToTop() {
+ taskRepository.setDeskInactive(deskId = 0)
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+ val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
+ // Second display task
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = 2)
+ taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = 2)
+ setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = 2)
+
+ val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay))
+
+ assertNotNull(result, "Should handle request")
+ assertThat(result.hierarchyOps?.size).isEqualTo(2)
+ // Add desktop wallpaper activity
+ result.assertPendingIntentAt(0, desktopWallpaperIntent)
+ // Bring new task to front
+ result.assertReorderAt(1, taskDefaultDisplay, toTop = true)
+ }
+
+ @Test
+ fun handleRequest_freeformTask_alreadyInDesktop_noOverrideDensity_noConfigDensityChange() {
+ desktopConfig.useDesktopOverrideDensity = false
+
+ val freeformTask1 = setUpFreeformTask()
+ markTaskVisible(freeformTask1)
+
+ val freeformTask2 = createFreeformTask()
+ val result =
+ controller.handleRequest(
+ freeformTask2.token.asBinder(),
+ createTransition(freeformTask2),
+ )
+ assertFalse(result.anyDensityConfigChange(freeformTask2.token))
+ }
+
+ @Test
+ fun handleRequest_freeformTask_alreadyInDesktop_overrideDensity_hasConfigDensityChange() {
+ desktopConfig.useDesktopOverrideDensity = true
+
+ val freeformTask1 = setUpFreeformTask()
+ markTaskVisible(freeformTask1)
+
+ val freeformTask2 = createFreeformTask()
+ val result =
+ controller.handleRequest(
+ freeformTask2.token.asBinder(),
+ createTransition(freeformTask2),
+ )
+ assertTrue(result.anyDensityConfigChange(freeformTask2.token))
+ }
+
+ @Test
+ fun handleRequest_freeformTask_keyguardLocked_returnNull() {
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
+ val freeformTask = createFreeformTask(displayId = DEFAULT_DISPLAY)
+
+ val result = controller.handleRequest(Binder(), createTransition(freeformTask))
+
+ assertNull(result, "Should NOT handle request")
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_freeformTask_notInDesktop_noForceEnterDesktop_movesTaskToDesk() {
+ desktopState.enterDesktopByDefaultOnFreeformDisplay = false
+ taskRepository.setDeskInactive(deskId = 0)
+ val freeformTask = createFreeformTask(displayId = DEFAULT_DISPLAY)
+
+ val result = controller.handleRequest(Binder(), createTransition(freeformTask))
+
+ assertNotNull(result)
+ verify(desksOrganizer).moveTaskToDesk(result, deskId = 0, freeformTask)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_freeformTask_alreadyInDesktop_noForceEnterDesktop_movesTaskToDesk() {
+ desktopState.enterDesktopByDefaultOnFreeformDisplay = false
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val freeformTask = createFreeformTask(displayId = DEFAULT_DISPLAY)
+
+ val result = controller.handleRequest(Binder(), createTransition(freeformTask))
+
+ assertNotNull(result)
+ verify(desksOrganizer).moveTaskToDesk(result, deskId = 0, freeformTask)
+ }
+
+ @Test
+ fun handleRequest_notOpenOrToFrontTransition_returnNull() {
+ val task =
+ TestRunningTaskInfoBuilder()
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .build()
+ val transition = createTransition(task = task, type = TRANSIT_CLOSE)
+ val result = controller.handleRequest(Binder(), transition)
+ assertThat(result).isNull()
+ }
+
+ @Test
+ fun handleRequest_noTriggerTask_returnNull() {
+ assertThat(controller.handleRequest(Binder(), createTransition(task = null))).isNull()
+ }
+
+ @Test
+ fun handleRequest_triggerTaskNotStandard_returnNull() {
+ val task = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
+ assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
+ }
+
+ @Test
+ fun handleRequest_triggerTaskNotFullscreenOrFreeform_returnNull() {
+ val task =
+ TestRunningTaskInfoBuilder()
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
+ .build()
+ assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
+ }
+
+ @Test
+ fun handleRequest_recentsAnimationRunning_returnNull() {
+ // Set up a visible freeform task so a fullscreen task should be converted to freeform
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ // Mark recents animation running
+ recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_ANIMATING)
+
+ // Open a fullscreen task, check that it does not result in a WCT with changes to it
+ val fullscreenTask = createFullscreenTask()
+ assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
+ }
+
+ @Test
+ fun handleRequest_recentsAnimationRunning_relaunchActiveTask_taskBecomesUndefined() {
+ // Set up a visible freeform task
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ // Mark recents animation running
+ recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_ANIMATING)
+
+ // Should become undefined as the TDA is set to fullscreen. It will inherit from the TDA.
+ val result = controller.handleRequest(Binder(), createTransition(freeformTask))
+ assertThat(result?.changes?.get(freeformTask.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_recentsAnimationRunning_relaunchActiveTask_tracksDeskDeactivation() {
+ // Set up a visible freeform task
+ val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+ markTaskVisible(freeformTask)
+
+ // Mark recents animation running
+ recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_ANIMATING)
+
+ val transition = Binder()
+ controller.handleRequest(transition, createTransition(freeformTask))
+
+ desksTransitionsObserver.addPendingTransition(
+ DeskTransition.DeactivateDesk(transition, deskId = 0)
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_topActivityTransparentWithoutDisplay_multiDesksDisabled_returnSwitchToFreeformWCT() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ val task =
+ createFullscreenTask().apply {
+ isActivityStackTransparent = true
+ isTopActivityNoDisplay = true
+ numActivities = 1
+ }
+
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun handleRequest_topActivityTransparentWithoutDisplay_multiDesksEnabled_returnSwitchToFreeformWCT() {
+ val deskId = 0
+ val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ markTaskVisible(freeformTask)
+
+ val task =
+ createFullscreenTask().apply {
+ isActivityStackTransparent = true
+ isTopActivityNoDisplay = true
+ numActivities = 1
+ }
+
+ val wct = controller.handleRequest(Binder(), createTransition(task))
+
+ assertNotNull(wct)
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId, task)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun handleRequest_exemptFromDesktopFreeformTask_notInDesktop_returnSwitchToFullscreenWCT() {
+ taskRepository.setDeskInactive(deskId = 0)
+ val tda =
+ DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, /* featureId= */ 0).apply {
+ configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ }
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
+ val freeformExemptTask =
+ createFreeformTask(displayId = DEFAULT_DISPLAY).apply {
+ baseActivity =
+ ComponentName(
+ context.resources.getString(com.android.internal.R.string.config_systemUi),
+ /* cls= */ "",
+ )
+ }
+
+ val wct = controller.handleRequest(Binder(), createTransition(freeformExemptTask))
+
+ assertNotNull(wct, "Should handle request")
+ val mode =
+ assertNotNull(
+ wct.changes[freeformExemptTask.token.asBinder()]?.windowingMode,
+ "Should have change for freeform task",
+ )
+ assertThat(mode).isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION,
+ Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PLATFORM_SIGNATURE,
+ )
+ fun handleRequest_topActivityTransparentWithDisplay_returnSwitchToFullscreenWCT() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ val task =
+ setUpFreeformTask().apply {
+ isActivityStackTransparent = true
+ isTopActivityNoDisplay = false
+ numActivities = 1
+ baseActivity = ComponentName(/* pkg= */ "", /* cls= */ "")
+ }
+
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
.isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleActiveTask_noToken() {
- val task = setUpFreeformTask()
- val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
- // Doesn't handle request
- assertThat(result).isNull()
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleActiveTask_hasToken_desktopWallpaperDisabled() {
- desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
-
- val task = setUpFreeformTask()
- val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
- // Doesn't handle request
- assertThat(result).isNull()
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleActiveTask_hasToken_desktopWallpaperEnabled() {
- val wallpaperToken = MockToken().token()
- desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
-
- val task = setUpFreeformTask()
- val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
- assertThat(result).isNotNull()
- // Creates remove wallpaper transaction
- result!!.assertRemoveAt(index = 0, wallpaperToken)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_multipleActiveTasks() {
- desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
-
- val task1 = setUpFreeformTask()
- setUpFreeformTask()
- val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
- // Doesn't handle request
- assertThat(result).isNull()
- }
-
- @Test
- fun desktopTasksVisibilityChange_visible_setLaunchAdjacentDisabled() {
- val task = setUpFreeformTask()
- clearInvocations(launchAdjacentController)
-
- markTaskVisible(task)
- shellExecutor.flushAll()
- verify(launchAdjacentController).launchAdjacentEnabled = false
- }
-
- @Test
- fun desktopTasksVisibilityChange_invisible_setLaunchAdjacentEnabled() {
- val task = setUpFreeformTask()
- markTaskVisible(task)
- clearInvocations(launchAdjacentController)
-
- markTaskHidden(task)
- shellExecutor.flushAll()
- verify(launchAdjacentController).launchAdjacentEnabled = true
- }
-
- @Test
- fun moveFocusedTaskToDesktop_fullscreenTaskIsMovedToDesktop() {
- val task1 = setUpFullscreenTask()
- val task2 = setUpFullscreenTask()
- val task3 = setUpFullscreenTask()
-
- task1.isFocused = true
- task2.isFocused = false
- task3.isFocused = false
-
- controller.moveFocusedTaskToDesktop(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
-
- val wct = getLatestEnterDesktopWct()
- assertThat(wct.changes[task1.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FREEFORM)
- }
-
- @Test
- fun moveFocusedTaskToDesktop_splitScreenTaskIsMovedToDesktop() {
- val task1 = setUpSplitScreenTask()
- val task2 = setUpFullscreenTask()
- val task3 = setUpFullscreenTask()
- val task4 = setUpSplitScreenTask()
-
- task1.isFocused = true
- task2.isFocused = false
- task3.isFocused = false
- task4.isFocused = true
-
- task4.parentTaskId = task1.taskId
-
- controller.moveFocusedTaskToDesktop(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
-
- val wct = getLatestEnterDesktopWct()
- assertThat(wct.changes[task4.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FREEFORM)
- verify(splitScreenController)
- .prepareExitSplitScreen(any(), anyInt(), eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE))
- }
-
- @Test
- fun moveFocusedTaskToFullscreen() {
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- val task3 = setUpFreeformTask()
-
- task1.isFocused = false
- task2.isFocused = true
- task3.isFocused = false
-
- controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
-
- val wct = getLatestExitDesktopWct()
- assertThat(wct.changes[task2.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
- val spyController = spy(controller)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
-
- val task = setUpFullscreenTask()
- setUpLandscapeDisplay()
-
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
- val wct = getLatestDragToDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun dragToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() {
- val spyController = spy(controller)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
-
- val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
- setUpLandscapeDisplay()
-
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
- val wct = getLatestDragToDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun dragToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() {
- val spyController = spy(controller)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
-
- val task =
- setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true)
- setUpLandscapeDisplay()
-
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
- val wct = getLatestDragToDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun dragToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() {
- val spyController = spy(controller)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
-
- val task =
- setUpFullscreenTask(isResizable = false, screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
- setUpLandscapeDisplay()
-
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
- val wct = getLatestDragToDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun dragToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() {
- val spyController = spy(controller)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
-
- val task =
- setUpFullscreenTask(
- isResizable = false,
- screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
- shouldLetterbox = true)
- setUpLandscapeDisplay()
-
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
- val wct = getLatestDragToDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun dragToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() {
- val spyController = spy(controller)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
-
- val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT)
- setUpPortraitDisplay()
-
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
- val wct = getLatestDragToDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun dragToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() {
- val spyController = spy(controller)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
-
- val task =
- setUpFullscreenTask(
- deviceOrientation = ORIENTATION_PORTRAIT,
- screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
- setUpPortraitDisplay()
-
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
- val wct = getLatestDragToDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun dragToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() {
- val spyController = spy(controller)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
-
- val task =
- setUpFullscreenTask(
- deviceOrientation = ORIENTATION_PORTRAIT,
- screenOrientation = SCREEN_ORIENTATION_LANDSCAPE,
- shouldLetterbox = true)
- setUpPortraitDisplay()
-
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
- val wct = getLatestDragToDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun dragToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() {
- val spyController = spy(controller)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
-
- val task =
- setUpFullscreenTask(
- isResizable = false,
- deviceOrientation = ORIENTATION_PORTRAIT,
- screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
- setUpPortraitDisplay()
-
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task)
- val wct = getLatestDragToDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun dragToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() {
- val spyController = spy(controller)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
-
- val task =
- setUpFullscreenTask(
- isResizable = false,
- deviceOrientation = ORIENTATION_PORTRAIT,
- screenOrientation = SCREEN_ORIENTATION_LANDSCAPE,
- shouldLetterbox = true)
- setUpPortraitDisplay()
-
- spyController.onDragPositioningEndThroughStatusBar(PointF(200f, 200f), task)
- val wct = getLatestDragToDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
- }
-
- @Test
- fun onDesktopDragMove_endsOutsideValidDragArea_snapsToValidBounds() {
- val task = setUpFreeformTask()
- val mockSurface = mock(SurfaceControl::class.java)
- val mockDisplayLayout = mock(DisplayLayout::class.java)
- whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
- whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
- controller.onDragPositioningMove(task, mockSurface, 200f, Rect(100, -100, 500, 1000))
-
- controller.onDragPositioningEnd(
- task,
- Point(100, -100), /* position */
- PointF(200f, -200f), /* inputCoordinate */
- Rect(100, -100, 500, 1000), /* taskBounds */
- Rect(0, 50, 2000, 2000) /* validDragArea */)
- val rectAfterEnd = Rect(100, 50, 500, 1150)
- verify(transitions)
- .startTransition(
- eq(TRANSIT_CHANGE),
- Mockito.argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- change.configuration.windowConfiguration.bounds == rectAfterEnd
- }
- },
- eq(null))
- }
-
- fun enterSplit_freeformTaskIsMovedToSplit() {
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- val task3 = setUpFreeformTask()
-
- task1.isFocused = false
- task2.isFocused = true
- task3.isFocused = false
-
- controller.enterSplit(DEFAULT_DISPLAY, false)
-
- verify(splitScreenController)
- .requestEnterSplitSelect(
- task2,
- any(),
- SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT,
- task2.configuration.windowConfiguration.bounds)
- }
-
- @Test
- fun toggleBounds_togglesToStableBounds() {
- val bounds = Rect(0, 0, 100, 100)
- val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
-
- controller.toggleDesktopTaskSize(task)
- // Assert bounds set to stable bounds
- val wct = getLatestToggleResizeDesktopTaskWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS)
- }
-
- @Test
- fun toggleBounds_lastBoundsBeforeMaximizeSaved() {
- val bounds = Rect(0, 0, 100, 100)
- val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
-
- controller.toggleDesktopTaskSize(task)
- assertThat(desktopModeTaskRepository.removeBoundsBeforeMaximize(task.taskId)).isEqualTo(bounds)
- }
-
- @Test
- fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize() {
- val boundsBeforeMaximize = Rect(0, 0, 100, 100)
- val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
-
- // Maximize
- controller.toggleDesktopTaskSize(task)
- task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS)
-
- // Restore
- controller.toggleDesktopTaskSize(task)
-
- // Assert bounds set to last bounds before maximize
- val wct = getLatestToggleResizeDesktopTaskWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
- }
-
- @Test
- fun toggleBounds_removesLastBoundsBeforeMaximizeAfterRestoringBounds() {
- val boundsBeforeMaximize = Rect(0, 0, 100, 100)
- val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
-
- // Maximize
- controller.toggleDesktopTaskSize(task)
- task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS)
-
- // Restore
- controller.toggleDesktopTaskSize(task)
-
- // Assert last bounds before maximize removed after use
- assertThat(desktopModeTaskRepository.removeBoundsBeforeMaximize(task.taskId)).isNull()
- }
-
- private val desktopWallpaperIntent: Intent
- get() = Intent(context, DesktopWallpaperActivity::class.java)
-
- private fun setUpFreeformTask(
- displayId: Int = DEFAULT_DISPLAY,
- bounds: Rect? = null
- ): RunningTaskInfo {
- val task = createFreeformTask(displayId, bounds)
- val activityInfo = ActivityInfo()
- task.topActivityInfo = activityInfo
- whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
- desktopModeTaskRepository.addActiveTask(displayId, task.taskId)
- desktopModeTaskRepository.addOrMoveFreeformTaskToTop(displayId, task.taskId)
- runningTasks.add(task)
- return task
- }
-
- private fun setUpHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
- val task = createHomeTask(displayId)
- whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
- runningTasks.add(task)
- return task
- }
-
- private fun setUpFullscreenTask(
- displayId: Int = DEFAULT_DISPLAY,
- isResizable: Boolean = true,
- windowingMode: Int = WINDOWING_MODE_FULLSCREEN,
- deviceOrientation: Int = ORIENTATION_LANDSCAPE,
- screenOrientation: Int = SCREEN_ORIENTATION_UNSPECIFIED,
- shouldLetterbox: Boolean = false
- ): RunningTaskInfo {
- val task = createFullscreenTask(displayId)
- val activityInfo = ActivityInfo()
- activityInfo.screenOrientation = screenOrientation
- with(task) {
- topActivityInfo = activityInfo
- isResizeable = isResizable
- configuration.orientation = deviceOrientation
- configuration.windowConfiguration.windowingMode = windowingMode
-
- if (shouldLetterbox) {
- if (deviceOrientation == ORIENTATION_LANDSCAPE &&
- screenOrientation == SCREEN_ORIENTATION_PORTRAIT) {
- // Letterbox to portrait size
- appCompatTaskInfo.topActivityBoundsLetterboxed = true
- appCompatTaskInfo.topActivityLetterboxWidth = 1200
- appCompatTaskInfo.topActivityLetterboxHeight = 1600
- } else if (deviceOrientation == ORIENTATION_PORTRAIT &&
- screenOrientation == SCREEN_ORIENTATION_LANDSCAPE) {
- // Letterbox to landscape size
- appCompatTaskInfo.topActivityBoundsLetterboxed = true
- appCompatTaskInfo.topActivityLetterboxWidth = 1600
- appCompatTaskInfo.topActivityLetterboxHeight = 1200
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+ )
+ @DisableFlags(
+ Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION,
+ Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PLATFORM_SIGNATURE,
+ )
+ fun handleRequest_topActivityTransparentWithDisplay_savedToDesktopRepository() {
+ val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ markTaskVisible(freeformTask)
+
+ val topTransparentTask =
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = DEFAULT_DISPLAY).apply {
+ isActivityStackTransparent = true
+ isTopActivityNoDisplay = false
+ numActivities = 1
+ baseActivity = ComponentName(/* pkg= */ "", /* cls= */ "")
+ }
+
+ val topTransparentTaskData =
+ DesktopRepository.TopTransparentFullscreenTaskData(
+ topTransparentTask.taskId,
+ topTransparentTask.token,
+ )
+ controller.handleRequest(Binder(), createTransition(topTransparentTask))
+ assertThat(taskRepository.getTopTransparentFullscreenTaskData(DEFAULT_DISPLAY))
+ .isEqualTo(topTransparentTaskData)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+ )
+ @DisableFlags(
+ Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION,
+ Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PLATFORM_SIGNATURE,
+ )
+ fun handleRequest_desktopNotShowing_topTransparentFullscreenTask_notSavedToDesktopRepository() {
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+
+ controller.handleRequest(Binder(), createTransition(task))
+ assertThat(taskRepository.getTopTransparentFullscreenTaskData(DEFAULT_DISPLAY)).isNull()
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+ )
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_onlyTopTransparentFullscreenTask_multiDesksDisabled_returnSwitchToFreeformWCT() {
+ val topTransparentTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+ taskRepository.setTopTransparentFullscreenTaskData(DEFAULT_DISPLAY, topTransparentTask)
+
+ val task = createFullscreenTask(displayId = DEFAULT_DISPLAY)
+
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_FORCE_CLOSE_TOP_TRANSPARENT_FULLSCREEN_TASK)
+ fun handleRequest_newTaskLaunch_topTransparentFullscreenTaskIdPassedToClear() {
+ val transition = Binder()
+ val topTransparentTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+ taskRepository.setTopTransparentFullscreenTaskData(DEFAULT_DISPLAY, topTransparentTask)
+
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ controller.handleRequest(transition, createTransition(task))
+
+ verify(desktopMixedTransitionHandler)
+ .addPendingMixedTransition(
+ DesktopMixedTransitionHandler.PendingMixedTransition.Launch(
+ transition,
+ task.taskId,
+ null,
+ topTransparentTask.taskId,
+ null,
+ )
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun handleRequest_desktopNotShowing_topTransparentFullscreenTask_returnNull() {
+ taskRepository.setDeskInactive(deskId = 0)
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+
+ assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun handleRequest_systemUIActivityWithDisplay_returnSwitchToFullscreenWCT() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ // Set task as systemUI package
+ val systemUIPackageName =
+ context.resources.getString(com.android.internal.R.string.config_systemUi)
+ val baseComponent = ComponentName(systemUIPackageName, /* cls= */ "")
+ val task =
+ setUpFreeformTask().apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = false
+ }
+
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ )
+ fun handleRequest_systemUIActivityWithDisplayInFreeformTask_inDesktop_tracksDeskDeactivation() {
+ val deskId = 5
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ val systemUIPackageName =
+ context.resources.getString(com.android.internal.R.string.config_systemUi)
+ val baseComponent = ComponentName(systemUIPackageName, /* cls= */ "")
+ val task =
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY).apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = false
+ }
+
+ val transition = Binder()
+ controller.handleRequest(transition, createTransition(task))
+
+ verify(desksTransitionsObserver)
+ .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_systemUIActivityWithoutDisplay_multiDesksDisabled_returnSwitchToFreeformWCT() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ // Set task as systemUI package
+ val systemUIPackageName =
+ context.resources.getString(com.android.internal.R.string.config_systemUi)
+ val baseComponent = ComponentName(systemUIPackageName, /* cls= */ "")
+ val task =
+ createFullscreenTask().apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = true
+ }
+
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun handleRequest_systemUIActivityWithoutDisplay_multiDesksEnabled_movesTaskToDesk() {
+ val deskId = 0
+ val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ markTaskVisible(freeformTask)
+
+ // Set task as systemUI package
+ val systemUIPackageName =
+ context.resources.getString(com.android.internal.R.string.config_systemUi)
+ val baseComponent = ComponentName(systemUIPackageName, /* cls= */ "")
+ val task =
+ createFullscreenTask(displayId = DEFAULT_DISPLAY).apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = true
+ }
+
+ val wct = controller.handleRequest(Binder(), createTransition(task))
+
+ assertNotNull(wct)
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId, task)
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun handleRequest_defaultHomePackageWithDisplay_returnSwitchToFullscreenWCT() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ val task =
+ setUpFullscreenTask().apply {
+ baseActivity = homeComponentName
+ isTopActivityNoDisplay = false
+ }
+
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun handleRequest_defaultHomePackageWithoutDisplay_returnSwitchToFreeformWCT() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ val task =
+ setUpFullscreenTask().apply {
+ baseActivity = homeComponentName
+ isTopActivityNoDisplay = false
+ }
+
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_systemUIActivityWithDisplay_returnSwitchToFullscreenWCT_enforcedDesktop() {
+ taskRepository.setDeskInactive(deskId = 0)
+ desktopState.enterDesktopByDefaultOnFreeformDisplay = true
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ // Set task as systemUI package
+ val systemUIPackageName =
+ context.resources.getString(com.android.internal.R.string.config_systemUi)
+ val baseComponent = ComponentName(systemUIPackageName, /* cls= */ "")
+ val task =
+ createFreeformTask().apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = false
+ }
+
+ assertThat(controller.isAnyDeskActive(DEFAULT_DISPLAY)).isFalse()
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_singleTaskNoToken_noWallpaper_doesNotHandle() {
+ val task = setUpFreeformTask()
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ )
+ @DisableFlags(Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE)
+ fun handleRequest_backTransition_singleTaskNoToken_withWallpaper_launchesHome() {
+ val task = setUpFreeformTask()
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ // Should launch home
+ assertNotNull(result, "Should handle request")
+ .assertPendingIntentAt(0, launchHomeIntent(DEFAULT_DISPLAY))
+ result!!.assertPendingIntentActivityOptionsLaunchDisplayIdAt(0, DEFAULT_DISPLAY)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE,
+ )
+ fun handleRequest_backTransition_singleTaskNoToken_withWallpaper_noChanges() {
+ val task = setUpFreeformTask()
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ // Should not have any change
+ result?.run { assertTrue(changes.isEmpty(), "Should not have changes") }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_singleTaskNoToken_withWallpaper_notInDesktop_doesNotHandle() {
+ val task = setUpFreeformTask()
+ markTaskHidden(task)
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ )
+ @DisableFlags(Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE)
+ fun handleRequest_backTransition_singleTaskNoToken_launchesHomes() {
+ val task = setUpFreeformTask()
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ // Should launch home
+ assertNotNull(result, "Should handle request")
+ .assertPendingIntentAt(0, launchHomeIntent(DEFAULT_DISPLAY))
+ result!!.assertPendingIntentActivityOptionsLaunchDisplayIdAt(0, DEFAULT_DISPLAY)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE,
+ )
+ fun handleRequest_backTransition_singleTaskNoToken_noChanges() {
+ val task = setUpFreeformTask()
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ // Should not have any change
+ result?.run { assertTrue(changes.isEmpty(), "Should not have changes") }
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_singleTaskWithToken_noWallpaper_doesNotHandle() {
+ val task = setUpFreeformTask()
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ )
+ @DisableFlags(Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE)
+ fun handleRequest_backTransition_singleTaskWithToken_removesWallpaper() {
+ val task = setUpFreeformTask()
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request")
+ .assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE,
+ )
+ fun handleRequest_backTransition_singleTaskWithToken_noChanges() {
+ val task = setUpFreeformTask()
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ // Should not have any change
+ result?.run { assertTrue(changes.isEmpty(), "Should not have changes") }
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_multipleTasks_noWallpaper_doesNotHandle() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_multipleTasks_doesNotHandle() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ )
+ @DisableFlags(Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE)
+ fun handleRequest_backTransition_multipleTasksSingleNonClosing_removesWallpaperAndTask() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+
+ taskRepository.addClosingTask(
+ displayId = DEFAULT_DISPLAY,
+ deskId = 0,
+ taskId = task2.taskId,
+ )
+ val result =
+ controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request")
+ .assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE,
+ )
+ fun handleRequest_backTransition_multipleTasksSingleNonClosing_noChanges() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+
+ taskRepository.addClosingTask(
+ displayId = DEFAULT_DISPLAY,
+ deskId = 0,
+ taskId = task2.taskId,
+ )
+ val result =
+ controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ // Should not have any change
+ result?.run { assertTrue(changes.isEmpty(), "Should not have changes") }
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ )
+ @DisableFlags(Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE)
+ fun handleRequest_backTransition_multipleTasksSingleNonMinimized_removesWallpaperAndTask() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+
+ taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result =
+ controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request")
+ .assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE,
+ )
+ fun handleRequest_backTransition_multipleTasksSingleNonMinimized_noChanges() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+
+ taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result =
+ controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ // Should not have any change
+ result?.run { assertTrue(changes.isEmpty(), "Should not have changes") }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_nonMinimizadTask_withWallpaper_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+
+ taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ // Task is being minimized so mark it as not visible.
+ taskRepository.updateTask(
+ displayId = DEFAULT_DISPLAY,
+ task2.taskId,
+ isVisible = false,
+ taskBounds = TASK_BOUNDS,
+ )
+ val result =
+ controller.handleRequest(Binder(), createTransition(task2, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_singleTaskNoToken_noWallpaper_doesNotHandle() {
+ val task = setUpFreeformTask()
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ )
+ @DisableFlags(Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE)
+ fun handleRequest_closeTransition_singleTaskNoToken_launchesHome() {
+ val task = setUpFreeformTask()
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ // Should launch home
+ assertNotNull(result, "Should handle request")
+ .assertPendingIntentAt(0, launchHomeIntent(DEFAULT_DISPLAY))
+ result!!.assertPendingIntentActivityOptionsLaunchDisplayIdAt(0, DEFAULT_DISPLAY)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE,
+ )
+ fun handleRequest_closeTransition_singleTaskNoToken_noChanges() {
+ val task = setUpFreeformTask()
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ // Should not have any change
+ result?.run { assertTrue(changes.isEmpty(), "Should not have changes") }
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ )
+ @DisableFlags(
+ Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION,
+ Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PLATFORM_SIGNATURE,
+ Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE,
+ )
+ fun handleRequest_closeTransition_singleTaskNoToken_secondaryDisplay_launchesHome() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ val task = setUpFreeformTask(displayId = SECOND_DISPLAY)
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ // Should launch home
+ assertNotNull(result, "Should handle request")
+ .assertPendingIntentAt(0, launchHomeIntent(SECOND_DISPLAY))
+ result!!.assertPendingIntentActivityOptionsLaunchDisplayIdAt(0, SECOND_DISPLAY)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE,
+ )
+ @DisableFlags(
+ Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION,
+ Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PLATFORM_SIGNATURE,
+ )
+ fun handleRequest_closeTransition_singleTaskNoToken_secondaryDisplay_noChanges() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ val task = setUpFreeformTask(displayId = SECOND_DISPLAY)
+ whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ // Should not have any change
+ result?.run { assertTrue(changes.isEmpty(), "Should not have changes") }
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_singleTaskWithToken_noWallpaper_doesNotHandle() {
+ val task = setUpFreeformTask()
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ )
+ @DisableFlags(Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE)
+ fun handleRequest_closeTransition_singleTaskWithToken_withWallpaper_removesWallpaper() {
+ val task = setUpFreeformTask()
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request")
+ .assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE,
+ )
+ fun handleRequest_closeTransition_singleTaskWithToken_withWallpaper_noChanges() {
+ val task = setUpFreeformTask()
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ // Should not have any change
+ result?.run { assertTrue(changes.isEmpty(), "Should not have changes") }
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ @DisableFlags(Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE)
+ fun handleRequest_closeTransition_onlyDesktopTask_deactivatesDesk() {
+ val task = setUpFreeformTask()
+
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ verify(desksOrganizer).deactivateDesk(any(), /* deskId= */ eq(0), skipReorder = eq(false))
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE,
+ )
+ fun handleRequest_closeTransition_onlyDesktopTask_dontDeactivateDesk() {
+ val task = setUpFreeformTask()
+
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ verifyNoInteractions(desksOrganizer)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ @DisableFlags(Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE)
+ fun handleRequest_closeTransition_onlyDesktopTask_addsDeactivatesDeskTransition() {
+ val transition = Binder()
+ val task = setUpFreeformTask()
+
+ controller.handleRequest(transition, createTransition(task, type = TRANSIT_CLOSE))
+
+ verify(desksTransitionsObserver)
+ .addPendingTransition(DeskTransition.DeactivateDesk(token = transition, deskId = 0))
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE,
+ )
+ fun handleRequest_closeTransition_onlyDesktopTask_dontAddDeactivatesDeskTransition() {
+ val transition = Binder()
+ val task = setUpFreeformTask()
+
+ controller.handleRequest(transition, createTransition(task, type = TRANSIT_CLOSE))
+
+ verifyNoInteractions(desksTransitionsObserver)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_multipleTasks_noWallpaper_doesNotHandle() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_multipleTasksFlagEnabled_doesNotHandle() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ )
+ @DisableFlags(Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE)
+ fun handleRequest_closeTransition_multipleTasksSingleNonClosing_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+
+ taskRepository.addClosingTask(
+ displayId = DEFAULT_DISPLAY,
+ deskId = 0,
+ taskId = task2.taskId,
+ )
+ val result =
+ controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request")
+ .assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE,
+ )
+ fun handleRequest_closeTransition_multipleTasksSingleNonClosing_noChanges() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+
+ taskRepository.addClosingTask(
+ displayId = DEFAULT_DISPLAY,
+ deskId = 0,
+ taskId = task2.taskId,
+ )
+ val result =
+ controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ // Should not have any change
+ result?.run { assertTrue(changes.isEmpty(), "Should not have changes") }
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ )
+ @DisableFlags(Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE)
+ fun handleRequest_closeTransition_multipleTasksSingleNonMinimized_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+
+ taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result =
+ controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request")
+ .assertReorderAt(index = 0, wallpaperToken, toTop = false)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE,
+ )
+ fun handleRequest_closeTransition_multipleTasksSingleNonMinimized_noChanges() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+
+ taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result =
+ controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ // Should not have any change
+ result?.run { assertTrue(changes.isEmpty(), "Should not have changes") }
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ )
+ fun handleRequest_toBackTransition_noActiveDesk_notHandled() {
+ taskRepository.setDeskInactive(deskId = 0)
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ )
+ @DisableFlags(Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE)
+ fun handleRequest_toBackTransition_minimizesTask() {
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ assertNotNull(result) { "Should handle request" }
+ verify(desksOrganizer).minimizeTask(result, deskId = 0, task)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE,
+ )
+ fun handleRequest_toBackTransition_minimizesTask_noChanges() {
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ // Should not have any change
+ result?.run { assertTrue(changes.isEmpty(), "Should not have changes") }
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ )
+ @DisableFlags(Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE)
+ fun handleRequest_toBackTransition_lastTask_deactivatesDesk() {
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+
+ val transition = Binder()
+ val result =
+ controller.handleRequest(transition, createTransition(task, type = TRANSIT_TO_BACK))
+
+ assertNotNull(result) { "Should handle request" }
+ verify(desksOrganizer).deactivateDesk(result, deskId = 0)
+ verify(desksTransitionsObserver)
+ .addPendingTransition(DeskTransition.DeactivateDesk(token = transition, deskId = 0))
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_EMPTY_DESK_ON_MINIMIZE,
+ )
+ fun handleRequest_toBackTransition_lastTask_noChanges() {
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+
+ val transition = Binder()
+ val result =
+ controller.handleRequest(transition, createTransition(task, type = TRANSIT_TO_BACK))
+
+ // Should not have any change
+ result?.run { assertTrue(changes.isEmpty(), "Should not have changes") }
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ )
+ fun handleRequest_toBackTransition_notLastTask_doesNotDeactivateDesk() {
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+
+ val transition = Binder()
+ controller.handleRequest(transition, createTransition(task, type = TRANSIT_TO_BACK))
+
+ verify(desksOrganizer, never())
+ .deactivateDesk(any(), deskId = eq(0), skipReorder = eq(false))
+ verify(desksTransitionsObserver, never())
+ .addPendingTransition(
+ argThat {
+ this is DeskTransition.RemoveDesk &&
+ this.token == transition &&
+ this.deskId == 0
+ }
+ )
+ }
+
+ @Test
+ fun handleRequest_freeformTask_displayDoesntHandleDesktop_returnNull() {
+ desktopState.overrideDesktopModeSupportPerDisplay[SECOND_DISPLAY] = false
+ val task1 = createFreeformTask(displayId = SECOND_DISPLAY)
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_OPEN))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_FIRST_FULLSCREEN_REFOCUS_BUGFIX,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun handleRequest_fullscreenTaskRelaunch_desktopFirst_returnNull() {
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ val task = setUpFullscreenTask()
+ // Deactivate desk as fullscreen task is visible on top.
+ taskRepository.getActiveDeskId(DEFAULT_DISPLAY)?.let { taskRepository.setDeskInactive(it) }
+
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_OPEN))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_FIRST_FULLSCREEN_REFOCUS_BUGFIX,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun handleRequest_backgroundFullscreenTaskRelaunch_desktopFirst_returnNull() {
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ val task = setUpFullscreenTask(visible = false)
+
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_OPEN))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_FIRST_FULLSCREEN_REFOCUS_BUGFIX,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun handleRequest_fullscreenTaskRelaunch_touchFirst_returnNull() {
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ val task = setUpFullscreenTask()
+ // Deactivate desk as fullscreen task is visible on top.
+ taskRepository.getActiveDeskId(DEFAULT_DISPLAY)?.let { taskRepository.setDeskInactive(it) }
+
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_OPEN))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_FIRST_FULLSCREEN_REFOCUS_BUGFIX,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun handleRequest_backgroundFullscreenTaskRelaunch_touchFirst_moveToDesk() {
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ val task = setUpFullscreenTask(visible = false)
+
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_OPEN))
+
+ verify(desksOrganizer).moveTaskToDesk(result!!, DEFAULT_DISPLAY, task)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun moveFocusedTaskToDesktop_noDisplayActivity_doesNothing() {
+ val task = setUpFullscreenTask().apply { isTopActivityNoDisplay = true }
+
+ controller.moveFocusedTaskToDesktop(task.taskId, transitionSource = UNKNOWN)
+ verifyEnterDesktopWCTNotExecuted()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun moveFocusedTaskToDesktop_transparentTask_doesNothing() {
+ val task =
+ setUpFullscreenTask().apply {
+ isActivityStackTransparent = true
+ numActivities = 1
+ }
+
+ controller.moveFocusedTaskToDesktop(task.taskId, transitionSource = UNKNOWN)
+ verifyEnterDesktopWCTNotExecuted()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun moveFocusedTaskToDesktop_systemUIActivity_doesNothing() {
+ // Set task as systemUI package
+ val systemUIPackageName =
+ context.resources.getString(com.android.internal.R.string.config_systemUi)
+ val baseComponent = ComponentName(systemUIPackageName, /* cls= */ "")
+ val task = setUpFullscreenTask().apply { baseActivity = baseComponent }
+
+ controller.moveFocusedTaskToDesktop(task.taskId, transitionSource = UNKNOWN)
+ verifyEnterDesktopWCTNotExecuted()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun moveFocusedTaskToDesktop_defaultHomePackage_doesNothing() {
+ val task = setUpFullscreenTask().apply { baseActivity = homeComponentName }
+
+ controller.moveFocusedTaskToDesktop(task.taskId, transitionSource = UNKNOWN)
+ verifyEnterDesktopWCTNotExecuted()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveFocusedTaskToDesktop_fullscreenTaskIsMovedToDesktop_multiDesksDisabled() {
+ val task1 = setUpFullscreenTask()
+ val task2 = setUpFullscreenTask()
+ val task3 = setUpFullscreenTask()
+
+ task1.isFocused = true
+ task2.isFocused = false
+ task3.isFocused = false
+
+ controller.moveFocusedTaskToDesktop(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ assertThat(wct.changes[task1.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveFocusedTaskToDesktop_fullscreenTaskIsMovedToDesktop_multiDesksEnabled() =
+ testScope.runTest {
+ val task1 = setUpFullscreenTask()
+ val task2 = setUpFullscreenTask()
+ val task3 = setUpFullscreenTask()
+
+ task1.isFocused = true
+ task2.isFocused = false
+ task3.isFocused = false
+
+ controller.moveFocusedTaskToDesktop(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+ runCurrent()
+
+ val wct = getLatestEnterDesktopWct()
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId = 0, task1)
}
- } else {
- appCompatTaskInfo.topActivityBoundsLetterboxed = false
- }
- if (deviceOrientation == ORIENTATION_LANDSCAPE) {
- configuration.windowConfiguration.appBounds =
- Rect(0, 0, DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT)
- } else {
- configuration.windowConfiguration.appBounds =
- Rect(0, 0, DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG)
- }
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveFocusedTaskToDesktop_splitScreenTaskIsMovedToDesktop_multiDesksDisabled() {
+ val task1 = setUpSplitScreenTask()
+ val task2 = setUpFullscreenTask()
+ val task3 = setUpFullscreenTask()
+ val task4 = setUpSplitScreenTask()
+
+ task1.isFocused = true
+ task2.isFocused = false
+ task3.isFocused = false
+ task4.isFocused = true
+
+ task4.parentTaskId = task1.taskId
+
+ controller.moveFocusedTaskToDesktop(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ assertThat(wct.changes[task4.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ verify(splitScreenController)
+ .prepareExitSplitScreen(
+ any(),
+ anyInt(),
+ eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE),
+ )
}
- whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
- runningTasks.add(task)
- return task
- }
- private fun setUpLandscapeDisplay() {
- whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_LONG)
- whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_SHORT)
- }
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveFocusedTaskToDesktop_splitScreenTaskIsMovedToDesktop_multiDesksEnabled() =
+ testScope.runTest {
+ val task1 = setUpSplitScreenTask()
+ val task2 = setUpFullscreenTask()
+ val task3 = setUpFullscreenTask()
+ val task4 = setUpSplitScreenTask()
- private fun setUpPortraitDisplay() {
- whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_SHORT)
- whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_LONG)
- }
+ task1.isFocused = true
+ task2.isFocused = false
+ task3.isFocused = false
+ task4.isFocused = true
- private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
- val task = createSplitScreenTask(displayId)
- whenever(splitScreenController.isTaskInSplitScreen(task.taskId)).thenReturn(true)
- whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
- runningTasks.add(task)
- return task
- }
+ task4.parentTaskId = task1.taskId
- private fun markTaskVisible(task: RunningTaskInfo) {
- desktopModeTaskRepository.updateVisibleFreeformTasks(
- task.displayId, task.taskId, visible = true)
- }
+ controller.moveFocusedTaskToDesktop(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+ runCurrent()
- private fun markTaskHidden(task: RunningTaskInfo) {
- desktopModeTaskRepository.updateVisibleFreeformTasks(
- task.displayId, task.taskId, visible = false)
- }
+ val wct = getLatestEnterDesktopWct()
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId = 0, task4)
+ verify(splitScreenController)
+ .prepareExitSplitScreen(
+ any(),
+ anyInt(),
+ eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE),
+ )
+ }
- private fun getLatestWct(
- @WindowManager.TransitionType type: Int = TRANSIT_OPEN,
- handlerClass: Class? = null
- ): WindowContainerTransaction {
- val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- if (ENABLE_SHELL_TRANSITIONS) {
- if (handlerClass == null) {
- verify(transitions).startTransition(eq(type), arg.capture(), isNull())
- } else {
- verify(transitions).startTransition(eq(type), arg.capture(), isA(handlerClass))
- }
- } else {
- verify(shellTaskOrganizer).applyTransaction(arg.capture())
+ @Test
+ fun moveFocusedTaskToFullscreen() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+
+ controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ assertThat(wct.changes[task2.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
}
- return arg.value
- }
- private fun getLatestToggleResizeDesktopTaskWct(): WindowContainerTransaction {
- val arg: ArgumentCaptor =
- ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- if (ENABLE_SHELL_TRANSITIONS) {
- verify(toggleResizeDesktopTaskTransitionHandler, atLeastOnce()).startTransition(capture(arg))
- } else {
- verify(shellTaskOrganizer).applyTransaction(capture(arg))
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
+ fun moveFocusedTaskToFullscreen_onlyVisibleNonMinimizedTask_removesWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ taskRepository.minimizeTask(DEFAULT_DISPLAY, task1.taskId)
+ taskRepository.updateTask(DEFAULT_DISPLAY, task3.taskId, isVisible = false, TASK_BOUNDS)
+
+ controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task2.token.asBinder()])
+ assertThat(taskChange.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+ wct.assertReorder(wallpaperToken, toTop = false)
}
- return arg.value
- }
- private fun getLatestEnterDesktopWct(): WindowContainerTransaction {
- val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- if (ENABLE_SHELL_TRANSITIONS) {
- verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any())
- } else {
- verify(shellTaskOrganizer).applyTransaction(arg.capture())
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
+ com.android.launcher3.Flags.FLAG_ENABLE_ALT_TAB_KQS_FLATENNING,
+ )
+ fun moveFocusedTaskToFullscreen_multipleVisibleTasks_doesNotRemoveWallpaperActivity() {
+ val homeTask = setUpHomeTask()
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task2.token.asBinder()])
+ assertThat(taskChange.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+ // Does not remove wallpaper activity, as desktop still has visible desktop tasks
+ assertThat(wct.hierarchyOps).hasSize(2)
+ // Moves home task behind the fullscreen task
+ wct.assertReorderAt(index = 0, homeTask.getToken(), toTop = true)
+ wct.assertReorderAt(index = 1, task2.getToken(), toTop = true)
}
- return arg.value
- }
- private fun getLatestDragToDesktopWct(): WindowContainerTransaction {
- val arg: ArgumentCaptor =
- ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- if (ENABLE_SHELL_TRANSITIONS) {
- verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(capture(arg))
- } else {
- verify(shellTaskOrganizer).applyTransaction(capture(arg))
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveFocusedTaskToFullscreen_multipleVisibleTasks_fullscreenOverHome_multiDesksEnabled() {
+ val homeTask = setUpHomeTask()
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task2.token.asBinder()])
+ assertThat(taskChange.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+ // Moves home task behind the fullscreen task
+ val homeReorderIndex = wct.indexOfReorder(homeTask, toTop = true)
+ val fullscreenReorderIndex = wct.indexOfReorder(task2, toTop = true)
+ assertThat(homeReorderIndex).isNotEqualTo(-1)
+ assertThat(fullscreenReorderIndex).isNotEqualTo(-1)
+ assertThat(fullscreenReorderIndex).isGreaterThan(homeReorderIndex)
}
- return arg.value
- }
- private fun getLatestExitDesktopWct(): WindowContainerTransaction {
- val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- if (ENABLE_SHELL_TRANSITIONS) {
- verify(exitDesktopTransitionHandler).startTransition(any(), arg.capture(), any(), any())
- } else {
- verify(shellTaskOrganizer).applyTransaction(arg.capture())
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeDesk_multipleTasks_removesAll() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
+
+ controller.removeDefaultDeskInDisplay(displayId = DEFAULT_DISPLAY)
+
+ val wct = getLatestWct(TRANSIT_CLOSE)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ wct.assertRemoveAt(index = 0, task1.token)
+ wct.assertRemoveAt(index = 1, task2.token)
+ wct.assertRemoveAt(index = 2, task3.token)
}
- return arg.value
- }
- private fun findBoundsChange(wct: WindowContainerTransaction, task: RunningTaskInfo): Rect? =
- wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeDesk_multipleTasksWithBackgroundTask_removesAll() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task3.taskId)).thenReturn(null)
- private fun verifyWCTNotExecuted() {
- if (ENABLE_SHELL_TRANSITIONS) {
- verify(transitions, never()).startTransition(anyInt(), any(), isNull())
- } else {
- verify(shellTaskOrganizer, never()).applyTransaction(any())
+ controller.removeDefaultDeskInDisplay(displayId = DEFAULT_DISPLAY)
+
+ val wct = getLatestWct(TRANSIT_CLOSE)
+ assertThat(wct.hierarchyOps).hasSize(2)
+ wct.assertRemoveAt(index = 0, task1.token)
+ wct.assertRemoveAt(index = 1, task2.token)
+ verify(recentTasksController).removeBackgroundTask(task3.taskId)
}
- }
- private fun verifyExitDesktopWCTNotExecuted() {
- if (ENABLE_SHELL_TRANSITIONS) {
- verify(exitDesktopTransitionHandler, never()).startTransition(any(), any(), any(), any())
- } else {
- verify(shellTaskOrganizer, never()).applyTransaction(any())
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun removeDesk_multipleDesks_addsPendingTransition() {
+ val transition = Binder()
+ whenever(transitions.startTransition(eq(TRANSIT_CLOSE), any(), anyOrNull()))
+ .thenReturn(transition)
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 2)
+
+ controller.removeDesk(deskId = 2)
+
+ verify(desksOrganizer).removeDesk(any(), eq(2), any())
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ argThat {
+ this is DeskTransition.RemoveDesk &&
+ this.token == transition &&
+ this.deskId == 2
+ }
+ )
}
- }
- private fun verifyEnterDesktopWCTNotExecuted() {
- if (ENABLE_SHELL_TRANSITIONS) {
- verify(enterDesktopTransitionHandler, never()).moveToDesktop(any(), any())
- } else {
- verify(shellTaskOrganizer, never()).applyTransaction(any())
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun removeDesk_multipleDesks_removesRunningTasks() {
+ val transition = Binder()
+ whenever(transitions.startTransition(eq(TRANSIT_CLOSE), any(), anyOrNull()))
+ .thenReturn(transition)
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 2)
+ val task1 = setUpFreeformTask(deskId = 2)
+ val task2 = setUpFreeformTask(deskId = 2)
+ val task3 = setUpFreeformTask(deskId = 2)
+
+ controller.removeDesk(deskId = 2)
+
+ val wct = getLatestWct(TRANSIT_CLOSE)
+ wct.assertRemove(task1.token)
+ wct.assertRemove(task2.token)
+ wct.assertRemove(task3.token)
}
- }
- private fun createTransition(
- task: RunningTaskInfo?,
- @WindowManager.TransitionType type: Int = TRANSIT_OPEN
- ): TransitionRequestInfo {
- return TransitionRequestInfo(type, task, null /* remoteTransition */)
- }
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun removeDesk_multipleDesks_removesRecentTasks() {
+ val transition = Binder()
+ whenever(transitions.startTransition(eq(TRANSIT_CLOSE), any(), anyOrNull()))
+ .thenReturn(transition)
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 2)
+ val task1 = setUpFreeformTask(deskId = 2, background = true)
+ val task2 = setUpFreeformTask(deskId = 2, background = true)
+ val task3 = setUpFreeformTask(deskId = 2, background = true)
- companion object {
- const val SECOND_DISPLAY = 2
- private val STABLE_BOUNDS = Rect(0, 0, 1000, 1000)
- }
+ controller.removeDesk(deskId = 2)
+
+ verify(recentTasksController).removeBackgroundTask(task1.taskId)
+ verify(recentTasksController).removeBackgroundTask(task2.taskId)
+ verify(recentTasksController).removeBackgroundTask(task3.taskId)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun activateDesk_multipleDesks_addsPendingTransition() {
+ val deskId = 0
+ val transition = Binder()
+ val deskChange = mock(TransitionInfo.Change::class.java)
+ whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
+ .thenReturn(transition)
+ whenever(desksOrganizer.isDeskActiveAtEnd(deskChange, deskId)).thenReturn(true)
+ // Make desk inactive by activating another desk.
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 1)
+ taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId = 1)
+
+ controller.activateDesk(deskId, RemoteTransition(TestRemoteTransition()))
+
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ argThat {
+ this is DeskTransition.ActivateDesk &&
+ this.token == transition &&
+ this.deskId == 0
+ }
+ )
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun activateDesk_hasNonRunningTask_startsTask() {
+ val deskId = 0
+ val nonRunningTask =
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0, background = true)
+
+ val transition = Binder()
+ val deskChange = mock(TransitionInfo.Change::class.java)
+ whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
+ .thenReturn(transition)
+ whenever(desksOrganizer.isDeskActiveAtEnd(deskChange, deskId)).thenReturn(true)
+ // Make desk inactive by activating another desk.
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 1)
+ taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId = 1)
+
+ controller.activateDesk(deskId, RemoteTransition(TestRemoteTransition()))
+
+ val wct = getLatestWct(TRANSIT_TO_FRONT, OneShotRemoteHandler::class.java)
+ assertNotNull(wct)
+ wct.assertLaunchTask(nonRunningTask.taskId, WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun activateDesk_hasRunningTask_reordersTask() {
+ val deskId = 0
+ val runningTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+
+ val transition = Binder()
+ val deskChange = mock(TransitionInfo.Change::class.java)
+ whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
+ .thenReturn(transition)
+ whenever(desksOrganizer.isDeskActiveAtEnd(deskChange, deskId)).thenReturn(true)
+ // Make desk inactive by activating another desk.
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 1)
+ taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId = 1)
+
+ controller.activateDesk(deskId, RemoteTransition(TestRemoteTransition()))
+
+ verify(desksOrganizer).reorderTaskToFront(any(), eq(deskId), eq(runningTask))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun activateDesk_otherDeskWasActive_deactivatesOtherDesk() {
+ val previouslyActiveDeskId = 1
+ val activatingDeskId = 0
+ val transition = Binder()
+ val deskChange = mock(TransitionInfo.Change::class.java)
+ whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
+ .thenReturn(transition)
+ whenever(desksOrganizer.isDeskActiveAtEnd(deskChange, activatingDeskId)).thenReturn(true)
+ // Make desk inactive by activating another desk.
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = previouslyActiveDeskId)
+ taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId = previouslyActiveDeskId)
+
+ controller.activateDesk(activatingDeskId, RemoteTransition(TestRemoteTransition()))
+
+ verify(desksOrganizer)
+ .deactivateDesk(any(), eq(previouslyActiveDeskId), skipReorder = eq(false))
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ argThat {
+ this is DeskTransition.DeactivateDesk &&
+ this.token == transition &&
+ this.deskId == previouslyActiveDeskId
+ }
+ )
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_KEYBOARD_SHORTCUTS_TO_SWITCH_DESKS,
+ )
+ fun activatePreviousDesk_activates() {
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = 1)
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = 2)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 2)
+
+ controller.activatePreviousDesk(DEFAULT_DISPLAY)
+
+ verify(desksOrganizer).activateDesk(any(), eq(1), skipReorder = eq(false))
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_KEYBOARD_SHORTCUTS_TO_SWITCH_DESKS,
+ )
+ fun activateNextDesk_activates() {
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = 1)
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = 2)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 1)
+
+ controller.activateNextDesk(DEFAULT_DISPLAY)
+
+ verify(desksOrganizer).activateDesk(any(), eq(2), skipReorder = eq(false))
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_KEYBOARD_SHORTCUTS_TO_SWITCH_DESKS,
+ )
+ fun activatePreviousDesk_deskDoesNotExist_doesNotActivate() {
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+
+ controller.activatePreviousDesk(DEFAULT_DISPLAY)
+
+ verify(desksOrganizer, never()).activateDesk(any(), any(), skipReorder = any())
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_KEYBOARD_SHORTCUTS_TO_SWITCH_DESKS,
+ )
+ fun activateNextDesk_deskDoesNotExist_doesNotActivate() {
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = 1)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 1)
+
+ controller.activateNextDesk(DEFAULT_DISPLAY)
+
+ verify(desksOrganizer, never()).activateDesk(any(), any(), skipReorder = any())
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_KEYBOARD_SHORTCUTS_TO_SWITCH_DESKS,
+ )
+ fun activatePreviousDesk_noDeskActive_doesNotActivate() {
+ taskRepository.setDeskInactive(deskId = 0)
+
+ controller.activatePreviousDesk(DEFAULT_DISPLAY)
+
+ verify(desksOrganizer, never()).activateDesk(any(), any(), any())
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_KEYBOARD_SHORTCUTS_TO_SWITCH_DESKS,
+ )
+ fun activateNextDesk_noDeskActive_doesNotActivate() {
+ taskRepository.setDeskInactive(deskId = 0)
+
+ controller.activateNextDesk(DEFAULT_DISPLAY)
+
+ verify(desksOrganizer, never()).activateDesk(any(), any(), any())
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_KEYBOARD_SHORTCUTS_TO_SWITCH_DESKS,
+ )
+ fun activatePreviousDesk_invalidDisplay_activatesFocusedDisplayDesk() {
+ val focusedDisplayId = 5
+ whenever(focusTransitionObserver.globallyFocusedDisplayId).thenReturn(focusedDisplayId)
+ taskRepository.addDesk(displayId = 5, deskId = 1)
+ taskRepository.addDesk(displayId = 5, deskId = 2)
+ taskRepository.setActiveDesk(displayId = 5, deskId = 2)
+ taskRepository.addDesk(displayId = 6, deskId = 3)
+ taskRepository.addDesk(displayId = 6, deskId = 4)
+ taskRepository.setActiveDesk(displayId = 6, deskId = 4)
+
+ controller.activatePreviousDesk(INVALID_DISPLAY)
+
+ verify(desksOrganizer).activateDesk(any(), eq(1), skipReorder = eq(false))
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_KEYBOARD_SHORTCUTS_TO_SWITCH_DESKS,
+ )
+ fun activateNextDesk_invalidDisplay_activatesFocusedDisplayDesk() {
+ val focusedDisplayId = 6
+ whenever(focusTransitionObserver.globallyFocusedDisplayId).thenReturn(focusedDisplayId)
+ taskRepository.addDesk(displayId = 5, deskId = 1)
+ taskRepository.addDesk(displayId = 5, deskId = 2)
+ taskRepository.setActiveDesk(displayId = 5, deskId = 1)
+ taskRepository.addDesk(displayId = 6, deskId = 3)
+ taskRepository.addDesk(displayId = 6, deskId = 4)
+ taskRepository.setActiveDesk(displayId = 6, deskId = 3)
+
+ controller.activateNextDesk(INVALID_DISPLAY)
+
+ verify(desksOrganizer).activateDesk(any(), eq(4), skipReorder = eq(false))
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun moveTaskToDesk_multipleDesks_addsPendingTransition() {
+ val transition = Binder()
+ whenever(enterDesktopTransitionHandler.moveToDesktop(any(), any())).thenReturn(transition)
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 3)
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+ task.isVisible = true
+
+ controller.moveTaskToDesk(taskId = task.taskId, deskId = 3, transitionSource = UNKNOWN)
+
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ argThat {
+ this is DeskTransition.ActivateDeskWithTask &&
+ this.token == transition &&
+ this.deskId == 3 &&
+ this.enterTaskId == task.taskId
+ }
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask()
+ setUpLandscapeDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(
+ DEFAULT_DISPLAY,
+ PointF(800f, 1280f),
+ task,
+ mockSurface,
+ )
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() {
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
+ setUpLandscapeDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(
+ DEFAULT_DISPLAY,
+ PointF(800f, 1280f),
+ task,
+ mockSurface,
+ )
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() {
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task =
+ setUpFullscreenTask(
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
+ shouldLetterbox = true,
+ )
+ setUpLandscapeDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(
+ DEFAULT_DISPLAY,
+ PointF(800f, 1280f),
+ task,
+ mockSurface,
+ )
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() {
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task =
+ setUpFullscreenTask(
+ isResizable = false,
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE,
+ )
+ setUpLandscapeDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(
+ DEFAULT_DISPLAY,
+ PointF(800f, 1280f),
+ task,
+ mockSurface,
+ )
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() {
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task =
+ setUpFullscreenTask(
+ isResizable = false,
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
+ shouldLetterbox = true,
+ )
+ setUpLandscapeDisplay()
+ spyController.onDragPositioningEndThroughStatusBar(
+ DEFAULT_DISPLAY,
+ PointF(800f, 1280f),
+ task,
+ mockSurface,
+ )
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() {
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT)
+ setUpPortraitDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(
+ DEFAULT_DISPLAY,
+ PointF(800f, 1280f),
+ task,
+ mockSurface,
+ )
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() {
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task =
+ setUpFullscreenTask(
+ deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
+ )
+ setUpPortraitDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(
+ DEFAULT_DISPLAY,
+ PointF(800f, 1280f),
+ task,
+ mockSurface,
+ )
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() {
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task =
+ setUpFullscreenTask(
+ deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE,
+ shouldLetterbox = true,
+ )
+ setUpPortraitDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(
+ DEFAULT_DISPLAY,
+ PointF(800f, 1280f),
+ task,
+ mockSurface,
+ )
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() {
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task =
+ setUpFullscreenTask(
+ isResizable = false,
+ deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
+ )
+ setUpPortraitDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(
+ DEFAULT_DISPLAY,
+ PointF(800f, 1280f),
+ task,
+ mockSurface,
+ )
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() {
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task =
+ setUpFullscreenTask(
+ isResizable = false,
+ deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE,
+ shouldLetterbox = true,
+ )
+ setUpPortraitDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(
+ DEFAULT_DISPLAY,
+ PointF(200f, 200f),
+ task,
+ mockSurface,
+ )
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun dragToDesktop_movesTaskToDesk() {
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+
+ spyController.onDragPositioningEndThroughStatusBar(
+ DEFAULT_DISPLAY,
+ PointF(200f, 200f),
+ task,
+ mockSurface,
+ )
+
+ val wct = getLatestDragToDesktopWct()
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId = 0, task)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun dragToDesktop_activatesDesk() {
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+
+ spyController.onDragPositioningEndThroughStatusBar(
+ DEFAULT_DISPLAY,
+ PointF(200f, 200f),
+ task,
+ mockSurface,
+ )
+
+ val wct = getLatestDragToDesktopWct()
+ verify(desksOrganizer).activateDesk(wct, deskId = 0)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun dragToDesktop_triggersEnterDesktopListener() {
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+
+ spyController.onDragPositioningEndThroughStatusBar(
+ DEFAULT_DISPLAY,
+ PointF(200f, 200f),
+ task,
+ mockSurface,
+ )
+
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(TO_DESKTOP_ANIM_DURATION)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun dragToDesktop_multipleDesks_addsPendingTransition() {
+ val transition = Binder()
+ val spyController = spy(controller)
+ whenever(dragToDesktopTransitionHandler.finishDragToDesktopTransition(any()))
+ .thenReturn(transition)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+
+ spyController.onDragPositioningEndThroughStatusBar(
+ DEFAULT_DISPLAY,
+ PointF(200f, 200f),
+ task,
+ mockSurface,
+ )
+
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ argThat {
+ this is DeskTransition.ActivateDeskWithTask &&
+ this.token == transition &&
+ this.deskId == 0 &&
+ this.enterTaskId == task.taskId
+ }
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
+ fun onDesktopDragMove_callVisualIndicatorUpdateScheduler() {
+ val task = setUpFreeformTask()
+ val spyController = spy(controller)
+ val mockSurface = mock(SurfaceControl::class.java)
+ val mockDisplayLayout = mock(DisplayLayout::class.java)
+ val indicatorType = DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR
+ whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
+ val inputX = 200f
+ val inputY = 10f
+ val bounds = Rect(100, -100, 500, 1000)
+ doReturn(desktopModeVisualIndicator)
+ .whenever(spyController)
+ .getOrCreateVisualIndicator(
+ eq(task),
+ eq(mockSurface),
+ eq(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM),
+ )
+ whenever(
+ desktopModeVisualIndicator.calculateIndicatorType(
+ eq(DEFAULT_DISPLAY),
+ eq(PointF(inputX, bounds.top.toFloat())),
+ )
+ )
+ .thenReturn(indicatorType)
+
+ spyController.onDragPositioningMove(
+ task,
+ mockSurface,
+ DEFAULT_DISPLAY,
+ inputX,
+ inputY,
+ bounds,
+ )
+
+ verify(visualIndicatorUpdateScheduler)
+ .schedule(
+ eq(task.displayId),
+ eq(indicatorType),
+ eq(inputX),
+ eq(inputY),
+ eq(bounds),
+ any(),
+ )
+ }
+
+ @Test
+ fun onDesktopDragMove_endsOutsideValidDragArea_snapsToValidBounds() {
+ val task = setUpFreeformTask()
+ val spyController = spy(controller)
+ val mockSurface = mock(SurfaceControl::class.java)
+ val mockDisplayLayout = mock(DisplayLayout::class.java)
+ whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
+ spyController.onDragPositioningMove(
+ task,
+ mockSurface,
+ DEFAULT_DISPLAY,
+ 200f,
+ 10f,
+ Rect(100, -100, 500, 1000),
+ )
+
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
+ spyController.onDragPositioningEnd(
+ task,
+ mockSurface,
+ DEFAULT_DISPLAY,
+ inputCoordinate = PointF(200f, -200f),
+ currentDragBounds = Rect(100, -100, 500, 1000),
+ validDragArea = Rect(0, 50, 2000, 2000),
+ dragStartBounds = Rect(),
+ motionEvent,
+ )
+ val rectAfterEnd = Rect(100, 50, 500, 1150)
+ verify(transitions)
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ Mockito.argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ change.configuration.windowConfiguration.bounds == rectAfterEnd
+ }
+ },
+ eq(null),
+ )
+ }
+
+ @Test
+ fun onDesktopDragEnd_noIndicator_updatesTaskBounds() {
+ val task = setUpFreeformTask()
+ val spyController = spy(controller)
+ val mockSurface = mock(SurfaceControl::class.java)
+ val mockDisplayLayout = mock(DisplayLayout::class.java)
+ whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
+ spyController.onDragPositioningMove(
+ task,
+ mockSurface,
+ DEFAULT_DISPLAY,
+ 200f,
+ 10f,
+ Rect(100, 200, 500, 1000),
+ )
+
+ val currentDragBounds = Rect(100, 200, 500, 1000)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
+
+ spyController.onDragPositioningEnd(
+ task,
+ mockSurface,
+ DEFAULT_DISPLAY,
+ inputCoordinate = PointF(200f, 300f),
+ currentDragBounds = currentDragBounds,
+ validDragArea = Rect(0, 50, 2000, 2000),
+ dragStartBounds = Rect(),
+ motionEvent,
+ )
+
+ verify(transitions)
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ Mockito.argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ change.configuration.windowConfiguration.bounds == currentDragBounds
+ }
+ },
+ eq(null),
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onDesktopDragEnd_noIndicatorAndMoveToNewDisplay_reparent() {
+ val task = setUpFreeformTask()
+ val spyController = spy(controller)
+ val mockSurface = mock(SurfaceControl::class.java)
+ val mockDisplayLayout = mock(DisplayLayout::class.java)
+ whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
+ spyController.onDragPositioningMove(
+ task,
+ mockSurface,
+ DEFAULT_DISPLAY,
+ 200f,
+ 10f,
+ Rect(100, 200, 500, 1000),
+ )
+
+ val currentDragBounds = Rect(100, 200, 500, 1000)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
+ whenever(motionEvent.displayId).thenReturn(SECONDARY_DISPLAY_ID)
+
+ spyController.onDragPositioningEnd(
+ task,
+ mockSurface,
+ DEFAULT_DISPLAY,
+ inputCoordinate = PointF(200f, 300f),
+ currentDragBounds,
+ validDragArea = Rect(0, 50, 2000, 2000),
+ dragStartBounds = Rect(),
+ motionEvent,
+ )
+
+ verify(transitions)
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ Mockito.argThat { wct ->
+ return@argThat wct.hierarchyOps[0].isReparent
+ },
+ eq(dragToDisplayTransitionHandler),
+ )
+ }
+
+ @Test
+ fun onDesktopDragEnd_fullscreenIndicator_dragToExitDesktop() {
+ val task = setUpFreeformTask(bounds = Rect(0, 0, 100, 100))
+ val spyController = spy(controller)
+ val mockSurface = mock(SurfaceControl::class.java)
+ val mockDisplayLayout = mock(DisplayLayout::class.java)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
+ whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ }
+ desktopConfig.shouldMaximizeWhenDragToTopEdge = false
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
+
+ // Drag move the task to the top edge
+ spyController.onDragPositioningMove(
+ task,
+ mockSurface,
+ DEFAULT_DISPLAY,
+ 200f,
+ 10f,
+ Rect(100, 200, 500, 1000),
+ )
+ spyController.onDragPositioningEnd(
+ task,
+ mockSurface,
+ DEFAULT_DISPLAY,
+ inputCoordinate = PointF(200f, 300f),
+ currentDragBounds = Rect(100, 50, 500, 1000),
+ validDragArea = Rect(0, 50, 2000, 2000),
+ dragStartBounds = Rect(),
+ motionEvent,
+ )
+
+ // Assert the task exits desktop mode
+ val wct = getLatestExitDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_FIRST_BASED_DRAG_TO_MAXIMIZE)
+ fun onDesktopDragEnd_fullscreenIndicator_dragToMaximize_desktopFirstDisabled() {
+ val task = setUpFreeformTask(bounds = Rect(0, 0, 100, 100))
+ val spyController = spy(controller)
+ val mockSurface = mock(SurfaceControl::class.java)
+ val mockDisplayLayout = mock(DisplayLayout::class.java)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
+ whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ }
+ desktopConfig.shouldMaximizeWhenDragToTopEdge = true
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
+
+ // Drag move the task to the top edge
+ val currentDragBounds = Rect(100, 50, 500, 1000)
+ spyController.onDragPositioningMove(
+ task,
+ mockSurface,
+ DEFAULT_DISPLAY,
+ 200f,
+ 10f,
+ Rect(100, 200, 500, 1000),
+ )
+ spyController.onDragPositioningEnd(
+ task,
+ mockSurface,
+ DEFAULT_DISPLAY,
+ inputCoordinate = PointF(200f, 300f),
+ currentDragBounds,
+ validDragArea = Rect(0, 50, 2000, 2000),
+ dragStartBounds = Rect(),
+ motionEvent,
+ )
+
+ // Assert bounds set to stable bounds
+ val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds)
+ assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS)
+ // Assert event is properly logged
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingStarted(
+ ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER,
+ InputMethod.UNKNOWN_INPUT_METHOD,
+ task,
+ task.configuration.windowConfiguration.bounds.width(),
+ task.configuration.windowConfiguration.bounds.height(),
+ displayController,
+ )
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER,
+ InputMethod.UNKNOWN_INPUT_METHOD,
+ task,
+ STABLE_BOUNDS.width(),
+ STABLE_BOUNDS.height(),
+ displayController,
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_FIRST_BASED_DRAG_TO_MAXIMIZE)
+ fun onDesktopDragEnd_fullscreenIndicator_dragToMaximize_alreadyMaximized_noBoundsChange_desktopFirstDisabled() {
+ val task = setUpFreeformTask(bounds = STABLE_BOUNDS)
+ val spyController = spy(controller)
+ val mockSurface = mock(SurfaceControl::class.java)
+ val mockDisplayLayout = mock(DisplayLayout::class.java)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
+ whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ }
+ desktopConfig.shouldMaximizeWhenDragToTopEdge = true
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
+
+ // Drag move the task to the top edge
+ val currentDragBounds = Rect(100, 50, 500, 1000)
+ spyController.onDragPositioningMove(
+ task,
+ mockSurface,
+ DEFAULT_DISPLAY,
+ 200f,
+ 10f,
+ Rect(100, 200, 500, 1000),
+ )
+ spyController.onDragPositioningEnd(
+ task,
+ mockSurface,
+ DEFAULT_DISPLAY,
+ inputCoordinate = PointF(200f, 300f),
+ currentDragBounds = currentDragBounds,
+ validDragArea = Rect(0, 50, 2000, 2000),
+ dragStartBounds = Rect(),
+ motionEvent,
+ )
+
+ // Assert that task is NOT updated via WCT
+ verify(toggleResizeDesktopTaskTransitionHandler, never())
+ .startTransition(any(), any(), any())
+ // Assert that task leash is updated via Surface Animations
+ verify(mReturnToDragStartAnimator)
+ .start(
+ eq(task.taskId),
+ eq(mockSurface),
+ eq(currentDragBounds),
+ eq(STABLE_BOUNDS),
+ anyOrNull(),
+ )
+ // Assert no event is logged
+ verify(desktopModeEventLogger, never())
+ .logTaskResizingStarted(any(), any(), any(), any(), any(), any(), any())
+ verify(desktopModeEventLogger, never())
+ .logTaskResizingEnded(any(), any(), any(), any(), any(), any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_FIRST_BASED_DRAG_TO_MAXIMIZE)
+ fun onDesktopDragEnd_fullscreenIndicator_dragToMaximize() {
+ val task = setUpFreeformTask(bounds = Rect(0, 0, 100, 100))
+ val spyController = spy(controller)
+ val mockSurface = mock(SurfaceControl::class.java)
+ val mockDisplayLayout = mock(DisplayLayout::class.java)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
+ whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ }
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
+ whenever(motionEvent.displayId).thenReturn(DEFAULT_DISPLAY)
+
+ // Drag move the task to the top edge
+ val currentDragBounds = Rect(100, 50, 500, 1000)
+ spyController.onDragPositioningMove(
+ task,
+ mockSurface,
+ DEFAULT_DISPLAY,
+ 200f,
+ 10f,
+ Rect(100, 200, 500, 1000),
+ )
+ spyController.onDragPositioningEnd(
+ task,
+ mockSurface,
+ DEFAULT_DISPLAY,
+ inputCoordinate = PointF(200f, 300f),
+ currentDragBounds,
+ validDragArea = Rect(0, 50, 2000, 2000),
+ dragStartBounds = Rect(),
+ motionEvent,
+ )
+
+ // Assert bounds set to stable bounds
+ val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds)
+ assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS)
+ // Assert event is properly logged
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingStarted(
+ ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER,
+ InputMethod.UNKNOWN_INPUT_METHOD,
+ task,
+ task.configuration.windowConfiguration.bounds.width(),
+ task.configuration.windowConfiguration.bounds.height(),
+ displayController,
+ )
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER,
+ InputMethod.UNKNOWN_INPUT_METHOD,
+ task,
+ STABLE_BOUNDS.width(),
+ STABLE_BOUNDS.height(),
+ displayController,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_FIRST_BASED_DRAG_TO_MAXIMIZE)
+ fun onDesktopDragEnd_fullscreenIndicator_dragToMaximize_alreadyMaximized_noBoundsChange() {
+ val task = setUpFreeformTask(bounds = STABLE_BOUNDS)
+ val spyController = spy(controller)
+ val mockSurface = mock(SurfaceControl::class.java)
+ val mockDisplayLayout = mock(DisplayLayout::class.java)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
+ whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ }
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(any(), anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
+ whenever(motionEvent.displayId).thenReturn(DEFAULT_DISPLAY)
+
+ // Drag move the task to the top edge
+ val currentDragBounds = Rect(100, 50, 500, 1000)
+ spyController.onDragPositioningMove(
+ task,
+ mockSurface,
+ DEFAULT_DISPLAY,
+ 200f,
+ 10f,
+ Rect(100, 200, 500, 1000),
+ )
+ spyController.onDragPositioningEnd(
+ task,
+ mockSurface,
+ DEFAULT_DISPLAY,
+ inputCoordinate = PointF(200f, 300f),
+ currentDragBounds = currentDragBounds,
+ validDragArea = Rect(0, 50, 2000, 2000),
+ dragStartBounds = Rect(),
+ motionEvent,
+ )
+
+ // Assert that task is NOT updated via WCT
+ verify(toggleResizeDesktopTaskTransitionHandler, never())
+ .startTransition(any(), any(), any())
+ // Assert that task leash is updated via Surface Animations
+ verify(mReturnToDragStartAnimator)
+ .start(
+ eq(task.taskId),
+ eq(mockSurface),
+ eq(currentDragBounds),
+ eq(STABLE_BOUNDS),
+ anyOrNull(),
+ )
+ // Assert no event is logged
+ verify(desktopModeEventLogger, never())
+ .logTaskResizingStarted(any(), any(), any(), any(), any(), any(), any())
+ verify(desktopModeEventLogger, never())
+ .logTaskResizingEnded(any(), any(), any(), any(), any(), any(), any())
+ }
+
+ @Test
+ fun enterSplit_freeformTaskIsMovedToSplit() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+
+ controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
+
+ verify(splitScreenController)
+ .requestEnterSplitSelect(
+ eq(task2),
+ eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
+ eq(task2.configuration.windowConfiguration.bounds),
+ /* startRecents= */ eq(true),
+ /* withRecentsWct= */ any(),
+ )
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
+ )
+ fun enterSplit_multipleVisibleNonMinimizedTasks_removesWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+
+ controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
+
+ val wctArgument = argumentCaptor()
+ verify(splitScreenController)
+ .requestEnterSplitSelect(
+ eq(task2),
+ eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
+ eq(task2.configuration.windowConfiguration.bounds),
+ /* startRecents= */ eq(true),
+ wctArgument.capture(),
+ )
+ // Does not remove wallpaper activity, as desktop still has visible desktop tasks
+ assertThat(wctArgument.firstValue.hierarchyOps).isEmpty()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun newWindow_fromFullscreenOpensInSplit() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask()
+ val optionsCaptor = argumentCaptor()
+ runOpenNewWindow(task)
+ verify(splitScreenController)
+ .startIntent(
+ any(),
+ anyInt(),
+ any(),
+ any(),
+ optionsCaptor.capture(),
+ anyOrNull(),
+ eq(true),
+ eq(SPLIT_INDEX_UNDEFINED),
+ eq(DEFAULT_DISPLAY),
+ )
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.firstValue).launchWindowingMode)
+ .isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun newWindow_fromSplitOpensInSplit() {
+ setUpLandscapeDisplay()
+ val task = setUpSplitScreenTask()
+ val optionsCaptor = argumentCaptor()
+ runOpenNewWindow(task)
+ verify(splitScreenController)
+ .startIntent(
+ any(),
+ anyInt(),
+ any(),
+ any(),
+ optionsCaptor.capture(),
+ anyOrNull(),
+ eq(true),
+ eq(SPLIT_INDEX_UNDEFINED),
+ eq(DEFAULT_DISPLAY),
+ )
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.firstValue).launchWindowingMode)
+ .isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun newWindow_fromFreeformAddsNewWindow() {
+ setUpLandscapeDisplay()
+ val task = setUpFreeformTask()
+ val wctCaptor = argumentCaptor()
+ val transition = Binder()
+ whenever(
+ mMockDesktopImmersiveController.exitImmersiveIfApplicable(
+ any(),
+ anyInt(),
+ anyOrNull(),
+ any(),
+ )
+ )
+ .thenReturn(ExitResult.NoExit)
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ anyInt(),
+ any(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(transition)
+
+ runOpenNewWindow(task)
+
+ verify(desktopMixedTransitionHandler)
+ .startLaunchTransition(
+ anyInt(),
+ wctCaptor.capture(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ assertThat(
+ ActivityOptions.fromBundle(wctCaptor.firstValue.hierarchyOps[0].launchOptions)
+ .launchWindowingMode
+ )
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY,
+ )
+ fun newWindow_fromFreeformAddsNewWindow_launchesToCallingDisplay() {
+ setUpLandscapeDisplay()
+ val deskId = 2
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = deskId)
+ taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = deskId)
+ val task = setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = deskId)
+ val wctCaptor = argumentCaptor()
+ val transition = Binder()
+ whenever(
+ mMockDesktopImmersiveController.exitImmersiveIfApplicable(
+ any(),
+ anyInt(),
+ anyOrNull(),
+ any(),
+ )
+ )
+ .thenReturn(ExitResult.NoExit)
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ anyInt(),
+ any(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(transition)
+
+ runOpenNewWindow(task)
+
+ verify(desktopMixedTransitionHandler)
+ .startLaunchTransition(
+ anyInt(),
+ wctCaptor.capture(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ wctCaptor.firstValue.assertLaunchTaskOnDisplay(SECOND_DISPLAY)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun newWindow_fromFreeform_exitsImmersiveIfNeeded() {
+ setUpLandscapeDisplay()
+ val immersiveTask = setUpFreeformTask()
+ val task = setUpFreeformTask()
+ val runOnStart = RunOnStartTransitionCallback()
+ val transition = Binder()
+ whenever(
+ mMockDesktopImmersiveController.exitImmersiveIfApplicable(
+ any(),
+ anyInt(),
+ anyOrNull(),
+ any(),
+ )
+ )
+ .thenReturn(ExitResult.Exit(immersiveTask.taskId, runOnStart))
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ anyInt(),
+ any(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(transition)
+
+ runOpenNewWindow(task)
+
+ runOnStart.assertOnlyInvocation(transition)
+ }
+
+ private fun runOpenNewWindow(task: RunningTaskInfo) {
+ markTaskVisible(task)
+ task.baseActivity = mock(ComponentName::class.java)
+ task.isFocused = true
+ runningTasks.add(task)
+ controller.openNewWindow(task)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun openInstance_fromFullscreenOpensInSplit() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask()
+ val taskToRequest = setUpFreeformTask()
+ val optionsCaptor = argumentCaptor()
+ runOpenInstance(task, taskToRequest.taskId)
+ verify(splitScreenController)
+ .startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull(), any())
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.firstValue).launchWindowingMode)
+ .isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun openInstance_fromSplitOpensInSplit() {
+ setUpLandscapeDisplay()
+ val task = setUpSplitScreenTask()
+ val taskToRequest = setUpFreeformTask()
+ val optionsCaptor = argumentCaptor()
+ runOpenInstance(task, taskToRequest.taskId)
+ verify(splitScreenController)
+ .startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull(), any())
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.firstValue).launchWindowingMode)
+ .isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun openInstance_fromFreeformAddsNewWindow_multiDesksDisabled() {
+ setUpLandscapeDisplay()
+ val task = setUpFreeformTask()
+ val taskToRequest = setUpFreeformTask()
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_TO_FRONT),
+ any(),
+ eq(taskToRequest.taskId),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+
+ runOpenInstance(task, taskToRequest.taskId)
+
+ verify(desktopMixedTransitionHandler)
+ .startLaunchTransition(
+ anyInt(),
+ any(),
+ anyInt(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT)
+ assertThat(wct.hierarchyOps).hasSize(1)
+ wct.assertReorderAt(index = 0, taskToRequest)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun openInstance_fromFreeformAddsNewWindow_multiDesksEnabled() {
+ setUpLandscapeDisplay()
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val taskToRequest = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_TO_FRONT),
+ any(),
+ eq(taskToRequest.taskId),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+
+ runOpenInstance(task, taskToRequest.taskId)
+
+ verify(desktopMixedTransitionHandler)
+ .startLaunchTransition(
+ anyInt(),
+ any(),
+ anyInt(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ verify(desksOrganizer).reorderTaskToFront(any(), eq(0), eq(taskToRequest))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION,
+ )
+ fun openInstance_fromFreeform_multiDesksDisabled_minimizesIfNeeded() {
+ setUpLandscapeDisplay()
+ val deskId = 0
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT + 1).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ }
+ val oldestTask = freeformTasks.first()
+ val newestTask = freeformTasks.last()
+
+ val transition = Binder()
+ val wctCaptor = argumentCaptor()
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ anyInt(),
+ wctCaptor.capture(),
+ anyInt(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(transition)
+
+ runOpenInstance(newestTask, freeformTasks[1].taskId)
+
+ val wct = wctCaptor.firstValue
+ assertThat(wct.hierarchyOps.size).isEqualTo(2) // move-to-front + minimize
+ wct.assertReorderAt(0, freeformTasks[1], toTop = true)
+ wct.assertReorderAt(1, oldestTask, toTop = false)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION)
+ fun openInstance_fromFreeform_multiDesksEnabled_minimizesIfNeeded() {
+ setUpLandscapeDisplay()
+ val deskId = 0
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT + 1).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ }
+ val oldestTask = freeformTasks.first()
+ val newestTask = freeformTasks.last()
+
+ val transition = Binder()
+ val wctCaptor = argumentCaptor()
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ anyInt(),
+ wctCaptor.capture(),
+ anyInt(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(transition)
+
+ runOpenInstance(newestTask, freeformTasks[1].taskId)
+
+ val wct = wctCaptor.firstValue
+ verify(desksOrganizer).minimizeTask(wct, deskId, oldestTask)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_TASK_LIMIT_SEPARATE_TRANSITION,
+ )
+ fun openInstance_fromFreeform_minimizesSeparately() {
+ setUpLandscapeDisplay()
+ val deskId = 0
+ val freeformTasks =
+ (1..MAX_TASK_LIMIT + 1).map { _ ->
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ }
+ val oldestTask = freeformTasks.first()
+ val newestTask = freeformTasks.last()
+
+ val transition = Binder()
+ val wctCaptor = argumentCaptor()
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ anyInt(),
+ wctCaptor.capture(),
+ anyInt(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(transition)
+
+ runOpenInstance(newestTask, freeformTasks[1].taskId)
+
+ val wct = wctCaptor.firstValue
+ verify(desksOrganizer, never()).minimizeTask(wct, deskId, oldestTask)
+ assertThat(desktopTasksLimiter.hasTaskLimitTransitionForTesting(transition)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun openInstance_fromFreeform_exitsImmersiveIfNeeded() {
+ setUpLandscapeDisplay()
+ val freeformTask = setUpFreeformTask()
+ val immersiveTask = setUpFreeformTask()
+ taskRepository.setTaskInFullImmersiveState(
+ displayId = immersiveTask.displayId,
+ taskId = immersiveTask.taskId,
+ immersive = true,
+ )
+ val runOnStartTransit = RunOnStartTransitionCallback()
+ val transition = Binder()
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ anyInt(),
+ any(),
+ anyInt(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(transition)
+ whenever(
+ mMockDesktopImmersiveController.exitImmersiveIfApplicable(
+ any(),
+ eq(DEFAULT_DISPLAY),
+ eq(freeformTask.taskId),
+ any(),
+ )
+ )
+ .thenReturn(
+ ExitResult.Exit(
+ exitingTask = immersiveTask.taskId,
+ runOnTransitionStart = runOnStartTransit,
+ )
+ )
+
+ runOpenInstance(immersiveTask, freeformTask.taskId)
+
+ verify(mMockDesktopImmersiveController)
+ .exitImmersiveIfApplicable(
+ any(),
+ eq(immersiveTask.displayId),
+ eq(freeformTask.taskId),
+ any(),
+ )
+ runOnStartTransit.assertOnlyInvocation(transition)
+ }
+
+ private fun runOpenInstance(callingTask: RunningTaskInfo, requestedTaskId: Int) {
+ markTaskVisible(callingTask)
+ callingTask.baseActivity = mock(ComponentName::class.java)
+ callingTask.isFocused = true
+ runningTasks.add(callingTask)
+ controller.openInstance(callingTask, requestedTaskId)
+ }
+
+ @Test
+ fun toggleBounds_togglesToStableBounds() {
+ val bounds = Rect(0, 0, 100, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
+
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH,
+ ),
+ )
+
+ // Assert bounds set to stable bounds
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS)
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ ResizeTrigger.MAXIMIZE_BUTTON,
+ InputMethod.TOUCH,
+ task,
+ STABLE_BOUNDS.width(),
+ STABLE_BOUNDS.height(),
+ displayController,
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TILE_RESIZING)
+ fun snapToHalfScreen_getSnapBounds_calculatesBoundsForResizable() {
+ val bounds = Rect(100, 100, 300, 300)
+ val task =
+ setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply {
+ topActivityInfo =
+ ActivityInfo().apply {
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE
+ configuration.windowConfiguration.appBounds = bounds
+ }
+ isResizeable = true
+ }
+
+ val currentDragBounds = Rect(0, 100, 200, 300)
+ val expectedBounds =
+ Rect(
+ STABLE_BOUNDS.left,
+ STABLE_BOUNDS.top,
+ STABLE_BOUNDS.right / 2,
+ STABLE_BOUNDS.bottom,
+ )
+
+ controller.snapToHalfScreen(
+ task,
+ mockSurface,
+ currentDragBounds,
+ SnapPosition.LEFT,
+ ResizeTrigger.SNAP_LEFT_MENU,
+ InputMethod.TOUCH,
+ )
+ // Assert bounds set to stable bounds
+ val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds)
+ assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ ResizeTrigger.SNAP_LEFT_MENU,
+ InputMethod.TOUCH,
+ task,
+ expectedBounds.width(),
+ expectedBounds.height(),
+ displayController,
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TILE_RESIZING)
+ fun snapToHalfScreen_snapBoundsWhenAlreadySnapped_animatesSurfaceWithoutWCT() {
+ // Set up task to already be in snapped-left bounds
+ val bounds =
+ Rect(
+ STABLE_BOUNDS.left,
+ STABLE_BOUNDS.top,
+ STABLE_BOUNDS.right / 2,
+ STABLE_BOUNDS.bottom,
+ )
+ val task =
+ setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply {
+ topActivityInfo =
+ ActivityInfo().apply {
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE
+ configuration.windowConfiguration.appBounds = bounds
+ }
+ isResizeable = true
+ }
+
+ // Attempt to snap left again
+ val currentDragBounds = Rect(bounds).apply { offset(-100, 0) }
+ controller.snapToHalfScreen(
+ task,
+ mockSurface,
+ currentDragBounds,
+ SnapPosition.LEFT,
+ ResizeTrigger.SNAP_LEFT_MENU,
+ InputMethod.TOUCH,
+ )
+ // Assert that task is NOT updated via WCT
+ verify(toggleResizeDesktopTaskTransitionHandler, never())
+ .startTransition(any(), any(), any())
+
+ // Assert that task leash is updated via Surface Animations
+ verify(mReturnToDragStartAnimator)
+ .start(eq(task.taskId), eq(mockSurface), eq(currentDragBounds), eq(bounds), anyOrNull())
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ ResizeTrigger.SNAP_LEFT_MENU,
+ InputMethod.TOUCH,
+ task,
+ bounds.width(),
+ bounds.height(),
+ displayController,
+ )
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING,
+ Flags.FLAG_ENABLE_TILE_RESIZING,
+ )
+ fun handleSnapResizingTaskOnDrag_nonResizable_snapsToHalfScreen() {
+ val task =
+ setUpFreeformTask(DEFAULT_DISPLAY, Rect(0, 0, 200, 100)).apply { isResizeable = false }
+ val preDragBounds = Rect(100, 100, 400, 500)
+ val currentDragBounds = Rect(0, 100, 300, 500)
+ val expectedBounds =
+ Rect(
+ STABLE_BOUNDS.left,
+ STABLE_BOUNDS.top,
+ STABLE_BOUNDS.right / 2,
+ STABLE_BOUNDS.bottom,
+ )
+
+ controller.handleSnapResizingTaskOnDrag(
+ task,
+ SnapPosition.LEFT,
+ mockSurface,
+ currentDragBounds,
+ preDragBounds,
+ motionEvent,
+ )
+ val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds)
+ assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingStarted(
+ ResizeTrigger.DRAG_LEFT,
+ InputMethod.UNKNOWN_INPUT_METHOD,
+ task,
+ preDragBounds.width(),
+ preDragBounds.height(),
+ displayController,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING)
+ fun handleSnapResizingTaskOnDrag_nonResizable_startsRepositionAnimation() {
+ val task =
+ setUpFreeformTask(DEFAULT_DISPLAY, Rect(0, 0, 200, 100)).apply { isResizeable = false }
+ val preDragBounds = Rect(100, 100, 400, 500)
+ val currentDragBounds = Rect(0, 100, 300, 500)
+
+ controller.handleSnapResizingTaskOnDrag(
+ task,
+ SnapPosition.LEFT,
+ mockSurface,
+ currentDragBounds,
+ preDragBounds,
+ motionEvent,
+ )
+ verify(mReturnToDragStartAnimator)
+ .start(
+ eq(task.taskId),
+ eq(mockSurface),
+ eq(currentDragBounds),
+ eq(preDragBounds),
+ any(),
+ )
+ verify(desktopModeEventLogger, never())
+ .logTaskResizingStarted(any(), any(), any(), any(), any(), any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING)
+ fun handleInstantSnapResizingTask_nonResizable_animatorNotStartedAndShowsToast() {
+ val taskBounds = Rect(0, 0, 200, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, taskBounds).apply { isResizeable = false }
+
+ controller.handleInstantSnapResizingTask(
+ task,
+ SnapPosition.LEFT,
+ ResizeTrigger.SNAP_LEFT_MENU,
+ InputMethod.MOUSE,
+ )
+
+ // Assert that task is NOT updated via WCT
+ verify(toggleResizeDesktopTaskTransitionHandler, never())
+ .startTransition(any(), any(), any())
+ verify(mockToast).show()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING)
+ @DisableFlags(Flags.FLAG_ENABLE_TILE_RESIZING)
+ fun handleInstantSnapResizingTask_resizable_snapsToHalfScreenAndNotShowToast() {
+ val taskBounds = Rect(0, 0, 200, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, taskBounds).apply { isResizeable = true }
+ val expectedBounds =
+ Rect(
+ STABLE_BOUNDS.left,
+ STABLE_BOUNDS.top,
+ STABLE_BOUNDS.right / 2,
+ STABLE_BOUNDS.bottom,
+ )
+
+ controller.handleInstantSnapResizingTask(
+ task,
+ SnapPosition.LEFT,
+ ResizeTrigger.SNAP_LEFT_MENU,
+ InputMethod.MOUSE,
+ )
+
+ // Assert bounds set to half of the stable bounds
+ val wct = getLatestToggleResizeDesktopTaskWct(taskBounds)
+ assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
+ verify(mockToast, never()).show()
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingStarted(
+ ResizeTrigger.SNAP_LEFT_MENU,
+ InputMethod.MOUSE,
+ task,
+ taskBounds.width(),
+ taskBounds.height(),
+ displayController,
+ )
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ ResizeTrigger.SNAP_LEFT_MENU,
+ InputMethod.MOUSE,
+ task,
+ expectedBounds.width(),
+ expectedBounds.height(),
+ displayController,
+ )
+ }
+
+ @Test
+ fun toggleBounds_togglesToCalculatedBoundsForNonResizable() {
+ val bounds = Rect(0, 0, 200, 100)
+ val task =
+ setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply {
+ topActivityInfo.apply {
+ this?.screenOrientation = SCREEN_ORIENTATION_LANDSCAPE
+ configuration.windowConfiguration.appBounds = bounds
+ }
+ appCompatTaskInfo.topActivityAppBounds.set(0, 0, bounds.width(), bounds.height())
+ isResizeable = false
+ }
+
+ // Bounds should be 1000 x 500, vertically centered in the 1000 x 1000 stable bounds
+ val expectedBounds = Rect(STABLE_BOUNDS.left, 250, STABLE_BOUNDS.right, 750)
+
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH,
+ ),
+ )
+
+ // Assert bounds set to stable bounds
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ ResizeTrigger.MAXIMIZE_BUTTON,
+ InputMethod.TOUCH,
+ task,
+ expectedBounds.width(),
+ expectedBounds.height(),
+ displayController,
+ )
+ }
+
+ @Test
+ fun toggleBounds_lastBoundsBeforeMaximizeSaved() {
+ val bounds = Rect(0, 0, 100, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
+
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH,
+ ),
+ )
+
+ assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isEqualTo(bounds)
+ verify(desktopModeEventLogger, never())
+ .logTaskResizingEnded(any(), any(), any(), any(), any(), any(), any())
+ }
+
+ @Test
+ fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize() {
+ val boundsBeforeMaximize = Rect(0, 0, 100, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
+
+ // Maximize
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH,
+ ),
+ )
+ task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS)
+
+ // Restore
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.RESTORE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+ InputMethod.TOUCH,
+ ),
+ )
+
+ // Assert bounds set to last bounds before maximize
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ ResizeTrigger.MAXIMIZE_BUTTON,
+ InputMethod.TOUCH,
+ task,
+ boundsBeforeMaximize.width(),
+ boundsBeforeMaximize.height(),
+ displayController,
+ )
+ }
+
+ @Test
+ fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize_nonResizeableEqualWidth() {
+ val boundsBeforeMaximize = Rect(0, 0, 100, 100)
+ val task =
+ setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize).apply { isResizeable = false }
+
+ // Maximize
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH,
+ ),
+ )
+ task.configuration.windowConfiguration.bounds.set(
+ STABLE_BOUNDS.left,
+ boundsBeforeMaximize.top,
+ STABLE_BOUNDS.right,
+ boundsBeforeMaximize.bottom,
+ )
+
+ // Restore
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.RESTORE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+ InputMethod.TOUCH,
+ ),
+ )
+
+ // Assert bounds set to last bounds before maximize
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ ResizeTrigger.MAXIMIZE_BUTTON,
+ InputMethod.TOUCH,
+ task,
+ boundsBeforeMaximize.width(),
+ boundsBeforeMaximize.height(),
+ displayController,
+ )
+ }
+
+ @Test
+ fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize_nonResizeableEqualHeight() {
+ val boundsBeforeMaximize = Rect(0, 0, 100, 100)
+ val task =
+ setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize).apply { isResizeable = false }
+
+ // Maximize
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH,
+ ),
+ )
+ task.configuration.windowConfiguration.bounds.set(
+ boundsBeforeMaximize.left,
+ STABLE_BOUNDS.top,
+ boundsBeforeMaximize.right,
+ STABLE_BOUNDS.bottom,
+ )
+
+ // Restore
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.RESTORE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+ InputMethod.TOUCH,
+ ),
+ )
+
+ // Assert bounds set to last bounds before maximize
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ ResizeTrigger.MAXIMIZE_BUTTON,
+ InputMethod.TOUCH,
+ task,
+ boundsBeforeMaximize.width(),
+ boundsBeforeMaximize.height(),
+ displayController,
+ )
+ }
+
+ @Test
+ fun toggleBounds_removesLastBoundsBeforeMaximizeAfterRestoringBounds() {
+ val boundsBeforeMaximize = Rect(0, 0, 100, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
+
+ // Maximize
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH,
+ ),
+ )
+ task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS)
+
+ // Restore
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.RESTORE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+ InputMethod.TOUCH,
+ ),
+ )
+
+ // Assert last bounds before maximize removed after use
+ assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isNull()
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ ResizeTrigger.MAXIMIZE_BUTTON,
+ InputMethod.TOUCH,
+ task,
+ boundsBeforeMaximize.width(),
+ boundsBeforeMaximize.height(),
+ displayController,
+ )
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX,
+ Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION,
+ )
+ fun onUnhandledDrag_newFreeformIntent_tabTearingAnimationBugfixFlagEnabled_tabTearingLaunchAnimationFlagEnabled() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
+ PointF(1200f, 700f),
+ Rect(240, 700, 2160, 1900),
+ tabTearingMinimizeAnimationFlagEnabled = true,
+ tabTearingLaunchAnimationFlagEnabled = true,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION)
+ fun onUnhandledDrag_newFreeformIntent_tabTearingAnimationBugfixFlagEnabled_tabTearingLaunchAnimationFlagDisabled() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
+ PointF(1200f, 700f),
+ Rect(240, 700, 2160, 1900),
+ tabTearingMinimizeAnimationFlagEnabled = true,
+ tabTearingLaunchAnimationFlagEnabled = false,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX)
+ fun onUnhandledDrag_newFreeformIntent_tabTearingAnimationBugfixFlagDisabled_tabTearingLaunchAnimationFlagEnabled() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
+ PointF(1200f, 700f),
+ Rect(240, 700, 2160, 1900),
+ tabTearingMinimizeAnimationFlagEnabled = false,
+ tabTearingLaunchAnimationFlagEnabled = true,
+ )
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX,
+ Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION,
+ )
+ fun onUnhandledDrag_newFreeformIntent_tabTearingAnimationBugfixFlagDisabled_tabTearingLaunchAnimationFlagDisabled() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
+ PointF(1200f, 700f),
+ Rect(240, 700, 2160, 1900),
+ tabTearingMinimizeAnimationFlagEnabled = false,
+ tabTearingLaunchAnimationFlagEnabled = false,
+ )
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX,
+ Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION,
+ )
+ fun onUnhandledDrag_newFreeformIntentSplitLeft_tabTearingAnimationBugfixFlagEnabled_tabTearingLaunchAnimationFlagEnabled() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR,
+ PointF(50f, 700f),
+ Rect(0, 0, 500, 1000),
+ tabTearingMinimizeAnimationFlagEnabled = true,
+ tabTearingLaunchAnimationFlagEnabled = true,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION)
+ fun onUnhandledDrag_newFreeformIntentSplitLeft_tabTearingAnimationBugfixFlagEnabled_tabTearingLaunchAnimationFlagDisabled() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR,
+ PointF(50f, 700f),
+ Rect(0, 0, 500, 1000),
+ tabTearingMinimizeAnimationFlagEnabled = true,
+ tabTearingLaunchAnimationFlagEnabled = false,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX)
+ fun onUnhandledDrag_newFreeformIntentSplitLeft_tabTearingAnimationBugfixFlagDisabled_tabTearingLaunchAnimationFlagEnabled() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR,
+ PointF(50f, 700f),
+ Rect(0, 0, 500, 1000),
+ tabTearingMinimizeAnimationFlagEnabled = false,
+ tabTearingLaunchAnimationFlagEnabled = true,
+ )
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX,
+ Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION,
+ )
+ fun onUnhandledDrag_newFreeformIntentSplitLeft_tabTearingAnimationBugfixFlagDisabled_tabTearingLaunchAnimationFlagDisabled() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR,
+ PointF(50f, 700f),
+ Rect(0, 0, 500, 1000),
+ tabTearingMinimizeAnimationFlagEnabled = false,
+ tabTearingLaunchAnimationFlagEnabled = false,
+ )
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX,
+ Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION,
+ )
+ fun onUnhandledDrag_newFreeformIntentSplitRight_tabTearingAnimationBugfixFlagEnabled_tabTearingLaunchAnimationFlagEnabled() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR,
+ PointF(2500f, 700f),
+ Rect(500, 0, 1000, 1000),
+ tabTearingMinimizeAnimationFlagEnabled = true,
+ tabTearingLaunchAnimationFlagEnabled = true,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION)
+ fun onUnhandledDrag_newFreeformIntentSplitRight_tabTearingAnimationBugfixFlagEnabled_tabTearingLaunchAnimationFlagDisabled() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR,
+ PointF(2500f, 700f),
+ Rect(500, 0, 1000, 1000),
+ tabTearingMinimizeAnimationFlagEnabled = true,
+ tabTearingLaunchAnimationFlagEnabled = false,
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION)
+ fun onUnhandledDrag_newFreeformIntentSplitRight_tabTearingAnimationBugfixFlagDisabled_tabTearingLaunchAnimationFlagEnabled() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR,
+ PointF(2500f, 700f),
+ Rect(500, 0, 1000, 1000),
+ tabTearingMinimizeAnimationFlagEnabled = false,
+ tabTearingLaunchAnimationFlagEnabled = true,
+ )
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX,
+ Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION,
+ )
+ fun onUnhandledDrag_newFreeformIntentSplitRight_tabTearingAnimationBugfixFlagDisabled_tabTearingLaunchAnimationFlagDisabled() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR,
+ PointF(2500f, 700f),
+ Rect(500, 0, 1000, 1000),
+ tabTearingMinimizeAnimationFlagEnabled = false,
+ tabTearingLaunchAnimationFlagEnabled = false,
+ )
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX,
+ Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION,
+ )
+ fun onUnhandledDrag_newFullscreenIntent_tabTearingAnimationBugfixFlagEnabled_tabTearingLaunchAnimationFlagEnabled() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
+ PointF(1200f, 50f),
+ Rect(),
+ tabTearingMinimizeAnimationFlagEnabled = true,
+ tabTearingLaunchAnimationFlagEnabled = true,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION)
+ fun onUnhandledDrag_newFullscreenIntent_tabTearingAnimationBugfixFlagEnabled_tabTearingLaunchAnimationFlagDisabled() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
+ PointF(1200f, 50f),
+ Rect(),
+ tabTearingMinimizeAnimationFlagEnabled = true,
+ tabTearingLaunchAnimationFlagEnabled = false,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX)
+ fun onUnhandledDrag_newFullscreenIntent_tabTearingAnimationBugfixFlagDisabled_tabTearingLaunchAnimationFlagEnabled() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
+ PointF(1200f, 50f),
+ Rect(),
+ tabTearingMinimizeAnimationFlagEnabled = false,
+ tabTearingLaunchAnimationFlagEnabled = true,
+ )
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX,
+ Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION,
+ )
+ fun onUnhandledDrag_newFullscreenIntent_tabTearingAnimationBugfixFlagDisabled_tabTearingLaunchAnimationFlagDisabled() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
+ PointF(1200f, 50f),
+ Rect(),
+ tabTearingMinimizeAnimationFlagEnabled = false,
+ tabTearingLaunchAnimationFlagEnabled = false,
+ )
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX,
+ Flags.FLAG_ENABLE_DESKTOP_TAB_TEARING_LAUNCH_ANIMATION,
+ )
+ fun onUnhandledDrag_crossDisplayDrag() {
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = 2)
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR,
+ PointF(1200f, 700f),
+ Rect(240, 700, 2160, 1900),
+ tabTearingMinimizeAnimationFlagEnabled = true,
+ tabTearingLaunchAnimationFlagEnabled = true,
+ destinationDisplayId = SECOND_DISPLAY,
+ )
+ }
+
+ @Test
+ fun shellController_registersUserChangeListener() {
+ verify(shellController, times(2)).addUserChangeListener(any())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun onTaskInfoChanged_inImmersiveUnrequestsImmersive_exits() {
+ val task = setUpFreeformTask(DEFAULT_DISPLAY)
+ taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, immersive = true)
+
+ task.requestedVisibleTypes = WindowInsets.Type.statusBars()
+ controller.onTaskInfoChanged(task)
+
+ verify(mMockDesktopImmersiveController).moveTaskToNonImmersive(eq(task), any())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun onTaskInfoChanged_notInImmersiveUnrequestsImmersive_noReExit() {
+ val task = setUpFreeformTask(DEFAULT_DISPLAY)
+ taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, immersive = false)
+
+ task.requestedVisibleTypes = WindowInsets.Type.statusBars()
+ controller.onTaskInfoChanged(task)
+
+ verify(mMockDesktopImmersiveController, never()).moveTaskToNonImmersive(eq(task), any())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun onTaskInfoChanged_inImmersiveUnrequestsImmersive_inRecentsTransition_noExit() {
+ val task = setUpFreeformTask(DEFAULT_DISPLAY)
+ taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, immersive = true)
+ recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_REQUESTED)
+
+ task.requestedVisibleTypes = WindowInsets.Type.statusBars()
+ controller.onTaskInfoChanged(task)
+
+ verify(mMockDesktopImmersiveController, never()).moveTaskToNonImmersive(eq(task), any())
+ }
+
+ @Test
+ fun moveTaskToDesktop_background_attemptsImmersiveExit() =
+ testScope.runTest {
+ val task = setUpFreeformTask(background = true)
+ val wct = WindowContainerTransaction()
+ val runOnStartTransit = RunOnStartTransitionCallback()
+ val transition = Binder()
+ whenever(
+ mMockDesktopImmersiveController.exitImmersiveIfApplicable(
+ eq(wct),
+ eq(task.displayId),
+ eq(task.taskId),
+ any(),
+ )
+ )
+ .thenReturn(
+ ExitResult.Exit(exitingTask = 5, runOnTransitionStart = runOnStartTransit)
+ )
+ whenever(enterDesktopTransitionHandler.moveToDesktop(wct, UNKNOWN))
+ .thenReturn(transition)
+
+ controller.moveTaskToDefaultDeskAndActivate(
+ taskId = task.taskId,
+ wct = wct,
+ transitionSource = UNKNOWN,
+ )
+ runCurrent()
+
+ verify(mMockDesktopImmersiveController)
+ .exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any())
+ runOnStartTransit.assertOnlyInvocation(transition)
+ }
+
+ @Test
+ fun moveTaskToDesktop_foreground_attemptsImmersiveExit() =
+ testScope.runTest {
+ val task = setUpFreeformTask(background = false)
+ val wct = WindowContainerTransaction()
+ val runOnStartTransit = RunOnStartTransitionCallback()
+ val transition = Binder()
+ whenever(
+ mMockDesktopImmersiveController.exitImmersiveIfApplicable(
+ eq(wct),
+ eq(task.displayId),
+ eq(task.taskId),
+ any(),
+ )
+ )
+ .thenReturn(
+ ExitResult.Exit(exitingTask = 5, runOnTransitionStart = runOnStartTransit)
+ )
+ whenever(enterDesktopTransitionHandler.moveToDesktop(wct, UNKNOWN))
+ .thenReturn(transition)
+
+ controller.moveTaskToDefaultDeskAndActivate(
+ taskId = task.taskId,
+ wct = wct,
+ transitionSource = UNKNOWN,
+ )
+ runCurrent()
+
+ verify(mMockDesktopImmersiveController)
+ .exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any())
+ runOnStartTransit.assertOnlyInvocation(transition)
+ }
+
+ @Test
+ fun moveTaskToFront_background_attemptsImmersiveExit() =
+ testScope.runTest {
+ val task = setUpFreeformTask(background = true)
+ val runOnStartTransit = RunOnStartTransitionCallback()
+ val transition = Binder()
+ whenever(
+ mMockDesktopImmersiveController.exitImmersiveIfApplicable(
+ any(),
+ eq(task.displayId),
+ eq(task.taskId),
+ any(),
+ )
+ )
+ .thenReturn(
+ ExitResult.Exit(exitingTask = 5, runOnTransitionStart = runOnStartTransit)
+ )
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ any(),
+ any(),
+ anyInt(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(transition)
+
+ controller.moveTaskToFront(task.taskId, unminimizeReason = UnminimizeReason.UNKNOWN)
+ runCurrent()
+
+ verify(mMockDesktopImmersiveController)
+ .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any())
+ runOnStartTransit.assertOnlyInvocation(transition)
+ }
+
+ @Test
+ fun moveTaskToFront_foreground_attemptsImmersiveExit() {
+ val task = setUpFreeformTask(background = false)
+ val runOnStartTransit = RunOnStartTransitionCallback()
+ val transition = Binder()
+ whenever(
+ mMockDesktopImmersiveController.exitImmersiveIfApplicable(
+ any(),
+ eq(task.displayId),
+ eq(task.taskId),
+ any(),
+ )
+ )
+ .thenReturn(ExitResult.Exit(exitingTask = 5, runOnTransitionStart = runOnStartTransit))
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ any(),
+ any(),
+ eq(task.taskId),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(transition)
+
+ controller.moveTaskToFront(task.taskId, unminimizeReason = UnminimizeReason.UNKNOWN)
+
+ verify(mMockDesktopImmersiveController)
+ .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any())
+ runOnStartTransit.assertOnlyInvocation(transition)
+ }
+
+ @Test
+ fun handleRequest_freeformLaunchToDesktop_attemptsImmersiveExit() {
+ markTaskVisible(setUpFreeformTask())
+ val task = setUpFreeformTask()
+ markTaskVisible(task)
+ val binder = Binder()
+
+ controller.handleRequest(binder, createTransition(task))
+
+ verify(mMockDesktopImmersiveController)
+ .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId), any())
+ }
+
+ @Test
+ fun handleRequest_fullscreenLaunchToDesktop_attemptsImmersiveExit() {
+ setUpFreeformTask()
+ val task = createFullscreenTask()
+ val binder = Binder()
+
+ controller.handleRequest(binder, createTransition(task))
+
+ verify(mMockDesktopImmersiveController)
+ .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId), any())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_homeTask_notHandled() {
+ val home = createHomeTask(DEFAULT_DISPLAY)
+
+ val transition = Binder()
+ val result = controller.handleRequest(transition, createTransition(home))
+
+ assertNull(result)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_homeTask_activeDesk_deactivates() {
+ taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId = 0)
+ val home = createHomeTask(DEFAULT_DISPLAY, userId = taskRepository.userId)
+
+ val transition = Binder()
+ val result = controller.handleRequest(transition, createTransition(home))
+
+ assertNotNull(result)
+ verify(desksOrganizer).deactivateDesk(result, deskId = 0)
+ verify(desksTransitionsObserver)
+ .addPendingTransition(DeskTransition.DeactivateDesk(token = transition, deskId = 0))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_homeTask_closing_notHandled() {
+ taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId = 0)
+ val home = createHomeTask(DEFAULT_DISPLAY)
+
+ val transition = Binder()
+ val result = controller.handleRequest(transition, createTransition(home, TRANSIT_CLOSE))
+
+ assertNull(result)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_homeTask_activeDesk_nonCurrentUser_deactivatesDeskOfCorrectUser() {
+ val currentUserDesk = 0
+ taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId = currentUserDesk)
+ val otherUser = 88
+ val otherUserDesk = 1
+ userRepositories.getProfile(otherUser).apply {
+ addDesk(DEFAULT_DISPLAY, deskId = otherUserDesk)
+ setActiveDesk(DEFAULT_DISPLAY, deskId = otherUserDesk)
+ }
+ val home = createHomeTask(DEFAULT_DISPLAY, userId = otherUser)
+
+ val transition = Binder()
+ val result = controller.handleRequest(transition, createTransition(home))
+
+ assertNotNull(result)
+ verify(desksOrganizer).deactivateDesk(result, deskId = otherUserDesk)
+ verify(desksOrganizer, never()).deactivateDesk(result, deskId = currentUserDesk)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun shouldPlayDesktopAnimation_notShowingDesktop_doesNotPlay() {
+ taskRepository.setDeskInactive(deskId = 0)
+ val triggerTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+ taskRepository.setTaskInFullImmersiveState(
+ displayId = triggerTask.displayId,
+ taskId = triggerTask.taskId,
+ immersive = true,
+ )
+
+ assertThat(
+ controller.shouldPlayDesktopAnimation(
+ TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null)
+ )
+ )
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun shouldPlayDesktopAnimation_notOpening_doesNotPlay() {
+ val triggerTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ taskRepository.setTaskInFullImmersiveState(
+ displayId = triggerTask.displayId,
+ taskId = triggerTask.taskId,
+ immersive = true,
+ )
+
+ assertThat(
+ controller.shouldPlayDesktopAnimation(
+ TransitionRequestInfo(TRANSIT_CHANGE, triggerTask, /* remoteTransition= */ null)
+ )
+ )
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun shouldPlayDesktopAnimation_notImmersive_doesNotPlay() {
+ val triggerTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ taskRepository.setTaskInFullImmersiveState(
+ displayId = triggerTask.displayId,
+ taskId = triggerTask.taskId,
+ immersive = false,
+ )
+
+ assertThat(
+ controller.shouldPlayDesktopAnimation(
+ TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null)
+ )
+ )
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun shouldPlayDesktopAnimation_fullscreenEntersDesktop_plays() {
+ // At least one freeform task to be in a desktop.
+ val existingTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val triggerTask = createFullscreenTask(displayId = DEFAULT_DISPLAY)
+ assertThat(controller.isAnyDeskActive(triggerTask.displayId)).isTrue()
+ taskRepository.setTaskInFullImmersiveState(
+ displayId = existingTask.displayId,
+ taskId = existingTask.taskId,
+ immersive = true,
+ )
+
+ assertThat(
+ controller.shouldPlayDesktopAnimation(
+ TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null)
+ )
+ )
+ .isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun shouldPlayDesktopAnimation_fullscreenStaysFullscreen_doesNotPlay() {
+ val triggerTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+ taskRepository.setDeskInactive(deskId = 0)
+ assertThat(controller.isAnyDeskActive(triggerTask.displayId)).isFalse()
+
+ assertThat(
+ controller.shouldPlayDesktopAnimation(
+ TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null)
+ )
+ )
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun shouldPlayDesktopAnimation_freeformStaysInDesktop_plays() {
+ // At least one freeform task to be in a desktop.
+ val existingTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val triggerTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, active = false)
+ assertThat(controller.isAnyDeskActive(triggerTask.displayId)).isTrue()
+ taskRepository.setTaskInFullImmersiveState(
+ displayId = existingTask.displayId,
+ taskId = existingTask.taskId,
+ immersive = true,
+ )
+
+ assertThat(
+ controller.shouldPlayDesktopAnimation(
+ TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null)
+ )
+ )
+ .isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun shouldPlayDesktopAnimation_freeformExitsDesktop_doesNotPlay() {
+ val triggerTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, active = false)
+ taskRepository.setDeskInactive(deskId = 0)
+ assertThat(controller.isAnyDeskActive(triggerTask.displayId)).isFalse()
+
+ assertThat(
+ controller.shouldPlayDesktopAnimation(
+ TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null)
+ )
+ )
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun testCreateDesk() {
+ desktopState.overrideDesktopModeSupportPerDisplay[DEFAULT_DISPLAY] = true
+ val currentDeskCount = taskRepository.getNumberOfDesks(DEFAULT_DISPLAY)
+ whenever(desksOrganizer.createDesk(eq(DEFAULT_DISPLAY), any(), any())).thenAnswer {
+ invocation ->
+ (invocation.arguments[2] as DesksOrganizer.OnCreateCallback).onCreated(deskId = 5)
+ }
+
+ controller.createDesk(DEFAULT_DISPLAY, taskRepository.userId)
+
+ assertThat(taskRepository.getNumberOfDesks(DEFAULT_DISPLAY)).isEqualTo(currentDeskCount + 1)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun testCreateDesk_invalidDisplay_dropsRequest() {
+ desktopState.overrideDesktopModeSupportPerDisplay[DEFAULT_DISPLAY] = true
+ controller.createDesk(INVALID_DISPLAY)
+
+ verify(desksOrganizer, never()).createDesk(any(), any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun testCreateDesk_systemUser_dropsRequest() {
+ desktopState.overrideDesktopModeSupportPerDisplay[DEFAULT_DISPLAY] = true
+ assumeTrue(UserManager.isHeadlessSystemUserMode())
+
+ controller.createDesk(DEFAULT_DISPLAY, UserHandle.USER_SYSTEM)
+
+ verify(desksOrganizer, never()).createDesk(any(), any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun testCreateDesk_enforceLimitAndOverLimit_dropsRequest() {
+ desktopState.overrideDesktopModeSupportPerDisplay[DEFAULT_DISPLAY] = true
+ desktopConfig.maxDeskLimit = 2
+ // Add a second desk to bring the number up to the limit.
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = 2)
+
+ controller.createDesk(DEFAULT_DISPLAY)
+
+ verify(desksOrganizer, never()).createDesk(any(), any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun testCreateDesk_displayDoesNotSupportDesks_dropsRequest() {
+ desktopState.overrideDesktopModeSupportPerDisplay[DEFAULT_DISPLAY] = false
+
+ controller.createDesk(DEFAULT_DISPLAY)
+
+ verify(desksOrganizer, never()).createDesk(any(), any(), any())
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DISPLAY_DISCONNECT_INTERACTION,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun onDisplayDisconnect_desktopModeNotSupported_reparentsDeskTasks_focusedTaskToTop() {
+ val defaultDisplayTask = setUpFullscreenTask()
+ val transition = Binder()
+ taskRepository.addDesk(SECOND_DISPLAY, DISCONNECTED_DESK_ID)
+ val secondDisplayTask = setUpFreeformTask(SECOND_DISPLAY)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+
+ val wct =
+ performDisplayDisconnectTransition(
+ transition = transition,
+ desktopSupportedOnDefaultDisplay = false,
+ taskOnSecondDisplayHasFocus = true,
+ defaultDisplayTask = defaultDisplayTask,
+ secondDisplayTask = secondDisplayTask,
+ )
+
+ assertThat(wct).isNotNull()
+ wct.assertHop(
+ ReparentPredicate(
+ token = secondDisplayTask.token,
+ parentToken = tda.token,
+ toTop = true,
+ )
+ )
+ verify(desksOrganizer).removeDesk(any(), eq(DISCONNECTED_DESK_ID), any())
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ argThat {
+ this is DeskTransition.RemoveDesk &&
+ this.token == transition &&
+ this.displayId == SECOND_DISPLAY &&
+ this.deskId == DISCONNECTED_DESK_ID
+ }
+ )
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ argThat {
+ this is DeskTransition.RemoveDisplay &&
+ this.token == transition &&
+ this.displayId == SECOND_DISPLAY
+ }
+ )
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DISPLAY_DISCONNECT_INTERACTION,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun onDisplayDisconnect_desktopModeNotSupported_reparentsDeskTasks_nonFocusedTaskToBottom() {
+ val defaultDisplayTask = setUpFullscreenTask()
+ val transition = Binder()
+ taskRepository.addDesk(SECOND_DISPLAY, DISCONNECTED_DESK_ID)
+ val secondDisplayTask = setUpFreeformTask(SECOND_DISPLAY)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ val wct =
+ performDisplayDisconnectTransition(
+ transition = transition,
+ desktopSupportedOnDefaultDisplay = false,
+ taskOnSecondDisplayHasFocus = false,
+ defaultDisplayTask = defaultDisplayTask,
+ secondDisplayTask = secondDisplayTask,
+ )
+ wct.assertHop(
+ ReparentPredicate(
+ token = secondDisplayTask.token,
+ parentToken = tda.token,
+ toTop = false,
+ )
+ )
+ verify(desksOrganizer).removeDesk(any(), eq(DISCONNECTED_DESK_ID), any())
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ argThat {
+ this is DeskTransition.RemoveDesk &&
+ this.token == transition &&
+ this.displayId == SECOND_DISPLAY &&
+ this.deskId == DISCONNECTED_DESK_ID
+ }
+ )
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ argThat {
+ this is DeskTransition.RemoveDisplay &&
+ this.token == transition &&
+ this.displayId == SECOND_DISPLAY
+ }
+ )
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DISPLAY_DISCONNECT_INTERACTION,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun onDisplayDisconnect_desktopModeSupported_reparentsDesks_nonFocusedDeskDeactivated() {
+ val defaultDisplayTask = setUpFreeformTask()
+ val transition = Binder()
+ taskRepository.addDesk(SECOND_DISPLAY, DISCONNECTED_DESK_ID)
+ taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = DISCONNECTED_DESK_ID)
+ val secondDisplayTask = setUpFreeformTask(SECOND_DISPLAY, deskId = DISCONNECTED_DESK_ID)
+
+ performDisplayDisconnectTransition(
+ transition = transition,
+ desktopSupportedOnDefaultDisplay = true,
+ taskOnSecondDisplayHasFocus = false,
+ defaultDisplayTask = defaultDisplayTask,
+ secondDisplayTask = secondDisplayTask,
+ )
+
+ verify(desksOrganizer)
+ .moveDeskToDisplay(any(), eq(DISCONNECTED_DESK_ID), eq(DEFAULT_DISPLAY), eq(false))
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ argThat {
+ this is DeskTransition.ChangeDeskDisplay &&
+ this.token == transition &&
+ this.displayId == DEFAULT_DISPLAY &&
+ this.deskId == DISCONNECTED_DESK_ID
+ }
+ )
+ verify(desksOrganizer)
+ .deactivateDesk(any(), eq(DISCONNECTED_DESK_ID), skipReorder = eq(false))
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ argThat {
+ this is DeskTransition.DeactivateDesk &&
+ this.token == transition &&
+ this.deskId == DISCONNECTED_DESK_ID
+ }
+ )
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DISPLAY_DISCONNECT_INTERACTION, FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onDisplayDisconnect_desktopModeSupported_reparentsDesks_focusedDeskActivated() {
+ val defaultDisplayTask = setUpFreeformTask()
+ val transition = Binder()
+ taskRepository.addDesk(SECOND_DISPLAY, DISCONNECTED_DESK_ID)
+ taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = DISCONNECTED_DESK_ID)
+ val secondDisplayTask = setUpFreeformTask(SECOND_DISPLAY, deskId = DISCONNECTED_DESK_ID)
+
+ performDisplayDisconnectTransition(
+ transition = transition,
+ desktopSupportedOnDefaultDisplay = true,
+ taskOnSecondDisplayHasFocus = true,
+ defaultDisplayTask = defaultDisplayTask,
+ secondDisplayTask = secondDisplayTask,
+ )
+
+ verify(desksOrganizer)
+ .moveDeskToDisplay(any(), eq(DISCONNECTED_DESK_ID), eq(DEFAULT_DISPLAY), eq(true))
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ argThat {
+ this is DeskTransition.ChangeDeskDisplay &&
+ this.token == transition &&
+ this.displayId == DEFAULT_DISPLAY &&
+ this.deskId == DISCONNECTED_DESK_ID
+ }
+ )
+ verify(desksOrganizer)
+ .activateDesk(any(), eq(DISCONNECTED_DESK_ID), skipReorder = eq(false))
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ argThat {
+ this is DeskTransition.ActivateDesk &&
+ this.token == transition &&
+ this.displayId == DEFAULT_DISPLAY &&
+ this.deskId == DISCONNECTED_DESK_ID
+ }
+ )
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DISPLAY_DISCONNECT_INTERACTION,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun onDisplayDisconnect_desktopModeSupported_emptyDeskRemoved() {
+ val defaultDisplayTask = setUpFreeformTask()
+ val transition = Binder()
+ taskRepository.addDesk(SECOND_DISPLAY, DISCONNECTED_DESK_ID)
+ taskRepository.setActiveDesk(SECOND_DISPLAY, DISCONNECTED_DESK_ID)
+
+ performDisplayDisconnectTransition(
+ transition = transition,
+ desktopSupportedOnDefaultDisplay = true,
+ taskOnSecondDisplayHasFocus = false,
+ defaultDisplayTask = defaultDisplayTask,
+ secondDisplayTask = null,
+ )
+
+ verify(desksOrganizer).removeDesk(any(), eq(DISCONNECTED_DESK_ID), anyInt())
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ argThat {
+ this is DeskTransition.RemoveDesk &&
+ this.token == transition &&
+ this.displayId == SECOND_DISPLAY &&
+ this.deskId == DISCONNECTED_DESK_ID
+ }
+ )
+ }
+
+ private fun performDisplayDisconnectTransition(
+ transition: IBinder,
+ desktopSupportedOnDefaultDisplay: Boolean,
+ taskOnSecondDisplayHasFocus: Boolean,
+ defaultDisplayTask: RunningTaskInfo,
+ secondDisplayTask: RunningTaskInfo?,
+ ): WindowContainerTransaction {
+ desktopState.overrideDesktopModeSupportPerDisplay[DEFAULT_DISPLAY] =
+ desktopSupportedOnDefaultDisplay
+ val displayChange = TransitionRequestInfo.DisplayChange(SECOND_DISPLAY)
+ displayChange.disconnectReparentDisplay = DEFAULT_DISPLAY
+ val transitionRequestInfo =
+ TransitionRequestInfo(
+ TRANSIT_CLOSE,
+ /* triggerTask = */ null,
+ /* remoteTransition= */ null,
+ )
+ .apply { setDisplayChange(displayChange) }
+ val focusedTaskId =
+ if (taskOnSecondDisplayHasFocus) {
+ secondDisplayTask?.taskId ?: error("Cannot have a focused null task")
+ } else {
+ defaultDisplayTask.taskId
+ }
+ whenever(focusTransitionObserver.globallyFocusedTaskId).thenReturn(focusedTaskId)
+ var preserveRequested = false
+ controller.preserveDisplayRequestHandler = PreserveDisplayRequestHandler {
+ preserveRequested = true
+ }
+
+ val wct = assertNotNull(controller.handleRequest(transition, transitionRequestInfo))
+ assertThat(preserveRequested).isTrue()
+
+ return wct
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING,
+ )
+ fun startLaunchTransition_desktopNotShowing_movesWallpaperToFront() {
+ taskRepository.setDeskInactive(deskId = 0)
+ val launchingTask = createFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wct = WindowContainerTransaction()
+ wct.reorder(launchingTask.token, /* onTop= */ true)
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_OPEN),
+ any(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+
+ controller.startLaunchTransition(
+ transitionType = TRANSIT_OPEN,
+ wct = wct,
+ launchingTaskId = null,
+ deskId = 0,
+ displayId = DEFAULT_DISPLAY,
+ )
+
+ val latestWct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
+ val launchingTaskReorderIndex = latestWct.indexOfReorder(launchingTask, toTop = true)
+ val wallpaperReorderIndex = latestWct.indexOfReorder(wallpaperToken, toTop = true)
+ assertThat(launchingTaskReorderIndex).isNotEqualTo(-1)
+ assertThat(wallpaperReorderIndex).isNotEqualTo(-1)
+ assertThat(launchingTaskReorderIndex).isGreaterThan(wallpaperReorderIndex)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun startLaunchTransition_desktopNotShowing_updatesDesktopEnterExitListener() {
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+ taskRepository.setDeskInactive(deskId = 0)
+
+ controller.startLaunchTransition(
+ transitionType = TRANSIT_OPEN,
+ wct = WindowContainerTransaction(),
+ launchingTaskId = null,
+ deskId = 0,
+ displayId = DEFAULT_DISPLAY,
+ )
+
+ verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(any())
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun startLaunchTransition_launchingTaskFromInactiveDesk_otherDeskActive_activatesDesk() {
+ val activeDeskId = 4
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = activeDeskId)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = activeDeskId)
+ val inactiveDesk = 5
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = inactiveDesk)
+ val launchingTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = inactiveDesk)
+ val transition = Binder()
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_OPEN),
+ any(),
+ eq(launchingTask.taskId),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(transition)
+
+ val wct = WindowContainerTransaction()
+ controller.startLaunchTransition(
+ transitionType = TRANSIT_OPEN,
+ wct = wct,
+ launchingTaskId = launchingTask.taskId,
+ deskId = inactiveDesk,
+ displayId = DEFAULT_DISPLAY,
+ )
+
+ verify(desksOrganizer).activateDesk(any(), eq(inactiveDesk), skipReorder = eq(false))
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ DeskTransition.ActivateDesk(
+ token = transition,
+ displayId = DEFAULT_DISPLAY,
+ deskId = inactiveDesk,
+ )
+ )
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ )
+ fun startLaunchTransition_desktopShowing_doesNotReorderWallpaper() {
+ val wct = WindowContainerTransaction()
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_OPEN),
+ any(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+
+ setUpFreeformTask(deskId = 0, displayId = DEFAULT_DISPLAY)
+ controller.startLaunchTransition(
+ transitionType = TRANSIT_OPEN,
+ wct = wct,
+ launchingTaskId = null,
+ deskId = 0,
+ displayId = DEFAULT_DISPLAY,
+ )
+
+ val latestWct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
+ assertNull(latestWct.hierarchyOps.find { op -> op.container == wallpaperToken.asBinder() })
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SHOW_HOME_BEHIND_DESKTOP)
+ fun showHomeBehindDesktop_wallpaperNotPresent() {
+ desktopState.shouldShowHomeBehindDesktop = true
+ val homeTask = setUpHomeTask()
+ val task1 = setUpFreeformTask()
+
+ controller.activateDesk(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ val wallpaperReorderIndex = wct.indexOfReorder(wallpaperToken, toTop = true)
+
+ // There should be no wallpaper present to reorder.
+ assertThat(wallpaperReorderIndex).isEqualTo(-1)
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onRecentsInDesktopAnimationFinishing_returningToApp_noDeskDeactivation() {
+ val deskId = 0
+ taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId)
+
+ val transition = Binder()
+ val finishWct = WindowContainerTransaction()
+ controller.onRecentsInDesktopAnimationFinishing(
+ transition = transition,
+ finishWct = finishWct,
+ returnToApp = true,
+ activeDeskIdOnRecentsStart = deskId,
+ )
+
+ verify(desksOrganizer, never()).deactivateDesk(finishWct, deskId)
+ verify(desksTransitionsObserver, never())
+ .addPendingTransition(
+ argThat { t -> t.token == transition && t is DeskTransition.DeactivateDesk }
+ )
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onRecentsInDesktopAnimationFinishing_returningToApp_snapEventHandlerNotified() {
+ val deskId = 0
+ taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId)
+
+ val transition = Binder()
+ val finishWct = WindowContainerTransaction()
+ controller.onRecentsInDesktopAnimationFinishing(
+ transition = transition,
+ finishWct = finishWct,
+ returnToApp = true,
+ activeDeskIdOnRecentsStart = deskId,
+ )
+
+ verify(snapEventHandler, times(1)).onRecentsAnimationEndedToSameDesk()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onRecentsInDesktopAnimationFinishing_deskNoLongerActive_noDeskDeactivation() {
+ val deskId = 0
+ taskRepository.setDeskInactive(deskId)
+
+ val transition = Binder()
+ val finishWct = WindowContainerTransaction()
+ controller.onRecentsInDesktopAnimationFinishing(
+ transition = transition,
+ finishWct = finishWct,
+ returnToApp = false,
+ activeDeskIdOnRecentsStart = deskId,
+ )
+
+ verify(desksOrganizer, never()).deactivateDesk(finishWct, deskId)
+ verify(desksTransitionsObserver, never())
+ .addPendingTransition(
+ argThat { t -> t.token == transition && t is DeskTransition.DeactivateDesk }
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onRecentsInDesktopAnimationFinishing_deskStillActive_notReturningToDesk_deactivatesDesk() {
+ val deskId = 0
+ taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId)
+
+ val transition = Binder()
+ val finishWct = WindowContainerTransaction()
+ controller.onRecentsInDesktopAnimationFinishing(
+ transition = transition,
+ finishWct = finishWct,
+ returnToApp = false,
+ activeDeskIdOnRecentsStart = deskId,
+ )
+
+ verify(desksOrganizer).deactivateDesk(finishWct, deskId)
+ verify(desksTransitionsObserver)
+ .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onRecentsInDesktopAnimationFinishing_deskStillActive_notReturningToDesk_doesNotBringUpWallpaperOrHome() {
+ val deskId = 0
+ taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId)
+
+ val transition = Binder()
+ val finishWct = WindowContainerTransaction()
+ controller.onRecentsInDesktopAnimationFinishing(
+ transition = transition,
+ finishWct = finishWct,
+ returnToApp = false,
+ activeDeskIdOnRecentsStart = deskId,
+ )
+
+ finishWct.assertWithoutHop { hop ->
+ hop.type == HIERARCHY_OP_TYPE_REORDER &&
+ hop.container == wallpaperToken.asBinder() &&
+ !hop.toTop
+ }
+ finishWct.assertWithoutHop { hop -> hop.type == HIERARCHY_OP_TYPE_PENDING_INTENT }
+ }
+
+ private class RunOnStartTransitionCallback : ((IBinder) -> Unit) {
+ var invocations = 0
+ private set
+
+ var lastInvoked: IBinder? = null
+ private set
+
+ override fun invoke(transition: IBinder) {
+ invocations++
+ lastInvoked = transition
+ }
+ }
+
+ private fun RunOnStartTransitionCallback.assertOnlyInvocation(transition: IBinder) {
+ assertThat(invocations).isEqualTo(1)
+ assertThat(lastInvoked).isEqualTo(transition)
+ }
+
+ /**
+ * Assert that an unhandled drag event launches a PendingIntent with the windowing mode and
+ * bounds we are expecting.
+ */
+ private fun testOnUnhandledDrag(
+ indicatorType: DesktopModeVisualIndicator.IndicatorType,
+ inputCoordinate: PointF,
+ expectedBounds: Rect,
+ tabTearingMinimizeAnimationFlagEnabled: Boolean,
+ tabTearingLaunchAnimationFlagEnabled: Boolean,
+ destinationDisplayId: Int = DEFAULT_DISPLAY,
+ ) {
+ desktopState.overrideDesktopModeSupportPerDisplay[DEFAULT_DISPLAY] = true
+ desktopState.overrideDesktopModeSupportPerDisplay[destinationDisplayId] = true
+ setUpLandscapeDisplay()
+ val task = setUpFreeformTask()
+ markTaskVisible(task)
+ task.isFocused = true
+ val runningTasks = ArrayList()
+ runningTasks.add(task)
+ val spyController = spy(controller)
+ val mockPendingIntent = mock(PendingIntent::class.java)
+ val mockDragEvent = mock(DragEvent::class.java)
+ val mockCallback = mock(Consumer::class.java)
+ val b = SurfaceControl.Builder()
+ b.setName("test surface")
+ val dragSurface = b.build()
+ whenever(focusTransitionObserver.globallyFocusedTaskId).thenReturn(task.taskId)
+ whenever(mockDragEvent.dragSurface).thenReturn(dragSurface)
+ whenever(mockDragEvent.x).thenReturn(inputCoordinate.x)
+ whenever(mockDragEvent.y).thenReturn(inputCoordinate.y)
+ whenever(mockDragEvent.displayId).thenReturn(destinationDisplayId)
+ whenever(multiInstanceHelper.supportsMultiInstanceSplit(anyOrNull(), anyInt()))
+ .thenReturn(true)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ doReturn(indicatorType)
+ .whenever(spyController)
+ .updateVisualIndicator(
+ eq(task),
+ anyOrNull(),
+ any(),
+ anyOrNull(),
+ anyOrNull(),
+ eq(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT),
+ )
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_OPEN),
+ any(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ eq(mockDragEvent),
+ )
+ )
+ .thenReturn(Binder())
+
+ spyController.onUnhandledDrag(
+ mockPendingIntent,
+ context.userId,
+ mockDragEvent,
+ mockCallback as Consumer,
+ )
+ val arg = argumentCaptor()
+ var expectedWindowingMode: Int
+ if (indicatorType == DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) {
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN
+ // Fullscreen launches currently use default transitions
+ verify(transitions).startTransition(any(), arg.capture(), anyOrNull())
+ } else {
+ expectedWindowingMode = WINDOWING_MODE_FREEFORM
+ if (tabTearingMinimizeAnimationFlagEnabled || tabTearingLaunchAnimationFlagEnabled) {
+ verify(desktopMixedTransitionHandler)
+ .startLaunchTransition(
+ eq(TRANSIT_OPEN),
+ arg.capture(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ eq(mockDragEvent),
+ )
+ } else {
+ // All other launches use a special handler.
+ verify(dragAndDropTransitionHandler)
+ .handleDropEvent(arg.capture(), eq(mockDragEvent))
+ }
+ }
+ assertThat(
+ ActivityOptions.fromBundle(arg.firstValue.hierarchyOps[0].launchOptions)
+ .launchWindowingMode
+ )
+ .isEqualTo(expectedWindowingMode)
+ assertThat(
+ ActivityOptions.fromBundle(arg.firstValue.hierarchyOps[0].launchOptions)
+ .launchBounds
+ )
+ .isEqualTo(expectedBounds)
+ }
+
+ private val desktopWallpaperIntent: Intent
+ get() = Intent(context, DesktopWallpaperActivity::class.java)
+
+ private fun launchHomeIntent(displayId: Int): Intent {
+ return Intent(Intent.ACTION_MAIN).apply {
+ if (displayId != DEFAULT_DISPLAY) {
+ addCategory(Intent.CATEGORY_SECONDARY_HOME)
+ } else {
+ addCategory(Intent.CATEGORY_HOME)
+ }
+ }
+ }
+
+ private fun addFreeformTaskAtPosition(
+ pos: DesktopTaskPosition,
+ stableBounds: Rect,
+ bounds: Rect = DEFAULT_LANDSCAPE_BOUNDS,
+ offsetPos: Point = Point(0, 0),
+ ): RunningTaskInfo {
+ val offset = pos.getTopLeftCoordinates(stableBounds, bounds)
+ val prevTaskBounds = Rect(bounds)
+ prevTaskBounds.offsetTo(offset.x + offsetPos.x, offset.y + offsetPos.y)
+ return setUpFreeformTask(bounds = prevTaskBounds)
+ }
+
+ private fun setUpFreeformTask(
+ displayId: Int = DEFAULT_DISPLAY,
+ bounds: Rect? = null,
+ active: Boolean = true,
+ background: Boolean = false,
+ deskId: Int? = null,
+ ): RunningTaskInfo {
+ val task = createFreeformTask(displayId, bounds)
+ val activityInfo = ActivityInfo()
+ activityInfo.applicationInfo = ApplicationInfo()
+ task.topActivityInfo = activityInfo
+ if (background) {
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null)
+ whenever(recentTasksController.findTaskInBackground(task.taskId))
+ .thenReturn(createRecentTaskInfo(taskId = task.taskId, displayId = displayId))
+ } else {
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ }
+ if (deskId != null) {
+ taskRepository.addTaskToDesk(
+ displayId,
+ deskId,
+ task.taskId,
+ isVisible = active,
+ TASK_BOUNDS,
+ )
+ } else {
+ taskRepository.addTask(displayId, task.taskId, isVisible = active, TASK_BOUNDS)
+ }
+ if (!background) {
+ runningTasks.add(task)
+ }
+ return task
+ }
+
+ private fun setUpPipTask(
+ autoEnterEnabled: Boolean,
+ displayId: Int = DEFAULT_DISPLAY,
+ deskId: Int = DEFAULT_DISPLAY,
+ ): RunningTaskInfo {
+ val task =
+ setUpFreeformTask(displayId = displayId, deskId = deskId).apply {
+ pictureInPictureParams =
+ PictureInPictureParams.Builder().setAutoEnterEnabled(autoEnterEnabled).build()
+ baseActivity = ComponentName("com.test.dummypackage", "TestClass")
+ }
+ whenever(packageManager.getApplicationInfoAsUser(any(), anyInt(), anyInt()))
+ .thenReturn(task.topActivityInfo?.applicationInfo)
+ return task
+ }
+
+ private fun setUpHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+ val task = createHomeTask(displayId)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ runningTasks.add(task)
+ return task
+ }
+
+ private fun setUpFullscreenTask(
+ displayId: Int = DEFAULT_DISPLAY,
+ isResizable: Boolean = true,
+ windowingMode: Int = WINDOWING_MODE_FULLSCREEN,
+ deviceOrientation: Int = ORIENTATION_LANDSCAPE,
+ screenOrientation: Int = SCREEN_ORIENTATION_UNSPECIFIED,
+ shouldLetterbox: Boolean = false,
+ gravity: Int = Gravity.NO_GRAVITY,
+ enableUserFullscreenOverride: Boolean = false,
+ enableSystemFullscreenOverride: Boolean = false,
+ aspectRatioOverrideApplied: Boolean = false,
+ visible: Boolean = true,
+ ): RunningTaskInfo {
+ val task = createFullscreenTask(displayId)
+ val activityInfo = ActivityInfo()
+ activityInfo.screenOrientation = screenOrientation
+ activityInfo.windowLayout = ActivityInfo.WindowLayout(0, 0F, 0, 0F, gravity, 0, 0)
+ activityInfo.applicationInfo = ApplicationInfo()
+ with(task) {
+ topActivityInfo = activityInfo
+ isResizeable = isResizable
+ configuration.orientation = deviceOrientation
+ configuration.windowConfiguration.windowingMode = windowingMode
+ appCompatTaskInfo.isUserFullscreenOverrideEnabled = enableUserFullscreenOverride
+ appCompatTaskInfo.isSystemFullscreenOverrideEnabled = enableSystemFullscreenOverride
+
+ if (deviceOrientation == ORIENTATION_LANDSCAPE) {
+ appCompatTaskInfo.topActivityAppBounds.set(
+ 0,
+ 0,
+ DISPLAY_DIMENSION_LONG,
+ DISPLAY_DIMENSION_SHORT,
+ )
+ } else {
+ appCompatTaskInfo.topActivityAppBounds.set(
+ 0,
+ 0,
+ DISPLAY_DIMENSION_SHORT,
+ DISPLAY_DIMENSION_LONG,
+ )
+ }
+
+ if (shouldLetterbox) {
+ appCompatTaskInfo.setHasMinAspectRatioOverride(aspectRatioOverrideApplied)
+ if (
+ deviceOrientation == ORIENTATION_LANDSCAPE &&
+ screenOrientation == SCREEN_ORIENTATION_PORTRAIT
+ ) {
+ // Letterbox to portrait size
+ appCompatTaskInfo.isTopActivityLetterboxed = true
+ appCompatTaskInfo.topActivityAppBounds.set(0, 0, 1200, 1600)
+ } else if (
+ deviceOrientation == ORIENTATION_PORTRAIT &&
+ screenOrientation == SCREEN_ORIENTATION_LANDSCAPE
+ ) {
+ // Letterbox to landscape size
+ appCompatTaskInfo.isTopActivityLetterboxed = true
+ appCompatTaskInfo.topActivityAppBounds.set(0, 0, 1600, 1200)
+ }
+ }
+ isVisible = visible
+ }
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ runningTasks.add(task)
+ return task
+ }
+
+ private fun setUpLandscapeDisplay() {
+ whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_LONG)
+ whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_SHORT)
+ val stableBounds =
+ Rect(0, 0, DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT - TASKBAR_FRAME_HEIGHT)
+ whenever(displayLayout.getStableBoundsForDesktopMode(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(stableBounds)
+ }
+ }
+
+ private fun setUpPortraitDisplay() {
+ whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_SHORT)
+ whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_LONG)
+ val stableBounds =
+ Rect(0, 0, DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG - TASKBAR_FRAME_HEIGHT)
+ whenever(displayLayout.getStableBoundsForDesktopMode(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(stableBounds)
+ }
+ }
+
+ private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+ val task = createSplitScreenTask(displayId)
+ whenever(splitScreenController.isTaskInSplitScreen(task.taskId)).thenReturn(true)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ runningTasks.add(task)
+ return task
+ }
+
+ private fun markTaskVisible(task: RunningTaskInfo) {
+ taskRepository.updateTask(task.displayId, task.taskId, isVisible = true, TASK_BOUNDS)
+ }
+
+ private fun markTaskHidden(task: RunningTaskInfo) {
+ taskRepository.updateTask(task.displayId, task.taskId, isVisible = false, TASK_BOUNDS)
+ }
+
+ private fun getLatestWct(
+ @WindowManager.TransitionType type: Int = TRANSIT_OPEN,
+ handlerClass: Class? = null,
+ ): WindowContainerTransaction {
+ val arg = argumentCaptor()
+ if (handlerClass == null) {
+ verify(transitions).startTransition(eq(type), arg.capture(), isNull())
+ } else {
+ verify(transitions).startTransition(eq(type), arg.capture(), isA(handlerClass))
+ }
+ return arg.lastValue
+ }
+
+ private fun getLatestToggleResizeDesktopTaskWct(
+ currentBounds: Rect? = null
+ ): WindowContainerTransaction {
+ val arg = argumentCaptor()
+ verify(toggleResizeDesktopTaskTransitionHandler, atLeastOnce())
+ .startTransition(arg.capture(), eq(currentBounds), isNull())
+ return arg.lastValue
+ }
+
+ private fun getLatestDesktopMixedTaskWct(
+ @WindowManager.TransitionType type: Int = TRANSIT_OPEN
+ ): WindowContainerTransaction {
+ val arg = argumentCaptor()
+ verify(desktopMixedTransitionHandler)
+ .startLaunchTransition(
+ eq(type),
+ arg.capture(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ return arg.lastValue
+ }
+
+ private fun getLatestTransition(): WindowContainerTransaction {
+ val arg = argumentCaptor()
+ verify(transitions).startTransition(any(), arg.capture(), anyOrNull())
+ return arg.lastValue
+ }
+
+ private fun getLatestEnterDesktopWct(): WindowContainerTransaction {
+ val arg = argumentCaptor()
+ verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any())
+ return arg.lastValue
+ }
+
+ private fun getLatestDragToDesktopWct(): WindowContainerTransaction {
+ val arg = argumentCaptor()
+ verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(arg.capture())
+ return arg.lastValue
+ }
+
+ private fun getLatestExitDesktopWct(): WindowContainerTransaction {
+ val arg = argumentCaptor()
+ verify(exitDesktopTransitionHandler).startTransition(any(), arg.capture(), any(), any())
+ return arg.lastValue
+ }
+
+ private fun findBoundsChange(wct: WindowContainerTransaction, task: RunningTaskInfo): Rect? =
+ wct.changes.entries
+ .find { (token, change) ->
+ token == task.token.asBinder() &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0
+ }
+ ?.value
+ ?.configuration
+ ?.windowConfiguration
+ ?.bounds
+
+ private fun verifyWCTNotExecuted() {
+ verify(transitions, never()).startTransition(anyInt(), any(), isNull())
+ }
+
+ private fun verifyExitDesktopWCTNotExecuted() {
+ verify(exitDesktopTransitionHandler, never()).startTransition(any(), any(), any(), any())
+ }
+
+ private fun verifyEnterDesktopWCTNotExecuted() {
+ verify(enterDesktopTransitionHandler, never()).moveToDesktop(any(), any())
+ }
+
+ private fun createTransition(
+ task: RunningTaskInfo?,
+ @WindowManager.TransitionType type: Int = TRANSIT_OPEN,
+ ): TransitionRequestInfo {
+ return TransitionRequestInfo(type, task, /* remoteTransition= */ null)
+ }
+
+ private companion object {
+ const val SECOND_DISPLAY = 2
+ val STABLE_BOUNDS = Rect(0, 0, 1000, 1000)
+ const val MAX_TASK_LIMIT = 6
+ private const val TASKBAR_FRAME_HEIGHT = 200
+ private const val FLOAT_TOLERANCE = 0.005f
+ private const val DEFAULT_DESK_ID = 100
+ // For testing disconnecting a display containing a desk.
+ private const val DISCONNECTED_DESK_ID = 200
+ private val TASK_BOUNDS = Rect(100, 100, 300, 300)
+
+ private const val TO_DESKTOP_ANIM_DURATION = 336
+
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List =
+ FlagsParameterization.allCombinationsOf(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_FIRST_BASED_DEFAULT_TO_DESKTOP_BUGFIX,
+ Flags.FLAG_ENABLE_DESKTOP_FIRST_FULLSCREEN_REFOCUS_BUGFIX,
+ )
+ }
}
private fun WindowContainerTransaction.assertIndexInBounds(index: Int) {
- assertWithMessage("WCT does not have a hierarchy operation at index $index")
- .that(hierarchyOps.size)
- .isGreaterThan(index)
+ assertWithMessage("WCT does not have a hierarchy operation at index $index")
+ .that(hierarchyOps.size)
+ .isGreaterThan(index)
+}
+
+private fun WindowContainerTransaction.assertHop(
+ predicate: (WindowContainerTransaction.HierarchyOp) -> Boolean
+) {
+ assertThat(hierarchyOps.any(predicate)).isTrue()
+}
+
+private fun WindowContainerTransaction.assertWithoutHop(
+ predicate: (WindowContainerTransaction.HierarchyOp) -> Boolean
+) {
+ assertThat(hierarchyOps.none(predicate)).isTrue()
+}
+
+private fun WindowContainerTransaction.indexOfReorder(
+ token: WindowContainerToken,
+ toTop: Boolean? = null,
+): Int {
+ val hop = hierarchyOps.singleOrNull(ReorderPredicate(token, toTop)) ?: return -1
+ return hierarchyOps.indexOf(hop)
+}
+
+private fun WindowContainerTransaction.indexOfReorder(
+ task: RunningTaskInfo,
+ toTop: Boolean? = null,
+): Int {
+ return indexOfReorder(task.token, toTop)
+}
+
+private class ReorderPredicate(
+ val token: WindowContainerToken,
+ val toTop: Boolean? = null,
+ val includingParents: Boolean? = null,
+) : ((WindowContainerTransaction.HierarchyOp) -> Boolean) {
+ override fun invoke(hop: WindowContainerTransaction.HierarchyOp): Boolean =
+ hop.type == HIERARCHY_OP_TYPE_REORDER &&
+ (toTop == null || hop.toTop == toTop) &&
+ (includingParents == null || hop.includingParents() == includingParents) &&
+ hop.container == token.asBinder()
+}
+
+private class ReparentPredicate(
+ val token: WindowContainerToken,
+ val parentToken: WindowContainerToken,
+ val toTop: Boolean? = null,
+) : ((WindowContainerTransaction.HierarchyOp) -> Boolean) {
+ override fun invoke(hop: WindowContainerTransaction.HierarchyOp): Boolean =
+ hop.isReparent &&
+ (toTop == null || hop.toTop == toTop) &&
+ hop.container == token.asBinder() &&
+ hop.newParent == parentToken.asBinder()
+}
+
+private fun WindowContainerTransaction.assertReorder(
+ task: RunningTaskInfo,
+ toTop: Boolean? = null,
+ includingParents: Boolean? = null,
+) {
+ assertReorder(task.token, toTop, includingParents)
+}
+
+private fun WindowContainerTransaction.assertReorder(
+ token: WindowContainerToken,
+ toTop: Boolean? = null,
+ includingParents: Boolean? = null,
+) {
+ assertHop(ReorderPredicate(token, toTop, includingParents))
}
private fun WindowContainerTransaction.assertReorderAt(
index: Int,
task: RunningTaskInfo,
- toTop: Boolean? = null
+ toTop: Boolean? = null,
+ includingParents: Boolean? = null,
) {
- assertIndexInBounds(index)
- val op = hierarchyOps[index]
- assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REORDER)
- assertThat(op.container).isEqualTo(task.token.asBinder())
- toTop?.let { assertThat(op.toTop).isEqualTo(it) }
+ assertIndexInBounds(index)
+ val op = hierarchyOps[index]
+ assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REORDER)
+ assertThat(op.container).isEqualTo(task.token.asBinder())
+ toTop?.let { assertThat(op.toTop).isEqualTo(it) }
+ includingParents?.let { assertThat(op.includingParents()).isEqualTo(it) }
+}
+
+private fun WindowContainerTransaction.assertReorderAt(
+ index: Int,
+ token: WindowContainerToken,
+ toTop: Boolean? = null,
+) {
+ assertIndexInBounds(index)
+ val op = hierarchyOps[index]
+ assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REORDER)
+ assertThat(op.container).isEqualTo(token.asBinder())
+ toTop?.let { assertThat(op.toTop).isEqualTo(it) }
}
private fun WindowContainerTransaction.assertReorderSequence(vararg tasks: RunningTaskInfo) {
- for (i in tasks.indices) {
- assertReorderAt(i, tasks[i])
- }
+ for (i in tasks.indices) {
+ assertReorderAt(i, tasks[i])
+ }
+}
+
+/** Checks if the reorder hierarchy operations in [range] correspond to [tasks] list */
+private fun WindowContainerTransaction.assertReorderSequenceInRange(
+ range: IntRange,
+ vararg tasks: RunningTaskInfo,
+) {
+ assertThat(hierarchyOps.slice(range).map { it.type to it.container })
+ .containsExactlyElementsIn(tasks.map { HIERARCHY_OP_TYPE_REORDER to it.token.asBinder() })
+ .inOrder()
+}
+
+private fun WindowContainerTransaction.assertRemove(token: WindowContainerToken) {
+ assertThat(
+ hierarchyOps.any { hop ->
+ hop.container == token.asBinder() && hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK
+ }
+ )
+ .isTrue()
}
private fun WindowContainerTransaction.assertRemoveAt(index: Int, token: WindowContainerToken) {
- assertIndexInBounds(index)
- val op = hierarchyOps[index]
- assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
- assertThat(op.container).isEqualTo(token.asBinder())
+ assertIndexInBounds(index)
+ val op = hierarchyOps[index]
+ assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
+ assertThat(op.container).isEqualTo(token.asBinder())
+}
+
+private fun WindowContainerTransaction.assertNoRemoveAt(index: Int, token: WindowContainerToken) {
+ assertIndexInBounds(index)
+ val op = hierarchyOps[index]
+ assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
+ assertThat(op.container).isEqualTo(token.asBinder())
+}
+
+private fun WindowContainerTransaction.hasRemoveAt(index: Int, token: WindowContainerToken) {
+ assertIndexInBounds(index)
+ val op = hierarchyOps[index]
+ assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
+ assertThat(op.container).isEqualTo(token.asBinder())
+}
+
+private fun WindowContainerTransaction.assertPendingIntent(intent: Intent) {
+ assertHop { hop ->
+ hop.type == HIERARCHY_OP_TYPE_PENDING_INTENT &&
+ hop.pendingIntent?.intent?.component == intent.component &&
+ hop.pendingIntent?.intent?.categories == intent.categories
+ }
+}
+
+private fun WindowContainerTransaction.assertWithoutPendingIntent(intent: Intent) {
+ assertWithoutHop { hop ->
+ hop.type == HIERARCHY_OP_TYPE_PENDING_INTENT &&
+ hop.pendingIntent?.intent?.component == intent.component
+ }
}
private fun WindowContainerTransaction.assertPendingIntentAt(index: Int, intent: Intent) {
- assertIndexInBounds(index)
- val op = hierarchyOps[index]
- assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_PENDING_INTENT)
- assertThat(op.pendingIntent?.intent?.component).isEqualTo(intent.component)
+ assertIndexInBounds(index)
+ val op = hierarchyOps[index]
+ assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_PENDING_INTENT)
+ assertThat(op.pendingIntent?.intent?.component).isEqualTo(intent.component)
+ assertThat(op.pendingIntent?.intent?.categories).isEqualTo(intent.categories)
+}
+
+private fun WindowContainerTransaction.assertPendingIntentActivityOptionsLaunchDisplayId(
+ displayId: Int
+) {
+ assertHop { hop ->
+ hop.launchOptions != null && ActivityOptions(hop.launchOptions).launchDisplayId == displayId
+ }
+}
+
+private fun WindowContainerTransaction.assertPendingIntentActivityOptionsLaunchDisplayIdAt(
+ index: Int,
+ displayId: Int,
+) {
+ assertIndexInBounds(index)
+ val op = hierarchyOps[index]
+ if (op.launchOptions != null) {
+ val options = ActivityOptions(op.launchOptions)
+ assertThat(options.launchDisplayId).isEqualTo(displayId)
+ }
+}
+
+private fun WindowContainerTransaction.assertLaunchTask(taskId: Int, windowingMode: Int) {
+ val keyLaunchWindowingMode = "android.activity.windowingMode"
+
+ assertHop { hop ->
+ hop.type == HIERARCHY_OP_TYPE_LAUNCH_TASK &&
+ hop.launchOptions?.getInt(LAUNCH_KEY_TASK_ID) == taskId &&
+ hop.launchOptions?.getInt(keyLaunchWindowingMode, WINDOWING_MODE_UNDEFINED) ==
+ windowingMode
+ }
+}
+
+private fun WindowContainerTransaction.assertLaunchTaskOnDisplay(displayId: Int) {
+ val keyLaunchWindowingMode = "android.activity.windowingMode"
+ val keyLaunchDisplayId = "android.activity.launchDisplayId"
+
+ assertHop { hop -> hop.launchOptions?.getInt(keyLaunchDisplayId, DEFAULT_DISPLAY) == displayId }
}
private fun WindowContainerTransaction.assertLaunchTaskAt(
index: Int,
taskId: Int,
- windowingMode: Int
+ windowingMode: Int,
) {
- val keyLaunchWindowingMode = "android.activity.windowingMode"
+ val keyLaunchWindowingMode = "android.activity.windowingMode"
- assertIndexInBounds(index)
- val op = hierarchyOps[index]
- assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_LAUNCH_TASK)
- assertThat(op.launchOptions?.getInt(LAUNCH_KEY_TASK_ID)).isEqualTo(taskId)
- assertThat(op.launchOptions?.getInt(keyLaunchWindowingMode, WINDOWING_MODE_UNDEFINED))
- .isEqualTo(windowingMode)
+ assertIndexInBounds(index)
+ val op = hierarchyOps[index]
+ assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_LAUNCH_TASK)
+ assertThat(op.launchOptions?.getInt(LAUNCH_KEY_TASK_ID)).isEqualTo(taskId)
+ assertThat(op.launchOptions?.getInt(keyLaunchWindowingMode, WINDOWING_MODE_UNDEFINED))
+ .isEqualTo(windowingMode)
}
private fun WindowContainerTransaction?.anyDensityConfigChange(
token: WindowContainerToken
): Boolean {
- return this?.changes?.any { change ->
- change.key == token.asBinder() && ((change.value.configSetMask and CONFIG_DENSITY) != 0)
- } ?: false
+ return this?.changes?.any { change ->
+ change.key == token.asBinder() && ((change.value.configSetMask and CONFIG_DENSITY) != 0)
+ } ?: false
}
-private fun createTaskInfo(id: Int) =
+private fun WindowContainerTransaction?.anyWindowingModeChange(
+ token: WindowContainerToken
+): Boolean {
+ return this?.changes?.any { change ->
+ change.key == token.asBinder() && change.value.windowingMode >= 0
+ } ?: false
+}
+
+private fun createRecentTaskInfo(taskId: Int, displayId: Int = DEFAULT_DISPLAY) =
RecentTaskInfo().apply {
- taskId = id
- token = WindowContainerToken(mock(IWindowContainerToken::class.java))
+ this.taskId = taskId
+ this.displayId = displayId
+ token = WindowContainerToken(mock(IWindowContainerToken::class.java))
+ positionInParent = Point()
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index 77f917cc28..f145f88012 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -17,36 +17,69 @@
package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.Rect
import android.os.Binder
-import android.platform.test.flag.junit.SetFlagsRule
+import android.os.IBinder
+import android.os.UserManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
+import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_BACK
+import android.window.TransitionInfo
import android.window.WindowContainerTransaction
+import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
import androidx.test.filters.SmallTest
-import com.android.dx.mockito.inline.extended.ExtendedMockito
-import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
-import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+import com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND
+import com.android.window.flags.Flags.FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
+import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer
+import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
+import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
+import com.android.wm.shell.shared.desktopmode.FakeDesktopConfig
+import com.android.wm.shell.shared.desktopmode.FakeDesktopState
+import com.android.wm.shell.sysui.ShellController
+import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.StubTransaction
+import com.android.wm.shell.windowdecor.tiling.SnapEventHandler
import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito.any
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
-import org.mockito.quality.Strictness
-
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.whenever
/**
* Test class for {@link DesktopTasksLimiter}
@@ -55,99 +88,160 @@ import org.mockito.quality.Strictness
*/
@SmallTest
@RunWith(AndroidTestingRunner::class)
+@ExperimentalCoroutinesApi
class DesktopTasksLimiterTest : ShellTestCase() {
- @JvmField
- @Rule
- val setFlagsRule = SetFlagsRule()
-
@Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
+ @Mock lateinit var desksOrganizer: DesksOrganizer
@Mock lateinit var transitions: Transitions
+ @Mock lateinit var testExecutor: ShellExecutor
+ @Mock lateinit var persistentRepository: DesktopPersistentRepository
+ @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer
+ @Mock lateinit var userManager: UserManager
+ @Mock lateinit var shellController: ShellController
+ @Mock lateinit var desktopMixedTransitionHandler: DesktopMixedTransitionHandler
+ @Mock lateinit var snapEventHandler: SnapEventHandler
- private lateinit var mockitoSession: StaticMockitoSession
private lateinit var desktopTasksLimiter: DesktopTasksLimiter
- private lateinit var desktopTaskRepo: DesktopModeTaskRepository
+ private lateinit var userRepositories: DesktopUserRepositories
+ private lateinit var desktopTaskRepo: DesktopRepository
+ private lateinit var shellInit: ShellInit
+ private lateinit var testScope: CoroutineScope
+ private val desktopState = FakeDesktopState()
+ private val desktopConfig = FakeDesktopConfig()
@Before
fun setUp() {
- mockitoSession = ExtendedMockito.mockitoSession().strictness(Strictness.LENIENT)
- .spyStatic(DesktopModeStatus::class.java).startMocking()
- doReturn(true).`when`{ DesktopModeStatus.canEnterDesktopMode(any()) }
+ desktopState.canEnterDesktopMode = true
+ shellInit = spy(ShellInit(testExecutor))
+ Dispatchers.setMain(StandardTestDispatcher())
+ testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
- desktopTaskRepo = DesktopModeTaskRepository()
-
- desktopTasksLimiter = DesktopTasksLimiter(
- transitions, desktopTaskRepo, shellTaskOrganizer)
+ userRepositories =
+ DesktopUserRepositories(
+ shellInit,
+ shellController,
+ persistentRepository,
+ repositoryInitializer,
+ testScope,
+ userManager,
+ desktopState,
+ desktopConfig,
+ )
+ desktopTaskRepo = userRepositories.current
+ desktopTasksLimiter =
+ DesktopTasksLimiter(
+ transitions,
+ userRepositories,
+ shellTaskOrganizer,
+ desksOrganizer,
+ desktopMixedTransitionHandler,
+ MAX_TASK_LIMIT,
+ )
+ desktopTasksLimiter.snapEventHandler = snapEventHandler
}
@After
fun tearDown() {
- mockitoSession.finishMocking()
+ testScope.cancel()
}
- // Currently, the task limit can be overridden through an adb flag. This test ensures the limit
- // hasn't been overridden.
@Test
- fun getMaxTaskLimit_isSameAsConstant() {
- assertThat(desktopTasksLimiter.getMaxTaskLimit()).isEqualTo(
- DesktopModeStatus.DEFAULT_MAX_TASK_LIMIT)
+ fun createDesktopTasksLimiter_withZeroLimit_shouldThrow() {
+ assertFailsWith {
+ DesktopTasksLimiter(
+ transitions,
+ userRepositories,
+ shellTaskOrganizer,
+ desksOrganizer,
+ desktopMixedTransitionHandler,
+ 0,
+ )
+ }
+ }
+
+ @Test
+ fun createDesktopTasksLimiter_withNegativeLimit_shouldThrow() {
+ assertFailsWith {
+ DesktopTasksLimiter(
+ transitions,
+ userRepositories,
+ shellTaskOrganizer,
+ desksOrganizer,
+ desktopMixedTransitionHandler,
+ -5,
+ )
+ }
+ }
+
+ @Test
+ fun createDesktopTasksLimiter_withNoLimit_shouldSucceed() {
+ // Instantiation should succeed without an error.
+ DesktopTasksLimiter(
+ transitions,
+ userRepositories,
+ shellTaskOrganizer,
+ desksOrganizer,
+ desktopMixedTransitionHandler,
+ maxTasksLimit = null,
+ )
}
@Test
fun addPendingMinimizeTransition_taskIsNotMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val task = setUpFreeformTask()
markTaskHidden(task)
- desktopTasksLimiter.addPendingMinimizeChange(Binder(), displayId = 1, taskId = task.taskId)
+ addPendingMinimizeChange(Binder(), displayId = 1, taskId = task.taskId)
assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse()
}
@Test
fun onTransitionReady_noPendingTransition_taskIsNotMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val task = setUpFreeformTask()
markTaskHidden(task)
- desktopTasksLimiter.getTransitionObserver().onTransitionReady(
- Binder() /* transition */,
- TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(),
- StubTransaction() /* startTransaction */,
- StubTransaction() /* finishTransaction */)
+ callOnTransitionReady(
+ Binder(),
+ TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(),
+ )
assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse()
}
@Test
fun onTransitionReady_differentPendingTransition_taskIsNotMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val pendingTransition = Binder()
val taskTransition = Binder()
val task = setUpFreeformTask()
markTaskHidden(task)
- desktopTasksLimiter.addPendingMinimizeChange(
- pendingTransition, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
+ addPendingMinimizeChange(pendingTransition, taskId = task.taskId)
- desktopTasksLimiter.getTransitionObserver().onTransitionReady(
- taskTransition /* transition */,
+ callOnTransitionReady(
+ taskTransition,
TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(),
- StubTransaction() /* startTransaction */,
- StubTransaction() /* finishTransaction */)
+ )
assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse()
}
@Test
fun onTransitionReady_pendingTransition_noTaskChange_taskVisible_taskIsNotMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val transition = Binder()
val task = setUpFreeformTask()
markTaskVisible(task)
- desktopTasksLimiter.addPendingMinimizeChange(
- transition, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
+ addPendingMinimizeChange(transition, taskId = task.taskId)
- desktopTasksLimiter.getTransitionObserver().onTransitionReady(
- transition,
- TransitionInfoBuilder(TRANSIT_OPEN).build(),
- StubTransaction() /* startTransaction */,
- StubTransaction() /* finishTransaction */)
+ callOnTransitionReady(transition, TransitionInfoBuilder(TRANSIT_OPEN).build())
assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse()
}
@@ -155,166 +249,737 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun onTransitionReady_pendingTransition_noTaskChange_taskInvisible_taskIsMinimized() {
val transition = Binder()
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val task = setUpFreeformTask()
markTaskHidden(task)
- desktopTasksLimiter.addPendingMinimizeChange(
- transition, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
+ addPendingMinimizeChange(transition, taskId = task.taskId)
- desktopTasksLimiter.getTransitionObserver().onTransitionReady(
- transition,
- TransitionInfoBuilder(TRANSIT_OPEN).build(),
- StubTransaction() /* startTransaction */,
- StubTransaction() /* finishTransaction */)
+ callOnTransitionReady(transition, TransitionInfoBuilder(TRANSIT_OPEN).build())
assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue()
}
@Test
fun onTransitionReady_pendingTransition_changeTaskToBack_taskIsMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val transition = Binder()
val task = setUpFreeformTask()
- desktopTasksLimiter.addPendingMinimizeChange(
- transition, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
+ addPendingMinimizeChange(transition, taskId = task.taskId)
- desktopTasksLimiter.getTransitionObserver().onTransitionReady(
- transition,
- TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(),
- StubTransaction() /* startTransaction */,
- StubTransaction() /* finishTransaction */)
+ callOnTransitionReady(
+ transition,
+ TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(),
+ )
assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue()
}
+ @Test
+ fun onTransitionReady_pendingTransition_changeTaskToBack_boundsSaved() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val bounds = Rect(0, 0, 200, 200)
+ val transition = Binder()
+ val task = setUpFreeformTask()
+ addPendingMinimizeChange(transition, taskId = task.taskId)
+
+ val change =
+ TransitionInfo.Change(task.token, mock(SurfaceControl::class.java)).apply {
+ mode = TRANSIT_TO_BACK
+ taskInfo = task
+ setStartAbsBounds(bounds)
+ }
+ callOnTransitionReady(
+ transition,
+ TransitionInfo(TRANSIT_OPEN, TransitionInfo.FLAG_NONE).apply { addChange(change) },
+ )
+
+ assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue()
+ assertThat(desktopTaskRepo.removeBoundsBeforeMinimize(taskId = task.taskId))
+ .isEqualTo(bounds)
+ }
+
@Test
fun onTransitionReady_transitionMergedFromPending_taskIsMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
val mergedTransition = Binder()
val newTransition = Binder()
val task = setUpFreeformTask()
- desktopTasksLimiter.addPendingMinimizeChange(
- mergedTransition, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
- desktopTasksLimiter.getTransitionObserver().onTransitionMerged(
- mergedTransition, newTransition)
+ addPendingMinimizeChange(mergedTransition, taskId = task.taskId)
+ desktopTasksLimiter
+ .getTransitionObserver()
+ .onTransitionMerged(mergedTransition, newTransition)
+ desktopTasksLimiter
+ .getTransitionObserver()
+ .onTransitionMerged(mergedTransition, newTransition)
- desktopTasksLimiter.getTransitionObserver().onTransitionReady(
+ callOnTransitionReady(
newTransition,
TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(),
- StubTransaction() /* startTransaction */,
- StubTransaction() /* finishTransaction */)
+ )
assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue()
}
@Test
- fun addAndGetMinimizeTaskChangesIfNeeded_tasksWithinLimit_noTaskMinimized() {
- val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
- (1.. setUpFreeformTask() }
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun removeLeftoverMinimizedTasks_activeNonMinimizedTasksStillAround_doesNothing() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.addTask(
+ displayId = DEFAULT_DISPLAY,
+ taskId = 1,
+ isVisible = true,
+ taskBounds = TASK_BOUNDS,
+ )
+ desktopTaskRepo.addTask(
+ displayId = DEFAULT_DISPLAY,
+ taskId = 1,
+ isVisible = true,
+ taskBounds = TASK_BOUNDS,
+ )
+ desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2)
+
+ val wct = WindowContainerTransaction()
+ desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks(
+ DEFAULT_DISPLAY,
+ wct,
+ )
+
+ assertThat(wct.isEmpty).isTrue()
+ }
+
+ @Test
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun removeLeftoverMinimizedTasks_noMinimizedTasks_doesNothing() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val wct = WindowContainerTransaction()
+ desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks(
+ DEFAULT_DISPLAY,
+ wct,
+ )
+
+ assertThat(wct.isEmpty).isTrue()
+ }
+
+ @Test
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun removeLeftoverMinimizedTasks_onlyMinimizedTasksLeft_removesAllMinimizedTasks() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task1.taskId)
+ desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+
+ val wct = WindowContainerTransaction()
+ desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks(
+ DEFAULT_DISPLAY,
+ wct,
+ )
+
+ assertThat(wct.hierarchyOps).hasSize(2)
+ assertThat(wct.hierarchyOps[0].type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
+ assertThat(wct.hierarchyOps[0].container).isEqualTo(task1.token.asBinder())
+ assertThat(wct.hierarchyOps[1].type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
+ assertThat(wct.hierarchyOps[1].container).isEqualTo(task2.token.asBinder())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun removeLeftoverMinimizedTasks_onlyMinimizedTasksLeft_backNavEnabled_doesNothing() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task1.taskId)
+ desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+
+ val wct = WindowContainerTransaction()
+ desktopTasksLimiter.leftoverMinimizedTasksRemover.onActiveTasksChanged(DEFAULT_DISPLAY)
+
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
+ @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun addAndGetMinimizeTaskChanges_tasksWithinLimit_multiDesksDisabled_noTaskMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ (1.. setUpFreeformTask() }
val wct = WindowContainerTransaction()
val minimizedTaskId =
- desktopTasksLimiter.addAndGetMinimizeTaskChangesIfNeeded(
- displayId = DEFAULT_DISPLAY,
- wct = wct,
- newFrontTaskInfo = setUpFreeformTask())
+ desktopTasksLimiter.addAndGetMinimizeTaskChanges(
+ deskId = 0,
+ wct = wct,
+ newFrontTaskId = setUpFreeformTask().taskId,
+ )
assertThat(minimizedTaskId).isNull()
assertThat(wct.hierarchyOps).isEmpty() // No reordering operations added
}
@Test
- fun addAndGetMinimizeTaskChangesIfNeeded_tasksAboveLimit_backTaskMinimized() {
- val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
- // The following list will be ordered bottom -> top, as the last task is moved to top last.
- val tasks = (1..taskLimit).map { setUpFreeformTask() }
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun addAndGetMinimizeTaskChanges_tasksWithinLimit_multiDesksEnabled_noTaskMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ (1.. setUpFreeformTask() }
val wct = WindowContainerTransaction()
val minimizedTaskId =
- desktopTasksLimiter.addAndGetMinimizeTaskChangesIfNeeded(
- displayId = DEFAULT_DISPLAY,
- wct = wct,
- newFrontTaskInfo = setUpFreeformTask())
+ desktopTasksLimiter.addAndGetMinimizeTaskChanges(
+ deskId = 0,
+ wct = wct,
+ newFrontTaskId = setUpFreeformTask().taskId,
+ )
- assertThat(minimizedTaskId).isEqualTo(tasks.first())
+ assertThat(minimizedTaskId).isNull()
+ verify(desksOrganizer, never()).minimizeTask(eq(wct), eq(0), any())
+ }
+
+ @Test
+ @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun addAndGetMinimizeTaskChanges_tasksAboveLimit_multiDesksDisabled_backTaskMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ // The following list will be ordered bottom -> top, as the last task is moved to top last.
+ val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
+
+ val wct = WindowContainerTransaction()
+ val minimizedTaskId =
+ desktopTasksLimiter.addAndGetMinimizeTaskChanges(
+ deskId = DEFAULT_DISPLAY,
+ wct = wct,
+ newFrontTaskId = setUpFreeformTask().taskId,
+ )
+
+ assertThat(minimizedTaskId).isEqualTo(tasks.first().taskId)
assertThat(wct.hierarchyOps.size).isEqualTo(1)
assertThat(wct.hierarchyOps[0].type).isEqualTo(HIERARCHY_OP_TYPE_REORDER)
assertThat(wct.hierarchyOps[0].toTop).isFalse() // Reorder to bottom
}
@Test
- fun addAndGetMinimizeTaskChangesIfNeeded_nonMinimizedTasksWithinLimit_noTaskMinimized() {
- val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
- val tasks = (1..taskLimit).map { setUpFreeformTask() }
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun addAndGetMinimizeTaskChanges_tasksAboveLimit_multiDesksEnabled_backTaskMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ // The following list will be ordered bottom -> top, as the last task is moved to top last.
+ val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
+
+ val wct = WindowContainerTransaction()
+ val minimizedTaskId =
+ desktopTasksLimiter.addAndGetMinimizeTaskChanges(
+ deskId = DEFAULT_DISPLAY,
+ wct = wct,
+ newFrontTaskId = setUpFreeformTask().taskId,
+ )
+
+ assertThat(minimizedTaskId).isEqualTo(tasks.first().taskId)
+ verify(desksOrganizer).minimizeTask(wct, deskId = 0, tasks.first())
+ }
+
+ @Test
+ @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun addAndGetMinimizeTaskChanges_nonMinimizedTasksWithinLimit_multiDesksDisabled_noTaskMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = tasks[0].taskId)
val wct = WindowContainerTransaction()
val minimizedTaskId =
- desktopTasksLimiter.addAndGetMinimizeTaskChangesIfNeeded(
- displayId = 0,
- wct = wct,
- newFrontTaskInfo = setUpFreeformTask())
+ desktopTasksLimiter.addAndGetMinimizeTaskChanges(
+ deskId = 0,
+ wct = wct,
+ newFrontTaskId = setUpFreeformTask().taskId,
+ )
assertThat(minimizedTaskId).isNull()
assertThat(wct.hierarchyOps).isEmpty() // No reordering operations added
}
@Test
- fun getTaskToMinimizeIfNeeded_tasksWithinLimit_returnsNull() {
- val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
- val tasks = (1..taskLimit).map { setUpFreeformTask() }
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun addAndGetMinimizeTaskChanges_nonMinimizedTasksWithinLimit_multiDesksEnabled_noTaskMinimized() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
+ desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = tasks[0].taskId)
- val minimizedTask = desktopTasksLimiter.getTaskToMinimizeIfNeeded(
- visibleFreeformTaskIdsOrderedFrontToBack = tasks.map { it.taskId })
+ val wct = WindowContainerTransaction()
+ val minimizedTaskId =
+ desktopTasksLimiter.addAndGetMinimizeTaskChanges(
+ deskId = 0,
+ wct = wct,
+ newFrontTaskId = setUpFreeformTask().taskId,
+ )
+
+ assertThat(minimizedTaskId).isNull()
+ verify(desksOrganizer, never()).minimizeTask(eq(wct), eq(0), any())
+ }
+
+ @Test
+ fun getTaskToMinimize_tasksWithinLimit_returnsNull() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
+
+ val minimizedTask =
+ desktopTasksLimiter.getTaskIdToMinimize(visibleOrderedTasks = tasks.map { it.taskId })
assertThat(minimizedTask).isNull()
}
@Test
- fun getTaskToMinimizeIfNeeded_tasksAboveLimit_returnsBackTask() {
- val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
- val tasks = (1..taskLimit + 1).map { setUpFreeformTask() }
+ fun getTaskToMinimize_tasksAboveLimit_returnsBackTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val tasks = (1..MAX_TASK_LIMIT + 1).map { setUpFreeformTask() }
- val minimizedTask = desktopTasksLimiter.getTaskToMinimizeIfNeeded(
- visibleFreeformTaskIdsOrderedFrontToBack = tasks.map { it.taskId })
+ val minimizedTask =
+ desktopTasksLimiter.getTaskIdToMinimize(visibleOrderedTasks = tasks.map { it.taskId })
// first == front, last == back
- assertThat(minimizedTask).isEqualTo(tasks.last())
+ assertThat(minimizedTask).isEqualTo(tasks.last().taskId)
}
@Test
- fun getTaskToMinimizeIfNeeded_withNewTask_tasksAboveLimit_returnsBackTask() {
- val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
- val tasks = (1..taskLimit).map { setUpFreeformTask() }
+ fun getTaskToMinimize_tasksAboveLimit_otherLimit_returnsBackTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTasksLimiter =
+ DesktopTasksLimiter(
+ transitions,
+ userRepositories,
+ shellTaskOrganizer,
+ desksOrganizer,
+ desktopMixedTransitionHandler,
+ MAX_TASK_LIMIT2,
+ )
+ val tasks = (1..MAX_TASK_LIMIT2 + 1).map { setUpFreeformTask() }
- val minimizedTask = desktopTasksLimiter.getTaskToMinimizeIfNeeded(
- visibleFreeformTaskIdsOrderedFrontToBack = tasks.map { it.taskId },
- newTaskIdInFront = setUpFreeformTask().taskId)
+ val minimizedTask =
+ desktopTasksLimiter.getTaskIdToMinimize(visibleOrderedTasks = tasks.map { it.taskId })
// first == front, last == back
- assertThat(minimizedTask).isEqualTo(tasks.last())
+ assertThat(minimizedTask).isEqualTo(tasks.last().taskId)
}
- private fun setUpFreeformTask(
- displayId: Int = DEFAULT_DISPLAY,
- ): RunningTaskInfo {
+ @Test
+ fun getTaskToMinimize_withNewTask_tasksAboveLimit_returnsBackTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
+
+ val minimizedTask =
+ desktopTasksLimiter.getTaskIdToMinimize(
+ visibleOrderedTasks = tasks.map { it.taskId },
+ newTaskIdInFront = setUpFreeformTask().taskId,
+ )
+
+ // first == front, last == back
+ assertThat(minimizedTask).isEqualTo(tasks.last().taskId)
+ }
+
+ @Test
+ fun getTaskToMinimize_tasksAtLimit_newIntentReturnsBackTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
+ val minimizedTask =
+ desktopTasksLimiter.getTaskIdToMinimize(
+ visibleOrderedTasks = tasks.map { it.taskId },
+ newTaskIdInFront = null,
+ launchingNewIntent = true,
+ )
+
+ // first == front, last == back
+ assertThat(minimizedTask).isEqualTo(tasks.last().taskId)
+ }
+
+ @Test
+ fun getMinimizingTask_noPendingTransition_returnsNull() {
+ val transition = Binder()
+
+ assertThat(desktopTasksLimiter.getMinimizingTask(transition)).isNull()
+ }
+
+ @Test
+ fun getMinimizingTask_pendingTaskTransition_returnsTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val transition = Binder()
+ val task = setUpFreeformTask()
+ addPendingMinimizeChange(
+ transition,
+ taskId = task.taskId,
+ minimizeReason = MinimizeReason.TASK_LIMIT,
+ )
+
+ assertThat(desktopTasksLimiter.getMinimizingTask(transition))
+ .isEqualTo(
+ createTaskDetails(taskId = task.taskId, minimizeReason = MinimizeReason.TASK_LIMIT)
+ )
+ }
+
+ @Test
+ fun getMinimizingTask_activeTaskTransition_returnsTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val transition = Binder()
+ val task = setUpFreeformTask()
+ addPendingMinimizeChange(
+ transition,
+ taskId = task.taskId,
+ minimizeReason = MinimizeReason.TASK_LIMIT,
+ )
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build()
+
+ callOnTransitionReady(transition, transitionInfo)
+
+ assertThat(desktopTasksLimiter.getMinimizingTask(transition))
+ .isEqualTo(
+ createTaskDetails(
+ taskId = task.taskId,
+ transitionInfo = transitionInfo,
+ minimizeReason = MinimizeReason.TASK_LIMIT,
+ )
+ )
+ }
+
+ @Test
+ fun getUnminimizingTask_noPendingTransition_returnsNull() {
+ val transition = Binder()
+
+ assertThat(desktopTasksLimiter.getMinimizingTask(transition)).isNull()
+ }
+
+ @Test
+ fun getUnminimizingTask_pendingTaskTransition_returnsTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val transition = Binder()
+ val task = setUpFreeformTask()
+ addPendingUnminimizeChange(
+ transition,
+ taskId = task.taskId,
+ unminimizeReason = UnminimizeReason.TASKBAR_TAP,
+ )
+
+ assertThat(desktopTasksLimiter.getUnminimizingTask(transition))
+ .isEqualTo(
+ createTaskDetails(
+ taskId = task.taskId,
+ unminimizeReason = UnminimizeReason.TASKBAR_TAP,
+ )
+ )
+ }
+
+ @Test
+ fun getUnminimizingTask_activeTaskTransition_returnsTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val transition = Binder()
+ val task = setUpFreeformTask()
+ addPendingMinimizeChange(
+ transition,
+ taskId = task.taskId,
+ minimizeReason = MinimizeReason.TASK_LIMIT,
+ )
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build()
+
+ callOnTransitionReady(transition, transitionInfo)
+
+ assertThat(desktopTasksLimiter.getMinimizingTask(transition))
+ .isEqualTo(
+ createTaskDetails(
+ taskId = task.taskId,
+ transitionInfo = transitionInfo,
+ minimizeReason = MinimizeReason.TASK_LIMIT,
+ )
+ )
+ }
+
+ @Test
+ fun onTransitionReady_taskLimitTransition_tasksOverLimit_startsMinimizeTransitionInRunOnIdle() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val transition = Binder()
+ val minimizeTransition = Binder()
+ val existingTasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
+ val launchTask = setUpFreeformTask()
+ desktopTasksLimiter.addPendingTaskLimitTransition(
+ transition,
+ deskId = 0,
+ taskId = launchTask.taskId,
+ )
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_OPEN, launchTask).build()
+ whenever(desktopMixedTransitionHandler.startTaskLimitMinimizeTransition(any(), anyInt()))
+ .thenReturn(minimizeTransition)
+
+ callOnTransitionReady(transition, transitionInfo)
+
+ val onIdleArgumentCaptor = argumentCaptor()
+ verify(transitions).runOnIdle(onIdleArgumentCaptor.capture())
+ onIdleArgumentCaptor.lastValue.run()
+ verify(desktopMixedTransitionHandler).startTaskLimitMinimizeTransition(any(), any())
+ assertThat(desktopTasksLimiter.getMinimizingTask(minimizeTransition)?.taskId)
+ .isEqualTo(existingTasks.first().taskId)
+ verify(snapEventHandler)
+ .removeTaskIfTiled(existingTasks.first().displayId, existingTasks.first().taskId)
+ }
+
+ @Test
+ fun onTransitionReady_taskLimitTransition_taskNotAvailable_minimizesNextTask() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val transition = Binder()
+ val minimizeTransition = Binder()
+ val existingTasks = (1..MAX_TASK_LIMIT + 1).map { setUpFreeformTask() }
+ // Make the bottom task non-available
+ `when`(shellTaskOrganizer.getRunningTaskInfo(existingTasks.first().taskId)).thenReturn(null)
+ val launchTask = setUpFreeformTask()
+ desktopTasksLimiter.addPendingTaskLimitTransition(
+ transition,
+ deskId = 0,
+ taskId = launchTask.taskId,
+ )
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_OPEN, launchTask).build()
+ whenever(desktopMixedTransitionHandler.startTaskLimitMinimizeTransition(any(), anyInt()))
+ .thenReturn(minimizeTransition)
+
+ callOnTransitionReady(transition, transitionInfo)
+
+ val onIdleArgumentCaptor = argumentCaptor()
+ verify(transitions).runOnIdle(onIdleArgumentCaptor.capture())
+ onIdleArgumentCaptor.lastValue.run()
+ verify(desktopMixedTransitionHandler).startTaskLimitMinimizeTransition(any(), any())
+ // Ensure we minimize the second task, since the first one is not available
+ assertThat(desktopTasksLimiter.getMinimizingTask(minimizeTransition)?.taskId)
+ .isEqualTo(existingTasks[1].taskId)
+ }
+
+ @Test
+ fun onTransitionReady_taskLimitTransition_taskNotAvailable_nextTaskBelowLimit_doesntMinimize() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val transition = Binder()
+ val existingTasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
+ // Make the bottom task non-available
+ `when`(shellTaskOrganizer.getRunningTaskInfo(existingTasks.first().taskId)).thenReturn(null)
+ val launchTask = setUpFreeformTask()
+ desktopTasksLimiter.addPendingTaskLimitTransition(
+ transition,
+ deskId = 0,
+ taskId = launchTask.taskId,
+ )
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_OPEN, launchTask).build()
+
+ callOnTransitionReady(transition, transitionInfo)
+
+ val onIdleArgumentCaptor = argumentCaptor()
+ verify(transitions).runOnIdle(onIdleArgumentCaptor.capture())
+ onIdleArgumentCaptor.lastValue.run()
+ verify(desktopMixedTransitionHandler, never())
+ .startTaskLimitMinimizeTransition(any(), any())
+ }
+
+ @Test
+ fun onTransitionReady_noPendingTaskLimitTransition_doesntTriggerOnIdle() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val transition = Binder()
+ (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
+ val launchTask = setUpFreeformTask()
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_OPEN, launchTask).build()
+ whenever(desktopMixedTransitionHandler.startTaskLimitMinimizeTransition(any(), anyInt()))
+ .thenReturn(Binder())
+
+ callOnTransitionReady(transition, transitionInfo)
+
+ verify(transitions, never()).runOnIdle(any())
+ verify(desktopMixedTransitionHandler, never())
+ .startTaskLimitMinimizeTransition(any(), any())
+ }
+
+ @Test
+ fun onTransitionReady_taskLimitTransition_tasksWithinLimit_doesntStartMinimizeTransition() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val transition = Binder()
+ val task = setUpFreeformTask()
+ desktopTasksLimiter.addPendingTaskLimitTransition(
+ transition,
+ deskId = 0,
+ taskId = task.taskId,
+ )
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_OPEN, task).build()
+ whenever(desktopMixedTransitionHandler.startTaskLimitMinimizeTransition(any(), anyInt()))
+ .thenReturn(Binder())
+
+ callOnTransitionReady(transition, transitionInfo)
+
+ val onIdleArgumentCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+ verify(transitions).runOnIdle(onIdleArgumentCaptor.capture())
+ onIdleArgumentCaptor.value.run()
+ verify(desktopMixedTransitionHandler, never())
+ .startTaskLimitMinimizeTransition(any(), any())
+ }
+
+ @Test
+ @DisableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS)
+ fun onTransitionReady_taskLimitTransition_taskTrampoline_doesntStartMinimizeTransition() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val transition1 = Binder()
+ val transition2 = Binder()
+ (2..MAX_TASK_LIMIT).map { setUpFreeformTask() }
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ desktopTasksLimiter.addPendingTaskLimitTransition(
+ transition1,
+ deskId = 0,
+ taskId = task1.taskId,
+ )
+ desktopTasksLimiter.addPendingTaskLimitTransition(
+ transition2,
+ deskId = 0,
+ taskId = task2.taskId,
+ )
+ val transitionInfo1 =
+ TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_OPEN, task1).build()
+ val transitionInfo2 =
+ TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN, task2)
+ .addChange(TRANSIT_CLOSE, task1)
+ .build()
+
+ // Start the initial task launch transition - launching a trampoline task
+ callOnTransitionReady(transition1, transitionInfo1)
+ // Start the second task launch transition - launching the final task and closing the
+ // trampoline task
+ callOnTransitionReady(transition2, transitionInfo2)
+
+ val onIdleArgumentCaptor = argumentCaptor()
+ verify(transitions, times(2)).runOnIdle(onIdleArgumentCaptor.capture())
+ onIdleArgumentCaptor.allValues.forEach { runnable -> runnable.run() }
+ verify(desktopMixedTransitionHandler, never())
+ .startTaskLimitMinimizeTransition(any(), any())
+ }
+
+ @Test
+ @DisableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS)
+ fun onTransitionReady_taskLimitTransition_taskTrampoline_marksTramplineAsClosed() {
+ desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0)
+ val transition1 = Binder()
+ val transition2 = Binder()
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ desktopTasksLimiter.addPendingTaskLimitTransition(
+ transition1,
+ deskId = 0,
+ taskId = task1.taskId,
+ )
+ desktopTasksLimiter.addPendingTaskLimitTransition(
+ transition2,
+ deskId = 0,
+ taskId = task2.taskId,
+ )
+ val transitionInfo1 =
+ TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_OPEN, task1).build()
+ val transitionInfo2 =
+ TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN, task2)
+ .addChange(TRANSIT_CLOSE, task1)
+ .build()
+
+ // Start the initial task launch transition - launching a trampoline task
+ callOnTransitionReady(transition1, transitionInfo1)
+ // Start the second task launch transition - launching the final task and closing the
+ // trampoline task
+ callOnTransitionReady(transition2, transitionInfo2)
+
+ assertThat(desktopTaskRepo.isClosingTask(task1.taskId)).isTrue()
+ }
+
+ private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
val task = createFreeformTask(displayId)
`when`(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
- desktopTaskRepo.addActiveTask(displayId, task.taskId)
- desktopTaskRepo.addOrMoveFreeformTaskToTop(displayId, task.taskId)
+ desktopTaskRepo.addTask(displayId, task.taskId, task.isVisible, TASK_BOUNDS)
return task
}
- private fun markTaskVisible(task: RunningTaskInfo) {
- desktopTaskRepo.updateVisibleFreeformTasks(
- task.displayId,
- task.taskId,
- visible = true
+ private fun createTaskDetails(
+ displayId: Int = DEFAULT_DISPLAY,
+ taskId: Int,
+ transitionInfo: TransitionInfo? = null,
+ minimizeReason: MinimizeReason? = null,
+ unminimizeReason: UnminimizeReason? = null,
+ ) =
+ DesktopTasksLimiter.TaskDetails(
+ displayId,
+ taskId,
+ transitionInfo,
+ minimizeReason,
+ unminimizeReason,
)
+
+ private fun callOnTransitionReady(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction = StubTransaction(),
+ finishTransaction: SurfaceControl.Transaction = StubTransaction(),
+ ) =
+ desktopTasksLimiter
+ .getTransitionObserver()
+ .onTransitionReady(transition, info, startTransaction, finishTransaction)
+
+ private fun addPendingMinimizeChange(
+ transition: IBinder,
+ displayId: Int = DEFAULT_DISPLAY,
+ taskId: Int,
+ minimizeReason: MinimizeReason = MinimizeReason.TASK_LIMIT,
+ ) = desktopTasksLimiter.addPendingMinimizeChange(transition, displayId, taskId, minimizeReason)
+
+ private fun addPendingUnminimizeChange(
+ transition: IBinder,
+ displayId: Int = DEFAULT_DISPLAY,
+ taskId: Int,
+ unminimizeReason: UnminimizeReason,
+ ) =
+ desktopTasksLimiter.addPendingUnminimizeChange(
+ transition,
+ displayId,
+ taskId,
+ unminimizeReason,
+ )
+
+ private fun markTaskVisible(task: RunningTaskInfo) {
+ desktopTaskRepo.updateTask(task.displayId, task.taskId, isVisible = true, TASK_BOUNDS)
}
private fun markTaskHidden(task: RunningTaskInfo) {
- desktopTaskRepo.updateVisibleFreeformTasks(
- task.displayId,
- task.taskId,
- visible = false
- )
+ desktopTaskRepo.updateTask(task.displayId, task.taskId, isVisible = false, TASK_BOUNDS)
+ }
+
+ private companion object {
+ const val MAX_TASK_LIMIT = 6
+ const val MAX_TASK_LIMIT2 = 9
+ val TASK_BOUNDS = Rect(100, 100, 300, 300)
}
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
index 52da7fb811..25f96f8383 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
@@ -22,67 +22,94 @@ import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
+import android.content.ComponentName
import android.graphics.Rect
import android.view.Display.DEFAULT_DISPLAY
import com.android.wm.shell.MockToken
import com.android.wm.shell.TestRunningTaskInfoBuilder
-class DesktopTestHelpers {
- companion object {
- /** Create a task that has windowing mode set to [WINDOWING_MODE_FREEFORM] */
- @JvmStatic
- @JvmOverloads
- fun createFreeformTask(
- displayId: Int = DEFAULT_DISPLAY,
- bounds: Rect? = null
- ): RunningTaskInfo {
- return TestRunningTaskInfoBuilder()
- .setDisplayId(displayId)
- .setToken(MockToken().token())
- .setActivityType(ACTIVITY_TYPE_STANDARD)
- .setWindowingMode(WINDOWING_MODE_FREEFORM)
- .setLastActiveTime(100)
- .apply { bounds?.let { setBounds(it) }}
- .build()
+object DesktopTestHelpers {
+ /** Create a task that has windowing mode set to [WINDOWING_MODE_FREEFORM] */
+ fun createFreeformTask(
+ displayId: Int = DEFAULT_DISPLAY,
+ bounds: Rect? = null,
+ ): RunningTaskInfo =
+ TestRunningTaskInfoBuilder()
+ .setDisplayId(displayId)
+ .setParentTaskId(displayId)
+ .setToken(MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM)
+ .setLastActiveTime(100)
+ .setUserId(DEFAULT_USER_ID)
+ .apply { bounds?.let { setBounds(it) } }
+ .build()
+
+ fun createPinnedTask(displayId: Int = DEFAULT_DISPLAY, bounds: Rect? = null): RunningTaskInfo =
+ TestRunningTaskInfoBuilder()
+ .setDisplayId(displayId)
+ .setParentTaskId(displayId)
+ .setToken(MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_PINNED)
+ .setLastActiveTime(100)
+ .apply { bounds?.let { setBounds(it) } }
+ .build()
+
+ fun createFullscreenTaskBuilder(displayId: Int = DEFAULT_DISPLAY): TestRunningTaskInfoBuilder =
+ TestRunningTaskInfoBuilder()
+ .setDisplayId(displayId)
+ .setToken(MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setUserId(DEFAULT_USER_ID)
+ .setLastActiveTime(100)
+
+ /** Create a task that has windowing mode set to [WINDOWING_MODE_FULLSCREEN] */
+ fun createFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo =
+ createFullscreenTaskBuilder(displayId).build()
+
+ /** Create a task that has windowing mode set to [WINDOWING_MODE_MULTI_WINDOW] */
+ fun createSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo =
+ TestRunningTaskInfoBuilder()
+ .setDisplayId(displayId)
+ .setToken(MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
+ .setUserId(DEFAULT_USER_ID)
+ .setLastActiveTime(100)
+ .build()
+
+ fun createHomeTask(
+ displayId: Int = DEFAULT_DISPLAY,
+ userId: Int = DEFAULT_USER_ID,
+ ): RunningTaskInfo =
+ TestRunningTaskInfoBuilder()
+ .setDisplayId(displayId)
+ .setToken(MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_HOME)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setUserId(userId)
+ .setLastActiveTime(100)
+ .build()
+
+ /**
+ * Create a new System Modal task builder, i.e. a builder for a task with only transparent
+ * activities.
+ */
+ fun createSystemModalTaskBuilder(displayId: Int = DEFAULT_DISPLAY): TestRunningTaskInfoBuilder =
+ createFullscreenTaskBuilder(displayId).setActivityStackTransparent(true).setNumActivities(1)
+
+ /** Create a new System Modal task, i.e. a task with only transparent activities. */
+ fun createSystemModalTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo =
+ createSystemModalTaskBuilder(displayId).build()
+
+ /** Create a new System Modal task with a base Activity. */
+ fun createSystemModalTaskWithBaseActivity() =
+ createSystemModalTask().apply {
+ baseActivity = ComponentName("com.test.dummypackage", "TestClass")
}
- /** Create a task that has windowing mode set to [WINDOWING_MODE_FULLSCREEN] */
- @JvmStatic
- @JvmOverloads
- fun createFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
- return TestRunningTaskInfoBuilder()
- .setDisplayId(displayId)
- .setToken(MockToken().token())
- .setActivityType(ACTIVITY_TYPE_STANDARD)
- .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
- .setLastActiveTime(100)
- .build()
- }
-
- /** Create a task that has windowing mode set to [WINDOWING_MODE_MULTI_WINDOW] */
- @JvmStatic
- @JvmOverloads
- fun createSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
- return TestRunningTaskInfoBuilder()
- .setDisplayId(displayId)
- .setToken(MockToken().token())
- .setActivityType(ACTIVITY_TYPE_STANDARD)
- .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
- .setLastActiveTime(100)
- .build()
- }
-
- /** Create a new home task */
- @JvmStatic
- @JvmOverloads
- fun createHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
- return TestRunningTaskInfoBuilder()
- .setDisplayId(displayId)
- .setToken(MockToken().token())
- .setActivityType(ACTIVITY_TYPE_HOME)
- .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
- .setLastActiveTime(100)
- .build()
- }
- }
-}
\ No newline at end of file
+ const val DEFAULT_USER_ID = 10
+}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index bbf523bc40..64504a2477 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -1,5 +1,6 @@
package com.android.wm.shell.desktopmode
+import android.animation.AnimatorTestRule
import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
@@ -7,84 +8,170 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WindowingMode
import android.graphics.PointF
+import android.graphics.Rect
import android.os.IBinder
+import android.os.SystemProperties
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import android.view.Display
import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_OPEN
import android.window.TransitionInfo
import android.window.TransitionInfo.FLAG_IS_WALLPAPER
import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
+import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.window.flags.Flags
+import com.android.window.flags.Flags.FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
-import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
-import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
+import com.android.wm.shell.bubbles.BubbleController
+import com.android.wm.shell.bubbles.BubbleTransitions
+import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP
+import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP
+import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
+import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.CancelState
+import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.Companion.DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS
+import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer
+import com.android.wm.shell.shared.desktopmode.FakeDesktopState
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.transition.Transitions
-import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP
-import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP
-import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import java.util.function.Supplier
+import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyFloat
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argThat
+import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
-import java.util.function.Supplier
+import org.mockito.quality.Strictness
/** Tests of [DragToDesktopTransitionHandler]. */
@SmallTest
@RunWithLooper
@RunWith(AndroidTestingRunner::class)
class DragToDesktopTransitionHandlerTest : ShellTestCase() {
+ @JvmField @Rule val mAnimatorTestRule = AnimatorTestRule(this)
@Mock private lateinit var transitions: Transitions
@Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ @Mock private lateinit var desksOrganizer: DesksOrganizer
@Mock private lateinit var splitScreenController: SplitScreenController
@Mock private lateinit var dragAnimator: MoveToDesktopAnimator
+ @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+ @Mock private lateinit var draggedTaskLeash: SurfaceControl
+ @Mock private lateinit var homeTaskLeash: SurfaceControl
+ @Mock private lateinit var wallpaperLeash: SurfaceControl
+ @Mock private lateinit var desktopUserRepositories: DesktopUserRepositories
+ @Mock private lateinit var bubbleController: BubbleController
+ @Mock private lateinit var visualIndicator: DesktopModeVisualIndicator
+ @Mock private lateinit var dragCancelCallback: Runnable
+ @Mock
+ private lateinit var dragToDesktopStateListener:
+ DragToDesktopTransitionHandler.DragToDesktopStateListener
+ private lateinit var desktopState: FakeDesktopState
- private val transactionSupplier = Supplier { mock() }
+ private val transactionSupplier = Supplier {
+ val transaction = mock()
+ whenever(transaction.setAlpha(any(), anyFloat())).thenReturn(transaction)
+ whenever(transaction.setFrameTimeline(anyLong())).thenReturn(transaction)
+ transaction
+ }
- private lateinit var handler: DragToDesktopTransitionHandler
+ private lateinit var defaultHandler: DragToDesktopTransitionHandler
+ private lateinit var springHandler: SpringDragToDesktopTransitionHandler
@Before
fun setUp() {
- handler =
- DragToDesktopTransitionHandler(
+ desktopState = FakeDesktopState()
+ desktopState.canEnterDesktopMode = true
+ defaultHandler =
+ DefaultDragToDesktopTransitionHandler(
context,
transitions,
taskDisplayAreaOrganizer,
- transactionSupplier
+ desksOrganizer,
+ desktopUserRepositories,
+ mockInteractionJankMonitor,
+ Optional.of(bubbleController),
+ transactionSupplier,
+ desktopState,
)
- .apply { setSplitScreenController(splitScreenController) }
+ .apply {
+ setSplitScreenController(splitScreenController)
+ dragToDesktopStateListener =
+ this@DragToDesktopTransitionHandlerTest.dragToDesktopStateListener
+ }
+ springHandler =
+ SpringDragToDesktopTransitionHandler(
+ context,
+ transitions,
+ taskDisplayAreaOrganizer,
+ desksOrganizer,
+ desktopUserRepositories,
+ mockInteractionJankMonitor,
+ Optional.of(bubbleController),
+ transactionSupplier,
+ desktopState,
+ )
+ .apply {
+ setSplitScreenController(splitScreenController)
+ dragToDesktopStateListener =
+ this@DragToDesktopTransitionHandlerTest.dragToDesktopStateListener
+ }
+ whenever(
+ transitions.startTransition(
+ eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP),
+ /* wct= */ any(),
+ eq(defaultHandler),
+ )
+ )
+ .thenReturn(mock())
}
@Test
fun startDragToDesktop_animateDragWhenReady() {
val task = createTask()
// Simulate transition is started.
- val transition = startDragToDesktopTransition(task, dragAnimator)
+ val transition = startDragToDesktopTransition(defaultHandler, task, dragAnimator)
// Now it's ready to animate.
- handler.startAnimation(
+ defaultHandler.startAnimation(
transition = transition,
info =
createTransitionInfo(
type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
- draggedTask = task
+ draggedTask = task,
),
startTransaction = mock(),
finishTransaction = mock(),
- finishCallback = {}
+ finishCallback = {},
)
verify(dragAnimator).startAnimation()
@@ -92,65 +179,108 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
@Test
fun startDragToDesktop_cancelledBeforeReady_startCancelTransition() {
- performEarlyCancel(DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL)
+ performEarlyCancel(
+ defaultHandler,
+ DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL,
+ )
verify(transitions)
- .startTransition(eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP), any(), eq(handler))
+ .startTransition(
+ eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP),
+ any(),
+ eq(defaultHandler),
+ )
}
@Test
fun startDragToDesktop_cancelledBeforeReady_verifySplitLeftCancel() {
- performEarlyCancel(DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT)
- verify(splitScreenController).requestEnterSplitSelect(
- any(),
- any(),
- eq(SPLIT_POSITION_TOP_OR_LEFT),
- any()
+ performEarlyCancel(
+ defaultHandler,
+ DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT,
)
+ verify(splitScreenController)
+ .requestEnterSplitSelect(
+ /* taskInfo = */ any(),
+ /* splitPosition = */ eq(SPLIT_POSITION_TOP_OR_LEFT),
+ /* taskBounds = */ any(),
+ /* startRecents = */ eq(false), // Home already running, so recents isn't needed.
+ /* withRecentsWct = */ anyOrNull(),
+ )
}
@Test
fun startDragToDesktop_cancelledBeforeReady_verifySplitRightCancel() {
- performEarlyCancel(DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT)
- verify(splitScreenController).requestEnterSplitSelect(
- any(),
- any(),
- eq(SPLIT_POSITION_BOTTOM_OR_RIGHT),
- any()
+ performEarlyCancel(
+ defaultHandler,
+ DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT,
)
+ verify(splitScreenController)
+ .requestEnterSplitSelect(
+ /* taskInfo = */ any(),
+ /* splitPosition = */ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT),
+ /* taskBounds = */ any(),
+ /* startRecents = */ eq(false), // Home already running, so recents isn't needed.
+ /* withRecentsWct = */ anyOrNull(),
+ )
+ }
+
+ @Test
+ fun startDragToDesktop_cancelledBeforeReady_verifyBubbleLeftCancel() {
+ performEarlyCancel(
+ defaultHandler,
+ DragToDesktopTransitionHandler.CancelState.CANCEL_BUBBLE_LEFT,
+ )
+ verify(bubbleController)
+ .expandStackAndSelectBubble(
+ any(),
+ argThat { isReleasedOnLeft },
+ )
+ }
+
+ @Test
+ fun startDragToDesktop_cancelledBeforeReady_verifyBubbleRightCancel() {
+ performEarlyCancel(
+ defaultHandler,
+ DragToDesktopTransitionHandler.CancelState.CANCEL_BUBBLE_RIGHT,
+ )
+ verify(bubbleController)
+ .expandStackAndSelectBubble(
+ any(),
+ argThat { !isReleasedOnLeft },
+ )
}
@Test
fun startDragToDesktop_aborted_finishDropped() {
val task = createTask()
// Simulate transition is started.
- val transition = startDragToDesktopTransition(task, dragAnimator)
+ val transition = startDragToDesktopTransition(defaultHandler, task, dragAnimator)
// But the transition was aborted.
- handler.onTransitionConsumed(transition, aborted = true, mock())
+ defaultHandler.onTransitionConsumed(transition, aborted = true, mock())
// Attempt to finish the failed drag start.
- handler.finishDragToDesktopTransition(WindowContainerTransaction())
+ defaultHandler.finishDragToDesktopTransition(WindowContainerTransaction())
// Should not be attempted and state should be reset.
verify(transitions, never())
- .startTransition(eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP), any(), any())
- assertFalse(handler.inProgress)
+ .startTransition(eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP), any(), any())
+ assertFalse(defaultHandler.inProgress)
}
@Test
fun startDragToDesktop_aborted_cancelDropped() {
val task = createTask()
// Simulate transition is started.
- val transition = startDragToDesktopTransition(task, dragAnimator)
+ val transition = startDragToDesktopTransition(defaultHandler, task, dragAnimator)
// But the transition was aborted.
- handler.onTransitionConsumed(transition, aborted = true, mock())
+ defaultHandler.onTransitionConsumed(transition, aborted = true, mock())
// Attempt to finish the failed drag start.
- handler.cancelDragToDesktopTransition(
+ defaultHandler.cancelDragToDesktopTransition(
DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL
)
// Should not be attempted and state should be reset.
- assertFalse(handler.inProgress)
+ assertFalse(defaultHandler.inProgress)
}
@Test
@@ -158,161 +288,946 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
val task = createTask()
// Simulate attempt to start two drag to desktop transitions.
- startDragToDesktopTransition(task, dragAnimator)
- startDragToDesktopTransition(task, dragAnimator)
+ startDragToDesktopTransition(defaultHandler, task, dragAnimator)
+ startDragToDesktopTransition(defaultHandler, task, dragAnimator)
// Verify transition only started once.
- verify(transitions, times(1)).startTransition(
+ verify(transitions, times(1))
+ .startTransition(
eq(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP),
any(),
- eq(handler)
- )
+ eq(defaultHandler),
+ )
}
@Test
- fun cancelDragToDesktop_startWasReady_cancel() {
- startDrag()
+ fun isHomeChange_withoutTaskInfo_returnsFalse() {
+ val change =
+ TransitionInfo.Change(mock(), homeTaskLeash).apply {
+ parent = null
+ taskInfo = null
+ }
+
+ assertFalse(defaultHandler.isHomeChange(change))
+ assertFalse(springHandler.isHomeChange(change))
+ }
+
+ @Test
+ fun isHomeChange_withStandardActivityTaskInfo_returnsFalse() {
+ val change =
+ TransitionInfo.Change(mock(), homeTaskLeash).apply {
+ parent = null
+ taskInfo =
+ TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_STANDARD).build()
+ }
+
+ assertFalse(defaultHandler.isHomeChange(change))
+ assertFalse(springHandler.isHomeChange(change))
+ }
+
+ @Test
+ fun isHomeChange_withHomeActivityTaskInfo_returnsTrue() {
+ val change =
+ TransitionInfo.Change(mock(), homeTaskLeash).apply {
+ parent = null
+ taskInfo = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
+ }
+
+ assertTrue(defaultHandler.isHomeChange(change))
+ assertTrue(springHandler.isHomeChange(change))
+ }
+
+ @Test
+ fun isHomeChange_withSingleTranslucentHomeActivityTaskInfo_returnsFalse() {
+ val change =
+ TransitionInfo.Change(mock(), homeTaskLeash).apply {
+ parent = null
+ taskInfo =
+ TestRunningTaskInfoBuilder()
+ .setActivityType(ACTIVITY_TYPE_HOME)
+ .setTopActivityTransparent(true)
+ .setNumActivities(1)
+ .build()
+ }
+
+ assertFalse(defaultHandler.isHomeChange(change))
+ assertFalse(springHandler.isHomeChange(change))
+ }
+
+ @Test
+ fun cancelDragToDesktop_startWasReady_cancel_merged() {
+ val startToken = startDrag(defaultHandler)
// Then user cancelled after it had already started.
- handler.cancelDragToDesktopTransition(
- DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL
+ val cancelToken =
+ cancelDragToDesktopTransition(
+ defaultHandler,
+ DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL,
+ )
+ defaultHandler.mergeAnimation(
+ cancelToken,
+ TransitionInfo(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, 0),
+ mock(),
+ mock(),
+ startToken,
+ mock(),
)
// Cancel animation should run since it had already started.
verify(dragAnimator).cancelAnimator()
+ assertFalse("Drag should not be in progress after cancelling", defaultHandler.inProgress)
+ }
+
+ @Test
+ fun cancelDragToDesktop_startWasReady_cancel_aborted() {
+ val startToken = startDrag(defaultHandler)
+
+ // Then user cancelled after it had already started.
+ val cancelToken =
+ cancelDragToDesktopTransition(
+ defaultHandler,
+ DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL,
+ )
+ defaultHandler.onTransitionConsumed(cancelToken, aborted = true, null)
+
+ // Cancel animation should run since it had already started.
+ verify(dragAnimator).cancelAnimator()
+ assertFalse("Drag should not be in progress after cancelling", defaultHandler.inProgress)
}
@Test
fun cancelDragToDesktop_splitLeftCancelType_splitRequested() {
- startDrag()
+ startDrag(defaultHandler)
// Then user cancelled it, requesting split.
- handler.cancelDragToDesktopTransition(
+ defaultHandler.cancelDragToDesktopTransition(
DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT
)
// Verify the request went through split controller.
- verify(splitScreenController).requestEnterSplitSelect(
- any(),
- any(),
- eq(SPLIT_POSITION_TOP_OR_LEFT),
- any()
- )
+ verify(splitScreenController)
+ .requestEnterSplitSelect(
+ /* taskInfo = */ any(),
+ /* splitPosition = */ eq(SPLIT_POSITION_TOP_OR_LEFT),
+ /* taskBounds = */ any(),
+ /* startRecents = */ eq(false), // Home already running, so recents isn't needed.
+ /* withRecentsWct = */ anyOrNull(),
+ )
}
@Test
fun cancelDragToDesktop_splitRightCancelType_splitRequested() {
- startDrag()
+ startDrag(defaultHandler)
// Then user cancelled it, requesting split.
- handler.cancelDragToDesktopTransition(
+ defaultHandler.cancelDragToDesktopTransition(
DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT
)
// Verify the request went through split controller.
- verify(splitScreenController).requestEnterSplitSelect(
- any(),
- any(),
- eq(SPLIT_POSITION_BOTTOM_OR_RIGHT),
- any()
+ verify(splitScreenController)
+ .requestEnterSplitSelect(
+ /* taskInfo = */ any(),
+ /* splitPosition = */ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT),
+ /* taskBounds = */ any(),
+ /* startRecents = */ eq(false), // Home already running, so recents isn't needed.
+ /* withRecentsWct = */ anyOrNull(),
+ )
+ }
+
+ @Test
+ fun cancelDragToDesktop_bubbleLeftCancelType_bubbleRequested() {
+ startDrag(defaultHandler)
+
+ // Then user cancelled it, requesting bubble.
+ defaultHandler.cancelDragToDesktopTransition(
+ DragToDesktopTransitionHandler.CancelState.CANCEL_BUBBLE_LEFT
)
+
+ // Verify the request went through bubble controller.
+ verify(bubbleController)
+ .expandStackAndSelectBubble(
+ any(),
+ argThat { isReleasedOnLeft },
+ )
+ }
+
+ @Test
+ fun cancelDragToDesktop_bubbleRightCancelType_bubbleRequested() {
+ startDrag(defaultHandler)
+
+ // Then user cancelled it, requesting bubble.
+ defaultHandler.cancelDragToDesktopTransition(
+ DragToDesktopTransitionHandler.CancelState.CANCEL_BUBBLE_RIGHT
+ )
+
+ // Verify the request went through bubble controller.
+ verify(bubbleController)
+ .expandStackAndSelectBubble(
+ any(),
+ argThat { !isReleasedOnLeft },
+ )
}
@Test
fun cancelDragToDesktop_startWasNotReady_animateCancel() {
val task = createTask()
// Simulate transition is started and is ready to animate.
- startDragToDesktopTransition(task, dragAnimator)
+ startDragToDesktopTransition(defaultHandler, task, dragAnimator)
// Then user cancelled before the transition was ready and animated.
- handler.cancelDragToDesktopTransition(
+ defaultHandler.cancelDragToDesktopTransition(
DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL
)
// No need to animate the cancel since the start animation couldn't even start.
- verifyZeroInteractions(dragAnimator)
+ verifyNoMoreInteractions(dragAnimator)
}
@Test
fun cancelDragToDesktop_transitionNotInProgress_dropCancel() {
// Then cancel is called before the transition was started.
- handler.cancelDragToDesktopTransition(
+ defaultHandler.cancelDragToDesktopTransition(
DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL
)
// Verify cancel is dropped.
- verify(transitions, never()).startTransition(
+ verify(transitions, never())
+ .startTransition(
eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP),
any(),
- eq(handler)
- )
+ eq(defaultHandler),
+ )
}
@Test
fun finishDragToDesktop_transitionNotInProgress_dropFinish() {
// Then finish is called before the transition was started.
- handler.finishDragToDesktopTransition(WindowContainerTransaction())
+ defaultHandler.finishDragToDesktopTransition(WindowContainerTransaction())
// Verify finish is dropped.
- verify(transitions, never()).startTransition(
+ verify(transitions, never())
+ .startTransition(
eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP),
any(),
- eq(handler)
+ eq(defaultHandler),
+ )
+ }
+
+ @Test
+ fun mergeAnimation_otherTransition_doesNotMerge() {
+ val mergedStartTransaction = mock()
+ val mergedFinishTransaction = mock()
+ val finishCallback = mock()
+ val task = createTask()
+
+ startDrag(defaultHandler, task)
+ defaultHandler.mergeAnimation(
+ transition = mock(),
+ info = createTransitionInfo(type = TRANSIT_OPEN, draggedTask = task),
+ startT = mergedStartTransaction,
+ finishT = mergedFinishTransaction,
+ mergeTarget = mock(),
+ finishCallback = finishCallback,
+ )
+
+ // Should NOT have any transaction changes
+ verifyNoMoreInteractions(mergedStartTransaction)
+ // Should NOT merge animation
+ verify(finishCallback, never()).onTransitionFinished(any())
+ }
+
+ @Test
+ fun mergeAnimation_endTransition_mergesAnimation() {
+ val playingFinishTransaction = mock()
+ val mergedStartTransaction = mock()
+ val mergedFinishTransaction = mock()
+ val finishCallback = mock()
+ val task = createTask()
+ val startTransition =
+ startDrag(defaultHandler, task, finishTransaction = playingFinishTransaction)
+ defaultHandler.onTaskResizeAnimationListener = mock()
+
+ defaultHandler.mergeAnimation(
+ transition = mock(),
+ info =
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+ draggedTask = task,
+ ),
+ startT = mergedStartTransaction,
+ finishT = mergedFinishTransaction,
+ mergeTarget = startTransition,
+ finishCallback = finishCallback,
+ )
+
+ // Should show dragged task layer in start and finish transaction
+ verify(mergedStartTransaction).show(draggedTaskLeash)
+ verify(playingFinishTransaction).show(draggedTaskLeash)
+ // Should update the dragged task layer
+ verify(mergedStartTransaction).setLayer(eq(draggedTaskLeash), anyInt())
+ // Should merge animation
+ verify(finishCallback).onTransitionFinished(null)
+ }
+
+ @Test
+ fun mergeAnimation_endTransition_hasDeskChange_doesNotMoveBehindDraggedTask() {
+ val playingFinishTransaction = mock()
+ val mergedStartTransaction = mock()
+ val mergedFinishTransaction = mock()
+ val finishCallback = mock()
+ val deskChange = createDeskChange()
+ val task = createTask()
+ val startTransition =
+ startDrag(defaultHandler, task, finishTransaction = playingFinishTransaction)
+ defaultHandler.onTaskResizeAnimationListener = mock()
+ whenever(desksOrganizer.isDeskChange(deskChange)).thenReturn(true)
+
+ defaultHandler.mergeAnimation(
+ transition = mock(),
+ info =
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+ draggedTask = task,
+ deskChange = deskChange,
+ ),
+ startT = mergedStartTransaction,
+ finishT = mergedFinishTransaction,
+ mergeTarget = startTransition,
+ finishCallback = finishCallback,
+ )
+
+ // Don't move the desk leash back, or it will take the dragged task with it.
+ verify(mergedStartTransaction, never())
+ .setRelativeLayer(eq(deskChange.leash), eq(draggedTaskLeash), anyInt())
+ verify(playingFinishTransaction, never())
+ .setRelativeLayer(eq(deskChange.leash), eq(draggedTaskLeash), anyInt())
+ }
+
+ @Test
+ fun mergeAnimation_endTransition_springHandler_hidesHome() {
+ whenever(dragAnimator.computeCurrentVelocity()).thenReturn(PointF())
+ val playingFinishTransaction = mock()
+ val mergedStartTransaction = mock()
+ val mergedFinishTransaction = mock()
+ val finishCallback = mock()
+ val task = createTask()
+ val startTransition =
+ startDrag(springHandler, task, finishTransaction = playingFinishTransaction)
+ springHandler.onTaskResizeAnimationListener = mock()
+
+ springHandler.mergeAnimation(
+ transition = mock(),
+ info =
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+ draggedTask = task,
+ ),
+ startT = mergedStartTransaction,
+ finishT = mergedFinishTransaction,
+ mergeTarget = startTransition,
+ finishCallback = finishCallback,
+ )
+
+ // Should show dragged task layer in start and finish transaction
+ verify(mergedStartTransaction).show(draggedTaskLeash)
+ verify(playingFinishTransaction).show(draggedTaskLeash)
+ // Should update the dragged task layer
+ verify(mergedStartTransaction).setLayer(eq(draggedTaskLeash), anyInt())
+ // Should hide home task leash in finish transaction
+ verify(playingFinishTransaction).hide(homeTaskLeash)
+ // Should merge animation
+ verify(finishCallback).onTransitionFinished(null)
+ }
+
+ @Test
+ fun mergeAnimation_endTransition_springHandler_noStartHomeChange_doesntCrash() {
+ whenever(dragAnimator.computeCurrentVelocity()).thenReturn(PointF())
+ val playingFinishTransaction = mock()
+ val mergedStartTransaction = mock()
+ val mergedFinishTransaction = mock()
+ val finishCallback = mock()
+ val task = createTask()
+ val startTransition =
+ startDrag(
+ springHandler,
+ task,
+ finishTransaction = playingFinishTransaction,
+ homeChange = null,
+ )
+ springHandler.onTaskResizeAnimationListener = mock()
+
+ springHandler.mergeAnimation(
+ transition = mock(),
+ info =
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+ draggedTask = task,
+ ),
+ startT = mergedStartTransaction,
+ finishT = mergedFinishTransaction,
+ mergeTarget = startTransition,
+ finishCallback = finishCallback,
+ )
+
+ // Should show dragged task layer in start and finish transaction
+ verify(mergedStartTransaction).show(draggedTaskLeash)
+ verify(playingFinishTransaction).show(draggedTaskLeash)
+ // Should update the dragged task layer
+ verify(mergedStartTransaction).setLayer(eq(draggedTaskLeash), anyInt())
+ // Should merge animation
+ verify(finishCallback).onTransitionFinished(null)
+ }
+
+ @Test
+ fun propertyValue_returnsSystemPropertyValue() {
+ val mockitoSession =
+ ExtendedMockito.mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .mockStatic(SystemProperties::class.java)
+ .startMocking()
+
+ val name = "property_name"
+ val value = 10f
+
+ whenever(SystemProperties.getInt(eq(systemPropertiesKey(name)), anyInt()))
+ .thenReturn(value.toInt())
+
+ assertEquals(
+ "Expects to return system properties stored value",
+ /* expected= */ value,
+ /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(name),
+ )
+
+ mockitoSession.finishMocking()
+ }
+
+ @Test
+ fun propertyValue_withScale_returnsScaledSystemPropertyValue() {
+ val mockitoSession =
+ ExtendedMockito.mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .mockStatic(SystemProperties::class.java)
+ .startMocking()
+
+ val name = "property_name"
+ val value = 10f
+ val scale = 100f
+
+ whenever(SystemProperties.getInt(eq(systemPropertiesKey(name)), anyInt()))
+ .thenReturn(value.toInt())
+
+ assertEquals(
+ "Expects to return scaled system properties stored value",
+ /* expected= */ value / scale,
+ /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(name, scale = scale),
+ )
+
+ mockitoSession.finishMocking()
+ }
+
+ @Test
+ fun propertyValue_notSet_returnsDefaultValue() {
+ val mockitoSession =
+ ExtendedMockito.mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .mockStatic(SystemProperties::class.java)
+ .startMocking()
+
+ val name = "property_name"
+ val defaultValue = 50f
+
+ whenever(SystemProperties.getInt(eq(systemPropertiesKey(name)), eq(defaultValue.toInt())))
+ .thenReturn(defaultValue.toInt())
+
+ assertEquals(
+ "Expects to return the default value",
+ /* expected= */ defaultValue,
+ /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(
+ name,
+ default = defaultValue,
+ ),
+ )
+
+ mockitoSession.finishMocking()
+ }
+
+ @Test
+ fun propertyValue_withScaleNotSet_returnsDefaultValue() {
+ val mockitoSession =
+ ExtendedMockito.mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .mockStatic(SystemProperties::class.java)
+ .startMocking()
+
+ val name = "property_name"
+ val defaultValue = 0.5f
+ val scale = 100f
+ // Default value is multiplied when provided as a default value for [SystemProperties]
+ val scaledDefault = (defaultValue * scale).toInt()
+
+ whenever(SystemProperties.getInt(eq(systemPropertiesKey(name)), eq(scaledDefault)))
+ .thenReturn(scaledDefault)
+
+ assertEquals(
+ "Expects to return the default value",
+ /* expected= */ defaultValue,
+ /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(
+ name,
+ default = defaultValue,
+ scale = scale,
+ ),
+ )
+
+ mockitoSession.finishMocking()
+ }
+
+ @Test
+ fun startDragToDesktop_aborted_logsDragHoldCancelled() {
+ val transition = startDragToDesktopTransition(defaultHandler, createTask(), dragAnimator)
+
+ defaultHandler.onTransitionConsumed(transition, aborted = true, mock())
+
+ verify(mockInteractionJankMonitor).cancel(eq(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD))
+ verify(mockInteractionJankMonitor, times(0))
+ .cancel(eq(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE))
+ }
+
+ @Test
+ fun mergeEndDragToDesktop_aborted_logsDragReleaseCancelled() {
+ val task = createTask()
+ val startTransition = startDrag(defaultHandler, task)
+ val endTransition = mock()
+ defaultHandler.onTaskResizeAnimationListener = mock()
+ mergeAnimation(
+ transition = endTransition,
+ type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+ task = task,
+ mergeTarget = startTransition,
+ )
+
+ defaultHandler.onTransitionConsumed(endTransition, aborted = true, mock())
+
+ verify(mockInteractionJankMonitor)
+ .cancel(eq(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE))
+ verify(mockInteractionJankMonitor, times(0))
+ .cancel(eq(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD))
+ }
+
+ @Test
+ @DisableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
+ fun mergeOtherTransition_flagDisabled_cancelAndEndNotYetRequested_doesNotInterruptStartDrag() {
+ val finishCallback = mock()
+ val task = createTask()
+ defaultHandler.onTaskResizeAnimationListener = mock()
+ val startTransition = startDrag(defaultHandler, task, finishCallback = finishCallback)
+
+ mergeInterruptingTransition(mergeTarget = startTransition)
+
+ verify(finishCallback, never()).onTransitionFinished(anyOrNull())
+ verify(dragAnimator, never()).cancelAnimator()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
+ fun mergeOtherTransition_cancelAndEndNotYetRequested_interruptsStartDrag() {
+ val finishCallback = mock()
+ val task = createTask()
+ defaultHandler.onTaskResizeAnimationListener = mock()
+ val startTransition = startDrag(defaultHandler, task, finishCallback = finishCallback)
+
+ mergeInterruptingTransition(mergeTarget = startTransition)
+
+ verify(dragAnimator).cancelAnimator()
+ verify(dragCancelCallback).run()
+ verify(dragToDesktopStateListener).onTransitionInterrupted()
+ assertThat(defaultHandler.inProgress).isTrue()
+ // Doesn't finish start transition yet
+ verify(finishCallback, never()).onTransitionFinished(/* wct= */ anyOrNull())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
+ fun mergeOtherTransition_cancelAndEndNotYetRequested_finishesStartAfterAnimation() {
+ val finishCallback = mock()
+ val task = createTask()
+ defaultHandler.onTaskResizeAnimationListener = mock()
+ val startTransition = startDrag(defaultHandler, task, finishCallback = finishCallback)
+
+ mergeInterruptingTransition(mergeTarget = startTransition)
+ mAnimatorTestRule.advanceTimeBy(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
+
+ verify(finishCallback).onTransitionFinished(/* wct= */ anyOrNull())
+ assertThat(defaultHandler.inProgress).isFalse()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
+ fun mergeOtherTransition_endDragAlreadyMerged_doesNotInterruptStartDrag() {
+ val startDragFinishCallback = mock()
+ val task = createTask()
+ val startTransition =
+ startDrag(defaultHandler, task, finishCallback = startDragFinishCallback)
+ defaultHandler.onTaskResizeAnimationListener = mock()
+ mergeAnimation(
+ type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+ task = task,
+ mergeTarget = startTransition,
+ )
+
+ mergeInterruptingTransition(mergeTarget = startTransition)
+
+ verify(startDragFinishCallback, never()).onTransitionFinished(anyOrNull())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
+ fun startEndAnimation_otherTransitionInterruptedStartAfterEndRequest_finishImmediately() {
+ val task1 = createTask()
+ val startTransition = startDrag(defaultHandler, task1)
+ val endTransition =
+ defaultHandler.finishDragToDesktopTransition(WindowContainerTransaction())
+ val startTransaction = mock()
+ val endDragFinishCallback = mock()
+ defaultHandler.onTaskResizeAnimationListener = mock()
+ mergeInterruptingTransition(mergeTarget = startTransition)
+
+ val didAnimate =
+ defaultHandler.startAnimation(
+ transition = requireNotNull(endTransition),
+ info =
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+ draggedTask = task1,
+ ),
+ startTransaction = startTransaction,
+ finishTransaction = mock(),
+ finishCallback = endDragFinishCallback,
+ )
+
+ assertThat(didAnimate).isTrue()
+ verify(startTransaction).apply()
+ verify(endDragFinishCallback).onTransitionFinished(anyOrNull())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
+ fun startDrag_otherTransitionInterruptedStartAfterEndRequested_animatesDragWhenReady() {
+ val task1 = createTask()
+ val startTransition = startDrag(defaultHandler, task1)
+ verify(dragAnimator).startAnimation()
+ val endTransition =
+ defaultHandler.finishDragToDesktopTransition(WindowContainerTransaction())
+ defaultHandler.onTaskResizeAnimationListener = mock()
+ mergeInterruptingTransition(mergeTarget = startTransition)
+ defaultHandler.startAnimation(
+ transition = requireNotNull(endTransition),
+ info =
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+ draggedTask = task1,
+ ),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = mock(),
+ )
+
+ startDrag(defaultHandler, createTask())
+
+ verify(dragAnimator, times(2)).startAnimation()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX)
+ fun startCancelAnimation_otherTransitionInterruptingAfterCancelRequest_finishImmediately() {
+ val task1 = createTask()
+ val startTransition = startDrag(defaultHandler, task1)
+ val cancelTransition =
+ cancelDragToDesktopTransition(defaultHandler, CancelState.STANDARD_CANCEL)
+ mergeInterruptingTransition(mergeTarget = startTransition)
+ val cancelFinishCallback = mock()
+ val startTransaction = mock()
+
+ val didAnimate =
+ defaultHandler.startAnimation(
+ transition = requireNotNull(cancelTransition),
+ info =
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP,
+ draggedTask = task1,
+ ),
+ startTransaction = startTransaction,
+ finishTransaction = mock(),
+ finishCallback = cancelFinishCallback,
+ )
+
+ assertThat(didAnimate).isTrue()
+ verify(startTransaction).apply()
+ verify(cancelFinishCallback).onTransitionFinished(/* wct= */ anyOrNull())
+ }
+
+ private fun mergeInterruptingTransition(mergeTarget: IBinder) {
+ defaultHandler.mergeAnimation(
+ transition = mock(),
+ info = createTransitionInfo(type = TRANSIT_OPEN, draggedTask = createTask()),
+ startT = mock(),
+ finishT = mock(),
+ mergeTarget = mergeTarget,
+ finishCallback = mock(),
)
}
- private fun startDrag() {
+ private fun mergeAnimation(
+ transition: IBinder = mock(),
+ type: Int,
+ mergeTarget: IBinder,
+ task: RunningTaskInfo,
+ ) {
+ defaultHandler.mergeAnimation(
+ transition = transition,
+ info = createTransitionInfo(type = type, draggedTask = task),
+ startT = mock(),
+ finishT = mock(),
+ mergeTarget = mergeTarget,
+ finishCallback = mock(),
+ )
+ }
+
+ @Test
+ fun getAnimationFraction_returnsFraction() {
+ val fraction =
+ SpringDragToDesktopTransitionHandler.getAnimationFraction(
+ startBounds = Rect(0, 0, 0, 0),
+ endBounds = Rect(0, 0, 10, 10),
+ animBounds = Rect(0, 0, 5, 5),
+ )
+ assertThat(fraction).isWithin(TOLERANCE).of(0.5f)
+ }
+
+ @Test
+ fun getAnimationFraction_animBoundsSameAsEnd_returnsOne() {
+ val fraction =
+ SpringDragToDesktopTransitionHandler.getAnimationFraction(
+ startBounds = Rect(0, 0, 0, 0),
+ endBounds = Rect(0, 0, 10, 10),
+ animBounds = Rect(0, 0, 10, 10),
+ )
+ assertThat(fraction).isWithin(TOLERANCE).of(1f)
+ }
+
+ @Test
+ fun getAnimationFraction_startAndEndBoundsSameWidth_usesHeight() {
+ val fraction =
+ SpringDragToDesktopTransitionHandler.getAnimationFraction(
+ startBounds = Rect(0, 0, 10, 10),
+ endBounds = Rect(0, 0, 10, 30),
+ animBounds = Rect(0, 0, 10, 25),
+ )
+ assertThat(fraction).isWithin(TOLERANCE).of(0.75f)
+ }
+
+ @Test
+ fun getAnimationFraction_startAndEndBoundsSame_returnsZero() {
+ val fraction =
+ SpringDragToDesktopTransitionHandler.getAnimationFraction(
+ startBounds = Rect(0, 0, 10, 10),
+ endBounds = Rect(0, 0, 10, 10),
+ animBounds = Rect(0, 0, 10, 25),
+ )
+ assertThat(fraction).isWithin(TOLERANCE).of(0f)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX)
+ fun startDrag_indicatorFlagEnabled_attachesIndicatorToTransitionRoot() {
val task = createTask()
+ val rootLeash = mock()
+ val startTransaction = mock()
+ startDrag(
+ defaultHandler,
+ task,
+ startTransaction = startTransaction,
+ transitionRootLeash = rootLeash,
+ )
+
+ verify(visualIndicator).reparentLeash(startTransaction, rootLeash)
+ verify(visualIndicator).fadeInIndicator()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX)
+ fun startDrag_indicatorFlagDisabled_doesNotAttachIndicatorToTransitionRoot() {
+ val task = createTask()
+ val rootLeash = mock()
+ val startTransaction = mock()
+ startDrag(
+ defaultHandler,
+ task,
+ startTransaction = startTransaction,
+ transitionRootLeash = rootLeash,
+ )
+
+ verify(visualIndicator, never()).reparentLeash(any(), any())
+ verify(visualIndicator, never()).fadeInIndicator()
+ }
+
+ @Test
+ fun startDrag_hasDesktop_layerOrder_taskOnWallpaperOnHome() {
+ desktopState.overrideDesktopModeSupportPerDisplay[Display.DEFAULT_DISPLAY] = true
+ val task = createTask()
+ val rootLeash = mock()
+ val startTransaction = mock()
+ startDrag(
+ defaultHandler,
+ task,
+ startTransaction = startTransaction,
+ transitionRootLeash = rootLeash,
+ )
+
+ val draggedTaskLayer =
+ argumentCaptor {
+ verify(startTransaction).setLayer(eq(draggedTaskLeash), capture())
+ }
+ .firstValue
+ val wallpaperLayer =
+ argumentCaptor { verify(startTransaction).setLayer(eq(wallpaperLeash), capture()) }
+ .firstValue
+ val homeLayer =
+ argumentCaptor { verify(startTransaction).setLayer(eq(homeTaskLeash), capture()) }
+ .firstValue
+
+ // dragged task -> wallpaper -> home
+ assertThat(draggedTaskLayer).isGreaterThan(wallpaperLayer)
+ assertThat(wallpaperLayer).isGreaterThan(homeLayer)
+ }
+
+ @Test
+ @EnableFlags(
+ com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN,
+ com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE,
+ )
+ fun startDrag_noDesktop_layerOrder_taskOnHomeOnWallpaper() {
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_canInternalDisplayHostDesktops,
+ false,
+ )
+ desktopState.overrideDesktopModeSupportPerDisplay[Display.DEFAULT_DISPLAY] = false
+ val task = createTask()
+ val rootLeash = mock()
+ val startTransaction = mock()
+ startDrag(
+ defaultHandler,
+ task,
+ startTransaction = startTransaction,
+ transitionRootLeash = rootLeash,
+ )
+
+ val draggedTaskLayer =
+ argumentCaptor {
+ verify(startTransaction).setLayer(eq(draggedTaskLeash), capture())
+ }
+ .firstValue
+ val homeLayer =
+ argumentCaptor { verify(startTransaction).setLayer(eq(homeTaskLeash), capture()) }
+ .firstValue
+ val wallpaperLayer =
+ argumentCaptor { verify(startTransaction).setLayer(eq(wallpaperLeash), capture()) }
+ .firstValue
+
+ // dragged task -> home -> wallpaper
+ assertThat(draggedTaskLayer).isGreaterThan(homeLayer)
+ assertThat(homeLayer).isGreaterThan(wallpaperLayer)
+ }
+
+ private fun startDrag(
+ handler: DragToDesktopTransitionHandler,
+ task: RunningTaskInfo = createTask(),
+ startTransaction: SurfaceControl.Transaction = mock(),
+ finishTransaction: SurfaceControl.Transaction = mock(),
+ homeChange: TransitionInfo.Change? = createHomeChange(),
+ transitionRootLeash: SurfaceControl = mock(),
+ finishCallback: Transitions.TransitionFinishCallback = mock(),
+ ): IBinder {
whenever(dragAnimator.position).thenReturn(PointF())
// Simulate transition is started and is ready to animate.
- val transition = startDragToDesktopTransition(task, dragAnimator)
+ val transition = startDragToDesktopTransition(handler, task, dragAnimator)
handler.startAnimation(
transition = transition,
info =
- createTransitionInfo(
- type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
- draggedTask = task
- ),
- startTransaction = mock(),
- finishTransaction = mock(),
- finishCallback = {}
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
+ draggedTask = task,
+ homeChange = homeChange,
+ rootLeash = transitionRootLeash,
+ ),
+ startTransaction = startTransaction,
+ finishTransaction = finishTransaction,
+ finishCallback = finishCallback,
)
+ return transition
}
private fun startDragToDesktopTransition(
+ handler: DragToDesktopTransitionHandler,
task: RunningTaskInfo,
- dragAnimator: MoveToDesktopAnimator
+ dragAnimator: MoveToDesktopAnimator,
): IBinder {
val token = mock()
whenever(
transitions.startTransition(
eq(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP),
any(),
- eq(handler)
+ eq(handler),
)
)
.thenReturn(token)
- handler.startDragToDesktopTransition(task.taskId, dragAnimator)
+ handler.startDragToDesktopTransition(
+ task,
+ dragAnimator,
+ visualIndicator,
+ dragCancelCallback,
+ )
return token
}
- private fun performEarlyCancel(cancelState: DragToDesktopTransitionHandler.CancelState) {
+ private fun cancelDragToDesktopTransition(
+ handler: DragToDesktopTransitionHandler,
+ cancelState: DragToDesktopTransitionHandler.CancelState,
+ ): IBinder {
+ val token = mock()
+ whenever(
+ transitions.startTransition(
+ eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP),
+ any(),
+ eq(handler),
+ )
+ )
+ .thenReturn(token)
+ handler.cancelDragToDesktopTransition(cancelState)
+ mAnimatorTestRule.advanceTimeBy(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS)
+ return token
+ }
+
+ private fun performEarlyCancel(
+ handler: DragToDesktopTransitionHandler,
+ cancelState: DragToDesktopTransitionHandler.CancelState,
+ ) {
val task = createTask()
// Simulate transition is started and is ready to animate.
- val transition = startDragToDesktopTransition(task, dragAnimator)
+ val transition = startDragToDesktopTransition(handler, task, dragAnimator)
handler.cancelDragToDesktopTransition(cancelState)
handler.startAnimation(
transition = transition,
info =
- createTransitionInfo(
- type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
- draggedTask = task
- ),
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
+ draggedTask = task,
+ ),
startTransaction = mock(),
finishTransaction = mock(),
- finishCallback = {}
+ finishCallback = {},
)
// Don't even animate the "drag" since it was already cancelled.
@@ -326,6 +1241,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
return TestRunningTaskInfoBuilder()
.setActivityType(if (isHome) ACTIVITY_TYPE_HOME else ACTIVITY_TYPE_STANDARD)
.setWindowingMode(windowingMode)
+ .setUserId(mContext.userId)
.build()
.also {
whenever(splitScreenController.isTaskInSplitScreen(it.taskId))
@@ -333,29 +1249,49 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
}
}
- private fun createTransitionInfo(type: Int, draggedTask: RunningTaskInfo): TransitionInfo {
- return TransitionInfo(type, 0 /* flags */).apply {
- addChange( // Home.
- TransitionInfo.Change(mock(), mock()).apply {
- parent = null
- taskInfo =
- TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
- flags = flags or FLAG_IS_WALLPAPER
- }
- )
+ private fun createTransitionInfo(
+ type: Int,
+ draggedTask: RunningTaskInfo,
+ homeChange: TransitionInfo.Change? = createHomeChange(),
+ rootLeash: SurfaceControl = mock(),
+ deskChange: TransitionInfo.Change? = null,
+ ) =
+ TransitionInfo(type, /* flags= */ 0).apply {
+ homeChange?.let { addChange(it) }
addChange( // Dragged Task.
- TransitionInfo.Change(mock(), mock()).apply {
+ TransitionInfo.Change(mock(), draggedTaskLeash).apply {
parent = null
taskInfo = draggedTask
}
)
+ deskChange?.let { addChange(it) }
addChange( // Wallpaper.
- TransitionInfo.Change(mock(), mock()).apply {
+ TransitionInfo.Change(mock(), wallpaperLeash).apply {
parent = null
taskInfo = null
flags = flags or FLAG_IS_WALLPAPER
}
)
+ addRootLeash(draggedTask.displayId, rootLeash, /* offsetLeft= */ 0, /* offsetTop= */ 0)
}
+
+ private fun createHomeChange() =
+ TransitionInfo.Change(mock(), homeTaskLeash).apply {
+ parent = null
+ taskInfo = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
+ flags = flags or FLAG_IS_WALLPAPER
+ }
+
+ private fun createDeskChange() =
+ TransitionInfo.Change(mock(), mock()).apply {
+ parent = null
+ taskInfo = TestRunningTaskInfoBuilder().build()
+ }
+
+ private fun systemPropertiesKey(name: String) =
+ "${SpringDragToDesktopTransitionHandler.SYSTEM_PROPERTIES_GROUP}.$name"
+
+ private companion object {
+ private const val TOLERANCE = 1e-5f
}
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
index 2c910cf530..a82e5e8434 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
@@ -19,8 +19,6 @@ package com.android.wm.shell.desktopmode;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread;
-
import static com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN;
import static org.junit.Assert.assertTrue;
@@ -28,13 +26,17 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.animation.AnimatorTestRule;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
+import android.os.Handler;
import android.os.IBinder;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.util.DisplayMetrics;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -45,26 +47,35 @@ import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.ArrayList;
import java.util.function.Supplier;
/** Tests of {@link com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler} */
@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase {
+ @Rule
+ public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this);
+
@Mock
private Transitions mTransitions;
@Mock
+ private InteractionJankMonitor mInteractionJankMonitor;
+ @Mock
IBinder mToken;
@Mock
Supplier mTransactionFactory;
@@ -78,6 +89,8 @@ public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase {
Transitions.TransitionFinishCallback mTransitionFinishCallback;
@Mock
ShellExecutor mExecutor;
+ @Mock
+ Handler mHandler;
private Point mPoint;
private ExitDesktopTaskTransitionHandler mExitDesktopTaskTransitionHandler;
@@ -94,12 +107,12 @@ public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase {
.thenReturn(getContext().getResources().getDisplayMetrics());
mExitDesktopTaskTransitionHandler = new ExitDesktopTaskTransitionHandler(mTransitions,
- mContext);
+ mContext, mInteractionJankMonitor, mHandler);
mPoint = new Point(0, 0);
}
@Test
- public void testTransitExitDesktopModeAnimation() throws Throwable {
+ public void testTransitExitDesktopModeAnimation() {
final int transitionType = TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN;
final int taskId = 1;
WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -112,21 +125,16 @@ public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase {
TransitionInfo.Change change =
createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FULLSCREEN);
TransitionInfo info = createTransitionInfo(TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN, change);
- ArrayList exceptions = new ArrayList<>();
- runOnUiThread(() -> {
- try {
- assertTrue(mExitDesktopTaskTransitionHandler
- .startAnimation(mToken, info,
- new SurfaceControl.Transaction(),
- new SurfaceControl.Transaction(),
- mTransitionFinishCallback));
- } catch (Exception e) {
- exceptions.add(e);
- }
- });
- if (!exceptions.isEmpty()) {
- throw exceptions.get(0);
- }
+
+ final boolean animated = mExitDesktopTaskTransitionHandler
+ .startAnimation(mToken, info,
+ new SurfaceControl.Transaction(),
+ new SurfaceControl.Transaction(),
+ mTransitionFinishCallback);
+ mAnimatorTestRule.advanceTimeBy(
+ ExitDesktopTaskTransitionHandler.FULLSCREEN_ANIMATION_DURATION);
+
+ assertTrue(animated);
}
private TransitionInfo.Change createChange(@WindowManager.TransitionType int type, int taskId,
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
index a64ebd301c..58ab872ac7 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
@@ -20,6 +20,8 @@ import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.DragEvent.ACTION_DRAG_STARTED;
+import static com.android.wm.shell.draganddrop.DragTestUtils.createAppClipData;
+
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
@@ -30,9 +32,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.ClipData;
-import android.content.ClipDescription;
import android.content.Context;
-import android.content.Intent;
import android.os.RemoteException;
import android.view.Display;
import android.view.DragEvent;
@@ -47,8 +47,10 @@ import com.android.internal.logging.UiEventLogger;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.bubbles.bar.DragToBubbleController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.shared.desktopmode.FakeDesktopState;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -76,6 +78,8 @@ public class DragAndDropControllerTest extends ShellTestCase {
@Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
+ private ShellTaskOrganizer mShellTaskOrganizer;
+ @Mock
private DisplayController mDisplayController;
@Mock
private UiEventLogger mUiEventLogger;
@@ -90,14 +94,20 @@ public class DragAndDropControllerTest extends ShellTestCase {
@Mock
private GlobalDragListener mGlobalDragListener;
+ @Mock
+ private DragToBubbleController mDragToBubbleController;
+ private FakeDesktopState mDesktopState;
+
private DragAndDropController mController;
@Before
public void setUp() throws RemoteException {
+ mDesktopState = new FakeDesktopState();
MockitoAnnotations.initMocks(this);
mController = new DragAndDropController(mContext, mShellInit, mShellController,
- mShellCommandHandler, mDisplayController, mUiEventLogger, mIconProvider,
- mGlobalDragListener, mTransitions, mMainExecutor);
+ mShellCommandHandler, mShellTaskOrganizer, mDisplayController, mUiEventLogger,
+ mIconProvider, mGlobalDragListener, mTransitions, () -> mDragToBubbleController,
+ mMainExecutor, mDesktopState);
mController.onInit();
}
@@ -126,7 +136,7 @@ public class DragAndDropControllerTest extends ShellTestCase {
doReturn(display).when(dragLayout).getDisplay();
doReturn(DEFAULT_DISPLAY).when(display).getDisplayId();
- final ClipData clipData = createClipData();
+ final ClipData clipData = createAppClipData(MIMETYPE_APPLICATION_SHORTCUT);
final DragEvent event = mock(DragEvent.class);
doReturn(ACTION_DRAG_STARTED).when(event).getAction();
doReturn(clipData).when(event).getClipData();
@@ -149,14 +159,23 @@ public class DragAndDropControllerTest extends ShellTestCase {
verify(mDragAndDropListener, never()).onDragStarted();
}
- private ClipData createClipData() {
- ClipDescription clipDescription = new ClipDescription(MIMETYPE_APPLICATION_SHORTCUT,
- new String[] { MIMETYPE_APPLICATION_SHORTCUT });
- Intent i = new Intent();
- i.putExtra(Intent.EXTRA_PACKAGE_NAME, "pkg");
- i.putExtra(Intent.EXTRA_SHORTCUT_ID, "shortcutId");
- i.putExtra(Intent.EXTRA_USER, android.os.Process.myUserHandle());
- ClipData.Item item = new ClipData.Item(i);
- return new ClipData(clipDescription, item);
+ @Test
+ public void testOnDragStarted_withNoClipDataOrDescription() {
+ final View dragLayout = mock(View.class);
+ final Display display = mock(Display.class);
+ doReturn(display).when(dragLayout).getDisplay();
+ doReturn(DEFAULT_DISPLAY).when(display).getDisplayId();
+
+ final DragEvent event = mock(DragEvent.class);
+ doReturn(ACTION_DRAG_STARTED).when(event).getAction();
+ doReturn(null).when(event).getClipData();
+ doReturn(null).when(event).getClipDescription();
+
+ // Ensure there's a target so that onDrag will execute
+ mController.addDisplayDropTarget(0, mContext, mock(WindowManager.class),
+ mock(FrameLayout.class), mock(DragLayout.class));
+
+ // Verify the listener is called on a valid drag action.
+ mController.onDrag(dragLayout, event);
}
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt b/wmshell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt
index d410151b46..5389c94bc1 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt
@@ -43,7 +43,7 @@ import org.mockito.kotlin.verify
*/
@SmallTest
@RunWith(AndroidJUnit4::class)
-class UnhandledDragControllerTest : ShellTestCase() {
+class GlobalDragListenerTest : ShellTestCase() {
private val mIWindowManager = mock()
private val mMainExecutor = mock()
@@ -74,7 +74,7 @@ class UnhandledDragControllerTest : ShellTestCase() {
@Test
fun onUnhandledDrop_noListener_expectNotifyUnhandled() {
// Simulate an unhandled drop
- val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, 0, null, null, null,
+ val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, 0, 0, null, null, null,
null, null, false)
val wmCallback = mock()
mController.onUnhandledDrop(dropEvent, wmCallback)
@@ -98,7 +98,7 @@ class UnhandledDragControllerTest : ShellTestCase() {
// Simulate an unhandled drop
val dragSurface = mock()
- val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, 0, null, null, null,
+ val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, 0, 0, null, null, null,
dragSurface, null, false)
val wmCallback = mock()
mController.onUnhandledDrop(dropEvent, wmCallback)
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/wmshell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
index 3f3cafcf63..019fa6c5cb 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
@@ -18,25 +18,36 @@ package com.android.wm.shell.freeform;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Display.INVALID_DISPLAY;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION;
+import static com.android.window.flags.Flags.FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS;
+import static com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.view.SurfaceControl;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.common.LaunchAdjacentController;
+import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
+import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
+import com.android.wm.shell.shared.desktopmode.FakeDesktopState;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -45,14 +56,13 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.quality.Strictness;
+import org.mockito.MockitoAnnotations;
import java.util.Optional;
/**
- * Tests for {@link FreeformTaskListener}
- * Build/Install/Run:
- * atest WMShellUnitTests:FreeformTaskListenerTests
+ * Tests for {@link FreeformTaskListener} Build/Install/Run: atest
+ * WMShellUnitTests:FreeformTaskListenerTests
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -65,50 +75,281 @@ public final class FreeformTaskListenerTests extends ShellTestCase {
@Mock
private WindowDecorViewModel mWindowDecorViewModel;
@Mock
- private DesktopModeTaskRepository mDesktopModeTaskRepository;
+ private SurfaceControl mMockSurfaceControl;
+ @Mock
+ private DesktopUserRepositories mDesktopUserRepositories;
+ @Mock
+ private DesktopRepository mDesktopRepository;
+ @Mock
+ private DesktopTasksController mDesktopTasksController;
+ @Mock
+ private DesktopModeLoggerTransitionObserver mDesktopModeLoggerTransitionObserver;
+ @Mock
+ private LaunchAdjacentController mLaunchAdjacentController;
+ @Mock
+ private TaskChangeListener mTaskChangeListener;
+
private FreeformTaskListener mFreeformTaskListener;
- private StaticMockitoSession mMockitoSession;
+ private AutoCloseable mMocksInit = null;
@Before
public void setup() {
- mMockitoSession = mockitoSession().initMocks(this)
- .strictness(Strictness.LENIENT).mockStatic(DesktopModeStatus.class).startMocking();
- doReturn(true).when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
+ mMocksInit = MockitoAnnotations.openMocks(this);
- mFreeformTaskListener = new FreeformTaskListener(
- mContext,
- mShellInit,
- mTaskOrganizer,
- Optional.of(mDesktopModeTaskRepository),
- mWindowDecorViewModel);
+ var desktopState = new FakeDesktopState();
+ desktopState.setCanEnterDesktopMode(true);
+ desktopState.setFreeformEnabled(true);
+
+ when(mDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository);
+ when(mDesktopUserRepositories.getProfile(anyInt())).thenReturn(mDesktopRepository);
+ mFreeformTaskListener =
+ new FreeformTaskListener(
+ mContext,
+ mShellInit,
+ mTaskOrganizer,
+ Optional.of(mDesktopUserRepositories),
+ Optional.of(mDesktopTasksController),
+ mDesktopModeLoggerTransitionObserver,
+ mLaunchAdjacentController,
+ mWindowDecorViewModel,
+ Optional.of(mTaskChangeListener),
+ desktopState);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mMocksInit != null) {
+ mMocksInit.close();
+ mMocksInit = null;
+ }
}
@Test
- public void testFocusTaskChanged_freeformTaskIsAddedToRepo() {
- ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder()
- .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ @DisableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS)
+ public void onTaskAppeared_noTransitionObservers_visibleTask_addsTaskToRepo() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isVisible = true;
+
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ verify(mDesktopUserRepositories.getCurrent())
+ .addTask(task.displayId, task.taskId, task.isVisible = true,
+ task.configuration.windowConfiguration.getBounds()
+ );
+ }
+
+ @Test
+ @DisableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS)
+ public void onTaskAppeared_noTransitionObservers_nonVisibleTask_addsTaskToRepo() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isVisible = false;
+
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ verify(mDesktopUserRepositories.getCurrent())
+ .addTask(task.displayId, task.taskId, task.isVisible,
+ task.configuration.windowConfiguration.getBounds());
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS)
+ public void onTaskAppeared_useTransitionObserver_noopInRepository() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isVisible = true;
+
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ verify(mDesktopUserRepositories.getCurrent(), never())
+ .addTask(task.displayId, task.taskId, task.isVisible,
+ task.configuration.windowConfiguration.getBounds());
+ }
+
+ @Test
+ @DisableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS)
+ public void focusTaskChanged_noTransitionObserversFlag_addsFreeformTaskToRepo() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
task.isFocused = true;
mFreeformTaskListener.onFocusTaskChanged(task);
- verify(mDesktopModeTaskRepository)
- .addOrMoveFreeformTaskToTop(task.displayId, task.taskId);
+ verify(mDesktopUserRepositories.getCurrent())
+ .addTask(task.displayId, task.taskId, task.isVisible,
+ task.configuration.windowConfiguration.getBounds());
}
@Test
- public void testFocusTaskChanged_fullscreenTaskIsNotAddedToRepo() {
- ActivityManager.RunningTaskInfo fullscreenTask = new TestRunningTaskInfoBuilder()
- .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build();
+ @EnableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS)
+ public void focusTaskChanged_enableTransitionObservers_freeformTaskNotAddedToRepo() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isFocused = true;
+
+ mFreeformTaskListener.onFocusTaskChanged(task);
+
+ verify(mDesktopUserRepositories.getCurrent(), never())
+ .addTask(task.displayId, task.taskId, task.isVisible,
+ task.configuration.windowConfiguration.getBounds());
+ }
+
+ @Test
+ public void focusTaskChanged_fullscreenTaskNotAddedToRepo() {
+ ActivityManager.RunningTaskInfo fullscreenTask =
+ new TestRunningTaskInfoBuilder()
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .build();
fullscreenTask.isFocused = true;
mFreeformTaskListener.onFocusTaskChanged(fullscreenTask);
- verify(mDesktopModeTaskRepository, never())
- .addOrMoveFreeformTaskToTop(fullscreenTask.displayId, fullscreenTask.taskId);
+ verify(mDesktopUserRepositories.getCurrent(), never())
+ .addTask(fullscreenTask.displayId, fullscreenTask.taskId, fullscreenTask.isVisible,
+ fullscreenTask.configuration.windowConfiguration.getBounds());
}
- @After
- public void tearDown() {
- mMockitoSession.finishMocking();
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ public void visibilityTaskChanged_visible_setLaunchAdjacentDisabled() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isVisible = true;
+
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ verify(mLaunchAdjacentController).setLaunchAdjacentEnabled(false);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ public void visibilityTaskChanged_notVisible_setLaunchAdjacentEnabled() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isVisible = true;
+
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ task.isVisible = false;
+ mFreeformTaskListener.onTaskInfoChanged(task);
+
+ verify(mLaunchAdjacentController).setLaunchAdjacentEnabled(true);
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ @DisableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS)
+ public void onTaskVanished_minimizedTask_noTransitionObservers_isNotRemoved() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isVisible = true;
+ when(mDesktopRepository.isMinimizedTask(task.taskId)).thenReturn(true);
+
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ task.isVisible = false;
+ task.displayId = INVALID_DISPLAY;
+ mFreeformTaskListener.onTaskVanished(task);
+
+ verify(mDesktopUserRepositories.getCurrent(), never()).removeTask(task.taskId);
+ }
+
+ @Test
+ @DisableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS)
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ public void onTaskVanished_closingTask_noTransitionObservers_isNotMinimized() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isVisible = true;
+
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ when(mDesktopUserRepositories.getCurrent()
+ .isClosingTask(task.taskId)).thenReturn(true);
+ task.isVisible = false;
+ task.displayId = INVALID_DISPLAY;
+ mFreeformTaskListener.onTaskVanished(task);
+
+ verify(mDesktopUserRepositories.getCurrent(), never())
+ .minimizeTask(task.displayId, task.taskId);
+ verify(mDesktopUserRepositories.getCurrent()).removeClosingTask(task.taskId);
+ verify(mDesktopUserRepositories.getCurrent()).removeTask(task.taskId);
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS)
+ public void onTaskVanished_usesTransitionObservers_noopInRepo() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ mFreeformTaskListener.onTaskVanished(task);
+
+ verify(mDesktopUserRepositories.getCurrent(), never())
+ .minimizeTask(task.displayId, task.taskId);
+ verify(mDesktopUserRepositories.getCurrent(), never())
+ .removeClosingTask(task.taskId);
+ verify(mDesktopUserRepositories.getCurrent(), never()).removeTask(task.taskId);
+ }
+
+ @Test
+ public void onTaskVanished_withDesktopModeLogger_forwards() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isVisible = true;
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ mFreeformTaskListener.onTaskVanished(task);
+
+ verify(mDesktopModeLoggerTransitionObserver).onTaskVanished(task);
+ }
+
+
+ @Test
+ public void onTaskInfoChanged_withDesktopController_forwards() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isVisible = true;
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ mFreeformTaskListener.onTaskInfoChanged(task);
+
+ verify(mDesktopTasksController).onTaskInfoChanged(task);
+ }
+
+ @Test
+ @DisableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS)
+ public void onTaskInfoChanged_noTransitionObservers_updatesTask() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isVisible = true;
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ mFreeformTaskListener.onTaskInfoChanged(task);
+
+ verify(mTaskChangeListener, never()).onTaskChanging(any());
+ verify(mDesktopUserRepositories.getCurrent())
+ .updateTask(task.displayId, task.taskId, task.isVisible,
+ task.configuration.windowConfiguration.getBounds()
+ );
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS)
+ @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
+ public void onTaskInfoChanged_useTransitionObserver_noopInRepository() {
+ ActivityManager.RunningTaskInfo task =
+ new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ task.isVisible = true;
+ mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+ mFreeformTaskListener.onTaskInfoChanged(task);
+
+ verify(mTaskChangeListener).onNonTransitionTaskChanging(any());
+ verify(mDesktopUserRepositories.getCurrent(), never())
+ .updateTask(task.displayId, task.taskId, task.isVisible,
+ task.configuration.windowConfiguration.getBounds()
+ );
}
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
index 499e339bc6..2dff2fb39a 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
@@ -17,9 +17,16 @@
package com.android.wm.shell.freeform;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_START_RECENTS_TRANSITION;
+
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -30,6 +37,8 @@ import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.IBinder;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.view.SurfaceControl;
import android.window.IWindowContainerToken;
import android.window.TransitionInfo;
@@ -37,66 +46,101 @@ import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.desktopmode.DesktopBackNavTransitionObserver;
+import com.android.wm.shell.desktopmode.DesktopImeHandler;
+import com.android.wm.shell.desktopmode.DesktopImmersiveController;
+import com.android.wm.shell.desktopmode.DesktopInOrderTransitionObserver;
+import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer;
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver;
+import com.android.wm.shell.shared.desktopmode.FakeDesktopState;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.FocusTransitionObserver;
import com.android.wm.shell.transition.TransitionInfoBuilder;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.StubTransaction;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-/**
- * Tests of {@link FreeformTaskTransitionObserver}
- */
+import java.util.Optional;
+
+/** Tests for {@link FreeformTaskTransitionObserver}. */
@SmallTest
-public class FreeformTaskTransitionObserverTest {
-
- @Mock
- private ShellInit mShellInit;
- @Mock
- private Transitions mTransitions;
- @Mock
- private WindowDecorViewModel mWindowDecorViewModel;
+public class FreeformTaskTransitionObserverTest extends ShellTestCase {
+ @Mock private ShellInit mShellInit;
+ @Mock private Transitions mTransitions;
+ @Mock private DesktopImmersiveController mDesktopImmersiveController;
+ @Mock private WindowDecorViewModel mWindowDecorViewModel;
+ @Mock private TaskChangeListener mTaskChangeListener;
+ @Mock private FocusTransitionObserver mFocusTransitionObserver;
+ @Mock private DesksOrganizer mDesksOrganizer;
+ @Mock private DesksTransitionObserver mDesksTransitionObserver;
+ @Mock private DesktopImeHandler mDesktopImeHandler;
+ @Mock private DesktopBackNavTransitionObserver mDesktopBackNavTransitionObserver;
+ @Mock private DesktopInOrderTransitionObserver mDesktopInOrderTransitionObserver;
+ private FakeDesktopState mDesktopState;
private FreeformTaskTransitionObserver mTransitionObserver;
+ private AutoCloseable mMocksInits = null;
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
+ mMocksInits = MockitoAnnotations.openMocks(this);
+
+ mDesktopState = new FakeDesktopState();
+ mDesktopState.setFreeformEnabled(true);
PackageManager pm = mock(PackageManager.class);
- doReturn(true).when(pm).hasSystemFeature(
- PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
+ doReturn(true).when(pm).hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
+ doReturn(false).when(mDesksOrganizer).isDeskChange(any());
final Context context = mock(Context.class);
doReturn(pm).when(context).getPackageManager();
- mTransitionObserver = new FreeformTaskTransitionObserver(
- context, mShellInit, mTransitions, mWindowDecorViewModel);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- final ArgumentCaptor initRunnableCaptor = ArgumentCaptor.forClass(
- Runnable.class);
- verify(mShellInit).addInitCallback(initRunnableCaptor.capture(),
- same(mTransitionObserver));
- initRunnableCaptor.getValue().run();
- } else {
- mTransitionObserver.onInit();
+ mTransitionObserver =
+ new FreeformTaskTransitionObserver(
+ mShellInit,
+ mTransitions,
+ Optional.of(mDesktopImmersiveController),
+ mWindowDecorViewModel,
+ Optional.of(mTaskChangeListener),
+ mFocusTransitionObserver,
+ mDesksOrganizer,
+ Optional.of(mDesksTransitionObserver),
+ mDesktopState,
+ Optional.of(mDesktopImeHandler),
+ Optional.of(mDesktopBackNavTransitionObserver),
+ Optional.of(mDesktopInOrderTransitionObserver));
+
+ final ArgumentCaptor initRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mShellInit).addInitCallback(initRunnableCaptor.capture(), same(mTransitionObserver));
+ initRunnableCaptor.getValue().run();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mMocksInits != null) {
+ mMocksInits.close();
+ mMocksInits = null;
}
}
@Test
- public void testRegistersObserverAtInit() {
+ public void init_registersObserver() {
verify(mTransitions).registerObserver(same(mTransitionObserver));
}
@Test
- public void testCreatesWindowDecorOnOpenTransition_freeform() {
- final TransitionInfo.Change change =
- createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
- .addChange(change).build();
+ public void openTransition_createsWindowDecor() {
+ final TransitionInfo.Change change = createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FREEFORM);
+ final TransitionInfo info =
+ new TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build();
final IBinder transition = mock(IBinder.class);
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
@@ -104,16 +148,17 @@ public class FreeformTaskTransitionObserverTest {
mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
mTransitionObserver.onTransitionStarting(transition);
- verify(mWindowDecorViewModel).onTaskOpening(
- change.getTaskInfo(), change.getLeash(), startT, finishT);
+ verify(mWindowDecorViewModel)
+ .onTaskOpening(change.getTaskInfo(), change.getLeash(), startT, finishT);
}
@Test
- public void testPreparesWindowDecorOnCloseTransition_freeform() {
- final TransitionInfo.Change change =
- createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
- .addChange(change).build();
+ @EnableFlags(Flags.FLAG_ENABLE_NO_WINDOW_DECORATION_FOR_DESKS)
+ public void desksChange_windowDecorNotCreatedForDesksTask() {
+ final TransitionInfo.Change change = createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FREEFORM);
+ final TransitionInfo info =
+ new TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build();
+ doReturn(true).when(mDesksOrganizer).isDeskChange(change);
final IBinder transition = mock(IBinder.class);
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
@@ -121,16 +166,157 @@ public class FreeformTaskTransitionObserverTest {
mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
mTransitionObserver.onTransitionStarting(transition);
- verify(mWindowDecorViewModel).onTaskClosing(
- change.getTaskInfo(), startT, finishT);
+ verify(mWindowDecorViewModel, never())
+ .onTaskOpening(change.getTaskInfo(), change.getLeash(), startT, finishT);
}
@Test
- public void testDoesntCloseWindowDecorDuringCloseTransition() throws Exception {
+ @EnableFlags(Flags.FLAG_ENABLE_NO_WINDOW_DECORATION_FOR_DESKS)
+ public void desksChange_listenerNotNotifiedOfTaskChange() {
+ final TransitionInfo.Change change =
+ createChange(TRANSIT_CHANGE, /* taskId= */ 1, WINDOWING_MODE_FREEFORM);
+ final TransitionInfo info =
+ new TransitionInfoBuilder(TRANSIT_CHANGE, /* flags= */ 0).addChange(change).build();
+ doReturn(true).when(mDesksOrganizer).isDeskChange(change);
+
+ final IBinder transition = mock(IBinder.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
+ mTransitionObserver.onTransitionStarting(transition);
+
+ verify(mTaskChangeListener, never()).onTaskChanging(change.getTaskInfo());
+ }
+
+ @Test
+ public void openTransition_notifiesOnTaskOpening() {
+ final TransitionInfo.Change change = createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FREEFORM);
+ final TransitionInfo info =
+ new TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build();
+
+ final IBinder transition = mock(IBinder.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
+ mTransitionObserver.onTransitionStarting(transition);
+
+ verify(mTaskChangeListener).onTaskOpening(change.getTaskInfo());
+ }
+
+ @Test
+ public void toFrontTransition_notifiesOnTaskMovingToFront() {
+ final TransitionInfo.Change change =
+ createChange(TRANSIT_TO_FRONT, /* taskId= */ 1, WINDOWING_MODE_FREEFORM);
+ final TransitionInfo info =
+ new TransitionInfoBuilder(TRANSIT_TO_FRONT, /* flags= */ 0)
+ .addChange(change)
+ .build();
+
+ final IBinder transition = mock(IBinder.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
+ mTransitionObserver.onTransitionStarting(transition);
+
+ verify(mTaskChangeListener).onTaskMovingToFront(change.getTaskInfo());
+ }
+
+ @Test
+ public void toBackTransition_notifiesOnTaskMovingToBack() {
+ final TransitionInfo.Change change =
+ createChange(TRANSIT_TO_BACK, /* taskId= */ 1, WINDOWING_MODE_FREEFORM);
+ final TransitionInfo info =
+ new TransitionInfoBuilder(TRANSIT_TO_BACK, /* flags= */ 0)
+ .addChange(change)
+ .build();
+
+ final IBinder transition = mock(IBinder.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
+ mTransitionObserver.onTransitionStarting(transition);
+
+ verify(mTaskChangeListener).onTaskMovingToBack(change.getTaskInfo());
+ }
+
+ @Test
+ public void recentsTransition_onTransitionFinished_notifiesOnTaskMovingToBack() {
+ final TransitionInfo.Change change =
+ createChange(TRANSIT_TO_BACK, /* taskId= */ 1, WINDOWING_MODE_FREEFORM);
+ final TransitionInfo.Change homeChange =
+ createChange(TRANSIT_TO_FRONT, /* taskId= */ 2, WINDOWING_MODE_FULLSCREEN);
+ final TransitionInfo info =
+ new TransitionInfoBuilder(TRANSIT_START_RECENTS_TRANSITION, /* flags= */ 0)
+ .addChange(homeChange)
+ .addChange(change)
+ .build();
+
+ final IBinder transition = mock(IBinder.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
+ mTransitionObserver.onTransitionStarting(transition);
+
+ verify(mTaskChangeListener, never()).onTaskMovingToBack(change.getTaskInfo());
+
+ mTransitionObserver.onTransitionFinished(transition, false);
+ verify(mTaskChangeListener).onTaskMovingToBack(change.getTaskInfo());
+ }
+
+ @Test
+ public void changeTransition_notifiesOnTaskChange() {
+ final TransitionInfo.Change change =
+ createChange(TRANSIT_CHANGE, /* taskId= */ 1, WINDOWING_MODE_FREEFORM);
+ final TransitionInfo info =
+ new TransitionInfoBuilder(TRANSIT_CHANGE, /* flags= */ 0).addChange(change).build();
+
+ final IBinder transition = mock(IBinder.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
+ mTransitionObserver.onTransitionStarting(transition);
+
+ verify(mTaskChangeListener).onTaskChanging(change.getTaskInfo());
+ }
+
+ @Test
+ public void closeTransition_preparesWindowDecor() {
final TransitionInfo.Change change =
createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
- .addChange(change).build();
+ final TransitionInfo info =
+ new TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build();
+
+ final IBinder transition = mock(IBinder.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
+ mTransitionObserver.onTransitionStarting(transition);
+
+ verify(mWindowDecorViewModel).onTaskClosing(change.getTaskInfo(), startT, finishT);
+ }
+
+ @Test
+ public void closeTransition_notifiesOnTaskClosing() {
+ final TransitionInfo.Change change =
+ createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
+ final TransitionInfo info =
+ new TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build();
+
+ final IBinder transition = mock(IBinder.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
+ mTransitionObserver.onTransitionStarting(transition);
+
+ verify(mTaskChangeListener).onTaskClosing(change.getTaskInfo());
+ }
+
+ @Test
+ public void closeTransition_doesntCloseWindowDecorDuringTransition() throws Exception {
+ final TransitionInfo.Change change =
+ createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
+ final TransitionInfo info =
+ new TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build();
final IBinder transition = mock(IBinder.class);
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
@@ -142,11 +328,11 @@ public class FreeformTaskTransitionObserverTest {
}
@Test
- public void testClosesWindowDecorAfterCloseTransition() throws Exception {
+ public void closeTransition_closesWindowDecorAfterTransition() throws Exception {
final TransitionInfo.Change change =
createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
- .addChange(change).build();
+ final TransitionInfo info =
+ new TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build();
final AutoCloseable windowDecor = mock(AutoCloseable.class);
@@ -161,12 +347,12 @@ public class FreeformTaskTransitionObserverTest {
}
@Test
- public void testClosesMergedWindowDecorationAfterTransitionFinishes() throws Exception {
+ public void transitionFinished_closesMergedWindowDecoration() throws Exception {
// The playing transition
final TransitionInfo.Change change1 =
createChange(TRANSIT_OPEN, 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
- .addChange(change1).build();
+ final TransitionInfo info1 =
+ new TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change1).build();
final IBinder transition1 = mock(IBinder.class);
final SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
@@ -177,8 +363,8 @@ public class FreeformTaskTransitionObserverTest {
// The merged transition
final TransitionInfo.Change change2 =
createChange(TRANSIT_CLOSE, 2, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
- .addChange(change2).build();
+ final TransitionInfo info2 =
+ new TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change2).build();
final IBinder transition2 = mock(IBinder.class);
final SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
@@ -192,12 +378,12 @@ public class FreeformTaskTransitionObserverTest {
}
@Test
- public void testClosesAllWindowDecorsOnTransitionMergeAfterCloseTransitions() throws Exception {
+ public void closeTransition_closesWindowDecorsOnTransitionMerge() throws Exception {
// The playing transition
final TransitionInfo.Change change1 =
createChange(TRANSIT_CLOSE, 1, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
- .addChange(change1).build();
+ final TransitionInfo info1 =
+ new TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change1).build();
final IBinder transition1 = mock(IBinder.class);
final SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
@@ -208,8 +394,8 @@ public class FreeformTaskTransitionObserverTest {
// The merged transition
final TransitionInfo.Change change2 =
createChange(TRANSIT_CLOSE, 2, WINDOWING_MODE_FREEFORM);
- final TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE, 0)
- .addChange(change2).build();
+ final TransitionInfo info2 =
+ new TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change2).build();
final IBinder transition2 = mock(IBinder.class);
final SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
@@ -223,14 +409,134 @@ public class FreeformTaskTransitionObserverTest {
verify(mWindowDecorViewModel).destroyWindowDecoration(change2.getTaskInfo());
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ @DisableFlags(Flags.FLAG_ENABLE_INORDER_TRANSITION_CALLBACKS_FOR_DESKTOP)
+ public void onTransitionReady_forwardsToDesktopImmersiveController() {
+ final IBinder transition = mock(IBinder.class);
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CHANGE, 0).build();
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+
+ mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
+
+ verify(mDesktopImmersiveController).onTransitionReady(transition, info, startT, finishT);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ @DisableFlags(Flags.FLAG_ENABLE_INORDER_TRANSITION_CALLBACKS_FOR_DESKTOP)
+ public void onTransitionMerged_forwardsToDesktopImmersiveController() {
+ final IBinder merged = mock(IBinder.class);
+ final IBinder playing = mock(IBinder.class);
+
+ mTransitionObserver.onTransitionMerged(merged, playing);
+
+ verify(mDesktopImmersiveController).onTransitionMerged(merged, playing);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ @DisableFlags(Flags.FLAG_ENABLE_INORDER_TRANSITION_CALLBACKS_FOR_DESKTOP)
+ public void onTransitionStarting_forwardsToDesktopImmersiveController() {
+ final IBinder transition = mock(IBinder.class);
+
+ mTransitionObserver.onTransitionStarting(transition);
+
+ verify(mDesktopImmersiveController).onTransitionStarting(transition);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ @DisableFlags(Flags.FLAG_ENABLE_INORDER_TRANSITION_CALLBACKS_FOR_DESKTOP)
+ public void onTransitionFinished_forwardsToDesktopImmersiveController() {
+ final IBinder transition = mock(IBinder.class);
+
+ mTransitionObserver.onTransitionFinished(transition, /* aborted= */ false);
+
+ verify(mDesktopImmersiveController).onTransitionFinished(transition, /* aborted= */ false);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_INORDER_TRANSITION_CALLBACKS_FOR_DESKTOP)
+ public void onTransitionReady_forwardsToDesksTransitionObserver() {
+ final IBinder transition = mock(IBinder.class);
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, /* flags= */ 0)
+ .build();
+
+ mTransitionObserver.onTransitionReady(transition, info, new StubTransaction(),
+ new StubTransaction());
+
+ verify(mDesksTransitionObserver).onTransitionReady(transition, info);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_INORDER_TRANSITION_CALLBACKS_FOR_DESKTOP)
+ public void onTransitionMerged_forwardsToDesksTransitionObserver() {
+ final IBinder merged = mock(IBinder.class);
+ final IBinder playing = mock(IBinder.class);
+
+ mTransitionObserver.onTransitionMerged(merged, playing);
+
+ verify(mDesksTransitionObserver).onTransitionMerged(merged, playing);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_INORDER_TRANSITION_CALLBACKS_FOR_DESKTOP)
+ public void onTransitionFinished_forwardsToDesksTransitionObserver() {
+ final IBinder transition = mock(IBinder.class);
+
+ mTransitionObserver.onTransitionFinished(transition, /* aborted = */ false);
+
+ verify(mDesksTransitionObserver).onTransitionFinished(transition);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_INORDER_TRANSITION_CALLBACKS_FOR_DESKTOP)
+ public void onTransitionReady_forwardsToDesktopInOrderTransitionObserver() {
+ final IBinder transition = mock(IBinder.class);
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, /* flags= */ 0)
+ .build();
+ final SurfaceControl.Transaction startT = new StubTransaction();
+ final SurfaceControl.Transaction finishT = new StubTransaction();
+
+
+ mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
+
+ verify(mDesktopInOrderTransitionObserver).onTransitionReady(transition, info, startT,
+ finishT);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_INORDER_TRANSITION_CALLBACKS_FOR_DESKTOP)
+ public void onTransitionMerged_forwardsToDesktopInOrderTransitionObserver() {
+ final IBinder merged = mock(IBinder.class);
+ final IBinder playing = mock(IBinder.class);
+
+ mTransitionObserver.onTransitionMerged(merged, playing);
+
+ verify(mDesktopInOrderTransitionObserver).onTransitionMerged(merged, playing);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_INORDER_TRANSITION_CALLBACKS_FOR_DESKTOP)
+ public void onTransitionFinished_forwardsToDesktopInOrderTransitionObserver() {
+ final IBinder transition = mock(IBinder.class);
+
+ mTransitionObserver.onTransitionFinished(transition, /* aborted = */ false);
+
+ verify(mDesktopInOrderTransitionObserver).onTransitionFinished(transition, false);
+ }
+
private static TransitionInfo.Change createChange(int mode, int taskId, int windowingMode) {
final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.taskId = taskId;
taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
- final TransitionInfo.Change change = new TransitionInfo.Change(
- new WindowContainerToken(mock(IWindowContainerToken.class)),
- mock(SurfaceControl.class));
+ final TransitionInfo.Change change =
+ new TransitionInfo.Change(
+ new WindowContainerToken(mock(IWindowContainerToken.class)),
+ mock(SurfaceControl.class));
change.setMode(mode);
change.setTaskInfo(taskInfo);
return change;
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index 8ad3d2a726..256ed413c2 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -51,7 +51,6 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Test;
@@ -179,7 +178,7 @@ public class OneHandedControllerTest extends OneHandedTestCase {
@Test
public void testControllerRegisteresExternalInterface() {
verify(mMockShellController, times(1)).addExternalInterface(
- eq(ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED), any(), any());
+ eq(IOneHanded.DESCRIPTOR), any(), any());
}
@Test
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
index 9c7f723787..9146906b63 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
@@ -37,6 +37,7 @@ import static org.mockito.Mockito.when;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
+import android.os.Handler;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.Display;
@@ -100,6 +101,8 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
OneHandedSettingsUtil mMockSettingsUitl;
@Mock
InteractionJankMonitor mJankMonitor;
+ @Mock
+ Handler mMockHandler;
List mDisplayAreaAppearedInfoList = new ArrayList<>();
@@ -142,7 +145,8 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
mMockAnimationController,
mTutorialHandler,
mJankMonitor,
- mMockShellMainExecutor));
+ mMockShellMainExecutor,
+ mMockHandler));
for (int i = 0; i < DISPLAYAREA_INFO_COUNT; i++) {
mDisplayAreaAppearedInfoList.add(getDummyDisplayAreaInfo());
@@ -429,7 +433,8 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
mMockAnimationController,
mTutorialHandler,
mJankMonitor,
- mMockShellMainExecutor));
+ mMockShellMainExecutor,
+ mMockHandler));
assertThat(testSpiedDisplayAreaOrganizer.isReady()).isFalse();
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
index 5880ffb0dc..7e68b68e46 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
@@ -28,8 +28,11 @@ import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTI
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import android.app.AppCompatTaskInfo;
import android.app.TaskInfo;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
@@ -75,6 +78,7 @@ public class PipAnimationControllerTest extends ShellTestCase {
.setContainerLayer()
.setName("FakeLeash")
.build();
+ mTaskInfo.appCompatTaskInfo = mock(AppCompatTaskInfo.class);
}
@Test
@@ -83,17 +87,21 @@ public class PipAnimationControllerTest extends ShellTestCase {
.getAnimator(mTaskInfo, mLeash, new Rect(), 0f, 1f);
assertEquals("Expect ANIM_TYPE_ALPHA animation",
- animator.getAnimationType(), PipAnimationController.ANIM_TYPE_ALPHA);
+ animator.getAnimationType(), PipTransitionController.ANIM_TYPE_ALPHA);
}
@Test
public void getAnimator_withBounds_returnBoundsAnimator() {
+ final Rect baseValue = new Rect(0, 0, 100, 100);
+ final Rect startValue = new Rect(0, 0, 100, 100);
+ final Rect endValue1 = new Rect(100, 100, 200, 200);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mTaskInfo, mLeash, new Rect(), new Rect(), new Rect(), null,
- TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
+ .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
assertEquals("Expect ANIM_TYPE_BOUNDS animation",
- animator.getAnimationType(), PipAnimationController.ANIM_TYPE_BOUNDS);
+ animator.getAnimationType(), PipTransitionController.ANIM_TYPE_BOUNDS);
}
@Test
@@ -104,14 +112,16 @@ public class PipAnimationControllerTest extends ShellTestCase {
final Rect endValue2 = new Rect(200, 200, 300, 300);
final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController
.getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
- TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
oldAnimator.setSurfaceControlTransactionFactory(
MockSurfaceControlHelper::createMockSurfaceControlTransaction);
oldAnimator.start();
final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController
.getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue2, null,
- TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
assertEquals("getAnimator with same type returns same animator",
oldAnimator, newAnimator);
@@ -142,7 +152,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
// Fullscreen to PiP.
PipAnimationController.PipTransitionAnimator> animator = mPipAnimationController
.getAnimator(mTaskInfo, mLeash, null, startBounds, endBounds, null,
- TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_90);
+ TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_90,
+ false /* alwaysAnimateTaskBounds */);
// Apply fraction 1 to compute the end value.
animator.applySurfaceControlTransaction(mLeash, tx, 1);
final Rect rotatedEndBounds = new Rect(endBounds);
@@ -154,7 +165,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
startBounds.set(0, 0, 1000, 500);
endBounds.set(200, 100, 400, 500);
animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, startBounds, startBounds,
- endBounds, null, TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_270);
+ endBounds, null, TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_270,
+ false /* alwaysAnimateTaskBounds */);
animator.applySurfaceControlTransaction(mLeash, tx, 1);
rotatedEndBounds.set(endBounds);
rotateBounds(rotatedEndBounds, startBounds, ROTATION_270);
@@ -162,6 +174,39 @@ public class PipAnimationControllerTest extends ShellTestCase {
assertEquals("Expect 270 degree rotated bounds", rotatedEndBounds, animator.mCurrentValue);
}
+ @Test
+ public void pipTransitionAnimator_rotatedEndValue_overrideMainWindowFrame() {
+ final SurfaceControl.Transaction tx = createMockSurfaceControlTransaction();
+ final Rect startBounds = new Rect(200, 700, 400, 800);
+ final Rect endBounds = new Rect(0, 0, 500, 1000);
+ mTaskInfo.topActivityMainWindowFrame = new Rect(0, 250, 1000, 500);
+
+ // Fullscreen task to PiP.
+ PipAnimationController.PipTransitionAnimator> animator = mPipAnimationController
+ .getAnimator(mTaskInfo, mLeash, null, startBounds, endBounds, null,
+ TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_90,
+ false /* alwaysAnimateTaskBounds */);
+ // Apply fraction 1 to compute the end value.
+ animator.applySurfaceControlTransaction(mLeash, tx, 1);
+
+ assertEquals("Expect main window frame", mTaskInfo.topActivityMainWindowFrame,
+ animator.mCurrentValue);
+
+ // PiP to fullscreen.
+ mTaskInfo.topActivityMainWindowFrame = new Rect(0, 250, 1000, 500);
+ startBounds.set(0, 0, 1000, 500);
+ endBounds.set(200, 100, 400, 500);
+ animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, startBounds, startBounds,
+ endBounds, null, TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_270,
+ false /* alwaysAnimateTaskBounds */);
+ animator.applySurfaceControlTransaction(mLeash, tx, 1);
+ final Rect rotatedEndBounds = new Rect(endBounds);
+ rotateBounds(rotatedEndBounds, startBounds, ROTATION_270);
+
+ assertEquals("Expect rotated bounds. We only use main window frame for "
+ + "leave-pip animation", rotatedEndBounds, animator.mCurrentValue);
+ }
+
@Test
@SuppressWarnings("unchecked")
public void pipTransitionAnimator_updateEndValue() {
@@ -171,7 +216,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
final Rect endValue2 = new Rect(200, 200, 300, 300);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
.getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
- TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
animator.updateEndValue(endValue2);
@@ -185,7 +231,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
final Rect endValue = new Rect(100, 100, 200, 200);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
.getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
- TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
animator.setSurfaceControlTransactionFactory(
MockSurfaceControlHelper::createMockSurfaceControlTransaction);
@@ -204,4 +251,126 @@ public class PipAnimationControllerTest extends ShellTestCase {
verify(mPipAnimationCallback).onPipAnimationEnd(eq(mTaskInfo),
any(SurfaceControl.Transaction.class), eq(animator));
}
+
+ @Test
+ public void pipTransitionAnimator_overrideMainWindowFrame() {
+ final Rect baseValue = new Rect(0, 0, 100, 100);
+ final Rect startValue = new Rect(0, 0, 100, 100);
+ final Rect endValue = new Rect(100, 100, 200, 200);
+ mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100);
+ PipAnimationController.PipTransitionAnimator> animator = mPipAnimationController
+ .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is overridden for in-PIP transition",
+ mTaskInfo.topActivityMainWindowFrame, animator.getBaseValue());
+ assertEquals("Expect start value is overridden for in-PIP transition",
+ mTaskInfo.topActivityMainWindowFrame, animator.getStartValue());
+ assertEquals("Expect end value is not overridden for in-PIP transition",
+ endValue, animator.getEndValue());
+
+ animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue,
+ endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is not overridden for leave-PIP transition",
+ baseValue, animator.getBaseValue());
+ assertEquals("Expect start value is not overridden for leave-PIP transition",
+ startValue, animator.getStartValue());
+ assertEquals("Expect end value is overridden for leave-PIP transition",
+ mTaskInfo.topActivityMainWindowFrame, animator.getEndValue());
+ }
+
+ @Test
+ public void pipTransitionAnimator_animateTaskBounds() {
+ final Rect baseValue = new Rect(0, 0, 100, 100);
+ final Rect startValue = new Rect(0, 0, 100, 100);
+ final Rect endValue = new Rect(100, 100, 200, 200);
+ mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100);
+ PipAnimationController.PipTransitionAnimator> animator = mPipAnimationController
+ .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ true /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is not overridden for in-PIP transition",
+ baseValue, animator.getBaseValue());
+ assertEquals("Expect start value is not overridden for in-PIP transition",
+ startValue, animator.getStartValue());
+ assertEquals("Expect end value is not overridden for in-PIP transition",
+ endValue, animator.getEndValue());
+
+ animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue,
+ endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0,
+ true /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is not overridden for leave-PIP transition",
+ baseValue, animator.getBaseValue());
+ assertEquals("Expect start value is not overridden for leave-PIP transition",
+ startValue, animator.getStartValue());
+ assertEquals("Expect end value is not overridden for leave-PIP transition",
+ endValue, animator.getEndValue());
+ }
+
+ @Test
+ public void pipTransitionAnimator_letterboxed_animateTaskBounds() {
+ final Rect baseValue = new Rect(0, 0, 100, 100);
+ final Rect startValue = new Rect(0, 0, 100, 100);
+ final Rect endValue = new Rect(100, 100, 200, 200);
+ mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100);
+ doReturn(true).when(mTaskInfo.appCompatTaskInfo).isTopActivityLetterboxed();
+ PipAnimationController.PipTransitionAnimator> animator = mPipAnimationController
+ .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is not overridden for in-PIP transition",
+ baseValue, animator.getBaseValue());
+ assertEquals("Expect start value is not overridden for in-PIP transition",
+ startValue, animator.getStartValue());
+ assertEquals("Expect end value is not overridden for in-PIP transition",
+ endValue, animator.getEndValue());
+
+ animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue,
+ endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is not overridden for leave-PIP transition",
+ baseValue, animator.getBaseValue());
+ assertEquals("Expect start value is not overridden for leave-PIP transition",
+ startValue, animator.getStartValue());
+ assertEquals("Expect end value is not overridden for leave-PIP transition",
+ endValue, animator.getEndValue());
+ }
+
+ @Test
+ public void pipTransitionAnimator_sizeCompat_animateTaskBounds() {
+ final Rect baseValue = new Rect(0, 0, 100, 100);
+ final Rect startValue = new Rect(0, 0, 100, 100);
+ final Rect endValue = new Rect(100, 100, 200, 200);
+ mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100);
+ doReturn(true).when(mTaskInfo.appCompatTaskInfo).isTopActivityInSizeCompat();
+ PipAnimationController.PipTransitionAnimator> animator = mPipAnimationController
+ .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
+ TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is not overridden for in-PIP transition",
+ baseValue, animator.getBaseValue());
+ assertEquals("Expect start value is not overridden for in-PIP transition",
+ startValue, animator.getStartValue());
+ assertEquals("Expect end value is not overridden for in-PIP transition",
+ endValue, animator.getEndValue());
+
+ animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue,
+ endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0,
+ false /* alwaysAnimateTaskBounds */);
+
+ assertEquals("Expect base value is not overridden for leave-PIP transition",
+ baseValue, animator.getBaseValue());
+ assertEquals("Expect start value is not overridden for leave-PIP transition",
+ startValue, animator.getStartValue());
+ assertEquals("Expect end value is not overridden for leave-PIP transition",
+ endValue, animator.getEndValue());
+ }
}
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index e74c804d4f..7c3345fe55 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -24,6 +24,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -36,6 +37,7 @@ import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Rational;
@@ -46,12 +48,14 @@ import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.Flags;
import com.android.wm.shell.MockSurfaceControlHelper;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -61,8 +65,10 @@ import com.android.wm.shell.common.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -79,8 +85,10 @@ import java.util.Optional;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
+@DisableFlags(Flags.FLAG_ENABLE_PIP2)
public class PipTaskOrganizerTest extends ShellTestCase {
private PipTaskOrganizer mPipTaskOrganizer;
+ private ShellInit mShellInit;
@Mock private DisplayController mMockDisplayController;
@Mock private SyncTransactionQueue mMockSyncTransactionQueue;
@@ -90,9 +98,12 @@ public class PipTaskOrganizerTest extends ShellTestCase {
@Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
@Mock private PipUiEventLogger mMockPipUiEventLogger;
@Mock private Optional mMockOptionalSplitScreen;
+ @Mock private Optional mMockOptionalDesktopUserRepositories;
+ @Mock private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
@Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
@Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder;
- private TestShellExecutor mMainExecutor;
+ @Mock private ShellExecutor mMockExecutor;
+ @Mock private DisplayController mDisplayController;
private PipBoundsState mPipBoundsState;
private PipTransitionState mPipTransitionState;
private PipBoundsAlgorithm mPipBoundsAlgorithm;
@@ -105,24 +116,34 @@ public class PipTaskOrganizerTest extends ShellTestCase {
@Before
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
+ doAnswer(invocation -> {
+ ((Runnable) invocation.getArgument(0)).run();
+ return null;
+ }).when(mMockExecutor).execute(any());
mComponent1 = new ComponentName(mContext, "component1");
mComponent2 = new ComponentName(mContext, "component2");
- mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mShellInit = new ShellInit(mMockExecutor);
+ mPipDisplayLayoutState = new PipDisplayLayoutState(mContext, mDisplayController,
+ mShellInit);
+ // Directly call onInit instead of using ShellInit
+ mPipDisplayLayoutState.onInit();
mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState);
mPipTransitionState = new PipTransitionState();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
mPipDisplayLayoutState, mSizeSpecSource);
- mMainExecutor = new TestShellExecutor();
- mPipTaskOrganizer = new PipTaskOrganizer(mContext, mMockSyncTransactionQueue,
+ mPipTaskOrganizer = new PipTaskOrganizer(mContext, mShellInit, mMockSyncTransactionQueue,
mPipTransitionState, mPipBoundsState, mPipDisplayLayoutState,
mPipBoundsAlgorithm, mMockPhonePipMenuController, mMockPipAnimationController,
mMockPipSurfaceTransactionHelper, mMockPipTransitionController,
mMockPipParamsChangedForwarder, mMockOptionalSplitScreen,
- Optional.empty() /* pipPerfHintControllerOptional */, mMockDisplayController,
- mMockPipUiEventLogger, mMockShellTaskOrganizer, mMainExecutor);
- mMainExecutor.flushAll();
+ Optional.empty() /* pipPerfHintControllerOptional */,
+ mMockOptionalDesktopUserRepositories, mRootTaskDisplayAreaOrganizer,
+ mMockDisplayController, mMockPipUiEventLogger, mMockShellTaskOrganizer,
+ mMockExecutor);
+ // Directly init mPipTaskOrganizer instead of using ShellInit
+ mPipTaskOrganizer.onInit();
preparePipTaskOrg();
preparePipSurfaceTransactionHelper();
}
@@ -155,10 +176,12 @@ public class PipTaskOrganizerTest extends ShellTestCase {
@Test
public void startSwipePipToHome_updatesOverrideMinSize() {
- final Size minSize = new Size(400, 320);
+ final Rational aspectRatio = new Rational(2, 1);
+ final Size defaultSize = mSizeSpecSource.getDefaultSize(aspectRatio.floatValue());
+ final Size minSize = new Size(defaultSize.getWidth() / 2, defaultSize.getHeight() / 2);
mPipTaskOrganizer.startSwipePipToHome(mComponent1, createActivityInfo(minSize),
- createPipParams(null));
+ createPipParams(aspectRatio));
assertEquals(minSize, mPipBoundsState.getOverrideMinSize());
}
@@ -183,10 +206,12 @@ public class PipTaskOrganizerTest extends ShellTestCase {
@Test
public void onTaskAppeared_updatesOverrideMinSize() {
- final Size minSize = new Size(400, 320);
+ final Rational aspectRatio = new Rational(2, 1);
+ final Size defaultSize = mSizeSpecSource.getDefaultSize(aspectRatio.floatValue());
+ final Size minSize = new Size(defaultSize.getWidth() / 2, defaultSize.getHeight() / 2);
mPipTaskOrganizer.onTaskAppeared(
- createTaskInfo(mComponent1, createPipParams(null), minSize),
+ createTaskInfo(mComponent1, createPipParams(aspectRatio), minSize),
mock(SurfaceControl.class));
assertEquals(minSize, mPipBoundsState.getOverrideMinSize());
@@ -239,13 +264,15 @@ public class PipTaskOrganizerTest extends ShellTestCase {
@Test
public void onTaskInfoChanged_inPip_updatesOverrideMinSize() {
+ final Rational aspectRatio = new Rational(2, 1);
+ final Size defaultSize = mSizeSpecSource.getDefaultSize(aspectRatio.floatValue());
+ final Size minSize = new Size(defaultSize.getWidth() / 2, defaultSize.getHeight() / 2);
mPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1,
- createPipParams(null)), mock(SurfaceControl.class));
+ createPipParams(aspectRatio)), mock(SurfaceControl.class));
sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
- final Size minSize = new Size(400, 320);
mPipTaskOrganizer.onTaskInfoChanged(createTaskInfo(mComponent2,
- createPipParams(null), minSize));
+ createPipParams(aspectRatio), minSize));
assertEquals(minSize, mPipBoundsState.getOverrideMinSize());
}
@@ -272,7 +299,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
DisplayLayout layout = new DisplayLayout(info,
mContext.getResources(), true, true);
mPipDisplayLayoutState.setDisplayLayout(layout);
- doReturn(PipAnimationController.ANIM_TYPE_ALPHA).when(mMockPipAnimationController)
+ doReturn(PipTransitionController.ANIM_TYPE_ALPHA).when(mMockPipAnimationController)
.takeOneShotEnterAnimationType();
mPipTaskOrganizer.setSurfaceControlTransactionFactory(
MockSurfaceControlHelper::createMockSurfaceControlTransaction);
@@ -280,7 +307,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
private void preparePipSurfaceTransactionHelper() {
doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper)
- .crop(any(), any(), any());
+ .cropAndPosition(any(), any(), any());
doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper)
.resetScale(any(), any(), any());
doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper)
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index d38fc6cb64..9a84bf75f0 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -34,19 +34,22 @@ import static org.mockito.Mockito.when;
import static java.lang.Integer.MAX_VALUE;
-import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.Handler;
import android.os.RemoteException;
+import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.Flags;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayController;
@@ -55,6 +58,7 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.pip.IPip;
import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -71,7 +75,6 @@ import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.sysui.ShellSharedConstants;
import org.junit.Before;
import org.junit.Test;
@@ -88,6 +91,7 @@ import java.util.Set;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
+@DisableFlags(Flags.FLAG_ENABLE_PIP2)
public class PipControllerTest extends ShellTestCase {
private PipController mPipController;
private ShellInit mShellInit;
@@ -116,6 +120,8 @@ public class PipControllerTest extends ShellTestCase {
@Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder;
@Mock private DisplayInsetsController mMockDisplayInsetsController;
@Mock private TabletopModeController mMockTabletopModeController;
+ @Mock private Handler mMockHandler;
+ @Mock private UserManager mMockUserManager;
@Mock private DisplayLayout mMockDisplayLayout1;
@Mock private DisplayLayout mMockDisplayLayout2;
@@ -127,9 +133,9 @@ public class PipControllerTest extends ShellTestCase {
((Runnable) invocation.getArgument(0)).run();
return null;
}).when(mMockExecutor).execute(any());
- mShellInit = spy(new ShellInit(mMockExecutor));
+ mShellInit = new ShellInit(mMockExecutor);
mShellController = spy(new ShellController(mContext, mShellInit, mMockShellCommandHandler,
- mMockDisplayInsetsController, mMockExecutor));
+ mMockDisplayInsetsController, mMockUserManager, mMockExecutor));
mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
mShellController, mMockDisplayController, mMockPipAnimationController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
@@ -139,15 +145,11 @@ public class PipControllerTest extends ShellTestCase {
mMockPipTransitionController, mMockWindowManagerShellWrapper,
mMockTaskStackListener, mMockPipParamsChangedForwarder,
mMockDisplayInsetsController, mMockTabletopModeController,
- mMockOneHandedController, mMockExecutor);
- mShellInit.init();
+ mMockOneHandedController, mMockExecutor, mMockHandler);
when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper);
- }
-
- @Test
- public void instantiatePipController_addInitCallback() {
- verify(mShellInit, times(1)).addInitCallback(any(), eq(mPipController));
+ // Directly init mPipController instead of using ShellInit
+ mPipController.onInit();
}
@Test
@@ -168,7 +170,7 @@ public class PipControllerTest extends ShellTestCase {
@Test
public void instantiatePipController_registerExternalInterface() {
verify(mShellController, times(1)).addExternalInterface(
- eq(ShellSharedConstants.KEY_EXTRA_SHELL_PIP), any(), eq(mPipController));
+ eq(IPip.DESCRIPTOR), any(), eq(mPipController));
}
@Test
@@ -183,7 +185,7 @@ public class PipControllerTest extends ShellTestCase {
@Test
public void instantiatePipController_registersPipTransitionCallback() {
- verify(mMockPipTransitionController).registerPipTransitionCallback(any());
+ verify(mMockPipTransitionController).registerPipTransitionCallback(any(), any());
}
@Test
@@ -231,28 +233,7 @@ public class PipControllerTest extends ShellTestCase {
mMockPipTransitionController, mMockWindowManagerShellWrapper,
mMockTaskStackListener, mMockPipParamsChangedForwarder,
mMockDisplayInsetsController, mMockTabletopModeController,
- mMockOneHandedController, mMockExecutor));
- }
-
- @Test
- public void onActivityHidden_isLastPipComponentName_clearLastPipComponent() {
- final ComponentName component1 = new ComponentName(mContext, "component1");
- when(mMockPipBoundsState.getLastPipComponentName()).thenReturn(component1);
-
- mPipController.mPinnedTaskListener.onActivityHidden(component1);
-
- verify(mMockPipBoundsState).setLastPipComponentName(null);
- }
-
- @Test
- public void onActivityHidden_isNotLastPipComponentName_lastPipComponentNotCleared() {
- final ComponentName component1 = new ComponentName(mContext, "component1");
- final ComponentName component2 = new ComponentName(mContext, "component2");
- when(mMockPipBoundsState.getLastPipComponentName()).thenReturn(component1);
-
- mPipController.mPinnedTaskListener.onActivityHidden(component2);
-
- verify(mMockPipBoundsState, never()).setLastPipComponentName(null);
+ mMockOneHandedController, mMockExecutor, mMockHandler));
}
@Test
@@ -278,7 +259,7 @@ public class PipControllerTest extends ShellTestCase {
when(mMockPipDisplayLayoutState.getDisplayLayout()).thenReturn(mMockDisplayLayout1);
when(mMockDisplayController.getDisplayLayout(displayId)).thenReturn(mMockDisplayLayout2);
- when(mMockPipTaskOrganizer.isInPip()).thenReturn(true);
+ when(mMockPipTransitionState.hasEnteredPip()).thenReturn(true);
mPipController.mDisplaysChangedListener.onDisplayConfigurationChanged(
displayId, new Configuration());
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index ace09a82d7..4ddbe76775 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -36,6 +36,7 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
@@ -48,6 +49,7 @@ import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -89,6 +91,12 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
@Mock
private ShellExecutor mMainExecutor;
+ @Mock
+ private DisplayController mDisplayController;
+
+ @Mock
+ private ShellInit mShellInit;
+
private PipResizeGestureHandler mPipResizeGestureHandler;
private PipBoundsState mPipBoundsState;
@@ -105,7 +113,10 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
final TestableResources res = mContext.getOrCreateTestableResources();
res.addOverride(R.bool.config_pipEnablePinchResize, true);
- mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipDisplayLayoutState = new PipDisplayLayoutState(mContext, mDisplayController,
+ mShellInit);
+ // Directly call onInit instead of using ShellInit
+ mPipDisplayLayoutState.onInit();
mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState);
final PipSnapAlgorithm pipSnapAlgorithm = new PipSnapAlgorithm();
@@ -114,8 +125,8 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext,
mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipDisplayLayoutState,
mSizeSpecSource);
- final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
- mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
+ final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mMainExecutor,
+ mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator,
Optional.empty() /* pipPerfHintControllerOptional */);
@@ -124,7 +135,7 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
mPipResizeGestureHandler = new PipResizeGestureHandler(mContext, pipBoundsAlgorithm,
mPipBoundsState, motionHelper, mPipTouchState, mPipTaskOrganizer,
mPipDismissTargetHandler,
- () -> {}, mPipUiEventLogger, mPhonePipMenuController,
+ (Rect bounds) -> new Rect(), () -> {}, mPipUiEventLogger, mPhonePipMenuController,
mMainExecutor, null /* pipPerfHintController */) {
@Override
public void pilferPointers() {
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 92762fa685..bc2185e9a9 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -17,7 +17,6 @@
package com.android.wm.shell.pip.phone;
import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -29,6 +28,7 @@ import android.util.Size;
import androidx.test.filters.SmallTest;
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.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
@@ -63,7 +63,6 @@ import java.util.Optional;
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class PipTouchHandlerTest extends ShellTestCase {
-
private static final int INSET = 10;
private static final int PIP_LENGTH = 100;
@@ -90,6 +89,9 @@ public class PipTouchHandlerTest extends ShellTestCase {
@Mock
private ShellExecutor mMainExecutor;
+ @Mock
+ private DisplayController mDisplayController;
+
private PipBoundsState mPipBoundsState;
private PipBoundsAlgorithm mPipBoundsAlgorithm;
private PipSnapAlgorithm mPipSnapAlgorithm;
@@ -110,14 +112,17 @@ public class PipTouchHandlerTest extends ShellTestCase {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipDisplayLayoutState = new PipDisplayLayoutState(mContext, mDisplayController,
+ mShellInit);
+ // Directly call onInit instead of using ShellInit
+ mPipDisplayLayoutState.onInit();
mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState);
mPipSnapAlgorithm = new PipSnapAlgorithm();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
new PipKeepClearAlgorithmInterface() {}, mPipDisplayLayoutState, mSizeSpecSource);
- PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
- mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
+ PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mMainExecutor,
+ mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator,
Optional.empty() /* pipPerfHintControllerOptional */);
mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController,
@@ -149,11 +154,6 @@ public class PipTouchHandlerTest extends ShellTestCase {
mImeHeight = 100;
}
- @Test
- public void instantiate_addInitCallback() {
- verify(mShellInit, times(1)).addInitCallback(any(), any());
- }
-
@Test
public void updateMovementBounds_minMaxBounds() {
final int shorterLength = Math.min(mPipBoundsState.getDisplayBounds().width(),
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS b/wmshell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS
index 736d4cff6c..a7d1890a02 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS
@@ -1,3 +1,2 @@
# WM shell sub-module TV pip owners
-galinap@google.com
-bronger@google.com
\ No newline at end of file
+bronger@google.com
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
index 974539f23b..ec7a432ed2 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
@@ -26,10 +26,12 @@ import static org.junit.Assert.assertEquals;
import android.view.Gravity;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.pip.LegacySizeSpecSource;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.SizeSpecSource;
+import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
@@ -45,6 +47,10 @@ public class TvPipGravityTest extends ShellTestCase {
@Mock
private PipSnapAlgorithm mMockPipSnapAlgorithm;
+ @Mock
+ private DisplayController mDisplayController;
+ @Mock
+ private ShellInit mShellInit;
private TvPipBoundsState mTvPipBoundsState;
private TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
@@ -56,7 +62,10 @@ public class TvPipGravityTest extends ShellTestCase {
assumeTelevision();
MockitoAnnotations.initMocks(this);
- mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipDisplayLayoutState = new PipDisplayLayoutState(mContext, mDisplayController,
+ mShellInit);
+ // Directly call onInit instead of using ShellInit
+ mPipDisplayLayoutState.onInit();
mSizeSpecSource = new LegacySizeSpecSource(mContext, mPipDisplayLayoutState);
mTvPipBoundsState = new TvPipBoundsState(mContext, mSizeSpecSource,
mPipDisplayLayoutState);
@@ -241,16 +250,16 @@ public class TvPipGravityTest extends ShellTestCase {
@Test
public void updateGravity_move_expanded_valid() {
- mTvPipBoundsState.setTvPipExpanded(true);
-
// Vertical expanded PiP.
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
+ mTvPipBoundsState.setTvPipExpanded(true);
mTvPipBoundsState.setTvPipGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT);
moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.CENTER_VERTICAL | Gravity.LEFT, true);
moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.CENTER_VERTICAL | Gravity.RIGHT, true);
// Horizontal expanded PiP.
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+ mTvPipBoundsState.setTvPipExpanded(true);
mTvPipBoundsState.setTvPipGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.TOP | Gravity.CENTER_HORIZONTAL, true);
moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, true);
@@ -281,10 +290,9 @@ public class TvPipGravityTest extends ShellTestCase {
@Test
public void updateGravity_move_expanded_invalid() {
- mTvPipBoundsState.setTvPipExpanded(true);
-
// Vertical expanded PiP.
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true);
+ mTvPipBoundsState.setTvPipExpanded(true);
mTvPipBoundsState.setTvPipGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT);
moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.CENTER_VERTICAL | Gravity.RIGHT, false);
moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.CENTER_VERTICAL | Gravity.RIGHT, false);
@@ -297,6 +305,7 @@ public class TvPipGravityTest extends ShellTestCase {
// Horizontal expanded PiP.
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true);
+ mTvPipBoundsState.setTvPipExpanded(true);
mTvPipBoundsState.setTvPipGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, false);
moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, false);
diff --git a/wmshell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/wmshell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index e291c0e1a1..836238a064 100644
--- a/wmshell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/wmshell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -20,9 +20,13 @@ import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
+import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE;
+import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_DESK;
+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.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -43,46 +47,49 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static java.lang.Integer.MAX_VALUE;
+import static java.util.stream.Collectors.joining;
-import android.app.ActivityManager;
+import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
+import android.app.KeyguardManager;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.UserManager;
import android.platform.test.annotations.DisableFlags;
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;
import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
+import com.android.wm.shell.desktopmode.DesktopWallpaperActivity;
+import com.android.wm.shell.shared.GroupedTaskInfo;
+import com.android.wm.shell.shared.desktopmode.FakeDesktopState;
+import com.android.wm.shell.shared.split.SplitBounds;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.sysui.ShellSharedConstants;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
-import com.android.wm.shell.util.SplitBounds;
import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.quality.Strictness;
+import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Arrays;
@@ -98,6 +105,7 @@ import java.util.function.Consumer;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class RecentTasksControllerTest extends ShellTestCase {
+ private static final String SYSTEM_UI_PACKAGE_NAME = "com.android.systemui";
@Mock
private Context mContext;
@@ -106,8 +114,6 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
- private DesktopModeTaskRepository mDesktopModeTaskRepository;
- @Mock
private ActivityTaskManager mActivityTaskManager;
@Mock
private DisplayInsetsController mDisplayInsetsController;
@@ -115,9 +121,13 @@ public class RecentTasksControllerTest extends ShellTestCase {
private IRecentTasksListener mRecentTasksListener;
@Mock
private TaskStackTransitionObserver mTaskStackTransitionObserver;
-
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Mock
+ private DesktopUserRepositories mDesktopUserRepositories;
+ @Mock
+ private DesktopRepository mDesktopRepository;
+ @Mock
+ private UserManager mUserManager;
+ private FakeDesktopState mDesktopState;
private ShellTaskOrganizer mShellTaskOrganizer;
private RecentTasksController mRecentTasksController;
@@ -125,24 +135,28 @@ public class RecentTasksControllerTest extends ShellTestCase {
private ShellInit mShellInit;
private ShellController mShellController;
private TestShellExecutor mMainExecutor;
- private static StaticMockitoSession sMockitoSession;
+ private AutoCloseable mMocksInit = null;
@Before
public void setUp() {
- sMockitoSession = mockitoSession().initMocks(this).strictness(Strictness.LENIENT)
- .mockStatic(DesktopModeStatus.class).startMocking();
- ExtendedMockito.doReturn(true)
- .when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
+ mDesktopState = new FakeDesktopState();
+ mDesktopState.setCanEnterDesktopMode(true);
+
+ mMocksInit = MockitoAnnotations.openMocks(this);
mMainExecutor = new TestShellExecutor();
+ when(mDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository);
+ when(mDesktopUserRepositories.getProfile(anyInt())).thenReturn(mDesktopRepository);
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
+ when(mContext.getSystemService(KeyguardManager.class))
+ .thenReturn(mock(KeyguardManager.class));
mShellInit = spy(new ShellInit(mMainExecutor));
mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
- mDisplayInsetsController, mMainExecutor));
+ mDisplayInsetsController, mUserManager, mMainExecutor));
mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
- Optional.of(mDesktopModeTaskRepository), mTaskStackTransitionObserver,
- mMainExecutor);
+ Optional.of(mDesktopUserRepositories), mTaskStackTransitionObserver,
+ mMainExecutor, mDesktopState);
mRecentTasksController = spy(mRecentTasksControllerReal);
mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
@@ -151,8 +165,11 @@ public class RecentTasksControllerTest extends ShellTestCase {
}
@After
- public void tearDown() {
- sMockitoSession.finishMocking();
+ public void tearDown() throws Exception {
+ if (mMocksInit != null) {
+ mMocksInit.close();
+ mMocksInit = null;
+ }
}
@Test
@@ -169,7 +186,13 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void instantiateController_addExternalInterface() {
verify(mShellController, times(1)).addExternalInterface(
- eq(ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS), any(), any());
+ eq(IRecentTasks.DESCRIPTOR), any(), any());
+ }
+
+ @Test
+ public void instantiateController_initializesRepository() {
+ verify(mDesktopUserRepositories, times(1)).getCurrent();
+ verify(mDesktopRepository, times(1)).addActiveTaskListener(any());
}
@Test
@@ -188,8 +211,9 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testAddRemoveSplitNotifyChange() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+ reset(mRecentTasksController);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
setRawList(t1, t2);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, mock(SplitBounds.class));
@@ -202,120 +226,136 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testAddSameSplitBoundsInfoSkipNotifyChange() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+ reset(mRecentTasksController);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
setRawList(t1, t2);
// Verify only one update if the split info is the same
SplitBounds bounds1 = new SplitBounds(new Rect(0, 0, 50, 50),
- new Rect(50, 50, 100, 100), t1.taskId, t2.taskId, SNAP_TO_50_50);
+ new Rect(50, 50, 100, 100), t1.taskId, t2.taskId, SNAP_TO_2_50_50);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds1);
SplitBounds bounds2 = new SplitBounds(new Rect(0, 0, 50, 50),
- new Rect(50, 50, 100, 100), t1.taskId, t2.taskId, SNAP_TO_50_50);
+ new Rect(50, 50, 100, 100), t1.taskId, t2.taskId, SNAP_TO_2_50_50);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds2);
verify(mRecentTasksController, times(1)).notifyRecentTasksChanged();
}
@Test
public void testGetRecentTasks() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
setRawList(t1, t2, t3);
- ArrayList recentTasks = mRecentTasksController.getRecentTasks(
- MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
- assertGroupedTasksListEquals(recentTasks,
- t1.taskId, -1,
- t2.taskId, -1,
- t3.taskId, -1);
+ ArrayList recentTasks =
+ mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+ assertGroupedTasksListEquals(recentTasks, List.of(
+ List.of(t1.taskId),
+ List.of(t2.taskId),
+ List.of(t3.taskId)));
+ }
+
+ @Test
+ public void testGetRecentTasks_removesDesktopWallpaperActivity() {
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo desktopWallpaperTaskInfo = makeDesktopWallpaperTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
+ setRawList(t1, desktopWallpaperTaskInfo, t3);
+
+ ArrayList recentTasks =
+ mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+ assertGroupedTasksListEquals(recentTasks, List.of(
+ List.of(t1.taskId),
+ List.of(t3.taskId)));
}
@Test
public void testGetRecentTasks_withPairs() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
- ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
- ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
- ActivityManager.RecentTaskInfo t6 = makeTaskInfo(6);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t4 = makeTaskInfo(4);
+ RecentTaskInfo t5 = makeTaskInfo(5);
+ RecentTaskInfo t6 = makeTaskInfo(6);
setRawList(t1, t2, t3, t4, t5, t6);
// Mark a couple pairs [t2, t4], [t3, t5]
SplitBounds pair1Bounds =
- new SplitBounds(new Rect(), new Rect(), 2, 4, SNAP_TO_50_50);
+ new SplitBounds(new Rect(), new Rect(), 2, 4, SNAP_TO_2_50_50);
SplitBounds pair2Bounds =
- new SplitBounds(new Rect(), new Rect(), 3, 5, SNAP_TO_50_50);
+ new SplitBounds(new Rect(), new Rect(), 3, 5, SNAP_TO_2_50_50);
mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds);
mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);
- ArrayList recentTasks = mRecentTasksController.getRecentTasks(
- MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
- assertGroupedTasksListEquals(recentTasks,
- t1.taskId, -1,
- t2.taskId, t4.taskId,
- t3.taskId, t5.taskId,
- t6.taskId, -1);
+ ArrayList recentTasks =
+ mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+ assertGroupedTasksListEquals(recentTasks, List.of(
+ List.of(t1.taskId),
+ List.of(t2.taskId, t4.taskId),
+ List.of(t3.taskId, t5.taskId),
+ List.of(t6.taskId)));
}
@Test
public void testGetRecentTasks_ReturnsRecentTasksAsynchronously() {
@SuppressWarnings("unchecked")
- final List[] recentTasks = new List[1];
- Consumer> consumer = argument -> recentTasks[0] = argument;
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
- ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
- ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
- ActivityManager.RecentTaskInfo t6 = makeTaskInfo(6);
+ final List[] recentTasks = new List[1];
+ Consumer> consumer = argument -> recentTasks[0] = argument;
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t4 = makeTaskInfo(4);
+ RecentTaskInfo t5 = makeTaskInfo(5);
+ RecentTaskInfo t6 = makeTaskInfo(6);
setRawList(t1, t2, t3, t4, t5, t6);
// Mark a couple pairs [t2, t4], [t3, t5]
SplitBounds pair1Bounds =
- new SplitBounds(new Rect(), new Rect(), 2, 4, SNAP_TO_50_50);
+ new SplitBounds(new Rect(), new Rect(), 2, 4, SNAP_TO_2_50_50);
SplitBounds pair2Bounds =
- new SplitBounds(new Rect(), new Rect(), 3, 5, SNAP_TO_50_50);
+ new SplitBounds(new Rect(), new Rect(), 3, 5, SNAP_TO_2_50_50);
mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds);
mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);
mRecentTasksController.asRecentTasks()
- .getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0, Runnable::run, consumer);
+ .getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0, Runnable::run,
+ consumer);
mMainExecutor.flushAll();
- assertGroupedTasksListEquals(recentTasks[0],
- t1.taskId, -1,
- t2.taskId, t4.taskId,
- t3.taskId, t5.taskId,
- t6.taskId, -1);
+ assertGroupedTasksListEquals(recentTasks[0], List.of(
+ List.of(t1.taskId),
+ List.of(t2.taskId, t4.taskId),
+ List.of(t3.taskId, t5.taskId),
+ List.of(t6.taskId)));
}
@Test
public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_groupFreeformTasks() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
- ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t4 = makeTaskInfo(4);
setRawList(t1, t2, t3, t4);
- when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
- when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
+ when(mDesktopUserRepositories.getCurrent().isActiveTask(1)).thenReturn(true);
+ when(mDesktopUserRepositories.getCurrent().isActiveTask(3)).thenReturn(true);
- ArrayList recentTasks = mRecentTasksController.getRecentTasks(
- MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+ ArrayList recentTasks =
+ mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
// 2 freeform tasks should be grouped into one, 3 total recents entries
assertEquals(3, recentTasks.size());
- GroupedRecentTaskInfo freeformGroup = recentTasks.get(0);
- GroupedRecentTaskInfo singleGroup1 = recentTasks.get(1);
- GroupedRecentTaskInfo singleGroup2 = recentTasks.get(2);
+ GroupedTaskInfo singleGroup1 = recentTasks.get(0);
+ GroupedTaskInfo singleGroup2 = recentTasks.get(1);
+ GroupedTaskInfo freeformGroup = recentTasks.get(2);
// Check that groups have expected types
- assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup1.getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup2.getType());
+ assertTrue(freeformGroup.isBaseType(TYPE_DESK));
+ assertTrue(singleGroup1.isBaseType(TYPE_FULLSCREEN));
+ assertTrue(singleGroup2.isBaseType(TYPE_FULLSCREEN));
// Check freeform group entries
assertEquals(t1, freeformGroup.getTaskInfoList().get(0));
@@ -328,33 +368,33 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_freeformTaskOrder() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
- ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
- ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t4 = makeTaskInfo(4);
+ RecentTaskInfo t5 = makeTaskInfo(5);
setRawList(t1, t2, t3, t4, t5);
SplitBounds pair1Bounds =
- new SplitBounds(new Rect(), new Rect(), 1, 2, SNAP_TO_50_50);
+ new SplitBounds(new Rect(), new Rect(), 1, 2, SNAP_TO_2_50_50);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, pair1Bounds);
- when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
- when(mDesktopModeTaskRepository.isActiveTask(5)).thenReturn(true);
+ when(mDesktopUserRepositories.getCurrent().isActiveTask(3)).thenReturn(true);
+ when(mDesktopUserRepositories.getCurrent().isActiveTask(5)).thenReturn(true);
- ArrayList recentTasks = mRecentTasksController.getRecentTasks(
- MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+ ArrayList recentTasks =
+ mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
// 2 split screen tasks grouped, 2 freeform tasks grouped, 3 total recents entries
assertEquals(3, recentTasks.size());
- GroupedRecentTaskInfo splitGroup = recentTasks.get(0);
- GroupedRecentTaskInfo freeformGroup = recentTasks.get(1);
- GroupedRecentTaskInfo singleGroup = recentTasks.get(2);
+ GroupedTaskInfo splitGroup = recentTasks.get(0);
+ GroupedTaskInfo singleGroup = recentTasks.get(1);
+ GroupedTaskInfo freeformGroup = recentTasks.get(2);
// Check that groups have expected types
- assertEquals(GroupedRecentTaskInfo.TYPE_SPLIT, splitGroup.getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup.getType());
+ assertTrue(splitGroup.isBaseType(TYPE_SPLIT));
+ assertTrue(freeformGroup.isBaseType(TYPE_DESK));
+ assertTrue(singleGroup.isBaseType(TYPE_FULLSCREEN));
// Check freeform group entries
assertEquals(t3, freeformGroup.getTaskInfoList().get(0));
@@ -370,27 +410,26 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testGetRecentTasks_hasActiveDesktopTasks_proto2Disabled_doNotGroupFreeformTasks() {
- ExtendedMockito.doReturn(false)
- .when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
+ mDesktopState.setCanEnterDesktopMode(false);
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
- ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t4 = makeTaskInfo(4);
setRawList(t1, t2, t3, t4);
- when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
- when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
+ when(mDesktopUserRepositories.getCurrent().isActiveTask(1)).thenReturn(true);
+ when(mDesktopUserRepositories.getCurrent().isActiveTask(3)).thenReturn(true);
- ArrayList recentTasks = mRecentTasksController.getRecentTasks(
- MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+ ArrayList recentTasks =
+ mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
// Expect no grouping of tasks
assertEquals(4, recentTasks.size());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(0).getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(1).getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(2).getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(3).getType());
+ assertTrue(recentTasks.get(0).isBaseType(TYPE_FULLSCREEN));
+ assertTrue(recentTasks.get(1).isBaseType(TYPE_FULLSCREEN));
+ assertTrue(recentTasks.get(2).isBaseType(TYPE_FULLSCREEN));
+ assertTrue(recentTasks.get(3).isBaseType(TYPE_FULLSCREEN));
assertEquals(t1, recentTasks.get(0).getTaskInfo1());
assertEquals(t2, recentTasks.get(1).getTaskInfo1());
@@ -399,60 +438,106 @@ public class RecentTasksControllerTest extends ShellTestCase {
}
@Test
- public void testGetRecentTasks_proto2Enabled_ignoresMinimizedFreeformTasks() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
- ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
- ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
+ public void testGetRecentTasks_proto2Enabled_includesMinimizedFreeformTasks() {
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t4 = makeTaskInfo(4);
+ RecentTaskInfo t5 = makeTaskInfo(5);
setRawList(t1, t2, t3, t4, t5);
- when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
- when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
- when(mDesktopModeTaskRepository.isActiveTask(5)).thenReturn(true);
- when(mDesktopModeTaskRepository.isMinimizedTask(3)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(2)).thenReturn(false);
+ when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
+ when(mDesktopRepository.isActiveTask(4)).thenReturn(false);
+ when(mDesktopRepository.isActiveTask(5)).thenReturn(true);
+ when(mDesktopRepository.isMinimizedTask(3)).thenReturn(true);
- ArrayList recentTasks = mRecentTasksController.getRecentTasks(
- MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+ ArrayList recentTasks =
+ mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
- // 2 freeform tasks should be grouped into one, 1 task should be skipped, 3 total recents
- // entries
+ // 3 freeform tasks should be grouped into one, 2 single tasks, 3 total recents entries
assertEquals(3, recentTasks.size());
- GroupedRecentTaskInfo freeformGroup = recentTasks.get(0);
- GroupedRecentTaskInfo singleGroup1 = recentTasks.get(1);
- GroupedRecentTaskInfo singleGroup2 = recentTasks.get(2);
+ GroupedTaskInfo singleGroup1 = recentTasks.get(0);
+ GroupedTaskInfo singleGroup2 = recentTasks.get(1);
+ GroupedTaskInfo freeformGroup = recentTasks.get(2);
// Check that groups have expected types
- assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup1.getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup2.getType());
+ assertTrue(freeformGroup.isBaseType(TYPE_DESK));
+ assertTrue(singleGroup1.isBaseType(TYPE_FULLSCREEN));
+ assertTrue(singleGroup2.isBaseType(TYPE_FULLSCREEN));
// Check freeform group entries
- assertEquals(2, freeformGroup.getTaskInfoList().size());
+ assertEquals(3, freeformGroup.getTaskInfoList().size());
assertEquals(t1, freeformGroup.getTaskInfoList().get(0));
- assertEquals(t5, freeformGroup.getTaskInfoList().get(1));
+ assertEquals(t3, freeformGroup.getTaskInfoList().get(1));
+ assertEquals(t5, freeformGroup.getTaskInfoList().get(2));
// Check single entries
assertEquals(t2, singleGroup1.getTaskInfo1());
assertEquals(t4, singleGroup2.getTaskInfo1());
}
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+ public void testGetRecentTasks_hasDesktopTasks_persistenceEnabled_freeformTaskHaveBoundsSet() {
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+
+ t1.lastNonFullscreenBounds = new Rect(100, 200, 300, 400);
+ t2.lastNonFullscreenBounds = new Rect(150, 250, 350, 450);
+ setRawList(t1, t2);
+
+ when(mDesktopUserRepositories.getCurrent().isActiveTask(1)).thenReturn(true);
+ when(mDesktopUserRepositories.getCurrent().isActiveTask(2)).thenReturn(true);
+
+ ArrayList recentTasks =
+ mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+
+ assertEquals(1, recentTasks.size());
+ GroupedTaskInfo freeformGroup = recentTasks.get(0);
+
+ // Check bounds
+ assertEquals(t1.lastNonFullscreenBounds, freeformGroup.getTaskInfoList().get(
+ 0).configuration.windowConfiguration.getAppBounds());
+ assertEquals(t2.lastNonFullscreenBounds, freeformGroup.getTaskInfoList().get(
+ 1).configuration.windowConfiguration.getAppBounds());
+
+ // Check position in parent
+ assertEquals(new Point(t1.lastNonFullscreenBounds.left,
+ t1.lastNonFullscreenBounds.top),
+ freeformGroup.getTaskInfoList().get(0).positionInParent);
+ assertEquals(new Point(t2.lastNonFullscreenBounds.left,
+ t2.lastNonFullscreenBounds.top),
+ freeformGroup.getTaskInfoList().get(1).positionInParent);
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+ public void testGetRecentTasks_lastNonFullscreenBoundsNull_doesNotThrow() {
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ t1.lastNonFullscreenBounds = null;
+ setRawList(t1);
+ when(mDesktopUserRepositories.getCurrent().isActiveTask(1)).thenReturn(true);
+
+ mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+ }
@Test
public void testRemovedTaskRemovesSplit() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
setRawList(t1, t2, t3);
// Add a pair
SplitBounds pair1Bounds =
- new SplitBounds(new Rect(), new Rect(), 2, 3, SNAP_TO_50_50);
+ new SplitBounds(new Rect(), new Rect(), 2, 3, SNAP_TO_2_50_50);
mRecentTasksController.addSplitPair(t2.taskId, t3.taskId, pair1Bounds);
reset(mRecentTasksController);
// Remove one of the tasks and ensure the pair is removed
SurfaceControl mockLeash = mock(SurfaceControl.class);
- ActivityManager.RunningTaskInfo rt2 = makeRunningTaskInfo(2);
+ RunningTaskInfo rt2 = makeRunningTaskInfo(2);
mShellTaskOrganizer.onTaskAppeared(rt2, mockLeash);
mShellTaskOrganizer.onTaskVanished(rt2);
@@ -461,23 +546,25 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testTaskWindowingModeChangedNotifiesChange() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
+ reset(mRecentTasksController);
+ RecentTaskInfo t1 = makeTaskInfo(1);
setRawList(t1);
// Remove one of the tasks and ensure the pair is removed
SurfaceControl mockLeash = mock(SurfaceControl.class);
- ActivityManager.RunningTaskInfo rt2Fullscreen = makeRunningTaskInfo(2);
+ RunningTaskInfo rt2Fullscreen = makeRunningTaskInfo(2);
rt2Fullscreen.configuration.windowConfiguration.setWindowingMode(
WINDOWING_MODE_FULLSCREEN);
mShellTaskOrganizer.onTaskAppeared(rt2Fullscreen, mockLeash);
// Change the windowing mode and ensure the recent tasks change is notified
- ActivityManager.RunningTaskInfo rt2MultiWIndow = makeRunningTaskInfo(2);
+ RunningTaskInfo rt2MultiWIndow = makeRunningTaskInfo(2);
rt2MultiWIndow.configuration.windowConfiguration.setWindowingMode(
WINDOWING_MODE_MULTI_WINDOW);
mShellTaskOrganizer.onTaskInfoChanged(rt2MultiWIndow);
- verify(mRecentTasksController).notifyRecentTasksChanged();
+ // One for onTaskAppeared and one for onTaskInfoChanged
+ verify(mRecentTasksController, times(2)).notifyRecentTasksChanged();
}
@Test
@@ -486,7 +573,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
public void onTaskAdded_desktopModeRunningAppsEnabled_triggersOnRunningTaskAppeared()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskAdded(taskInfo);
@@ -499,7 +586,20 @@ public class RecentTasksControllerTest extends ShellTestCase {
public void onTaskAdded_desktopModeRunningAppsDisabled_doesNotTriggerOnRunningTaskAppeared()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+
+ mRecentTasksControllerReal.onTaskAdded(taskInfo);
+
+ verify(mRecentTasksListener, never()).onRunningTaskAppeared(any());
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS})
+ public void onTaskAdded_orDesktopWallpaperActivity_doesNotTriggerOnRunningTaskAppeared()
+ throws Exception {
+ RunningTaskInfo taskInfo = makeDesktopWallpaperActivityTaskInfo(/* taskId= */10);
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
mRecentTasksControllerReal.onTaskAdded(taskInfo);
@@ -512,13 +612,26 @@ public class RecentTasksControllerTest extends ShellTestCase {
public void taskWindowingModeChanged_desktopRunningAppsEnabled_triggersOnRunningTaskChanged()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskRunningInfoChanged(taskInfo);
verify(mRecentTasksListener).onRunningTaskChanged(taskInfo);
}
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS})
+ public void taskInfoChanged_forDesktopWallpaperActivity_doesNotTriggerOnRunningTaskChanged()
+ throws Exception {
+ RunningTaskInfo taskInfo = makeDesktopWallpaperActivityTaskInfo(/* taskId= */10);
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+
+ mRecentTasksControllerReal.onTaskRunningInfoChanged(taskInfo);
+
+ verify(mRecentTasksListener, never()).onRunningTaskChanged(any());
+ }
+
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS)
@@ -526,7 +639,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
taskWindowingModeChanged_desktopRunningAppsDisabled_doesNotTriggerOnRunningTaskChanged()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskRunningInfoChanged(taskInfo);
@@ -539,20 +652,34 @@ public class RecentTasksControllerTest extends ShellTestCase {
public void onTaskRemoved_desktopModeRunningAppsEnabled_triggersOnRunningTaskVanished()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskRemoved(taskInfo);
verify(mRecentTasksListener).onRunningTaskVanished(taskInfo);
}
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS})
+ public void onTaskRemoved_forDesktopWallpaperActivity_doesNotTriggerOnRunningTaskVanished()
+ throws Exception {
+ RunningTaskInfo taskInfo = makeDesktopWallpaperActivityTaskInfo(/* taskId= */10);
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+
+ mRecentTasksControllerReal.onTaskRemoved(taskInfo);
+
+ verify(mRecentTasksListener, never()).onRunningTaskVanished(any());
+ }
+
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS)
public void onTaskRemoved_desktopModeRunningAppsDisabled_doesNotTriggerOnRunningTaskVanished()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskRemoved(taskInfo);
@@ -561,14 +688,16 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
@EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @DisableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
public void onTaskMovedToFront_TaskStackObserverEnabled_triggersOnTaskMovedToFront()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskMovedToFrontThroughTransition(taskInfo);
- verify(mRecentTasksListener).onTaskMovedToFront(taskInfo);
+ GroupedTaskInfo runningTask = GroupedTaskInfo.forFullscreenTasks(taskInfo);
+ verify(mRecentTasksListener).onTaskMovedToFront(eq(runningTask));
}
@Test
@@ -576,13 +705,25 @@ public class RecentTasksControllerTest extends ShellTestCase {
public void onTaskMovedToFront_TaskStackObserverEnabled_doesNotTriggersOnTaskMovedToFront()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskMovedToFront(taskInfo);
verify(mRecentTasksListener, never()).onTaskMovedToFront(any());
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ public void onDesktopWallpaperActivityMovedToFront_doesNotTriggerOnTaskMovedToFront()
+ throws Exception {
+ RunningTaskInfo taskInfo = makeDesktopWallpaperActivityTaskInfo(/* taskId= */10);
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+
+ mRecentTasksControllerReal.onTaskMovedToFrontThroughTransition(taskInfo);
+
+ verify(mRecentTasksListener, never()).onTaskMovedToFront(any());
+ }
+
@Test
public void getNullSplitBoundsNonSplitTask() {
SplitBounds sb = mRecentTasksController.getSplitBoundsForTaskId(3);
@@ -614,71 +755,211 @@ public class RecentTasksControllerTest extends ShellTestCase {
assertEquals(splitBounds4, pair2Bounds);
}
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ public void shellTopTaskTracker_onTaskStackChanged_expectNoRecentsChanged() throws Exception {
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+ mRecentTasksControllerReal.onTaskStackChanged();
+ verify(mRecentTasksListener, never()).onRecentTasksChanged();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ public void shellTopTaskTracker_onTaskRemoved_expectNoRecentsChanged() throws Exception {
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ mRecentTasksControllerReal.onTaskRemoved(taskInfo);
+ verify(mRecentTasksListener, never()).onRecentTasksChanged();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ public void shellTopTaskTracker_onVisibleTasksChanged() throws Exception {
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ mRecentTasksControllerReal.onVisibleTasksChanged(List.of(taskInfo));
+ verify(mRecentTasksListener, never()).onVisibleTasksChanged(any());
+ }
+
+ @Test
+ public void generateList_emptyTaskList_expectNoGroupedTasks() throws Exception {
+ assertTrue(mRecentTasksControllerReal.generateList(List.of(), "test").isEmpty());
+ }
+
+ @Test
+ public void generateList_excludePipTask() throws Exception {
+ RunningTaskInfo task1 = makeRunningTaskInfo(1);
+ RunningTaskInfo pipTask = makeRunningTaskInfo(2);
+ pipTask.configuration.windowConfiguration.setWindowingMode(
+ WINDOWING_MODE_PINNED);
+
+ ArrayList groupedTasks = mRecentTasksControllerReal.generateList(
+ List.of(task1, pipTask),
+ "test");
+
+ assertGroupedTasksListEquals(groupedTasks, List.of(List.of(task1.taskId)));
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ public void generateList_fullscreen_noVisibleTasks_expectNoGrouping() throws Exception {
+ RunningTaskInfo task1 = makeRunningTaskInfo(1);
+ RunningTaskInfo task2 = makeRunningTaskInfo(2);
+ RunningTaskInfo task3 = makeRunningTaskInfo(3);
+ // Reset visible tasks list
+ mRecentTasksControllerReal.onVisibleTasksChanged(List.of());
+
+ // Generate a list with a number of fullscreen tasks
+ ArrayList groupedTasks = mRecentTasksControllerReal.generateList(
+ List.of(task1, task2, task3),
+ "test");
+
+ assertGroupedTasksListEquals(groupedTasks, List.of(
+ List.of(task1.taskId),
+ List.of(task2.taskId),
+ List.of(task3.taskId)));
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ public void generateList_fullscreen_singleVisibleTask_expectNoGrouping() throws Exception {
+ RunningTaskInfo task1 = makeRunningTaskInfo(1);
+ RunningTaskInfo task2 = makeRunningTaskInfo(2);
+ RunningTaskInfo task3 = makeRunningTaskInfo(3);
+
+ // Reset visible tasks list
+ mRecentTasksControllerReal.onVisibleTasksChanged(List.of(task1));
+
+ // Generate a list with a number of fullscreen tasks
+ ArrayList groupedTasks = mRecentTasksControllerReal.generateList(
+ List.of(task1, task2, task3),
+ "test");
+
+ assertGroupedTasksListEquals(groupedTasks, List.of(
+ List.of(task1.taskId),
+ List.of(task2.taskId),
+ List.of(task3.taskId)));
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ public void generateList_fullscreen_multipleVisibleTasks_expectGrouping() throws Exception {
+ RunningTaskInfo task1 = makeRunningTaskInfo(1);
+ RunningTaskInfo task2 = makeRunningTaskInfo(2);
+ RunningTaskInfo task3 = makeRunningTaskInfo(3);
+
+ // Reset visible tasks list
+ mRecentTasksControllerReal.onVisibleTasksChanged(List.of(task1, task2));
+
+ // Generate a list with a number of fullscreen tasks
+ ArrayList groupedTasks = mRecentTasksControllerReal.generateList(
+ List.of(task1, task2, task3),
+ "test");
+
+ assertGroupedTasksListEquals(groupedTasks, List.of(
+ List.of(task1.taskId),
+ List.of(task2.taskId),
+ List.of(task3.taskId)));
+ }
+
/**
* Helper to create a task with a given task id.
*/
- private ActivityManager.RecentTaskInfo makeTaskInfo(int taskId) {
- ActivityManager.RecentTaskInfo info = new ActivityManager.RecentTaskInfo();
+ private RecentTaskInfo makeTaskInfo(int taskId) {
+ RecentTaskInfo info = new RecentTaskInfo();
info.taskId = taskId;
+ info.realActivity = new ComponentName("testPackage", "testClass");
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName("com." + taskId, "Activity" + taskId));
+ info.baseIntent = intent;
+ info.lastNonFullscreenBounds = new Rect();
+ return info;
+ }
+
+ /**
+ * Helper to create a desktop wallpaper activity with a given task id.
+ */
+ private RecentTaskInfo makeDesktopWallpaperTaskInfo(int taskId) {
+ RecentTaskInfo info = makeTaskInfo(taskId);
+ info.baseIntent.setComponent(DesktopWallpaperActivity.getWallpaperActivityComponent());
return info;
}
/**
* Helper to create a running task with a given task id.
*/
- private ActivityManager.RunningTaskInfo makeRunningTaskInfo(int taskId) {
- ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
+ private RunningTaskInfo makeRunningTaskInfo(int taskId) {
+ return makeRunningTaskInfo(taskId, new ComponentName("com." + taskId, "Activity" + taskId));
+ }
+
+ private RunningTaskInfo makeRunningTaskInfo(int taskId, ComponentName intentComponent) {
+ RunningTaskInfo info = new RunningTaskInfo();
info.taskId = taskId;
info.realActivity = new ComponentName("testPackage", "testClass");
+ Intent intent = new Intent();
+ intent.setComponent(intentComponent);
+ info.baseIntent = intent;
+ info.lastNonFullscreenBounds = new Rect();
return info;
}
+ private RunningTaskInfo makeDesktopWallpaperActivityTaskInfo(int taskId) {
+ return makeRunningTaskInfo(taskId, new ComponentName(SYSTEM_UI_PACKAGE_NAME,
+ DesktopWallpaperActivity.class.getName()));
+ }
+
/**
* Helper to set the raw task list on the controller.
*/
- private ArrayList setRawList(
- ActivityManager.RecentTaskInfo... tasks) {
- ArrayList rawList = new ArrayList<>();
- for (ActivityManager.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;
}
/**
* Asserts that the recent tasks matches the given task ids.
+ * TODO(346588978): Separate out specific split verification during the iteration below
*
- * @param expectedTaskIds list of task ids that map to the flattened task ids of the tasks in
- * the grouped task list
+ * @param expectedTaskIds a list of expected grouped task ids (itself a list of ints)
*/
- private void assertGroupedTasksListEquals(List recentTasks,
- int... expectedTaskIds) {
- int[] flattenedTaskIds = new int[recentTasks.size() * 2];
- for (int i = 0; i < recentTasks.size(); i++) {
- GroupedRecentTaskInfo pair = recentTasks.get(i);
- int taskId1 = pair.getTaskInfo1().taskId;
- flattenedTaskIds[2 * i] = taskId1;
- flattenedTaskIds[2 * i + 1] = pair.getTaskInfo2() != null
- ? pair.getTaskInfo2().taskId
- : -1;
+ private void assertGroupedTasksListEquals(List groupedTasks,
+ List