diff options
Diffstat (limited to 'libs')
339 files changed, 9172 insertions, 3340 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index 9ea2943bc6da..f0613cec6a0b 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -398,27 +398,23 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { new TaskFragmentAnimationParams.Builder(); final int animationBackgroundColor = getAnimationBackgroundColor(splitAttributes); builder.setAnimationBackgroundColor(animationBackgroundColor); - if (Flags.activityEmbeddingAnimationCustomizationFlag()) { - final int openAnimationResId = - splitAttributes.getAnimationParams().getOpenAnimationResId(); - builder.setOpenAnimationResId(openAnimationResId); - final int closeAnimationResId = - splitAttributes.getAnimationParams().getCloseAnimationResId(); - builder.setCloseAnimationResId(closeAnimationResId); - final int changeAnimationResId = - splitAttributes.getAnimationParams().getChangeAnimationResId(); - builder.setChangeAnimationResId(changeAnimationResId); - } + final int openAnimationResId = + splitAttributes.getAnimationParams().getOpenAnimationResId(); + builder.setOpenAnimationResId(openAnimationResId); + final int closeAnimationResId = + splitAttributes.getAnimationParams().getCloseAnimationResId(); + builder.setCloseAnimationResId(closeAnimationResId); + final int changeAnimationResId = + splitAttributes.getAnimationParams().getChangeAnimationResId(); + builder.setChangeAnimationResId(changeAnimationResId); return builder.build(); } @ColorInt private static int getAnimationBackgroundColor(@NonNull SplitAttributes splitAttributes) { int animationBackgroundColor = DEFAULT_ANIMATION_BACKGROUND_COLOR; - AnimationBackground animationBackground = splitAttributes.getAnimationBackground(); - if (Flags.activityEmbeddingAnimationCustomizationFlag()) { - animationBackground = splitAttributes.getAnimationParams().getAnimationBackground(); - } + final AnimationBackground animationBackground = + splitAttributes.getAnimationParams().getAnimationBackground(); if (animationBackground instanceof AnimationBackground.ColorBackground colorBackground) { animationBackgroundColor = colorBackground.getColor(); } diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/TestShellExecutor.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/TestShellExecutor.kt new file mode 100644 index 000000000000..ef8e71c2590b --- /dev/null +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/TestShellExecutor.kt @@ -0,0 +1,49 @@ +/* + * 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 + +import com.android.wm.shell.common.ShellExecutor + +/** + * Simple implementation of [ShellExecutor] that collects all runnables and executes them + * sequentially once [flushAll] is called + */ +class TestShellExecutor : ShellExecutor { + + private val runnables: MutableList<Runnable> = mutableListOf() + + override fun execute(runnable: Runnable) { + runnables.add(runnable) + } + + override fun executeDelayed(runnable: Runnable, delayMillis: Long) { + execute(runnable) + } + + override fun removeCallbacks(runnable: Runnable?) {} + + override fun hasCallback(runnable: Runnable?): Boolean = false + + /** + * Execute all posted runnables sequentially + */ + fun flushAll() { + while (runnables.isNotEmpty()) { + runnables.removeAt(0).run() + } + } +} diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt index f535fbd653c5..2b4e5417f188 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt @@ -34,6 +34,7 @@ import com.android.internal.protolog.ProtoLog import com.android.internal.statusbar.IStatusBarService import com.android.wm.shell.Flags import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.WindowManagerShellWrapper import com.android.wm.shell.bubbles.Bubbles.SysuiProxy import com.android.wm.shell.bubbles.properties.ProdBubbleProperties @@ -41,7 +42,6 @@ import com.android.wm.shell.bubbles.storage.BubblePersistentRepository import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayInsetsController import com.android.wm.shell.common.FloatingContentCoordinator -import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.common.TaskStackListenerImpl import com.android.wm.shell.draganddrop.DragAndDropController @@ -84,16 +84,16 @@ class BubbleControllerBubbleBarTest { private lateinit var uiEventLoggerFake: UiEventLoggerFake private lateinit var bubblePositioner: BubblePositioner private lateinit var bubbleData: BubbleData - private lateinit var mainExecutor: TestExecutor - private lateinit var bgExecutor: TestExecutor + private lateinit var mainExecutor: TestShellExecutor + private lateinit var bgExecutor: TestShellExecutor @Before fun setUp() { ProtoLog.REQUIRE_PROTOLOGTOOL = false ProtoLog.init() - mainExecutor = TestExecutor() - bgExecutor = TestExecutor() + mainExecutor = TestShellExecutor() + bgExecutor = TestShellExecutor() uiEventLoggerFake = UiEventLoggerFake() val bubbleLogger = BubbleLogger(uiEventLoggerFake) @@ -232,8 +232,8 @@ class BubbleControllerBubbleBarTest { bubbleData: BubbleData, bubbleLogger: BubbleLogger, bubblePositioner: BubblePositioner, - mainExecutor: TestExecutor, - bgExecutor: TestExecutor, + mainExecutor: TestShellExecutor, + bgExecutor: TestShellExecutor, ): BubbleController { val shellCommandHandler = ShellCommandHandler() val shellController = @@ -289,29 +289,6 @@ class BubbleControllerBubbleBarTest { ) } - private class TestExecutor : ShellExecutor { - - private val runnables: MutableList<Runnable> = mutableListOf() - - override fun execute(runnable: Runnable) { - runnables.add(runnable) - } - - override fun executeDelayed(runnable: Runnable, delayMillis: Long) { - execute(runnable) - } - - override fun removeCallbacks(runnable: Runnable?) {} - - override fun hasCallback(runnable: Runnable?): Boolean = false - - fun flushAll() { - while (runnables.isNotEmpty()) { - runnables.removeAt(0).run() - } - } - } - private class FakeBubblesStateListener : Bubbles.BubbleStateListener { override fun onBubbleStateChange(update: BubbleBarUpdate?) {} diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt index b38d00da6dfa..1d0c5057c77f 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt @@ -602,8 +602,72 @@ class BubblePositionerTest { testGetBubbleBarExpandedViewBounds(onLeft = false, isOverflow = true) } + @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 testGetBubbleBarExpandedViewBounds(onLeft: Boolean, isOverflow: Boolean) { - positioner.setShowingInBubbleBar(true) + positioner.isShowingInBubbleBar = true val windowBounds = Rect(0, 0, 2000, 2600) val insets = Insets.of(10, 20, 5, 15) val deviceConfig = diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt index 5f42bb161204..239acd37f286 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt @@ -31,20 +31,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 -import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.protolog.ProtoLog import com.android.launcher3.icons.BubbleIconFactory import com.android.wm.shell.Flags import com.android.wm.shell.R +import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.bubbles.Bubbles.SysuiProxy import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix import com.android.wm.shell.common.FloatingContentCoordinator -import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils -import com.android.wm.shell.shared.bubbles.BubbleBarLocation -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 @@ -52,6 +48,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito import org.mockito.kotlin.mock import org.mockito.kotlin.verify import java.util.concurrent.Semaphore @@ -71,7 +68,7 @@ class BubbleStackViewTest { private lateinit var iconFactory: BubbleIconFactory private lateinit var expandedViewManager: FakeBubbleExpandedViewManager private lateinit var bubbleStackView: BubbleStackView - private lateinit var shellExecutor: ShellExecutor + private lateinit var shellExecutor: TestShellExecutor private lateinit var windowManager: WindowManager private lateinit var bubbleTaskViewFactory: BubbleTaskViewFactory private lateinit var bubbleData: BubbleData @@ -108,7 +105,7 @@ class BubbleStackViewTest { ) bubbleStackViewManager = FakeBubbleStackViewManager() expandedViewManager = FakeBubbleExpandedViewManager() - bubbleTaskViewFactory = FakeBubbleTaskViewFactory() + bubbleTaskViewFactory = FakeBubbleTaskViewFactory(context, shellExecutor) bubbleStackView = BubbleStackView( context, @@ -168,6 +165,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() @@ -206,6 +204,7 @@ class BubbleStackViewTest { bubbleStackView.setSelectedBubble(bubble2) bubbleStackView.isExpanded = true + shellExecutor.flushAll() } assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue() @@ -223,6 +222,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) @@ -233,6 +233,7 @@ class BubbleStackViewTest { // listener wired up. bubbleStackView.setSelectedBubble(bubble1) bubble1.iconView!!.performClick() + shellExecutor.flushAll() } assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue() @@ -355,7 +356,7 @@ class BubbleStackViewTest { @Test fun removeFromWindow_stopMonitoringSwipeUpGesture() { - spyOn(bubbleStackView) + bubbleStackView = Mockito.spy(bubbleStackView) InstrumentationRegistry.getInstrumentation().runOnMainSync { // No way to add to window in the test environment right now so just pretend bubbleStackView.onDetachedFromWindow() @@ -426,55 +427,4 @@ class BubbleStackViewTest { 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<TaskViewTaskController>() - val taskView = TaskView(context, taskViewTaskController) - return BubbleTaskView(taskView, shellExecutor) - } - } - - private inner class FakeBubbleExpandedViewManager : BubbleExpandedViewManager { - - override val overflowBubbles: List<Bubble> - 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() {} - - override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {} - } } diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt index 776ea9226b06..680d015dfd2f 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt @@ -35,13 +35,13 @@ import com.android.internal.protolog.ProtoLog import com.android.internal.statusbar.IStatusBarService import com.android.launcher3.icons.BubbleIconFactory import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.WindowManagerShellWrapper import com.android.wm.shell.bubbles.properties.BubbleProperties import com.android.wm.shell.bubbles.storage.BubblePersistentRepository import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayInsetsController import com.android.wm.shell.common.FloatingContentCoordinator -import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.common.TaskStackListenerImpl import com.android.wm.shell.shared.TransactionPool @@ -70,8 +70,8 @@ class BubbleViewInfoTaskTest { private lateinit var metadataFlagListener: Bubbles.BubbleMetadataFlagListener private lateinit var iconFactory: BubbleIconFactory private lateinit var bubbleController: BubbleController - private lateinit var mainExecutor: TestExecutor - private lateinit var bgExecutor: TestExecutor + private lateinit var mainExecutor: TestShellExecutor + private lateinit var bgExecutor: TestShellExecutor private lateinit var bubbleStackView: BubbleStackView private lateinit var bubblePositioner: BubblePositioner private lateinit var bubbleLogger: BubbleLogger @@ -94,8 +94,8 @@ class BubbleViewInfoTaskTest { context.resources.getDimensionPixelSize(R.dimen.importance_ring_stroke_width) ) - mainExecutor = TestExecutor() - bgExecutor = TestExecutor() + mainExecutor = TestShellExecutor() + bgExecutor = TestShellExecutor() val windowManager = context.getSystemService(WindowManager::class.java) val shellInit = ShellInit(mainExecutor) val shellCommandHandler = ShellCommandHandler() @@ -335,27 +335,4 @@ class BubbleViewInfoTaskTest { bgExecutor ) } - - private class TestExecutor : ShellExecutor { - - private val runnables: MutableList<Runnable> = mutableListOf() - - override fun execute(runnable: Runnable) { - runnables.add(runnable) - } - - override fun executeDelayed(runnable: Runnable, delayMillis: Long) { - execute(runnable) - } - - override fun removeCallbacks(runnable: Runnable?) {} - - override fun hasCallback(runnable: Runnable?): Boolean = false - - fun flushAll() { - while (runnables.isNotEmpty()) { - runnables.removeAt(0).run() - } - } - } } diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleExpandedViewManager.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleExpandedViewManager.kt new file mode 100644 index 000000000000..3c013d3636e8 --- /dev/null +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleExpandedViewManager.kt @@ -0,0 +1,54 @@ +/* + * 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 com.android.wm.shell.shared.bubbles.BubbleBarLocation +import java.util.Collections + +/** Fake implementation of [BubbleExpandedViewManager] for testing. */ +class FakeBubbleExpandedViewManager(var bubbleBar: Boolean = false, var expanded: Boolean = false) : + BubbleExpandedViewManager { + + override val overflowBubbles: List<Bubble> + get() = Collections.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 { + return expanded + } + + override fun isShowingAsBubbleBar(): Boolean { + return bubbleBar + } + + override fun hideCurrentInputMethod() {} + + override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {} +} diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt index 3279d561d4f1..bcaa63bfad36 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt @@ -19,6 +19,10 @@ package com.android.wm.shell.bubbles import android.content.Context import android.content.pm.ShortcutInfo import android.content.res.Resources +import android.view.LayoutInflater +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.wm.shell.R +import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.bubbles.BubbleViewInfoTask.BubbleViewInfo import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView import com.google.common.util.concurrent.MoreExecutors.directExecutor @@ -27,6 +31,33 @@ import com.google.common.util.concurrent.MoreExecutors.directExecutor class FakeBubbleFactory { companion object { + fun createExpandedView( + context: Context, + bubblePositioner: BubblePositioner, + expandedViewManager: BubbleExpandedViewManager, + bubbleTaskView: BubbleTaskView, + mainExecutor: TestShellExecutor, + bgExecutor: TestShellExecutor, + bubbleLogger: BubbleLogger = BubbleLogger(UiEventLoggerFake()), + ): BubbleBarExpandedView { + val bubbleBarExpandedView = + (LayoutInflater.from(context) + .inflate(R.layout.bubble_bar_expanded_view, null, false /* attachToRoot */) + as BubbleBarExpandedView) + .apply { + initialize( + expandedViewManager, + bubblePositioner, + bubbleLogger, + false, /* isOverflow */ + bubbleTaskView, + mainExecutor, + bgExecutor, + null, /* regionSamplingProvider */ + ) + } + return bubbleBarExpandedView + } fun createViewInfo(bubbleExpandedView: BubbleBarExpandedView): BubbleViewInfo { return BubbleViewInfo().apply { bubbleBarExpandedView = bubbleExpandedView } diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleTaskViewFactory.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleTaskViewFactory.kt new file mode 100644 index 000000000000..42b66aa29bfc --- /dev/null +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleTaskViewFactory.kt @@ -0,0 +1,41 @@ +/* + * 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.app.ActivityManager +import android.content.Context +import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.taskview.TaskView +import com.android.wm.shell.taskview.TaskViewTaskController +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +/** + * Implementation of [BubbleTaskViewFactory] for testing. + */ +class FakeBubbleTaskViewFactory( + private val context: Context, + private val mainExecutor: ShellExecutor, +) : BubbleTaskViewFactory { + override fun create(): BubbleTaskView { + val taskViewTaskController = mock<TaskViewTaskController>() + val taskView = TaskView(context, taskViewTaskController) + val taskInfo = mock<ActivityManager.RunningTaskInfo>() + whenever(taskViewTaskController.taskInfo).thenReturn(taskInfo) + return BubbleTaskView(taskView, mainExecutor) + } +} diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt index 1bf6af8d1f6d..bfc798bb9c79 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt @@ -33,32 +33,30 @@ import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.protolog.ProtoLog import com.android.wm.shell.R +import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.bubbles.Bubble -import com.android.wm.shell.bubbles.BubbleData import com.android.wm.shell.bubbles.BubbleExpandedViewManager import com.android.wm.shell.bubbles.BubbleLogger import com.android.wm.shell.bubbles.BubblePositioner import com.android.wm.shell.bubbles.BubbleTaskView import com.android.wm.shell.bubbles.BubbleTaskViewFactory import com.android.wm.shell.bubbles.DeviceConfig +import com.android.wm.shell.bubbles.FakeBubbleExpandedViewManager import com.android.wm.shell.bubbles.RegionSamplingProvider import com.android.wm.shell.bubbles.UiEventSubject.Companion.assertThat -import com.android.wm.shell.common.ShellExecutor -import com.android.wm.shell.shared.bubbles.BubbleBarLocation import com.android.wm.shell.shared.handles.RegionSamplingHelper 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.truth.Truth.assertWithMessage import com.google.common.util.concurrent.MoreExecutors.directExecutor -import java.util.Collections -import java.util.concurrent.Executor import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.mock import org.mockito.kotlin.whenever +import java.util.concurrent.Executor /** Tests for [BubbleBarExpandedViewTest] */ @SmallTest @@ -72,8 +70,8 @@ class BubbleBarExpandedViewTest { private val context = ApplicationProvider.getApplicationContext<Context>() private val windowManager = context.getSystemService(WindowManager::class.java) - private lateinit var mainExecutor: TestExecutor - private lateinit var bgExecutor: TestExecutor + private lateinit var mainExecutor: TestShellExecutor + private lateinit var bgExecutor: TestShellExecutor private lateinit var expandedViewManager: BubbleExpandedViewManager private lateinit var positioner: BubblePositioner @@ -90,8 +88,8 @@ class BubbleBarExpandedViewTest { fun setUp() { ProtoLog.REQUIRE_PROTOLOGTOOL = false ProtoLog.init() - mainExecutor = TestExecutor() - bgExecutor = TestExecutor() + mainExecutor = TestShellExecutor() + bgExecutor = TestShellExecutor() positioner = BubblePositioner(context, windowManager) positioner.setShowingInBubbleBar(true) val deviceConfig = @@ -105,7 +103,7 @@ class BubbleBarExpandedViewTest { ) positioner.update(deviceConfig) - expandedViewManager = createExpandedViewManager() + expandedViewManager = FakeBubbleExpandedViewManager(bubbleBar = true, expanded = true) bubbleTaskView = FakeBubbleTaskViewFactory().create() val inflater = LayoutInflater.from(context) @@ -426,63 +424,4 @@ class BubbleBarExpandedViewTest { setWindowInvisible = false } } - - private fun createExpandedViewManager(): BubbleExpandedViewManager { - return object : BubbleExpandedViewManager { - override val overflowBubbles: List<Bubble> - get() = Collections.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 { - return true - } - - override fun isShowingAsBubbleBar(): Boolean { - return true - } - - override fun hideCurrentInputMethod() { - } - - override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) { - } - } - } - - private class TestExecutor : ShellExecutor { - - private val runnables: MutableList<Runnable> = mutableListOf() - - override fun execute(runnable: Runnable) { - runnables.add(runnable) - } - - override fun executeDelayed(runnable: Runnable, delayMillis: Long) { - execute(runnable) - } - - override fun removeCallbacks(runnable: Runnable?) {} - - override fun hasCallback(runnable: Runnable?): Boolean = false - } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt index 7280f8aa07a6..04c9ffbac287 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt @@ -16,14 +16,12 @@ package com.android.wm.shell.bubbles.bar -import android.app.ActivityManager import android.content.Context import android.content.pm.LauncherApps import android.graphics.PointF import android.os.Handler import android.os.UserManager import android.view.IWindowManager -import android.view.LayoutInflater import android.view.MotionEvent import android.view.View import android.view.WindowManager @@ -37,19 +35,19 @@ import com.android.internal.protolog.ProtoLog import com.android.internal.statusbar.IStatusBarService import com.android.wm.shell.R import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.WindowManagerShellWrapper import com.android.wm.shell.bubbles.Bubble import com.android.wm.shell.bubbles.BubbleController import com.android.wm.shell.bubbles.BubbleData import com.android.wm.shell.bubbles.BubbleDataRepository import com.android.wm.shell.bubbles.BubbleEducationController -import com.android.wm.shell.bubbles.BubbleExpandedViewManager import com.android.wm.shell.bubbles.BubbleLogger import com.android.wm.shell.bubbles.BubblePositioner -import com.android.wm.shell.bubbles.BubbleTaskView -import com.android.wm.shell.bubbles.BubbleTaskViewFactory import com.android.wm.shell.bubbles.Bubbles.SysuiProxy +import com.android.wm.shell.bubbles.FakeBubbleExpandedViewManager import com.android.wm.shell.bubbles.FakeBubbleFactory +import com.android.wm.shell.bubbles.FakeBubbleTaskViewFactory import com.android.wm.shell.bubbles.UiEventSubject.Companion.assertThat import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix import com.android.wm.shell.bubbles.properties.BubbleProperties @@ -57,7 +55,6 @@ import com.android.wm.shell.bubbles.storage.BubblePersistentRepository import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayInsetsController import com.android.wm.shell.common.FloatingContentCoordinator -import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.common.TaskStackListenerImpl import com.android.wm.shell.shared.TransactionPool @@ -66,20 +63,16 @@ import com.android.wm.shell.shared.bubbles.BubbleBarLocation 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.taskview.TaskView -import com.android.wm.shell.taskview.TaskViewTaskController import com.android.wm.shell.taskview.TaskViewTransitions import com.android.wm.shell.transition.Transitions import com.google.common.truth.Truth.assertThat import org.junit.After -import java.util.Collections import org.junit.Before import org.junit.ClassRule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock import org.mockito.kotlin.mock -import org.mockito.kotlin.whenever /** Tests for [BubbleBarLayerView] */ @SmallTest @@ -87,8 +80,7 @@ import org.mockito.kotlin.whenever class BubbleBarLayerViewTest { companion object { - @JvmField @ClassRule - val animatorTestRule: AnimatorTestRule = AnimatorTestRule() + @JvmField @ClassRule val animatorTestRule: AnimatorTestRule = AnimatorTestRule() } private val context = ApplicationProvider.getApplicationContext<Context>() @@ -112,8 +104,8 @@ class BubbleBarLayerViewTest { uiEventLoggerFake = UiEventLoggerFake() val bubbleLogger = BubbleLogger(uiEventLoggerFake) - val mainExecutor = TestExecutor() - val bgExecutor = TestExecutor() + val mainExecutor = TestShellExecutor() + val bgExecutor = TestShellExecutor() val windowManager = context.getSystemService(WindowManager::class.java) @@ -145,24 +137,18 @@ class BubbleBarLayerViewTest { bubbleBarLayerView = BubbleBarLayerView(context, bubbleController, bubbleData, bubbleLogger) - val expandedViewManager = createExpandedViewManager() - val bubbleTaskView = FakeBubbleTaskViewFactory(mainExecutor).create() + val expandedViewManager = FakeBubbleExpandedViewManager(bubbleBar = true, expanded = true) + val bubbleTaskView = FakeBubbleTaskViewFactory(context, mainExecutor).create() val bubbleBarExpandedView = - (LayoutInflater.from(context) - .inflate(R.layout.bubble_bar_expanded_view, null, false /* attachToRoot */) - as BubbleBarExpandedView) - .apply { - initialize( - expandedViewManager, - bubblePositioner, - bubbleLogger, - false /* isOverflow */, - bubbleTaskView, - mainExecutor, - bgExecutor, - null, /* regionSamplingProvider */ - ) - } + FakeBubbleFactory.createExpandedView( + context, + bubblePositioner, + expandedViewManager, + bubbleTaskView, + mainExecutor, + bgExecutor, + bubbleLogger, + ) val viewInfo = FakeBubbleFactory.createViewInfo(bubbleBarExpandedView) bubble = FakeBubbleFactory.createChatBubble(context, viewInfo = viewInfo) @@ -179,8 +165,8 @@ class BubbleBarLayerViewTest { windowManager: WindowManager?, bubbleLogger: BubbleLogger, bubblePositioner: BubblePositioner, - mainExecutor: TestExecutor, - bgExecutor: TestExecutor, + mainExecutor: TestShellExecutor, + bgExecutor: TestShellExecutor, ): BubbleController { val shellInit = ShellInit(mainExecutor) val shellCommandHandler = ShellCommandHandler() @@ -310,74 +296,9 @@ class BubbleBarLayerViewTest { getInstrumentation().waitForIdleSync() getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(200) } PhysicsAnimatorTestUtils.blockUntilAnimationsEnd( - AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y) - } - - private inner class FakeBubbleTaskViewFactory(private val mainExecutor: ShellExecutor) : - BubbleTaskViewFactory { - override fun create(): BubbleTaskView { - val taskViewTaskController = mock<TaskViewTaskController>() - val taskView = TaskView(context, taskViewTaskController) - val taskInfo = mock<ActivityManager.RunningTaskInfo>() - whenever(taskViewTaskController.taskInfo).thenReturn(taskInfo) - return BubbleTaskView(taskView, mainExecutor) - } - } - - private fun createExpandedViewManager(): BubbleExpandedViewManager { - return object : BubbleExpandedViewManager { - override val overflowBubbles: List<Bubble> - get() = Collections.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 { - return true - } - - override fun isShowingAsBubbleBar(): Boolean { - return true - } - - override fun hideCurrentInputMethod() {} - - override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {} - } - } - - private class TestExecutor : ShellExecutor { - - private val runnables: MutableList<Runnable> = mutableListOf() - - override fun execute(runnable: Runnable) { - runnables.add(runnable) - } - - override fun executeDelayed(runnable: Runnable, delayMillis: Long) { - execute(runnable) - } - - override fun removeCallbacks(runnable: Runnable?) {} - - override fun hasCallback(runnable: Runnable?): Boolean = false - - fun flushAll() { - while (runnables.isNotEmpty()) { - runnables.removeAt(0).run() - } - } + AnimatableScaleMatrix.SCALE_X, + AnimatableScaleMatrix.SCALE_Y, + ) } private fun View.dispatchTouchEvent(eventTime: Long, action: Int, point: PointF) { diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml index 3dbf7542ac6e..fcf74e3c1936 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml @@ -46,15 +46,19 @@ <TextView android:id="@+id/application_name" android:layout_width="0dp" - android:layout_height="20dp" - android:maxWidth="86dp" + android:layout_height="wrap_content" + android:maxWidth="130dp" android:textAppearance="@android:style/TextAppearance.Material.Title" android:textSize="14sp" android:textFontWeight="500" - android:lineHeight="20dp" + android:lineHeight="20sp" android:layout_gravity="center_vertical" android:layout_weight="1" android:layout_marginStart="8dp" + android:singleLine="true" + android:ellipsize="none" + android:requiresFadingEdge="horizontal" + android:fadingEdgeLength="28dp" android:clickable="false" android:focusable="false" tools:text="Gmail"/> diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml index 9a1a3da06a77..07cc0e769a59 100644 --- a/libs/WindowManager/Shell/res/values-af/strings.xml +++ b/libs/WindowManager/Shell/res/values-af/strings.xml @@ -102,7 +102,7 @@ <string name="windowing_desktop_mode_exit_education_tooltip" msgid="5225660258192054132">"Keer enige tyd terug na volskerm vanaf die appkieslys"</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Sien en doen meer"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Sleep ’n ander app in vir verdeelde skerm"</string> - <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dubbeltik buite ’n program om dit te herposisioneer"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dubbeltik buite ’n app om dit te herposisioneer"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Het dit"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vou uit vir meer inligting."</string> <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Herbegin vir ’n beter aansig?"</string> @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Maak kieslys toe"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Maak kieslys oop"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimeer skerm"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Verander grootte"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App kan nie hierheen geskuif word nie"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Meesleurend"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Stel terug"</string> diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml index 9d22fef66636..a6921b992234 100644 --- a/libs/WindowManager/Shell/res/values-am/strings.xml +++ b/libs/WindowManager/Shell/res/values-am/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"ምናሌ ዝጋ"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ምናሌን ክፈት"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"የማያ ገጹ መጠን አሳድግ"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"መጠን ቀይር"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"መተግበሪያ ወደዚህ መንቀሳቀስ አይችልም"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"አስማጭ"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ወደነበረበት መልስ"</string> diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml index 46ab090e310e..b72d25519e4f 100644 --- a/libs/WindowManager/Shell/res/values-ar/strings.xml +++ b/libs/WindowManager/Shell/res/values-ar/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"إغلاق القائمة"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"فتح القائمة"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"تكبير الشاشة إلى أقصى حدّ"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"تغيير الحجم"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"لا يمكن نقل التطبيق إلى هنا"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"مجسَّم"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"استعادة"</string> diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml index 1e35d6ed132f..632d1265a1e6 100644 --- a/libs/WindowManager/Shell/res/values-as/strings.xml +++ b/libs/WindowManager/Shell/res/values-as/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"মেনু বন্ধ কৰক"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"মেনু খোলক"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"স্ক্ৰীন মেক্সিমাইজ কৰক"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"আকাৰ সলনি কৰক"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ইয়ালৈ এপ্টো আনিব নোৱাৰি"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ইমাৰ্ছিভ"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"পুনঃস্থাপন কৰক"</string> diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml index 136d4c18c3d5..cf9f1b251af7 100644 --- a/libs/WindowManager/Shell/res/values-az/strings.xml +++ b/libs/WindowManager/Shell/res/values-az/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Menyunu bağlayın"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menyunu açın"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranı maksimum böyüdün"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ölçüsünü dəyişin"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Tətbiqi bura köçürmək mümkün deyil"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"İmmersiv"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Bərpa edin"</string> diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml index 10a33bb6aca7..c2d4d8b0613e 100644 --- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml +++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Zatvorite meni"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvorite meni"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Povećaj ekran"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Promeni veličinu"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacija ne može da se premesti ovde"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Imerzivne"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Vrati"</string> diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml index 163fbddbc967..dde2374ea491 100644 --- a/libs/WindowManager/Shell/res/values-be/strings.xml +++ b/libs/WindowManager/Shell/res/values-be/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Закрыць меню"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Адкрыць меню"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Разгарнуць на ўвесь экран"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Змяніць памер"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Нельга перамясціць сюды праграму"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"З эфектам прысутнасці"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Аднавіць"</string> diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml index d7da3aef02bb..7e804843dfce 100644 --- a/libs/WindowManager/Shell/res/values-bg/strings.xml +++ b/libs/WindowManager/Shell/res/values-bg/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Затваряне на менюто"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Отваряне на менюто"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Увеличаване на екрана"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Преоразмеряване"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Приложението не може да бъде преместено тук"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Реалистично"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Възстановяване"</string> diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml index 9c2fc6e98818..4c6e6c1fee2f 100644 --- a/libs/WindowManager/Shell/res/values-bn/strings.xml +++ b/libs/WindowManager/Shell/res/values-bn/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"\'মেনু\' বন্ধ করুন"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"মেনু খুলুন"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"স্ক্রিন বড় করুন"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ছোট বড় করুন"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"অ্যাপটি এখানে সরানো যাবে না"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ইমারসিভ"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ফিরিয়ে আনুন"</string> diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml index 911285d060f1..244149b855f6 100644 --- a/libs/WindowManager/Shell/res/values-bs/strings.xml +++ b/libs/WindowManager/Shell/res/values-bs/strings.xml @@ -87,7 +87,7 @@ <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Upravljajte oblačićima u svakom trenutku"</string> <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Dodirnite ovdje da upravljate time koje aplikacije i razgovori mogu imati oblačić"</string> <string name="notification_bubble_title" msgid="6082910224488253378">"Oblačić"</string> - <string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljaj"</string> + <string name="manage_bubbles_text" msgid="7730624269650594419">"Upravljajte"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Oblačić je odbačen."</string> <string name="bubble_shortcut_label" msgid="666269077944378311">"Oblačići"</string> <string name="bubble_shortcut_long_label" msgid="6088437544312894043">"Prikaz oblačića"</string> @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Zatvaranje menija"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvaranje menija"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimiziraj ekran"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Promijeni veličinu"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ne možete premjestiti aplikaciju ovdje"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Uvjerljivo"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Vraćanje"</string> diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml index 4249373e0a3d..786ed769e7b7 100644 --- a/libs/WindowManager/Shell/res/values-ca/strings.xml +++ b/libs/WindowManager/Shell/res/values-ca/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Tanca el menú"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Obre el menú"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximitza la pantalla"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Canvia la mida"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"L\'aplicació no es pot moure aquí"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersiu"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaura"</string> diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml index a12534372135..99e9a8350822 100644 --- a/libs/WindowManager/Shell/res/values-cs/strings.xml +++ b/libs/WindowManager/Shell/res/values-cs/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Zavřít nabídku"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otevřít nabídku"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximalizovat obrazovku"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Změnit velikost"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikaci sem nelze přesunout"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Pohlcující"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Obnovit"</string> diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml index 5b657f4c9bb6..6021a96e8cbe 100644 --- a/libs/WindowManager/Shell/res/values-da/strings.xml +++ b/libs/WindowManager/Shell/res/values-da/strings.xml @@ -72,9 +72,9 @@ <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"udvid <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string> <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"skjul <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string> <string name="bubbles_app_settings" msgid="3617224938701566416">"Indstillinger for <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string> - <string name="bubble_dismiss_text" msgid="8816558050659478158">"Afvis boble"</string> + <string name="bubble_dismiss_text" msgid="8816558050659478158">"Luk boble"</string> <string name="bubble_fullscreen_text" msgid="1006758103218086231">"Flyt til fuld skærm"</string> - <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Vis ikke samtaler i bobler"</string> + <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Vis ikke samtale i boble"</string> <string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat ved hjælp af bobler"</string> <string name="bubbles_user_education_description" msgid="4215862563054175407">"Nye samtaler vises som svævende ikoner eller bobler. Tryk for at åbne boblen. Træk for at flytte den."</string> <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Styr bobler når som helst"</string> @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Luk menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Åbn menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimér skærm"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Tilpas størrelse"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Apps kan ikke flyttes hertil"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Opslugende"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Gendan"</string> diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml index 6d360e8e0af2..7b296620099b 100644 --- a/libs/WindowManager/Shell/res/values-de/strings.xml +++ b/libs/WindowManager/Shell/res/values-de/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Menü schließen"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menü öffnen"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Bildschirm maximieren"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Größe ändern"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Die App kann nicht hierher verschoben werden"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersiv"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Wiederherstellen"</string> diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml index 85a44f6d760d..879347adf406 100644 --- a/libs/WindowManager/Shell/res/values-el/strings.xml +++ b/libs/WindowManager/Shell/res/values-el/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Κλείσιμο μενού"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Άνοιγμα μενού"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Μεγιστοποίηση οθόνης"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Αλλαγή μεγέθους"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Δεν είναι δυνατή η μετακίνηση της εφαρμογής εδώ"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Καθηλωτικό"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Επαναφορά"</string> diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml index 3e30ff048c6d..358e31476242 100644 --- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Resize"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restore"</string> diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml index 0d7189bd16b3..923f30b9a5ba 100644 --- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Close Menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open Menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximize Screen"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Resize"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restore"</string> diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml index 3e30ff048c6d..358e31476242 100644 --- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Resize"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restore"</string> diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml index 3e30ff048c6d..358e31476242 100644 --- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Resize"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restore"</string> diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml index 6a1a2e5a4d39..7a2e8cffffcf 100644 --- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml +++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Cerrar menú"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir el menú"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Cambiar el tamaño"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"No se puede mover la app aquí"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Inmersivo"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restablecer"</string> diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml index f93cf5a2fefd..2a30bfbd1ba1 100644 --- a/libs/WindowManager/Shell/res/values-es/strings.xml +++ b/libs/WindowManager/Shell/res/values-es/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Cerrar menú"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir menú"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Cambiar tamaño"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"La aplicación no se puede mover aquí"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Inmersivo"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string> diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml index f0d1d4e60392..9a15f90ac27e 100644 --- a/libs/WindowManager/Shell/res/values-et/strings.xml +++ b/libs/WindowManager/Shell/res/values-et/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Sule menüü"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ava menüü"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Kuva täisekraanil"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Suuruse muutmine"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Rakendust ei saa siia teisaldada"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Kaasahaarav"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Taasta"</string> diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml index c6a7f2eca877..7c03b24eaef8 100644 --- a/libs/WindowManager/Shell/res/values-eu/strings.xml +++ b/libs/WindowManager/Shell/res/values-eu/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Itxi menua"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ireki menua"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Handitu pantaila"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Aldatu tamaina"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikazioa ezin da hona ekarri"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Murgiltzailea"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Leheneratu"</string> diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml index d10a02d75c18..eb50ba7c9477 100644 --- a/libs/WindowManager/Shell/res/values-fa/strings.xml +++ b/libs/WindowManager/Shell/res/values-fa/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"بستن منو"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"باز کردن منو"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"بزرگ کردن صفحه"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"تغییر اندازه"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"برنامه را نمیتوان به اینجا منتقل کرد"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"فراگیر"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"بازیابی"</string> diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml index 0655f9a390a0..d89e36aad3d3 100644 --- a/libs/WindowManager/Shell/res/values-fi/strings.xml +++ b/libs/WindowManager/Shell/res/values-fi/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Sulje valikko"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Avaa valikko"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Suurenna näyttö"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Muuta kokoa"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Sovellusta ei voi siirtää tänne"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersiivinen"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Palauta"</string> diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml index b9bdbd73aed6..e2730d422013 100644 --- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml @@ -74,7 +74,7 @@ <string name="bubbles_app_settings" msgid="3617224938701566416">"Paramètres <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string> <string name="bubble_dismiss_text" msgid="8816558050659478158">"Ignorer la bulle"</string> <string name="bubble_fullscreen_text" msgid="1006758103218086231">"Passez en plein écran"</string> - <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne pas afficher les conversations dans des bulles"</string> + <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ne pas afficher la conversation dans une bulle"</string> <string name="bubbles_user_education_title" msgid="2112319053732691899">"Clavarder en utilisant des bulles"</string> <string name="bubbles_user_education_description" msgid="4215862563054175407">"Les nouvelles conversations s\'affichent sous forme d\'icônes flottantes (de bulles). Touchez une bulle pour l\'ouvrir. Faites-la glisser pour la déplacer."</string> <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Paramètres des bulles"</string> @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Fermer le menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ouvrir le menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Agrandir l\'écran"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionner"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Impossible de déplacer l\'appli ici"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersif"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurer"</string> diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml index a1eb028a4f9c..a97a48cdcd46 100644 --- a/libs/WindowManager/Shell/res/values-fr/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Fermer le menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Ouvrir le menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Mettre en plein écran"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionner"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Impossible de déplacer l\'appli ici"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersif"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurer"</string> diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml index 22a7f7fcb35b..445cc70d4e8d 100644 --- a/libs/WindowManager/Shell/res/values-gl/strings.xml +++ b/libs/WindowManager/Shell/res/values-gl/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Pechar o menú"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir o menú"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Cambiar tamaño"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Non se pode mover aquí a aplicación"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Envolvente"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string> diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml index 06c21b46a97e..6bef1bb6e061 100644 --- a/libs/WindowManager/Shell/res/values-gu/strings.xml +++ b/libs/WindowManager/Shell/res/values-gu/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"મેનૂ બંધ કરો"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"મેનૂ ખોલો"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"સ્ક્રીન કરો મોટી કરો"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"કદ બદલો"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ઍપ અહીં ખસેડી શકાતી નથી"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ઇમર્સિવ"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"રિસ્ટોર કરો"</string> diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml index 0eab10c34606..95b3fc0fafd5 100644 --- a/libs/WindowManager/Shell/res/values-hi/strings.xml +++ b/libs/WindowManager/Shell/res/values-hi/strings.xml @@ -84,7 +84,7 @@ <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"हाल ही के बबल्स और हटाए गए बबल्स यहां दिखेंगे"</string> <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"बबल्स का इस्तेमाल करके चैट करें"</string> <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"नई बातचीत, आपकी स्क्रीन पर सबसे नीचे आइकॉन के तौर पर दिखती हैं. किसी आइकॉन को बड़ा करने के लिए उस पर टैप करें या खारिज करने के लिए उसे खींचें और छोड़ें."</string> - <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"जब चाहें, बबल्स की सुविधा को कंट्रोल करें"</string> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"जब चाहें, बबल्स को कंट्रोल करें"</string> <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"किसी ऐप्लिकेशन और बातचीत के लिए बबल की सुविधा को मैनेज करने के लिए यहां टैप करें"</string> <string name="notification_bubble_title" msgid="6082910224488253378">"बबल"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"मैनेज करें"</string> @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"मेन्यू बंद करें"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"मेन्यू खोलें"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"स्क्रीन को बड़ा करें"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"साइज़ बदलें"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ऐप्लिकेशन को यहां मूव नहीं किया जा सकता"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"इमर्सिव"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"वापस लाएं"</string> diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml index bf756f63395b..28bab79042a0 100644 --- a/libs/WindowManager/Shell/res/values-hr/strings.xml +++ b/libs/WindowManager/Shell/res/values-hr/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Zatvorite izbornik"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvaranje izbornika"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimalno povećaj zaslon"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Promijeni veličinu"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacija se ne može premjestiti ovdje"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Interaktivno"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Vrati"</string> diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml index b02be18cb6f2..1afb57d8c80a 100644 --- a/libs/WindowManager/Shell/res/values-hu/strings.xml +++ b/libs/WindowManager/Shell/res/values-hu/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Menü bezárása"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menü megnyitása"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Képernyő méretének maximalizálása"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Átméretezés"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Az alkalmazás nem helyezhető át ide"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Magával ragadó"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Visszaállítás"</string> diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml index 59a95f0c1393..7266942434c0 100644 --- a/libs/WindowManager/Shell/res/values-hy/strings.xml +++ b/libs/WindowManager/Shell/res/values-hy/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Փակել ընտրացանկը"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Բացել ընտրացանկը"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ծավալել էկրանը"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Փոխել չափը"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Հավելվածը հնարավոր չէ տեղափոխել այստեղ"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Ներկայության էֆեկտով"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Վերականգնել"</string> diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml index baa1d0ec6bae..1197413553db 100644 --- a/libs/WindowManager/Shell/res/values-in/strings.xml +++ b/libs/WindowManager/Shell/res/values-in/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Tutup Menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Buka Menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Perbesar Layar"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ubah ukuran"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikasi tidak dapat dipindahkan ke sini"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Imersif"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Pulihkan"</string> diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml index c3ad5d66e17a..9646cb375f2f 100644 --- a/libs/WindowManager/Shell/res/values-is/strings.xml +++ b/libs/WindowManager/Shell/res/values-is/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Loka valmynd"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Opna valmynd"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Stækka skjá"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Breyta stærð"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ekki er hægt að færa forritið hingað"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Umlykjandi"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Endurheimta"</string> diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml index b75c041afdab..c3f6b3b49d9f 100644 --- a/libs/WindowManager/Shell/res/values-it/strings.xml +++ b/libs/WindowManager/Shell/res/values-it/strings.xml @@ -79,12 +79,12 @@ <string name="bubbles_user_education_description" msgid="4215862563054175407">"Le nuove conversazioni vengono mostrate come icone mobili o bolle. Tocca per aprire la bolla. Trascinala per spostarla."</string> <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Controlla le bolle quando vuoi"</string> <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Tocca Gestisci per disattivare le bolle dall\'app"</string> - <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string> + <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Ok"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Nessuna bolla recente"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Le bolle recenti e ignorate appariranno qui"</string> <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chatta utilizzando le bolle"</string> <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Le nuove conversazioni vengono visualizzate sotto forma di icone in un angolo inferiore dello schermo. Tocca per espanderle o trascina per chiuderle."</string> - <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Gestisci le bolle in qualsiasi momento"</string> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Controlla le bolle quando vuoi"</string> <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tocca qui per gestire le app e le conversazioni per cui mostrare le bolle"</string> <string name="notification_bubble_title" msgid="6082910224488253378">"Fumetto"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Gestisci"</string> @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Chiudi il menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Apri il menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Massimizza schermo"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ridimensiona"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Impossibile spostare l\'app qui"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersivo"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Ripristina"</string> diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml index 5f37590298de..6f18eda13caf 100644 --- a/libs/WindowManager/Shell/res/values-iw/strings.xml +++ b/libs/WindowManager/Shell/res/values-iw/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"סגירת התפריט"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"פתיחת התפריט"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"הגדלת המסך"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"שינוי הגודל"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"לא ניתן להעביר את האפליקציה לכאן"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"סוחף"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"שחזור"</string> diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml index 2960a19cef0a..c955ecb4f508 100644 --- a/libs/WindowManager/Shell/res/values-ja/strings.xml +++ b/libs/WindowManager/Shell/res/values-ja/strings.xml @@ -80,7 +80,7 @@ <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"バブルはいつでも管理可能"</string> <string name="bubbles_user_education_manage" msgid="3460756219946517198">"このアプリからのバブルを OFF にするには、[管理] をタップしてください"</string> <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string> - <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"最近閉じたバブルはありません"</string> + <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"最近のバブルはありません"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"最近表示されたバブルや閉じたバブルが、ここに表示されます"</string> <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"チャットでバブルを使う"</string> <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"新しい会話がアイコンとして画面下部に表示されます。タップすると開き、ドラッグして閉じることができます。"</string> @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"メニューを閉じる"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"メニューを開く"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"画面の最大化"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"サイズ変更"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"アプリはここに移動できません"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"没入モード"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"復元"</string> diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml index 6420bf531f24..2c286d2644df 100644 --- a/libs/WindowManager/Shell/res/values-ka/strings.xml +++ b/libs/WindowManager/Shell/res/values-ka/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"მენიუს დახურვა"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"მენიუს გახსნა"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"აპლიკაციის გაშლა სრულ ეკრანზე"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ზომის შეცვლა"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"აპის აქ გადატანა შეუძლებელია"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"იმერსიული"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"აღდგენა"</string> diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml index ef169537a899..58afb7fdd6c4 100644 --- a/libs/WindowManager/Shell/res/values-kk/strings.xml +++ b/libs/WindowManager/Shell/res/values-kk/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Мәзірді жабу"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Мәзірді ашу"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Экранды ұлғайту"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Өлшемін өзгерту"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Қолданба бұл жерге қойылмайды."</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Әсерлі"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Қалпына келтіру"</string> diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml index a625201261b1..6abb66dc9ade 100644 --- a/libs/WindowManager/Shell/res/values-km/strings.xml +++ b/libs/WindowManager/Shell/res/values-km/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"បិទម៉ឺនុយ"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"បើកម៉ឺនុយ"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ពង្រីកអេក្រង់"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ប្ដូរទំហំ"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"មិនអាចផ្លាស់ទីកម្មវិធីមកទីនេះបានទេ"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ជក់ចិត្ត"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ស្ដារ"</string> diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml index b2bf3a50d505..1da093d666bb 100644 --- a/libs/WindowManager/Shell/res/values-kn/strings.xml +++ b/libs/WindowManager/Shell/res/values-kn/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"ಮೆನು ಮುಚ್ಚಿ"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ಮೆನು ತೆರೆಯಿರಿ"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ಸ್ಕ್ರೀನ್ ಅನ್ನು ಮ್ಯಾಕ್ಸಿಮೈಸ್ ಮಾಡಿ"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ಮರುಗಾತ್ರಗೊಳಿಸಿ"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ಆ್ಯಪ್ ಅನ್ನು ಇಲ್ಲಿಗೆ ಸರಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ಇಮ್ಮರ್ಸಿವ್"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ಮರುಸ್ಥಾಪಿಸಿ"</string> diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml index ad0368a57460..22f2e0632b46 100644 --- a/libs/WindowManager/Shell/res/values-ko/strings.xml +++ b/libs/WindowManager/Shell/res/values-ko/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"메뉴 닫기"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"메뉴 열기"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"화면 최대화"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"크기 조절"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"앱을 여기로 이동할 수 없음"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"몰입형"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"복원"</string> diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml index 0b4eb934ff99..86529a292cff 100644 --- a/libs/WindowManager/Shell/res/values-ky/strings.xml +++ b/libs/WindowManager/Shell/res/values-ky/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Менюну жабуу"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Менюну ачуу"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Экранды чоңойтуу"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Өлчөмүн өзгөртүү"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Колдонмону бул жерге жылдырууга болбойт"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Сүңгүтүүчү"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Калыбына келтирүү"</string> diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml index 9710e69a0418..fab0cb245cfd 100644 --- a/libs/WindowManager/Shell/res/values-lo/strings.xml +++ b/libs/WindowManager/Shell/res/values-lo/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"ປິດເມນູ"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ເປີດເມນູ"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ປັບຈໍໃຫຍ່ສຸດ"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ປັບຂະໜາດ"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ບໍ່ສາມາດຍ້າຍແອັບມາບ່ອນນີ້ໄດ້"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ສົມຈິງ"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ກູ້ຄືນ"</string> diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml index 5bfb8e34fd53..d036e35e9cbf 100644 --- a/libs/WindowManager/Shell/res/values-lt/strings.xml +++ b/libs/WindowManager/Shell/res/values-lt/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Uždaryti meniu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Atidaryti meniu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Išskleisti ekraną"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Pakeisti dydį"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Programos negalima perkelti čia"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Įtraukiantis"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Atkurti"</string> diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml index 07342000ca66..dc1f7b04c21a 100644 --- a/libs/WindowManager/Shell/res/values-lv/strings.xml +++ b/libs/WindowManager/Shell/res/values-lv/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Aizvērt izvēlni"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Atvērt izvēlni"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimizēt ekrānu"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Mainīt lielumu"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Lietotni nevar pārvietot šeit."</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Iekļaujoši"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Atjaunot"</string> diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml index 2c4503c23e80..3da196b52984 100644 --- a/libs/WindowManager/Shell/res/values-mk/strings.xml +++ b/libs/WindowManager/Shell/res/values-mk/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Затворете го менито"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Отвори го менито"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Максимизирај го екранот"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Промени ја големината"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Апликацијата не може да се премести овде"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Реалистично"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Врати"</string> diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml index 7e20ee1fc36b..c2e747c590d8 100644 --- a/libs/WindowManager/Shell/res/values-ml/strings.xml +++ b/libs/WindowManager/Shell/res/values-ml/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"മെനു അടയ്ക്കുക"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"മെനു തുറക്കുക"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"സ്ക്രീൻ വലുതാക്കുക"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"വലുപ്പം മാറ്റുക"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ആപ്പ് ഇവിടേക്ക് നീക്കാനാകില്ല"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ഇമേഴ്സീവ്"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"പുനഃസ്ഥാപിക്കുക"</string> diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml index ef24222cdef1..045fc2101481 100644 --- a/libs/WindowManager/Shell/res/values-mn/strings.xml +++ b/libs/WindowManager/Shell/res/values-mn/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Цэсийг хаах"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Цэсийг нээх"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Дэлгэцийг томруулах"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Хэмжээг өөрчлөх"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Аппыг ийш зөөх боломжгүй"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Бодит мэт"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Сэргээх"</string> diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml index e665639ca217..01398d5c4d3a 100644 --- a/libs/WindowManager/Shell/res/values-mr/strings.xml +++ b/libs/WindowManager/Shell/res/values-mr/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"मेनू बंद करा"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"मेनू उघडा"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"स्क्रीन मोठी करा"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"आकार बदला"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"अॅप इथे हलवू शकत नाही"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"इमर्सिव्ह"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"रिस्टोअर करा"</string> diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml index 5de79c2c9afc..3d687dcbd800 100644 --- a/libs/WindowManager/Shell/res/values-ms/strings.xml +++ b/libs/WindowManager/Shell/res/values-ms/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Tutup Menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Buka Menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimumkan Skrin"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ubah saiz"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Apl tidak boleh dialihkan ke sini"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Mengasyikkan"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Pulihkan"</string> diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml index e6d355379f9a..08a935f75355 100644 --- a/libs/WindowManager/Shell/res/values-my/strings.xml +++ b/libs/WindowManager/Shell/res/values-my/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"မီနူး ပိတ်ရန်"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"မီနူး ဖွင့်ရန်"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"စခရင်ကို ချဲ့မည်"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"အရွယ်ပြင်ရန်"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"အက်ပ်ကို ဤနေရာသို့ ရွှေ့၍မရပါ"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"သုံးဘက်မြင်"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ပြန်ပြောင်းရန်"</string> diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml index bde7ec67d0cb..196507866aaf 100644 --- a/libs/WindowManager/Shell/res/values-nb/strings.xml +++ b/libs/WindowManager/Shell/res/values-nb/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Lukk menyen"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Åpne menyen"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimer skjermen"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Endre størrelse"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Appen kan ikke flyttes hit"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Oppslukende"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Gjenopprett"</string> diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml index a40e3adede16..10e933245e60 100644 --- a/libs/WindowManager/Shell/res/values-ne/strings.xml +++ b/libs/WindowManager/Shell/res/values-ne/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"मेनु बन्द गर्नुहोस्"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"मेनु खोल्नुहोस्"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"स्क्रिन ठुलो बनाउनुहोस्"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"आकार बदल्नुहोस्"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"एप सारेर यहाँ ल्याउन सकिएन"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"इमर्सिभ"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"रिस्टोर गर्नुहोस्"</string> diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml index b28b69080051..fc8451522f21 100644 --- a/libs/WindowManager/Shell/res/values-nl/strings.xml +++ b/libs/WindowManager/Shell/res/values-nl/strings.xml @@ -84,7 +84,7 @@ <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Recente bubbels en gesloten bubbels zie je hier"</string> <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"Chatten met bubbels"</string> <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"Nieuwe gesprekken verschijnen als iconen in een benedenhoek van je scherm. Tik om ze uit te vouwen of sleep om ze te sluiten."</string> - <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Bubbels beheren wanneer je wilt"</string> + <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"Bubbels beheren"</string> <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"Tik hier om te beheren welke apps en gesprekken als bubbel kunnen worden getoond"</string> <string name="notification_bubble_title" msgid="6082910224488253378">"Bubbel"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Beheren"</string> @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Menu sluiten"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menu openen"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Scherm maximaliseren"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Formaat aanpassen"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Kan de app niet hierheen verplaatsen"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersief"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Herstellen"</string> diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml index 842e3def41f1..be01593fda39 100644 --- a/libs/WindowManager/Shell/res/values-or/strings.xml +++ b/libs/WindowManager/Shell/res/values-or/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"ମେନୁ ବନ୍ଦ କରନ୍ତୁ"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ମେନୁ ଖୋଲନ୍ତୁ"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ସ୍କ୍ରିନକୁ ବଡ଼ କରନ୍ତୁ"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ରିସାଇଜ କରନ୍ତୁ"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ଆପକୁ ଏଠାକୁ ମୁଭ କରାଯାଇପାରିବ ନାହିଁ"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ଇମର୍ସିଭ"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ରିଷ୍ଟୋର କରନ୍ତୁ"</string> diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml index e1c804a56289..fb4c83e352f7 100644 --- a/libs/WindowManager/Shell/res/values-pa/strings.xml +++ b/libs/WindowManager/Shell/res/values-pa/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"ਮੀਨੂ ਬੰਦ ਕਰੋ"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ਮੀਨੂ ਖੋਲ੍ਹੋ"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ਸਕ੍ਰੀਨ ਦਾ ਆਕਾਰ ਵਧਾਓ"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ਆਕਾਰ ਬਦਲੋ"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ਐਪ ਨੂੰ ਇੱਥੇ ਨਹੀਂ ਲਿਜਾਇਆ ਜਾ ਸਕਦਾ"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ਇਮਰਸਿਵ"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ਮੁੜ-ਬਹਾਲ ਕਰੋ"</string> diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml index e82916b89033..fa0e7c318f7e 100644 --- a/libs/WindowManager/Shell/res/values-pl/strings.xml +++ b/libs/WindowManager/Shell/res/values-pl/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Zamknij menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otwórz menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksymalizuj ekran"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Zmień rozmiar"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Nie można przenieść aplikacji tutaj"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Tryb immersyjny"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Przywróć"</string> diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml index 6d8c9ce2a72f..d9e5f8c77897 100644 --- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir o menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ampliar tela"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionar"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Não é possível mover o app para cá"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Imersivo"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string> diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml index e0e91e715ed0..28dc7b0d228e 100644 --- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar ecrã"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionar"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Não é possível mover a app para aqui"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Envolvente"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string> diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml index 6d8c9ce2a72f..d9e5f8c77897 100644 --- a/libs/WindowManager/Shell/res/values-pt/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir o menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ampliar tela"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionar"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Não é possível mover o app para cá"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Imersivo"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string> diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml index 5a381556207e..b63a8b3b05df 100644 --- a/libs/WindowManager/Shell/res/values-ro/strings.xml +++ b/libs/WindowManager/Shell/res/values-ro/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Închide meniul"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Deschide meniul"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizează fereastra"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Redimensionează"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplicația nu poate fi mutată aici"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Captivant"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restabilește"</string> diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml index d26eb532be28..709e90eb7fd9 100644 --- a/libs/WindowManager/Shell/res/values-ru/strings.xml +++ b/libs/WindowManager/Shell/res/values-ru/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Закрыть меню"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Открыть меню"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Развернуть на весь экран"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Изменить размер"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Приложение нельзя сюда переместить"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Погружение"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Восстановить"</string> diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml index 5bfa4c91bc6c..da1aa9d71c15 100644 --- a/libs/WindowManager/Shell/res/values-si/strings.xml +++ b/libs/WindowManager/Shell/res/values-si/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"මෙනුව වසන්න"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"මෙනුව විවෘත කරන්න"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"තිරය උපරිම කරන්න"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ප්රතිප්රමාණය කරන්න"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"යෙදුම මෙතැනට ගෙන යා නොහැක"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ගිලෙන සුළු"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ප්රතිසාධනය කරන්න"</string> diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml index cd20df5e1a1c..aa7799723993 100644 --- a/libs/WindowManager/Shell/res/values-sk/strings.xml +++ b/libs/WindowManager/Shell/res/values-sk/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Zavrieť ponuku"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otvoriť ponuku"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximalizovať obrazovku"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Zmeniť veľkosť"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikácia sa sem nedá presunúť"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Pútavé"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Obnoviť"</string> diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml index 063e47c80d5f..55452bd0e854 100644 --- a/libs/WindowManager/Shell/res/values-sl/strings.xml +++ b/libs/WindowManager/Shell/res/values-sl/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Zapri meni"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Odpri meni"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimiraj zaslon"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Spremeni velikost"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacije ni mogoče premakniti sem"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Poglobljeno"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Obnovi"</string> diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml index f2fb7da44807..0492b2f9a51f 100644 --- a/libs/WindowManager/Shell/res/values-sq/strings.xml +++ b/libs/WindowManager/Shell/res/values-sq/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Mbyll menynë"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Hap menynë"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimizo ekranin"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ndrysho përmasat"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacioni nuk mund të zhvendoset këtu"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Përfshirës"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restauro"</string> diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml index f74e460226b6..af8ac6898e83 100644 --- a/libs/WindowManager/Shell/res/values-sr/strings.xml +++ b/libs/WindowManager/Shell/res/values-sr/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Затворите мени"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Отворите мени"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Повећај екран"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Промени величину"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Апликација не може да се премести овде"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Имерзивне"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Врати"</string> diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml index 7a5549eb5f59..0c3c18c70040 100644 --- a/libs/WindowManager/Shell/res/values-sv/strings.xml +++ b/libs/WindowManager/Shell/res/values-sv/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Stäng menyn"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Öppna menyn"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximera skärmen"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ändra storlek"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Det går inte att flytta appen hit"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Uppslukande"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Återställ"</string> diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml index 128ba74fd80c..4f0a6ac93b55 100644 --- a/libs/WindowManager/Shell/res/values-sw/strings.xml +++ b/libs/WindowManager/Shell/res/values-sw/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Funga Menyu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Fungua Menyu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Panua Dirisha kwenye Skrini"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Badilisha ukubwa"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Imeshindwa kuhamishia programu hapa"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Shirikishi"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Rejesha"</string> diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml index 668efd5ebf82..5fca404d5614 100644 --- a/libs/WindowManager/Shell/res/values-ta/strings.xml +++ b/libs/WindowManager/Shell/res/values-ta/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"மெனுவை மூடும்"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"மெனுவைத் திறக்கும்"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"திரையைப் பெரிதாக்கு"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"அளவை மாற்று"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ஆப்ஸை இங்கே நகர்த்த முடியாது"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ஈடுபட வைக்கும்"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"மீட்டெடுக்கும்"</string> diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml index a3827bb3703a..9e0f107f7e55 100644 --- a/libs/WindowManager/Shell/res/values-te/strings.xml +++ b/libs/WindowManager/Shell/res/values-te/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"మెనూను మూసివేయండి"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"మెనూను తెరవండి"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"స్క్రీన్ సైజ్ను పెంచండి"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"సైజ్ మార్చండి"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"యాప్ను ఇక్కడకి తరలించడం సాధ్యం కాదు"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"లీనమయ్యే"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"రీస్టోర్ చేయండి"</string> diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml index fe7556156e78..7be7373e03a9 100644 --- a/libs/WindowManager/Shell/res/values-th/strings.xml +++ b/libs/WindowManager/Shell/res/values-th/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"ปิดเมนู"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"เปิดเมนู"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ขยายหน้าจอให้ใหญ่สุด"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"ปรับขนาด"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ย้ายแอปมาที่นี่ไม่ได้"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"สมจริง"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"คืนค่า"</string> diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml index d9fb13bb0b6f..22b0174c0252 100644 --- a/libs/WindowManager/Shell/res/values-tl/strings.xml +++ b/libs/WindowManager/Shell/res/values-tl/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Isara ang Menu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Buksan ang Menu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"I-maximize ang Screen"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"I-resize"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Hindi mailipat dito ang app"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"I-restore"</string> diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml index e6d900af0cf8..79d64ba1f117 100644 --- a/libs/WindowManager/Shell/res/values-tr/strings.xml +++ b/libs/WindowManager/Shell/res/values-tr/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Menüyü kapat"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menüyü aç"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranı Büyüt"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Yeniden boyutlandır"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Uygulama buraya taşınamıyor"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Etkileyici"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Geri yükle"</string> diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml index ea599952a0e7..aeba9824d3f4 100644 --- a/libs/WindowManager/Shell/res/values-uk/strings.xml +++ b/libs/WindowManager/Shell/res/values-uk/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Закрити меню"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Відкрити меню"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Розгорнути екран"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Змінити розмір"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Сюди не можна перемістити додаток"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Реалістичність"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Відновити"</string> diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml index 6749629f2c3e..cf6fb8926f55 100644 --- a/libs/WindowManager/Shell/res/values-ur/strings.xml +++ b/libs/WindowManager/Shell/res/values-ur/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"مینیو بند کریں"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"مینو کھولیں"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"اسکرین کو بڑا کریں"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"سائز تبدیل کریں"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ایپ کو یہاں منتقل نہیں کیا جا سکتا"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"عمیق"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"بحال کریں"</string> diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml index 3417fef84de5..c64b84373b17 100644 --- a/libs/WindowManager/Shell/res/values-uz/strings.xml +++ b/libs/WindowManager/Shell/res/values-uz/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Menyuni yopish"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menyuni ochish"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranni yoyish"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Oʻlchamini oʻzgartirish"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ilova bu yerga surilmaydi"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersiv"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Tiklash"</string> diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml index 559bff8711bf..2a7dae4cbaef 100644 --- a/libs/WindowManager/Shell/res/values-vi/strings.xml +++ b/libs/WindowManager/Shell/res/values-vi/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Đóng trình đơn"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Mở Trình đơn"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Mở rộng màn hình"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Đổi kích thước"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Không di chuyển được ứng dụng đến đây"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Hiển thị tối đa"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Khôi phục"</string> diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml index f327653bd5d9..e45fbba6e196 100644 --- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"关闭菜单"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"打开菜单"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"最大化屏幕"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"调整大小"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"无法将应用移至此处"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"沉浸式"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"恢复"</string> diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml index e1ecd44eb69f..d5e106394720 100644 --- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml @@ -125,7 +125,7 @@ <string name="select_text" msgid="5139083974039906583">"選取"</string> <string name="screenshot_text" msgid="1477704010087786671">"螢幕截圖"</string> <string name="open_in_browser_text" msgid="9181692926376072904">"在瀏覽器中開啟"</string> - <string name="open_in_app_text" msgid="2874590745116268525">"在應用程式中開啟"</string> + <string name="open_in_app_text" msgid="2874590745116268525">"喺應用程式入面打開"</string> <string name="new_window_text" msgid="6318648868380652280">"新視窗"</string> <string name="manage_windows_text" msgid="5567366688493093920">"管理視窗"</string> <string name="change_aspect_ratio_text" msgid="9104456064548212806">"變更長寬比"</string> @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"關閉選單"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"打開選單"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"畫面最大化"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"調整大小"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"應用程式無法移至這裡"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"身歷其境"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"還原"</string> diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml index 1b8f704cef29..a0357e12b722 100644 --- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"關閉選單"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"開啟選單"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"畫面最大化"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"調整大小"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"應用程式無法移至此處"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"沉浸"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"還原"</string> diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml index 604031733ed2..810b6c82e09d 100644 --- a/libs/WindowManager/Shell/res/values-zu/strings.xml +++ b/libs/WindowManager/Shell/res/values-zu/strings.xml @@ -133,8 +133,7 @@ <string name="collapse_menu_text" msgid="7515008122450342029">"Vala Imenyu"</string> <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Vula Imenyu"</string> <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Khulisa Isikrini Sifike Ekugcineni"</string> - <!-- no translation found for desktop_mode_maximize_menu_snap_text (5673738963174074006) --> - <skip /> + <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Shintsha usayizi"</string> <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"I-app ayikwazi ukuhanjiswa lapha"</string> <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Okugxilile"</string> <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Buyisela"</string> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 21ec84d9bc0a..9e2d23b41556 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -266,6 +266,8 @@ <dimen name="bubble_bar_expanded_view_handle_height">4dp</dimen> <!-- Width of the expanded bubble bar view shown when the bubble is expanded. --> <dimen name="bubble_bar_expanded_view_width">412dp</dimen> + <!-- Offset of the expanded view when it starts sliding in as part of the switch animation --> + <dimen name="bubble_bar_expanded_view_switch_offset">48dp</dimen> <!-- Minimum width of the bubble bar manage menu. --> <dimen name="bubble_bar_manage_menu_min_width">200dp</dimen> <!-- Size of the dismiss icon in the bubble bar manage menu. --> diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java index 8f7a2e5a6789..01d2201a5a0c 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java @@ -20,29 +20,6 @@ package com.android.wm.shell.shared; * General shell-related constants that are shared with users of the library. */ public class ShellSharedConstants { - // See IPip.aidl - public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip"; - // See IBubbles.aidl - public static final String KEY_EXTRA_SHELL_BUBBLES = "extra_shell_bubbles"; - // See ISplitScreen.aidl - public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen"; - // See IOneHanded.aidl - public static final String KEY_EXTRA_SHELL_ONE_HANDED = "extra_shell_one_handed"; - // See IShellTransitions.aidl - public static final String KEY_EXTRA_SHELL_SHELL_TRANSITIONS = - "extra_shell_shell_transitions"; - // See IStartingWindow.aidl - public static final String KEY_EXTRA_SHELL_STARTING_WINDOW = - "extra_shell_starting_window"; - // See IRecentTasks.aidl - public static final String KEY_EXTRA_SHELL_RECENT_TASKS = "extra_shell_recent_tasks"; - // See IBackAnimation.aidl - public static final String KEY_EXTRA_SHELL_BACK_ANIMATION = "extra_shell_back_animation"; - // See IDesktopMode.aidl - public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode"; - // See IDragAndDrop.aidl - public static final String KEY_EXTRA_SHELL_DRAG_AND_DROP = "extra_shell_drag_and_drop"; - // See IRecentsAnimationController.aidl public static final String KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION = "extra_shell_can_hand_off_animation"; } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java index 04c17e54d11f..a5205ee24d05 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java @@ -162,6 +162,21 @@ public class DesktopModeStatus { } /** + * Return the maximum size of the window decoration surface control view host pool, or zero if + * there should be no pooling. + */ + public static int getWindowDecorScvhPoolSize(@NonNull Context context) { + if (!Flags.enableDesktopWindowingScvhCacheBugFix()) return 0; + final int maxTaskLimit = getMaxTaskLimit(context); + if (maxTaskLimit > 0) { + return maxTaskLimit; + } + // TODO: b/368032552 - task limit equal to 0 means unlimited. Figure out what the pool + // size should be in that case. + return 0; + } + + /** * Return {@code true} if the current device supports desktop mode. */ @VisibleForTesting diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java index 5a2a723cacee..f9f43bc8dfae 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java @@ -85,6 +85,9 @@ public class SplitScreenConstants { public @interface SplitIndex { } + /** Signifies that user is currently not in split screen. */ + public static final int NOT_IN_SPLIT = -1; + /** * A snap target for two apps, where the split is 33-66. With FLAG_ENABLE_FLEXIBLE_SPLIT, * only used on tablets. @@ -152,6 +155,23 @@ public class SplitScreenConstants { public @interface PersistentSnapPosition {} /** + * These are all the valid "states" that split screen can be in. It's the set of + * {@link PersistentSnapPosition} + {@link #NOT_IN_SPLIT}. + */ + @IntDef(value = { + NOT_IN_SPLIT, + SNAP_TO_2_33_66, + SNAP_TO_2_50_50, + SNAP_TO_2_66_33, + SNAP_TO_2_90_10, + SNAP_TO_2_10_90, + SNAP_TO_3_33_33_33, + SNAP_TO_3_45_45_10, + SNAP_TO_3_10_45_45, + }) + public @interface SplitScreenState {} + + /** * Checks if the snapPosition in question is a {@link PersistentSnapPosition}. */ public static boolean isPersistentSnapPosition(@SnapPosition int snapPosition) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java index d2cef4baf798..f269b3831aab 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java @@ -268,10 +268,7 @@ class ActivityEmbeddingAnimationRunner { final Animation animation = animationProvider.get(info, change, openingWholeScreenBounds); if (shouldUseJumpCutForAnimation(animation)) { - if (Flags.activityEmbeddingAnimationCustomizationFlag()) { - return new ArrayList<>(); - } - continue; + return new ArrayList<>(); } final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter( info, change, animation, openingWholeScreenBounds); @@ -296,10 +293,7 @@ class ActivityEmbeddingAnimationRunner { final Animation animation = animationProvider.get(info, change, closingWholeScreenBounds); if (shouldUseJumpCutForAnimation(animation)) { - if (Flags.activityEmbeddingAnimationCustomizationFlag()) { - return new ArrayList<>(); - } - continue; + return new ArrayList<>(); } final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter( info, change, animation, closingWholeScreenBounds); @@ -455,11 +449,9 @@ class ActivityEmbeddingAnimationRunner { final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(info, change, parentBounds); // Jump cut if either animation has zero for duration. - if (Flags.activityEmbeddingAnimationCustomizationFlag()) { - for (Animation animation : animations) { - if (shouldUseJumpCutForAnimation(animation)) { - return new ArrayList<>(); - } + for (Animation animation : animations) { + if (shouldUseJumpCutForAnimation(animation)) { + return new ArrayList<>(); } } // Keep track as we might need to add background color for the animation. @@ -516,10 +508,8 @@ class ActivityEmbeddingAnimationRunner { mAnimationSpec.createChangeBoundsOpenAnimation(info, change, parentBounds); shouldShowBackgroundColor = false; } - if (Flags.activityEmbeddingAnimationCustomizationFlag()) { - if (shouldUseJumpCutForAnimation(animation)) { - return new ArrayList<>(); - } + if (shouldUseJumpCutForAnimation(animation)) { + return new ArrayList<>(); } adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change, TransitionUtil.getRootFor(change, info))); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java index 3046307702c2..77799e99607b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java @@ -96,11 +96,9 @@ class ActivityEmbeddingAnimationSpec { @NonNull Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) { - if (Flags.activityEmbeddingAnimationCustomizationFlag()) { - final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE); - if (customAnimation != null) { - return customAnimation; - } + final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE); + if (customAnimation != null) { + return customAnimation; } // Use end bounds for opening. final Rect bounds = change.getEndAbsBounds(); @@ -130,11 +128,9 @@ class ActivityEmbeddingAnimationSpec { @NonNull Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) { - if (Flags.activityEmbeddingAnimationCustomizationFlag()) { - final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE); - if (customAnimation != null) { - return customAnimation; - } + final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE); + if (customAnimation != null) { + return customAnimation; } // Use start bounds for closing. final Rect bounds = change.getStartAbsBounds(); @@ -168,14 +164,12 @@ class ActivityEmbeddingAnimationSpec { @NonNull Animation[] createChangeBoundsChangeAnimations(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) { - if (Flags.activityEmbeddingAnimationCustomizationFlag()) { - // TODO(b/293658614): Support more complicated animations that may need more than a noop - // animation as the start leash. - final Animation noopAnimation = createNoopAnimation(change); - final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE); - if (customAnimation != null) { - return new Animation[]{noopAnimation, customAnimation}; - } + // TODO(b/293658614): Support more complicated animations that may need more than a noop + // animation as the start leash. + final Animation noopAnimation = createNoopAnimation(change); + final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE); + if (customAnimation != null) { + return new Animation[]{noopAnimation, customAnimation}; } // Both start bounds and end bounds are in screen coordinates. We will post translate // to the local coordinates in ActivityEmbeddingAnimationAdapter#onAnimationUpdate @@ -320,13 +314,9 @@ class ActivityEmbeddingAnimationSpec { } final Animation anim; - if (Flags.activityEmbeddingAnimationCustomizationFlag()) { - // TODO(b/293658614): Consider allowing custom animations from non-default packages. - // Enforce limiting to animations from the default "android" package for now. - anim = mTransitionAnimation.loadDefaultAnimationRes(resId); - } else { - anim = mTransitionAnimation.loadAnimationRes(options.getPackageName(), resId); - } + // TODO(b/293658614): Consider allowing custom animations from non-default packages. + // Enforce limiting to animations from the default "android" package for now. + anim = mTransitionAnimation.loadDefaultAnimationRes(resId); if (anim != null) { return anim; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/OWNERS new file mode 100644 index 000000000000..84596b015209 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/OWNERS @@ -0,0 +1,6 @@ +# WM shell sub-module automotive owners + +winsonc@google.com +stenning@google.com +gauravbhola@google.com +xiangw@google.com
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index e9cfd9bc2209..60a52a808a54 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -32,7 +32,6 @@ import static com.android.window.flags.Flags.migratePredictiveBackTransition; import static com.android.window.flags.Flags.predictiveBackSystemAnims; import static com.android.window.flags.Flags.unifyBackNavigationTransition; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; -import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION; import android.annotation.NonNull; import android.annotation.Nullable; @@ -215,6 +214,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Navigation window gone."); setTriggerBack(false); + // Trigger close transition if necessary. + if (Flags.migratePredictiveBackTransition()) { + mBackTransitionHandler.onAnimationFinished(); + } resetTouchTracker(); // Don't wait for animation start mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable); @@ -308,7 +311,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler); updateEnableAnimationFromFlags(); createAdapter(); - mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION, + mShellController.addExternalInterface(IBackAnimation.DESCRIPTOR, this::createExternalInterface, this); mShellCommandHandler.addDumpCallback(this::dump, this); mShellController.addConfigurationChangeListener(this); @@ -552,6 +555,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont // start animation immediately for non-gestural sources (without ACTION_MOVE // events) mThresholdCrossed = true; + mPointersPilfered = true; onGestureStarted(touchX, touchY, swipeEdge); mShouldStartOnNextMoveEvent = false; } else { @@ -731,6 +735,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont callback.onBackStarted(backEvent); if (mBackTransitionHandler.canHandOffAnimation()) { callback.setHandoffHandler(mHandoffHandler); + } else { + callback.setHandoffHandler(null); } mOnBackStartDispatched = true; } catch (RemoteException e) { @@ -1273,14 +1279,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @NonNull SurfaceControl.Transaction st, @NonNull SurfaceControl.Transaction ft, @NonNull Transitions.TransitionFinishCallback finishCallback) { - final boolean isPrepareTransition = - info.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION; - if (isPrepareTransition) { - if (checkTakeoverFlags()) { - mTakeoverHandler = mTransitions.getHandlerForTakeover(transition, info); - } - kickStartAnimation(); - } // Both mShellExecutor and Transitions#mMainExecutor are ShellMainThread, so we don't // need to post to ShellExecutor when called. if (info.getType() == WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION) { @@ -1308,7 +1306,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont // animation never start, consume directly applyAndFinish(st, ft, finishCallback); return true; - } else if (mClosePrepareTransition == null && isPrepareTransition) { + } else if (mClosePrepareTransition == null + && info.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION) { // Gesture animation was cancelled before prepare transition ready, create // the close prepare transition createClosePrepareTransition(); @@ -1316,6 +1315,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } if (handlePrepareTransition(info, st, ft, finishCallback)) { + if (checkTakeoverFlags()) { + mTakeoverHandler = mTransitions.getHandlerForTakeover(transition, info); + } + kickStartAnimation(); return true; } return handleCloseTransition(info, st, ft, finishCallback); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index b82496e45415..bec73a1500a7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -35,7 +35,6 @@ import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED; import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; -import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_BUBBLES; import android.annotation.BinderThread; import android.annotation.NonNull; @@ -274,8 +273,11 @@ public class BubbleController implements ConfigurationChangeListener, private final DragAndDropController mDragAndDropController; /** Used to send bubble events to launcher. */ private Bubbles.BubbleStateListener mBubbleStateListener; - /** Used to track previous navigation mode to detect switch to buttons navigation. */ - private boolean mIsPrevNavModeGestures; + /** + * Used to track previous navigation mode to detect switch to buttons navigation. Set to + * true to switch the bubble bar to the opposite side for 3 nav buttons mode on device boot. + */ + private boolean mIsPrevNavModeGestures = true; /** Used to send updates to the views from {@link #mBubbleDataListener}. */ private BubbleViewCallback mBubbleViewCallback; @@ -357,7 +359,6 @@ public class BubbleController implements ConfigurationChangeListener, } }; mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(this); - mIsPrevNavModeGestures = ContextUtils.isGestureNavigationMode(mContext); } private void registerOneHandedState(OneHandedController oneHanded) { @@ -520,7 +521,7 @@ public class BubbleController implements ConfigurationChangeListener, } mShellController.addConfigurationChangeListener(this); - mShellController.addExternalInterface(KEY_EXTRA_SHELL_BUBBLES, + mShellController.addExternalInterface(IBubbles.DESCRIPTOR, this::createExternalInterface, this); mShellCommandHandler.addDumpCallback(this::dump, this); } @@ -593,9 +594,9 @@ public class BubbleController implements ConfigurationChangeListener, if (mBubbleStateListener != null) { boolean isCurrentNavModeGestures = ContextUtils.isGestureNavigationMode(mContext); if (mIsPrevNavModeGestures && !isCurrentNavModeGestures) { - BubbleBarLocation navButtonsLocation = ContextUtils.isRtl(mContext) + BubbleBarLocation bubbleBarLocation = ContextUtils.isRtl(mContext) ? BubbleBarLocation.RIGHT : BubbleBarLocation.LEFT; - mBubblePositioner.setBubbleBarLocation(navButtonsLocation); + mBubblePositioner.setBubbleBarLocation(bubbleBarLocation); } mIsPrevNavModeGestures = isCurrentNavModeGestures; BubbleBarUpdate update = mBubbleData.getInitialStateForBubbleBar(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index 0fd4206c0545..de85d9af127d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -163,8 +163,11 @@ public class BubblePositioner { mExpandedViewLargeScreenWidth = (int) (bounds.width() * EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT); } else { - mExpandedViewLargeScreenWidth = - res.getDimensionPixelSize(R.dimen.bubble_expanded_view_largescreen_width); + int expandedViewLargeScreenSpacing = res.getDimensionPixelSize( + R.dimen.bubble_expanded_view_largescreen_landscape_padding); + mExpandedViewLargeScreenWidth = Math.min( + res.getDimensionPixelSize(R.dimen.bubble_expanded_view_largescreen_width), + bounds.width() - expandedViewLargeScreenSpacing * 2); } if (mDeviceConfig.isLargeScreen()) { if (mDeviceConfig.isSmallTablet()) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java index a313bd004a51..4d7c7fad53f8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java @@ -16,6 +16,7 @@ package com.android.wm.shell.bubbles.bar; import static android.view.View.ALPHA; +import static android.view.View.INVISIBLE; import static android.view.View.SCALE_X; import static android.view.View.SCALE_Y; import static android.view.View.TRANSLATION_X; @@ -25,6 +26,7 @@ import static android.view.View.X; import static android.view.View.Y; import static com.android.wm.shell.bubbles.bar.BubbleBarExpandedView.CORNER_RADIUS; +import static com.android.wm.shell.bubbles.bar.BubbleBarExpandedView.TASK_VIEW_ALPHA; import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED; import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED_DECELERATE; @@ -32,7 +34,6 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Point; import android.graphics.Rect; @@ -42,11 +43,12 @@ import android.widget.FrameLayout; import androidx.annotation.Nullable; +import com.android.app.animation.Interpolators; +import com.android.wm.shell.R; import com.android.wm.shell.bubbles.BubbleOverflow; import com.android.wm.shell.bubbles.BubblePositioner; import com.android.wm.shell.bubbles.BubbleViewProvider; import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix; -import com.android.wm.shell.shared.animation.Interpolators; import com.android.wm.shell.shared.animation.PhysicsAnimator; import com.android.wm.shell.shared.magnetictarget.MagnetizedObject.MagneticTarget; @@ -59,7 +61,7 @@ public class BubbleBarAnimationHelper { private static final float EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT = 0.1f; private static final float EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT = .75f; - private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150; + private static final int EXPANDED_VIEW_EXPAND_ALPHA_DURATION = 150; private static final int EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION = 400; private static final int EXPANDED_VIEW_ANIMATE_TO_REST_DURATION = 400; private static final int EXPANDED_VIEW_DISMISS_DURATION = 250; @@ -72,6 +74,17 @@ public class BubbleBarAnimationHelper { private static final float DISMISS_VIEW_SCALE = 1.25f; private static final int HANDLE_ALPHA_ANIMATION_DURATION = 100; + private static final float SWITCH_OUT_SCALE = 0.97f; + private static final long SWITCH_OUT_SCALE_DURATION = 200L; + private static final long SWITCH_OUT_ALPHA_DURATION = 100L; + private static final long SWITCH_OUT_HANDLE_ALPHA_DURATION = 50L; + private static final long SWITCH_IN_ANIM_DELAY = 50L; + private static final long SWITCH_IN_TX_DURATION = 350L; + private static final long SWITCH_IN_ALPHA_DURATION = 50L; + // Keep this handle alpha delay at least as long as alpha animation for both expanded views. + private static final long SWITCH_IN_HANDLE_ALPHA_DELAY = 150L; + private static final long SWITCH_IN_HANDLE_ALPHA_DURATION = 100L; + /** Spring config for the expanded view scale-in animation. */ private final PhysicsAnimator.SpringConfig mScaleInSpringConfig = new PhysicsAnimator.SpringConfig(300f, 0.9f); @@ -80,68 +93,24 @@ public class BubbleBarAnimationHelper { private final PhysicsAnimator.SpringConfig mScaleOutSpringConfig = new PhysicsAnimator.SpringConfig(900f, 1f); + private final int mSwitchAnimPositionOffset; + /** Matrix used to scale the expanded view container with a given pivot point. */ private final AnimatableScaleMatrix mExpandedViewContainerMatrix = new AnimatableScaleMatrix(); - /** Animator for animating the expanded view's alpha (including the TaskView inside it). */ - private final ValueAnimator mExpandedViewAlphaAnimator = ValueAnimator.ofFloat(0f, 1f); - @Nullable - private Animator mRunningDragAnimator; + private Animator mRunningAnimator; - private final Context mContext; - private final BubbleBarLayerView mLayerView; private final BubblePositioner mPositioner; private final int[] mTmpLocation = new int[2]; + // TODO(b/381936992): remove expanded bubble state from this helper class private BubbleViewProvider mExpandedBubble; - private boolean mIsExpanded = false; - public BubbleBarAnimationHelper(Context context, - BubbleBarLayerView bubbleBarLayerView, - BubblePositioner positioner) { - mContext = context; - mLayerView = bubbleBarLayerView; + public BubbleBarAnimationHelper(Context context, BubblePositioner positioner) { mPositioner = positioner; - - mExpandedViewAlphaAnimator.setDuration(EXPANDED_VIEW_ALPHA_ANIMATION_DURATION); - mExpandedViewAlphaAnimator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED); - mExpandedViewAlphaAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - BubbleBarExpandedView bbev = getExpandedView(); - if (bbev != null) { - // We need to be Z ordered on top in order for alpha animations to work. - bbev.setSurfaceZOrderedOnTop(true); - bbev.setAnimating(true); - } - } - - @Override - public void onAnimationEnd(Animator animation) { - BubbleBarExpandedView bbev = getExpandedView(); - if (bbev != null) { - // The surface needs to be Z ordered on top for alpha values to work on the - // TaskView, and if we're temporarily hidden, we are still on the screen - // with alpha = 0f until we animate back. Stay Z ordered on top so the alpha - // = 0f remains in effect. - if (mIsExpanded) { - bbev.setSurfaceZOrderedOnTop(false); - } - - bbev.setContentVisibility(mIsExpanded); - bbev.setAnimating(false); - } - } - }); - mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> { - BubbleBarExpandedView bbev = getExpandedView(); - if (bbev != null) { - float alpha = (float) valueAnimator.getAnimatedValue(); - bbev.setTaskViewAlpha(alpha); - bbev.setAlpha(alpha); - } - }); + mSwitchAnimPositionOffset = context.getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_switch_offset); } /** @@ -154,18 +123,11 @@ public class BubbleBarAnimationHelper { if (bbev == null) { return; } - mIsExpanded = true; mExpandedViewContainerMatrix.setScaleX(0f); mExpandedViewContainerMatrix.setScaleY(0f); - updateExpandedView(); - bbev.setAnimating(true); - bbev.setSurfaceZOrderedOnTop(true); - bbev.setContentVisibility(false); - bbev.setAlpha(0f); - bbev.setTaskViewAlpha(0f); - bbev.setVisibility(VISIBLE); + prepareForAnimateIn(bbev); setScaleFromBubbleBar(mExpandedViewContainerMatrix, 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT); @@ -173,7 +135,16 @@ public class BubbleBarAnimationHelper { bbev.setAnimationMatrix(mExpandedViewContainerMatrix); bbev.animateExpansionWhenTaskViewVisible(() -> { - mExpandedViewAlphaAnimator.start(); + ObjectAnimator alphaAnim = createAlphaAnimator(bbev, /* visible= */ true); + alphaAnim.setDuration(EXPANDED_VIEW_EXPAND_ALPHA_DURATION); + alphaAnim.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED); + alphaAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + bbev.setAnimating(false); + } + }); + startNewAnimator(alphaAnim); PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel(); PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) @@ -188,7 +159,7 @@ public class BubbleBarAnimationHelper { }) .withEndActions(() -> { bbev.setAnimationMatrix(null); - updateExpandedView(); + updateExpandedView(bbev); if (afterAnimation != null) { afterAnimation.run(); } @@ -197,13 +168,24 @@ public class BubbleBarAnimationHelper { }); } + private void prepareForAnimateIn(BubbleBarExpandedView bbev) { + bbev.setAnimating(true); + updateExpandedView(bbev); + // We need to be Z ordered on top in order for taskView alpha to work. + // It is also set when the alpha animation starts, but needs to be set here to too avoid + // flickers. + bbev.setSurfaceZOrderedOnTop(true); + bbev.setTaskViewAlpha(0f); + bbev.setContentVisibility(false); + bbev.setVisibility(VISIBLE); + } + /** * Collapses the currently expanded bubble. * * @param endRunnable a runnable to run at the end of the animation. */ public void animateCollapse(Runnable endRunnable) { - mIsExpanded = false; final BubbleBarExpandedView bbev = getExpandedView(); if (bbev == null) { Log.w(TAG, "Trying to animate collapse without a bubble"); @@ -214,6 +196,19 @@ public class BubbleBarAnimationHelper { setScaleFromBubbleBar(mExpandedViewContainerMatrix, 1f); + bbev.setAnimating(true); + + ObjectAnimator alphaAnim = createAlphaAnimator(bbev, /* visible= */ false); + alphaAnim.setDuration(EXPANDED_VIEW_EXPAND_ALPHA_DURATION); + alphaAnim.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED); + alphaAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + bbev.setAnimating(false); + } + }); + startNewAnimator(alphaAnim); + PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel(); PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) .spring(AnimatableScaleMatrix.SCALE_X, @@ -234,7 +229,6 @@ public class BubbleBarAnimationHelper { } }) .start(); - mExpandedViewAlphaAnimator.reverse(); } private void setScaleFromBubbleBar(AnimatableScaleMatrix matrix, float scale) { @@ -246,6 +240,142 @@ public class BubbleBarAnimationHelper { } /** + * Animate between two bubble views using a switch animation + * + * @param fromBubble bubble to hide + * @param toBubble bubble to show + * @param afterAnimation optional runnable after animation finishes + */ + public void animateSwitch(BubbleViewProvider fromBubble, BubbleViewProvider toBubble, + @Nullable Runnable afterAnimation) { + /* + * Switch animation + * + * |.....................fromBubble scale to 0.97.....................| + * |fromBubble handle alpha 0|....fromBubble alpha to 0.....| | + * 0-------------------------50-----------------------100---150--------200----------250--400 + * |..toBubble alpha to 1...| |toBubble handle alpha 1| | + * |................toBubble position +/-48 to 0...............| + */ + + mExpandedBubble = toBubble; + final BubbleBarExpandedView toBbev = toBubble.getBubbleBarExpandedView(); + final BubbleBarExpandedView fromBbev = fromBubble.getBubbleBarExpandedView(); + if (toBbev == null || fromBbev == null) { + return; + } + + fromBbev.setAnimating(true); + + prepareForAnimateIn(toBbev); + final float endTx = toBbev.getTranslationX(); + final float startTx = getSwitchAnimationInitialTx(endTx); + toBbev.setTranslationX(startTx); + toBbev.getHandleView().setAlpha(0f); + + toBbev.animateExpansionWhenTaskViewVisible(() -> { + AnimatorSet switchAnim = new AnimatorSet(); + switchAnim.playTogether(switchOutAnimator(fromBbev), switchInAnimator(toBbev, endTx)); + + switchAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (afterAnimation != null) { + afterAnimation.run(); + } + } + }); + startNewAnimator(switchAnim); + }); + } + + private float getSwitchAnimationInitialTx(float endTx) { + if (mPositioner.isBubbleBarOnLeft()) { + return endTx - mSwitchAnimPositionOffset; + } else { + return endTx + mSwitchAnimPositionOffset; + } + } + + private Animator switchOutAnimator(BubbleBarExpandedView bbev) { + setPivotToCenter(bbev); + AnimatorSet scaleAnim = new AnimatorSet(); + scaleAnim.playTogether( + ObjectAnimator.ofFloat(bbev, SCALE_X, SWITCH_OUT_SCALE), + ObjectAnimator.ofFloat(bbev, SCALE_Y, SWITCH_OUT_SCALE) + ); + scaleAnim.setInterpolator(Interpolators.ACCELERATE); + scaleAnim.setDuration(SWITCH_OUT_SCALE_DURATION); + + ObjectAnimator alphaAnim = createAlphaAnimator(bbev, /* visible= */ false); + alphaAnim.setStartDelay(SWITCH_OUT_HANDLE_ALPHA_DURATION); + alphaAnim.setDuration(SWITCH_OUT_ALPHA_DURATION); + + ObjectAnimator handleAlphaAnim = ObjectAnimator.ofFloat(bbev.getHandleView(), ALPHA, 0f) + .setDuration(SWITCH_OUT_HANDLE_ALPHA_DURATION); + + AnimatorSet animator = new AnimatorSet(); + animator.playTogether(scaleAnim, alphaAnim, handleAlphaAnim); + + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + bbev.setAnimating(false); + } + }); + return animator; + } + + private Animator switchInAnimator(BubbleBarExpandedView bbev, float restingTx) { + ObjectAnimator positionAnim = ObjectAnimator.ofFloat(bbev, TRANSLATION_X, restingTx); + positionAnim.setInterpolator(Interpolators.EMPHASIZED_DECELERATE); + positionAnim.setStartDelay(SWITCH_IN_ANIM_DELAY); + positionAnim.setDuration(SWITCH_IN_TX_DURATION); + + // Animate alpha directly to have finer control over surface z-ordering + ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(bbev, TASK_VIEW_ALPHA, 1f); + alphaAnim.setStartDelay(SWITCH_IN_ANIM_DELAY); + alphaAnim.setDuration(SWITCH_IN_ALPHA_DURATION); + alphaAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + bbev.setSurfaceZOrderedOnTop(true); + } + + @Override + public void onAnimationEnd(Animator animation) { + bbev.setContentVisibility(true); + // The outgoing expanded view alpha animation is still in progress. + // Do not reset the surface z-order as otherwise the outgoing expanded view is + // placed on top. + } + }); + + ObjectAnimator handleAlphaAnim = ObjectAnimator.ofFloat(bbev.getHandleView(), ALPHA, 1f); + handleAlphaAnim.setStartDelay(SWITCH_IN_HANDLE_ALPHA_DELAY); + handleAlphaAnim.setDuration(SWITCH_IN_HANDLE_ALPHA_DURATION); + handleAlphaAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + bbev.setSurfaceZOrderedOnTop(false); + bbev.setAnimating(false); + } + }); + + AnimatorSet animator = new AnimatorSet(); + animator.playTogether(positionAnim, alphaAnim, handleAlphaAnim); + + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + updateExpandedView(bbev); + } + }); + return animator; + } + + + /** * Animate the expanded bubble when it is being dragged */ public void animateStartDrag() { @@ -273,7 +403,7 @@ public class BubbleBarAnimationHelper { AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(contentAnim, handleAnim); animatorSet.addListener(new DragAnimatorListenerAdapter(bbev)); - startNewDragAnimation(animatorSet); + startNewAnimator(animatorSet); } /** @@ -282,7 +412,6 @@ public class BubbleBarAnimationHelper { * @param endRunnable a runnable to run at the end of the animation */ public void animateDismiss(Runnable endRunnable) { - mIsExpanded = false; final BubbleBarExpandedView bbev = getExpandedView(); if (bbev == null) { Log.w(TAG, "Trying to animate dismiss without a bubble"); @@ -335,7 +464,7 @@ public class BubbleBarAnimationHelper { bbev.setDragging(false); } }); - startNewDragAnimation(animatorSet); + startNewAnimator(animatorSet); } /** @@ -409,7 +538,7 @@ public class BubbleBarAnimationHelper { } } }); - startNewDragAnimation(animatorSet); + startNewAnimator(animatorSet); } /** @@ -435,7 +564,7 @@ public class BubbleBarAnimationHelper { animatorSet.setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION).setInterpolator( EMPHASIZED_DECELERATE); animatorSet.addListener(new DragAnimatorListenerAdapter(bbev)); - startNewDragAnimation(animatorSet); + startNewAnimator(animatorSet); } /** @@ -443,14 +572,15 @@ public class BubbleBarAnimationHelper { */ public void cancelAnimations() { PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel(); - mExpandedViewAlphaAnimator.cancel(); BubbleBarExpandedView bbev = getExpandedView(); if (bbev != null) { bbev.animate().cancel(); } - if (mRunningDragAnimator != null) { - mRunningDragAnimator.cancel(); - mRunningDragAnimator = null; + if (mRunningAnimator != null) { + if (mRunningAnimator.isRunning()) { + mRunningAnimator.cancel(); + } + mRunningAnimator = null; } } @@ -462,8 +592,7 @@ public class BubbleBarAnimationHelper { return null; } - private void updateExpandedView() { - BubbleBarExpandedView bbev = getExpandedView(); + private void updateExpandedView(BubbleBarExpandedView bbev) { if (bbev == null) { Log.w(TAG, "Trying to update the expanded view without a bubble"); return; @@ -477,6 +606,8 @@ public class BubbleBarAnimationHelper { bbev.setLayoutParams(lp); bbev.setX(position.x); bbev.setY(position.y); + bbev.setScaleX(1f); + bbev.setScaleY(1f); bbev.updateLocation(); bbev.maybeShowOverflow(); } @@ -500,18 +631,54 @@ public class BubbleBarAnimationHelper { return new Size(width, height); } - private void startNewDragAnimation(Animator animator) { + private void startNewAnimator(Animator animator) { cancelAnimations(); - mRunningDragAnimator = animator; + mRunningAnimator = animator; animator.start(); } + /** + * Animate the alpha of the expanded view between visible (1) and invisible (0). + * {@link BubbleBarExpandedView} requires + * {@link com.android.wm.shell.bubbles.BubbleExpandedView#setSurfaceZOrderedOnTop(boolean)} to + * be called before alpha can be applied. + * Only supports alpha of 1 or 0. Otherwise we can't reset surface z-order at the end. + */ + private ObjectAnimator createAlphaAnimator(BubbleBarExpandedView bubbleBarExpandedView, + boolean visible) { + ObjectAnimator animator = ObjectAnimator.ofFloat(bubbleBarExpandedView, TASK_VIEW_ALPHA, + visible ? 1f : 0f); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + // Move task view to the top of the window so alpha can be applied to it + bubbleBarExpandedView.setSurfaceZOrderedOnTop(true); + } + + @Override + public void onAnimationEnd(Animator animation) { + bubbleBarExpandedView.setContentVisibility(visible); + if (!visible) { + // Hide the expanded view before we reset the z-ordering + bubbleBarExpandedView.setVisibility(INVISIBLE); + } + bubbleBarExpandedView.setSurfaceZOrderedOnTop(false); + } + }); + return animator; + } + private static void setDragPivot(BubbleBarExpandedView bbev) { bbev.setPivotX(bbev.getWidth() / 2f); bbev.setPivotY(0f); } - private class DragAnimatorListenerAdapter extends AnimatorListenerAdapter { + private static void setPivotToCenter(BubbleBarExpandedView bbev) { + bbev.setPivotX(bbev.getWidth() / 2f); + bbev.setPivotY(bbev.getHeight() / 2f); + } + + private static class DragAnimatorListenerAdapter extends AnimatorListenerAdapter { private final BubbleBarExpandedView mBubbleBarExpandedView; @@ -523,11 +690,9 @@ public class BubbleBarAnimationHelper { public void onAnimationStart(Animator animation) { mBubbleBarExpandedView.setAnimating(true); } - @Override public void onAnimationEnd(Animator animation) { mBubbleBarExpandedView.setAnimating(false); - mRunningDragAnimator = null; } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java index ed49417ad3bd..2c0483c50710 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java @@ -85,6 +85,22 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView } }; + /** + * Property to set alpha for the task view + */ + public static final FloatProperty<BubbleBarExpandedView> TASK_VIEW_ALPHA = new FloatProperty<>( + "taskViewAlpha") { + @Override + public void setValue(BubbleBarExpandedView bbev, float alpha) { + bbev.setTaskViewAlpha(alpha); + } + + @Override + public Float get(BubbleBarExpandedView bbev) { + return bbev.mTaskView != null ? bbev.mTaskView.getAlpha() : bbev.getAlpha(); + } + }; + private static final String TAG = BubbleBarExpandedView.class.getSimpleName(); private static final int INVALID_TASK_ID = -1; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index 0c05e3c5115c..425afbed0742 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -103,8 +103,7 @@ public class BubbleBarLayerView extends FrameLayout mPositioner = mBubbleController.getPositioner(); mBubbleLogger = bubbleLogger; - mAnimationHelper = new BubbleBarAnimationHelper(context, - this, mPositioner); + mAnimationHelper = new BubbleBarAnimationHelper(context, mPositioner); mEducationViewController = new BubbleEducationViewController(context, (boolean visible) -> { if (mExpandedView == null) return; mExpandedView.setObscured(visible); @@ -183,8 +182,14 @@ public class BubbleBarLayerView extends FrameLayout // Already showing this bubble, skip animating return; } + BubbleViewProvider previousBubble = null; if (mExpandedBubble != null && !b.getKey().equals(mExpandedBubble.getKey())) { - removeView(mExpandedView); + if (mIsExpanded) { + // Previous expanded view open, keep it visible to animate the switch + previousBubble = mExpandedBubble; + } else { + removeView(mExpandedView); + } mExpandedView = null; } if (mExpandedView == null) { @@ -246,7 +251,8 @@ public class BubbleBarLayerView extends FrameLayout mIsExpanded = true; mBubbleController.getSysuiProxy().onStackExpandChanged(true); - mAnimationHelper.animateExpansion(mExpandedBubble, () -> { + + final Runnable afterAnimation = () -> { if (mExpandedView == null) return; // Touch delegate for the menu BubbleBarHandleView view = mExpandedView.getHandleView(); @@ -256,7 +262,18 @@ public class BubbleBarLayerView extends FrameLayout mHandleTouchDelegate = new TouchDelegate(mHandleTouchBounds, mExpandedView.getHandleView()); setTouchDelegate(mHandleTouchDelegate); - }); + }; + + if (previousBubble != null) { + final BubbleBarExpandedView previousExpandedView = + previousBubble.getBubbleBarExpandedView(); + mAnimationHelper.animateSwitch(previousBubble, mExpandedBubble, () -> { + removeView(previousExpandedView); + afterAnimation.run(); + }); + } else { + mAnimationHelper.animateExpansion(mExpandedBubble, afterAnimation); + } showScrim(true); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/BoostExecutor.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/BoostExecutor.kt new file mode 100644 index 000000000000..498d0e406e4b --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/BoostExecutor.kt @@ -0,0 +1,47 @@ +/* + * 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.common + +import android.os.Looper +import java.util.concurrent.Executor + +/** Executor implementation which can be boosted temporarily to a different thread priority. */ +interface BoostExecutor : Executor { + /** + * Requests that the executor is boosted until {@link #resetBoost()} is called. + */ + fun setBoost() {} + + /** + * Requests that the executor is not boosted (only resets if there are no other boost requests + * in progress). + */ + fun resetBoost() {} + + /** + * Returns whether the executor is boosted. + */ + fun isBoosted() : Boolean { + return false + } + + /** + * Returns the looper for this executor. + */ + fun getLooper() : Looper? { + return Looper.myLooper() + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java index 12d20bf0e517..f532be6b8277 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java @@ -96,14 +96,6 @@ public class DisplayController { } /** - * Get all the displays from DisplayManager. - */ - public Display[] getDisplays() { - final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); - return displayManager.getDisplays(); - } - - /** * Gets the DisplayLayout associated with a display. */ public @Nullable DisplayLayout getDisplayLayout(int displayId) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java index 38087c066918..ec3c0b83fe2d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java @@ -228,7 +228,6 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged public class PerDisplay implements DisplayInsetsController.OnInsetsChangedListener { final int mDisplayId; final InsetsState mInsetsState = new InsetsState(); - @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible(); boolean mImeRequestedVisible = (WindowInsets.Type.defaultVisible() & WindowInsets.Type.ime()) != 0; InsetsSourceControl mImeSourceControl = null; @@ -403,8 +402,11 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged @Override // TODO(b/335404678): pass control target - public void setImeInputTargetRequestedVisibility(boolean visible) { + public void setImeInputTargetRequestedVisibility(boolean visible, + @NonNull ImeTracker.Token statsToken) { if (android.view.inputmethod.Flags.refactorInsetsController()) { + ImeTracker.forLogging().onProgress(statsToken, + ImeTracker.PHASE_WM_DISPLAY_IME_CONTROLLER_SET_IME_REQUESTED_VISIBLE); mImeRequestedVisible = visible; dispatchImeRequested(mDisplayId, mImeRequestedVisible); @@ -414,21 +416,19 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged // therefore have to start the show animation from here startAnimation(mImeRequestedVisible /* show */, false /* forceRestart */); - setVisibleDirectly(mImeRequestedVisible || mAnimation != null); + setVisibleDirectly(mImeRequestedVisible || mAnimation != null, statsToken); } } /** * Sends the local visibility state back to window manager. Needed for legacy adjustForIme. */ - private void setVisibleDirectly(boolean visible) { + private void setVisibleDirectly(boolean visible, @Nullable ImeTracker.Token statsToken) { mInsetsState.setSourceVisible(InsetsSource.ID_IME, visible); - mRequestedVisibleTypes = visible - ? mRequestedVisibleTypes | WindowInsets.Type.ime() - : mRequestedVisibleTypes & ~WindowInsets.Type.ime(); + int visibleTypes = visible ? WindowInsets.Type.ime() : 0; try { mWmService.updateDisplayWindowRequestedVisibleTypes(mDisplayId, - mRequestedVisibleTypes); + visibleTypes, WindowInsets.Type.ime(), statsToken); } catch (RemoteException e) { } } @@ -640,7 +640,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged t.hide(animatingLeash); removeImeSurface(mDisplayId); if (android.view.inputmethod.Flags.refactorInsetsController()) { - setVisibleDirectly(false /* visible */); + setVisibleDirectly(false /* visible */, statsToken); } ImeTracker.forLogging().onHidden(mStatsToken); } else if (mAnimationDirection == DIRECTION_SHOW && !mCancelled) { @@ -669,13 +669,13 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged if (!android.view.inputmethod.Flags.refactorInsetsController() && !show) { // When going away, queue up insets change first, otherwise any bounds changes // can have a "flicker" of ime-provided insets. - setVisibleDirectly(false /* visible */); + setVisibleDirectly(false /* visible */, null /* statsToken */); } mAnimation.start(); if (!android.view.inputmethod.Flags.refactorInsetsController() && show) { // When showing away, queue up insets change last, otherwise any bounds changes // can have a "flicker" of ime-provided insets. - setVisibleDirectly(true /* visible */); + setVisibleDirectly(true /* visible */, null /* statsToken */); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java index c4c177cbcc28..c45f09be8430 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java @@ -16,6 +16,7 @@ package com.android.wm.shell.common; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.os.RemoteException; @@ -223,13 +224,14 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan } } - private void setImeInputTargetRequestedVisibility(boolean visible) { + private void setImeInputTargetRequestedVisibility(boolean visible, + @NonNull ImeTracker.Token statsToken) { CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId); if (listeners == null) { return; } for (OnInsetsChangedListener listener : listeners) { - listener.setImeInputTargetRequestedVisibility(visible); + listener.setImeInputTargetRequestedVisibility(visible, statsToken); } } @@ -276,10 +278,11 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan } @Override - public void setImeInputTargetRequestedVisibility(boolean visible) + public void setImeInputTargetRequestedVisibility(boolean visible, + @NonNull ImeTracker.Token statsToken) throws RemoteException { mMainExecutor.execute(() -> { - PerDisplay.this.setImeInputTargetRequestedVisibility(visible); + PerDisplay.this.setImeInputTargetRequestedVisibility(visible, statsToken); }); } } @@ -345,7 +348,10 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan * Called to set the requested visibility of the IME in DisplayImeController. Invoked by * {@link com.android.server.wm.DisplayContent.RemoteInsetsControlTarget}. * @param visible requested status of the IME + * @param statsToken the token tracking the current IME request */ - default void setImeInputTargetRequestedVisibility(boolean visible) {} + default void setImeInputTargetRequestedVisibility(boolean visible, + @NonNull ImeTracker.Token statsToken) { + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java index 736d954513b1..803f16ce39c4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java @@ -16,15 +16,50 @@ package com.android.wm.shell.common; +import static android.os.Process.THREAD_PRIORITY_DEFAULT; +import static android.os.Process.setThreadPriority; + import android.annotation.NonNull; import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; + +import androidx.annotation.VisibleForTesting; + +import java.util.function.BiConsumer; /** Executor implementation which is backed by a Handler. */ public class HandlerExecutor implements ShellExecutor { + @NonNull private final Handler mHandler; + // See android.os.Process#THREAD_PRIORITY_* + private final int mDefaultThreadPriority; + private final int mBoostedThreadPriority; + // Number of current requests to boost thread priority + private int mBoostCount; + private final Object mBoostLock = new Object(); + // Default function for setting thread priority (tid, priority) + private BiConsumer<Integer, Integer> mSetThreadPriorityFn = + HandlerExecutor::setThreadPriorityInternal; public HandlerExecutor(@NonNull Handler handler) { + this(handler, THREAD_PRIORITY_DEFAULT, THREAD_PRIORITY_DEFAULT); + } + + /** + * Used only if this executor can be boosted, if so, it can be boosted to the given + * {@param boostPriority}. + */ + public HandlerExecutor(@NonNull Handler handler, int defaultThreadPriority, + int boostedThreadPriority) { mHandler = handler; + mDefaultThreadPriority = defaultThreadPriority; + mBoostedThreadPriority = boostedThreadPriority; + } + + @VisibleForTesting + void replaceSetThreadPriorityFn(BiConsumer<Integer, Integer> setThreadPriorityFn) { + mSetThreadPriorityFn = setThreadPriorityFn; } @Override @@ -56,9 +91,54 @@ public class HandlerExecutor implements ShellExecutor { } @Override + public void setBoost() { + synchronized (mBoostLock) { + if (mDefaultThreadPriority == mBoostedThreadPriority) { + // Nothing to boost + return; + } + if (mBoostCount == 0) { + mSetThreadPriorityFn.accept( + ((HandlerThread) mHandler.getLooper().getThread()).getThreadId(), + mBoostedThreadPriority); + } + mBoostCount++; + } + } + + @Override + public void resetBoost() { + synchronized (mBoostLock) { + mBoostCount--; + if (mBoostCount == 0) { + mSetThreadPriorityFn.accept( + ((HandlerThread) mHandler.getLooper().getThread()).getThreadId(), + mDefaultThreadPriority); + } + } + } + + @Override + public boolean isBoosted() { + synchronized (mBoostLock) { + return mBoostCount > 0; + } + } + + @Override + @NonNull + public Looper getLooper() { + return mHandler.getLooper(); + } + + @Override public void assertCurrentThread() { if (!mHandler.getLooper().isCurrentThread()) { throw new IllegalStateException("must be called on " + mHandler); } } + + private static void setThreadPriorityInternal(Integer tid, Integer priority) { + setThreadPriority(tid, priority); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java index 2c2961fd4b65..9e5071e8347b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java @@ -18,15 +18,15 @@ package com.android.wm.shell.common; import java.lang.reflect.Array; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; /** * Super basic Executor interface that adds support for delayed execution and removing callbacks. - * Intended to wrap Handler while better-supporting testing. + * Intended to wrap Handler while better-supporting testing. Not every ShellExecutor implementation + * may support boosting. */ -public interface ShellExecutor extends Executor { +public interface ShellExecutor extends BoostExecutor { /** * Executes the given runnable. If the caller is running on the same looper as this executor, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index 6beff1979e6d..1852cda7e804 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -55,6 +55,7 @@ import android.graphics.Insets; import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; +import android.util.Log; import android.view.Display; import android.view.InsetsController; import android.view.InsetsSource; @@ -142,6 +143,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange @ShellMainThread private final Handler mHandler; + /** Singleton source of truth for the current state of split screen on this device. */ + private final SplitState mSplitState; + private int mDividerWindowWidth; private int mDividerInsets; private int mDividerSize; @@ -204,7 +208,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange SplitLayoutHandler splitLayoutHandler, SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks, DisplayController displayController, DisplayImeController displayImeController, - ShellTaskOrganizer taskOrganizer, int parallaxType, @ShellMainThread Handler handler) { + ShellTaskOrganizer taskOrganizer, int parallaxType, SplitState splitState, + @ShellMainThread Handler handler) { mHandler = handler; mContext = context.createConfigurationContext(configuration); mOrientation = configuration.orientation; @@ -220,6 +225,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mTaskOrganizer = taskOrganizer; mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId()); mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType); + mSplitState = splitState; final Resources res = mContext.getResources(); mDimNonImeSide = res.getBoolean(R.bool.config_dimNonImeAttachedSide); @@ -381,6 +387,11 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange return mDividerSnapAlgorithm.calculateNearestSnapPosition(mDividerPosition); } + /** Updates the {@link SplitState} using the current divider position. */ + public void updateStateWithCurrentPosition() { + mSplitState.set(calculateCurrentSnapPosition()); + } + /** * Returns the divider position as a fraction from 0 to 1. */ @@ -413,7 +424,13 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange removeTouchZones(); } - int currentPosition = calculateCurrentSnapPosition(); + int currentPosition = mSplitState.get(); + // TODO (b/349828130): Can delete this warning after brief soak time. + if (currentPosition != calculateCurrentSnapPosition()) { + Log.wtf(TAG, "SplitState is " + mSplitState.get() + + ", expected " + calculateCurrentSnapPosition()); + } + switch (currentPosition) { case SNAP_TO_2_10_90: case SNAP_TO_3_10_45_45: @@ -764,7 +781,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange break; default: flingDividerPosition(currentPosition, snapTarget.position, duration, interpolator, - () -> setDividerPosition(snapTarget.position, true /* applyLayoutChange */)); + () -> { + setDividerPosition(snapTarget.position, true /* applyLayoutChange */); + mSplitState.set(snapTarget.snapPosition); + }); break; } } @@ -836,10 +856,12 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange /** Fling divider from current position to center position. */ public void flingDividerToCenter(@Nullable Runnable finishCallback) { - final int pos = mDividerSnapAlgorithm.getMiddleTarget().position; + final SnapTarget target = mDividerSnapAlgorithm.getMiddleTarget(); + final int pos = target.position; flingDividerPosition(getDividerPosition(), pos, FLING_ENTER_DURATION, FAST_OUT_SLOW_IN, () -> { setDividerPosition(pos, true /* applyLayoutChange */); + mSplitState.set(target.snapPosition); if (finishCallback != null) { finishCallback.run(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java new file mode 100644 index 000000000000..71758e0d2159 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java @@ -0,0 +1,42 @@ +/* + * 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.common.split; + +import static com.android.wm.shell.shared.split.SplitScreenConstants.NOT_IN_SPLIT; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SplitScreenState; + +/** + * A class that manages the "state" of split screen. See {@link SplitScreenState} for definitions. + */ +public class SplitState { + private @SplitScreenState int mState = NOT_IN_SPLIT; + + /** Updates the current state of split screen on this device. */ + public void set(@SplitScreenState int newState) { + mState = newState; + } + + /** Reports the current state of split screen on this device. */ + public @SplitScreenState int get() { + return mState; + } + + /** Sets NOT_IN_SPLIT when user exits split. */ + public void exit() { + set(NOT_IN_SPLIT); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt index 62d5098f2a27..bc56637b2a1e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt @@ -30,7 +30,7 @@ import com.android.internal.R * desktop windowing environment. */ fun isTopActivityExemptFromDesktopWindowing(context: Context, task: TaskInfo) = - (isSystemUiTask(context, task) || (task.isTopActivityTransparent && task.numActivities == 1)) + (isSystemUiTask(context, task) || (task.numActivities > 0 && task.isActivityStackTransparent)) && !task.isTopActivityNoDisplay private fun isSystemUiTask(context: Context, task: TaskInfo): Boolean { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index c99d9ba862c1..9d4b4bbb33de 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -54,6 +54,7 @@ import com.android.wm.shell.compatui.api.CompatUIEvent; import com.android.wm.shell.compatui.api.CompatUIHandler; import com.android.wm.shell.compatui.api.CompatUIInfo; import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonClicked; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -65,6 +66,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; @@ -194,7 +196,7 @@ public class CompatUIController implements OnDisplaysChangedListener, private final CompatUIStatusManager mCompatUIStatusManager; @NonNull - private final IntPredicate mInDesktopModePredicate; + private final Optional<DesktopUserRepositories> mDesktopUserRepositories; public CompatUIController(@NonNull Context context, @NonNull ShellInit shellInit, @@ -210,7 +212,7 @@ public class CompatUIController implements OnDisplaysChangedListener, @NonNull CompatUIShellCommandHandler compatUIShellCommandHandler, @NonNull AccessibilityManager accessibilityManager, @NonNull CompatUIStatusManager compatUIStatusManager, - @NonNull IntPredicate isDesktopModeEnablePredicate) { + @NonNull Optional<DesktopUserRepositories> desktopUserRepositories) { mContext = context; mShellController = shellController; mDisplayController = displayController; @@ -226,7 +228,7 @@ public class CompatUIController implements OnDisplaysChangedListener, mDisappearTimeSupplier = flags -> accessibilityManager.getRecommendedTimeoutMillis( DISAPPEAR_DELAY_MS, flags); mCompatUIStatusManager = compatUIStatusManager; - mInDesktopModePredicate = isDesktopModeEnablePredicate; + mDesktopUserRepositories = desktopUserRepositories; shellInit.addInitCallback(this::onInit, this); } @@ -267,7 +269,6 @@ public class CompatUIController implements OnDisplaysChangedListener, updateActiveTaskInfo(taskInfo); } - // We're showing the first reachability education so we ignore incoming TaskInfo // until the education flow has completed or we double tap. The double-tap // basically cancel all the onboarding flow. We don't have to ignore events in case @@ -865,7 +866,11 @@ public class CompatUIController implements OnDisplaysChangedListener, } private boolean isInDesktopMode(@Nullable TaskInfo taskInfo) { - return taskInfo != null && Flags.skipCompatUiEducationInDesktopMode() - && mInDesktopModePredicate.test(taskInfo.displayId); + if (mDesktopUserRepositories.isEmpty() || taskInfo == null) { + return false; + } + boolean isDesktopModeShowing = mDesktopUserRepositories.get().getCurrent() + .getVisibleTaskCount(taskInfo.displayId) > 0; + return Flags.skipCompatUiEducationInDesktopMode() && isDesktopModeShowing; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java index 2128cbc144b5..0d16880aec3d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java @@ -26,6 +26,7 @@ import android.content.Context; import android.graphics.Rect; import android.util.Pair; import android.view.LayoutInflater; +import android.view.SurfaceControl; import android.view.View; import android.window.DesktopModeFlags; @@ -70,6 +71,9 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { private final float mHideScmTolerance; + @NonNull + private final Rect mLayoutBounds = new Rect(); + CompatUIWindowManager(@NonNull Context context, @NonNull TaskInfo taskInfo, @NonNull SyncTransactionQueue syncQueue, @NonNull Consumer<CompatUIEvent> callback, @@ -105,6 +109,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { @Override protected void removeLayout() { + mLayoutBounds.setEmpty(); mLayout = null; } @@ -171,18 +176,21 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { @Override @VisibleForTesting public void updateSurfacePosition() { - if (mLayout == null) { + updateLayoutBounds(); + if (mLayoutBounds.isEmpty()) { return; } - // Position of the button in the container coordinate. - final Rect taskBounds = getTaskBounds(); - final Rect taskStableBounds = getTaskStableBounds(); - final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL - ? taskStableBounds.left - taskBounds.left - : taskStableBounds.right - taskBounds.left - mLayout.getMeasuredWidth(); - final int positionY = taskStableBounds.bottom - taskBounds.top - - mLayout.getMeasuredHeight(); - updateSurfacePosition(positionX, positionY); + updateSurfacePosition(mLayoutBounds.left, mLayoutBounds.top); + } + + @Override + @VisibleForTesting + public void updateSurfacePosition(@NonNull SurfaceControl.Transaction tx) { + updateLayoutBounds(); + if (mLayoutBounds.isEmpty()) { + return; + } + updateSurfaceBounds(tx, mLayoutBounds); } @VisibleForTesting @@ -219,6 +227,23 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { return percentageAreaOfLetterboxInTask < mHideScmTolerance; } + private void updateLayoutBounds() { + if (mLayout == null) { + mLayoutBounds.setEmpty(); + return; + } + // Position of the button in the container coordinate. + final Rect taskBounds = getTaskBounds(); + final Rect taskStableBounds = getTaskStableBounds(); + final int layoutWidth = mLayout.getMeasuredWidth(); + final int layoutHeight = mLayout.getMeasuredHeight(); + final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL + ? taskStableBounds.left - taskBounds.left + : taskStableBounds.right - taskBounds.left - layoutWidth; + final int positionY = taskStableBounds.bottom - taskBounds.top - layoutHeight; + mLayoutBounds.set(positionX, positionY, positionX + layoutWidth, positionY + layoutHeight); + } + private void updateVisibilityOfViews() { if (mLayout == null) { return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java index d2b4f1ab6b0d..82acfe5478b4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java @@ -43,6 +43,7 @@ import android.view.WindowManager; import android.view.WindowlessWindowManager; import com.android.internal.annotations.VisibleForTesting; +import com.android.window.flags.Flags; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; @@ -327,8 +328,15 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana if (mViewHost == null) { return; } - mViewHost.relayout(windowLayoutParams); - updateSurfacePosition(); + if (Flags.appCompatAsyncRelayout()) { + mViewHost.relayout(windowLayoutParams, tx -> { + updateSurfacePosition(tx); + tx.apply(); + }); + } else { + mViewHost.relayout(windowLayoutParams); + updateSurfacePosition(); + } } @NonNull @@ -349,6 +357,10 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana */ protected abstract void updateSurfacePosition(); + protected void updateSurfacePosition(@NonNull SurfaceControl.Transaction tx) { + + } + /** * Updates the position of the surface with respect to the given {@code positionX} and {@code * positionY}. @@ -366,6 +378,15 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana }); } + protected void updateSurfaceBounds(@NonNull SurfaceControl.Transaction tx, + @NonNull Rect bounds) { + if (mLeash == null) { + return; + } + tx.setPosition(mLeash, bounds.left, bounds.top) + .setWindowCrop(mLeash, bounds.width(), bounds.height()); + } + protected int getLayoutDirection() { return mContext.getResources().getConfiguration().getLayoutDirection(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java index 3f67172ca636..650d2170ce00 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java @@ -27,6 +27,7 @@ import android.content.Intent; import android.graphics.Rect; import android.os.SystemClock; import android.view.LayoutInflater; +import android.view.SurfaceControl; import android.view.View; import android.view.accessibility.AccessibilityManager; @@ -69,6 +70,9 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract @NonNull final CompatUIHintsState mCompatUIHintsState; + @NonNull + private final Rect mLayoutBounds = new Rect(); + @Nullable private UserAspectRatioSettingsLayout mLayout; @@ -108,6 +112,7 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract @Override protected void removeLayout() { + mLayoutBounds.setEmpty(); mLayout = null; } @@ -168,18 +173,21 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract @Override @VisibleForTesting public void updateSurfacePosition() { - if (mLayout == null) { + updateLayoutBounds(); + if (mLayoutBounds.isEmpty()) { return; } - // Position of the button in the container coordinate. - final Rect taskBounds = getTaskBounds(); - final Rect taskStableBounds = getTaskStableBounds(); - final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL - ? taskStableBounds.left - taskBounds.left - : taskStableBounds.right - taskBounds.left - mLayout.getMeasuredWidth(); - final int positionY = taskStableBounds.bottom - taskBounds.top - - mLayout.getMeasuredHeight(); - updateSurfacePosition(positionX, positionY); + updateSurfacePosition(mLayoutBounds.left, mLayoutBounds.top); + } + + @Override + @VisibleForTesting + public void updateSurfacePosition(@NonNull SurfaceControl.Transaction tx) { + updateLayoutBounds(); + if (mLayoutBounds.isEmpty()) { + return; + } + updateSurfaceBounds(tx, mLayoutBounds); } @VisibleForTesting @@ -202,6 +210,23 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract && !isHideDelayReached(mNextButtonHideTimeMs)); } + private void updateLayoutBounds() { + if (mLayout == null) { + mLayoutBounds.setEmpty(); + return; + } + // Position of the button in the container coordinate. + final Rect taskBounds = getTaskBounds(); + final Rect taskStableBounds = getTaskStableBounds(); + final int layoutWidth = mLayout.getMeasuredWidth(); + final int layoutHeight = mLayout.getMeasuredHeight(); + final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL + ? taskStableBounds.left - taskBounds.left + : taskStableBounds.right - taskBounds.left - layoutWidth; + final int positionY = taskStableBounds.bottom - taskBounds.top - layoutHeight; + mLayoutBounds.set(positionX, positionY, positionX + layoutWidth, positionY + layoutHeight); + } + private void showUserAspectRatioButton() { if (mLayout == null) { return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxCommandHandler.kt index 819b11095a9b..2d0a3f54f85f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxCommandHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxCommandHandler.kt @@ -67,8 +67,8 @@ class LetterboxCommandHandler @Inject constructor( return false } return when (args.size) { - 1 -> onShellDisplayCommand(args[0], pw) - 2 -> onShellUpdateCommand(args[0], args[1], pw) + 1 -> onNoParamsCommand(args[0], pw) + 2 -> onSingleParamCommand(args[0], args[1], pw) else -> { pw.println("Invalid command: " + args[0]) return false @@ -89,11 +89,17 @@ class LetterboxCommandHandler @Inject constructor( $prefix name, for example, @android:color/system_accent2_50. $prefix backgroundColorReset" $prefix Resets the background color to the default value." + $prefix cornerRadius" + $prefix Corners radius (in pixels) for activities in the letterbox mode." + $prefix If cornerRadius < 0, it will be ignored and corners of the" + $prefix activity won't be rounded." + $prefix cornerRadiusReset" + $prefix Resets the rounded corners radius to the default value." """.trimIndent() ) } - private fun onShellUpdateCommand(command: String, value: String, pw: PrintWriter): Boolean { + private fun onSingleParamCommand(command: String, value: String, pw: PrintWriter): Boolean { when (command) { "backgroundColor" -> { return invokeWhenValid( @@ -120,10 +126,17 @@ class LetterboxCommandHandler @Inject constructor( } ) - "backgroundColorReset" -> { - letterboxConfiguration.resetLetterboxBackgroundColor() - return true - } + "cornerRadius" -> return invokeWhenValid( + pw, + value, + ::strToInt{ it >= 0 }, + { radius -> + letterboxConfiguration.setLetterboxActivityCornersRadius(radius) + }, + { r -> + "$r is not a valid radius. It must be an integer >= 0." + } + ) else -> { pw.println("Invalid command: $value") @@ -132,7 +145,7 @@ class LetterboxCommandHandler @Inject constructor( } } - private fun onShellDisplayCommand(command: String, pw: PrintWriter): Boolean { + private fun onNoParamsCommand(command: String, pw: PrintWriter): Boolean { when (command) { "backgroundColor" -> { pw.println( @@ -144,6 +157,24 @@ class LetterboxCommandHandler @Inject constructor( return true } + "backgroundColorReset" -> { + letterboxConfiguration.resetLetterboxBackgroundColor() + return true + } + + "cornerRadius" -> { + pw.println( + " Rounded corners radius: " + + "${letterboxConfiguration.getLetterboxActivityCornersRadius()} px." + ) + return true + } + + "cornerRadiusReset" -> { + letterboxConfiguration.resetLetterboxActivityCornersRadius() + return true + } + else -> { pw.println("Invalid command: $command") return false @@ -181,4 +212,15 @@ class LetterboxCommandHandler @Inject constructor( } catch (e: IllegalArgumentException) { null } + + // Converts a String to Int which if possible or it returns null otherwise. + // If a predicate is set, it also returns [null] if the predicate evaluate to [false]. + private fun strToInt(predicate: (Int) -> Boolean = { _ -> true }): (String) -> Int? = { str -> + try { + val value = str.toInt() + if (predicate(value)) value else null + } catch (e: IllegalArgumentException) { + null + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxConfiguration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxConfiguration.kt index 83a8e3103e3f..244ff99cadd0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxConfiguration.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxConfiguration.kt @@ -36,6 +36,21 @@ class LetterboxConfiguration @Inject constructor( // Color resource id for the solid color letterbox background type. private var letterboxBackgroundColorResourceIdOverride: Int? = null + // Default value for corners radius for activities presented in the letterbox mode. + // Values < 0 will be ignored. + private val letterboxActivityDefaultCornersRadius: Int + + // Current corners radius for activities presented in the letterbox mode. + // Values can be modified at runtime and values < 0 will be ignored. + private var letterboxActivityCornersRadius = 0 + + init { + letterboxActivityDefaultCornersRadius = context.resources.getInteger( + R.integer.config_letterboxActivityCornersRadius + ) + letterboxActivityCornersRadius = letterboxActivityDefaultCornersRadius + } + /** * Sets color of letterbox background which is used when using the solid background mode. */ @@ -64,7 +79,7 @@ class LetterboxConfiguration @Inject constructor( } // Query color dynamically because material colors extracted from wallpaper are updated // when wallpaper is changed. - return Color.valueOf(context.getResources().getColor(colorId!!, /* theme */null)) + return Color.valueOf(context.getResources().getColor(colorId!!, null)) } /** @@ -79,4 +94,33 @@ class LetterboxConfiguration @Inject constructor( * The background color for the Letterbox. */ fun getBackgroundColorRgbArray(): FloatArray = getLetterboxBackgroundColor().components + + /** + * Overrides corners radius for activities presented in the letterbox mode. Values < 0, + * will be ignored and corners of the activity won't be rounded. + */ + fun setLetterboxActivityCornersRadius(cornersRadius: Int) { + letterboxActivityCornersRadius = cornersRadius + } + + /** + * Resets corners radius for activities presented in the letterbox mode. + */ + fun resetLetterboxActivityCornersRadius() { + letterboxActivityCornersRadius = letterboxActivityDefaultCornersRadius + } + + /** + * Whether corners of letterboxed activities are rounded. + */ + fun isLetterboxActivityCornersRounded(): Boolean { + return getLetterboxActivityCornersRadius() > 0 + } + + /** + * Gets corners radius for activities presented in the letterbox mode. + */ + fun getLetterboxActivityCornersRadius(): Int { + return maxOf(letterboxActivityCornersRadius, 0) + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt index 523e2f5cf7dc..2c52e9ea3fc8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt @@ -58,7 +58,8 @@ interface LetterboxController { fun updateLetterboxSurfaceBounds( key: LetterboxKey, transaction: Transaction, - taskBounds: Rect + taskBounds: Rect, + activityBounds: Rect ) /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt new file mode 100644 index 000000000000..0c3769e778cd --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt @@ -0,0 +1,58 @@ +/* + * Copyright 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.compatui.letterbox + +import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.MULTIPLE_SURFACES +import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.SINGLE_SURFACE +import com.android.wm.shell.dagger.WMSingleton +import javax.inject.Inject + +/** + * Encapsulate the logic related to the use of a single or multiple surfaces when + * implementing letterbox in shell. + */ +@WMSingleton +class LetterboxControllerStrategy @Inject constructor( + private val letterboxConfiguration: LetterboxConfiguration +) { + + // Different letterbox implementation modes. + enum class LetterboxMode { SINGLE_SURFACE, MULTIPLE_SURFACES } + + @Volatile + private var currentMode: LetterboxMode = SINGLE_SURFACE + + fun configureLetterboxMode() { + // TODO(b/377875146): Define criteria for switching between [LetterboxMode]s. + // At the moment, we use the presence of rounded corners to understand if to use a single + // surface or multiple surfaces for the letterbox areas. This rule will change when + // considering transparent activities which won't have rounded corners leading to the + // [MULTIPLE_SURFACES] option. + // The chosen strategy will depend on performance considerations, + // including surface memory usage and the impact of the rounded corners solution. + currentMode = if (letterboxConfiguration.isLetterboxActivityCornersRounded()) { + SINGLE_SURFACE + } else { + MULTIPLE_SURFACES + } + } + + /** + * @return The specific mode to use for implementing letterboxing for the given [request]. + */ + fun getLetterboxImplementationMode(): LetterboxMode = currentMode +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt index adb034cc4787..771d61832889 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt @@ -16,5 +16,18 @@ package com.android.wm.shell.compatui.letterbox +import android.view.SurfaceControl + // The key to use for identify the letterbox sessions. -data class LetterboxKey(val displayId: Int, val taskId: Int)
\ No newline at end of file +data class LetterboxKey(val displayId: Int, val taskId: Int) + +// Encapsulates the surfaces in the multiple surfaces scenario. +data class LetterboxSurfaces( + var leftSurface: SurfaceControl? = null, + var topSurface: SurfaceControl? = null, + var rightSurface: SurfaceControl? = null, + var bottomSurface: SurfaceControl? = null +) : Iterable<SurfaceControl?> { + override fun iterator() = + listOf(leftSurface, topSurface, rightSurface, bottomSurface).iterator() +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt index b50716ad07a3..47180718634c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt @@ -22,6 +22,7 @@ import android.view.SurfaceControl import android.window.TransitionInfo import com.android.internal.protolog.ProtoLog import com.android.window.flags.Flags.appCompatRefactoring +import com.android.wm.shell.common.transition.TransitionStateHolder import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_APP_COMPAT import com.android.wm.shell.shared.TransitionUtil.isClosingType import com.android.wm.shell.sysui.ShellInit @@ -33,12 +34,16 @@ import com.android.wm.shell.transition.Transitions class LetterboxTransitionObserver( shellInit: ShellInit, private val transitions: Transitions, - private val letterboxController: LetterboxController + private val letterboxController: LetterboxController, + private val transitionStateHolder: TransitionStateHolder, + private val letterboxModeStrategy: LetterboxControllerStrategy ) : Transitions.TransitionObserver { companion object { @JvmStatic private val TAG = "LetterboxTransitionObserver" + @JvmStatic + private val EMPTY_BOUNDS = Rect() } init { @@ -59,7 +64,6 @@ class LetterboxTransitionObserver( // We recognise the operation to execute and delegate to the LetterboxController // the related operation. // TODO(b/377875151): Identify Desktop Windowing Transactions. - // TODO(b/377857898): Handling multiple surfaces // TODO(b/371500295): Handle input events detection. for (change in info.changes) { change.taskInfo?.let { ti -> @@ -71,23 +75,27 @@ class LetterboxTransitionObserver( change.endAbsBounds.height() ) with(letterboxController) { - if (isClosingType(change.mode)) { - destroyLetterboxSurface( - key, - startTransaction - ) + // TODO(b/380274087) Handle return to home from a recents transition. + if (isClosingType(change.mode) && + !transitionStateHolder.isRecentsTransitionRunning()) { + // For the other types of close we need to check the recents. + destroyLetterboxSurface(key, finishTransaction) } else { val isTopActivityLetterboxed = ti.appCompatTaskInfo.isTopActivityLetterboxed if (isTopActivityLetterboxed) { + letterboxModeStrategy.configureLetterboxMode() createLetterboxSurface( key, startTransaction, change.leash ) + val activityBounds = + ti.appCompatTaskInfo.topActivityLetterboxBounds ?: EMPTY_BOUNDS updateLetterboxSurfaceBounds( key, startTransaction, - taskBounds + taskBounds, + activityBounds ) } updateLetterboxSurfaceVisibility( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxUtils.kt new file mode 100644 index 000000000000..ef964f40dab3 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxUtils.kt @@ -0,0 +1,68 @@ +/* + * Copyright 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.compatui.letterbox + +import android.graphics.Rect +import android.view.SurfaceControl +import android.view.SurfaceControl.Transaction + +/** + * Creates a [LetterboxController] which is the composition of other two [LetterboxController]. + * It basically invokes the method on both of them. + */ +infix fun LetterboxController.append(other: LetterboxController) = object : LetterboxController { + override fun createLetterboxSurface( + key: LetterboxKey, + transaction: Transaction, + parentLeash: SurfaceControl + ) { + this@append.createLetterboxSurface(key, transaction, parentLeash) + other.createLetterboxSurface(key, transaction, parentLeash) + } + + override fun destroyLetterboxSurface( + key: LetterboxKey, + transaction: Transaction + ) { + this@append.destroyLetterboxSurface(key, transaction) + other.destroyLetterboxSurface(key, transaction) + } + + override fun updateLetterboxSurfaceVisibility( + key: LetterboxKey, + transaction: Transaction, + visible: Boolean + ) { + this@append.updateLetterboxSurfaceVisibility(key, transaction, visible) + other.updateLetterboxSurfaceVisibility(key, transaction, visible) + } + + override fun updateLetterboxSurfaceBounds( + key: LetterboxKey, + transaction: Transaction, + taskBounds: Rect, + activityBounds: Rect + ) { + this@append.updateLetterboxSurfaceBounds(key, transaction, taskBounds, activityBounds) + other.updateLetterboxSurfaceBounds(key, transaction, taskBounds, activityBounds) + } + + override fun dump() { + this@append.dump() + other.dump() + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxController.kt new file mode 100644 index 000000000000..8d065703c380 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxController.kt @@ -0,0 +1,54 @@ +/* + * Copyright 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.compatui.letterbox + +import android.view.SurfaceControl +import android.view.SurfaceControl.Transaction +import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.MULTIPLE_SURFACES +import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.SINGLE_SURFACE +import com.android.wm.shell.dagger.WMSingleton +import javax.inject.Inject + +/** + * [LetterboxController] implementation working as coordinator of other [LetterboxController] + * implementations. + */ +@WMSingleton +class MixedLetterboxController @Inject constructor( + private val singleSurfaceController: SingleSurfaceLetterboxController, + private val multipleSurfaceController: MultiSurfaceLetterboxController, + private val controllerStrategy: LetterboxControllerStrategy +) : LetterboxController by singleSurfaceController append multipleSurfaceController { + + override fun createLetterboxSurface( + key: LetterboxKey, + transaction: Transaction, + parentLeash: SurfaceControl + ) { + when (controllerStrategy.getLetterboxImplementationMode()) { + SINGLE_SURFACE -> { + multipleSurfaceController.destroyLetterboxSurface(key, transaction) + singleSurfaceController.createLetterboxSurface(key, transaction, parentLeash) + } + + MULTIPLE_SURFACES -> { + singleSurfaceController.destroyLetterboxSurface(key, transaction) + multipleSurfaceController.createLetterboxSurface(key, transaction, parentLeash) + } + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxController.kt new file mode 100644 index 000000000000..5129d03b9dbc --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxController.kt @@ -0,0 +1,176 @@ +/* + * Copyright 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.compatui.letterbox + +import android.graphics.Rect +import android.view.SurfaceControl +import android.view.SurfaceControl.Transaction +import com.android.internal.protolog.ProtoLog +import com.android.wm.shell.dagger.WMSingleton +import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_APP_COMPAT +import javax.inject.Inject + +/** + * Component responsible for handling the lifecycle of multiple letterbox surfaces when needed. + */ +@WMSingleton +class MultiSurfaceLetterboxController @Inject constructor( + private val letterboxBuilder: LetterboxSurfaceBuilder +) : LetterboxController { + + companion object { + @JvmStatic + private val TAG = "MultiSurfaceLetterboxController" + } + + private val letterboxMap = mutableMapOf<LetterboxKey, LetterboxSurfaces>() + + override fun createLetterboxSurface( + key: LetterboxKey, + transaction: Transaction, + parentLeash: SurfaceControl + ) { + val surfaceBuilderFn = { position: String -> + letterboxBuilder.createSurface( + transaction, + parentLeash, + "ShellLetterboxSurface-$key-$position", + "MultiSurfaceLetterboxController#createLetterboxSurface" + ) + } + letterboxMap.runOnItem(key, onMissed = { k, m -> + m[k] = LetterboxSurfaces( + leftSurface = surfaceBuilderFn("Left"), + topSurface = surfaceBuilderFn("Top"), + rightSurface = surfaceBuilderFn("Right"), + bottomSurface = surfaceBuilderFn("Bottom"), + ) + }) + } + + override fun destroyLetterboxSurface( + key: LetterboxKey, + transaction: Transaction + ) { + letterboxMap.runOnItem(key, onFound = { item -> + item.forEach { s -> + s.remove(transaction) + } + }) + letterboxMap.remove(key) + } + + override fun updateLetterboxSurfaceVisibility( + key: LetterboxKey, + transaction: Transaction, + visible: Boolean + ) { + letterboxMap.runOnItem(key, onFound = { item -> + item.forEach { s -> + s.setVisibility(transaction, visible) + } + }) + } + + override fun updateLetterboxSurfaceBounds( + key: LetterboxKey, + transaction: Transaction, + taskBounds: Rect, + activityBounds: Rect + ) { + letterboxMap.runOnItem(key, onFound = { item -> + item.updateSurfacesBounds(transaction, taskBounds, activityBounds) + }) + } + + override fun dump() { + ProtoLog.v(WM_SHELL_APP_COMPAT, "%s: %s", TAG, "${letterboxMap.keys}") + } + + /* + * Executes [onFound] on the [LetterboxItem] if present or [onMissed] if not present. + */ + private fun MutableMap<LetterboxKey, LetterboxSurfaces>.runOnItem( + key: LetterboxKey, + onFound: (LetterboxSurfaces) -> Unit = { _ -> }, + onMissed: ( + LetterboxKey, + MutableMap<LetterboxKey, LetterboxSurfaces> + ) -> Unit = { _, _ -> } + ) { + this[key]?.let { + return onFound(it) + } + return onMissed(key, this) + } + + private fun SurfaceControl?.remove( + tx: Transaction + ) = this?.let { + tx.remove(this) + } + + private fun SurfaceControl?.setVisibility( + tx: Transaction, + visible: Boolean + ) = this?.let { + tx.setVisibility(this, visible) + } + + private fun Transaction.moveAndCrop( + surface: SurfaceControl, + rect: Rect + ): Transaction = + setPosition(surface, rect.left.toFloat(), rect.top.toFloat()) + .setWindowCrop( + surface, + rect.width(), + rect.height() + ) + + private fun LetterboxSurfaces.updateSurfacesBounds( + tx: Transaction, + taskBounds: Rect, + activityBounds: Rect + ) { + // Update the bounds depending on the activity position. + leftSurface?.let { s -> + tx.moveAndCrop( + s, + Rect(taskBounds.left, taskBounds.top, activityBounds.left, taskBounds.bottom) + ) + } + rightSurface?.let { s -> + tx.moveAndCrop( + s, + Rect(activityBounds.right, taskBounds.top, taskBounds.right, taskBounds.bottom) + ) + } + topSurface?.let { s -> + tx.moveAndCrop( + s, + Rect(taskBounds.left, taskBounds.top, taskBounds.right, activityBounds.top) + ) + } + bottomSurface?.let { s -> + tx.moveAndCrop( + s, + Rect(taskBounds.left, activityBounds.bottom, taskBounds.right, taskBounds.bottom) + ) + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxController.kt index f21a7272287e..a67f6082c892 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxController.kt @@ -34,7 +34,7 @@ class SingleSurfaceLetterboxController @Inject constructor( companion object { @JvmStatic - private val TAG = "LetterboxController" + private val TAG = "SingleSurfaceLetterboxController" } private val letterboxMap = mutableMapOf<LetterboxKey, SurfaceControl>() @@ -93,7 +93,8 @@ class SingleSurfaceLetterboxController @Inject constructor( override fun updateLetterboxSurfaceBounds( key: LetterboxKey, transaction: Transaction, - taskBounds: Rect + taskBounds: Rect, + activityBounds: Rect ) { letterboxMap.runOnItem(key, onFound = { item -> item.run { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/HasWMComponent.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/HasWMComponent.kt new file mode 100644 index 000000000000..d5e0240ea9ad --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/HasWMComponent.kt @@ -0,0 +1,27 @@ +/* + * 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.dagger + +/** + * An interface implemented by the application that uses [WMComponent]. + * + * This exposes the component to allow classes to do member injection for bindings where constructor + * injection is not possible, e.g. views. + */ +interface HasWMComponent { + fun getWMComponent(): WMComponent +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/LetterboxModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/LetterboxModule.java new file mode 100644 index 000000000000..76279ec8cfec --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/LetterboxModule.java @@ -0,0 +1,56 @@ +/* + * Copyright 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.dagger; + +import android.annotation.NonNull; + +import com.android.wm.shell.common.transition.TransitionStateHolder; +import com.android.wm.shell.compatui.letterbox.LetterboxController; +import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy; +import com.android.wm.shell.compatui.letterbox.LetterboxTransitionObserver; +import com.android.wm.shell.compatui.letterbox.MixedLetterboxController; +import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; + +import dagger.Binds; +import dagger.Module; +import dagger.Provides; + +/** + * Provides Letterbox Shell implementation components to Dagger dependency Graph. + */ +@Module +public abstract class LetterboxModule { + + @WMSingleton + @Provides + static LetterboxTransitionObserver provideLetterboxTransitionObserver( + @NonNull ShellInit shellInit, + @NonNull Transitions transitions, + @NonNull LetterboxController letterboxController, + @NonNull TransitionStateHolder transitionStateHolder, + @NonNull LetterboxControllerStrategy letterboxControllerStrategy + ) { + return new LetterboxTransitionObserver(shellInit, transitions, letterboxController, + transitionStateHolder, letterboxControllerStrategy); + } + + @WMSingleton + @Binds + abstract LetterboxController bindsLetterboxController( + MixedLetterboxController letterboxController); +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java index 33e4fd8c1a46..aebd94fc173a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java @@ -30,6 +30,7 @@ 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.common.SystemWindows; +import com.android.wm.shell.common.split.SplitState; import com.android.wm.shell.dagger.pip.TvPipModule; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.shared.TransactionPool; @@ -89,6 +90,7 @@ public class TvWMShellModule { Optional<RecentTasksController> recentTasks, LaunchAdjacentController launchAdjacentController, MultiInstanceHelper multiInstanceHelper, + SplitState splitState, @ShellMainThread ShellExecutor mainExecutor, Handler mainHandler, SystemWindows systemWindows) { @@ -96,6 +98,6 @@ public class TvWMShellModule { shellTaskOrganizer, syncQueue, rootTDAOrganizer, displayController, displayImeController, displayInsetsController, transitions, transactionPool, iconProvider, recentTasks, launchAdjacentController, multiInstanceHelper, - mainExecutor, mainHandler, systemWindows); + splitState, mainExecutor, mainHandler, systemWindows); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java new file mode 100644 index 000000000000..c493aadd57b0 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java @@ -0,0 +1,115 @@ +/* + * 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 com.android.wm.shell.dagger; + +import android.os.HandlerThread; + +import androidx.annotation.Nullable; + +import com.android.wm.shell.back.BackAnimation; +import com.android.wm.shell.bubbles.Bubbles; +import com.android.wm.shell.desktopmode.DesktopMode; +import com.android.wm.shell.displayareahelper.DisplayAreaHelper; +import com.android.wm.shell.keyguard.KeyguardTransitions; +import com.android.wm.shell.onehanded.OneHanded; +import com.android.wm.shell.pip.Pip; +import com.android.wm.shell.recents.RecentTasks; +import com.android.wm.shell.shared.ShellTransitions; +import com.android.wm.shell.shared.annotations.ShellMainThread; +import com.android.wm.shell.splitscreen.SplitScreen; +import com.android.wm.shell.startingsurface.StartingSurface; +import com.android.wm.shell.sysui.ShellInterface; +import com.android.wm.shell.taskview.TaskViewFactory; + +import dagger.BindsInstance; +import dagger.Subcomponent; + +import java.util.Optional; + +/** + * Dagger Subcomponent for WindowManager. This class explicitly describes the interfaces exported + * from the WM component into the SysUI component, and references the specific dependencies + * provided by its particular device/form-factor SystemUI implementation. + * + * <p> ie. {@link WMComponent} includes {@link WMShellModule} and {@code TvWMComponent} includes + * {@link TvWMShellModule} + */ +@WMSingleton +@Subcomponent(modules = {WMShellModule.class}) +public interface WMComponent { + + /** + * Builder for a WMComponent. + */ + @Subcomponent.Builder + interface Builder { + + @BindsInstance + Builder setShellMainThread(@Nullable @ShellMainThread HandlerThread t); + + WMComponent build(); + } + + /** + * Initializes all the WMShell components before starting any of the SystemUI components. + */ + default void init() { + getShell().onInit(); + } + + @WMSingleton + ShellInterface getShell(); + + @WMSingleton + Optional<OneHanded> getOneHanded(); + + @WMSingleton + Optional<Pip> getPip(); + + @WMSingleton + Optional<SplitScreen> getSplitScreen(); + + @WMSingleton + Optional<Bubbles> getBubbles(); + + @WMSingleton + Optional<TaskViewFactory> getTaskViewFactory(); + + @WMSingleton + ShellTransitions getShellTransitions(); + + @WMSingleton + KeyguardTransitions getKeyguardTransitions(); + + @WMSingleton + Optional<StartingSurface> getStartingSurface(); + + @WMSingleton + Optional<DisplayAreaHelper> getDisplayAreaHelper(); + + @WMSingleton + Optional<RecentTasks> getRecentTasks(); + + @WMSingleton + Optional<BackAnimation> getBackAnimation(); + + /** + * Optional {@link DesktopMode} component for interacting with desktop mode. + */ + @WMSingleton + Optional<DesktopMode> getDesktopMode(); +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index cb9c20e9b7ec..de86b22e0a99 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -72,6 +72,7 @@ import com.android.wm.shell.common.pip.PipPerfHintController; 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.common.split.SplitState; import com.android.wm.shell.compatui.CompatUIConfiguration; import com.android.wm.shell.compatui.CompatUIController; import com.android.wm.shell.compatui.CompatUIShellCommandHandler; @@ -87,8 +88,8 @@ import com.android.wm.shell.compatui.impl.DefaultCompatUIHandler; import com.android.wm.shell.compatui.impl.DefaultCompatUIRepository; import com.android.wm.shell.compatui.impl.DefaultComponentIdGenerator; import com.android.wm.shell.desktopmode.DesktopMode; -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.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.displayareahelper.DisplayAreaHelperController; import com.android.wm.shell.freeform.FreeformComponents; @@ -138,7 +139,6 @@ import dagger.Module; import dagger.Provides; import java.util.Optional; -import java.util.function.IntPredicate; /** * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only @@ -267,7 +267,7 @@ public abstract class WMShellBaseModule { Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler, Lazy<AccessibilityManager> accessibilityManager, CompatUIRepository compatUIRepository, - Optional<DesktopRepository> desktopRepository, + Optional<DesktopUserRepositories> desktopUserRepositories, @NonNull CompatUIState compatUIState, @NonNull CompatUIComponentIdGenerator componentIdGenerator, @NonNull CompatUIComponentFactory compatUIComponentFactory, @@ -280,10 +280,6 @@ public abstract class WMShellBaseModule { new DefaultCompatUIHandler(compatUIRepository, compatUIState, componentIdGenerator, compatUIComponentFactory, mainExecutor)); } - final IntPredicate inDesktopModePredicate = - desktopRepository.<IntPredicate>map(modeTaskRepository -> displayId -> - modeTaskRepository.getVisibleTaskCount(displayId) > 0) - .orElseGet(() -> displayId -> false); return Optional.of( new CompatUIController( context, @@ -300,7 +296,7 @@ public abstract class WMShellBaseModule { compatUIShellCommandHandler.get(), accessibilityManager.get(), compatUIStatusManager, - inDesktopModePredicate)); + desktopUserRepositories)); } @WMSingleton @@ -704,14 +700,14 @@ public abstract class WMShellBaseModule { ShellCommandHandler shellCommandHandler, TaskStackListenerImpl taskStackListener, ActivityTaskManager activityTaskManager, - Optional<DesktopRepository> desktopRepository, + Optional<DesktopUserRepositories> desktopUserRepositories, TaskStackTransitionObserver taskStackTransitionObserver, @ShellMainThread ShellExecutor mainExecutor ) { return Optional.ofNullable( RecentTasksController.create(context, shellInit, shellController, shellCommandHandler, taskStackListener, activityTaskManager, - desktopRepository, taskStackTransitionObserver, mainExecutor)); + desktopUserRepositories, taskStackTransitionObserver, mainExecutor)); } @BindsOptionalOf @@ -867,6 +863,12 @@ public abstract class WMShellBaseModule { return Optional.empty(); } + @WMSingleton + @Provides + static SplitState provideSplitState() { + return new SplitState(); + } + // // Starting window // @@ -1002,16 +1004,16 @@ public abstract class WMShellBaseModule { @BindsOptionalOf @DynamicOverride - abstract DesktopRepository optionalDesktopRepository(); + abstract DesktopUserRepositories optionalDesktopUserRepositories(); @WMSingleton @Provides - static Optional<DesktopRepository> provideDesktopRepository(Context context, - @DynamicOverride Optional<Lazy<DesktopRepository>> desktopRepository) { + static Optional<DesktopUserRepositories> provideDesktopUserRepositories(Context context, + @DynamicOverride Optional<Lazy<DesktopUserRepositories>> desktopUserRepositories) { // Use optional-of-lazy for the dependency that this provider relies on. // Lazy ensures that this provider will not be the cause the dependency is created // when it will not be returned due to the condition below. - return desktopRepository.flatMap((lazy) -> { + return desktopUserRepositories.flatMap((lazy) -> { if (DesktopModeStatus.canEnterDesktopMode(context)) { return Optional.of(lazy.get()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java index c5644a8f6876..d7ddbdeaa6da 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java @@ -18,6 +18,7 @@ package com.android.wm.shell.dagger; import static android.os.Process.THREAD_PRIORITY_BACKGROUND; import static android.os.Process.THREAD_PRIORITY_DISPLAY; +import static android.os.Process.THREAD_PRIORITY_FOREGROUND; import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST; import android.content.Context; @@ -205,13 +206,14 @@ public abstract class WMShellConcurrencyModule { } /** - * Provides a Shell background thread Executor for low priority background tasks. + * Provides a Shell background thread Executor for low priority background tasks. The thread + * may also be boosted to THREAD_PRIORITY_FOREGROUND if necessary. */ @WMSingleton @Provides @ShellBackgroundThread public static ShellExecutor provideSharedBackgroundExecutor( @ShellBackgroundThread Handler handler) { - return new HandlerExecutor(handler); + return new HandlerExecutor(handler, THREAD_PRIORITY_BACKGROUND, THREAD_PRIORITY_FOREGROUND); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 974535385334..f9e3be9c770f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -17,6 +17,7 @@ package com.android.wm.shell.dagger; import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS; +import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX; import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY; import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT; import static android.window.DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS; @@ -50,6 +51,7 @@ import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser; import com.android.wm.shell.apptoweb.AssistContentRequester; +import com.android.wm.shell.back.BackAnimationController; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.bubbles.BubbleData; import com.android.wm.shell.bubbles.BubbleDataRepository; @@ -68,10 +70,9 @@ 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.common.TaskStackListenerImpl; +import com.android.wm.shell.common.split.SplitState; import com.android.wm.shell.compatui.letterbox.LetterboxCommandHandler; -import com.android.wm.shell.compatui.letterbox.LetterboxController; import com.android.wm.shell.compatui.letterbox.LetterboxTransitionObserver; -import com.android.wm.shell.compatui.letterbox.SingleSurfaceLetterboxController; import com.android.wm.shell.dagger.back.ShellBackAnimationModule; import com.android.wm.shell.dagger.pip.PipModule; import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler; @@ -86,11 +87,11 @@ import com.android.wm.shell.desktopmode.DesktopModeEventLogger; import com.android.wm.shell.desktopmode.DesktopModeKeyGestureHandler; import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver; import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger; -import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.desktopmode.DesktopTaskChangeListener; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.DesktopTasksLimiter; import com.android.wm.shell.desktopmode.DesktopTasksTransitionObserver; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler; import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler; import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler; @@ -150,6 +151,10 @@ import com.android.wm.shell.windowdecor.CaptionWindowDecorViewModel; import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel; import com.android.wm.shell.windowdecor.WindowDecorViewModel; import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer; +import com.android.wm.shell.windowdecor.common.viewhost.DefaultWindowDecorViewHostSupplier; +import com.android.wm.shell.windowdecor.common.viewhost.PooledWindowDecorViewHostSupplier; +import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost; +import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier; import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationPromoController; import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController; import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel; @@ -175,7 +180,8 @@ import java.util.Optional; * <p>This module only defines Shell dependencies for handheld SystemUI implementation. Common * dependencies should go into {@link WMShellBaseModule}. */ -@Module(includes = {WMShellBaseModule.class, PipModule.class, ShellBackAnimationModule.class}) +@Module(includes = {WMShellBaseModule.class, PipModule.class, ShellBackAnimationModule.class, + LetterboxModule.class}) public abstract class WMShellModule { // @@ -297,6 +303,7 @@ public abstract class WMShellModule { Transitions transitions, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, FocusTransitionObserver focusTransitionObserver, + WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier, Optional<DesktopModeWindowDecorViewModel> desktopModeWindowDecorViewModel) { if (desktopModeWindowDecorViewModel.isPresent()) { return desktopModeWindowDecorViewModel.get(); @@ -314,7 +321,8 @@ public abstract class WMShellModule { rootTaskDisplayAreaOrganizer, syncQueue, transitions, - focusTransitionObserver); + focusTransitionObserver, + windowDecorViewHostSupplier); } @WMSingleton @@ -337,6 +345,18 @@ public abstract class WMShellModule { return new AdditionalSystemViewContainer.Factory(); } + @WMSingleton + @Provides + static WindowDecorViewHostSupplier<WindowDecorViewHost> provideWindowDecorViewHostSupplier( + @NonNull Context context, + @ShellMainThread @NonNull CoroutineScope mainScope) { + final int poolSize = DesktopModeStatus.getWindowDecorScvhPoolSize(context); + if (DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context) && poolSize > 0) { + return new PooledWindowDecorViewHostSupplier(mainScope, poolSize); + } + return new DefaultWindowDecorViewHostSupplier(mainScope); + } + // // Freeform // @@ -362,7 +382,7 @@ public abstract class WMShellModule { Context context, ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, - Optional<DesktopRepository> desktopRepository, + Optional<DesktopUserRepositories> desktopUserRepositories, Optional<DesktopTasksController> desktopTasksController, LaunchAdjacentController launchAdjacentController, WindowDecorViewModel windowDecorViewModel, @@ -374,7 +394,7 @@ public abstract class WMShellModule { context, init, shellTaskOrganizer, - desktopRepository, + desktopUserRepositories, desktopTasksController, launchAdjacentController, windowDecorViewModel, @@ -492,6 +512,7 @@ public abstract class WMShellModule { Optional<WindowDecorViewModel> windowDecorViewModel, Optional<DesktopTasksController> desktopTasksController, MultiInstanceHelper multiInstanceHelper, + SplitState splitState, @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler) { return new SplitScreenController( @@ -515,6 +536,7 @@ public abstract class WMShellModule { desktopTasksController, null /* stageCoordinator */, multiInstanceHelper, + splitState, mainExecutor, mainHandler); } @@ -690,7 +712,7 @@ public abstract class WMShellModule { DesktopModeDragAndDropTransitionHandler desktopModeDragAndDropTransitionHandler, ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, DragToDesktopTransitionHandler dragToDesktopTransitionHandler, - @DynamicOverride DesktopRepository desktopRepository, + @DynamicOverride DesktopUserRepositories desktopUserRepositories, Optional<DesktopImmersiveController> desktopImmersiveController, DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver, LaunchAdjacentController launchAdjacentController, @@ -726,7 +748,7 @@ public abstract class WMShellModule { toggleResizeDesktopTaskTransitionHandler, dragToDesktopTransitionHandler, desktopImmersiveController.get(), - desktopRepository, + desktopUserRepositories, recentsTransitionHandler, multiInstanceHelper, mainExecutor, @@ -749,7 +771,7 @@ public abstract class WMShellModule { ShellTaskOrganizer shellTaskOrganizer, ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, ReturnToDragStartAnimator returnToDragStartAnimator, - @DynamicOverride DesktopRepository desktopRepository, + @DynamicOverride DesktopUserRepositories desktopUserRepositories, DesktopModeEventLogger desktopModeEventLogger) { return new DesktopTilingDecorViewModel( context, @@ -760,7 +782,7 @@ public abstract class WMShellModule { shellTaskOrganizer, toggleResizeDesktopTaskTransitionHandler, returnToDragStartAnimator, - desktopRepository, + desktopUserRepositories, desktopModeEventLogger ); } @@ -768,10 +790,10 @@ public abstract class WMShellModule { @WMSingleton @Provides static Optional<TaskChangeListener> provideDesktopTaskChangeListener( - Context context, @DynamicOverride DesktopRepository desktopRepository) { + Context context, @DynamicOverride DesktopUserRepositories desktopUserRepositories) { if (ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS.isTrue() && DesktopModeStatus.canEnterDesktopMode(context)) { - return Optional.of(new DesktopTaskChangeListener(desktopRepository)); + return Optional.of(new DesktopTaskChangeListener(desktopUserRepositories)); } return Optional.empty(); } @@ -781,7 +803,7 @@ public abstract class WMShellModule { static Optional<DesktopTasksLimiter> provideDesktopTasksLimiter( Context context, Transitions transitions, - @DynamicOverride DesktopRepository desktopRepository, + @DynamicOverride DesktopUserRepositories desktopUserRepositories, ShellTaskOrganizer shellTaskOrganizer, InteractionJankMonitor interactionJankMonitor, @ShellMainThread Handler handler) { @@ -794,7 +816,7 @@ public abstract class WMShellModule { return Optional.of( new DesktopTasksLimiter( transitions, - desktopRepository, + desktopUserRepositories, shellTaskOrganizer, maxTaskLimit, interactionJankMonitor, @@ -808,7 +830,7 @@ public abstract class WMShellModule { Context context, ShellInit shellInit, Transitions transitions, - @DynamicOverride DesktopRepository desktopRepository, + @DynamicOverride DesktopUserRepositories desktopUserRepositories, DisplayController displayController, ShellTaskOrganizer shellTaskOrganizer, ShellCommandHandler shellCommandHandler) { @@ -817,7 +839,7 @@ public abstract class WMShellModule { new DesktopImmersiveController( shellInit, transitions, - desktopRepository, + desktopUserRepositories, displayController, shellTaskOrganizer, shellCommandHandler)); @@ -840,7 +862,8 @@ public abstract class WMShellModule { RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, InteractionJankMonitor interactionJankMonitor) { return (Flags.enableDesktopWindowingTransitions() - || ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS.isTrue()) + || ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS.isTrue() + || ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX.isTrue()) ? new SpringDragToDesktopTransitionHandler( context, transitions, rootTaskDisplayAreaOrganizer, interactionJankMonitor) : new DefaultDragToDesktopTransitionHandler( @@ -856,14 +879,16 @@ public abstract class WMShellModule { InputManager inputManager, ShellTaskOrganizer shellTaskOrganizer, FocusTransitionObserver focusTransitionObserver, - @ShellMainThread ShellExecutor mainExecutor) { + @ShellMainThread ShellExecutor mainExecutor, + DisplayController displayController) { if (DesktopModeStatus.canEnterDesktopMode(context) && useKeyGestureEventHandler() && manageKeyGestures() && (Flags.enableMoveToNextDisplayShortcut() || Flags.enableTaskResizingKeyboardShortcuts())) { return Optional.of(new DesktopModeKeyGestureHandler(context, desktopModeWindowDecorViewModel, desktopTasksController, - inputManager, shellTaskOrganizer, focusTransitionObserver, mainExecutor)); + inputManager, shellTaskOrganizer, focusTransitionObserver, + mainExecutor, displayController)); } return Optional.empty(); } @@ -880,7 +905,7 @@ public abstract class WMShellModule { ShellCommandHandler shellCommandHandler, IWindowManager windowManager, ShellTaskOrganizer taskOrganizer, - @DynamicOverride DesktopRepository desktopRepository, + @DynamicOverride DesktopUserRepositories desktopUserRepositories, DisplayController displayController, ShellController shellController, DisplayInsetsController displayInsetsController, @@ -892,6 +917,7 @@ public abstract class WMShellModule { InteractionJankMonitor interactionJankMonitor, AppToWebGenericLinksParser genericLinksParser, AssistContentRequester assistContentRequester, + WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier, MultiInstanceHelper multiInstanceHelper, Optional<DesktopTasksLimiter> desktopTasksLimiter, AppHandleEducationController appHandleEducationController, @@ -907,12 +933,12 @@ public abstract class WMShellModule { } return Optional.of(new DesktopModeWindowDecorViewModel(context, shellExecutor, mainHandler, mainChoreographer, bgExecutor, shellInit, shellCommandHandler, windowManager, - taskOrganizer, desktopRepository, displayController, shellController, + taskOrganizer, desktopUserRepositories, displayController, shellController, displayInsetsController, syncQueue, transitions, desktopTasksController, desktopImmersiveController.get(), rootTaskDisplayAreaOrganizer, interactionJankMonitor, genericLinksParser, - assistContentRequester, multiInstanceHelper, desktopTasksLimiter, - appHandleEducationController, appToWebEducationController, + assistContentRequester, windowDecorViewHostSupplier, multiInstanceHelper, + desktopTasksLimiter, appHandleEducationController, appToWebEducationController, windowDecorCaptionHandleRepository, activityOrientationChangeHandler, focusTransitionObserver, desktopModeEventLogger, desktopModeUiEventLogger)); } @@ -925,7 +951,7 @@ public abstract class WMShellModule { @ShellAnimationThread ShellExecutor animExecutor, ShellInit shellInit, Transitions transitions, - @DynamicOverride DesktopRepository desktopRepository) { + @DynamicOverride DesktopUserRepositories desktopUserRepositories) { if (!DesktopModeStatus.canEnterDesktopMode(context) || !ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() || !Flags.enableDesktopSystemDialogsTransitions()) { @@ -934,7 +960,7 @@ public abstract class WMShellModule { return Optional.of( new SystemModalsTransitionHandler( context, mainExecutor, animExecutor, shellInit, transitions, - desktopRepository)); + desktopUserRepositories)); } @WMSingleton @@ -993,16 +1019,17 @@ public abstract class WMShellModule { @WMSingleton @Provides @DynamicOverride - static DesktopRepository provideDesktopRepository( + static DesktopUserRepositories provideDesktopUserRepositories( Context context, ShellInit shellInit, DesktopPersistentRepository desktopPersistentRepository, DesktopRepositoryInitializer desktopRepositoryInitializer, - @ShellMainThread CoroutineScope mainScope + @ShellMainThread CoroutineScope mainScope, + UserManager userManager ) { - return new DesktopRepository(context, shellInit, desktopPersistentRepository, + return new DesktopUserRepositories(context, shellInit, desktopPersistentRepository, desktopRepositoryInitializer, - mainScope); + mainScope, userManager); } @WMSingleton @@ -1013,7 +1040,7 @@ public abstract class WMShellModule { ShellTaskOrganizer shellTaskOrganizer, TaskStackListenerImpl taskStackListener, ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, - @DynamicOverride DesktopRepository desktopRepository) { + @DynamicOverride DesktopUserRepositories desktopUserRepositories) { if (DesktopModeStatus.canEnterDesktopMode(context)) { return Optional.of( new DesktopActivityOrientationChangeHandler( @@ -1022,7 +1049,7 @@ public abstract class WMShellModule { shellTaskOrganizer, taskStackListener, toggleResizeDesktopTaskTransitionHandler, - desktopRepository)); + desktopUserRepositories)); } return Optional.empty(); } @@ -1031,12 +1058,13 @@ public abstract class WMShellModule { @Provides static Optional<DesktopTasksTransitionObserver> provideDesktopTasksTransitionObserver( Context context, - Optional<DesktopRepository> desktopRepository, + Optional<DesktopUserRepositories> desktopUserRepositories, Transitions transitions, ShellTaskOrganizer shellTaskOrganizer, Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler, + Optional<BackAnimationController> backAnimationController, ShellInit shellInit) { - return desktopRepository.flatMap( + return desktopUserRepositories.flatMap( repository -> Optional.of( new DesktopTasksTransitionObserver( @@ -1045,6 +1073,7 @@ public abstract class WMShellModule { transitions, shellTaskOrganizer, desktopMixedTransitionHandler.get(), + backAnimationController.get(), shellInit))); } @@ -1053,7 +1082,7 @@ public abstract class WMShellModule { static Optional<DesktopMixedTransitionHandler> provideDesktopMixedTransitionHandler( Context context, Transitions transitions, - @DynamicOverride DesktopRepository desktopRepository, + @DynamicOverride DesktopUserRepositories desktopUserRepositories, FreeformTaskTransitionHandler freeformTaskTransitionHandler, CloseDesktopTaskTransitionHandler closeDesktopTaskTransitionHandler, Optional<DesktopImmersiveController> desktopImmersiveController, @@ -1071,7 +1100,7 @@ public abstract class WMShellModule { new DesktopMixedTransitionHandler( context, transitions, - desktopRepository, + desktopUserRepositories, freeformTaskTransitionHandler, closeDesktopTaskTransitionHandler, desktopImmersiveController.get(), @@ -1307,22 +1336,4 @@ public abstract class WMShellModule { return new Object(); } - // - // App Compat - // - - @WMSingleton - @Provides - static LetterboxTransitionObserver provideLetterboxTransitionObserver( - @NonNull ShellInit shellInit, - @NonNull Transitions transitions, - @NonNull LetterboxController letterboxController - ) { - return new LetterboxTransitionObserver(shellInit, transitions, letterboxController); - } - - @WMSingleton - @Binds - abstract LetterboxController bindsLetterboxController( - SingleSurfaceLetterboxController letterboxController); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java index 3cd5df3121c1..cfdfe3d52011 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java @@ -42,7 +42,7 @@ import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.dagger.WMShellBaseModule; import com.android.wm.shell.dagger.WMSingleton; -import com.android.wm.shell.desktopmode.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipParamsChangedForwarder; @@ -171,7 +171,7 @@ public abstract class Pip1Module { PipParamsChangedForwarder pipParamsChangedForwarder, Optional<SplitScreenController> splitScreenControllerOptional, Optional<PipPerfHintController> pipPerfHintControllerOptional, - Optional<DesktopRepository> desktopRepositoryOptional, + Optional<DesktopUserRepositories> desktopUserRepositoriesOptional, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, DisplayController displayController, PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer, @@ -181,7 +181,7 @@ public abstract class Pip1Module { pipBoundsAlgorithm, menuPhoneController, pipAnimationController, pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder, splitScreenControllerOptional, pipPerfHintControllerOptional, - desktopRepositoryOptional, rootTaskDisplayAreaOrganizer, displayController, + desktopUserRepositoriesOptional, rootTaskDisplayAreaOrganizer, displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java index 3508ecee6d51..3a9961917f79 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.content.Context; import android.os.Handler; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayInsetsController; @@ -38,6 +39,7 @@ import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.dagger.WMShellBaseModule; import com.android.wm.shell.dagger.WMSingleton; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.pip2.phone.PhonePipMenuController; import com.android.wm.shell.pip2.phone.PipController; import com.android.wm.shell.pip2.phone.PipMotionHelper; @@ -128,8 +130,11 @@ public abstract class Pip2Module { static PipScheduler providePipScheduler(Context context, PipBoundsState pipBoundsState, @ShellMainThread ShellExecutor mainExecutor, - PipTransitionState pipTransitionState) { - return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState); + PipTransitionState pipTransitionState, + Optional<DesktopUserRepositories> desktopUserRepositoriesOptional, + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { + return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState, + desktopUserRepositoriesOptional, rootTaskDisplayAreaOrganizer); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt index a16c15dfdf1a..9b5a28916148 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt @@ -106,13 +106,13 @@ constructor( // Scale the end bounds of the window down with an anchor in the center inset( (startBounds.width().toFloat() * (1 - CLOSE_ANIM_SCALE) / 2).toInt(), - (startBounds.height().toFloat() * (1 - CLOSE_ANIM_SCALE) / 2).toInt() + (startBounds.height().toFloat() * (1 - CLOSE_ANIM_SCALE) / 2).toInt(), ) val offsetY = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, CLOSE_ANIM_OFFSET_Y, - context.resources.displayMetrics + context.resources.displayMetrics, ) .toInt() offset(/* dx= */ 0, offsetY) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt index 606aa6cd3353..6104e79efc66 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt @@ -39,7 +39,7 @@ class DesktopActivityOrientationChangeHandler( private val shellTaskOrganizer: ShellTaskOrganizer, private val taskStackListener: TaskStackListenerImpl, private val resizeHandler: ToggleResizeDesktopTaskTransitionHandler, - private val taskRepository: DesktopRepository, + private val desktopUserRepositories: DesktopUserRepositories, ) { init { @@ -49,15 +49,17 @@ class DesktopActivityOrientationChangeHandler( } private fun onInit() { - taskStackListener.addListener(object : TaskStackListenerCallback { - override fun onActivityRequestedOrientationChanged( - taskId: Int, - @ScreenOrientation requestedOrientation: Int - ) { - // Handle requested screen orientation changes at runtime. - handleActivityOrientationChange(taskId, requestedOrientation) + taskStackListener.addListener( + object : TaskStackListenerCallback { + override fun onActivityRequestedOrientationChanged( + taskId: Int, + @ScreenOrientation requestedOrientation: Int, + ) { + // Handle requested screen orientation changes at runtime. + handleActivityOrientationChange(taskId, requestedOrientation) + } } - }) + ) } /** @@ -77,11 +79,13 @@ class DesktopActivityOrientationChangeHandler( private fun handleActivityOrientationChange( taskId: Int, - @ScreenOrientation requestedOrientation: Int + @ScreenOrientation requestedOrientation: Int, ) { if (!Flags.respectOrientationChangeForUnresizeable()) return val task = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: return - if (!isDesktopModeShowing(task.displayId) || !task.isFreeform || task.isResizeable) return + val taskRepository = desktopUserRepositories.current + val isDesktopModeShowing = taskRepository.getVisibleTaskCount(task.displayId) > 0 + if (!isDesktopModeShowing || !task.isFreeform || task.isResizeable) return val taskBounds = task.configuration.windowConfiguration.bounds val taskHeight = taskBounds.height() @@ -91,10 +95,12 @@ class DesktopActivityOrientationChangeHandler( if (taskWidth > taskHeight) ORIENTATION_LANDSCAPE else ORIENTATION_PORTRAIT // Non-resizeable activity requested opposite orientation. - if (orientation == ORIENTATION_PORTRAIT - && ActivityInfo.isFixedOrientationLandscape(requestedOrientation) - || orientation == ORIENTATION_LANDSCAPE - && ActivityInfo.isFixedOrientationPortrait(requestedOrientation)) { + if ( + orientation == ORIENTATION_PORTRAIT && + ActivityInfo.isFixedOrientationLandscape(requestedOrientation) || + orientation == ORIENTATION_LANDSCAPE && + ActivityInfo.isFixedOrientationPortrait(requestedOrientation) + ) { val finalSize = Size(taskHeight, taskWidth) // Use the center x as the resizing anchor point. @@ -106,7 +112,4 @@ class DesktopActivityOrientationChangeHandler( resizeHandler.startTransition(wct) } } - - private fun isDesktopModeShowing(displayId: Int): Boolean = - taskRepository.getVisibleTaskCount(displayId) > 0 -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt index 83b0f8413a28..56c50ff484d4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt @@ -71,8 +71,7 @@ class DesktopBackNavigationTransitionHandler( animations += info.changes .filter { - it.mode == info.type && - it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM + it.mode == info.type && it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM } .mapNotNull { createMinimizeAnimation(it, finishTransaction, onAnimFinish) } if (animations.isEmpty()) return false @@ -83,7 +82,7 @@ class DesktopBackNavigationTransitionHandler( private fun createMinimizeAnimation( change: TransitionInfo.Change, finishTransaction: Transaction, - onAnimFinish: (Animator) -> Unit + onAnimFinish: (Animator) -> Unit, ): Animator? { val t = Transaction() val sc = change.leash diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt index ba383fac8b47..43e8d2a30930 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt @@ -12,7 +12,7 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. -*/ + */ package com.android.wm.shell.desktopmode @@ -64,16 +64,22 @@ class DesktopDisplayEventHandler( private fun refreshDisplayWindowingMode() { // TODO: b/375319538 - Replace the check with a DisplayManager API once it's available. - val isExtendedDisplayEnabled = 0 != Settings.Global.getInt( - context.contentResolver, DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0 - ) + val isExtendedDisplayEnabled = + 0 != + Settings.Global.getInt( + context.contentResolver, + DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, + 0, + ) if (!isExtendedDisplayEnabled) { // No action needed in mirror or projected mode. return } - val hasNonDefaultDisplay = rootTaskDisplayAreaOrganizer.getDisplayIds() - .any { displayId -> displayId != DEFAULT_DISPLAY } + val hasNonDefaultDisplay = + rootTaskDisplayAreaOrganizer.getDisplayIds().any { displayId -> + displayId != DEFAULT_DISPLAY + } val targetDisplayWindowingMode = if (hasNonDefaultDisplay) { WINDOWING_MODE_FREEFORM diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt index dd95273dd4f3..8e2a412764eb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt @@ -43,14 +43,14 @@ import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener import java.io.PrintWriter /** - * A controller to move tasks in/out of desktop's full immersive state where the task - * remains freeform while being able to take fullscreen bounds and have its App Header visibility - * be transient below the status bar like in fullscreen immersive mode. + * A controller to move tasks in/out of desktop's full immersive state where the task remains + * freeform while being able to take fullscreen bounds and have its App Header visibility be + * transient below the status bar like in fullscreen immersive mode. */ class DesktopImmersiveController( shellInit: ShellInit, private val transitions: Transitions, - private val desktopRepository: DesktopRepository, + private val desktopUserRepositories: DesktopUserRepositories, private val displayController: DisplayController, private val shellTaskOrganizer: ShellTaskOrganizer, private val shellCommandHandler: ShellCommandHandler, @@ -60,25 +60,23 @@ class DesktopImmersiveController( constructor( shellInit: ShellInit, transitions: Transitions, - desktopRepository: DesktopRepository, + desktopUserRepositories: DesktopUserRepositories, displayController: DisplayController, shellTaskOrganizer: ShellTaskOrganizer, shellCommandHandler: ShellCommandHandler, ) : this( shellInit, transitions, - desktopRepository, + desktopUserRepositories, displayController, shellTaskOrganizer, shellCommandHandler, - { SurfaceControl.Transaction() } + { SurfaceControl.Transaction() }, ) - @VisibleForTesting - var state: TransitionState? = null + @VisibleForTesting var state: TransitionState? = null - @VisibleForTesting - val pendingExternalExitTransitions = mutableListOf<ExternalPendingExit>() + @VisibleForTesting val pendingExternalExitTransitions = mutableListOf<ExternalPendingExit>() /** Whether there is an immersive transition that hasn't completed yet. */ private val inProgress: Boolean @@ -103,21 +101,20 @@ class DesktopImmersiveController( if (inProgress) { logV( "Cannot start entry because transition(s) already in progress: %s", - getRunningTransitions() + getRunningTransitions(), ) return } - val wct = WindowContainerTransaction().apply { - setBounds(taskInfo.token, Rect()) - } + val wct = WindowContainerTransaction().apply { setBounds(taskInfo.token, Rect()) } logV("Moving task ${taskInfo.taskId} into immersive mode") val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ this) - state = TransitionState( - transition = transition, - displayId = taskInfo.displayId, - taskId = taskInfo.taskId, - direction = Direction.ENTER - ) + state = + TransitionState( + transition = transition, + displayId = taskInfo.displayId, + taskId = taskInfo.taskId, + direction = Direction.ENTER, + ) } /** Starts a transition to move an immersive task out of immersive. */ @@ -126,22 +123,24 @@ class DesktopImmersiveController( if (inProgress) { logV( "Cannot start exit because transition(s) already in progress: %s", - getRunningTransitions() + getRunningTransitions(), ) return } - val wct = WindowContainerTransaction().apply { - setBounds(taskInfo.token, getExitDestinationBounds(taskInfo)) - } + val wct = + WindowContainerTransaction().apply { + setBounds(taskInfo.token, getExitDestinationBounds(taskInfo)) + } logV("Moving task %d out of immersive mode, reason: %s", taskInfo.taskId, reason) val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ this) - state = TransitionState( - transition = transition, - displayId = taskInfo.displayId, - taskId = taskInfo.taskId, - direction = Direction.EXIT - ) + state = + TransitionState( + transition = transition, + displayId = taskInfo.displayId, + taskId = taskInfo.taskId, + direction = Direction.EXIT, + ) } /** @@ -177,23 +176,26 @@ class DesktopImmersiveController( reason: ExitReason, ): ExitResult { if (!Flags.enableFullyImmersiveInDesktop()) return ExitResult.NoExit - val immersiveTask = desktopRepository.getTaskInFullImmersiveState(displayId) - ?: return ExitResult.NoExit + val immersiveTask = + desktopUserRepositories.current.getTaskInFullImmersiveState(displayId) + ?: return ExitResult.NoExit if (immersiveTask == excludeTaskId) { return ExitResult.NoExit } - val taskInfo = shellTaskOrganizer.getRunningTaskInfo(immersiveTask) - ?: return ExitResult.NoExit + val taskInfo = + shellTaskOrganizer.getRunningTaskInfo(immersiveTask) ?: return ExitResult.NoExit logV( "Appending immersive exit for task: %d in display: %d for reason: %s", - immersiveTask, displayId, reason + immersiveTask, + displayId, + reason, ) wct.setBounds(taskInfo.token, getExitDestinationBounds(taskInfo)) return ExitResult.Exit( exitingTask = immersiveTask, runOnTransitionStart = { transition -> addPendingImmersiveExit(immersiveTask, displayId, transition) - } + }, ) } @@ -210,7 +212,7 @@ class DesktopImmersiveController( reason: ExitReason, ): ExitResult { if (!Flags.enableFullyImmersiveInDesktop()) return ExitResult.NoExit - if (desktopRepository.isTaskInFullImmersiveState(taskInfo.taskId)) { + if (desktopUserRepositories.current.isTaskInFullImmersiveState(taskInfo.taskId)) { // A full immersive task is being minimized, make sure the immersive state is broken // (i.e. resize back to max bounds). wct.setBounds(taskInfo.token, getExitDestinationBounds(taskInfo)) @@ -221,20 +223,16 @@ class DesktopImmersiveController( addPendingImmersiveExit( taskId = taskInfo.taskId, displayId = taskInfo.displayId, - transition = transition + transition = transition, ) - } + }, ) } return ExitResult.NoExit } - /** Whether the [change] in the [transition] is a known immersive change. */ - fun isImmersiveChange( - transition: IBinder, - change: TransitionInfo.Change, - ): Boolean { + fun isImmersiveChange(transition: IBinder, change: TransitionInfo.Change): Boolean { return pendingExternalExitTransitions.any { it.transition == transition && it.taskId == change.taskInfo?.taskId } @@ -242,11 +240,7 @@ class DesktopImmersiveController( private fun addPendingImmersiveExit(taskId: Int, displayId: Int, transition: IBinder) { pendingExternalExitTransitions.add( - ExternalPendingExit( - taskId = taskId, - displayId = displayId, - transition = transition - ) + ExternalPendingExit(taskId = taskId, displayId = displayId, transition = transition) ) } @@ -255,7 +249,7 @@ class DesktopImmersiveController( info: TransitionInfo, startTransaction: SurfaceControl.Transaction, finishTransaction: SurfaceControl.Transaction, - finishCallback: Transitions.TransitionFinishCallback + finishCallback: Transitions.TransitionFinishCallback, ): Boolean { val state = requireState() check(state.transition == transition) { @@ -283,10 +277,11 @@ class DesktopImmersiveController( finishCallback: Transitions.TransitionFinishCallback, ) { logD("animateResize for task#%d", targetTaskId) - val change = info.changes.firstOrNull { c -> - val taskInfo = c.taskInfo - return@firstOrNull taskInfo != null && taskInfo.taskId == targetTaskId - } + val change = + info.changes.firstOrNull { c -> + val taskInfo = c.taskInfo + return@firstOrNull taskInfo != null && taskInfo.taskId == targetTaskId + } if (change == null) { logD("Did not find change for task#%d to animate", targetTaskId) startTransaction.apply() @@ -297,9 +292,9 @@ class DesktopImmersiveController( } /** - * Animate an immersive change. + * Animate an immersive change. * - * As of now, both enter and exit transitions have the same animation, a veiled resize. + * As of now, both enter and exit transitions have the same animation, a veiled resize. */ fun animateResizeChange( change: TransitionInfo.Change, @@ -317,8 +312,7 @@ class DesktopImmersiveController( .setPosition(leash, startBounds.left.toFloat(), startBounds.top.toFloat()) .setWindowCrop(leash, startBounds.width(), startBounds.height()) .show(leash) - onTaskResizeAnimationListener - ?.onAnimationStart(taskId, startTransaction, startBounds) + onTaskResizeAnimationListener?.onAnimationStart(taskId, startTransaction, startBounds) ?: startTransaction.apply() val updateTransaction = transactionSupplier() ValueAnimator.ofObject(rectEvaluator, startBounds, endBounds).apply { @@ -340,8 +334,7 @@ class DesktopImmersiveController( .setPosition(leash, rect.left.toFloat(), rect.top.toFloat()) .setWindowCrop(leash, rect.width(), rect.height()) .apply() - onTaskResizeAnimationListener - ?.onBoundsChange(taskId, updateTransaction, rect) + onTaskResizeAnimationListener?.onBoundsChange(taskId, updateTransaction, rect) ?: updateTransaction.apply() } start() @@ -350,13 +343,13 @@ class DesktopImmersiveController( override fun handleRequest( transition: IBinder, - request: TransitionRequestInfo + request: TransitionRequestInfo, ): WindowContainerTransaction? = null override fun onTransitionConsumed( transition: IBinder, aborted: Boolean, - finishTransaction: SurfaceControl.Transaction? + finishTransaction: SurfaceControl.Transaction?, ) { val state = this.state ?: return if (transition == state.transition && aborted) { @@ -377,9 +370,12 @@ class DesktopImmersiveController( startTransaction: SurfaceControl.Transaction, finishTransaction: SurfaceControl.Transaction, ) { + val desktopRepository: DesktopRepository = desktopUserRepositories.current // Check if this is a pending external exit transition. - val pendingExit = pendingExternalExitTransitions - .firstOrNull { pendingExit -> pendingExit.transition == transition } + val pendingExit = + pendingExternalExitTransitions.firstOrNull { pendingExit -> + pendingExit.transition == transition + } if (pendingExit != null) { if (info.hasTaskChange(taskId = pendingExit.taskId)) { if (desktopRepository.isTaskInFullImmersiveState(pendingExit.taskId)) { @@ -387,7 +383,7 @@ class DesktopImmersiveController( desktopRepository.setTaskInFullImmersiveState( displayId = pendingExit.displayId, taskId = pendingExit.taskId, - immersive = false + immersive = false, ) if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) { desktopRepository.removeBoundsBeforeFullImmersive(pendingExit.taskId) @@ -400,24 +396,25 @@ class DesktopImmersiveController( // Check if this is a direct immersive enter/exit transition. if (transition == state?.transition) { val state = requireState() - val immersiveChange = info.changes.firstOrNull { c -> - c.taskInfo?.taskId == state.taskId - } + val immersiveChange = + info.changes.firstOrNull { c -> c.taskInfo?.taskId == state.taskId } if (immersiveChange == null) { logV( "Direct move for task#%d in %s direction missing immersive change.", - state.taskId, state.direction + state.taskId, + state.direction, ) return } val startBounds = immersiveChange.startAbsBounds logV("Direct move for task#%d in %s direction verified", state.taskId, state.direction) + when (state.direction) { Direction.ENTER -> { desktopRepository.setTaskInFullImmersiveState( displayId = state.displayId, taskId = state.taskId, - immersive = true + immersive = true, ) if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) { desktopRepository.saveBoundsBeforeFullImmersive(state.taskId, startBounds) @@ -427,7 +424,7 @@ class DesktopImmersiveController( desktopRepository.setTaskInFullImmersiveState( displayId = state.displayId, taskId = state.taskId, - immersive = false + immersive = false, ) if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) { desktopRepository.removeBoundsBeforeFullImmersive(state.taskId) @@ -447,31 +444,34 @@ class DesktopImmersiveController( desktopRepository.setTaskInFullImmersiveState( displayId = c.taskInfo!!.displayId, taskId = c.taskInfo!!.taskId, - immersive = false + immersive = false, ) } } override fun onTransitionMerged(merged: IBinder, playing: IBinder) { - val pendingExit = pendingExternalExitTransitions - .firstOrNull { pendingExit -> pendingExit.transition == merged } + val pendingExit = + pendingExternalExitTransitions.firstOrNull { pendingExit -> + pendingExit.transition == merged + } if (pendingExit != null) { logV( "Pending exit transition %s for task#%s merged into %s", - merged, pendingExit.taskId, playing + merged, + pendingExit.taskId, + playing, ) pendingExit.transition = playing } } override fun onTransitionFinished(transition: IBinder, aborted: Boolean) { - val pendingExit = pendingExternalExitTransitions - .firstOrNull { pendingExit -> pendingExit.transition == transition } + val pendingExit = + pendingExternalExitTransitions.firstOrNull { pendingExit -> + pendingExit.transition == transition + } if (pendingExit != null) { - logV( - "Pending exit transition %s for task#%s finished", - transition, pendingExit - ) + logV("Pending exit transition %s for task#%s finished", transition, pendingExit) pendingExternalExitTransitions.remove(pendingExit) } } @@ -481,10 +481,11 @@ class DesktopImmersiveController( } private fun getExitDestinationBounds(taskInfo: RunningTaskInfo): Rect { - val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) - ?: error("Expected non-null display layout for displayId: ${taskInfo.displayId}") + val displayLayout = + displayController.getDisplayLayout(taskInfo.displayId) + ?: error("Expected non-null display layout for displayId: ${taskInfo.displayId}") return if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) { - desktopRepository.removeBoundsBeforeFullImmersive(taskInfo.taskId) + desktopUserRepositories.current.removeBoundsBeforeFullImmersive(taskInfo.taskId) ?: if (ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()) { calculateInitialBounds(displayLayout, taskInfo) } else { @@ -500,12 +501,8 @@ class DesktopImmersiveController( private fun getRunningTransitions(): List<IBinder> { val running = mutableListOf<IBinder>() - state?.let { - running.add(it.transition) - } - pendingExternalExitTransitions.forEach { - running.add(it.transition) - } + state?.let { running.add(it.transition) } + pendingExternalExitTransitions.forEach { running.add(it.transition) } return running } @@ -525,28 +522,23 @@ class DesktopImmersiveController( val transition: IBinder, val displayId: Int, val taskId: Int, - val direction: Direction + val direction: Direction, ) /** * Tracks state of a transition involving an immersive exit that is external to this class' own - * transitions. This usually means transitions that exit immersive mode as a side-effect and - * not the primary action (for example, minimizing the immersive task or launching a new task - * on top of the immersive task). + * transitions. This usually means transitions that exit immersive mode as a side-effect and not + * the primary action (for example, minimizing the immersive task or launching a new task on top + * of the immersive task). */ - data class ExternalPendingExit( - val taskId: Int, - val displayId: Int, - var transition: IBinder, - ) + data class ExternalPendingExit(val taskId: Int, val displayId: Int, var transition: IBinder) /** The result of an external exit request. */ sealed class ExitResult { /** An immersive task exit (meaning, resize) was appended to the request. */ - data class Exit( - val exitingTask: Int, - val runOnTransitionStart: ((IBinder) -> Unit) - ) : ExitResult() + data class Exit(val exitingTask: Int, val runOnTransitionStart: ((IBinder) -> Unit)) : + ExitResult() + /** There was no exit appended to the request. */ data object NoExit : ExitResult() @@ -556,7 +548,8 @@ class DesktopImmersiveController( @VisibleForTesting enum class Direction { - ENTER, EXIT + ENTER, + EXIT, } /** The reason for moving the task out of desktop immersive mode. */ @@ -579,7 +572,6 @@ class DesktopImmersiveController( companion object { private const val TAG = "DesktopImmersive" - @VisibleForTesting - const val FULL_IMMERSIVE_ANIM_DURATION_MS = 336L + @VisibleForTesting const val FULL_IMMERSIVE_ANIM_DURATION_MS = 336L } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt index 82c2ebc7ec77..50187d552b09 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt @@ -23,6 +23,7 @@ import android.os.Handler import android.os.IBinder import android.view.SurfaceControl import android.view.WindowManager +import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_OPEN import android.window.DesktopModeFlags import android.window.TransitionInfo @@ -49,7 +50,7 @@ import com.android.wm.shell.transition.Transitions.TransitionFinishCallback class DesktopMixedTransitionHandler( private val context: Context, private val transitions: Transitions, - private val desktopRepository: DesktopRepository, + private val desktopUserRepositories: DesktopUserRepositories, private val freeformTaskTransitionHandler: FreeformTaskTransitionHandler, private val closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler, private val desktopImmersiveController: DesktopImmersiveController, @@ -61,11 +62,10 @@ class DesktopMixedTransitionHandler( ) : MixedTransitionHandler, FreeformTaskTransitionStarter { init { - shellInit.addInitCallback ({ transitions.addHandler(this) }, this) + shellInit.addInitCallback({ transitions.addHandler(this) }, this) } - @VisibleForTesting - val pendingMixedTransitions = mutableListOf<PendingMixedTransition>() + @VisibleForTesting val pendingMixedTransitions = mutableListOf<PendingMixedTransition>() /** Delegates starting transition to [FreeformTaskTransitionHandler]. */ override fun startWindowingModeTransition( @@ -77,13 +77,21 @@ class DesktopMixedTransitionHandler( override fun startMinimizedModeTransition(wct: WindowContainerTransaction?): IBinder = freeformTaskTransitionHandler.startMinimizedModeTransition(wct) + /** Delegates starting PiP transition to [FreeformTaskTransitionHandler]. */ + override fun startPipTransition(wct: WindowContainerTransaction?): IBinder = + freeformTaskTransitionHandler.startPipTransition(wct) + /** Starts close transition and handles or delegates desktop task close animation. */ override fun startRemoveTransition(wct: WindowContainerTransaction?): IBinder { - if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS.isTrue) { + if ( + !DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS.isTrue && + !DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX.isTrue + ) { return freeformTaskTransitionHandler.startRemoveTransition(wct) } requireNotNull(wct) - return transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, /* handler= */ this) + return transitions + .startTransition(WindowManager.TRANSIT_CLOSE, wct, /* handler= */ this) .also { transition -> pendingMixedTransitions.add(PendingMixedTransition.Close(transition)) } @@ -100,8 +108,11 @@ class DesktopMixedTransitionHandler( minimizingTaskId: Int? = null, exitingImmersiveTask: Int? = null, ): IBinder { - if (!Flags.enableFullyImmersiveInDesktop() && - !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) { + if ( + !Flags.enableFullyImmersiveInDesktop() && + !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue && + !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue + ) { return transitions.startTransition(transitionType, wct, /* handler= */ null) } if (exitingImmersiveTask == null) { @@ -109,18 +120,21 @@ class DesktopMixedTransitionHandler( } else { logV( "Starting mixed launch transition for task#%d with immersive exit of task#%d", - taskId, exitingImmersiveTask + taskId, + exitingImmersiveTask, ) } - return transitions.startTransition(transitionType, wct, /* handler= */ this) - .also { transition -> - pendingMixedTransitions.add(PendingMixedTransition.Launch( + return transitions.startTransition(transitionType, wct, /* handler= */ this).also { + transition -> + pendingMixedTransitions.add( + PendingMixedTransition.Launch( transition = transition, launchingTask = taskId, minimizingTask = minimizingTaskId, exitingImmersiveTask = exitingImmersiveTask, - )) - } + ) + ) + } } /** Notifies this handler that there is a pending transition for it to handle. */ @@ -141,36 +155,38 @@ class DesktopMixedTransitionHandler( finishTransaction: SurfaceControl.Transaction, finishCallback: TransitionFinishCallback, ): Boolean { - val pending = pendingMixedTransitions.find { pending -> pending.transition == transition } - ?: return false.also { - logV("No pending desktop transition") - } + val pending = + pendingMixedTransitions.find { pending -> pending.transition == transition } + ?: return false.also { logV("No pending desktop transition") } pendingMixedTransitions.remove(pending) logV("Animating pending mixed transition: %s", pending) return when (pending) { - is PendingMixedTransition.Close -> animateCloseTransition( - transition, - info, - startTransaction, - finishTransaction, - finishCallback - ) - is PendingMixedTransition.Launch -> animateLaunchTransition( - pending, - transition, - info, - startTransaction, - finishTransaction, - finishCallback - ) - is PendingMixedTransition.Minimize -> animateMinimizeTransition( - pending, - transition, - info, - startTransaction, - finishTransaction, - finishCallback - ) + is PendingMixedTransition.Close -> + animateCloseTransition( + transition, + info, + startTransaction, + finishTransaction, + finishCallback, + ) + is PendingMixedTransition.Launch -> + animateLaunchTransition( + pending, + transition, + info, + startTransaction, + finishTransaction, + finishCallback, + ) + is PendingMixedTransition.Minimize -> + animateMinimizeTransition( + pending, + transition, + info, + startTransaction, + finishTransaction, + finishCallback, + ) } } @@ -186,8 +202,9 @@ class DesktopMixedTransitionHandler( logW("Should have closing desktop task") return false } - if (isLastDesktopTask(closeChange)) { - // Dispatch close desktop task animation to the default transition handlers. + if (isWallpaperActivityClosing(info)) { + // If the wallpaper activity is closing then the desktop is closing, animate the closing + // desktop by dispatching to other transition handlers. return dispatchCloseLastDesktopTaskAnimation( transition, info, @@ -216,12 +233,10 @@ class DesktopMixedTransitionHandler( finishCallback: TransitionFinishCallback, ): Boolean { // Check if there's also an immersive change during this launch. - val immersiveExitChange = pending.exitingImmersiveTask?.let { exitingTask -> - findTaskChange(info, exitingTask) - } - val minimizeChange = pending.minimizingTask?.let { minimizingTask -> - findTaskChange(info, minimizingTask) - } + val immersiveExitChange = + pending.exitingImmersiveTask?.let { exitingTask -> findTaskChange(info, exitingTask) } + val minimizeChange = + pending.minimizingTask?.let { minimizingTask -> findTaskChange(info, minimizingTask) } val launchChange = findDesktopTaskLaunchChange(info, pending.launchingTask) if (launchChange == null) { check(minimizeChange == null) @@ -241,10 +256,14 @@ class DesktopMixedTransitionHandler( logV( "Animating mixed launch transition task#%d, minimizingTask#%s immersiveExitTask#%s", - launchChange.taskInfo!!.taskId, minimizeChange?.taskInfo?.taskId, - immersiveExitChange?.taskInfo?.taskId + launchChange.taskInfo!!.taskId, + minimizeChange?.taskInfo?.taskId, + immersiveExitChange?.taskInfo?.taskId, ) - if (DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) { + if ( + DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue || + DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue + ) { // Only apply minimize change reparenting here if we implement the new app launch // transitions, otherwise this reparenting is handled in the default handler. minimizeChange?.let { @@ -259,7 +278,7 @@ class DesktopMixedTransitionHandler( immersiveExitChange, startTransaction, finishTransaction, - finishCb + finishCb, ) // Let the leftover/default handler animate the remaining changes. return dispatchToLeftoverHandler( @@ -267,7 +286,7 @@ class DesktopMixedTransitionHandler( info, startTransaction, finishTransaction, - finishCb + finishCb, ) } // There's nothing to animate separately, so let the left over handler animate @@ -278,7 +297,7 @@ class DesktopMixedTransitionHandler( info, startTransaction, finishTransaction, - finishCb + finishCb, ) } @@ -304,7 +323,7 @@ class DesktopMixedTransitionHandler( info, startTransaction, finishTransaction, - finishCallback + finishCallback, ) } @@ -321,7 +340,7 @@ class DesktopMixedTransitionHandler( override fun onTransitionConsumed( transition: IBinder, aborted: Boolean, - finishTransaction: SurfaceControl.Transaction? + finishTransaction: SurfaceControl.Transaction?, ) { pendingMixedTransitions.removeAll { pending -> pending.transition == transition } super.onTransitionConsumed(transition, aborted, finishTransaction) @@ -356,7 +375,7 @@ class DesktopMixedTransitionHandler( doOnFinishCallback = { // Finish the jank trace when closing the last window in desktop mode. interactionJankMonitor.end(CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE) - } + }, ) } @@ -379,7 +398,10 @@ class DesktopMixedTransitionHandler( require(taskInfo.isFreeform) logV("Reparenting minimizing task#%d", taskInfo.taskId) rootTaskDisplayAreaOrganizer.reparentToDisplayArea( - taskInfo.displayId, minimizeChange.leash, startTransaction) + taskInfo.displayId, + minimizeChange.leash, + startTransaction, + ) } private fun dispatchToLeftoverHandler( @@ -399,14 +421,16 @@ class DesktopMixedTransitionHandler( doOnFinishCallback?.invoke() finishCallback.onTransitionFinished(wct) }, - /* skip= */ this + /* skip= */ this, ) != null } - private fun isLastDesktopTask(change: TransitionInfo.Change): Boolean = - change.taskInfo?.let { - desktopRepository.getExpandedTaskCount(it.displayId) == 1 - } ?: false + private fun isWallpaperActivityClosing(info: TransitionInfo) = + info.changes.any { change -> + change.mode == TRANSIT_CLOSE && + change.taskInfo != null && + DesktopWallpaperActivity.isWallpaperTask(change.taskInfo!!) + } private fun findCloseDesktopTaskChange(info: TransitionInfo): TransitionInfo.Change? { if (info.type != WindowManager.TRANSIT_CLOSE) return null @@ -423,7 +447,7 @@ class DesktopMixedTransitionHandler( private fun findDesktopTaskLaunchChange( info: TransitionInfo, - launchTaskId: Int? + launchTaskId: Int?, ): TransitionInfo.Change? { return if (launchTaskId != null) { // Launching a known task (probably from background or moving to front), so @@ -432,8 +456,9 @@ class DesktopMixedTransitionHandler( } else { // Launching a new task, so the first opening freeform task. info.changes.firstOrNull { change -> - change.mode == TRANSIT_OPEN - && change.taskInfo != null && change.taskInfo!!.isFreeform + change.mode == TRANSIT_OPEN && + change.taskInfo != null && + change.taskInfo!!.isFreeform } } } @@ -451,9 +476,7 @@ class DesktopMixedTransitionHandler( abstract val transition: IBinder /** A task is closing. */ - data class Close( - override val transition: IBinder, - ) : PendingMixedTransition() + data class Close(override val transition: IBinder) : PendingMixedTransition() /** A task is opening or moving to front. */ data class Launch( @@ -463,8 +486,10 @@ class DesktopMixedTransitionHandler( val exitingImmersiveTask: Int?, ) : PendingMixedTransition() - /** A task is minimizing. This should be used for task going to back and some closing cases - * with back navigation. */ + /** + * A task is minimizing. This should be used for task going to back and some closing cases + * with back navigation. + */ data class Minimize( override val transition: IBinder, val minimizingTask: Int, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeDragAndDropTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeDragAndDropTransitionHandler.kt index a7a4a1036b5d..ca02c72c174e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeDragAndDropTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeDragAndDropTransitionHandler.kt @@ -27,16 +27,14 @@ import android.window.WindowContainerTransaction import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TransitionFinishCallback -/** - * Transition handler for drag-and-drop (i.e., tab tear) transitions that occur in desktop mode. - */ +/** Transition handler for drag-and-drop (i.e., tab tear) transitions that occur in desktop mode. */ class DesktopModeDragAndDropTransitionHandler(private val transitions: Transitions) : Transitions.TransitionHandler { private val pendingTransitionTokens: MutableList<IBinder> = mutableListOf() /** - * Begin a transition when a [android.app.PendingIntent] is dropped without a window to - * accept it. + * Begin a transition when a [android.app.PendingIntent] is dropped without a window to accept + * it. */ fun handleDropEvent(wct: WindowContainerTransaction): IBinder { val token = transitions.startTransition(TRANSIT_OPEN, wct, this) @@ -49,29 +47,32 @@ class DesktopModeDragAndDropTransitionHandler(private val transitions: Transitio info: TransitionInfo, startTransaction: SurfaceControl.Transaction, finishTransaction: SurfaceControl.Transaction, - finishCallback: TransitionFinishCallback + finishCallback: TransitionFinishCallback, ): Boolean { if (!pendingTransitionTokens.contains(transition)) return false val change = findRelevantChange(info) val leash = change.leash val endBounds = change.endAbsBounds - startTransaction.hide(leash) + startTransaction + .hide(leash) .setWindowCrop(leash, endBounds.width(), endBounds.height()) .apply() val animator = ValueAnimator() animator.setFloatValues(0f, 1f) animator.setDuration(FADE_IN_ANIMATION_DURATION) val t = SurfaceControl.Transaction() - animator.addListener(object : AnimatorListenerAdapter() { - override fun onAnimationStart(animation: Animator) { - t.show(leash) - t.apply() - } + animator.addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator) { + t.show(leash) + t.apply() + } - override fun onAnimationEnd(animation: Animator) { - finishCallback.onTransitionFinished(null) + override fun onAnimationEnd(animation: Animator) { + finishCallback.onTransitionFinished(null) + } } - }) + ) animator.addUpdateListener { animation: ValueAnimator -> t.setAlpha(leash, animation.animatedFraction) t.apply() @@ -83,9 +84,7 @@ class DesktopModeDragAndDropTransitionHandler(private val transitions: Transitio private fun findRelevantChange(info: TransitionInfo): TransitionInfo.Change { val matchingChanges = - info.changes.filter { c -> - isValidTaskChange(c) && c.mode == TRANSIT_OPEN - } + info.changes.filter { c -> isValidTaskChange(c) && c.mode == TRANSIT_OPEN } if (matchingChanges.size != 1) { throw IllegalStateException( "Expected 1 relevant change but found: ${matchingChanges.size}" @@ -100,7 +99,7 @@ class DesktopModeDragAndDropTransitionHandler(private val transitions: Transitio override fun handleRequest( transition: IBinder, - request: TransitionRequestInfo + request: TransitionRequestInfo, ): WindowContainerTransaction? { return null } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt index dc23128b7b2a..ff6fb59d6494 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt @@ -35,29 +35,25 @@ import java.security.SecureRandom import java.util.Random import java.util.concurrent.atomic.AtomicInteger - /** Event logger for logging desktop mode session events */ class DesktopModeEventLogger { private val random: Random = SecureRandom() /** The session id for the current desktop mode session */ - @VisibleForTesting - val currentSessionId: AtomicInteger = AtomicInteger(NO_SESSION_ID) + @VisibleForTesting val currentSessionId: AtomicInteger = AtomicInteger(NO_SESSION_ID) private fun generateSessionId() = 1 + random.nextInt(1 shl 20) - /** - * Logs enter into desktop mode with [enterReason] - */ + /** Logs enter into desktop mode with [enterReason] */ fun logSessionEnter(enterReason: EnterReason) { val sessionId = generateSessionId() val previousSessionId = currentSessionId.getAndSet(sessionId) if (previousSessionId != NO_SESSION_ID) { ProtoLog.w( WM_SHELL_DESKTOP_MODE, - "DesktopModeLogger: Existing desktop mode session id: %s found on desktop " - + "mode enter", - previousSessionId + "DesktopModeLogger: Existing desktop mode session id: %s found on desktop " + + "mode enter", + previousSessionId, ) } @@ -65,27 +61,25 @@ class DesktopModeEventLogger { WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging session enter, session: %s reason: %s", sessionId, - enterReason.name + enterReason.name, ) FrameworkStatsLog.write( DESKTOP_MODE_ATOM_ID, /* event */ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER, /* enterReason */ enterReason.reason, /* exitReason */ 0, - /* session_id */ sessionId + /* session_id */ sessionId, ) EventLogTags.writeWmShellEnterDesktopMode(enterReason.reason, sessionId) } - /** - * Logs exit from desktop mode session with [exitReason] - */ + /** Logs exit from desktop mode session with [exitReason] */ fun logSessionExit(exitReason: ExitReason) { val sessionId = currentSessionId.getAndSet(NO_SESSION_ID) if (sessionId == NO_SESSION_ID) { ProtoLog.w( WM_SHELL_DESKTOP_MODE, - "DesktopModeLogger: No session id found for logging exit from desktop mode" + "DesktopModeLogger: No session id found for logging exit from desktop mode", ) return } @@ -94,27 +88,25 @@ class DesktopModeEventLogger { WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging session exit, session: %s reason: %s", sessionId, - exitReason.name + exitReason.name, ) FrameworkStatsLog.write( DESKTOP_MODE_ATOM_ID, /* event */ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__EXIT, /* enterReason */ 0, /* exitReason */ exitReason.reason, - /* session_id */ sessionId + /* session_id */ sessionId, ) EventLogTags.writeWmShellExitDesktopMode(exitReason.reason, sessionId) } - /** - * Logs that a task with [taskUpdate] was added in a desktop mode session - */ + /** Logs that a task with [taskUpdate] was added in a desktop mode session */ fun logTaskAdded(taskUpdate: TaskUpdate) { val sessionId = currentSessionId.get() if (sessionId == NO_SESSION_ID) { ProtoLog.w( WM_SHELL_DESKTOP_MODE, - "DesktopModeLogger: No session id found for logging task added" + "DesktopModeLogger: No session id found for logging task added", ) return } @@ -123,23 +115,22 @@ class DesktopModeEventLogger { WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging task added, session: %s taskId: %s", sessionId, - taskUpdate.instanceId + taskUpdate.instanceId, ) logTaskUpdate( FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED, - sessionId, taskUpdate + sessionId, + taskUpdate, ) } - /** - * Logs that a task with [taskUpdate] was removed from a desktop mode session - */ + /** Logs that a task with [taskUpdate] was removed from a desktop mode session */ fun logTaskRemoved(taskUpdate: TaskUpdate) { val sessionId = currentSessionId.get() if (sessionId == NO_SESSION_ID) { ProtoLog.w( WM_SHELL_DESKTOP_MODE, - "DesktopModeLogger: No session id found for logging task removed" + "DesktopModeLogger: No session id found for logging task removed", ) return } @@ -148,23 +139,22 @@ class DesktopModeEventLogger { WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging task remove, session: %s taskId: %s", sessionId, - taskUpdate.instanceId + taskUpdate.instanceId, ) logTaskUpdate( FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED, - sessionId, taskUpdate + sessionId, + taskUpdate, ) } - /** - * Logs that a task with [taskUpdate] had it's info changed in a desktop mode session - */ + /** Logs that a task with [taskUpdate] had it's info changed in a desktop mode session */ fun logTaskInfoChanged(taskUpdate: TaskUpdate) { val sessionId = currentSessionId.get() if (sessionId == NO_SESSION_ID) { ProtoLog.w( WM_SHELL_DESKTOP_MODE, - "DesktopModeLogger: No session id found for logging task info changed" + "DesktopModeLogger: No session id found for logging task info changed", ) return } @@ -173,11 +163,12 @@ class DesktopModeEventLogger { WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging task info changed, session: %s taskId: %s", sessionId, - taskUpdate.instanceId + taskUpdate.instanceId, ) logTaskUpdate( FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED, - sessionId, taskUpdate + sessionId, + taskUpdate, ) } @@ -200,30 +191,32 @@ class DesktopModeEventLogger { if (sessionId == NO_SESSION_ID) { ProtoLog.w( WM_SHELL_DESKTOP_MODE, - "DesktopModeLogger: No session id found for logging start of task resizing" + "DesktopModeLogger: No session id found for logging start of task resizing", ) return } - val taskSizeUpdate = createTaskSizeUpdate( - resizeTrigger, - inputMethod, - taskInfo, - taskWidth, - taskHeight, - displayController = displayController, - displayLayoutSize = displayLayoutSize, - ) + val taskSizeUpdate = + createTaskSizeUpdate( + resizeTrigger, + inputMethod, + taskInfo, + taskWidth, + taskHeight, + displayController = displayController, + displayLayoutSize = displayLayoutSize, + ) ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging task resize is starting, session: %s, taskSizeUpdate: %s", sessionId, - taskSizeUpdate + taskSizeUpdate, ) logTaskSizeUpdated( FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE, - sessionId, taskSizeUpdate + sessionId, + taskSizeUpdate, ) } @@ -245,31 +238,33 @@ class DesktopModeEventLogger { if (sessionId == NO_SESSION_ID) { ProtoLog.w( WM_SHELL_DESKTOP_MODE, - "DesktopModeLogger: No session id found for logging end of task resizing" + "DesktopModeLogger: No session id found for logging end of task resizing", ) return } - val taskSizeUpdate = createTaskSizeUpdate( - resizeTrigger, - inputMethod, - taskInfo, - taskWidth, - taskHeight, - displayController, - displayLayoutSize, - ) + val taskSizeUpdate = + createTaskSizeUpdate( + resizeTrigger, + inputMethod, + taskInfo, + taskWidth, + taskHeight, + displayController, + displayLayoutSize, + ) ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging task resize is ending, session: %s, taskSizeUpdate: %s", sessionId, - taskSizeUpdate + taskSizeUpdate, ) logTaskSizeUpdated( FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE, - sessionId, taskSizeUpdate + sessionId, + taskSizeUpdate, ) } @@ -287,12 +282,15 @@ class DesktopModeEventLogger { val height = taskHeight ?: taskBounds.height() val width = taskWidth ?: taskBounds.width() - val displaySize = when { - displayLayoutSize != null -> displayLayoutSize.height * displayLayoutSize.width - displayController != null -> displayController.getDisplayLayout(taskInfo.displayId) - ?.let { it.height() * it.width() } - else -> null - } + val displaySize = + when { + displayLayoutSize != null -> displayLayoutSize.height * displayLayoutSize.width + displayController != null -> + displayController.getDisplayLayout(taskInfo.displayId)?.let { + it.height() * it.width() + } + else -> null + } return TaskSizeUpdate( resizeTrigger, @@ -316,8 +314,8 @@ class DesktopModeEventLogger { taskHeight = 0, taskWidth = 0, taskX = 0, - taskY = 0 - ) + taskY = 0, + ), ) } @@ -343,7 +341,7 @@ class DesktopModeEventLogger { taskUpdate.minimizeReason?.reason ?: UNSET_MINIMIZE_REASON, taskUpdate.unminimizeReason?.reason ?: UNSET_UNMINIMIZE_REASON, /* visible_task_count */ - taskUpdate.visibleTaskCount + taskUpdate.visibleTaskCount, ) EventLogTags.writeWmShellDesktopModeTaskUpdate( /* task_event */ @@ -365,14 +363,14 @@ class DesktopModeEventLogger { taskUpdate.minimizeReason?.reason ?: UNSET_MINIMIZE_REASON, taskUpdate.unminimizeReason?.reason ?: UNSET_UNMINIMIZE_REASON, /* visible_task_count */ - taskUpdate.visibleTaskCount + taskUpdate.visibleTaskCount, ) } private fun logTaskSizeUpdated( resizingStage: Int, sessionId: Int, - taskSizeUpdate: TaskSizeUpdate + taskSizeUpdate: TaskSizeUpdate, ) { FrameworkStatsLog.write( DESKTOP_MODE_TASK_SIZE_UPDATED_ATOM_ID, @@ -393,7 +391,7 @@ class DesktopModeEventLogger { /* task_width */ taskSizeUpdate.taskWidth, /* display_area */ - taskSizeUpdate.displayArea ?: -1 + taskSizeUpdate.displayArea ?: -1, ) } @@ -410,7 +408,6 @@ class DesktopModeEventLogger { * @property taskY y-coordinate of the top-left corner * @property minimizeReason the reason the task was minimized * @property unminimizeEvent the reason the task was unminimized - * */ data class TaskUpdate( val instanceId: Int, @@ -425,8 +422,7 @@ class DesktopModeEventLogger { ) /** - * Describes a task size update (resizing, snapping or maximizing to - * stable bounds). + * Describes a task size update (resizing, snapping or maximizing to stable bounds). * * @property resizeTrigger the trigger for task resize * @property inputMethod the input method for resizing this task @@ -450,9 +446,7 @@ class DesktopModeEventLogger { fun getInputMethodFromMotionEvent(e: MotionEvent?): InputMethod { if (e == null) return InputMethod.UNKNOWN_INPUT_METHOD - val toolType = e.getToolType( - e.findPointerIndex(e.getPointerId(0)) - ) + val toolType = e.getToolType(e.findPointerIndex(e.getPointerId(0))) return when { toolType == TOOL_TYPE_STYLUS -> InputMethod.STYLUS toolType == TOOL_TYPE_MOUSE -> InputMethod.MOUSE @@ -474,8 +468,7 @@ class DesktopModeEventLogger { .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_TASK_LIMIT ), MINIMIZE_BUTTON( // TODO(b/356843241): use this enum value - FrameworkStatsLog - .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_BUTTON + FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_BUTTON ), } @@ -611,20 +604,16 @@ class DesktopModeEventLogger { */ enum class InputMethod(val method: Int) { UNKNOWN_INPUT_METHOD( - FrameworkStatsLog - .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD + FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD ), TOUCH( - FrameworkStatsLog - .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__TOUCH_INPUT_METHOD + FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__TOUCH_INPUT_METHOD ), STYLUS( - FrameworkStatsLog - .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__STYLUS_INPUT_METHOD + FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__STYLUS_INPUT_METHOD ), MOUSE( - FrameworkStatsLog - .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__MOUSE_INPUT_METHOD + FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__MOUSE_INPUT_METHOD ), TOUCHPAD( FrameworkStatsLog @@ -643,4 +632,4 @@ class DesktopModeEventLogger { FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED @VisibleForTesting const val NO_SESSION_ID = 0 } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt index 2b0724d64d0e..1ddb834399cb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt @@ -16,30 +16,28 @@ package com.android.wm.shell.desktopmode -import android.hardware.input.KeyGestureEvent - -import android.hardware.input.InputManager -import android.hardware.input.InputManager.KeyGestureEventHandler -import android.os.IBinder -import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut -import com.android.wm.shell.ShellTaskOrganizer import android.app.ActivityManager.RunningTaskInfo -import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel -import com.android.internal.protolog.ProtoLog import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.content.Context +import android.hardware.input.InputManager +import android.hardware.input.InputManager.KeyGestureEventHandler +import android.hardware.input.KeyGestureEvent +import android.os.IBinder import com.android.hardware.input.Flags.manageKeyGestures +import com.android.internal.protolog.ProtoLog +import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut import com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts +import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.ShellExecutor -import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger -import com.android.wm.shell.transition.FocusTransitionObserver +import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.annotations.ShellMainThread +import com.android.wm.shell.transition.FocusTransitionObserver +import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel import java.util.Optional -/** - * Handles key gesture events (keyboard shortcuts) in Desktop Mode. - */ +/** Handles key gesture events (keyboard shortcuts) in Desktop Mode. */ class DesktopModeKeyGestureHandler( private val context: Context, private val desktopModeWindowDecorViewModel: Optional<DesktopModeWindowDecorViewModel>, @@ -48,70 +46,86 @@ class DesktopModeKeyGestureHandler( private val shellTaskOrganizer: ShellTaskOrganizer, private val focusTransitionObserver: FocusTransitionObserver, @ShellMainThread private val mainExecutor: ShellExecutor, - ) : KeyGestureEventHandler { + private val displayController: DisplayController, +) : KeyGestureEventHandler { init { inputManager.registerKeyGestureEventHandler(this) } override fun handleKeyGestureEvent(event: KeyGestureEvent, focusedToken: IBinder?): Boolean { - if (!isKeyGestureSupported(event.keyGestureType) || !desktopTasksController.isPresent - || !desktopModeWindowDecorViewModel.isPresent) { + if ( + !isKeyGestureSupported(event.keyGestureType) || + !desktopTasksController.isPresent || + !desktopModeWindowDecorViewModel.isPresent + ) { return false } when (event.keyGestureType) { KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY -> { logV("Key gesture MOVE_TO_NEXT_DISPLAY is handled") getGloballyFocusedFreeformTask()?.let { - desktopTasksController.get().moveToNextDisplay( - it.taskId - ) + mainExecutor.execute { + desktopTasksController.get().moveToNextDisplay(it.taskId) + } } return true } KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW -> { logV("Key gesture SNAP_LEFT_FREEFORM_WINDOW is handled") getGloballyFocusedFreeformTask()?.let { - desktopModeWindowDecorViewModel.get().onSnapResize( - it.taskId, - true, - DesktopModeEventLogger.Companion.InputMethod.KEYBOARD, - /* fromMenu= */ false - ) + mainExecutor.execute { + desktopModeWindowDecorViewModel + .get() + .onSnapResize( + it.taskId, + true, + DesktopModeEventLogger.Companion.InputMethod.KEYBOARD, + /* fromMenu= */ false, + ) + } } return true } KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW -> { logV("Key gesture SNAP_RIGHT_FREEFORM_WINDOW is handled") getGloballyFocusedFreeformTask()?.let { - desktopModeWindowDecorViewModel.get().onSnapResize( - it.taskId, - false, - DesktopModeEventLogger.Companion.InputMethod.KEYBOARD, - /* fromMenu= */ false - ) + mainExecutor.execute { + desktopModeWindowDecorViewModel + .get() + .onSnapResize( + it.taskId, + false, + DesktopModeEventLogger.Companion.InputMethod.KEYBOARD, + /* fromMenu= */ false, + ) + } } return true } KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW -> { logV("Key gesture TOGGLE_MAXIMIZE_FREEFORM_WINDOW is handled") - getGloballyFocusedFreeformTask()?.let { - desktopTasksController.get().toggleDesktopTaskSize( - it, - ResizeTrigger.MAXIMIZE_MENU, - DesktopModeEventLogger.Companion.InputMethod.KEYBOARD, - ) + getGloballyFocusedFreeformTask()?.let { taskInfo -> + mainExecutor.execute { + desktopTasksController + .get() + .toggleDesktopTaskSize( + taskInfo, + ToggleTaskSizeInteraction( + isMaximized = isTaskMaximized(taskInfo, displayController), + source = ToggleTaskSizeInteraction.Source.KEYBOARD_SHORTCUT, + inputMethod = + DesktopModeEventLogger.Companion.InputMethod.KEYBOARD, + ), + ) + } } return true } KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW -> { logV("Key gesture MINIMIZE_FREEFORM_WINDOW is handled") getGloballyFocusedFreeformTask()?.let { - mainExecutor.execute { - desktopTasksController.get().minimizeTask( - it, - ) - } + mainExecutor.execute { desktopTasksController.get().minimizeTask(it) } } return true } @@ -119,16 +133,17 @@ class DesktopModeKeyGestureHandler( } } - override fun isKeyGestureSupported(gestureType: Int): Boolean = when (gestureType) { - KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY - -> enableMoveToNextDisplayShortcut() - KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW, - KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW, - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW, - KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW - -> enableTaskResizingKeyboardShortcuts() && manageKeyGestures() - else -> false - } + override fun isKeyGestureSupported(gestureType: Int): Boolean = + when (gestureType) { + KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY -> + enableMoveToNextDisplayShortcut() + KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW, + KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW, + KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW -> + enableTaskResizingKeyboardShortcuts() && manageKeyGestures() + else -> false + } // TODO: b/364154795 - wait for the completion of moveToNextDisplay transition, otherwise it // will pick a wrong task when a user quickly perform other actions with keyboard shortcuts @@ -136,7 +151,7 @@ class DesktopModeKeyGestureHandler( private fun getGloballyFocusedFreeformTask(): RunningTaskInfo? = shellTaskOrganizer.getRunningTasks().find { taskInfo -> taskInfo.windowingMode == WINDOWING_MODE_FREEFORM && - focusTransitionObserver.hasGlobalFocus(taskInfo) + focusTransitionObserver.hasGlobalFocus(taskInfo) } private fun logV(msg: String, vararg arguments: Any?) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt index 41febdfb3c2e..dfa2d9b6bb63 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt @@ -60,7 +60,7 @@ class DesktopModeLoggerTransitionObserver( context: Context, shellInit: ShellInit, private val transitions: Transitions, - private val desktopModeEventLogger: DesktopModeEventLogger + private val desktopModeEventLogger: DesktopModeEventLogger, ) : Transitions.TransitionObserver { init { @@ -89,7 +89,8 @@ class DesktopModeLoggerTransitionObserver( transitions.registerObserver(this) SystemProperties.set( VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY, - VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY_DEFAULT_VALUE) + VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY_DEFAULT_VALUE, + ) desktopModeEventLogger.logTaskInfoStateInit() } @@ -97,13 +98,13 @@ class DesktopModeLoggerTransitionObserver( transition: IBinder, info: TransitionInfo, startTransaction: SurfaceControl.Transaction, - finishTransaction: SurfaceControl.Transaction + finishTransaction: SurfaceControl.Transaction, ) { // this was a new recents animation if (info.isExitToRecentsTransition() && tasksSavedForRecents.isEmpty()) { ProtoLog.v( WM_SHELL_DESKTOP_MODE, - "DesktopModeLogger: Recents animation running, saving tasks for later" + "DesktopModeLogger: Recents animation running, saving tasks for later", ) // TODO (b/326391303) - avoid logging session exit if we can identify a cancelled // recents animation @@ -129,7 +130,7 @@ class DesktopModeLoggerTransitionObserver( ) { ProtoLog.v( WM_SHELL_DESKTOP_MODE, - "DesktopModeLogger: Canceled recents animation, restoring tasks" + "DesktopModeLogger: Canceled recents animation, restoring tasks", ) // restore saved tasks in the updated set and clear for next use postTransitionVisibleFreeformTasks += tasksSavedForRecents @@ -140,7 +141,7 @@ class DesktopModeLoggerTransitionObserver( identifyLogEventAndUpdateState( transitionInfo = info, preTransitionVisibleFreeformTasks = visibleFreeformTaskInfos, - postTransitionVisibleFreeformTasks = postTransitionVisibleFreeformTasks + postTransitionVisibleFreeformTasks = postTransitionVisibleFreeformTasks, ) wasPreviousTransitionExitToOverview = info.isExitToRecentsTransition() } @@ -200,7 +201,7 @@ class DesktopModeLoggerTransitionObserver( ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: taskInfo map after processing changes %s", - postTransitionFreeformTasks.size() + postTransitionFreeformTasks.size(), ) return postTransitionFreeformTasks @@ -226,7 +227,7 @@ class DesktopModeLoggerTransitionObserver( private fun identifyLogEventAndUpdateState( transitionInfo: TransitionInfo, preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, - postTransitionVisibleFreeformTasks: SparseArray<TaskInfo> + postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, ) { if ( postTransitionVisibleFreeformTasks.isEmpty() && @@ -236,12 +237,10 @@ class DesktopModeLoggerTransitionObserver( // Sessions is finishing, log task updates followed by an exit event identifyAndLogTaskUpdates( preTransitionVisibleFreeformTasks, - postTransitionVisibleFreeformTasks + postTransitionVisibleFreeformTasks, ) - desktopModeEventLogger.logSessionExit( - getExitReason(transitionInfo) - ) + desktopModeEventLogger.logSessionExit(getExitReason(transitionInfo)) isSessionActive = false } else if ( postTransitionVisibleFreeformTasks.isNotEmpty() && @@ -250,19 +249,17 @@ class DesktopModeLoggerTransitionObserver( ) { // Session is starting, log enter event followed by task updates isSessionActive = true - desktopModeEventLogger.logSessionEnter( - getEnterReason(transitionInfo) - ) + desktopModeEventLogger.logSessionEnter(getEnterReason(transitionInfo)) identifyAndLogTaskUpdates( preTransitionVisibleFreeformTasks, - postTransitionVisibleFreeformTasks + postTransitionVisibleFreeformTasks, ) } else if (isSessionActive) { // Session is neither starting, nor finishing, log task updates if there are any identifyAndLogTaskUpdates( preTransitionVisibleFreeformTasks, - postTransitionVisibleFreeformTasks + postTransitionVisibleFreeformTasks, ) } @@ -274,11 +271,11 @@ class DesktopModeLoggerTransitionObserver( /** Compare the old and new state of taskInfos and identify and log the changes */ private fun identifyAndLogTaskUpdates( preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, - postTransitionVisibleFreeformTasks: SparseArray<TaskInfo> + postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, ) { postTransitionVisibleFreeformTasks.forEach { taskId, taskInfo -> - val currentTaskUpdate = buildTaskUpdateForTask(taskInfo, - postTransitionVisibleFreeformTasks.size()) + val currentTaskUpdate = + buildTaskUpdateForTask(taskInfo, postTransitionVisibleFreeformTasks.size()) val previousTaskInfo = preTransitionVisibleFreeformTasks[taskId] when { // new tasks added @@ -287,16 +284,20 @@ class DesktopModeLoggerTransitionObserver( Trace.setCounter( Trace.TRACE_TAG_WINDOW_MANAGER, VISIBLE_TASKS_COUNTER_NAME, - postTransitionVisibleFreeformTasks.size().toLong() + postTransitionVisibleFreeformTasks.size().toLong(), + ) + SystemProperties.set( + VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY, + postTransitionVisibleFreeformTasks.size().toString(), ) - SystemProperties.set(VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY, - postTransitionVisibleFreeformTasks.size().toString()) } // old tasks that were resized or repositioned // TODO(b/347935387): Log changes only once they are stable. - buildTaskUpdateForTask(previousTaskInfo, postTransitionVisibleFreeformTasks.size()) - != currentTaskUpdate -> - desktopModeEventLogger.logTaskInfoChanged(currentTaskUpdate) + buildTaskUpdateForTask( + previousTaskInfo, + postTransitionVisibleFreeformTasks.size(), + ) != currentTaskUpdate -> + desktopModeEventLogger.logTaskInfoChanged(currentTaskUpdate) } } @@ -304,14 +305,17 @@ class DesktopModeLoggerTransitionObserver( preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo -> if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) { desktopModeEventLogger.logTaskRemoved( - buildTaskUpdateForTask(taskInfo, postTransitionVisibleFreeformTasks.size())) + buildTaskUpdateForTask(taskInfo, postTransitionVisibleFreeformTasks.size()) + ) Trace.setCounter( Trace.TRACE_TAG_WINDOW_MANAGER, VISIBLE_TASKS_COUNTER_NAME, - postTransitionVisibleFreeformTasks.size().toLong() + postTransitionVisibleFreeformTasks.size().toLong(), + ) + SystemProperties.set( + VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY, + postTransitionVisibleFreeformTasks.size().toString(), ) - SystemProperties.set(VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY, - postTransitionVisibleFreeformTasks.size().toString()) } } } @@ -332,45 +336,50 @@ class DesktopModeLoggerTransitionObserver( /** Get [EnterReason] for this session enter */ private fun getEnterReason(transitionInfo: TransitionInfo): EnterReason { - val enterReason = when { - transitionInfo.type == WindowManager.TRANSIT_WAKE - // If there is a screen lock, desktop window entry is after dismissing keyguard - || (transitionInfo.type == WindowManager.TRANSIT_TO_BACK - && wasPreviousTransitionExitByScreenOff) -> EnterReason.SCREEN_ON - transitionInfo.type == Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP -> - EnterReason.APP_HANDLE_DRAG - transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON -> - EnterReason.APP_HANDLE_MENU_BUTTON - transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW -> - EnterReason.APP_FROM_OVERVIEW - transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT -> - EnterReason.KEYBOARD_SHORTCUT_ENTER - // NOTE: the below condition also applies for EnterReason quickswitch - transitionInfo.type == WindowManager.TRANSIT_TO_FRONT -> EnterReason.OVERVIEW - // Enter desktop mode from cancelled recents has no transition. Enter is detected on the - // next transition involving freeform windows. - // TODO(b/346564416): Modify logging for cancelled recents once it transition is - // changed. Also see how to account to time difference between actual enter time and - // time of this log. Also account for the missed session when exit happens just after - // a cancelled recents. - wasPreviousTransitionExitToOverview -> EnterReason.OVERVIEW - transitionInfo.type == WindowManager.TRANSIT_OPEN -> EnterReason.APP_FREEFORM_INTENT - else -> { - ProtoLog.w( - WM_SHELL_DESKTOP_MODE, - "Unknown enter reason for transition type: %s", - transitionInfo.type - ) - EnterReason.UNKNOWN_ENTER + val enterReason = + when { + transitionInfo.type == WindowManager.TRANSIT_WAKE + // If there is a screen lock, desktop window entry is after dismissing keyguard + || + (transitionInfo.type == WindowManager.TRANSIT_TO_BACK && + wasPreviousTransitionExitByScreenOff) -> EnterReason.SCREEN_ON + transitionInfo.type == Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP -> + EnterReason.APP_HANDLE_DRAG + transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON -> + EnterReason.APP_HANDLE_MENU_BUTTON + transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW -> + EnterReason.APP_FROM_OVERVIEW + transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT -> + EnterReason.KEYBOARD_SHORTCUT_ENTER + // NOTE: the below condition also applies for EnterReason quickswitch + transitionInfo.type == WindowManager.TRANSIT_TO_FRONT -> EnterReason.OVERVIEW + // Enter desktop mode from cancelled recents has no transition. Enter is detected on + // the + // next transition involving freeform windows. + // TODO(b/346564416): Modify logging for cancelled recents once it transition is + // changed. Also see how to account to time difference between actual enter time + // and + // time of this log. Also account for the missed session when exit happens just + // after + // a cancelled recents. + wasPreviousTransitionExitToOverview -> EnterReason.OVERVIEW + transitionInfo.type == WindowManager.TRANSIT_OPEN -> EnterReason.APP_FREEFORM_INTENT + else -> { + ProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "Unknown enter reason for transition type: %s", + transitionInfo.type, + ) + EnterReason.UNKNOWN_ENTER + } } - } wasPreviousTransitionExitByScreenOff = false return enterReason } /** Get [ExitReason] for this session exit */ private fun getExitReason(transitionInfo: TransitionInfo): ExitReason = - when { + when { transitionInfo.type == WindowManager.TRANSIT_SLEEP -> { wasPreviousTransitionExitByScreenOff = true ExitReason.SCREEN_OFF @@ -387,7 +396,7 @@ class DesktopModeLoggerTransitionObserver( ProtoLog.w( WM_SHELL_DESKTOP_MODE, "Unknown exit reason for transition type: %s", - transitionInfo.type + transitionInfo.type, ) ExitReason.UNKNOWN_EXIT } @@ -413,8 +422,7 @@ class DesktopModeLoggerTransitionObserver( } companion object { - @VisibleForTesting - const val VISIBLE_TASKS_COUNTER_NAME = "desktop_mode_visible_tasks" + @VisibleForTesting const val VISIBLE_TASKS_COUNTER_NAME = "desktop_mode_visible_tasks" @VisibleForTesting const val VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY = "debug.tracing." + VISIBLE_TASKS_COUNTER_NAME diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt index d6fccd116061..9b3caca7b2d4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt @@ -43,7 +43,7 @@ object DesktopModeTransitionTypes { TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON, TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT, - TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN + TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN, ) } @@ -73,7 +73,7 @@ object DesktopModeTransitionTypes { TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG, TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON, TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT, - TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN + TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN, ) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt index 2c432bcb55ab..301ba9e76fc5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt @@ -75,7 +75,7 @@ class DesktopModeUiEventLogger( instanceId: InstanceId, uid: Int, packageName: String, - event: DesktopUiEventEnum + event: DesktopUiEventEnum, ) { if (packageName.isEmpty() || uid < 0) { logD("Skip logging since package name is empty or bad uid") @@ -84,11 +84,12 @@ class DesktopModeUiEventLogger( uiEventLogger.logWithInstanceId(event, uid, packageName, instanceId) } - private fun getUid(packageName: String, userId: Int): Int = try { - packageManager.getApplicationInfoAsUser(packageName, /* flags= */ 0, userId).uid - } catch (e: PackageManager.NameNotFoundException) { - INVALID_PACKAGE_UID - } + private fun getUid(packageName: String, userId: Int): Int = + try { + packageManager.getApplicationInfoAsUser(packageName, /* flags= */ 0, userId).uid + } catch (e: PackageManager.NameNotFoundException) { + INVALID_PACKAGE_UID + } private fun logD(msg: String, vararg arguments: Any?) { ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) @@ -103,8 +104,12 @@ class DesktopModeUiEventLogger( DESKTOP_WINDOW_CORNER_DRAG_RESIZE(1722), @UiEvent(doc = "Tap on the window header maximize button in desktop windowing mode") DESKTOP_WINDOW_MAXIMIZE_BUTTON_TAP(1723), + @UiEvent(doc = "Tap on the window header restore button in desktop windowing mode") + DESKTOP_WINDOW_RESTORE_BUTTON_TAP(2017), @UiEvent(doc = "Double tap on window header to maximize it in desktop windowing mode") DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_MAXIMIZE(1724), + @UiEvent(doc = "Double tap on window header to restore from maximize in desktop windowing") + DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_RESTORE(2018), @UiEvent(doc = "Tap on the window Handle to open the Handle Menu") DESKTOP_WINDOW_APP_HANDLE_TAP(1998), @UiEvent(doc = "Tap on the desktop mode option under app handle menu") @@ -136,7 +141,11 @@ class DesktopModeUiEventLogger( @UiEvent(doc = "Tap on the tile to left option in the maximize button menu") DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_TILE_TO_LEFT(2012), @UiEvent(doc = "Tap on the tile to right option in the maximize button menu") - DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_TILE_TO_RIGHT(2013); + DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_TILE_TO_RIGHT(2013), + @UiEvent(doc = "Moving the desktop window by dragging the header") + DESKTOP_WINDOW_MOVE_BY_HEADER_DRAG(2021), + @UiEvent(doc = "Double tap on the window header to refocus a desktop window") + DESKTOP_WINDOW_HEADER_TAP_TO_REFOCUS(2022); override fun getId(): Int = mId } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt index c7cf31081c8b..14623cf9e703 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt @@ -28,6 +28,7 @@ import android.content.res.Configuration.ORIENTATION_PORTRAIT import android.graphics.Rect import android.os.SystemProperties import android.util.Size +import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout val DESKTOP_MODE_INITIAL_BOUNDS_SCALE: Float = @@ -36,21 +37,14 @@ val DESKTOP_MODE_INITIAL_BOUNDS_SCALE: Float = val DESKTOP_MODE_LANDSCAPE_APP_PADDING: Int = SystemProperties.getInt("persist.wm.debug.desktop_mode_landscape_app_padding", 25) -/** - * Calculates the initial bounds to enter desktop, centered on the display. - */ +/** Calculates the initial bounds to enter desktop, centered on the display. */ fun calculateDefaultDesktopTaskBounds(displayLayout: DisplayLayout): Rect { // TODO(b/319819547): Account for app constraints so apps do not become letterboxed val desiredWidth = (displayLayout.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt() val desiredHeight = (displayLayout.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt() val heightOffset = (displayLayout.height() - desiredHeight) / 2 val widthOffset = (displayLayout.width() - desiredWidth) / 2 - return Rect( - widthOffset, - heightOffset, - desiredWidth + widthOffset, - desiredHeight + heightOffset - ) + return Rect(widthOffset, heightOffset, desiredWidth + widthOffset, desiredHeight + heightOffset) } /** @@ -62,7 +56,7 @@ fun calculateDefaultDesktopTaskBounds(displayLayout: DisplayLayout): Rect { fun calculateInitialBounds( displayLayout: DisplayLayout, taskInfo: RunningTaskInfo, - scale: Float = DESKTOP_MODE_INITIAL_BOUNDS_SCALE + scale: Float = DESKTOP_MODE_INITIAL_BOUNDS_SCALE, ): Rect { val screenBounds = Rect(0, 0, displayLayout.width(), displayLayout.height()) val appAspectRatio = calculateAspectRatio(taskInfo) @@ -87,8 +81,10 @@ fun calculateInitialBounds( if (isFixedOrientationPortrait(topActivityInfo.screenOrientation)) { // For portrait resizeable activities, respect apps fullscreen width but // apply ideal size height. - Size(taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth, - idealSize.height) + Size( + taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth, + idealSize.height, + ) } else { // For landscape resizeable activities, simply apply ideal size. idealSize @@ -108,7 +104,7 @@ fun calculateInitialBounds( // apply custom app width. Size( customPortraitWidthForLandscapeApp, - taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight + taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight, ) } else { // For portrait resizeable activities, simply apply ideal size. @@ -122,7 +118,7 @@ fun calculateInitialBounds( maximizeSizeGivenAspectRatio( taskInfo, Size(customPortraitWidthForLandscapeApp, idealSize.height), - appAspectRatio + appAspectRatio, ) } else { // For portrait unresizeable activities, calculate maximum size (within the @@ -140,13 +136,10 @@ fun calculateInitialBounds( } /** - * Calculates the maximized bounds of a task given in the given [DisplayLayout], taking - * resizability into consideration. + * Calculates the maximized bounds of a task given in the given [DisplayLayout], taking resizability + * into consideration. */ -fun calculateMaximizeBounds( - displayLayout: DisplayLayout, - taskInfo: RunningTaskInfo, -): Rect { +fun calculateMaximizeBounds(displayLayout: DisplayLayout, taskInfo: RunningTaskInfo): Rect { val stableBounds = Rect() displayLayout.getStableBounds(stableBounds) if (taskInfo.isResizeable) { @@ -155,10 +148,13 @@ fun calculateMaximizeBounds( } else { // if non-resizable then calculate max bounds according to aspect ratio val activityAspectRatio = calculateAspectRatio(taskInfo) - val newSize = maximizeSizeGivenAspectRatio(taskInfo, - Size(stableBounds.width(), stableBounds.height()), activityAspectRatio) - return centerInArea( - newSize, stableBounds, stableBounds.left, stableBounds.top) + val newSize = + maximizeSizeGivenAspectRatio( + taskInfo, + Size(stableBounds.width(), stableBounds.height()), + activityAspectRatio, + ) + return centerInArea(newSize, stableBounds, stableBounds.left, stableBounds.top) } } @@ -169,7 +165,7 @@ fun calculateMaximizeBounds( fun maximizeSizeGivenAspectRatio( taskInfo: RunningTaskInfo, targetArea: Size, - aspectRatio: Float + aspectRatio: Float, ): Size { val targetHeight = targetArea.height val targetWidth = targetArea.width @@ -211,16 +207,36 @@ fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float { minOf(appBounds.height(), appBounds.width()).toFloat() } +/** Returns whether the task is maximized. */ +fun isTaskMaximized(taskInfo: RunningTaskInfo, displayController: DisplayController): Boolean { + val displayLayout = + displayController.getDisplayLayout(taskInfo.displayId) + ?: error("Could not get display layout for display=${taskInfo.displayId}") + val stableBounds = Rect() + displayLayout.getStableBounds(stableBounds) + return isTaskMaximized(taskInfo, stableBounds) +} + +/** Returns whether the task is maximized. */ +fun isTaskMaximized(taskInfo: RunningTaskInfo, stableBounds: Rect): Boolean { + val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds + return if (taskInfo.isResizeable) { + isTaskBoundsEqual(currentTaskBounds, stableBounds) + } else { + isTaskWidthOrHeightEqual(currentTaskBounds, stableBounds) + } +} + /** Returns true if task's width or height is maximized else returns false. */ fun isTaskWidthOrHeightEqual(taskBounds: Rect, stableBounds: Rect): Boolean { return taskBounds.width() == stableBounds.width() || - taskBounds.height() == stableBounds.height() + taskBounds.height() == stableBounds.height() } /** Returns true if task bound is equal to stable bounds else returns false. */ fun isTaskBoundsEqual(taskBounds: Rect, stableBounds: Rect): Boolean { return taskBounds.width() == stableBounds.width() && - taskBounds.height() == stableBounds.height() + taskBounds.height() == stableBounds.height() } /** @@ -248,8 +264,8 @@ private val TaskInfo.canChangeAspectRatio: Boolean get() = isResizeable && !appCompatTaskInfo.hasMinAspectRatioOverride() /** - * Adjusts bounds to be positioned in the middle of the area provided, not necessarily the - * entire screen, as area can be offset by left and top start. + * Adjusts bounds to be positioned in the middle of the area provided, not necessarily the entire + * screen, as area can be offset by left and top start. */ fun centerInArea(desiredSize: Size, areaBounds: Rect, leftStart: Int, topStart: Int): Rect { val heightOffset = (areaBounds.height() - desiredSize.height) / 2 @@ -286,6 +302,6 @@ private fun TaskInfo.hasPortraitTopActivity(): Boolean { } private fun hasFullscreenOverride(taskInfo: RunningTaskInfo): Boolean { - return taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled - || taskInfo.appCompatTaskInfo.isSystemFullscreenOverrideEnabled + return taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled || + taskInfo.appCompatTaskInfo.isSystemFullscreenOverrideEnabled } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java index 80d8ecc127fe..cd37113666bb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java @@ -53,6 +53,7 @@ import android.view.animation.DecelerateInterpolator; import androidx.annotation.VisibleForTesting; import com.android.internal.policy.SystemBarUtils; +import com.android.window.flags.Flags; import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.common.DisplayController; @@ -245,9 +246,17 @@ public class DesktopModeVisualIndicator { final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); final Resources resources = mContext.getResources(); final DisplayMetrics metrics = resources.getDisplayMetrics(); - final int screenWidth = metrics.widthPixels; - final int screenHeight = metrics.heightPixels; - + final int screenWidth; + final int screenHeight; + if (Flags.enableBugFixesForSecondaryDisplay()) { + final DisplayLayout displayLayout = + mDisplayController.getDisplayLayout(mTaskInfo.displayId); + screenWidth = displayLayout.width(); + screenHeight = displayLayout.height(); + } else { + screenWidth = metrics.widthPixels; + screenHeight = metrics.heightPixels; + } mView = new View(mContext); final SurfaceControl.Builder builder = new SurfaceControl.Builder(); mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt index 7fcb7678f6af..bccb609c41e8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.desktopmode -import android.content.Context import android.graphics.Rect import android.graphics.Region import android.util.ArrayMap @@ -30,11 +29,8 @@ import androidx.core.util.keyIterator import androidx.core.util.valueIterator import com.android.internal.protolog.ProtoLog import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository -import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.annotations.ShellMainThread -import com.android.wm.shell.shared.desktopmode.DesktopModeStatus -import com.android.wm.shell.sysui.ShellInit import java.io.PrintWriter import java.util.concurrent.Executor import java.util.function.Consumer @@ -42,26 +38,23 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch /** Tracks desktop data for Android Desktop Windowing. */ -class DesktopRepository ( - private val context: Context, - shellInit: ShellInit, +class DesktopRepository( private val persistentRepository: DesktopPersistentRepository, - private val repositoryInitializer: DesktopRepositoryInitializer, @ShellMainThread private val mainCoroutineScope: CoroutineScope, -){ - + val userId: Int, +) { /** * Task data tracked per desktop. * * @property activeTasks task ids of active tasks currently or previously visible in Desktop - * mode session. Tasks become inactive when task closes or when desktop mode session ends. + * mode session. Tasks become inactive when task closes or when desktop mode session ends. * @property visibleTasks task ids for active freeform tasks that are currently visible. There - * might be other active tasks in desktop mode that are not visible. + * might be other active tasks in desktop mode that are not visible. * @property minimizedTasks task ids for active freeform tasks that are currently minimized. * @property closingTasks task ids for tasks that are going to close, but are currently visible. * @property freeformTasksInZOrder list of current freeform task ids ordered from top to bottom * @property fullImmersiveTaskId the task id of the desktop task that is in full-immersive mode. - * (top is at index 0). + * (top is at index 0). */ private data class DesktopTaskData( val activeTasks: ArraySet<Int> = ArraySet(), @@ -72,14 +65,16 @@ class DesktopRepository ( val freeformTasksInZOrder: ArrayList<Int> = ArrayList(), var fullImmersiveTaskId: Int? = null, ) { - fun deepCopy(): DesktopTaskData = DesktopTaskData( - activeTasks = ArraySet(activeTasks), - visibleTasks = ArraySet(visibleTasks), - minimizedTasks = ArraySet(minimizedTasks), - closingTasks = ArraySet(closingTasks), - freeformTasksInZOrder = ArrayList(freeformTasksInZOrder), - fullImmersiveTaskId = fullImmersiveTaskId - ) + fun deepCopy(): DesktopTaskData = + DesktopTaskData( + activeTasks = ArraySet(activeTasks), + visibleTasks = ArraySet(visibleTasks), + minimizedTasks = ArraySet(minimizedTasks), + closingTasks = ArraySet(closingTasks), + freeformTasksInZOrder = ArrayList(freeformTasksInZOrder), + fullImmersiveTaskId = fullImmersiveTaskId, + ) + fun clear() { activeTasks.clear() visibleTasks.clear() @@ -111,21 +106,12 @@ class DesktopRepository ( private var desktopGestureExclusionListener: Consumer<Region>? = null private var desktopGestureExclusionExecutor: Executor? = null - private val desktopTaskDataByDisplayId = object : SparseArray<DesktopTaskData>() { - /** Gets [DesktopTaskData] for existing [displayId] or creates a new one. */ - fun getOrCreate(displayId: Int): DesktopTaskData = - this[displayId] ?: DesktopTaskData().also { this[displayId] = it } - } - - init { - if (DesktopModeStatus.canEnterDesktopMode(context)) { - shellInit.addInitCallback(::initRepoFromPersistentStorage, this) + private val desktopTaskDataByDisplayId = + object : SparseArray<DesktopTaskData>() { + /** Gets [DesktopTaskData] for existing [displayId] or creates a new one. */ + fun getOrCreate(displayId: Int): DesktopTaskData = + this[displayId] ?: DesktopTaskData().also { this[displayId] = it } } - } - - private fun initRepoFromPersistentStorage() { - repositoryInitializer.initialize(this) - } /** Adds [activeTasksListener] to be notified of updates to active tasks. */ fun addActiveTaskListener(activeTasksListener: ActiveTasksListener) { @@ -137,9 +123,7 @@ class DesktopRepository ( visibleTasksListeners[visibleTasksListener] = executor desktopTaskDataByDisplayId.keyIterator().forEach { val visibleTaskCount = getVisibleTaskCount(it) - executor.execute { - visibleTasksListener.onTasksVisibilityChanged(it, visibleTaskCount) - } + executor.execute { visibleTasksListener.onTasksVisibilityChanged(it, visibleTaskCount) } } } @@ -201,8 +185,7 @@ class DesktopRepository ( /** Removes task from active task list of displays excluding the [excludedDisplayId]. */ fun removeActiveTask(taskId: Int, excludedDisplayId: Int? = null) { desktopTaskDataByDisplayId.forEach { displayId, desktopTaskData -> - if ((displayId != excludedDisplayId) - && desktopTaskData.activeTasks.remove(taskId)) { + if ((displayId != excludedDisplayId) && desktopTaskData.activeTasks.remove(taskId)) { logD("Removed active task=%d displayId=%d", taskId, displayId) updateActiveTasksListeners(displayId) } @@ -229,16 +212,18 @@ class DesktopRepository ( } fun isActiveTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.activeTasks } + fun isClosingTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.closingTasks } + fun isVisibleTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.visibleTasks } + fun isMinimizedTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.minimizedTasks } /** Checks if a task is the only visible, non-closing, non-minimized task on its display. */ fun isOnlyVisibleNonClosingTask(taskId: Int): Boolean = - desktopTaskDataSequence().any { it.visibleTasks - .subtract(it.closingTasks) - .subtract(it.minimizedTasks) - .singleOrNull() == taskId + desktopTaskDataSequence().any { + it.visibleTasks.subtract(it.closingTasks).subtract(it.minimizedTasks).singleOrNull() == + taskId } fun getActiveTasks(displayId: Int): ArraySet<Int> = @@ -272,10 +257,12 @@ class DesktopRepository ( /** * Updates visibility of a freeform task with [taskId] on [displayId] and notifies listeners. * - * If task was visible on a different display with a different [displayId], removes from - * the set of visible tasks on that display and notifies listeners. + * If task was visible on a different display with a different [displayId], removes from the set + * of visible tasks on that display and notifies listeners. */ fun updateTask(displayId: Int, taskId: Int, isVisible: Boolean) { + logD("updateTask taskId=%d, displayId=%d, isVisible=%b", taskId, displayId, isVisible) + if (isVisible) { // If task is visible, remove it from any other display besides [displayId]. removeVisibleTask(taskId, excludedDisplayId = displayId) @@ -293,8 +280,12 @@ class DesktopRepository ( } val newCount = getVisibleTaskCount(displayId) if (prevCount != newCount) { - logD("Update task visibility taskId=%d visible=%b displayId=%d", - taskId, isVisible, displayId) + logD( + "Update task visibility taskId=%d visible=%b displayId=%d", + taskId, + isVisible, + displayId, + ) logD("VisibleTaskCount has changed from %d to %d", prevCount, newCount) notifyVisibleTaskListeners(displayId, newCount) if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) { @@ -332,9 +323,8 @@ class DesktopRepository ( /** Gets number of visible tasks on given [displayId] */ fun getVisibleTaskCount(displayId: Int): Int = - desktopTaskDataByDisplayId[displayId]?.visibleTasks?.size ?: 0.also { - logD("getVisibleTaskCount=$it") - } + desktopTaskDataByDisplayId[displayId]?.visibleTasks?.size + ?: 0.also { logD("getVisibleTaskCount=$it") } /** * Adds task (or moves if it already exists) to the top of the ordered list. @@ -343,7 +333,9 @@ class DesktopRepository ( */ private fun addOrMoveFreeformTaskToTop(displayId: Int, taskId: Int) { logD("Add or move task to top: display=%d taskId=%d", taskId, displayId) - desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.remove(taskId) + desktopTaskDataByDisplayId.forEach { _, value -> + value.freeformTasksInZOrder.remove(taskId) + } desktopTaskDataByDisplayId.getOrCreate(displayId).freeformTasksInZOrder.add(0, taskId) // Unminimize the task if it is minimized. unminimizeTask(displayId, taskId) @@ -357,9 +349,8 @@ class DesktopRepository ( if (displayId == INVALID_DISPLAY) { // When a task vanishes it doesn't have a displayId. Find the display of the task and // mark it as minimized. - getDisplayIdForTask(taskId)?.let { - minimizeTask(it, taskId) - } ?: logW("Minimize task: No display id found for task: taskId=%d", taskId) + getDisplayIdForTask(taskId)?.let { minimizeTask(it, taskId) } + ?: logW("Minimize task: No display id found for task: taskId=%d", taskId) } else { logD("Minimize Task: display=%d, task=%d", displayId, taskId) desktopTaskDataByDisplayId.getOrCreate(displayId).minimizedTasks.add(taskId) @@ -373,8 +364,8 @@ class DesktopRepository ( /** Unminimizes the task for [taskId] and [displayId] */ fun unminimizeTask(displayId: Int, taskId: Int) { logD("Unminimize Task: display=%d, task=%d", displayId, taskId) - desktopTaskDataByDisplayId[displayId]?.minimizedTasks?.remove(taskId) ?: - logW("Unminimize Task: display=%d, task=%d, no task data", displayId, taskId) + desktopTaskDataByDisplayId[displayId]?.minimizedTasks?.remove(taskId) + ?: logW("Unminimize Task: display=%d, task=%d, no task data", displayId, taskId) } private fun getDisplayIdForTask(taskId: Int): Int? { @@ -407,16 +398,14 @@ class DesktopRepository ( desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.remove(taskId) boundsBeforeMaximizeByTaskId.remove(taskId) boundsBeforeFullImmersiveByTaskId.remove(taskId) - logD("Remaining freeform tasks: %s", - desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.toDumpString()) + logD( + "Remaining freeform tasks: %s", + desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.toDumpString(), + ) // Remove task from unminimized task if it is minimized. unminimizeTask(displayId, taskId) // Mark task as not in immersive if it was immersive. - setTaskInFullImmersiveState( - displayId = displayId, - taskId = taskId, - immersive = false - ) + setTaskInFullImmersiveState(displayId = displayId, taskId = taskId, immersive = false) removeActiveTask(taskId) removeVisibleTask(taskId) if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) { @@ -495,15 +484,16 @@ class DesktopRepository ( persistentRepository.addOrUpdateDesktop( // Use display id as desktop id for now since only once desktop per display // is supported. + userId = userId, desktopId = displayId, visibleTasks = desktopTaskDataByDisplayIdCopy.visibleTasks, minimizedTasks = desktopTaskDataByDisplayIdCopy.minimizedTasks, - freeformTasksInZOrder = desktopTaskDataByDisplayIdCopy.freeformTasksInZOrder + freeformTasksInZOrder = desktopTaskDataByDisplayIdCopy.freeformTasksInZOrder, ) } catch (exception: Exception) { logE( "An exception occurred while updating the persistent repository \n%s", - exception.stackTrace + exception.stackTrace, ) } } @@ -529,6 +519,7 @@ class DesktopRepository ( ) pw.println("${innerPrefix}minimizedTasks=${data.minimizedTasks.toDumpString()}") pw.println("${innerPrefix}fullImmersiveTaskId=${data.fullImmersiveTaskId}") + pw.println("${innerPrefix}wallpaperActivityToken=$wallpaperActivityToken") } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt index 94ac2e665f61..947a8dddb239 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt @@ -22,72 +22,81 @@ import android.window.DesktopModeFlags import com.android.wm.shell.freeform.TaskChangeListener /** Manages tasks handling specific to Android Desktop Mode. */ -class DesktopTaskChangeListener( - private val desktopRepository: DesktopRepository, -) : TaskChangeListener { +class DesktopTaskChangeListener(private val desktopUserRepositories: DesktopUserRepositories) : + TaskChangeListener { - override fun onTaskOpening(taskInfo: RunningTaskInfo) { - if (!isFreeformTask(taskInfo) && desktopRepository.isActiveTask(taskInfo.taskId)) { - desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId) - return + override fun onTaskOpening(taskInfo: RunningTaskInfo) { + val desktopRepository: DesktopRepository = + desktopUserRepositories.getProfile(taskInfo.userId) + if (!isFreeformTask(taskInfo) && desktopRepository.isActiveTask(taskInfo.taskId)) { + desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId) + return + } + if (isFreeformTask(taskInfo)) { + desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible) + } } - if (isFreeformTask(taskInfo)) { - desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible) - } - } - override fun onTaskChanging(taskInfo: RunningTaskInfo) { - if (!desktopRepository.isActiveTask(taskInfo.taskId)) return + override fun onTaskChanging(taskInfo: RunningTaskInfo) { + val desktopRepository: DesktopRepository = + desktopUserRepositories.getProfile(taskInfo.userId) + if (!desktopRepository.isActiveTask(taskInfo.taskId)) return - // Case 1: Freeform task is changed in Desktop Mode. - if (isFreeformTask(taskInfo)) { - if (taskInfo.isVisible) { - desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible) - } - desktopRepository.updateTask( - taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible) - } else { - // Case 2: Freeform task is changed outside Desktop Mode. - desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId) + // Case 1: Freeform task is changed in Desktop Mode. + if (isFreeformTask(taskInfo)) { + if (taskInfo.isVisible) { + desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible) + } + desktopRepository.updateTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible) + } else { + // Case 2: Freeform task is changed outside Desktop Mode. + desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId) + } } - } - // This method should only be used for scenarios where the task info changes are not propagated to - // [DesktopTaskChangeListener#onTaskChanging] via [TransitionsObserver]. - // Any changes to [DesktopRepository] from this method should be made carefully to minimize risk - // of race conditions and possible duplications with [onTaskChanging]. - override fun onNonTransitionTaskChanging(taskInfo: RunningTaskInfo) { - // TODO: b/367268953 - Propapagate usages from FreeformTaskListener to this method. - } + // This method should only be used for scenarios where the task info changes are not propagated + // to + // [DesktopTaskChangeListener#onTaskChanging] via [TransitionsObserver]. + // Any changes to [DesktopRepository] from this method should be made carefully to minimize risk + // of race conditions and possible duplications with [onTaskChanging]. + override fun onNonTransitionTaskChanging(taskInfo: RunningTaskInfo) { + // TODO: b/367268953 - Propapagate usages from FreeformTaskListener to this method. + } - override fun onTaskMovingToFront(taskInfo: RunningTaskInfo) { - if (!desktopRepository.isActiveTask(taskInfo.taskId)) return - if (!isFreeformTask(taskInfo)) { - desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId) + override fun onTaskMovingToFront(taskInfo: RunningTaskInfo) { + val desktopRepository: DesktopRepository = + desktopUserRepositories.getProfile(taskInfo.userId) + if (!desktopRepository.isActiveTask(taskInfo.taskId)) return + if (!isFreeformTask(taskInfo)) { + desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId) + } + // TODO: b/367268953 - Connect this with DesktopRepository for handling + // task moving to front for tasks in windowing mode. } - // TODO: b/367268953 - Connect this with DesktopRepository for handling - // task moving to front for tasks in windowing mode. - } - override fun onTaskMovingToBack(taskInfo: RunningTaskInfo) { - // TODO: b/367268953 - Connect this with DesktopRepository. - } + override fun onTaskMovingToBack(taskInfo: RunningTaskInfo) { + // TODO: b/367268953 - Connect this with DesktopRepository. + } - override fun onTaskClosing(taskInfo: RunningTaskInfo) { - if (!desktopRepository.isActiveTask(taskInfo.taskId)) return - // TODO: b/370038902 - Handle Activity#finishAndRemoveTask. - if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue() || - desktopRepository.isClosingTask(taskInfo.taskId)) { - // A task that's vanishing should be removed: - // - If it's closed by the X button which means it's marked as a closing task. - desktopRepository.removeClosingTask(taskInfo.taskId) - desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId) - } else { - desktopRepository.updateTask(taskInfo.displayId, taskInfo.taskId, isVisible = false) - desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId) + override fun onTaskClosing(taskInfo: RunningTaskInfo) { + val desktopRepository: DesktopRepository = + desktopUserRepositories.getProfile(taskInfo.userId) + if (!desktopRepository.isActiveTask(taskInfo.taskId)) return + // TODO: b/370038902 - Handle Activity#finishAndRemoveTask. + if ( + !DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue() || + desktopRepository.isClosingTask(taskInfo.taskId) + ) { + // A task that's vanishing should be removed: + // - If it's closed by the X button which means it's marked as a closing task. + desktopRepository.removeClosingTask(taskInfo.taskId) + desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId) + } else { + desktopRepository.updateTask(taskInfo.displayId, taskInfo.taskId, isVisible = false) + desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId) + } } - } - private fun isFreeformTask(taskInfo: RunningTaskInfo): Boolean = - taskInfo.windowingMode == WINDOWING_MODE_FREEFORM + private fun isFreeformTask(taskInfo: RunningTaskInfo): Boolean = + taskInfo.windowingMode == WINDOWING_MODE_FREEFORM } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt index 65f12cf4a196..848d80ff4f0b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt @@ -22,16 +22,14 @@ import android.graphics.Point import android.graphics.Rect import android.view.Gravity import com.android.internal.annotations.VisibleForTesting +import com.android.wm.shell.R import com.android.wm.shell.desktopmode.DesktopTaskPosition.BottomLeft import com.android.wm.shell.desktopmode.DesktopTaskPosition.BottomRight import com.android.wm.shell.desktopmode.DesktopTaskPosition.Center import com.android.wm.shell.desktopmode.DesktopTaskPosition.TopLeft import com.android.wm.shell.desktopmode.DesktopTaskPosition.TopRight -import com.android.wm.shell.R -/** - * The position of a task window in desktop mode. - */ +/** The position of a task window in desktop mode. */ sealed class DesktopTaskPosition { data object Center : DesktopTaskPosition() { private const val WINDOW_HEIGHT_PROPORTION = 0.375 @@ -89,8 +87,8 @@ sealed class DesktopTaskPosition { } /** - * Returns the top left coordinates for the window to be placed in the given - * DesktopTaskPosition in the frame. + * Returns the top left coordinates for the window to be placed in the given DesktopTaskPosition + * in the frame. */ abstract fun getTopLeftCoordinates(frame: Rect, window: Rect): Point @@ -98,8 +96,8 @@ sealed class DesktopTaskPosition { } /** - * If the app has specified horizontal or vertical gravity layout, don't change the - * task position for cascading effect. + * If the app has specified horizontal or vertical gravity layout, don't change the task position + * for cascading effect. */ fun canChangeTaskPosition(taskInfo: TaskInfo): Boolean { taskInfo.topActivityInfo?.windowLayout?.let { @@ -110,9 +108,7 @@ fun canChangeTaskPosition(taskInfo: TaskInfo): Boolean { return true } -/** - * Returns the current DesktopTaskPosition for a given window in the frame. - */ +/** Returns the current DesktopTaskPosition for a given window in the frame. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) fun Rect.getDesktopTaskPosition(bounds: Rect): DesktopTaskPosition { return when { @@ -140,8 +136,8 @@ internal fun cascadeWindow(res: Resources, frame: Rect, prev: Rect, dest: Rect) internal fun prevBoundsMovedAboveThreshold(res: Resources, prev: Rect, newBounds: Rect): Boolean { // This is the required minimum dp for a task to be touchable. - val moveThresholdPx = res.getDimensionPixelSize( - R.dimen.freeform_required_visible_empty_space_in_header) + val moveThresholdPx = + res.getDimensionPixelSize(R.dimen.freeform_required_visible_empty_space_in_header) val leftFar = newBounds.left - prev.left > moveThresholdPx val topFar = newBounds.top - prev.top > moveThresholdPx val rightFar = prev.right - newBounds.right > moveThresholdPx diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index c479ab382acb..0bc7ca982ec2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -50,6 +50,7 @@ import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_NONE import android.view.WindowManager.TRANSIT_OPEN +import android.view.WindowManager.TRANSIT_PIP import android.view.WindowManager.TRANSIT_TO_FRONT import android.widget.Toast import android.window.DesktopModeFlags @@ -67,7 +68,6 @@ 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.Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE import com.android.internal.jank.InteractionJankMonitor -import com.android.internal.policy.ScreenDecorationsUtils import com.android.internal.protolog.ProtoLog import com.android.window.flags.Flags import com.android.wm.shell.Flags.enableFlexibleSplit @@ -84,10 +84,17 @@ import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SingleInstanceRemoteListener import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger +import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType import com.android.wm.shell.desktopmode.DesktopRepository.VisibleTasksListener +import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.Companion.DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener +import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION +import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCREEN_ANIMATION_DURATION +import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler import com.android.wm.shell.draganddrop.DragAndDropController import com.android.wm.shell.freeform.FreeformTaskTransitionStarter @@ -95,7 +102,8 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE 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.ShellSharedConstants +import com.android.wm.shell.recents.RecentsTransitionStateListener.RecentsTransitionState +import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.shared.annotations.ExternalThread import com.android.wm.shell.shared.annotations.ShellMainThread @@ -127,15 +135,8 @@ import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel import java.io.PrintWriter import java.util.Optional import java.util.concurrent.Executor +import java.util.concurrent.TimeUnit import java.util.function.Consumer -import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod -import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger -import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum -import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.Companion.DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS -import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION -import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCREEN_ANIMATION_DURATION -import com.android.wm.shell.recents.RecentsTransitionStateListener.RecentsTransitionState -import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING /** Handles moving tasks in and out of desktop */ class DesktopTasksController( @@ -158,7 +159,7 @@ class DesktopTasksController( private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler, private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler, private val desktopImmersiveController: DesktopImmersiveController, - private val taskRepository: DesktopRepository, + private val userRepositories: DesktopUserRepositories, private val recentsTransitionHandler: RecentsTransitionHandler, private val multiInstanceHelper: MultiInstanceHelper, @ShellMainThread private val mainExecutor: ShellExecutor, @@ -176,6 +177,7 @@ class DesktopTasksController( UserChangeListener { private val desktopMode: DesktopModeImpl + private var taskRepository: DesktopRepository private var visualIndicator: DesktopModeVisualIndicator? = null private var userId: Int private val desktopModeShellCommandHandler: DesktopModeShellCommandHandler = @@ -203,8 +205,7 @@ class DesktopTasksController( } } - @VisibleForTesting - var taskbarDesktopTaskListener: TaskbarDesktopTaskListener? = null + @VisibleForTesting var taskbarDesktopTaskListener: TaskbarDesktopTaskListener? = null @VisibleForTesting var desktopModeEnterExitTransitionListener: DesktopModeEntryExitTransitionListener? = null @@ -213,14 +214,14 @@ class DesktopTasksController( val draggingTaskId get() = dragToDesktopTransitionHandler.draggingTaskId - @RecentsTransitionState - private var recentsTransitionState = TRANSITION_STATE_NOT_RUNNING + @RecentsTransitionState private var recentsTransitionState = TRANSITION_STATE_NOT_RUNNING private lateinit var splitScreenController: SplitScreenController lateinit var freeformTaskTransitionStarter: FreeformTaskTransitionStarter // Launch cookie used to identify a drag and drop transition to fullscreen after it has begun. // Used to prevent handleRequest from moving the new fullscreen task to freeform. private var dragAndDropFullscreenCookie: Binder? = null + private var pendingPipTransitionAndTask: Pair<IBinder, Int>? = null init { desktopMode = DesktopModeImpl() @@ -228,6 +229,7 @@ class DesktopTasksController( shellInit.addInitCallback({ onInit() }, this) } userId = ActivityManager.getCurrentUser() + taskRepository = userRepositories.getProfile(userId) } private fun onInit() { @@ -235,9 +237,9 @@ class DesktopTasksController( shellCommandHandler.addDumpCallback(this::dump, this) shellCommandHandler.addCommandCallback("desktopmode", desktopModeShellCommandHandler, this) shellController.addExternalInterface( - ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE, + IDesktopMode.DESCRIPTOR, { createExternalInterface() }, - this + this, ) shellController.addUserChangeListener(this) transitions.addHandler(this) @@ -247,7 +249,7 @@ class DesktopTasksController( override fun onTransitionStateChanged(@RecentsTransitionState state: Int) { logV( "Recents transition state changed: %s", - RecentsTransitionStateListener.stateToString(state) + RecentsTransitionStateListener.stateToString(state), ) recentsTransitionState = state desktopTilingDecorViewModel.onOverviewAnimationStateChange( @@ -297,17 +299,17 @@ class DesktopTasksController( bringDesktopAppsToFront(displayId, wct) val transitionType = transitionType(remoteTransition) - val handler = remoteTransition?.let { - OneShotRemoteHandler(transitions.mainExecutor, remoteTransition) - } + val handler = + remoteTransition?.let { + OneShotRemoteHandler(transitions.mainExecutor, remoteTransition) + } transitions.startTransition(transitionType, wct, handler).also { t -> handler?.setTransition(t) } } /** Gets number of visible tasks in [displayId]. */ - fun visibleTaskCount(displayId: Int): Int = - taskRepository.getVisibleTaskCount(displayId) + fun visibleTaskCount(displayId: Int): Int = taskRepository.getVisibleTaskCount(displayId) /** Returns true if any tasks are visible in Desktop Mode. */ fun isDesktopModeShowing(displayId: Int): Boolean = visibleTaskCount(displayId) > 0 @@ -318,29 +320,37 @@ class DesktopTasksController( when (allFocusedTasks.size) { 0 -> return // Full screen case - 1 -> moveRunningTaskToDesktop( - allFocusedTasks.single(), transitionSource = transitionSource) + 1 -> + moveRunningTaskToDesktop( + allFocusedTasks.single(), + transitionSource = transitionSource, + ) // Split-screen case where there are two focused tasks, then we find the child // task to move to desktop. - 2 -> moveRunningTaskToDesktop( - getSplitFocusedTask(allFocusedTasks[0], allFocusedTasks[1]), - transitionSource = transitionSource) - else -> logW( - "DesktopTasksController: Cannot enter desktop, expected less " + - "than 3 focused tasks but found %d", allFocusedTasks.size) + 2 -> + moveRunningTaskToDesktop( + getSplitFocusedTask(allFocusedTasks[0], allFocusedTasks[1]), + transitionSource = transitionSource, + ) + else -> + logW( + "DesktopTasksController: Cannot enter desktop, expected less " + + "than 3 focused tasks but found %d", + allFocusedTasks.size, + ) } } /** - * Returns all focused tasks in full screen or split screen mode in [displayId] when - * it is not the home activity. + * Returns all focused tasks in full screen or split screen mode in [displayId] when it is not + * the home activity. */ private fun getAllFocusedTasks(displayId: Int): List<RunningTaskInfo> = shellTaskOrganizer.getRunningTasks(displayId).filter { it.isFocused && - (it.windowingMode == WINDOWING_MODE_FULLSCREEN || - it.windowingMode == WINDOWING_MODE_MULTI_WINDOW) && - it.activityType != ACTIVITY_TYPE_HOME + (it.windowingMode == WINDOWING_MODE_FULLSCREEN || + it.windowingMode == WINDOWING_MODE_MULTI_WINDOW) && + it.activityType != ACTIVITY_TYPE_HOME } /** Returns child task from two focused tasks in split screen mode. */ @@ -353,8 +363,15 @@ class DesktopTasksController( } val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId) - requireNotNull(tdaInfo) { - "This method can only be called with the ID of a display having non-null DisplayArea." + // A non-organized display (e.g., non-trusted virtual displays used in CTS) doesn't have + // TDA. + if (tdaInfo == null) { + logW( + "forceEnterDesktop cannot find DisplayAreaInfo for displayId=%d. This could happen" + + " when the display is a non-trusted virtual display.", + displayId, + ) + return false } val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode val isFreeformDisplay = tdaWindowingMode == WINDOWING_MODE_FREEFORM @@ -376,9 +393,9 @@ class DesktopTasksController( } private fun moveBackgroundTaskToDesktop( - taskId: Int, - wct: WindowContainerTransaction, - transitionSource: DesktopModeTransitionSource, + taskId: Int, + wct: WindowContainerTransaction, + transitionSource: DesktopModeTransitionSource, ): Boolean { if (recentTasksController?.findTaskInBackground(taskId) == null) { logW("moveBackgroundTaskToDesktop taskId=%d not found", taskId) @@ -386,54 +403,62 @@ class DesktopTasksController( } logV("moveBackgroundTaskToDesktop with taskId=%d", taskId) // TODO(342378842): Instead of using default display, support multiple displays - val taskIdToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask( - DEFAULT_DISPLAY, wct, taskId) - val exitResult = desktopImmersiveController.exitImmersiveIfApplicable( - wct = wct, - displayId = DEFAULT_DISPLAY, - excludeTaskId = taskId, - reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH, - ) + val taskIdToMinimize = + bringDesktopAppsToFrontBeforeShowingNewTask(DEFAULT_DISPLAY, wct, taskId) + val exitResult = + desktopImmersiveController.exitImmersiveIfApplicable( + wct = wct, + displayId = DEFAULT_DISPLAY, + excludeTaskId = taskId, + reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH, + ) wct.startTask( taskId, - ActivityOptions.makeBasic().apply { - launchWindowingMode = WINDOWING_MODE_FREEFORM - }.toBundle(), + ActivityOptions.makeBasic() + .apply { launchWindowingMode = WINDOWING_MODE_FREEFORM } + .toBundle(), ) // TODO(343149901): Add DPI changes for task launch val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource) - desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) + desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted( + FREEFORM_ANIMATION_DURATION + ) taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) } exitResult.asExit()?.runOnTransitionStart?.invoke(transition) return true } - /** Moves a running task to desktop. */ + /** Moves a running task to desktop. */ fun moveRunningTaskToDesktop( task: RunningTaskInfo, wct: WindowContainerTransaction = WindowContainerTransaction(), transitionSource: DesktopModeTransitionSource, ) { - if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() - && isTopActivityExemptFromDesktopWindowing(context, task)) { + if ( + DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() && + isTopActivityExemptFromDesktopWindowing(context, task) + ) { logW("Cannot enter desktop for taskId %d, ineligible top activity found", task.taskId) return } logV("moveRunningTaskToDesktop taskId=%d", task.taskId) exitSplitIfApplicable(wct, task) - val exitResult = desktopImmersiveController.exitImmersiveIfApplicable( - wct = wct, - displayId = task.displayId, - excludeTaskId = task.taskId, - reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH, - ) + val exitResult = + desktopImmersiveController.exitImmersiveIfApplicable( + wct = wct, + displayId = task.displayId, + excludeTaskId = task.taskId, + reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH, + ) // Bring other apps to front first val taskIdToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId) addMoveToDesktopChanges(wct, task) val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource) - desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION) + desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted( + FREEFORM_ANIMATION_DURATION + ) taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) } exitResult.asExit()?.runOnTransitionStart?.invoke(transition) } @@ -448,11 +473,18 @@ class DesktopTasksController( taskSurface: SurfaceControl, ) { logV("startDragToDesktop taskId=%d", taskInfo.taskId) - interactionJankMonitor.begin(taskSurface, context, handler, - CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD) + val jankConfigBuilder = + InteractionJankMonitor.Configuration.Builder.withSurface( + CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD, + context, + taskSurface, + handler, + ) + .setTimeout(APP_HANDLE_DRAG_HOLD_CUJ_TIMEOUT_MS) + interactionJankMonitor.begin(jankConfigBuilder) dragToDesktopTransitionHandler.startDragToDesktopTransition( - taskInfo.taskId, - dragToDesktopValueAnimator + taskInfo, + dragToDesktopValueAnimator, ) } @@ -464,7 +496,7 @@ class DesktopTasksController( ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: finalizeDragToDesktop taskId=%d", - taskInfo.taskId + taskInfo.taskId, ) val wct = WindowContainerTransaction() exitSplitIfApplicable(wct, taskInfo) @@ -472,12 +504,13 @@ class DesktopTasksController( val taskIdToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId) addMoveToDesktopChanges(wct, taskInfo) - val exitResult = desktopImmersiveController.exitImmersiveIfApplicable( - wct = wct, - displayId = taskInfo.displayId, - excludeTaskId = null, - reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH - ) + val exitResult = + desktopImmersiveController.exitImmersiveIfApplicable( + wct = wct, + displayId = taskInfo.displayId, + excludeTaskId = null, + reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH, + ) val transition = dragToDesktopTransitionHandler.finishDragToDesktopTransition(wct) desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted( DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS.toInt() @@ -520,29 +553,50 @@ class DesktopTasksController( performDesktopExitCleanupIfNeeded(taskId, wct) taskRepository.addClosingTask(displayId, taskId) taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate( - doesAnyTaskRequireTaskbarRounding( - displayId, - taskId - ) + doesAnyTaskRequireTaskbarRounding(displayId, taskId) ) - return desktopImmersiveController.exitImmersiveIfApplicable( - wct = wct, - taskInfo = taskInfo, - reason = DesktopImmersiveController.ExitReason.CLOSED - ).asExit()?.runOnTransitionStart + return desktopImmersiveController + .exitImmersiveIfApplicable( + wct = wct, + taskInfo = taskInfo, + reason = DesktopImmersiveController.ExitReason.CLOSED, + ) + .asExit() + ?.runOnTransitionStart } fun minimizeTask(taskInfo: RunningTaskInfo) { + val wct = WindowContainerTransaction() + + val isMinimizingToPip = taskInfo.pictureInPictureParams?.isAutoEnterEnabled() ?: false + // If task is going to PiP, start a PiP transition instead of a minimize transition + if (isMinimizingToPip) { + val requestInfo = TransitionRequestInfo( + TRANSIT_PIP, /* triggerTask= */ null, taskInfo, /* remoteTransition= */ null, + /* displayChange= */ null, /* flags= */ 0 + ) + val requestRes = transitions.dispatchRequest(Binder(), requestInfo, /* skip= */ null) + wct.merge(requestRes.second, true) + pendingPipTransitionAndTask = + freeformTaskTransitionStarter.startPipTransition(wct) to taskInfo.taskId + return + } + + minimizeTaskInner(taskInfo) + } + + private fun minimizeTaskInner(taskInfo: RunningTaskInfo) { val taskId = taskInfo.taskId val displayId = taskInfo.displayId val wct = WindowContainerTransaction() performDesktopExitCleanupIfNeeded(taskId, wct) // Notify immersive handler as it might need to exit immersive state. - val exitResult = desktopImmersiveController.exitImmersiveIfApplicable( - wct = wct, - taskInfo = taskInfo, - reason = DesktopImmersiveController.ExitReason.MINIMIZED - ) + val exitResult = + desktopImmersiveController.exitImmersiveIfApplicable( + wct = wct, + taskInfo = taskInfo, + reason = DesktopImmersiveController.ExitReason.MINIMIZED, + ) wct.reorder(taskInfo.token, false) val transition = freeformTaskTransitionStarter.startMinimizedModeTransition(wct) @@ -550,7 +604,7 @@ class DesktopTasksController( it.addPendingMinimizeChange( transition = transition, displayId = displayId, - taskId = taskId + taskId = taskId, ) } exitResult.asExit()?.runOnTransitionStart?.invoke(transition) @@ -577,7 +631,7 @@ class DesktopTasksController( splitScreenController.prepareExitSplitScreen( wct, splitScreenController.getStageOfTask(taskInfo.taskId), - EXIT_REASON_DESKTOP_MODE + EXIT_REASON_DESKTOP_MODE, ) splitScreenController.transitionHandler?.onSplitToDesktop() } @@ -597,22 +651,24 @@ class DesktopTasksController( private fun moveToFullscreenWithAnimation( task: RunningTaskInfo, position: Point, - transitionSource: DesktopModeTransitionSource + transitionSource: DesktopModeTransitionSource, ) { logV("moveToFullscreenWithAnimation taskId=%d", task.taskId) val wct = WindowContainerTransaction() addMoveToFullscreenChanges(wct, task) exitDesktopTaskTransitionHandler.startTransition( - transitionSource, - wct, - position, - mOnAnimationFinishedCallback - ) + transitionSource, + wct, + position, + mOnAnimationFinishedCallback, + ) // handles case where we are moving to full screen without closing all DW tasks. if (!taskRepository.isOnlyVisibleNonClosingTask(task.taskId)) { - desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION) + desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted( + FULLSCREEN_ANIMATION_DURATION + ) } } @@ -642,16 +698,11 @@ class DesktopTasksController( val wct = WindowContainerTransaction() wct.startTask( taskId, - ActivityOptions.makeBasic().apply { - launchWindowingMode = WINDOWING_MODE_FREEFORM - }.toBundle(), - ) - startLaunchTransition( - TRANSIT_OPEN, - wct, - taskId, - remoteTransition = remoteTransition + ActivityOptions.makeBasic() + .apply { launchWindowingMode = WINDOWING_MODE_FREEFORM } + .toBundle(), ) + startLaunchTransition(TRANSIT_OPEN, wct, taskId, remoteTransition = remoteTransition) } /** @@ -686,29 +737,32 @@ class DesktopTasksController( remoteTransition: RemoteTransition? = null, displayId: Int = DEFAULT_DISPLAY, ): IBinder { - val taskIdToMinimize = if (launchingTaskId != null) { - addAndGetMinimizeChanges(displayId, wct, newTaskId = launchingTaskId) - } else { - logW("Starting desktop task launch without checking the task-limit") - // TODO(b/378920066): This currently does not respect the desktop window limit. - // It's possible that |launchingTaskId| is null when launching using an intent, and - // the task-limit should be respected then too. - null - } - val exitImmersiveResult = desktopImmersiveController.exitImmersiveIfApplicable( - wct = wct, - displayId = displayId, - excludeTaskId = launchingTaskId, - reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH, - ) - if (remoteTransition == null) { - val t = desktopMixedTransitionHandler.startLaunchTransition( - transitionType = transitionType, + val taskIdToMinimize = + if (launchingTaskId != null) { + addAndGetMinimizeChanges(displayId, wct, newTaskId = launchingTaskId) + } else { + logW("Starting desktop task launch without checking the task-limit") + // TODO(b/378920066): This currently does not respect the desktop window limit. + // It's possible that |launchingTaskId| is null when launching using an intent, and + // the task-limit should be respected then too. + null + } + val exitImmersiveResult = + desktopImmersiveController.exitImmersiveIfApplicable( wct = wct, - taskId = launchingTaskId, - minimizingTaskId = taskIdToMinimize, - exitingImmersiveTask = exitImmersiveResult.asExit()?.exitingTask, + displayId = displayId, + excludeTaskId = launchingTaskId, + reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH, ) + if (remoteTransition == null) { + val t = + desktopMixedTransitionHandler.startLaunchTransition( + transitionType = transitionType, + wct = wct, + taskId = launchingTaskId, + minimizingTaskId = taskIdToMinimize, + exitingImmersiveTask = exitImmersiveResult.asExit()?.exitingTask, + ) taskIdToMinimize?.let { addPendingMinimizeTransition(t, it) } exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t) return t @@ -722,7 +776,11 @@ class DesktopTasksController( } val remoteTransitionHandler = DesktopWindowLimitRemoteHandler( - mainExecutor, rootTaskDisplayAreaOrganizer, remoteTransition, taskIdToMinimize) + mainExecutor, + rootTaskDisplayAreaOrganizer, + remoteTransition, + taskIdToMinimize, + ) val t = transitions.startTransition(transitionType, wct, remoteTransitionHandler) remoteTransitionHandler.setTransition(t) taskIdToMinimize.let { addPendingMinimizeTransition(t, it) } @@ -793,34 +851,23 @@ class DesktopTasksController( * bounds) and a free floating state (either the last saved bounds if available or the default * bounds otherwise). */ - fun toggleDesktopTaskSize( - taskInfo: RunningTaskInfo, - resizeTrigger: ResizeTrigger, - inputMethod: InputMethod, - maximizeCujRecorder: (() -> Unit)? = null, - unmaximizeCujRecorder: (() -> Unit)? = null, - ) { + fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo, interaction: ToggleTaskSizeInteraction) { val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds desktopModeEventLogger.logTaskResizingStarted( - resizeTrigger, - inputMethod, + interaction.resizeTrigger, + interaction.inputMethod, taskInfo, currentTaskBounds.width(), currentTaskBounds.height(), - displayController + displayController, ) - val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return - - val stableBounds = Rect().apply { displayLayout.getStableBounds(this) } val destinationBounds = Rect() - - val isMaximized = isTaskMaximized(taskInfo, stableBounds) + val isMaximized = interaction.direction == ToggleTaskSizeInteraction.Direction.RESTORE // If the task is currently maximized, we will toggle it not to be and vice versa. This is // helpful to eliminate the current task from logic to calculate taskbar corner rounding. - val willMaximize = !isMaximized + val willMaximize = interaction.direction == ToggleTaskSizeInteraction.Direction.MAXIMIZE if (isMaximized) { - unmaximizeCujRecorder?.invoke() // The desktop task is at the maximized width and/or height of the stable bounds. // If the task's pre-maximize stable bounds were saved, toggle the task to those bounds. // Otherwise, toggle to the default bounds. @@ -836,7 +883,6 @@ class DesktopTasksController( } } } else { - maximizeCujRecorder?.invoke() // Save current bounds so that task can be restored back to original bounds if necessary // and toggle to the stable bounds. desktopTilingDecorViewModel.removeTaskIfTiled(taskInfo.displayId, taskInfo.taskId) @@ -845,36 +891,41 @@ class DesktopTasksController( destinationBounds.set(calculateMaximizeBounds(displayLayout, taskInfo)) } - val shouldRestoreToSnap = isMaximized && isTaskSnappedToHalfScreen(taskInfo, destinationBounds) logD("willMaximize = %s", willMaximize) logD("shouldRestoreToSnap = %s", shouldRestoreToSnap) - val doesAnyTaskRequireTaskbarRounding = willMaximize || shouldRestoreToSnap || + val doesAnyTaskRequireTaskbarRounding = + willMaximize || + shouldRestoreToSnap || doesAnyTaskRequireTaskbarRounding(taskInfo.displayId, taskInfo.taskId) taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding) val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds) + interaction.uiEvent?.let { uiEvent -> desktopModeUiEventLogger.log(taskInfo, uiEvent) } desktopModeEventLogger.logTaskResizingEnded( - resizeTrigger, inputMethod, - taskInfo, destinationBounds.width(), - destinationBounds.height(), displayController + interaction.resizeTrigger, + interaction.inputMethod, + taskInfo, + destinationBounds.width(), + destinationBounds.height(), + displayController, + ) + toggleResizeDesktopTaskTransitionHandler.startTransition( + wct, + interaction.animationStartBounds, ) - toggleResizeDesktopTaskTransitionHandler.startTransition(wct) } private fun dragToMaximizeDesktopTask( taskInfo: RunningTaskInfo, taskSurface: SurfaceControl, currentDragBounds: Rect, - motionEvent: MotionEvent + motionEvent: MotionEvent, ) { - val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return - val stableBounds = Rect() - displayLayout.getStableBounds(stableBounds) - if (isTaskMaximized(taskInfo, stableBounds)) { + if (isTaskMaximized(taskInfo, displayController)) { // Handle the case where we attempt to drag-to-maximize when already maximized: the task // position won't need to change but we want to animate the surface going back to the // maximized position. @@ -892,8 +943,12 @@ class DesktopTasksController( toggleDesktopTaskSize( taskInfo, - ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER, - DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent), + ToggleTaskSizeInteraction( + direction = ToggleTaskSizeInteraction.Direction.MAXIMIZE, + source = ToggleTaskSizeInteraction.Source.HEADER_DRAG_TO_TOP, + inputMethod = DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent), + animationStartBounds = currentDragBounds, + ), ) } @@ -904,29 +959,19 @@ class DesktopTasksController( } else { // if non-resizable then calculate max bounds according to aspect ratio val activityAspectRatio = calculateAspectRatio(taskInfo) - val newSize = maximizeSizeGivenAspectRatio(taskInfo, - Size(stableBounds.width(), stableBounds.height()), activityAspectRatio) - return centerInArea( - newSize, stableBounds, stableBounds.left, stableBounds.top) - } - } - - private fun isTaskMaximized( - taskInfo: RunningTaskInfo, - stableBounds: Rect - ): Boolean { - val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds - - return if (taskInfo.isResizeable) { - isTaskBoundsEqual(currentTaskBounds, stableBounds) - } else { - isTaskWidthOrHeightEqual(currentTaskBounds, stableBounds) + val newSize = + maximizeSizeGivenAspectRatio( + taskInfo, + Size(stableBounds.width(), stableBounds.height()), + activityAspectRatio, + ) + return centerInArea(newSize, stableBounds, stableBounds.left, stableBounds.top) } } private fun isMaximizedToStableBoundsEdges( taskInfo: RunningTaskInfo, - stableBounds: Rect + stableBounds: Rect, ): Boolean { val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds return isTaskBoundsEqual(currentTaskBounds, stableBounds) @@ -935,18 +980,16 @@ class DesktopTasksController( /** Returns if current task bound is snapped to half screen */ private fun isTaskSnappedToHalfScreen( taskInfo: RunningTaskInfo, - taskBounds: Rect = taskInfo.configuration.windowConfiguration.bounds + taskBounds: Rect = taskInfo.configuration.windowConfiguration.bounds, ): Boolean = getSnapBounds(taskInfo, SnapPosition.LEFT) == taskBounds || - getSnapBounds(taskInfo, SnapPosition.RIGHT) == taskBounds + getSnapBounds(taskInfo, SnapPosition.RIGHT) == taskBounds @VisibleForTesting - fun doesAnyTaskRequireTaskbarRounding( - displayId: Int, - excludeTaskId: Int? = null, - ): Boolean { + fun doesAnyTaskRequireTaskbarRounding(displayId: Int, excludeTaskId: Int? = null): Boolean { val doesAnyTaskRequireTaskbarRounding = - taskRepository.getExpandedTasksOrdered(displayId) + taskRepository + .getExpandedTasksOrdered(displayId) // exclude current task since maximize/restore transition has not taken place yet. .filterNot { taskId -> taskId == excludeTaskId } .any { taskId -> @@ -956,14 +999,14 @@ class DesktopTasksController( logD("taskInfo = %s", taskInfo) logD( "isTaskSnappedToHalfScreen(taskInfo) = %s", - isTaskSnappedToHalfScreen(taskInfo) + isTaskSnappedToHalfScreen(taskInfo), ) logD( "isMaximizedToStableBoundsEdges(taskInfo, stableBounds) = %s", - isMaximizedToStableBoundsEdges(taskInfo, stableBounds) + isMaximizedToStableBoundsEdges(taskInfo, stableBounds), ) - isTaskSnappedToHalfScreen(taskInfo) - || isMaximizedToStableBoundsEdges(taskInfo, stableBounds) + isTaskSnappedToHalfScreen(taskInfo) || + isMaximizedToStableBoundsEdges(taskInfo, stableBounds) } logD("doesAnyTaskRequireTaskbarRounding = %s", doesAnyTaskRequireTaskbarRounding) @@ -976,7 +1019,7 @@ class DesktopTasksController( * @param taskInfo current task that is being snap-resized via dragging or maximize menu button * @param taskSurface the leash of the task being dragged * @param currentDragBounds current position of the task leash being dragged (or current task - * bounds if being snapped resize via maximize menu button) + * bounds if being snapped resize via maximize menu button) * @param position the portion of the screen (RIGHT or LEFT) we want to snap the task to. */ fun snapToHalfScreen( @@ -994,7 +1037,7 @@ class DesktopTasksController( taskInfo, currentDragBounds.width(), currentDragBounds.height(), - displayController + displayController, ) val destinationBounds = getSnapBounds(taskInfo, position) @@ -1008,12 +1051,13 @@ class DesktopTasksController( ) if (DesktopModeFlags.ENABLE_TILE_RESIZING.isTrue()) { - val isTiled = desktopTilingDecorViewModel.snapToHalfScreen( - taskInfo, - desktopWindowDecoration, - position, - currentDragBounds, - ) + val isTiled = + desktopTilingDecorViewModel.snapToHalfScreen( + taskInfo, + desktopWindowDecoration, + position, + currentDragBounds, + ) if (isTiled) { taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(true) } @@ -1054,10 +1098,11 @@ class DesktopTasksController( ) { if (!isSnapResizingAllowed(taskInfo)) { Toast.makeText( - getContext(), - R.string.desktop_mode_non_resizable_snap_text, - Toast.LENGTH_SHORT - ).show() + getContext(), + R.string.desktop_mode_non_resizable_snap_text, + Toast.LENGTH_SHORT, + ) + .show() return } @@ -1068,11 +1113,10 @@ class DesktopTasksController( position, resizeTrigger, inputMethod, - desktopModeWindowDecoration + desktopModeWindowDecoration, ) } - @VisibleForTesting fun handleSnapResizingTaskOnDrag( taskInfo: RunningTaskInfo, @@ -1086,7 +1130,11 @@ class DesktopTasksController( releaseVisualIndicator() if (!isSnapResizingAllowed(taskInfo)) { interactionJankMonitor.begin( - taskSurface, context, handler, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_non_resizable" + taskSurface, + context, + handler, + CUJ_DESKTOP_MODE_SNAP_RESIZE, + "drag_non_resizable", ) // reposition non-resizable app back to its original position before being dragged @@ -1097,20 +1145,26 @@ class DesktopTasksController( endBounds = dragStartBounds, doOnEnd = { Toast.makeText( - context, - com.android.wm.shell.R.string.desktop_mode_non_resizable_snap_text, - Toast.LENGTH_SHORT - ).show() + context, + com.android.wm.shell.R.string.desktop_mode_non_resizable_snap_text, + Toast.LENGTH_SHORT, + ) + .show() }, ) } else { - val resizeTrigger = if (position == SnapPosition.LEFT) { - ResizeTrigger.DRAG_LEFT - } else { - ResizeTrigger.DRAG_RIGHT - } + val resizeTrigger = + if (position == SnapPosition.LEFT) { + ResizeTrigger.DRAG_LEFT + } else { + ResizeTrigger.DRAG_RIGHT + } interactionJankMonitor.begin( - taskSurface, context, handler, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_resizable" + taskSurface, + context, + handler, + CUJ_DESKTOP_MODE_SNAP_RESIZE, + "drag_resizable", ) snapToHalfScreen( taskInfo, @@ -1140,7 +1194,7 @@ class DesktopTasksController( stableBounds.left, stableBounds.top, stableBounds.left + destinationWidth, - stableBounds.bottom + stableBounds.bottom, ) } SnapPosition.RIGHT -> { @@ -1148,7 +1202,7 @@ class DesktopTasksController( stableBounds.right - destinationWidth, stableBounds.top, stableBounds.right, - stableBounds.bottom + stableBounds.bottom, ) } } @@ -1168,36 +1222,35 @@ class DesktopTasksController( private fun bringDesktopAppsToFrontBeforeShowingNewTask( displayId: Int, wct: WindowContainerTransaction, - newTaskIdInFront: Int + newTaskIdInFront: Int, ): Int? = bringDesktopAppsToFront(displayId, wct, newTaskIdInFront) private fun bringDesktopAppsToFront( displayId: Int, wct: WindowContainerTransaction, - newTaskIdInFront: Int? = null + newTaskIdInFront: Int? = null, ): Int? { logV("bringDesktopAppsToFront, newTaskId=%d", newTaskIdInFront) // Move home to front, ensures that we go back home when all desktop windows are closed moveHomeTask(wct, toTop = true) // Currently, we only handle the desktop on the default display really. - if (displayId == DEFAULT_DISPLAY - && ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()) { + if ( + (displayId == DEFAULT_DISPLAY || Flags.enableBugFixesForSecondaryDisplay()) && + ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue() + ) { // Add translucent wallpaper activity to show the wallpaper underneath - addWallpaperActivity(wct) + addWallpaperActivity(displayId, wct) } - val expandedTasksOrderedFrontToBack = - taskRepository.getExpandedTasksOrdered(displayId) + val expandedTasksOrderedFrontToBack = taskRepository.getExpandedTasksOrdered(displayId) // If we're adding a new Task we might need to minimize an old one // TODO(b/365725441): Handle non running task minimization val taskIdToMinimize: Int? = if (newTaskIdInFront != null && desktopTasksLimiter.isPresent) { - desktopTasksLimiter.get() - .getTaskIdToMinimize( - expandedTasksOrderedFrontToBack, - newTaskIdInFront - ) + desktopTasksLimiter + .get() + .getTaskIdToMinimize(expandedTasksOrderedFrontToBack, newTaskIdInFront) } else { null } @@ -1215,15 +1268,16 @@ class DesktopTasksController( // Task is not running, start it wct.startTask( taskId, - ActivityOptions.makeBasic().apply { - launchWindowingMode = WINDOWING_MODE_FREEFORM - }.toBundle(), + ActivityOptions.makeBasic() + .apply { launchWindowingMode = WINDOWING_MODE_FREEFORM } + .toBundle(), ) } } - taskbarDesktopTaskListener?. - onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding(displayId)) + taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate( + doesAnyTaskRequireTaskbarRounding(displayId) + ) return taskIdToMinimize } @@ -1235,11 +1289,10 @@ class DesktopTasksController( ?.let { homeTask -> wct.reorder(homeTask.getToken(), /* onTop= */ toTop) } } - private fun addWallpaperActivity(wct: WindowContainerTransaction) { + private fun addWallpaperActivity(displayId: Int, wct: WindowContainerTransaction) { logV("addWallpaperActivity") val userHandle = UserHandle.of(userId) - val userContext = - context.createContextAsUser(userHandle, /* flags= */ 0) + val userContext = context.createContextAsUser(userHandle, /* flags= */ 0) val intent = Intent(userContext, DesktopWallpaperActivity::class.java) intent.putExtra(Intent.EXTRA_USER_HANDLE, userId) val options = @@ -1247,6 +1300,9 @@ class DesktopTasksController( launchWindowingMode = WINDOWING_MODE_FULLSCREEN pendingIntentBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS + if (Flags.enableBugFixesForSecondaryDisplay()) { + launchDisplayId = displayId + } } val pendingIntent = PendingIntent.getActivityAsUser( @@ -1254,8 +1310,8 @@ class DesktopTasksController( /* requestCode= */ 0, intent, PendingIntent.FLAG_IMMUTABLE, - /* bundle= */ null, - userHandle + /* options= */ null, + userHandle, ) wct.sendPendingIntent(pendingIntent, intent, options.toBundle()) } @@ -1275,13 +1331,14 @@ class DesktopTasksController( if (!taskRepository.isOnlyVisibleNonClosingTask(taskId)) { return } - desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION) + desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted( + FULLSCREEN_ANIMATION_DURATION + ) if (taskRepository.wallpaperActivityToken != null) { removeWallpaperActivity(wct) } } - fun releaseVisualIndicator() { val t = SurfaceControl.Transaction() visualIndicator?.releaseVisualIndicator(t) @@ -1305,15 +1362,30 @@ class DesktopTasksController( info: TransitionInfo, startTransaction: SurfaceControl.Transaction, finishTransaction: SurfaceControl.Transaction, - finishCallback: Transitions.TransitionFinishCallback + finishCallback: Transitions.TransitionFinishCallback, ): Boolean { // This handler should never be the sole handler, so should not animate anything. return false } + override fun onTransitionConsumed( + transition: IBinder, + aborted: Boolean, + finishT: Transaction? + ) { + pendingPipTransitionAndTask?.let { (pipTransition, taskId) -> + if (transition == pipTransition) { + if (aborted) { + shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { minimizeTaskInner(it) } + } + pendingPipTransitionAndTask = null + } + } + } + override fun handleRequest( transition: IBinder, - request: TransitionRequestInfo + request: TransitionRequestInfo, ): WindowContainerTransaction? { logV("handleRequest request=%s", request) // Check if we should skip handling this transition @@ -1376,11 +1448,8 @@ class DesktopTasksController( // Check if freeform task launch during recents should be handled shouldHandleMidRecentsFreeformLaunch -> handleMidRecentsFreeformTaskLaunch(task) // Check if the closing task needs to be handled - TransitionUtil.isClosingType(request.type) -> handleTaskClosing( - task, - transition, - request.type - ) + TransitionUtil.isClosingType(request.type) -> + handleTaskClosing(task, transition, request.type) // Check if the top task shouldn't be allowed to enter desktop mode isIncompatibleTask(task) -> handleIncompatibleTaskLaunch(task) // Check if fullscreen task should be updated @@ -1397,19 +1466,16 @@ class DesktopTasksController( } /** Whether the given [change] in the [transition] is a known desktop change. */ - fun isDesktopChange( - transition: IBinder, - change: TransitionInfo.Change, - ): Boolean { + fun isDesktopChange(transition: IBinder, change: TransitionInfo.Change): Boolean { // Only the immersive controller is currently involved in mixed transitions. - return Flags.enableFullyImmersiveInDesktop() - && desktopImmersiveController.isImmersiveChange(transition, change) + return Flags.enableFullyImmersiveInDesktop() && + desktopImmersiveController.isImmersiveChange(transition, change) } /** - * Whether the given transition [info] will potentially include a desktop change, in which - * case the transition should be treated as mixed so that the change is in part animated by - * one of the desktop transition handlers. + * Whether the given transition [info] will potentially include a desktop change, in which case + * the transition should be treated as mixed so that the change is in part animated by one of + * the desktop transition handlers. */ fun shouldPlayDesktopAnimation(info: TransitionRequestInfo): Boolean { // Only immersive mixed transition are currently supported. @@ -1453,7 +1519,7 @@ class DesktopTasksController( change, startTransaction, finishTransaction, - finishCallback + finishCallback, ) } @@ -1470,7 +1536,10 @@ class DesktopTasksController( if (!DesktopModeStatus.useRoundedCorners()) { return } - val cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context) + val cornerRadius = + context.resources + .getDimensionPixelSize(R.dimen.desktop_windowing_freeform_rounded_corner_radius) + .toFloat() info.changes .filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM } .forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) } @@ -1478,13 +1547,14 @@ class DesktopTasksController( /** Returns whether an existing desktop task is being relaunched in freeform or not. */ private fun isFreeformRelaunch(triggerTask: RunningTaskInfo?, request: TransitionRequestInfo) = - (triggerTask != null && triggerTask.windowingMode == WINDOWING_MODE_FREEFORM - && TransitionUtil.isOpeningType(request.type) - && taskRepository.isActiveTask(triggerTask.taskId)) + (triggerTask != null && + triggerTask.windowingMode == WINDOWING_MODE_FREEFORM && + TransitionUtil.isOpeningType(request.type) && + taskRepository.isActiveTask(triggerTask.taskId)) private fun isIncompatibleTask(task: TaskInfo) = - DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() - && isTopActivityExemptFromDesktopWindowing(context, task) + DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() && + isTopActivityExemptFromDesktopWindowing(context, task) private fun shouldHandleTaskClosing(request: TransitionRequestInfo): Boolean { return ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue() && @@ -1493,67 +1563,67 @@ class DesktopTasksController( } /** Open an existing instance of an app. */ - fun openInstance( - callingTask: RunningTaskInfo, - requestedTaskId: Int - ) { - val wct = WindowContainerTransaction() - val options = createNewWindowOptions(callingTask) - if (options.launchWindowingMode == WINDOWING_MODE_FREEFORM) { - wct.startTask(requestedTaskId, options.toBundle()) - val taskIdToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask( - callingTask.displayId, wct, requestedTaskId) - val exitResult = desktopImmersiveController - .exitImmersiveIfApplicable( - wct = wct, - displayId = callingTask.displayId, - excludeTaskId = requestedTaskId, - reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH, + fun openInstance(callingTask: RunningTaskInfo, requestedTaskId: Int) { + if (callingTask.isFreeform) { + val requestedTaskInfo = shellTaskOrganizer.getRunningTaskInfo(requestedTaskId) + if (requestedTaskInfo?.isFreeform == true) { + // If requested task is an already open freeform task, just move it to front. + moveTaskToFront(requestedTaskId) + } else { + moveBackgroundTaskToDesktop( + requestedTaskId, + WindowContainerTransaction(), + DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON, ) - val transition = transitions.startTransition(TRANSIT_OPEN, wct, null) - taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) } - addPendingAppLaunchTransition(transition, requestedTaskId, taskIdToMinimize) - exitResult.asExit()?.runOnTransitionStart?.invoke(transition) + } } else { + val options = createNewWindowOptions(callingTask) val splitPosition = splitScreenController.determineNewInstancePosition(callingTask) - splitScreenController.startTask(requestedTaskId, splitPosition, - options.toBundle(), null /* hideTaskToken */) + splitScreenController.startTask( + requestedTaskId, + splitPosition, + options.toBundle(), + null, /* hideTaskToken */ + ) } } /** Create an Intent to open a new window of a task. */ - fun openNewWindow( - callingTaskInfo: RunningTaskInfo - ) { + fun openNewWindow(callingTaskInfo: RunningTaskInfo) { // TODO(b/337915660): Add a transition handler for these; animations // need updates in some cases. val baseActivity = callingTaskInfo.baseActivity ?: return - val fillIn: Intent = context.packageManager - .getLaunchIntentForPackage( - baseActivity.packageName - ) ?: return - fillIn - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK) - val launchIntent = PendingIntent.getActivity( - context, - /* requestCode= */ 0, - fillIn, - PendingIntent.FLAG_IMMUTABLE - ) + val fillIn: Intent = + context.packageManager.getLaunchIntentForPackage(baseActivity.packageName) ?: return + fillIn.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK) + val launchIntent = + PendingIntent.getActivity( + context, + /* requestCode= */ 0, + fillIn, + PendingIntent.FLAG_IMMUTABLE, + ) val options = createNewWindowOptions(callingTaskInfo) when (options.launchWindowingMode) { WINDOWING_MODE_MULTI_WINDOW -> { - val splitPosition = splitScreenController - .determineNewInstancePosition(callingTaskInfo) + val splitPosition = + splitScreenController.determineNewInstancePosition(callingTaskInfo) // TODO(b/349828130) currently pass in index_undefined until we can revisit these // specific cases in the future. - val splitIndex = if (enableFlexibleSplit()) - splitScreenController.determineNewInstanceIndex(callingTaskInfo) else - SPLIT_INDEX_UNDEFINED + val splitIndex = + if (enableFlexibleSplit()) + splitScreenController.determineNewInstanceIndex(callingTaskInfo) + else SPLIT_INDEX_UNDEFINED splitScreenController.startIntent( - launchIntent, context.userId, fillIn, splitPosition, - options.toBundle(), null /* hideTaskToken */, - true /* forceLaunchNewTask */, splitIndex) + launchIntent, + context.userId, + fillIn, + splitPosition, + options.toBundle(), + null /* hideTaskToken */, + true /* forceLaunchNewTask */, + splitIndex, + ) } WINDOWING_MODE_FREEFORM -> { val wct = WindowContainerTransaction() @@ -1562,36 +1632,39 @@ class DesktopTasksController( transitionType = TRANSIT_OPEN, wct = wct, launchingTaskId = null, - displayId = callingTaskInfo.displayId + displayId = callingTaskInfo.displayId, ) } } } private fun createNewWindowOptions(callingTask: RunningTaskInfo): ActivityOptions { - val newTaskWindowingMode = when { - callingTask.isFreeform -> { - WINDOWING_MODE_FREEFORM - } - callingTask.isFullscreen || callingTask.isMultiWindow -> { - WINDOWING_MODE_MULTI_WINDOW - } - else -> { - error("Invalid windowing mode: ${callingTask.windowingMode}") - } - } - val bounds = when (newTaskWindowingMode) { - WINDOWING_MODE_FREEFORM -> { - displayController.getDisplayLayout(callingTask.displayId) - ?.let { getInitialBounds(it, callingTask, callingTask.displayId) } - } - WINDOWING_MODE_MULTI_WINDOW -> { - Rect() + val newTaskWindowingMode = + when { + callingTask.isFreeform -> { + WINDOWING_MODE_FREEFORM + } + callingTask.isFullscreen || callingTask.isMultiWindow -> { + WINDOWING_MODE_MULTI_WINDOW + } + else -> { + error("Invalid windowing mode: ${callingTask.windowingMode}") + } } - else -> { - error("Invalid windowing mode: $newTaskWindowingMode") + val bounds = + when (newTaskWindowingMode) { + WINDOWING_MODE_FREEFORM -> { + displayController.getDisplayLayout(callingTask.displayId)?.let { + getInitialBounds(it, callingTask, callingTask.displayId) + } + } + WINDOWING_MODE_MULTI_WINDOW -> { + Rect() + } + else -> { + error("Invalid windowing mode: $newTaskWindowingMode") + } } - } return ActivityOptions.makeBasic().apply { launchWindowingMode = newTaskWindowingMode pendingIntentBackgroundActivityStartMode = @@ -1617,7 +1690,7 @@ class DesktopTasksController( private fun handleFreeformTaskLaunch( task: RunningTaskInfo, - transition: IBinder + transition: IBinder, ): WindowContainerTransaction? { logV("handleFreeformTaskLaunch") if (keyguardManager.isKeyguardLocked) { @@ -1644,8 +1717,10 @@ class DesktopTasksController( // TODO(b/365723620): Handle non running tasks that were launched after reboot. // If task is already visible, it must have been handled already and added to desktop mode. // Cascade task only if it's not visible yet. - if (DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue() - && !taskRepository.isVisibleTask(task.taskId)) { + if ( + DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue() && + !taskRepository.isVisibleTask(task.taskId) + ) { val displayLayout = displayController.getDisplayLayout(task.displayId) if (displayLayout != null) { val initialBounds = Rect(task.configuration.windowConfiguration.bounds) @@ -1680,7 +1755,7 @@ class DesktopTasksController( private fun handleFullscreenTaskLaunch( task: RunningTaskInfo, - transition: IBinder + transition: IBinder, ): WindowContainerTransaction? { logV("handleFullscreenTaskLaunch") if (shouldFullscreenTaskLaunchSwitchToDesktop(task)) { @@ -1691,8 +1766,10 @@ class DesktopTasksController( // that's not the case for launches in desktop. Also, if this launch is the first // one to trigger the desktop mode (e.g., when [forceEnterDesktop()]), activate the // desktop mode here. - if (task.baseIntent.flags.and(Intent.FLAG_ACTIVITY_TASK_ON_HOME) != 0 - || !isDesktopModeShowing(task.displayId)) { + if ( + task.baseIntent.flags.and(Intent.FLAG_ACTIVITY_TASK_ON_HOME) != 0 || + !isDesktopModeShowing(task.displayId) + ) { bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId) wct.reorder(task.token, true) } @@ -1706,7 +1783,7 @@ class DesktopTasksController( transition, wct, task.displayId, - reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH + reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH, ) } } else if (taskRepository.isActiveTask(task.taskId)) { @@ -1737,10 +1814,13 @@ class DesktopTasksController( } /** Handle task closing by removing wallpaper activity if it's the last active task */ - private fun handleTaskClosing(task: RunningTaskInfo, transition: IBinder, requestType: Int): WindowContainerTransaction? { + private fun handleTaskClosing( + task: RunningTaskInfo, + transition: IBinder, + requestType: Int, + ): WindowContainerTransaction? { logV("handleTaskClosing") - if (!isDesktopModeShowing(task.displayId)) - return null + if (!isDesktopModeShowing(task.displayId)) return null val wct = WindowContainerTransaction() performDesktopExitCleanupIfNeeded(task.taskId, wct) @@ -1748,28 +1828,18 @@ class DesktopTasksController( if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) { taskRepository.addClosingTask(task.displayId, task.taskId) desktopTilingDecorViewModel.removeTaskIfTiled(task.displayId, task.taskId) - } else if (requestType == TRANSIT_CLOSE) { - // Handle closing tasks, tasks that are going to back are handled in - // [DesktopTasksTransitionObserver]. - desktopMixedTransitionHandler.addPendingMixedTransition( - DesktopMixedTransitionHandler.PendingMixedTransition.Minimize( - transition, task.taskId, taskRepository.getVisibleTaskCount(task.displayId) == 1 - ) - ) } + taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate( - doesAnyTaskRequireTaskbarRounding( - task.displayId, - task.taskId - ) + doesAnyTaskRequireTaskbarRounding(task.displayId, task.taskId) ) return if (wct.isEmpty) null else wct } /** * Apply all changes required when task is first added to desktop. Uses the task's current - * display by default to apply initial bounds and placement relative to the display. - * Use a different [displayId] if the task should be moved to a different display. + * display by default to apply initial bounds and placement relative to the display. Use a + * different [displayId] if the task should be moved to a different display. */ @VisibleForTesting fun addMoveToDesktopChanges( @@ -1804,11 +1874,12 @@ class DesktopTasksController( taskInfo: RunningTaskInfo, displayId: Int, ): Rect { - val bounds = if (ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue) { - calculateInitialBounds(displayLayout, taskInfo) - } else { - calculateDefaultDesktopTaskBounds(displayLayout) - } + val bounds = + if (ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue) { + calculateInitialBounds(displayLayout, taskInfo) + } else { + calculateDefaultDesktopTaskBounds(displayLayout) + } if (DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue) { cascadeWindow(bounds, displayLayout, displayId) @@ -1818,7 +1889,7 @@ class DesktopTasksController( private fun addMoveToFullscreenChanges( wct: WindowContainerTransaction, - taskInfo: RunningTaskInfo + taskInfo: RunningTaskInfo, ) { val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!! val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode @@ -1845,8 +1916,12 @@ class DesktopTasksController( val activeTasks = taskRepository.getExpandedTasksOrdered(displayId) activeTasks.firstOrNull()?.let { activeTask -> shellTaskOrganizer.getRunningTaskInfo(activeTask)?.let { - cascadeWindow(context.resources, stableBounds, - it.configuration.windowConfiguration.bounds, bounds) + cascadeWindow( + context.resources, + stableBounds, + it.configuration.windowConfiguration.bounds, + bounds, + ) } } } @@ -1872,24 +1947,19 @@ class DesktopTasksController( private fun addAndGetMinimizeChanges( displayId: Int, wct: WindowContainerTransaction, - newTaskId: Int + newTaskId: Int, ): Int? { if (!desktopTasksLimiter.isPresent) return null - return desktopTasksLimiter - .get() - .addAndGetMinimizeTaskChanges(displayId, wct, newTaskId) + return desktopTasksLimiter.get().addAndGetMinimizeTaskChanges(displayId, wct, newTaskId) } - private fun addPendingMinimizeTransition( - transition: IBinder, - taskIdToMinimize: Int, - ) { + private fun addPendingMinimizeTransition(transition: IBinder, taskIdToMinimize: Int) { val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize) desktopTasksLimiter.ifPresent { it.addPendingMinimizeChange( transition = transition, displayId = taskToMinimize?.displayId ?: DEFAULT_DISPLAY, - taskId = taskIdToMinimize + taskId = taskIdToMinimize, ) } } @@ -1899,13 +1969,21 @@ class DesktopTasksController( launchTaskId: Int, minimizeTaskId: Int?, ) { - if (!DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) { + if ( + !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue && + !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue + ) { return } // TODO b/359523924: pass immersive task here? desktopMixedTransitionHandler.addPendingMixedTransition( DesktopMixedTransitionHandler.PendingMixedTransition.Launch( - transition, launchTaskId, minimizeTaskId, /* exitingImmersiveTask= */ null)) + transition, + launchTaskId, + minimizeTaskId, + /* exitingImmersiveTask= */ null, + ) + ) } fun removeDesktop(displayId: Int) { @@ -1940,10 +2018,7 @@ class DesktopTasksController( * changes if this transition is enabled. */ @JvmOverloads - fun requestSplit( - taskInfo: RunningTaskInfo, - leftOrTop: Boolean = false - ) { + fun requestSplit(taskInfo: RunningTaskInfo, leftOrTop: Boolean = false) { // If a drag to desktop is in progress, we want to enter split select // even if the requesting task is already in split. val isDragging = dragToDesktopTransitionHandler.inProgress @@ -1951,11 +2026,12 @@ class DesktopTasksController( if (shouldRequestSplit) { if (isDragging) { releaseVisualIndicator() - val cancelState = if (leftOrTop) { - DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT - } else { - DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT - } + val cancelState = + if (leftOrTop) { + DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT + } else { + DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT + } dragToDesktopTransitionHandler.cancelDragToDesktopTransition(cancelState) } else { val wct = WindowContainerTransaction() @@ -1964,7 +2040,7 @@ class DesktopTasksController( taskInfo, wct, if (leftOrTop) SPLIT_POSITION_TOP_OR_LEFT else SPLIT_POSITION_BOTTOM_OR_RIGHT, - taskInfo.configuration.windowConfiguration.bounds + taskInfo.configuration.windowConfiguration.bounds, ) } } @@ -1999,12 +2075,17 @@ class DesktopTasksController( taskInfo: RunningTaskInfo, taskSurface: SurfaceControl, inputX: Float, - taskBounds: Rect + taskBounds: Rect, ) { if (taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) return desktopTilingDecorViewModel.removeTaskIfTiled(taskInfo.displayId, taskInfo.taskId) - updateVisualIndicator(taskInfo, taskSurface, inputX, taskBounds.top.toFloat(), - DragStartState.FROM_FREEFORM) + updateVisualIndicator( + taskInfo, + taskSurface, + inputX, + taskBounds.top.toFloat(), + DragStartState.FROM_FREEFORM, + ) } fun updateVisualIndicator( @@ -2012,7 +2093,7 @@ class DesktopTasksController( taskSurface: SurfaceControl?, inputX: Float, taskTop: Float, - dragStartState: DragStartState + dragStartState: DragStartState, ): DesktopModeVisualIndicator.IndicatorType { // If the visual indicator does not exist, create it. val indicator = @@ -2021,10 +2102,14 @@ class DesktopTasksController( syncQueue, taskInfo, displayController, - context, + if (Flags.enableBugFixesForSecondaryDisplay()) { + displayController.getDisplayContext(taskInfo.displayId) + } else { + context + }, taskSurface, rootTaskDisplayAreaOrganizer, - dragStartState + dragStartState, ) if (visualIndicator == null) visualIndicator = indicator return indicator.updateIndicatorType(PointF(inputX, taskTop)) @@ -2039,7 +2124,7 @@ class DesktopTasksController( * @param position position of surface when drag ends. * @param inputCoordinate the coordinates of the motion event * @param currentDragBounds the current bounds of where the visible task is (might be actual - * task bounds or just task leash) + * task bounds or just task leash) * @param validDragArea the bounds of where the task can be dragged within the display. * @param dragStartBounds the bounds of the task before starting dragging. */ @@ -2061,7 +2146,7 @@ class DesktopTasksController( val indicator = getVisualIndicator() ?: return val indicatorType = indicator.updateIndicatorType( - PointF(inputCoordinate.x, currentDragBounds.top.toFloat()), + PointF(inputCoordinate.x, currentDragBounds.top.toFloat()) ) when (indicatorType) { IndicatorType.TO_FULLSCREEN_INDICATOR -> { @@ -2070,19 +2155,19 @@ class DesktopTasksController( } else { desktopModeUiEventLogger.log( taskInfo, - DesktopUiEventEnum.DESKTOP_WINDOW_APP_HEADER_DRAG_TO_FULL_SCREEN + DesktopUiEventEnum.DESKTOP_WINDOW_APP_HEADER_DRAG_TO_FULL_SCREEN, ) moveToFullscreenWithAnimation( taskInfo, position, - DesktopModeTransitionSource.TASK_DRAG + DesktopModeTransitionSource.TASK_DRAG, ) } } IndicatorType.TO_SPLIT_LEFT_INDICATOR -> { desktopModeUiEventLogger.log( taskInfo, - DesktopUiEventEnum.DESKTOP_WINDOW_APP_HEADER_DRAG_TO_TILE_TO_LEFT + DesktopUiEventEnum.DESKTOP_WINDOW_APP_HEADER_DRAG_TO_TILE_TO_LEFT, ) handleSnapResizingTaskOnDrag( taskInfo, @@ -2097,7 +2182,7 @@ class DesktopTasksController( IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> { desktopModeUiEventLogger.log( taskInfo, - DesktopUiEventEnum.DESKTOP_WINDOW_APP_HEADER_DRAG_TO_TILE_TO_RIGHT + DesktopUiEventEnum.DESKTOP_WINDOW_APP_HEADER_DRAG_TO_TILE_TO_RIGHT, ) handleSnapResizingTaskOnDrag( taskInfo, @@ -2116,7 +2201,7 @@ class DesktopTasksController( // If task bounds are outside valid drag area, snap them inward DragPositioningCallbackUtility.snapTaskBoundsIfNecessary( destinationBounds, - validDragArea + validDragArea, ) if (destinationBounds == dragStartBounds) { @@ -2148,8 +2233,9 @@ class DesktopTasksController( } // A freeform drag-move ended, remove the indicator immediately. releaseVisualIndicator() - taskbarDesktopTaskListener - ?.onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding(taskInfo.displayId)) + taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate( + doesAnyTaskRequireTaskbarRounding(taskInfo.displayId) + ) } /** @@ -2157,9 +2243,7 @@ class DesktopTasksController( * * @param taskInfo the task being dragged. */ - fun onDragPositioningCancelThroughStatusBar( - taskInfo: RunningTaskInfo, - ) { + fun onDragPositioningCancelThroughStatusBar(taskInfo: RunningTaskInfo) { interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD) cancelDragToDesktop(taskInfo) } @@ -2183,11 +2267,16 @@ class DesktopTasksController( when (indicatorType) { IndicatorType.TO_DESKTOP_INDICATOR -> { // Start a new jank interaction for the drag release to desktop window animation. - interactionJankMonitor.begin(taskSurface, context, handler, - CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE, "to_desktop") + interactionJankMonitor.begin( + taskSurface, + context, + handler, + CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE, + "to_desktop", + ) desktopModeUiEventLogger.log( taskInfo, - DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_DRAG_TO_DESKTOP_MODE + DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_DRAG_TO_DESKTOP_MODE, ) finalizeDragToDesktop(taskInfo) } @@ -2195,21 +2284,21 @@ class DesktopTasksController( IndicatorType.TO_FULLSCREEN_INDICATOR -> { desktopModeUiEventLogger.log( taskInfo, - DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_DRAG_TO_FULL_SCREEN + DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_DRAG_TO_FULL_SCREEN, ) cancelDragToDesktop(taskInfo) } IndicatorType.TO_SPLIT_LEFT_INDICATOR -> { desktopModeUiEventLogger.log( taskInfo, - DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_DRAG_TO_SPLIT_SCREEN + DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_DRAG_TO_SPLIT_SCREEN, ) requestSplit(taskInfo, leftOrTop = true) } IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> { desktopModeUiEventLogger.log( taskInfo, - DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_DRAG_TO_SPLIT_SCREEN + DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_DRAG_TO_SPLIT_SCREEN, ) requestSplit(taskInfo, leftOrTop = false) } @@ -2251,7 +2340,7 @@ class DesktopTasksController( override fun onUnhandledDrag( launchIntent: PendingIntent, dragEvent: DragEvent, - onFinishCallback: Consumer<Boolean> + onFinishCallback: Consumer<Boolean>, ): Boolean { // TODO(b/320797628): Pass through which display we are dropping onto if (!isDesktopModeShowing(DEFAULT_DISPLAY)) { @@ -2270,22 +2359,27 @@ class DesktopTasksController( // window will accept a drag event. This way, we can hide the indicator when we won't // be handling the transition here, allowing us to display the indicator accurately. // For now, we create the indicator only on drag end and immediately dispose it. - val indicatorType = updateVisualIndicator(taskInfo, dragEvent.dragSurface, - dragEvent.x, dragEvent.y, - DragStartState.DRAGGED_INTENT) + val indicatorType = + updateVisualIndicator( + taskInfo, + dragEvent.dragSurface, + dragEvent.x, + dragEvent.y, + DragStartState.DRAGGED_INTENT, + ) releaseVisualIndicator() - val windowingMode = when (indicatorType) { - IndicatorType.TO_FULLSCREEN_INDICATOR -> { - WINDOWING_MODE_FULLSCREEN - } - IndicatorType.TO_SPLIT_LEFT_INDICATOR, - IndicatorType.TO_SPLIT_RIGHT_INDICATOR, - IndicatorType.TO_DESKTOP_INDICATOR - -> { - WINDOWING_MODE_FREEFORM + val windowingMode = + when (indicatorType) { + IndicatorType.TO_FULLSCREEN_INDICATOR -> { + WINDOWING_MODE_FULLSCREEN + } + IndicatorType.TO_SPLIT_LEFT_INDICATOR, + IndicatorType.TO_SPLIT_RIGHT_INDICATOR, + IndicatorType.TO_DESKTOP_INDICATOR -> { + WINDOWING_MODE_FREEFORM + } + else -> error("Invalid indicator type: $indicatorType") } - else -> error("Invalid indicator type: $indicatorType") - } val displayLayout = displayController.getDisplayLayout(DEFAULT_DISPLAY) ?: return false val newWindowBounds = Rect() when (indicatorType) { @@ -2294,7 +2388,7 @@ class DesktopTasksController( newWindowBounds.set(calculateDefaultDesktopTaskBounds(displayLayout)) newWindowBounds.offsetTo( dragEvent.x.toInt() - (newWindowBounds.width() / 2), - dragEvent.y.toInt() + dragEvent.y.toInt(), ) } IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> { @@ -2342,7 +2436,9 @@ class DesktopTasksController( // TODO(b/366397912): Support full multi-user mode in Windowing. override fun onUserChanged(newUserId: Int, userContext: Context) { + logV("onUserChanged previousUserId=%d, newUserId=%d", userId, newUserId) userId = newUserId + taskRepository = userRepositories.getProfile(userId) desktopTilingDecorViewModel.onUserChange() } @@ -2351,12 +2447,15 @@ class DesktopTasksController( if (!Flags.enableFullyImmersiveInDesktop()) return val inImmersive = taskRepository.isTaskInFullImmersiveState(taskInfo.taskId) val requestingImmersive = taskInfo.requestingImmersive - if (inImmersive && !requestingImmersive - && !RecentsTransitionStateListener.isRunning(recentsTransitionState)) { + if ( + inImmersive && + !requestingImmersive && + !RecentsTransitionStateListener.isRunning(recentsTransitionState) + ) { // Exit immersive if the app is no longer requesting it. desktopImmersiveController.moveTaskToNonImmersive( taskInfo, - DesktopImmersiveController.ExitReason.APP_NOT_IMMERSIVE + DesktopImmersiveController.ExitReason.APP_NOT_IMMERSIVE, ) } } @@ -2365,6 +2464,7 @@ class DesktopTasksController( val innerPrefix = "$prefix " pw.println("${prefix}DesktopTasksController") DesktopModeStatus.dump(pw, innerPrefix, context) + pw.println("${prefix}userId=$userId") taskRepository.dump(pw, innerPrefix) } @@ -2373,7 +2473,7 @@ class DesktopTasksController( private inner class DesktopModeImpl : DesktopMode { override fun addVisibleTasksListener( listener: VisibleTasksListener, - callbackExecutor: Executor + callbackExecutor: Executor, ) { mainExecutor.execute { this@DesktopTasksController.addVisibleTasksListener(listener, callbackExecutor) @@ -2382,7 +2482,7 @@ class DesktopTasksController( override fun addDesktopGestureExclusionRegionListener( listener: Consumer<Region>, - callbackExecutor: Executor + callbackExecutor: Executor, ) { mainExecutor.execute { this@DesktopTasksController.setTaskRegionListener(listener, callbackExecutor) @@ -2391,8 +2491,9 @@ class DesktopTasksController( override fun moveFocusedTaskToDesktop( displayId: Int, - transitionSource: DesktopModeTransitionSource + transitionSource: DesktopModeTransitionSource, ) { + logV("moveFocusedTaskToDesktop") mainExecutor.execute { this@DesktopTasksController.moveFocusedTaskToDesktop(displayId, transitionSource) } @@ -2400,14 +2501,16 @@ class DesktopTasksController( override fun moveFocusedTaskToFullscreen( displayId: Int, - transitionSource: DesktopModeTransitionSource + transitionSource: DesktopModeTransitionSource, ) { + logV("moveFocusedTaskToFullscreen") mainExecutor.execute { this@DesktopTasksController.enterFullscreen(displayId, transitionSource) } } override fun moveFocusedTaskToStageSplit(displayId: Int, leftOrTop: Boolean) { + logV("moveFocusedTaskToStageSplit") mainExecutor.execute { this@DesktopTasksController.enterSplit(displayId, leftOrTop) } } } @@ -2427,7 +2530,7 @@ class DesktopTasksController( WM_SHELL_DESKTOP_MODE, "IDesktopModeImpl: onVisibilityChanged display=%d visible=%d", displayId, - visibleTasksCount + visibleTasksCount, ) remoteListener.call { l -> l.onTasksVisibilityChanged(displayId, visibleTasksCount) @@ -2436,21 +2539,22 @@ class DesktopTasksController( } private val taskbarDesktopTaskListener: TaskbarDesktopTaskListener = - object : TaskbarDesktopTaskListener { - override fun onTaskbarCornerRoundingUpdate( - hasTasksRequiringTaskbarRounding: Boolean) { - ProtoLog.v( - WM_SHELL_DESKTOP_MODE, - "IDesktopModeImpl: onTaskbarCornerRoundingUpdate " + - "doesAnyTaskRequireTaskbarRounding=%s", - hasTasksRequiringTaskbarRounding - ) + object : TaskbarDesktopTaskListener { + override fun onTaskbarCornerRoundingUpdate( + hasTasksRequiringTaskbarRounding: Boolean + ) { + ProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "IDesktopModeImpl: onTaskbarCornerRoundingUpdate " + + "doesAnyTaskRequireTaskbarRounding=%s", + hasTasksRequiringTaskbarRounding, + ) - remoteListener.call { l -> - l.onTaskbarCornerRoundingUpdate(hasTasksRequiringTaskbarRounding) - } + remoteListener.call { l -> + l.onTaskbarCornerRoundingUpdate(hasTasksRequiringTaskbarRounding) } } + } private val desktopModeEntryExitTransitionListener: DesktopModeEntryExitTransitionListener = object : DesktopModeEntryExitTransitionListener { @@ -2458,18 +2562,22 @@ class DesktopTasksController( ProtoLog.v( WM_SHELL_DESKTOP_MODE, "IDesktopModeImpl: onEnterDesktopModeTransitionStarted transitionTime=%s", - transitionDuration + transitionDuration, ) - remoteListener.call { l -> l.onEnterDesktopModeTransitionStarted(transitionDuration) } + remoteListener.call { l -> + l.onEnterDesktopModeTransitionStarted(transitionDuration) + } } override fun onExitDesktopModeTransitionStarted(transitionDuration: Int) { ProtoLog.v( WM_SHELL_DESKTOP_MODE, "IDesktopModeImpl: onExitDesktopModeTransitionStarted transitionTime=%s", - transitionDuration + transitionDuration, ) - remoteListener.call { l -> l.onExitDesktopModeTransitionStarted(transitionDuration) } + remoteListener.call { l -> + l.onExitDesktopModeTransitionStarted(transitionDuration) + } } } @@ -2491,7 +2599,7 @@ class DesktopTasksController( c.taskbarDesktopTaskListener = null c.desktopModeEnterExitTransitionListener = null } - } + }, ) } @@ -2518,8 +2626,10 @@ class DesktopTasksController( } override fun hideStashedDesktopApps(displayId: Int) { - ProtoLog.w(WM_SHELL_DESKTOP_MODE, - "IDesktopModeImpl: hideStashedDesktopApps is deprecated") + ProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "IDesktopModeImpl: hideStashedDesktopApps is deprecated", + ) } override fun getVisibleTaskCount(displayId: Int): Int { @@ -2528,16 +2638,14 @@ class DesktopTasksController( controller, "visibleTaskCount", { controller -> result[0] = controller.visibleTaskCount(displayId) }, - true /* blocking */ + true, /* blocking */ ) return result[0] } override fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo) { - executeRemoteCallWithTaskPermission( - controller, - "onDesktopSplitSelectAnimComplete" - ) { c -> + executeRemoteCallWithTaskPermission(controller, "onDesktopSplitSelectAnimComplete") { c + -> c.onDesktopSplitSelectAnimComplete(taskInfo) } } @@ -2571,9 +2679,11 @@ class DesktopTasksController( private fun logV(msg: String, vararg arguments: Any?) { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } + private fun logD(msg: String, vararg arguments: Any?) { ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } + private fun logW(msg: String, vararg arguments: Any?) { ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } @@ -2583,6 +2693,10 @@ class DesktopTasksController( val DESKTOP_MODE_INITIAL_BOUNDS_SCALE = SystemProperties.getInt("persist.wm.debug.desktop_mode_initial_bounds_scale", 75) / 100f + // Timeout used for CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD, this is longer than the + // default timeout to avoid timing out in the middle of a drag action. + private val APP_HANDLE_DRAG_HOLD_CUJ_TIMEOUT_MS: Long = TimeUnit.SECONDS.toMillis(10L) + private const val TAG = "DesktopTasksController" } @@ -2608,6 +2722,6 @@ class DesktopTasksController( /** The positions on a screen that a task can snap to. */ enum class SnapPosition { RIGHT, - LEFT + LEFT, } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt index 77af627a948a..635078e68a00 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt @@ -16,14 +16,15 @@ package com.android.wm.shell.desktopmode +import android.app.ActivityManager import android.content.Context import android.os.Handler import android.os.IBinder import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_TO_BACK +import android.window.DesktopModeFlags import android.window.TransitionInfo import android.window.WindowContainerTransaction -import android.window.DesktopModeFlags import androidx.annotation.VisibleForTesting import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW import com.android.internal.jank.InteractionJankMonitor @@ -31,6 +32,7 @@ import com.android.internal.protolog.ProtoLog import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.annotations.ShellMainThread +import com.android.wm.shell.sysui.UserChangeListener import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TransitionObserver @@ -38,35 +40,37 @@ import com.android.wm.shell.transition.Transitions.TransitionObserver * Limits the number of tasks shown in Desktop Mode. * * This class should only be used if - * [android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT] - * is enabled and [maxTasksLimit] is strictly greater than 0. + * [android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT] is enabled and + * [maxTasksLimit] is strictly greater than 0. */ -class DesktopTasksLimiter ( - transitions: Transitions, - private val taskRepository: DesktopRepository, - private val shellTaskOrganizer: ShellTaskOrganizer, - private val maxTasksLimit: Int, - private val interactionJankMonitor: InteractionJankMonitor, - private val context: Context, - @ShellMainThread private val handler: Handler, +class DesktopTasksLimiter( + transitions: Transitions, + private val desktopUserRepositories: DesktopUserRepositories, + private val shellTaskOrganizer: ShellTaskOrganizer, + private val maxTasksLimit: Int, + private val interactionJankMonitor: InteractionJankMonitor, + private val context: Context, + @ShellMainThread private val handler: Handler, ) { private val minimizeTransitionObserver = MinimizeTransitionObserver() - @VisibleForTesting - val leftoverMinimizedTasksRemover = LeftoverMinimizedTasksRemover() + @VisibleForTesting val leftoverMinimizedTasksRemover = LeftoverMinimizedTasksRemover() + + private var userId: Int init { require(maxTasksLimit > 0) { "DesktopTasksLimiter: maxTasksLimit should be greater than 0. Current value: $maxTasksLimit." } transitions.registerObserver(minimizeTransitionObserver) - taskRepository.addActiveTaskListener(leftoverMinimizedTasksRemover) + userId = ActivityManager.getCurrentUser() + desktopUserRepositories.current.addActiveTaskListener(leftoverMinimizedTasksRemover) logV("Starting limiter with a maximum of %d tasks", maxTasksLimit) } private data class TaskDetails( val displayId: Int, val taskId: Int, - var transitionInfo: TransitionInfo? + var transitionInfo: TransitionInfo?, ) // TODO(b/333018485): replace this observer when implementing the minimize-animation @@ -82,8 +86,9 @@ class DesktopTasksLimiter ( transition: IBinder, info: TransitionInfo, startTransaction: SurfaceControl.Transaction, - finishTransaction: SurfaceControl.Transaction + finishTransaction: SurfaceControl.Transaction, ) { + val taskRepository = desktopUserRepositories.current val taskToMinimize = pendingTransitionTokensAndTasks.remove(transition) ?: return if (!taskRepository.isActiveTask(taskToMinimize.taskId)) return if (!isTaskReadyForMinimize(info, taskToMinimize)) { @@ -94,12 +99,13 @@ class DesktopTasksLimiter ( activeTransitionTokensAndTasks[transition] = taskToMinimize // Save current bounds before minimizing in case we need to restore to it later. - val boundsBeforeMinimize = info.changes.find { change -> - change.taskInfo?.taskId == taskToMinimize.taskId }?.startAbsBounds + val boundsBeforeMinimize = + info.changes + .find { change -> change.taskInfo?.taskId == taskToMinimize.taskId } + ?.startAbsBounds taskRepository.saveBoundsBeforeMinimize(taskToMinimize.taskId, boundsBeforeMinimize) - this@DesktopTasksLimiter.minimizeTask( - taskToMinimize.displayId, taskToMinimize.taskId) + this@DesktopTasksLimiter.minimizeTask(taskToMinimize.displayId, taskToMinimize.taskId) } /** @@ -110,10 +116,11 @@ class DesktopTasksLimiter ( */ private fun isTaskReadyForMinimize( info: TransitionInfo, - taskDetails: TaskDetails + taskDetails: TaskDetails, ): Boolean { - val taskChange = info.changes.find { change -> - change.taskInfo?.taskId == taskDetails.taskId } + val taskChange = + info.changes.find { change -> change.taskInfo?.taskId == taskDetails.taskId } + val taskRepository = desktopUserRepositories.current if (taskChange == null) return !taskRepository.isVisibleTask(taskDetails.taskId) return taskChange.mode == TRANSIT_TO_BACK } @@ -123,8 +130,10 @@ class DesktopTasksLimiter ( if (mActiveTaskDetails != null && mActiveTaskDetails.transitionInfo != null) { // Begin minimize window CUJ instrumentation. interactionJankMonitor.begin( - mActiveTaskDetails.transitionInfo?.rootLeash, context, handler, - CUJ_DESKTOP_MODE_MINIMIZE_WINDOW + mActiveTaskDetails.transitionInfo?.rootLeash, + context, + handler, + CUJ_DESKTOP_MODE_MINIMIZE_WINDOW, ) } } @@ -151,7 +160,8 @@ class DesktopTasksLimiter ( } @VisibleForTesting - inner class LeftoverMinimizedTasksRemover : DesktopRepository.ActiveTasksListener { + inner class LeftoverMinimizedTasksRemover : + DesktopRepository.ActiveTasksListener, UserChangeListener { override fun onActiveTasksChanged(displayId: Int) { // If back navigation is enabled, we shouldn't remove the leftover tasks if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return @@ -161,6 +171,7 @@ class DesktopTasksLimiter ( } fun removeLeftoverMinimizedTasks(displayId: Int, wct: WindowContainerTransaction) { + val taskRepository = desktopUserRepositories.current if (taskRepository.getExpandedTasksOrdered(displayId).isNotEmpty()) return val remainingMinimizedTasks = taskRepository.getMinimizedTasks(displayId) if (remainingMinimizedTasks.isEmpty()) return @@ -173,39 +184,46 @@ class DesktopTasksLimiter ( } } } + + override fun onUserChanged(newUserId: Int, userContext: Context) { + // Removes active task listener for the previous repository + desktopUserRepositories.getProfile(userId).removeActiveTasksListener(this) + + // Sets active listener for the current repository. + userId = newUserId + desktopUserRepositories.getProfile(newUserId).addActiveTaskListener(this) + } } /** * Mark task with [taskId] on [displayId] as minimized. * - * This should be after the corresponding transition has finished so we don't - * minimize the task if the transition fails. + * This should be after the corresponding transition has finished so we don't minimize the task + * if the transition fails. */ private fun minimizeTask(displayId: Int, taskId: Int) { logV("Minimize taskId=%d, displayId=%d", taskId, displayId) + val taskRepository = desktopUserRepositories.current taskRepository.minimizeTask(displayId, taskId) } /** - * Adds a minimize-transition to [wct] if adding [newFrontTaskInfo] crosses task - * limit, returning the task to minimize. + * Adds a minimize-transition to [wct] if adding [newFrontTaskInfo] crosses task limit, + * returning the task to minimize. */ fun addAndGetMinimizeTaskChanges( - displayId: Int, - wct: WindowContainerTransaction, - newFrontTaskId: Int, + displayId: Int, + wct: WindowContainerTransaction, + newFrontTaskId: Int, ): Int? { logV("addAndGetMinimizeTaskChanges, newFrontTask=%d", newFrontTaskId) - + val taskRepository = desktopUserRepositories.current val taskIdToMinimize = - getTaskIdToMinimize( - taskRepository.getExpandedTasksOrdered(displayId), - newFrontTaskId - ) + getTaskIdToMinimize(taskRepository.getExpandedTasksOrdered(displayId), newFrontTaskId) // If it's a running task, reorder it to back. - taskIdToMinimize?.let { shellTaskOrganizer.getRunningTaskInfo(it) }?.let { - wct.reorder(it.token, false /* onTop */) - } + taskIdToMinimize + ?.let { shellTaskOrganizer.getRunningTaskInfo(it) } + ?.let { wct.reorder(it.token, false /* onTop */) } return taskIdToMinimize } @@ -215,20 +233,19 @@ class DesktopTasksLimiter ( */ fun addPendingMinimizeChange(transition: IBinder, displayId: Int, taskId: Int) { minimizeTransitionObserver.addPendingTransitionToken( - transition, TaskDetails(displayId, taskId, transitionInfo = null)) + transition, + TaskDetails(displayId, taskId, transitionInfo = null), + ) } /** - * Returns the minimized task from the list of visible tasks ordered from front to back with - * the new task placed in front of other tasks. + * Returns the minimized task from the list of visible tasks ordered from front to back with the + * new task placed in front of other tasks. */ - fun getTaskIdToMinimize( - visibleOrderedTasks: List<Int>, - newTaskIdInFront: Int? = null - ): Int? { + fun getTaskIdToMinimize(visibleOrderedTasks: List<Int>, newTaskIdInFront: Int? = null): Int? { return getTaskIdToMinimize( - createOrderedTaskListWithGivenTaskInFront( - visibleOrderedTasks, newTaskIdInFront)) + createOrderedTaskListWithGivenTaskInFront(visibleOrderedTasks, newTaskIdInFront) + ) } /** Returns the Task to minimize given a list of visible tasks ordered from front to back. */ @@ -242,16 +259,16 @@ class DesktopTasksLimiter ( } private fun createOrderedTaskListWithGivenTaskInFront( - existingTaskIdsOrderedFrontToBack: List<Int>, - newTaskId: Int? + existingTaskIdsOrderedFrontToBack: List<Int>, + newTaskId: Int?, ): List<Int> { return if (newTaskId == null) existingTaskIdsOrderedFrontToBack - else listOf(newTaskId) + + else + listOf(newTaskId) + existingTaskIdsOrderedFrontToBack.filter { taskId -> taskId != newTaskId } } - @VisibleForTesting - fun getTransitionObserver(): TransitionObserver = minimizeTransitionObserver + @VisibleForTesting fun getTransitionObserver(): TransitionObserver = minimizeTransitionObserver private fun logV(msg: String, vararg arguments: Any?) { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt index c39c715e685c..9625b71ad3cb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.desktopmode +import android.app.ActivityManager import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.content.Context import android.os.IBinder @@ -23,12 +24,13 @@ import android.view.SurfaceControl import android.view.WindowManager import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_TO_BACK -import android.window.TransitionInfo -import android.window.WindowContainerTransaction import android.window.DesktopModeFlags import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY +import android.window.TransitionInfo +import android.window.WindowContainerTransaction import com.android.internal.protolog.ProtoLog import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.back.BackAnimationController import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.isExitDesktopModeTransition import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.TransitionUtil @@ -43,19 +45,22 @@ import com.android.wm.shell.transition.Transitions */ class DesktopTasksTransitionObserver( private val context: Context, - private val desktopRepository: DesktopRepository, + private val desktopUserRepositories: DesktopUserRepositories, private val transitions: Transitions, private val shellTaskOrganizer: ShellTaskOrganizer, private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler, - shellInit: ShellInit + private val backAnimationController: BackAnimationController, + shellInit: ShellInit, ) : Transitions.TransitionObserver { private var transitionToCloseWallpaper: IBinder? = null + private var currentProfileId: Int init { if (DesktopModeStatus.canEnterDesktopMode(context)) { shellInit.addInitCallback(::onInit, this) } + currentProfileId = ActivityManager.getCurrentUser() } fun onInit() { @@ -67,7 +72,7 @@ class DesktopTasksTransitionObserver( transition: IBinder, info: TransitionInfo, startTransaction: SurfaceControl.Transaction, - finishTransaction: SurfaceControl.Transaction + finishTransaction: SurfaceControl.Transaction, ) { // TODO: b/332682201 Update repository state updateWallpaperToken(info) @@ -89,8 +94,11 @@ class DesktopTasksTransitionObserver( val taskInfo = change.taskInfo if (taskInfo == null || taskInfo.taskId == -1) continue - if (desktopRepository.isActiveTask(taskInfo.taskId) && - taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) { + val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId) + if ( + desktopRepository.isActiveTask(taskInfo.taskId) && + taskInfo.windowingMode != WINDOWING_MODE_FREEFORM + ) { desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId) } } @@ -105,35 +113,103 @@ class DesktopTasksTransitionObserver( if (taskInfo == null || taskInfo.taskId == -1) { continue } - + val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId) val visibleTaskCount = desktopRepository.getVisibleTaskCount(taskInfo.displayId) - if (visibleTaskCount > 0 && - change.mode == TRANSIT_TO_BACK && - taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) { + if ( + visibleTaskCount > 0 && + change.mode == TRANSIT_TO_BACK && + taskInfo.windowingMode == WINDOWING_MODE_FREEFORM + ) { desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId) desktopMixedTransitionHandler.addPendingMixedTransition( DesktopMixedTransitionHandler.PendingMixedTransition.Minimize( - transition, taskInfo.taskId, visibleTaskCount == 1)) + transition, + taskInfo.taskId, + visibleTaskCount == 1, + ) + ) + } + } + } else if (info.type == TRANSIT_CLOSE) { + // In some cases app will be closing as a result of back navigation but we would like + // to minimize. Mark the task closing as minimized. + var hasWallpaperClosing = false + var minimizingTask: Int? = null + for (change in info.changes) { + val taskInfo = change.taskInfo + if (taskInfo == null || taskInfo.taskId == -1) continue + if (change.mode != TRANSIT_CLOSE) continue + + if (minimizingTask == null) { + minimizingTask = getMinimizingTaskForClosingTransition(taskInfo) + } + + if (DesktopWallpaperActivity.isWallpaperTask(taskInfo)) { + hasWallpaperClosing = true } } + + if (minimizingTask == null) return + // If the transition has wallpaper closing, it means we are moving out of desktop. + desktopMixedTransitionHandler.addPendingMixedTransition( + DesktopMixedTransitionHandler.PendingMixedTransition.Minimize( + transition, + minimizingTask, + isLastTask = hasWallpaperClosing, + ) + ) + } + } + + /** + * Given this a closing task in a closing transition, a task is assumed to be closed by back + * navigation if: + * 1) Desktop mode is visible. + * 2) Task is in freeform. + * 3) Task is the latest task that the back gesture is triggered on. + * 4) It's not marked as a closing task as a result of closing it by the app header. + * + * This doesn't necessarily mean all the cases are because of back navigation but those cases + * will be rare. E.g. triggering back navigation on an app that pops up a close dialog, and + * closing it will minimize it here. + */ + private fun getMinimizingTaskForClosingTransition( + taskInfo: ActivityManager.RunningTaskInfo + ): Int? { + val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId) + val visibleTaskCount = desktopRepository.getVisibleTaskCount(taskInfo.displayId) + if ( + visibleTaskCount > 0 && + taskInfo.windowingMode == WINDOWING_MODE_FREEFORM && + backAnimationController.latestTriggerBackTask == taskInfo.taskId && + !desktopRepository.isClosingTask(taskInfo.taskId) + ) { + desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId) + return taskInfo.taskId } + return null } private fun removeWallpaperOnLastTaskClosingIfNeeded( transition: IBinder, - info: TransitionInfo + info: TransitionInfo, ) { + // TODO: 380868195 - Smooth animation for wallpaper activity closing just by itself for (change in info.changes) { val taskInfo = change.taskInfo if (taskInfo == null || taskInfo.taskId == -1) { continue } - if (desktopRepository.getVisibleTaskCount(taskInfo.displayId) == 1 && - change.mode == TRANSIT_CLOSE && - taskInfo.windowingMode == WINDOWING_MODE_FREEFORM && - desktopRepository.wallpaperActivityToken != null) { + val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId) + if ( + desktopRepository.getVisibleTaskCount(taskInfo.displayId) == 0 && + change.mode == TRANSIT_CLOSE && + taskInfo.windowingMode == WINDOWING_MODE_FREEFORM && + desktopRepository.wallpaperActivityToken != null + ) { transitionToCloseWallpaper = transition + currentProfileId = taskInfo.userId } } } @@ -150,11 +226,13 @@ class DesktopTasksTransitionObserver( // TODO: b/332682201 Update repository state if (transitionToCloseWallpaper == transition) { // TODO: b/362469671 - Handle merging the animation when desktop is also closing. + val desktopRepository = desktopUserRepositories.getProfile(currentProfileId) desktopRepository.wallpaperActivityToken?.let { wallpaperActivityToken -> transitions.startTransition( TRANSIT_CLOSE, WindowContainerTransaction().removeTask(wallpaperActivityToken), - null) + null, + ) } transitionToCloseWallpaper = null } @@ -167,6 +245,7 @@ class DesktopTasksTransitionObserver( info.changes.forEach { change -> change.taskInfo?.let { taskInfo -> if (DesktopWallpaperActivity.isWallpaperTask(taskInfo)) { + val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId) when (change.mode) { WindowManager.TRANSIT_OPEN -> { desktopRepository.wallpaperActivityToken = taskInfo.token @@ -175,10 +254,10 @@ class DesktopTasksTransitionObserver( // task. shellTaskOrganizer.applyTransaction( WindowContainerTransaction() - .setTaskTrimmableFromRecents(taskInfo.token, false)) + .setTaskTrimmableFromRecents(taskInfo.token, false) + ) } - TRANSIT_CLOSE -> - desktopRepository.wallpaperActivityToken = null + TRANSIT_CLOSE -> desktopRepository.wallpaperActivityToken = null else -> {} } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt new file mode 100644 index 000000000000..e5f52839d9f4 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt @@ -0,0 +1,107 @@ +/* + * 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.desktopmode + +import android.app.ActivityManager +import android.content.Context +import android.content.pm.UserInfo +import android.os.UserManager +import android.util.SparseArray +import com.android.internal.protolog.ProtoLog +import com.android.window.flags.Flags +import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository +import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer +import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import com.android.wm.shell.shared.annotations.ShellMainThread +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus +import com.android.wm.shell.sysui.ShellInit +import com.android.wm.shell.sysui.UserChangeListener +import kotlinx.coroutines.CoroutineScope + +/** Manages per-user DesktopRepository instances. */ +class DesktopUserRepositories( + context: Context, + shellInit: ShellInit, + private val persistentRepository: DesktopPersistentRepository, + private val repositoryInitializer: DesktopRepositoryInitializer, + @ShellMainThread private val mainCoroutineScope: CoroutineScope, + userManager: UserManager, +) : UserChangeListener { + private var userId: Int + private var userIdToProfileIdsMap: MutableMap<Int, List<Int>> = mutableMapOf() + + // TODO(b/357060209): Add caching for this logic to improve efficiency. + val current: DesktopRepository + get() = desktopRepoByUserId.getOrCreate(userId) + + private val desktopRepoByUserId = + object : SparseArray<DesktopRepository>() { + /** Gets [DesktopRepository] for existing [userId] or creates a new one. */ + fun getOrCreate(userId: Int): DesktopRepository = + this[userId] + ?: DesktopRepository(persistentRepository, mainCoroutineScope, userId).also { + this[userId] = it + } + } + + init { + userId = ActivityManager.getCurrentUser() + if (DesktopModeStatus.canEnterDesktopMode(context)) { + shellInit.addInitCallback(::initRepoFromPersistentStorage, this) + } + if (Flags.enableDesktopWindowingHsum()) { + userIdToProfileIdsMap[userId] = userManager.getProfiles(userId).map { it.id } + } + } + + private fun initRepoFromPersistentStorage() { + repositoryInitializer.initialize(this) + } + + /** Returns [DesktopRepository] for the parent user id. */ + fun getProfile(profileId: Int): DesktopRepository { + if (Flags.enableDesktopWindowingHsum()) { + for ((uid, profileIds) in userIdToProfileIdsMap) { + if (profileId in profileIds) { + return desktopRepoByUserId.getOrCreate(uid) + } + } + } + return desktopRepoByUserId.getOrCreate(profileId) + } + + override fun onUserChanged(newUserId: Int, userContext: Context) { + logD("onUserChanged previousUserId=%d, newUserId=%d", userId, newUserId) + userId = newUserId + } + + override fun onUserProfilesChanged(profiles: MutableList<UserInfo>) { + logD("onUserProfilesChanged profiles=%s", profiles.toString()) + if (Flags.enableDesktopWindowingHsum()) { + // TODO(b/366397912): Remove all persisted profile data when the profile changes. + userIdToProfileIdsMap[userId] = profiles.map { it.id } + } + } + + private fun logD(msg: String, vararg arguments: Any?) { + ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) + } + + companion object { + private const val TAG = "DesktopUserRepositories" + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt index 909a06604382..fd39becc2715 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt @@ -49,7 +49,6 @@ class DesktopWallpaperActivity : Activity() { taskInfo.baseIntent.component?.let(::isWallpaperComponent) ?: false @JvmStatic - fun isWallpaperComponent(component: ComponentName) = - component == wallpaperActivityComponent + fun isWallpaperComponent(component: ComponentName) = component == wallpaperActivityComponent } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt index d7d55195d4cf..d23c9d0b8ffd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt @@ -19,10 +19,10 @@ import android.content.Intent import android.content.Intent.FILL_IN_COMPONENT import android.graphics.PointF import android.graphics.Rect -import android.os.Bundle import android.os.IBinder import android.os.SystemClock import android.os.SystemProperties +import android.os.UserHandle import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_CLOSE import android.window.TransitionInfo @@ -109,13 +109,13 @@ sealed class DragToDesktopTransitionHandler( * after one of the "end" or "cancel" transitions is merged into this transition. */ fun startDragToDesktopTransition( - taskId: Int, + taskInfo: RunningTaskInfo, dragToDesktopAnimator: MoveToDesktopAnimator, ) { if (inProgress) { ProtoLog.v( ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, - "DragToDesktop: Drag to desktop transition already in progress." + "DragToDesktop: Drag to desktop transition already in progress.", ) return } @@ -127,35 +127,41 @@ sealed class DragToDesktopTransitionHandler( pendingIntentCreatorBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED } + val taskUser = UserHandle.of(taskInfo.userId) val pendingIntent = - PendingIntent.getActivity( - context, + PendingIntent.getActivityAsUser( + context.createContextAsUser(taskUser, /* flags= */ 0), 0 /* requestCode */, launchHomeIntent, FLAG_MUTABLE or FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or FILL_IN_COMPONENT, - options.toBundle() + options.toBundle(), + taskUser, ) val wct = WindowContainerTransaction() - wct.sendPendingIntent(pendingIntent, launchHomeIntent, Bundle()) + // The app that is being dragged into desktop mode might cause new transitions, make this + // launch transient to make sure those transitions can execute in parallel and thus won't + // block the end-drag transition. + val intentOptions = ActivityOptions.makeBasic().setTransientLaunch() + wct.sendPendingIntent(pendingIntent, launchHomeIntent, intentOptions.toBundle()) val startTransitionToken = transitions.startTransition(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, wct, this) transitionState = - if (isSplitTask(taskId)) { + if (isSplitTask(taskInfo.taskId)) { val otherTask = - getOtherSplitTask(taskId) + getOtherSplitTask(taskInfo.taskId) ?: throw IllegalStateException("Expected split task to have a counterpart.") TransitionState.FromSplit( - draggedTaskId = taskId, + draggedTaskId = taskInfo.taskId, dragAnimator = dragToDesktopAnimator, startTransitionToken = startTransitionToken, - otherSplitTask = otherTask + otherSplitTask = otherTask, ) } else { TransitionState.FromFullscreen( - draggedTaskId = taskId, + draggedTaskId = taskInfo.taskId, dragAnimator = dragToDesktopAnimator, - startTransitionToken = startTransitionToken + startTransitionToken = startTransitionToken, ) } } @@ -244,7 +250,7 @@ sealed class DragToDesktopTransitionHandler( /** Calculate the bounds of a scaled task, then use those bounds to request split select. */ private fun requestSplitFromScaledTask( @SplitPosition splitPosition: Int, - wct: WindowContainerTransaction + wct: WindowContainerTransaction, ) { val state = requireTransitionState() val taskInfo = state.draggedTaskChange?.taskInfo ?: error("Expected non-null taskInfo") @@ -259,7 +265,7 @@ sealed class DragToDesktopTransitionHandler( dragPosition.x.toInt(), dragPosition.y.toInt(), (dragPosition.x + scaledWidth).toInt(), - (dragPosition.y + scaledHeight).toInt() + (dragPosition.y + scaledHeight).toInt(), ) requestSplitSelect(wct, taskInfo, splitPosition, animatedTaskBounds) } @@ -268,14 +274,14 @@ sealed class DragToDesktopTransitionHandler( wct: WindowContainerTransaction, taskInfo: RunningTaskInfo, @SplitPosition splitPosition: Int, - taskBounds: Rect = Rect(taskInfo.configuration.windowConfiguration.bounds) + taskBounds: Rect = Rect(taskInfo.configuration.windowConfiguration.bounds), ) { // Prepare to exit split in order to enter split select. if (taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) { splitScreenController.prepareExitSplitScreen( wct, splitScreenController.getStageOfTask(taskInfo.taskId), - SplitScreenController.EXIT_REASON_DESKTOP_MODE + SplitScreenController.EXIT_REASON_DESKTOP_MODE, ) splitScreenController.transitionHandler.onSplitToDesktop() } @@ -289,7 +295,7 @@ sealed class DragToDesktopTransitionHandler( info: TransitionInfo, startTransaction: SurfaceControl.Transaction, finishTransaction: SurfaceControl.Transaction, - finishCallback: Transitions.TransitionFinishCallback + finishCallback: Transitions.TransitionFinishCallback, ): Boolean { val state = requireTransitionState() @@ -387,7 +393,7 @@ sealed class DragToDesktopTransitionHandler( taskDisplayAreaOrganizer.reparentToDisplayArea( change.endDisplayId, change.leash, - startTransaction + startTransaction, ) val bounds = change.endAbsBounds startTransaction.apply { @@ -454,7 +460,7 @@ sealed class DragToDesktopTransitionHandler( info: TransitionInfo, t: SurfaceControl.Transaction, mergeTarget: IBinder, - finishCallback: Transitions.TransitionFinishCallback + finishCallback: Transitions.TransitionFinishCallback, ) { val state = requireTransitionState() // We don't want to merge the split select animation if that's what we requested. @@ -483,7 +489,7 @@ sealed class DragToDesktopTransitionHandler( setupEndDragToDesktop( info, startTransaction = t, - finishTransaction = startTransactionFinishT + finishTransaction = startTransactionFinishT, ) // Call finishCallback to merge animation before startTransitionFinishCb is called finishCallback.onTransitionFinished(null /* wct */) @@ -503,7 +509,7 @@ sealed class DragToDesktopTransitionHandler( protected open fun setupEndDragToDesktop( info: TransitionInfo, startTransaction: SurfaceControl.Transaction, - finishTransaction: SurfaceControl.Transaction + finishTransaction: SurfaceControl.Transaction, ) { val state = requireTransitionState() val freeformTaskChanges = mutableListOf<Change>() @@ -545,7 +551,7 @@ sealed class DragToDesktopTransitionHandler( protected open fun animateEndDragToDesktop( startTransaction: SurfaceControl.Transaction, - startTransitionFinishCb: Transitions.TransitionFinishCallback + startTransitionFinishCb: Transitions.TransitionFinishCallback, ) { val state = requireTransitionState() val draggedTaskChange = @@ -568,7 +574,7 @@ sealed class DragToDesktopTransitionHandler( startPosition.x.toInt(), startPosition.y.toInt(), startPosition.x.toInt() + unscaledStartWidth, - startPosition.y.toInt() + unscaledStartHeight + startPosition.y.toInt() + unscaledStartHeight, ) dragToDesktopStateListener?.onCommitToDesktopAnimationStart(startTransaction) @@ -578,7 +584,7 @@ sealed class DragToDesktopTransitionHandler( onTaskResizeAnimationListener.onAnimationStart( state.draggedTaskId, startTransaction, - unscaledStartBounds + unscaledStartBounds, ) val tx: SurfaceControl.Transaction = transactionSupplier.get() ValueAnimator.ofObject(rectEvaluator, unscaledStartBounds, endBounds) @@ -594,21 +600,21 @@ sealed class DragToDesktopTransitionHandler( setPosition( draggedTaskLeash, animBounds.left.toFloat(), - animBounds.top.toFloat() + animBounds.top.toFloat(), ) setWindowCrop(draggedTaskLeash, animBounds.width(), animBounds.height()) } onTaskResizeAnimationListener.onBoundsChange( state.draggedTaskId, tx, - animBounds + animBounds, ) } addListener( object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId) - startTransitionFinishCb.onTransitionFinished(/* wct = */ null) + startTransitionFinishCb.onTransitionFinished(/* wct= */ null) clearState() interactionJankMonitor.end( CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE @@ -622,7 +628,7 @@ sealed class DragToDesktopTransitionHandler( override fun handleRequest( transition: IBinder, - request: TransitionRequestInfo + request: TransitionRequestInfo, ): WindowContainerTransaction? { // Only handle transitions started from shell. return null @@ -631,7 +637,7 @@ sealed class DragToDesktopTransitionHandler( override fun onTransitionConsumed( transition: IBinder, aborted: Boolean, - finishTransaction: SurfaceControl.Transaction? + finishTransaction: SurfaceControl.Transaction?, ) { val state = transitionState ?: return if (!aborted) { @@ -640,15 +646,13 @@ sealed class DragToDesktopTransitionHandler( if (state.startTransitionToken == transition) { ProtoLog.v( ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, - "DragToDesktop: onTransitionConsumed() start transition aborted" + "DragToDesktop: onTransitionConsumed() start transition aborted", ) state.startAborted = true // The start-transition (DRAG_HOLD) is aborted, cancel its jank interaction. interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD) } else if (state.cancelTransitionToken == transition) { - state.draggedTaskChange?.leash?.let { - state.startTransitionFinishTransaction?.show(it) - } + state.draggedTaskChange?.leash?.let { state.startTransitionFinishTransaction?.show(it) } state.startTransitionFinishCb?.onTransitionFinished(null /* wct */) clearState() } else { @@ -724,7 +728,7 @@ sealed class DragToDesktopTransitionHandler( private fun restoreWindowOrder( wct: WindowContainerTransaction, - state: TransitionState = requireTransitionState() + state: TransitionState = requireTransitionState(), ) { when (state) { is TransitionState.FromFullscreen -> { @@ -831,7 +835,7 @@ sealed class DragToDesktopTransitionHandler( override var surfaceLayers: DragToDesktopLayers? = null, override var cancelState: CancelState = CancelState.NO_CANCEL, override var startAborted: Boolean = false, - var otherRootChanges: MutableList<Change> = mutableListOf() + var otherRootChanges: MutableList<Change> = mutableListOf(), ) : TransitionState() data class FromSplit( @@ -848,7 +852,7 @@ sealed class DragToDesktopTransitionHandler( override var cancelState: CancelState = CancelState.NO_CANCEL, override var startAborted: Boolean = false, var splitRootChange: Change? = null, - var otherSplitTask: Int + var otherSplitTask: Int, ) : TransitionState() } @@ -861,7 +865,7 @@ sealed class DragToDesktopTransitionHandler( /** A cancel event where the task will request to enter split on the left side. */ CANCEL_SPLIT_LEFT, /** A cancel event where the task will request to enter split on the right side. */ - CANCEL_SPLIT_RIGHT + CANCEL_SPLIT_RIGHT, } companion object { @@ -888,7 +892,7 @@ constructor( transitions, taskDisplayAreaOrganizer, interactionJankMonitor, - transactionSupplier + transactionSupplier, ) { /** @@ -903,7 +907,7 @@ constructor( topAppLayer = info.changes.size, topHomeLayer = info.changes.size * 2, topWallpaperLayer = info.changes.size * 3, - dragLayer = info.changes.size * 3 + dragLayer = info.changes.size * 3, ) } @@ -924,7 +928,7 @@ constructor( transitions, taskDisplayAreaOrganizer, interactionJankMonitor, - transactionSupplier + transactionSupplier, ) { private val positionSpringConfig = @@ -945,13 +949,13 @@ constructor( topAppLayer = -1, topHomeLayer = info.changes.size - 1, topWallpaperLayer = info.changes.size * 2 - 1, - dragLayer = info.changes.size * 2 + dragLayer = info.changes.size * 2, ) override fun setupEndDragToDesktop( info: TransitionInfo, startTransaction: SurfaceControl.Transaction, - finishTransaction: SurfaceControl.Transaction + finishTransaction: SurfaceControl.Transaction, ) { super.setupEndDragToDesktop(info, startTransaction, finishTransaction) @@ -974,7 +978,7 @@ constructor( override fun animateEndDragToDesktop( startTransaction: SurfaceControl.Transaction, - startTransitionFinishCb: Transitions.TransitionFinishCallback + startTransitionFinishCb: Transitions.TransitionFinishCallback, ) { val state = requireTransitionState() val draggedTaskChange = @@ -1002,7 +1006,7 @@ constructor( onTaskResizeAnimationListener.onAnimationStart( state.draggedTaskId, startTransaction, - startBoundsWithOffset + startBoundsWithOffset, ) val tx: SurfaceControl.Transaction = transactionSupplier.get() @@ -1011,13 +1015,13 @@ constructor( FloatProperties.RECT_X, endBounds.left.toFloat(), currentVelocity.x, - positionSpringConfig + positionSpringConfig, ) .spring( FloatProperties.RECT_Y, endBounds.top.toFloat(), currentVelocity.y, - positionSpringConfig + positionSpringConfig, ) .spring(FloatProperties.RECT_WIDTH, endBounds.width().toFloat(), sizeSpringConfig) .spring(FloatProperties.RECT_HEIGHT, endBounds.height().toFloat(), sizeSpringConfig) @@ -1050,7 +1054,7 @@ constructor( setPosition( draggedTaskLeash, animBounds.left.toFloat(), - animBounds.top.toFloat() + animBounds.top.toFloat(), ) // Update freeform tasks freeformTaskChanges.forEach { @@ -1069,7 +1073,7 @@ constructor( } .withEndActions({ onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId) - startTransitionFinishCb.onTransitionFinished(/* wct = */ null) + startTransitionFinishCb.onTransitionFinished(/* wct= */ null) clearState() interactionJankMonitor.end(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE) }) @@ -1094,7 +1098,7 @@ constructor( propertyValue( "position_damping_ratio", scale = 100f, - default = SpringForce.DAMPING_RATIO_LOW_BOUNCY + default = SpringForce.DAMPING_RATIO_LOW_BOUNCY, ) /** The spring force stiffness used to resize the window into the final bounds. */ @@ -1106,7 +1110,7 @@ constructor( propertyValue( "size_damping_ratio", scale = 100f, - default = SpringForce.DAMPING_RATIO_NO_BOUNCY + default = SpringForce.DAMPING_RATIO_NO_BOUNCY, ) /** Drag to desktop transition system properties group. */ @@ -1123,7 +1127,7 @@ constructor( fun propertyValue(name: String, scale: Float = 1f, default: Float = 0f): Float = SystemProperties.getInt( /* key= */ "$SYSTEM_PROPERTIES_GROUP.$name", - /* def= */ (default * scale).toInt() + /* def= */ (default * scale).toInt(), ) / scale } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt index 4e08d106052a..3edeecbdca5d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt @@ -30,13 +30,14 @@ import java.util.function.Supplier /** Animates the task surface moving from its current drag position to its pre-drag position. */ class ReturnToDragStartAnimator( private val transactionSupplier: Supplier<SurfaceControl.Transaction>, - private val interactionJankMonitor: InteractionJankMonitor + private val interactionJankMonitor: InteractionJankMonitor, ) { private var boundsAnimator: Animator? = null private lateinit var taskRepositionAnimationListener: OnTaskRepositionAnimationListener - constructor(interactionJankMonitor: InteractionJankMonitor) : - this(Supplier { SurfaceControl.Transaction() }, interactionJankMonitor) + constructor( + interactionJankMonitor: InteractionJankMonitor + ) : this(Supplier { SurfaceControl.Transaction() }, interactionJankMonitor) /** Sets a listener for the start and end of the reposition animation. */ fun setTaskRepositionAnimationListener(listener: OnTaskRepositionAnimationListener) { @@ -65,7 +66,7 @@ class ReturnToDragStartAnimator( .setPosition( taskSurface, startBounds.left.toFloat(), - startBounds.top.toFloat() + startBounds.top.toFloat(), ) .show(taskSurface) .apply() @@ -77,7 +78,7 @@ class ReturnToDragStartAnimator( .setPosition( taskSurface, endBounds.left.toFloat(), - endBounds.top.toFloat() + endBounds.top.toFloat(), ) .show(taskSurface) .apply() @@ -85,7 +86,7 @@ class ReturnToDragStartAnimator( boundsAnimator = null doOnEnd?.invoke() interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE) - } + }, ) addUpdateListener { anim -> val rect = anim.animatedValue as Rect @@ -100,4 +101,4 @@ class ReturnToDragStartAnimator( companion object { const val RETURN_TO_DRAG_START_ANIMATION_MS = 300L } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt index 6df3302f47a5..e683f62644c4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt @@ -39,7 +39,7 @@ import java.util.function.Supplier class ToggleResizeDesktopTaskTransitionHandler( private val transitions: Transitions, private val transactionSupplier: Supplier<SurfaceControl.Transaction>, - private val interactionJankMonitor: InteractionJankMonitor + private val interactionJankMonitor: InteractionJankMonitor, ) : Transitions.TransitionHandler { private val rectEvaluator = RectEvaluator(Rect()) @@ -50,16 +50,16 @@ class ToggleResizeDesktopTaskTransitionHandler( constructor( transitions: Transitions, - interactionJankMonitor: InteractionJankMonitor + interactionJankMonitor: InteractionJankMonitor, ) : this(transitions, Supplier { SurfaceControl.Transaction() }, interactionJankMonitor) /** * Starts a quick resize transition. * - * @param wct WindowContainerTransaction that will update core about the task changes applied - * @param taskLeashBounds current bounds of the task leash (Note: not guaranteed to be the - * bounds of the actual task). This is provided so that the animation - * resizing can begin where the task leash currently is for smoother UX. + * @param wct WindowContainerTransaction that will update core about the task changes applied + * @param taskLeashBounds current bounds of the task leash (Note: not guaranteed to be the + * bounds of the actual task). This is provided so that the animation resizing can begin where + * the task leash currently is for smoother UX. */ fun startTransition(wct: WindowContainerTransaction, taskLeashBounds: Rect? = null) { transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this) @@ -75,7 +75,7 @@ class ToggleResizeDesktopTaskTransitionHandler( info: TransitionInfo, startTransaction: SurfaceControl.Transaction, finishTransaction: SurfaceControl.Transaction, - finishCallback: Transitions.TransitionFinishCallback + finishCallback: Transitions.TransitionFinishCallback, ): Boolean { val change = findRelevantChange(info) val leash = change.leash @@ -95,7 +95,7 @@ class ToggleResizeDesktopTaskTransitionHandler( .setPosition( leash, startBounds.left.toFloat(), - startBounds.top.toFloat() + startBounds.top.toFloat(), ) .setWindowCrop(leash, startBounds.width(), startBounds.height()) .show(leash) @@ -110,7 +110,7 @@ class ToggleResizeDesktopTaskTransitionHandler( .setPosition( leash, endBounds.left.toFloat(), - endBounds.top.toFloat() + endBounds.top.toFloat(), ) .setWindowCrop(leash, endBounds.width(), endBounds.height()) .show(leash) @@ -121,7 +121,7 @@ class ToggleResizeDesktopTaskTransitionHandler( interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW) interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW) interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE) - } + }, ) addUpdateListener { anim -> val rect = anim.animatedValue as Rect @@ -138,7 +138,7 @@ class ToggleResizeDesktopTaskTransitionHandler( override fun handleRequest( transition: IBinder, - request: TransitionRequestInfo + request: TransitionRequestInfo, ): WindowContainerTransaction? { return null } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt index 8bfcca093855..131b7485bfa5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt @@ -27,23 +27,22 @@ import kotlinx.coroutines.flow.StateFlow /** Repository to observe caption state. */ class WindowDecorCaptionHandleRepository { - private val _captionStateFlow = MutableStateFlow<CaptionState>(CaptionState.NoCaption) - /** Observer for app handle state changes. */ - val captionStateFlow: StateFlow<CaptionState> = _captionStateFlow - private val _appToWebUsageFlow = MutableSharedFlow<Unit>() - /** Observer for App-to-Web usage. */ - val appToWebUsageFlow = _appToWebUsageFlow + private val _captionStateFlow = MutableStateFlow<CaptionState>(CaptionState.NoCaption) + /** Observer for app handle state changes. */ + val captionStateFlow: StateFlow<CaptionState> = _captionStateFlow + private val _appToWebUsageFlow = MutableSharedFlow<Unit>() + /** Observer for App-to-Web usage. */ + val appToWebUsageFlow = _appToWebUsageFlow + /** Notifies [captionStateFlow] if there is a change to caption state. */ + fun notifyCaptionChanged(captionState: CaptionState) { + _captionStateFlow.value = captionState + } - /** Notifies [captionStateFlow] if there is a change to caption state. */ - fun notifyCaptionChanged(captionState: CaptionState) { - _captionStateFlow.value = captionState - } - - /** Notifies [appToWebUsageFlow] if App-to-Web feature is used. */ - fun onAppToWebUsage() { - _appToWebUsageFlow.tryEmit(Unit) - } + /** Notifies [appToWebUsageFlow] if App-to-Web feature is used. */ + fun onAppToWebUsage() { + _appToWebUsageFlow.tryEmit(Unit) + } } /** @@ -54,20 +53,20 @@ class WindowDecorCaptionHandleRepository { * * [AppHeader]: Indicating that there is at least one visible app chip on the screen. * * [NoCaption]: Signifying that no caption handle is currently visible on the device. */ -sealed class CaptionState{ - data class AppHandle( - val runningTaskInfo: RunningTaskInfo, - val isHandleMenuExpanded: Boolean, - val globalAppHandleBounds: Rect, - val isCapturedLinkAvailable: Boolean - ) : CaptionState() +sealed class CaptionState { + data class AppHandle( + val runningTaskInfo: RunningTaskInfo, + val isHandleMenuExpanded: Boolean, + val globalAppHandleBounds: Rect, + val isCapturedLinkAvailable: Boolean, + ) : CaptionState() - data class AppHeader( - val runningTaskInfo: RunningTaskInfo, - val isHeaderMenuExpanded: Boolean, - val globalAppChipBounds: Rect, - val isCapturedLinkAvailable: Boolean - ) : CaptionState() + data class AppHeader( + val runningTaskInfo: RunningTaskInfo, + val isHeaderMenuExpanded: Boolean, + val globalAppChipBounds: Rect, + val isCapturedLinkAvailable: Boolean, + ) : CaptionState() - data object NoCaption : CaptionState() + data object NoCaption : CaptionState() } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt new file mode 100644 index 000000000000..f6ebf7221e82 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt @@ -0,0 +1,153 @@ +/* + * 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.desktopmode.common + +import android.graphics.Rect +import com.android.internal.jank.Cuj +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger +import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum +import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction.AmbiguousSource +import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction.Source + +/** Represents a user interaction to toggle a desktop task's size from to maximize or vice versa. */ +data class ToggleTaskSizeInteraction +@JvmOverloads +constructor( + val direction: Direction, + val source: Source, + val inputMethod: InputMethod, + val animationStartBounds: Rect? = null, +) { + constructor( + isMaximized: Boolean, + source: Source, + inputMethod: InputMethod, + ) : this( + direction = if (isMaximized) Direction.RESTORE else Direction.MAXIMIZE, + source = source, + inputMethod = inputMethod, + ) + + val jankTag: String? = + when (source) { + Source.HEADER_BUTTON_TO_MAXIMIZE -> "caption_bar_button" + Source.HEADER_BUTTON_TO_RESTORE -> "caption_bar_button" + Source.KEYBOARD_SHORTCUT -> null + Source.HEADER_DRAG_TO_TOP -> null + Source.MAXIMIZE_MENU_TO_MAXIMIZE -> "maximize_menu" + Source.MAXIMIZE_MENU_TO_RESTORE -> "maximize_menu" + Source.DOUBLE_TAP_TO_MAXIMIZE -> "double_tap" + Source.DOUBLE_TAP_TO_RESTORE -> "double_tap" + } + val uiEvent: DesktopUiEventEnum? = + when (source) { + Source.HEADER_BUTTON_TO_MAXIMIZE -> + DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_TAP + Source.HEADER_BUTTON_TO_RESTORE -> DesktopUiEventEnum.DESKTOP_WINDOW_RESTORE_BUTTON_TAP + Source.KEYBOARD_SHORTCUT -> null + Source.HEADER_DRAG_TO_TOP -> null + Source.MAXIMIZE_MENU_TO_MAXIMIZE -> { + DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_MAXIMIZE + } + Source.MAXIMIZE_MENU_TO_RESTORE -> { + DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_RESTORE + } + Source.DOUBLE_TAP_TO_MAXIMIZE -> { + DesktopUiEventEnum.DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_MAXIMIZE + } + Source.DOUBLE_TAP_TO_RESTORE -> { + DesktopUiEventEnum.DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_RESTORE + } + } + val resizeTrigger = + when (source) { + Source.HEADER_BUTTON_TO_MAXIMIZE -> ResizeTrigger.MAXIMIZE_BUTTON + Source.HEADER_BUTTON_TO_RESTORE -> ResizeTrigger.MAXIMIZE_BUTTON + Source.KEYBOARD_SHORTCUT -> ResizeTrigger.UNKNOWN_RESIZE_TRIGGER + Source.HEADER_DRAG_TO_TOP -> ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER + Source.MAXIMIZE_MENU_TO_MAXIMIZE -> ResizeTrigger.MAXIMIZE_MENU + Source.MAXIMIZE_MENU_TO_RESTORE -> ResizeTrigger.MAXIMIZE_MENU + Source.DOUBLE_TAP_TO_MAXIMIZE -> ResizeTrigger.DOUBLE_TAP_APP_HEADER + Source.DOUBLE_TAP_TO_RESTORE -> ResizeTrigger.DOUBLE_TAP_APP_HEADER + } + val cujTracing: Int? = + when (source) { + Source.HEADER_BUTTON_TO_MAXIMIZE -> Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW + Source.HEADER_BUTTON_TO_RESTORE -> Cuj.CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW + Source.KEYBOARD_SHORTCUT -> null + Source.HEADER_DRAG_TO_TOP -> null + Source.MAXIMIZE_MENU_TO_MAXIMIZE -> null + Source.MAXIMIZE_MENU_TO_RESTORE -> null + Source.DOUBLE_TAP_TO_MAXIMIZE -> null + Source.DOUBLE_TAP_TO_RESTORE -> null + } + + /** The direction to which the task is being resized. */ + enum class Direction { + MAXIMIZE, + RESTORE, + } + + /** The user interaction source. */ + enum class Source { + HEADER_BUTTON_TO_MAXIMIZE, + HEADER_BUTTON_TO_RESTORE, + KEYBOARD_SHORTCUT, + HEADER_DRAG_TO_TOP, + MAXIMIZE_MENU_TO_MAXIMIZE, + MAXIMIZE_MENU_TO_RESTORE, + DOUBLE_TAP_TO_MAXIMIZE, + DOUBLE_TAP_TO_RESTORE, + } + + /** + * Temporary sources for interactions that should be broken into more specific sources, for + * example, the header button click should use [Source.HEADER_BUTTON_TO_MAXIMIZE] and + * [Source.HEADER_BUTTON_TO_RESTORE]. + * + * TODO: b/341320112 - break these out into different [Source]s. + */ + enum class AmbiguousSource { + HEADER_BUTTON, + MAXIMIZE_MENU, + DOUBLE_TAP, + } +} + +/** Returns the non-ambiguous [Source] based on the maximized state of the task. */ +fun AmbiguousSource.toSource(isMaximized: Boolean): Source { + return when (this) { + AmbiguousSource.HEADER_BUTTON -> + if (isMaximized) { + Source.HEADER_BUTTON_TO_RESTORE + } else { + Source.HEADER_BUTTON_TO_MAXIMIZE + } + AmbiguousSource.MAXIMIZE_MENU -> + if (isMaximized) { + Source.MAXIMIZE_MENU_TO_RESTORE + } else { + Source.MAXIMIZE_MENU_TO_MAXIMIZE + } + AmbiguousSource.DOUBLE_TAP -> + if (isMaximized) { + Source.DOUBLE_TAP_TO_RESTORE + } else { + Source.DOUBLE_TAP_TO_MAXIMIZE + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt index 826de08557bd..a428ce18a49e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandler.kt @@ -29,7 +29,7 @@ import com.android.app.animation.Interpolators import com.android.internal.protolog.ProtoLog import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing -import com.android.wm.shell.desktopmode.DesktopRepository +import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.TransitionUtil.isClosingMode import com.android.wm.shell.shared.TransitionUtil.isClosingType @@ -46,7 +46,7 @@ class SystemModalsTransitionHandler( private val animExecutor: ShellExecutor, private val shellInit: ShellInit, private val transitions: Transitions, - private val desktopRepository: DesktopRepository, + private val desktopUserRepositories: DesktopUserRepositories, ) : TransitionHandler { private val showingSystemModalsIds = mutableSetOf<Int>() @@ -156,7 +156,7 @@ class SystemModalsTransitionHandler( } private fun isDesktopModeShowing(displayId: Int): Boolean = - desktopRepository.getVisibleTaskCount(displayId) > 0 + desktopUserRepositories.current.getVisibleTaskCount(displayId) > 0 override fun handleRequest( transition: IBinder, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt index bfe1b12c9605..ac0a6275cfbd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt @@ -116,7 +116,12 @@ class AppToWebEducationController( } private inline fun runIfEducationFeatureEnabled(block: () -> Unit) { - if (canEnterDesktopMode(context) && Flags.enableDesktopWindowingAppToWebEducation()) block() + if ( + canEnterDesktopMode(context) && + Flags.enableDesktopWindowingAppToWebEducationIntegration() + ) { + block() + } } private fun showEducation(captionState: CaptionState, colorScheme: EducationColorScheme) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/minimize/DesktopWindowLimitRemoteHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/minimize/DesktopWindowLimitRemoteHandler.kt index 7554cbb96606..4298bd25dc95 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/minimize/DesktopWindowLimitRemoteHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/minimize/DesktopWindowLimitRemoteHandler.kt @@ -43,7 +43,7 @@ class DesktopWindowLimitRemoteHandler( private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, remoteTransition: RemoteTransition, private val taskIdToMinimize: Int, - ) : TransitionHandler { +) : TransitionHandler { private val oneShotRemoteHandler = OneShotRemoteHandler(mainExecutor, remoteTransition) private var transition: IBinder? = null @@ -56,7 +56,7 @@ class DesktopWindowLimitRemoteHandler( override fun handleRequest( transition: IBinder, - request: TransitionRequestInfo + request: TransitionRequestInfo, ): WindowContainerTransaction? { this.transition = transition return oneShotRemoteHandler.handleRequest(transition, request) @@ -67,7 +67,7 @@ class DesktopWindowLimitRemoteHandler( info: TransitionInfo, startTransaction: SurfaceControl.Transaction, finishTransaction: SurfaceControl.Transaction, - finishCallback: Transitions.TransitionFinishCallback + finishCallback: Transitions.TransitionFinishCallback, ): Boolean { if (transition != this.transition) return false val minimizeChange = findMinimizeChange(info, taskIdToMinimize) ?: return false @@ -76,7 +76,12 @@ class DesktopWindowLimitRemoteHandler( // have access to RootTaskDisplayAreaOrganizer. applyMinimizeChangeReparenting(info, minimizeChange, startTransaction) return oneShotRemoteHandler.startAnimation( - transition, info, startTransaction, finishTransaction, finishCallback) + transition, + info, + startTransaction, + finishTransaction, + finishCallback, + ) } private fun applyMinimizeChangeReparenting( @@ -87,14 +92,15 @@ class DesktopWindowLimitRemoteHandler( val taskInfo = minimizeChange.taskInfo ?: return if (taskInfo.isFreeform && TransitionUtil.isOpeningMode(info.type)) { rootTaskDisplayAreaOrganizer.reparentToDisplayArea( - taskInfo.displayId, minimizeChange.leash, startTransaction) + taskInfo.displayId, + minimizeChange.leash, + startTransaction, + ) } } - private fun findMinimizeChange( - info: TransitionInfo, - taskIdToMinimize: Int, - ): Change? = + private fun findMinimizeChange(info: TransitionInfo, taskIdToMinimize: Int): Change? = info.changes.find { change -> - change.taskInfo?.taskId == taskIdToMinimize && change.mode == TRANSIT_TO_BACK } + change.taskInfo?.taskId == taskIdToMinimize && change.mode == TRANSIT_TO_BACK + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt index 9e646f430c98..a6998e1179fa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt @@ -40,9 +40,7 @@ import kotlinx.coroutines.flow.first * * The main constructor is public only for testing purposes. */ -class DesktopPersistentRepository( - private val dataStore: DataStore<DesktopPersistentRepositories>, -) { +class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersistentRepositories>) { constructor( context: Context, @ShellBackgroundThread bgCoroutineScope: CoroutineScope, @@ -51,7 +49,7 @@ class DesktopPersistentRepository( serializer = DesktopPersistentRepositoriesSerializer, produceFile = { context.dataStoreFile(DESKTOP_REPOSITORIES_DATASTORE_FILE) }, scope = bgCoroutineScope, - ), + ) ) /** Provides `dataStore.data` flow and handles exceptions thrown during collection */ @@ -63,7 +61,8 @@ class DesktopPersistentRepository( TAG, "Error in reading desktop mode related data from datastore, data is " + "stored in a file named $DESKTOP_REPOSITORIES_DATASTORE_FILE", - exception) + exception, + ) } else { throw exception } @@ -73,13 +72,17 @@ class DesktopPersistentRepository( * Reads and returns the [DesktopRepositoryState] proto object from the DataStore for a user. If * the DataStore is empty or there's an error reading, it returns the default value of Proto. */ - private suspend fun getDesktopRepositoryState( - userId: Int = DEFAULT_USER_ID - ): DesktopRepositoryState? = + suspend fun getDesktopRepositoryState(userId: Int): DesktopRepositoryState? = try { - dataStoreFlow - .first() - .desktopRepoByUserMap[userId] + dataStoreFlow.first().desktopRepoByUserMap[userId] + } catch (e: Exception) { + Log.e(TAG, "Unable to read from datastore", e) + null + } + + suspend fun getUserDesktopRepositoryMap(): Map<Int, DesktopRepositoryState>? = + try { + dataStoreFlow.first().desktopRepoByUserMap } catch (e: Exception) { Log.e(TAG, "Unable to read from datastore", e) null @@ -89,10 +92,7 @@ class DesktopPersistentRepository( * Reads the [Desktop] of a desktop filtering by the [userId] and [desktopId]. Executes the * [callback] using the [mainCoroutineScope]. */ - suspend fun readDesktop( - userId: Int = DEFAULT_USER_ID, - desktopId: Int = DEFAULT_DESKTOP_ID, - ): Desktop? = + suspend fun readDesktop(userId: Int, desktopId: Int = DEFAULT_DESKTOP_ID): Desktop? = try { val repository = getDesktopRepositoryState(userId) repository?.getDesktopOrThrow(desktopId) @@ -103,7 +103,7 @@ class DesktopPersistentRepository( /** Adds or updates a desktop stored in the datastore */ suspend fun addOrUpdateDesktop( - userId: Int = DEFAULT_USER_ID, + userId: Int, desktopId: Int = 0, visibleTasks: ArraySet<Int> = ArraySet(), minimizedTasks: ArraySet<Int> = ArraySet(), @@ -111,36 +111,33 @@ class DesktopPersistentRepository( ) { // TODO: b/367609270 - Improve the API to support multi-user try { - dataStore.updateData { desktopPersistentRepositories: DesktopPersistentRepositories -> + dataStore.updateData { persistentRepositories: DesktopPersistentRepositories -> val currentRepository = - desktopPersistentRepositories.getDesktopRepoByUserOrDefault( - userId, DesktopRepositoryState.getDefaultInstance()) + persistentRepositories.getDesktopRepoByUserOrDefault( + userId, + DesktopRepositoryState.getDefaultInstance(), + ) val desktop = getDesktop(currentRepository, desktopId) .toBuilder() - .updateTaskStates( - visibleTasks, - minimizedTasks, - freeformTasksInZOrder, - ) + .updateTaskStates(visibleTasks, minimizedTasks, freeformTasksInZOrder) .updateZOrder(freeformTasksInZOrder) - desktopPersistentRepositories + persistentRepositories .toBuilder() .putDesktopRepoByUser( userId, - currentRepository - .toBuilder() - .putDesktop(desktopId, desktop.build()) - .build()) + currentRepository.toBuilder().putDesktop(desktopId, desktop.build()).build(), + ) .build() } - } catch (exception: IOException) { + } catch (exception: Exception) { Log.e( TAG, "Error in updating desktop mode related data, data is " + "stored in a file named $DESKTOP_REPOSITORIES_DATASTORE_FILE", - exception) + exception, + ) } } @@ -148,13 +145,13 @@ class DesktopPersistentRepository( // If there are no desktops set up, create one on the default display currentRepository.getDesktopOrDefault( desktopId, - Desktop.newBuilder().setDesktopId(desktopId).setDisplayId(DEFAULT_DISPLAY).build()) + Desktop.newBuilder().setDesktopId(desktopId).setDisplayId(DEFAULT_DISPLAY).build(), + ) companion object { private const val TAG = "DesktopPersistenceRepo" private const val DESKTOP_REPOSITORIES_DATASTORE_FILE = "desktop_persistent_repositories.pb" - private const val DEFAULT_USER_ID = 1000 private const val DEFAULT_DESKTOP_ID = 0 object DesktopPersistentRepositoriesSerializer : Serializer<DesktopPersistentRepositories> { @@ -185,19 +182,22 @@ class DesktopPersistentRepository( // visible, they will be marked as not visible afterwards. This ensures that they are // still persisted as visible. // TODO - b/350476823: Remove this logic once repository holds expanded tasks - if (freeformTasksInZOrder.size > visibleTasks.size + minimizedTasks.size && - visibleTasks.isEmpty() + if ( + freeformTasksInZOrder.size > visibleTasks.size + minimizedTasks.size && + visibleTasks.isEmpty() ) { visibleTasks.addAll(freeformTasksInZOrder.filterNot { it in minimizedTasks }) } putAllTasksByTaskId( visibleTasks.associateWith { createDesktopTask(it, state = DesktopTaskState.VISIBLE) - }) + } + ) putAllTasksByTaskId( minimizedTasks.associateWith { createDesktopTask(it, state = DesktopTaskState.MINIMIZED) - }) + } + ) return this } @@ -211,7 +211,7 @@ class DesktopPersistentRepository( private fun createDesktopTask( taskId: Int, - state: DesktopTaskState = DesktopTaskState.VISIBLE + state: DesktopTaskState = DesktopTaskState.VISIBLE, ): DesktopTask = DesktopTask.newBuilder().setTaskId(taskId).setDesktopTaskState(state).build() } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt index 771c3d1cb9a1..a26ebbf4c99a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt @@ -16,9 +16,9 @@ package com.android.wm.shell.desktopmode.persistence -import com.android.wm.shell.desktopmode.DesktopRepository +import com.android.wm.shell.desktopmode.DesktopUserRepositories -/** Interface for initializing the [DesktopRepository]. */ +/** Interface for initializing the [DesktopUserRepositories]. */ fun interface DesktopRepositoryInitializer { - fun initialize(repository: DesktopRepository) + fun initialize(userRepositories: DesktopUserRepositories) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt index d8156561ff19..58a49a035bb6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt @@ -19,6 +19,7 @@ package com.android.wm.shell.desktopmode.persistence import android.content.Context import android.window.DesktopModeFlags import com.android.wm.shell.desktopmode.DesktopRepository +import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import kotlinx.coroutines.CoroutineScope @@ -35,32 +36,50 @@ class DesktopRepositoryInitializerImpl( private val persistentRepository: DesktopPersistentRepository, @ShellMainThread private val mainCoroutineScope: CoroutineScope, ) : DesktopRepositoryInitializer { - override fun initialize(repository: DesktopRepository) { + override fun initialize(userRepositories: DesktopUserRepositories) { if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) return // TODO: b/365962554 - Handle the case that user moves to desktop before it's initialized mainCoroutineScope.launch { - val desktop = persistentRepository.readDesktop() ?: return@launch - - val maxTasks = - DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 } - ?: desktop.zOrderedTasksCount - - var visibleTasksCount = 0 - desktop.zOrderedTasksList - // Reverse it so we initialize the repo from bottom to top. - .reversed() - .mapNotNull { taskId -> desktop.tasksByTaskIdMap[taskId] } - .forEach { task -> - if (task.desktopTaskState == DesktopTaskState.VISIBLE - && visibleTasksCount < maxTasks - ) { - visibleTasksCount++ - repository.addTask(desktop.displayId, task.taskId, isVisible = false) - } else { - repository.addTask(desktop.displayId, task.taskId, isVisible = false) - repository.minimizeTask(desktop.displayId, task.taskId) - } + val desktopUserPersistentRepositoryMap = + persistentRepository.getUserDesktopRepositoryMap() ?: return@launch + for (userId in desktopUserPersistentRepositoryMap.keys) { + val repository = userRepositories.getProfile(userId) + val desktopRepositoryState = + persistentRepository.getDesktopRepositoryState(userId) ?: continue + val desktopByDesktopIdMap = desktopRepositoryState.desktopMap + for (desktopId in desktopByDesktopIdMap.keys) { + val persistentDesktop = + persistentRepository.readDesktop(userId, desktopId) ?: continue + val maxTasks = + DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 } + ?: persistentDesktop.zOrderedTasksCount + var visibleTasksCount = 0 + persistentDesktop.zOrderedTasksList + // Reverse it so we initialize the repo from bottom to top. + .reversed() + .mapNotNull { taskId -> persistentDesktop.tasksByTaskIdMap[taskId] } + .forEach { task -> + if ( + task.desktopTaskState == DesktopTaskState.VISIBLE && + visibleTasksCount < maxTasks + ) { + visibleTasksCount++ + repository.addTask( + persistentDesktop.displayId, + task.taskId, + isVisible = false, + ) + } else { + repository.addTask( + persistentDesktop.displayId, + task.taskId, + isVisible = false, + ) + repository.minimizeTask(persistentDesktop.displayId, task.taskId) + } + } } + } } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md index 9d015357b60b..837a6dd32ff2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md @@ -36,7 +36,8 @@ the product. thread) - This is always another thread even if config_enableShellMainThread is not set true - **Note**: - - This thread runs with `THREAD_PRIORITY_BACKGROUND` priority + - This thread runs with `THREAD_PRIORITY_BACKGROUND` priority but can be requested to be boosted + to `THREAD_PRIORITY_FOREGROUND` - `ShellAnimationThread` (currently only used for Transitions and Splitscreen, but potentially all animations could be offloaded here) - `ShellSplashScreenThread` (only for use with splashscreens) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java index 22e8dc186e9b..491b577386d7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java @@ -31,8 +31,6 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMA import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; -import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP; - import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.PendingIntent; @@ -167,7 +165,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll mMainExecutor.executeDelayed(() -> { mDisplayController.addDisplayWindowListener(this); }, 0); - mShellController.addExternalInterface(KEY_EXTRA_SHELL_DRAG_AND_DROP, + mShellController.addExternalInterface(IDragAndDrop.DESCRIPTOR, this::createExternalInterface, this); mShellTaskOrganizer.addTaskVanishedListener(this); mShellCommandHandler.addDumpCallback(this::dump, this); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index cd20d97c7964..4b59efbcecf2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -31,6 +31,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.LaunchAdjacentController; 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.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.sysui.ShellInit; @@ -49,7 +50,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, private final Context mContext; private final ShellTaskOrganizer mShellTaskOrganizer; - private final Optional<DesktopRepository> mDesktopRepository; + private final Optional<DesktopUserRepositories> mDesktopUserRepositories; private final Optional<DesktopTasksController> mDesktopTasksController; private final WindowDecorViewModel mWindowDecorationViewModel; private final LaunchAdjacentController mLaunchAdjacentController; @@ -61,7 +62,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, Context context, ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, - Optional<DesktopRepository> desktopRepository, + Optional<DesktopUserRepositories> desktopUserRepositories, Optional<DesktopTasksController> desktopTasksController, LaunchAdjacentController launchAdjacentController, WindowDecorViewModel windowDecorationViewModel, @@ -69,7 +70,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, mContext = context; mShellTaskOrganizer = shellTaskOrganizer; mWindowDecorationViewModel = windowDecorationViewModel; - mDesktopRepository = desktopRepository; + mDesktopUserRepositories = desktopUserRepositories; mDesktopTasksController = desktopTasksController; mLaunchAdjacentController = launchAdjacentController; mTaskChangeListener = taskChangeListener; @@ -99,8 +100,9 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, if (!DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS.isTrue() && DesktopModeStatus.canEnterDesktopMode(mContext)) { - mDesktopRepository.ifPresent(repository -> { - repository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible); + mDesktopUserRepositories.ifPresent(userRepositories -> { + DesktopRepository currentRepo = userRepositories.getProfile(taskInfo.userId); + currentRepo.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible); }); } updateLaunchAdjacentController(); @@ -113,21 +115,20 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, mTasks.remove(taskInfo.taskId); if (!DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS.isTrue() && - DesktopModeStatus.canEnterDesktopMode(mContext)) { - mDesktopRepository.ifPresent(repository -> { - // TODO: b/370038902 - Handle Activity#finishAndRemoveTask. - if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue() - || repository.isClosingTask(taskInfo.taskId)) { - // A task that's vanishing should be removed: - // - If it's closed by the X button which means it's marked as a closing task. - repository.removeClosingTask(taskInfo.taskId); - repository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId); - } else { - repository.updateTask(taskInfo.displayId, taskInfo.taskId, /* isVisible= */ - false); - repository.minimizeTask(taskInfo.displayId, taskInfo.taskId); - } - }); + DesktopModeStatus.canEnterDesktopMode(mContext) + && mDesktopUserRepositories.isPresent()) { + DesktopRepository repository = + mDesktopUserRepositories.get().getProfile(taskInfo.userId); + // TODO: b/370038902 - Handle Activity#finishAndRemoveTask. + if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue() + || !repository.isMinimizedTask(taskInfo.taskId)) { + // A task that's vanishing should be removed: + // - If it's not yet minimized. It can be minimized when a back navigation is + // triggered on a task and the task is closing. It will be marked as minimized in + // [DesktopTasksTransitionObserver] before it gets here. + repository.removeClosingTask(taskInfo.taskId); + repository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId); + } } mWindowDecorationViewModel.onTaskVanished(taskInfo); updateLaunchAdjacentController(); @@ -148,11 +149,11 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, // does not propagate all task info changes. mTaskChangeListener.ifPresent(listener -> listener.onNonTransitionTaskChanging(taskInfo)); - } else { - mDesktopRepository.ifPresent(repository -> { - repository.updateTask(taskInfo.displayId, taskInfo.taskId, - taskInfo.isVisible); - }); + } else if (mDesktopUserRepositories.isPresent()) { + DesktopRepository currentRepo = + mDesktopUserRepositories.get().getProfile(taskInfo.userId); + currentRepo.updateTask(taskInfo.displayId, taskInfo.taskId, + taskInfo.isVisible); } } updateLaunchAdjacentController(); @@ -176,10 +177,11 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Focus Changed: #%d focused=%b", taskInfo.taskId, taskInfo.isFocused); - if (DesktopModeStatus.canEnterDesktopMode(mContext) && taskInfo.isFocused) { - mDesktopRepository.ifPresent(repository -> { - repository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible); - }); + if (DesktopModeStatus.canEnterDesktopMode(mContext) && taskInfo.isFocused + && mDesktopUserRepositories.isPresent()) { + DesktopRepository repository = + mDesktopUserRepositories.get().getProfile(taskInfo.userId); + repository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java index 2ae9828ca0db..52b6c62b0721 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java @@ -18,6 +18,7 @@ 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_PIP; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -99,6 +100,12 @@ public class FreeformTaskTransitionHandler return token; } + @Override + public IBinder startPipTransition(WindowContainerTransaction wct) { + final IBinder token = mTransitions.startTransition(TRANSIT_PIP, wct, null); + mPendingTransitionTokens.add(token); + return token; + } @Override public IBinder startRemoveTransition(WindowContainerTransaction wct) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java index 5984d486f838..a874a5be426d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java @@ -51,4 +51,13 @@ public interface FreeformTaskTransitionStarter { * @return the started transition */ IBinder startRemoveTransition(WindowContainerTransaction wct); + + /** + * Starts PiP transition + * + * @param wct the {@link WindowContainerTransaction} that launches the PiP + * + * @return the started transition + */ + IBinder startPipTransition(WindowContainerTransaction wct); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java index 4c316de98744..f8e6285b0493 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java @@ -42,7 +42,6 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.ArrayMap; import android.util.Log; -import android.view.Display; import android.view.SurfaceControl; import android.view.WindowManager; import android.window.IRemoteTransition; @@ -444,10 +443,8 @@ public class KeyguardTransitionHandler @Override public void startKeyguardTransition(boolean keyguardShowing, boolean aodShowing) { final WindowContainerTransaction wct = new WindowContainerTransaction(); - for (Display display : mDisplayController.getDisplays()) { - wct.addKeyguardState(new KeyguardState.Builder(display.getDisplayId()) - .setKeyguardShowing(keyguardShowing).setAodShowing(aodShowing).build()); - } + wct.addKeyguardState(new KeyguardState.Builder().setKeyguardShowing(keyguardShowing) + .setAodShowing(aodShowing).build()); mMainExecutor.execute(() -> { mTransitions.startTransition(keyguardShowing ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK, wct, KeyguardTransitionHandler.this); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index 15472ebc149b..862520208d23 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -23,7 +23,6 @@ import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE; import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING; import static com.android.wm.shell.onehanded.OneHandedState.STATE_EXITING; import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE; -import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED; import android.annotation.BinderThread; import android.content.ComponentName; @@ -298,7 +297,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, mShellController.addConfigurationChangeListener(this); mShellController.addKeyguardChangeListener(this); mShellController.addUserChangeListener(this); - mShellController.addExternalInterface(KEY_EXTRA_SHELL_ONE_HANDED, + mShellController.addExternalInterface(IOneHanded.DESCRIPTOR, this::createExternalInterface, this); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt index f7977f88006e..c655d86c3ece 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/performance/PerfHintController.kt @@ -45,9 +45,15 @@ class PerfHintController(private val mContext: Context, private fun onInit() { mShellCommandHandler.addDumpCallback(this::dump, this) val perfHintMgr = mContext.getSystemService(PerformanceHintManager::class.java) - val adpfSession = perfHintMgr!!.createHintSession(intArrayOf(Process.myTid()), - TimeUnit.SECONDS.toNanos(1)) - hinter.setAdpfSession(adpfSession) + if (perfHintMgr != null) { + val adpfSession = perfHintMgr.createHintSession( + intArrayOf(Process.myTid()), + TimeUnit.SECONDS.toNanos(1) + ) + if (adpfSession != null) { + hinter.setAdpfSession(adpfSession) + } + } } fun dump(pw: PrintWriter, prefix: String?) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index 5276d9d6a4df..55e90e74a404 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -324,6 +324,8 @@ public class PipAnimationController { private final @AnimationType int mAnimationType; private final Rect mDestinationBounds = new Rect(); + private final Point mLeashOffset = new Point(); + private T mBaseValue; protected T mCurrentValue; protected T mStartValue; @@ -338,13 +340,22 @@ public class PipAnimationController { // Flag to avoid double-end private boolean mHasRequestedEnd; - private PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash, - @AnimationType int animationType, - Rect destinationBounds, T baseValue, T startValue, T endValue) { + private PipTransitionAnimator(@NonNull TaskInfo taskInfo, @NonNull SurfaceControl leash, + @AnimationType int animationType, @NonNull Rect destinationBounds, + @NonNull T baseValue, @NonNull T startValue, @NonNull T endValue) { + this(taskInfo, leash, animationType, destinationBounds, new Point(), baseValue, + startValue, endValue); + } + + private PipTransitionAnimator(@NonNull TaskInfo taskInfo, @NonNull SurfaceControl leash, + @AnimationType int animationType, @NonNull Rect destinationBounds, + @NonNull Point leashOffset, @NonNull T baseValue, @NonNull T startValue, + @NonNull T endValue) { mTaskInfo = taskInfo; mLeash = leash; mAnimationType = animationType; mDestinationBounds.set(destinationBounds); + mLeashOffset.set(leashOffset); mBaseValue = baseValue; mStartValue = startValue; mEndValue = endValue; @@ -496,6 +507,15 @@ public class PipAnimationController { } } + /** + * Returns the offset of the {@link #mLeash}. + */ + @NonNull + Point getLeashOffset() { + // Use copy to prevent the leash to be modified unexpectedly. + return new Point(mLeashOffset); + } + void setCurrentValue(T value) { mCurrentValue = value; } @@ -692,8 +712,8 @@ public class PipAnimationController { final Rect zeroInsets = new Rect(0, 0, 0, 0); // construct new Rect instances in case they are recycled - return new PipTransitionAnimator<Rect>(taskInfo, leash, ANIM_TYPE_BOUNDS, - endBounds, new Rect(baseBounds), new Rect(startBounds), new Rect(endBounds)) { + return new PipTransitionAnimator<Rect>(taskInfo, leash, ANIM_TYPE_BOUNDS, endBounds, + leashOffset, new Rect(baseBounds), new Rect(startBounds), new Rect(endBounds)) { private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect()); private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect()); @@ -720,6 +740,7 @@ public class PipAnimationController { // Use the bounds relative to the task leash in case the leash does not // start from (0, 0). final Rect relativeEndBounds = new Rect(end); + final Point leashOffset = getLeashOffset(); relativeEndBounds.offset(-leashOffset.x, -leashOffset.y); getSurfaceTransactionHelper() .crop(tx, leash, relativeEndBounds) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index fb4afe41e193..af187682d379 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -94,6 +94,7 @@ import com.android.wm.shell.common.pip.PipPerfHintController; import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.desktopmode.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.pip.phone.PipMotionHelper; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.animation.Interpolators; @@ -152,7 +153,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; private final Optional<SplitScreenController> mSplitScreenOptional; @Nullable private final PipPerfHintController mPipPerfHintController; - private final Optional<DesktopRepository> mDesktopRepositoryOptional; + private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional; private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; private final DisplayController mDisplayController; protected final ShellTaskOrganizer mTaskOrganizer; @@ -398,7 +399,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, @NonNull PipParamsChangedForwarder pipParamsChangedForwarder, Optional<SplitScreenController> splitScreenOptional, Optional<PipPerfHintController> pipPerfHintControllerOptional, - Optional<DesktopRepository> desktopRepositoryOptional, + Optional<DesktopUserRepositories> desktopUserRepositoriesOptional, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, @NonNull DisplayController displayController, @NonNull PipUiEventLogger pipUiEventLogger, @@ -426,7 +427,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory(); mSplitScreenOptional = splitScreenOptional; mPipPerfHintController = pipPerfHintControllerOptional.orElse(null); - mDesktopRepositoryOptional = desktopRepositoryOptional; + mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional; mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; mDisplayController = displayController; mTaskOrganizer = shellTaskOrganizer; @@ -764,7 +765,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // previous freeform bounds that is saved in DesktopRepository. // 2) If PiP was entered through other means (e.g. user swipe up), exit to initial // freeform bounds. Note that this case has a flicker at the moment (b/379984108). - Rect freeformBounds = mDesktopRepositoryOptional.get().removeBoundsBeforeMinimize( + Rect freeformBounds = getCurrentRepo().removeBoundsBeforeMinimize( mTaskInfo.taskId); return freeformBounds != null ? freeformBounds @@ -779,11 +780,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, /** Returns whether PiP is exiting while we're in desktop mode. */ // TODO(b/377581840): Update this check to include non-minimized cases, e.g. split to PiP etc. private boolean isPipExitingToDesktopMode() { - return Flags.enableDesktopWindowingPip() && mDesktopRepositoryOptional.isPresent() - && (mDesktopRepositoryOptional.get().getVisibleTaskCount(mTaskInfo.displayId) > 0 + DesktopRepository currentRepo = getCurrentRepo(); + return Flags.enableDesktopWindowingPip() && currentRepo != null + && (currentRepo.getVisibleTaskCount(mTaskInfo.displayId) > 0 || isDisplayInFreeform()); } + private DesktopRepository getCurrentRepo() { + return mDesktopUserRepositoriesOptional.map(DesktopUserRepositories::getCurrent).orElse( + null); + } + private void exitLaunchIntoPipTask(WindowContainerTransaction wct) { wct.startTask(mTaskInfo.launchIntoPipHostTaskId, null /* ActivityOptions */); mTaskOrganizer.applyTransaction(wct); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 72c1ef06806a..0042ec954f32 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -507,8 +507,8 @@ public class PipTransition extends PipTransitionController { } @Override - public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds, - @PipAnimationController.TransitionDirection int direction, + public void onFinishResize(@NonNull TaskInfo taskInfo, @NonNull Rect destinationBounds, + @NonNull Point leashOffset, @PipAnimationController.TransitionDirection int direction, @NonNull SurfaceControl.Transaction tx) { final boolean enteringPip = isInPipDirection(direction); if (enteringPip) { @@ -531,12 +531,16 @@ public class PipTransition extends PipTransitionController { if (mFixedRotationState != FIXED_ROTATION_TRANSITION && mFinishTransaction != null) { mFinishTransaction.merge(tx); - // Set window crop and position to destination bounds to avoid flickering. + // Set crop and position to destination bounds to avoid flickering. if (hasValidLeash) { - mFinishTransaction.setWindowCrop(leash, destinationBounds.width(), - destinationBounds.height()); - mFinishTransaction.setPosition(leash, destinationBounds.left, - destinationBounds.top); + final Rect relativeDestinationBounds = new Rect(destinationBounds); + relativeDestinationBounds.offset(-leashOffset.x, -leashOffset.y); + mFinishTransaction + .setCrop(leash, relativeDestinationBounds) + // Note that we should set the position to the start position of + // leash then the visible region will be at the same place even if + // the crop region doesn't start at (0, 0). + .setPosition(leash, leashOffset.x, leashOffset.y); } } } else { @@ -1267,7 +1271,8 @@ public class PipTransition extends PipTransitionController { mPipBoundsState.setBounds(destinationBounds); final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); - onFinishResize(pipTaskInfo, destinationBounds, TRANSITION_DIRECTION_TO_PIP, tx); + onFinishResize(pipTaskInfo, destinationBounds, animator.getLeashOffset(), + TRANSITION_DIRECTION_TO_PIP, tx); sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); if (swipePipToHomeOverlay != null) { mPipOrganizer.fadeOutAndRemoveOverlay(swipePipToHomeOverlay, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index a273822759f6..6e7740dc13e3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -31,6 +31,7 @@ import android.app.PictureInPictureUiState; import android.app.TaskInfo; import android.content.ComponentName; import android.content.pm.ActivityInfo; +import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; import android.os.RemoteException; @@ -38,6 +39,7 @@ import android.view.SurfaceControl; import android.view.WindowManager; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; +import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; @@ -92,7 +94,8 @@ public abstract class PipTransitionController implements Transitions.TransitionH mPipOrganizer.fadeOutAndRemoveOverlay(mPipOrganizer.mPipOverlay, null /* callback */, true /* withStartDelay*/); } - onFinishResize(taskInfo, animator.getDestinationBounds(), direction, tx); + onFinishResize(taskInfo, animator.getDestinationBounds(), + animator.getLeashOffset(), direction, tx); sendOnPipTransitionFinished(direction); } @@ -112,9 +115,9 @@ public abstract class PipTransitionController implements Transitions.TransitionH * Called when transition is about to finish. This is usually for performing tasks such as * applying WindowContainerTransaction to finalize the PiP bounds and send to the framework. */ - public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds, - @PipAnimationController.TransitionDirection int direction, - SurfaceControl.Transaction tx) { + public void onFinishResize(@NonNull TaskInfo taskInfo, @NonNull Rect destinationBounds, + @NonNull Point leashOffset, @PipAnimationController.TransitionDirection int direction, + @NonNull SurfaceControl.Transaction tx) { } /** @@ -310,6 +313,23 @@ public abstract class PipTransitionController implements Transitions.TransitionH return false; } + /** + * @return a change representing a config-at-end activity for a given parent. + */ + @Nullable + public TransitionInfo.Change getDeferConfigActivityChange(TransitionInfo info, + @android.annotation.NonNull WindowContainerToken parent) { + for (TransitionInfo.Change change : info.getChanges()) { + if (change.getTaskInfo() == null + && change.hasFlags(TransitionInfo.FLAG_CONFIG_AT_END) + && change.getParent() != null && change.getParent().equals(parent)) { + return change; + } + } + return null; + } + + /** Whether a particular package is same as current pip package. */ public boolean isPackageActiveInPip(@Nullable String packageName) { // No-op, to be handled differently in PIP1 and PIP2 diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 588b88753eb9..582df486b2b3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -32,7 +32,6 @@ import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTI import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE; import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection; -import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_PIP; import android.app.ActivityManager; import android.app.ActivityTaskManager; @@ -727,7 +726,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb mShellController.addConfigurationChangeListener(this); mShellController.addKeyguardChangeListener(this); mShellController.addUserChangeListener(this); - mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP, + mShellController.addExternalInterface(IPip.DESCRIPTOR, this::createExternalInterface, this); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java index 2c5d346224a3..e309da10864d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java @@ -20,8 +20,6 @@ import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; -import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_PIP; - import android.annotation.NonNull; import android.app.ActivityManager; import android.app.PictureInPictureParams; @@ -101,7 +99,7 @@ public class PipController implements ConfigurationChangeListener, private final List<Consumer<Boolean>> mOnIsInPipStateChangedListeners = new ArrayList<>(); // Wrapper for making Binder calls into PiP animation listener hosted in launcher's Recents. - private PipAnimationListener mPipRecentsAnimationListener; + @Nullable private PipAnimationListener mPipRecentsAnimationListener; @VisibleForTesting interface PipAnimationListener { @@ -213,7 +211,7 @@ public class PipController implements ConfigurationChangeListener, }); // Allow other outside processes to bind to PiP controller using the key below. - mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP, + mShellController.addExternalInterface(IPip.DESCRIPTOR, this::createExternalInterface, this); mShellController.addConfigurationChangeListener(this); @@ -380,7 +378,9 @@ public class PipController implements ConfigurationChangeListener, tx.setLayer(overlay, Integer.MAX_VALUE); tx.apply(); } - mPipRecentsAnimationListener.onPipAnimationStarted(); + if (mPipRecentsAnimationListener != null) { + mPipRecentsAnimationListener.onPipAnimationStarted(); + } } private void setLauncherKeepClearAreaHeight(boolean visible, int height) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java index 5438a014af00..4461a5c6a70c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java @@ -16,6 +16,7 @@ package com.android.wm.shell.pip2.phone; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; @@ -25,6 +26,7 @@ import android.content.Context; import android.graphics.Matrix; import android.graphics.Rect; import android.view.SurfaceControl; +import android.window.DisplayAreaInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -33,13 +35,19 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLog; +import com.android.window.flags.Flags; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; import com.android.wm.shell.pip2.animation.PipAlphaAnimator; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import java.util.Objects; +import java.util.Optional; + /** * Scheduler for Shell initiated PiP transitions and animations. */ @@ -50,6 +58,8 @@ public class PipScheduler { private final PipBoundsState mPipBoundsState; private final ShellExecutor mMainExecutor; private final PipTransitionState mPipTransitionState; + private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional; + private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; private PipTransitionController mPipTransitionController; private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mSurfaceControlTransactionFactory; @@ -61,11 +71,15 @@ public class PipScheduler { public PipScheduler(Context context, PipBoundsState pipBoundsState, ShellExecutor mainExecutor, - PipTransitionState pipTransitionState) { + PipTransitionState pipTransitionState, + Optional<DesktopUserRepositories> desktopUserRepositoriesOptional, + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { mContext = context; mPipBoundsState = pipBoundsState; mMainExecutor = mainExecutor; mPipTransitionState = pipTransitionState; + mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional; + mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; mSurfaceControlTransactionFactory = new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory(); @@ -87,7 +101,7 @@ public class PipScheduler { wct.setBounds(pipTaskToken, null); // if we are hitting a multi-activity case // windowing mode change will reparent to original host task - wct.setWindowingMode(pipTaskToken, WINDOWING_MODE_UNDEFINED); + wct.setWindowingMode(pipTaskToken, getOutPipWindowingMode()); return wct; } @@ -241,6 +255,47 @@ public class PipScheduler { maybeUpdateMovementBounds(); } + /** Returns whether the display is in freeform windowing mode. */ + private boolean isDisplayInFreeform() { + final DisplayAreaInfo tdaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo( + Objects.requireNonNull(mPipTransitionState.getPipTaskInfo()).displayId); + if (tdaInfo != null) { + return tdaInfo.configuration.windowConfiguration.getWindowingMode() + == WINDOWING_MODE_FREEFORM; + } + return false; + } + + /** Returns whether PiP is exiting while we're in desktop mode. */ + private boolean isPipExitingToDesktopMode() { + return Flags.enableDesktopWindowingPip() && mDesktopUserRepositoriesOptional.isPresent() + && (mDesktopUserRepositoriesOptional.get().getCurrent().getVisibleTaskCount( + Objects.requireNonNull(mPipTransitionState.getPipTaskInfo()).displayId) > 0 + || isDisplayInFreeform()); + } + + /** + * The windowing mode to restore to when resizing out of PIP direction. Defaults to undefined + * and can be overridden to restore to an alternate windowing mode. + */ + private int getOutPipWindowingMode() { + // If we are exiting PiP while the device is in Desktop mode (the task should expand to + // freeform windowing mode): + // 1) If the display windowing mode is freeform, set windowing mode to undefined so it will + // resolve the windowing mode to the display's windowing mode. + // 2) If the display windowing mode is not freeform, set windowing mode to freeform. + if (isPipExitingToDesktopMode()) { + if (isDisplayInFreeform()) { + return WINDOWING_MODE_UNDEFINED; + } else { + return WINDOWING_MODE_FREEFORM; + } + } + + // By default, or if the task is going to fullscreen, reset the windowing mode to undefined. + return WINDOWING_MODE_UNDEFINED; + } + @VisibleForTesting void setSurfaceControlTransactionFactory( @NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java index 44cc563eadf4..fc3fbe299605 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java @@ -222,7 +222,10 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha pipBoundsState, mTouchState, mPipScheduler, mPipTransitionState, pipUiEventLogger, menuController, mainExecutor, mPipPerfHintController); - mPipBoundsState.addOnAspectRatioChangedCallback(this::updateMinMaxSize); + mPipBoundsState.addOnAspectRatioChangedCallback(aspectRatio -> { + updateMinMaxSize(aspectRatio); + onAspectRatioChanged(); + }); mMoveOnShelVisibilityChanged = () -> { if (mIsImeShowing && mImeHeight > mShelfHeight) { @@ -768,16 +771,17 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha private void animateToNormalSize(Runnable callback) { // Save the current bounds as the user-resize bounds. mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds()); + final Rect adjustedNormalBounds = getAdjustedNormalBounds(); + mSavedSnapFraction = mMotionHelper.animateToExpandedState(adjustedNormalBounds, + getMovementBounds(mPipBoundsState.getBounds()), + getMovementBounds(adjustedNormalBounds), callback /* callback */); + } + private Rect getAdjustedNormalBounds() { final Size minMenuSize = mMenuController.getEstimatedMinMenuSize(); final Size defaultSize = mSizeSpecSource.getDefaultSize(mPipBoundsState.getAspectRatio()); final Rect normalBounds = new Rect(0, 0, defaultSize.getWidth(), defaultSize.getHeight()); - final Rect adjustedNormalBounds = mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu( - normalBounds, minMenuSize); - - mSavedSnapFraction = mMotionHelper.animateToExpandedState(adjustedNormalBounds, - getMovementBounds(mPipBoundsState.getBounds()), - getMovementBounds(adjustedNormalBounds), callback /* callback */); + return mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(normalBounds, minMenuSize); } private void animateToUnexpandedState(Rect restoreBounds) { @@ -1065,6 +1069,10 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(), insetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0); mMotionHelper.onMovementBoundsChanged(); + + if (mPipResizeGestureHandler.getUserResizeBounds().isEmpty()) { + mPipResizeGestureHandler.setUserResizeBounds(getAdjustedNormalBounds()); + } } private Rect getMovementBounds(Rect curBounds) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index 8f02c1b157b5..dae3c21b6697 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; +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_PIP; @@ -30,6 +31,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP; import static com.android.wm.shell.transition.Transitions.TRANSIT_RESIZE_PIP; +import static com.android.wm.shell.transition.Transitions.transitTypeToString; import android.animation.ValueAnimator; import android.annotation.NonNull; @@ -44,6 +46,7 @@ import android.os.Bundle; import android.os.IBinder; import android.view.Surface; import android.view.SurfaceControl; +import android.view.WindowManager; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerToken; @@ -52,6 +55,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.Nullable; import com.android.internal.util.Preconditions; +import com.android.window.flags.Flags; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; @@ -193,7 +197,7 @@ public class PipTransition extends PipTransitionController implements @NonNull TransitionRequestInfo request) { if (isAutoEnterInButtonNavigation(request) || isEnterPictureInPictureModeRequest(request)) { mEnterTransition = transition; - return getEnterPipTransaction(transition, request); + return getEnterPipTransaction(transition, request.getPipChange()); } return null; } @@ -202,7 +206,8 @@ public class PipTransition extends PipTransitionController implements public void augmentRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWct) { if (isAutoEnterInButtonNavigation(request) || isEnterPictureInPictureModeRequest(request)) { - outWct.merge(getEnterPipTransaction(transition, request), true /* transfer */); + outWct.merge(getEnterPipTransaction(transition, request.getPipChange()), + true /* transfer */); mEnterTransition = transition; } } @@ -282,6 +287,31 @@ public class PipTransition extends PipTransitionController implements } @Override + public boolean isEnteringPip(@NonNull TransitionInfo.Change change, + @WindowManager.TransitionType int transitType) { + if (change.getTaskInfo() != null + && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) { + // TRANSIT_TO_FRONT, though uncommon with triggering PiP, should semantically also + // be allowed to animate if the task in question is pinned already - see b/308054074. + if (transitType == TRANSIT_PIP || transitType == TRANSIT_OPEN + || transitType == TRANSIT_TO_FRONT) { + return true; + } + // This can happen if the request to enter PIP happens when we are collecting for + // another transition, such as TRANSIT_CHANGE (display rotation). + if (transitType == TRANSIT_CHANGE) { + return true; + } + + // Please file a bug to handle the unexpected transition type. + android.util.Slog.e(TAG, "Found new PIP in transition with mis-matched type=" + + transitTypeToString(transitType), new Throwable()); + } + return false; + } + + + @Override public void end() { if (mTransitionAnimator != null && mTransitionAnimator.isRunning()) { mTransitionAnimator.end(); @@ -627,6 +657,12 @@ public class PipTransition extends PipTransitionController implements finishTransition(); }); cacheAndStartTransitionAnimator(animator); + + // Save the PiP bounds in case, we re-enter the PiP with the same component. + float snapFraction = mPipBoundsAlgorithm.getSnapFraction( + mPipBoundsState.getBounds()); + mPipBoundsState.saveReentryState(snapFraction); + return true; } @@ -656,19 +692,6 @@ public class PipTransition extends PipTransitionController implements } @Nullable - private TransitionInfo.Change getDeferConfigActivityChange(TransitionInfo info, - @NonNull WindowContainerToken parent) { - for (TransitionInfo.Change change : info.getChanges()) { - if (change.getTaskInfo() == null - && change.hasFlags(TransitionInfo.FLAG_CONFIG_AT_END) - && change.getParent() != null && change.getParent().equals(parent)) { - return change; - } - } - return null; - } - - @Nullable private TransitionInfo.Change getChangeByToken(TransitionInfo info, WindowContainerToken token) { for (TransitionInfo.Change change : info.getChanges()) { @@ -707,6 +730,10 @@ public class PipTransition extends PipTransitionController implements && getFixedRotationDelta(info, pipTaskChange) == ROTATION_90) { adjustedSourceRectHint.offset(cutoutInsets.left, cutoutInsets.top); } + if (Flags.enableDesktopWindowingPip()) { + adjustedSourceRectHint.offset(-pipActivityChange.getStartAbsBounds().left, + -pipActivityChange.getStartAbsBounds().top); + } } else { // For non-valid app provided src-rect-hint, calculate one to crop into during // app icon overlay animation. @@ -754,9 +781,9 @@ public class PipTransition extends PipTransitionController implements } private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition, - @NonNull TransitionRequestInfo request) { + @NonNull TransitionRequestInfo.PipChange pipChange) { // cache the original task token to check for multi-activity case later - final ActivityManager.RunningTaskInfo pipTask = request.getPipTask(); + final ActivityManager.RunningTaskInfo pipTask = pipChange.getTaskInfo(); PictureInPictureParams pipParams = pipTask.pictureInPictureParams; mPipTaskListener.setPictureInPictureParams(pipParams); mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo, @@ -766,14 +793,18 @@ public class PipTransition extends PipTransitionController implements final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); mPipBoundsState.setBounds(entryBounds); + // Operate on the TF token in case we are dealing with AE case; this should avoid marking + // activities in other TFs as config-at-end. + WindowContainerToken token = pipChange.getTaskFragmentToken(); WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.movePipActivityToPinnedRootTask(pipTask.token, entryBounds); - wct.deferConfigToTransitionEnd(pipTask.token); + wct.movePipActivityToPinnedRootTask(token, entryBounds); + wct.deferConfigToTransitionEnd(token); return wct; } private boolean isAutoEnterInButtonNavigation(@NonNull TransitionRequestInfo requestInfo) { - final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipTask(); + final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipChange() != null + ? requestInfo.getPipChange().getTaskInfo() : null; if (pipTask == null) { return false; } @@ -912,11 +943,6 @@ public class PipTransition extends PipTransitionController implements "Unexpected bundle for " + mPipTransitionState); break; case PipTransitionState.EXITED_PIP: - // Save the PiP bounds in case, we re-enter the PiP with the same component. - float snapFraction = mPipBoundsAlgorithm.getSnapFraction( - mPipBoundsState.getBounds()); - mPipBoundsState.saveReentryState(snapFraction); - mPipTransitionState.setPinnedTaskLeash(null); mPipTransitionState.setPipTaskInfo(null); break; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 363c95fcf010..0869caa55369 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -24,7 +24,6 @@ import static android.view.Display.INVALID_DISPLAY; import static com.android.wm.shell.Flags.enableShellTopTaskTracking; import static com.android.wm.shell.desktopmode.DesktopWallpaperActivity.isWallpaperTask; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_OBSERVER; -import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS; import android.Manifest; import android.annotation.RequiresPermission; @@ -63,6 +62,7 @@ import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.desktopmode.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.GroupedTaskInfo; import com.android.wm.shell.shared.annotations.ExternalThread; @@ -72,6 +72,7 @@ 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.UserChangeListener; import java.io.PrintWriter; import java.util.ArrayList; @@ -89,13 +90,14 @@ import java.util.function.Consumer; */ public class RecentTasksController implements TaskStackListenerCallback, RemoteCallable<RecentTasksController>, DesktopRepository.ActiveTasksListener, - TaskStackTransitionObserver.TaskStackTransitionObserverListener { + TaskStackTransitionObserver.TaskStackTransitionObserverListener, UserChangeListener { private static final String TAG = RecentTasksController.class.getSimpleName(); private final Context mContext; private final ShellController mShellController; private final ShellCommandHandler mShellCommandHandler; - private final Optional<DesktopRepository> mDesktopRepository; + private final Optional<DesktopUserRepositories> mDesktopUserRepositories; + private final ShellExecutor mMainExecutor; private final TaskStackListenerImpl mTaskStackListener; private final RecentTasksImpl mImpl = new RecentTasksImpl(); @@ -108,6 +110,8 @@ public class RecentTasksController implements TaskStackListenerCallback, // Mapping of split task ids, mappings are symmetrical (ie. if t1 is the taskid of a task in a // pair, then mSplitTasks[t1] = t2, and mSplitTasks[t2] = t1) private final SparseIntArray mSplitTasks = new SparseIntArray(); + + private int mUserId; /** * Maps taskId to {@link SplitBounds} for both taskIDs. * Meaning there will be two taskId integers mapping to the same object. @@ -133,7 +137,7 @@ public class RecentTasksController implements TaskStackListenerCallback, ShellCommandHandler shellCommandHandler, TaskStackListenerImpl taskStackListener, ActivityTaskManager activityTaskManager, - Optional<DesktopRepository> desktopRepository, + Optional<DesktopUserRepositories> desktopUserRepositories, TaskStackTransitionObserver taskStackTransitionObserver, @ShellMainThread ShellExecutor mainExecutor ) { @@ -141,7 +145,7 @@ public class RecentTasksController implements TaskStackListenerCallback, return null; } return new RecentTasksController(context, shellInit, shellController, shellCommandHandler, - taskStackListener, activityTaskManager, desktopRepository, + taskStackListener, activityTaskManager, desktopUserRepositories, taskStackTransitionObserver, mainExecutor); } @@ -151,7 +155,7 @@ public class RecentTasksController implements TaskStackListenerCallback, ShellCommandHandler shellCommandHandler, TaskStackListenerImpl taskStackListener, ActivityTaskManager activityTaskManager, - Optional<DesktopRepository> desktopRepository, + Optional<DesktopUserRepositories> desktopUserRepositories, TaskStackTransitionObserver taskStackTransitionObserver, ShellExecutor mainExecutor) { mContext = context; @@ -160,7 +164,7 @@ public class RecentTasksController implements TaskStackListenerCallback, mActivityTaskManager = activityTaskManager; mPcFeatureEnabled = mContext.getPackageManager().hasSystemFeature(FEATURE_PC); mTaskStackListener = taskStackListener; - mDesktopRepository = desktopRepository; + mDesktopUserRepositories = desktopUserRepositories; mTaskStackTransitionObserver = taskStackTransitionObserver; mMainExecutor = mainExecutor; shellInit.addInitCallback(this::onInit, this); @@ -175,12 +179,15 @@ public class RecentTasksController implements TaskStackListenerCallback, } @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) - private void onInit() { - mShellController.addExternalInterface(KEY_EXTRA_SHELL_RECENT_TASKS, + void onInit() { + mShellController.addExternalInterface(IRecentTasks.DESCRIPTOR, this::createExternalInterface, this); mShellCommandHandler.addDumpCallback(this::dump, this); + mUserId = ActivityManager.getCurrentUser(); + mDesktopUserRepositories.ifPresent( + desktopUserRepositories -> + desktopUserRepositories.getCurrent().addActiveTaskListener(this)); mTaskStackListener.addListener(this); - mDesktopRepository.ifPresent(it -> it.addActiveTaskListener(this)); mTaskStackTransitionObserver.addTaskStackTransitionObserverListener(this, mMainExecutor); mContext.getSystemService(KeyguardManager.class).addKeyguardLockedStateListener( @@ -291,9 +298,9 @@ public class RecentTasksController implements TaskStackListenerCallback, */ @Override public void onRecentTaskRemovedForAddTask(int taskId) { - mDesktopRepository.ifPresent( - repo -> repo.removeFreeformTask(INVALID_DISPLAY, taskId) - ); + mDesktopUserRepositories.ifPresent( + desktopUserRepositories -> desktopUserRepositories.getCurrent().removeFreeformTask( + INVALID_DISPLAY, taskId)); } public void onTaskAdded(RunningTaskInfo taskInfo) { @@ -512,10 +519,9 @@ public class RecentTasksController implements TaskStackListenerCallback, // If it's not in the mapping, then it was already paired with another task continue; } - - if (DesktopModeStatus.canEnterDesktopMode(mContext) - && mDesktopRepository.isPresent() - && mDesktopRepository.get().isActiveTask(taskInfo.taskId)) { + if (DesktopModeStatus.canEnterDesktopMode(mContext) && + mDesktopUserRepositories.isPresent() + && mDesktopUserRepositories.get().getCurrent().isActiveTask(taskInfo.taskId)) { // Freeform tasks will be added as a separate entry if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) { mostRecentFreeformTaskIndex = groupedTasks.size(); @@ -531,7 +537,7 @@ public class RecentTasksController implements TaskStackListenerCallback, taskInfo.lastNonFullscreenBounds.top); } freeformTasks.add(taskInfo); - if (mDesktopRepository.get().isMinimizedTask(taskInfo.taskId)) { + if (mDesktopUserRepositories.get().getCurrent().isMinimizedTask(taskInfo.taskId)) { minimizedFreeformTasks.add(taskInfo.taskId); } continue; @@ -544,7 +550,9 @@ public class RecentTasksController implements TaskStackListenerCallback, groupedTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo, mTaskSplitBoundsMap.get(pairedTaskId))); } else { - if (Flags.enableRefactorTaskThumbnail() && isWallpaperTask(taskInfo)) { + if ( + Flags.enableUseTopVisibleActivityForExcludeFromRecentTask() + && isWallpaperTask(taskInfo)) { // Don't add the wallpaper task as an entry in grouped tasks continue; } @@ -703,6 +711,21 @@ public class RecentTasksController implements TaskStackListenerCallback, } } + @Override + public void onUserChanged(int newUserId, @NonNull Context userContext) { + if (mDesktopUserRepositories.isEmpty()) return; + + DesktopRepository previousUserRepository = + mDesktopUserRepositories.get().getProfile(mUserId); + mUserId = newUserId; + DesktopRepository currentUserRepository = + mDesktopUserRepositories.get().getProfile(newUserId); + + // No-op if both profile ids map to the same user. + if (previousUserRepository.getUserId() == currentUserRepository.getUserId()) return; + previousUserRepository.removeActiveTasksListener(this); + currentUserRepository.addActiveTaskListener(this); + } /** * The interface for calls from outside the host process. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 6e0e696e15fe..a368245db25f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -32,7 +32,6 @@ import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit; import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition; import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; -import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN; import static com.android.wm.shell.shared.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_0; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_1; @@ -94,6 +93,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.split.SplitScreenUtils; +import com.android.wm.shell.common.split.SplitState; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.draganddrop.SplitDragPolicy; @@ -199,6 +199,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter, private final Optional<WindowDecorViewModel> mWindowDecorViewModel; private final Optional<DesktopTasksController> mDesktopTasksController; private final MultiInstanceHelper mMultiInstanceHelpher; + private final SplitState mSplitState; private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler; @VisibleForTesting @@ -228,6 +229,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter, Optional<DesktopTasksController> desktopTasksController, @Nullable StageCoordinator stageCoordinator, MultiInstanceHelper multiInstanceHelper, + SplitState splitState, ShellExecutor mainExecutor, Handler mainHandler) { mShellCommandHandler = shellCommandHandler; @@ -252,6 +254,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter, mDesktopTasksController = desktopTasksController; mStageCoordinator = stageCoordinator; mMultiInstanceHelpher = multiInstanceHelper; + mSplitState = splitState; mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this); // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic // override for this controller from the base module @@ -278,7 +281,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter, mShellCommandHandler.addCommandCallback("splitscreen", mSplitScreenShellCommandHandler, this); mShellController.addKeyguardChangeListener(this); - mShellController.addExternalInterface(KEY_EXTRA_SHELL_SPLIT_SCREEN, + mShellController.addExternalInterface(ISplitScreen.DESCRIPTOR, this::createExternalInterface, this); if (mStageCoordinator == null) { // TODO: Multi-display @@ -296,7 +299,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter, mTaskOrganizer, mDisplayController, mDisplayImeController, mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider, mMainExecutor, mMainHandler, mRecentTasksOptional, mLaunchAdjacentController, - mWindowDecorViewModel); + mWindowDecorViewModel, mSplitState); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 88a95660f098..b40996f52bd3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -139,6 +139,7 @@ import com.android.wm.shell.common.split.OffscreenTouchZone; import com.android.wm.shell.common.split.SplitDecorManager; import com.android.wm.shell.common.split.SplitLayout; import com.android.wm.shell.common.split.SplitScreenUtils; +import com.android.wm.shell.common.split.SplitState; import com.android.wm.shell.common.split.SplitWindowManager; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentTasksController; @@ -218,6 +219,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private final Optional<RecentTasksController> mRecentTasks; private final LaunchAdjacentController mLaunchAdjacentController; private final Optional<WindowDecorViewModel> mWindowDecorViewModel; + /** Singleton source of truth for the current state of split screen on this device. */ + private final SplitState mSplitState; private final Rect mTempRect1 = new Rect(); private final Rect mTempRect2 = new Rect(); @@ -344,7 +347,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, TransactionPool transactionPool, IconProvider iconProvider, ShellExecutor mainExecutor, Handler mainHandler, Optional<RecentTasksController> recentTasks, LaunchAdjacentController launchAdjacentController, - Optional<WindowDecorViewModel> windowDecorViewModel) { + Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState) { mContext = context; mDisplayId = displayId; mSyncQueue = syncQueue; @@ -355,6 +358,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRecentTasks = recentTasks; mLaunchAdjacentController = launchAdjacentController; mWindowDecorViewModel = windowDecorViewModel; + mSplitState = splitState; taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */); @@ -412,7 +416,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, Transitions transitions, TransactionPool transactionPool, ShellExecutor mainExecutor, Handler mainHandler, Optional<RecentTasksController> recentTasks, LaunchAdjacentController launchAdjacentController, - Optional<WindowDecorViewModel> windowDecorViewModel) { + Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState) { mContext = context; mDisplayId = displayId; mSyncQueue = syncQueue; @@ -432,6 +436,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRecentTasks = recentTasks; mLaunchAdjacentController = launchAdjacentController; mWindowDecorViewModel = windowDecorViewModel; + mSplitState = splitState; + mDisplayController.addDisplayWindowListener(this); transitions.addHandler(this); mSplitUnsupportedToast = Toast.makeText(mContext, @@ -501,6 +507,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** * Deactivates main stage by removing the stage from the top level split root (usually when a * task underneath gets removed from the stage root). + * This function should always be called as part of exiting split screen. * @param stageToTop stage which we want to put on top */ private void deactivateSplit(WindowContainerTransaction wct, @StageType int stageToTop) { @@ -833,7 +840,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, setSideStagePosition(splitPosition, wct); options1 = options1 != null ? options1 : new Bundle(); - addActivityOptions(options1, mSideStage); + StageTaskListener stageForTask1; + if (enableFlexibleSplit()) { + stageForTask1 = mStageOrderOperator.getStageForLegacyPosition(splitPosition, + true /*checkAllStagesIfNotActive*/); + } else { + stageForTask1 = mSideStage; + } + addActivityOptions(options1, stageForTask1); wct.sendPendingIntent(pendingIntent, fillInIntent, options1); prepareTasksForSplitScreen(new int[] {taskId}, wct); @@ -878,7 +892,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, setSideStagePosition(splitPosition, wct); options1 = options1 != null ? options1 : new Bundle(); - addActivityOptions(options1, mSideStage); + StageTaskListener stageForTask1; + if (enableFlexibleSplit()) { + stageForTask1 = mStageOrderOperator.getStageForLegacyPosition(splitPosition, + true /*checkAllStagesIfNotActive*/); + } else { + stageForTask1 = mSideStage; + } + addActivityOptions(options1, stageForTask1); wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1); prepareTasksForSplitScreen(new int[] {taskId}, wct); @@ -1010,14 +1031,29 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, setRootForceTranslucent(false, wct); options1 = options1 != null ? options1 : new Bundle(); - addActivityOptions(options1, mSideStage); + StageTaskListener stageForTask1; + if (enableFlexibleSplit()) { + stageForTask1 = mStageOrderOperator.getStageForLegacyPosition(splitPosition, + true /*checkAllStagesIfNotActive*/); + } else { + stageForTask1 = mSideStage; + } + addActivityOptions(options1, stageForTask1); if (shortcutInfo1 != null) { wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1); } else { wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1); } + + StageTaskListener stageForTask2; + if (enableFlexibleSplit()) { + stageForTask2 = mStageOrderOperator.getStageForLegacyPosition( + reverseSplitPosition(splitPosition), true /*checkAllStagesIfNotActive*/); + } else { + stageForTask2 = mMainStage; + } options2 = options2 != null ? options2 : new Bundle(); - addActivityOptions(options2, mMainStage); + addActivityOptions(options2, stageForTask2); if (shortcutInfo2 != null) { wct.startShortcut(mContext.getPackageName(), shortcutInfo2, options2); } else { @@ -1246,9 +1282,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Restore focus-ability to the windows and divider wct.setFocusable(mRootTaskInfo.token, true); + if (enableFlexibleSplit()) { + mStageOrderOperator.onDoubleTappedDivider(); + } setSideStagePosition(reverseSplitPosition(mSideStagePosition), wct); mSyncQueue.queue(wct); mSyncQueue.runInSync(st -> { + mSplitLayout.updateStateWithCurrentPosition(); updateSurfaceBounds(mSplitLayout, st, false /* applyResizingOffset */); // updateSurfaceBounds(), above, officially puts the two apps in their new @@ -1404,6 +1444,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (!isSplitActive() || mIsExiting) return; onSplitScreenExit(); + mSplitState.exit(); clearSplitPairedInRecents(exitReason); mShouldUpdateRecents = false; @@ -1599,6 +1640,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE); } deactivateSplit(wct, stageToTop); + mSplitState.exit(); } private void prepareEnterSplitScreen(WindowContainerTransaction wct) { @@ -1697,6 +1739,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, void finishEnterSplitScreen(SurfaceControl.Transaction finishT) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "finishEnterSplitScreen"); + mSplitLayout.updateStateWithCurrentPosition(); mSplitLayout.update(null, true /* resetImePosition */); if (enableFlexibleSplit()) { runForActiveStages((stage) -> @@ -1921,6 +1964,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } + int currentSnapPosition = mSplitLayout.calculateCurrentSnapPosition(); + if (Flags.enableFlexibleTwoAppSplit()) { // Split screen can be laid out in such a way that some of the apps are offscreen. // For the purposes of passing SplitBounds up to launcher (for use in thumbnails @@ -1933,10 +1978,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, Math.min(bottomRightBounds.right, mSplitLayout.getDisplayWidth()); bottomRightBounds.top = Math.min(bottomRightBounds.top, mSplitLayout.getDisplayHeight()); + + // TODO (b/349828130): Can change to getState() fully after brief soak time. + if (mSplitState.get() != currentSnapPosition) { + Log.wtf(TAG, "SplitState is " + mSplitState.get() + + ", expected " + currentSnapPosition); + currentSnapPosition = mSplitState.get(); + } } SplitBounds splitBounds = new SplitBounds(topLeftBounds, bottomRightBounds, - leftTopTaskId, rightBottomTaskId, mSplitLayout.calculateCurrentSnapPosition()); + leftTopTaskId, rightBottomTaskId, currentSnapPosition); if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) { // Update the pair for the top tasks boolean added = recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId, @@ -1975,7 +2027,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext, mRootTaskInfo.configuration, this, mParentContainerCallbacks, mDisplayController, mDisplayImeController, mTaskOrganizer, - PARALLAX_ALIGN_CENTER /* parallaxType */, mMainHandler); + PARALLAX_ALIGN_CENTER /* parallaxType */, mSplitState, mMainHandler); mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt index b7b3c9b62a58..3fa8df40dfef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt @@ -38,6 +38,7 @@ import com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_B import com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_C import com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString import com.android.wm.shell.windowdecor.WindowDecorViewModel +import java.util.Collections import java.util.Optional /** @@ -148,6 +149,24 @@ class StageOrderOperator ( } /** + * This will swap the stages for the two stages on either side of the given divider. + * Note: This will keep [activeStages] and [allStages] in sync by swapping both of them + * If there are no [activeStages] then this will be a no-op. + * + * TODO(b/379984874): Take in a divider identifier to determine which array indices to swap + */ + fun onDoubleTappedDivider() { + if (activeStages.isEmpty()) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + "Stages not active, ignoring swap request") + return + } + + Collections.swap(activeStages, 0, 1) + Collections.swap(allStages, 0, 1) + } + + /** * Returns a legacy split position for the given stage. If no stages are active then this will * return [SPLIT_POSITION_UNDEFINED] */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java index 34681569a16c..c5e158c6b452 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java @@ -32,6 +32,7 @@ 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.common.SystemWindows; +import com.android.wm.shell.common.split.SplitState; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.shared.TransactionPool; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -60,6 +61,7 @@ public class TvSplitScreenController extends SplitScreenController { private final IconProvider mIconProvider; private final Optional<RecentTasksController> mRecentTasksOptional; private final LaunchAdjacentController mLaunchAdjacentController; + private final SplitState mSplitState; private final Handler mMainHandler; private final SystemWindows mSystemWindows; @@ -80,6 +82,7 @@ public class TvSplitScreenController extends SplitScreenController { Optional<RecentTasksController> recentTasks, LaunchAdjacentController launchAdjacentController, MultiInstanceHelper multiInstanceHelper, + SplitState splitState, ShellExecutor mainExecutor, Handler mainHandler, SystemWindows systemWindows) { @@ -87,8 +90,8 @@ public class TvSplitScreenController extends SplitScreenController { syncQueue, rootTDAOrganizer, displayController, displayImeController, displayInsetsController, null, transitions, transactionPool, iconProvider, recentTasks, launchAdjacentController, Optional.empty(), - Optional.empty(), null /* stageCoordinator */, multiInstanceHelper, mainExecutor, - mainHandler); + Optional.empty(), null /* stageCoordinator */, multiInstanceHelper, splitState, + mainExecutor, mainHandler); mTaskOrganizer = shellTaskOrganizer; mSyncQueue = syncQueue; @@ -102,6 +105,7 @@ public class TvSplitScreenController extends SplitScreenController { mIconProvider = iconProvider; mRecentTasksOptional = recentTasks; mLaunchAdjacentController = launchAdjacentController; + mSplitState = splitState; mMainHandler = mainHandler; mSystemWindows = systemWindows; @@ -117,7 +121,7 @@ public class TvSplitScreenController extends SplitScreenController { mTaskOrganizer, mDisplayController, mDisplayImeController, mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider, mMainExecutor, mMainHandler, - mRecentTasksOptional, mLaunchAdjacentController, mSystemWindows); + mRecentTasksOptional, mLaunchAdjacentController, mSplitState, mSystemWindows); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java index 4451ee887363..ef1f88e635d3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java @@ -28,6 +28,7 @@ import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; +import com.android.wm.shell.common.split.SplitState; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.shared.TransactionPool; import com.android.wm.shell.shared.split.SplitScreenConstants; @@ -53,10 +54,12 @@ public class TvStageCoordinator extends StageCoordinator Handler mainHandler, Optional<RecentTasksController> recentTasks, LaunchAdjacentController launchAdjacentController, + SplitState splitState, SystemWindows systemWindows) { super(context, displayId, syncQueue, taskOrganizer, displayController, displayImeController, displayInsetsController, transitions, transactionPool, iconProvider, - mainExecutor, mainHandler, recentTasks, launchAdjacentController, Optional.empty()); + mainExecutor, mainHandler, recentTasks, launchAdjacentController, Optional.empty(), + splitState); mTvSplitMenuController = new TvSplitMenuController(context, this, systemWindows, mainHandler); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java index 7cb8e8aa7b49..72bad4193f4a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java @@ -23,8 +23,6 @@ import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN; import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_WINDOWLESS; -import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW; - import android.app.ActivityManager.RunningTaskInfo; import android.app.TaskInfo; import android.content.Context; @@ -119,7 +117,7 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo private void onInit() { mShellTaskOrganizer.initStartingWindow(this); - mShellController.addExternalInterface(KEY_EXTRA_SHELL_STARTING_WINDOW, + mShellController.addExternalInterface(IStartingWindow.DESCRIPTOR, this::createExternalInterface, this); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java index 82c0aaf3bc8b..361d766370e5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java @@ -186,6 +186,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, */ public void setObscuredTouchRect(Rect obscuredRect) { mObscuredTouchRegion = obscuredRect != null ? new Region(obscuredRect) : null; + invalidate(); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index 92d1f9c26bbc..41c0a11827bb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -287,6 +287,7 @@ public class DefaultMixedHandler implements MixedTransitionHandler, MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING, transition)); // Postpone transition splitting to later. WindowContainerTransaction out = new WindowContainerTransaction(); + mPipHandler.augmentRequest(transition, request, out); return out; } else if (request.getRemoteTransition() != null && TransitionUtil.isOpeningType(request.getType()) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java index 3d3de88cdafc..03ded730865e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java @@ -16,6 +16,7 @@ package com.android.wm.shell.transition; +import static android.view.WindowManager.TRANSIT_PIP; import static android.view.WindowManager.TRANSIT_TO_BACK; import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy; @@ -37,6 +38,8 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.StageCoordinator; import com.android.wm.shell.unfold.UnfoldTransitionHandler; +import java.util.List; + class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { private final UnfoldTransitionHandler mUnfoldHandler; private final ActivityEmbeddingController mActivityEmbeddingController; @@ -127,6 +130,13 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { } } + TransitionInfo.Change pipActivityChange = null; + if (pipChange != null) { + pipActivityChange = mPipHandler.getDeferConfigActivityChange( + info, pipChange.getContainer()); + everythingElse.getChanges().remove(pipActivityChange); + } + final Transitions.TransitionFinishCallback finishCB = (wct) -> { --mInFlightSubAnimations; joinFinishArgs(wct); @@ -139,13 +149,23 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { return false; } - // PIP window should always be on the highest Z order. - if (pipChange != null) { + if (pipChange != null && pipActivityChange == null) { + // We are operating on a single PiP task for the enter animation here. mInFlightSubAnimations = 2; + // PIP window should always be on the highest Z order. mPipHandler.startEnterAnimation( pipChange, startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE), finishTransaction, finishCB); + } else if (pipActivityChange != null) { + // If there is both a PiP task and a PiP config-at-end activity change, + // put them into a separate TransitionInfo, and send to be animated as TRANSIT_PIP. + mInFlightSubAnimations = 2; + TransitionInfo pipInfo = subCopy(info, TRANSIT_PIP, false /* withChanges */); + pipInfo.getChanges().addAll(List.of(pipChange, pipActivityChange)); + mPipHandler.startAnimation(mTransition, pipInfo, + startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE), + finishTransaction, finishCB); } else { mInFlightSubAnimations = 1; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java index 4ea4613185e1..d8884f6d8d38 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java @@ -41,9 +41,9 @@ public class DefaultSurfaceAnimator { @NonNull Animation anim, @NonNull SurfaceControl leash, @NonNull Runnable finishCallback, @NonNull TransactionPool pool, @NonNull ShellExecutor mainExecutor, @Nullable Point position, float cornerRadius, - @Nullable Rect clipRect, boolean isActivity) { + @Nullable Rect clipRect) { final DefaultAnimationAdapter adapter = new DefaultAnimationAdapter(anim, leash, - position, clipRect, cornerRadius, isActivity); + position, clipRect, cornerRadius); buildSurfaceAnimation(animations, anim, finishCallback, pool, mainExecutor, adapter); } @@ -138,11 +138,9 @@ public class DefaultSurfaceAnimator { @Nullable final Rect mClipRect; @Nullable private final Rect mAnimClipRect; final float mCornerRadius; - final boolean mIsActivity; DefaultAnimationAdapter(@NonNull Animation anim, @NonNull SurfaceControl leash, - @Nullable Point position, @Nullable Rect clipRect, float cornerRadius, - boolean isActivity) { + @Nullable Point position, @Nullable Rect clipRect, float cornerRadius) { super(leash); mAnim = anim; mPosition = (position != null && (position.x != 0 || position.y != 0)) @@ -150,7 +148,6 @@ public class DefaultSurfaceAnimator { mClipRect = (clipRect != null && !clipRect.isEmpty()) ? clipRect : null; mAnimClipRect = mClipRect != null ? new Rect() : null; mCornerRadius = cornerRadius; - mIsActivity = isActivity; } @Override @@ -160,10 +157,6 @@ public class DefaultSurfaceAnimator { final SurfaceControl leash = mLeash; transformation.clear(); mAnim.getTransformation(currentPlayTime, transformation); - if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader() - && mIsActivity && mAnim.getExtensionEdges() != 0) { - t.setEdgeExtensionEffect(leash, mAnim.getExtensionEdges()); - } if (mPosition != null) { transformation.getMatrix().postTranslate(mPosition.x, mPosition.y); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 9fcf98b9efc2..e80016d07f15 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -506,6 +506,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { if (!isTask && a.getExtensionEdges() != 0x0) { if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()) { + startTransaction.setEdgeExtensionEffect( + change.getLeash(), a.getExtensionEdges()); finishTransaction.setEdgeExtensionEffect(change.getLeash(), /* edge */ 0); } else { if (!TransitionUtil.isOpeningType(mode)) { @@ -564,7 +566,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { buildSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish, mTransactionPool, mMainExecutor, animRelOffset, cornerRadius, - clipRect, change.getActivityComponent() != null); + clipRect); final TransitionInfo.AnimationOptions options; if (Flags.moveAnimationOptionsToChange()) { @@ -876,8 +878,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { a.restrictDuration(MAX_ANIMATION_DURATION); a.scaleCurrentDuration(mTransitionAnimationScaleSetting); buildSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool, - mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds(), - change.getActivityComponent() != null); + mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds()); } private void attachThumbnailAnimation(@NonNull ArrayList<Animator> animations, @@ -901,8 +902,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { a.restrictDuration(MAX_ANIMATION_DURATION); a.scaleCurrentDuration(mTransitionAnimationScaleSetting); buildSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool, - mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds(), - change.getActivityComponent() != null); + mMainExecutor, change.getEndRelOffset(), cornerRadius, change.getEndAbsBounds()); } private static int getWallpaperTransitType(TransitionInfo info) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java index 6f3aa11a8f52..aa42b7f0ca76 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java @@ -347,21 +347,21 @@ class ScreenRotationAnimation { @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) { buildSurfaceAnimation(animations, mRotateEnterAnimation, getEnterSurface(), finishCallback, mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */, - null /* clipRect */, false /* isActivity */); + null /* clipRect */); } private void startScreenshotRotationAnimation(@NonNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) { buildSurfaceAnimation(animations, mRotateExitAnimation, mAnimLeash, finishCallback, mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */, - null /* clipRect */, false /* isActivity */); + null /* clipRect */); } private void buildScreenshotAlphaAnimation(@NonNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) { buildSurfaceAnimation(animations, mRotateAlphaAnimation, mAnimLeash, finishCallback, mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */, - null /* clipRect */, false /* isActivity */); + null /* clipRect */); } private void buildLumaAnimation(@NonNull ArrayList<Animator> animations, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 3f191497e1ed..611f3e0ac5e8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -41,7 +41,6 @@ import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPI import static com.android.systemui.shared.Flags.returnAnimationFrameworkLongLived; import static com.android.window.flags.Flags.ensureWallpaperInTransitions; import static com.android.window.flags.Flags.migratePredictiveBackTransition; -import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS; import static com.android.wm.shell.shared.TransitionUtil.isClosingType; import static com.android.wm.shell.shared.TransitionUtil.isOpeningType; @@ -373,7 +372,7 @@ public class Transitions implements RemoteCallable<Transitions>, if (Transitions.ENABLE_SHELL_TRANSITIONS) { mOrganizer.shareTransactionQueue(); } - mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS, + mShellController.addExternalInterface(IShellTransitions.DESCRIPTOR, this::createExternalInterface, this); ContentResolver resolver = mContext.getContentResolver(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index c9f2d2e8c0e2..0b919668f7fe 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -63,10 +63,13 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; import com.android.wm.shell.shared.FocusTransitionListener; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.FocusTransitionObserver; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost; +import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; /** @@ -89,6 +92,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT private final Transitions mTransitions; private final Region mExclusionRegion = Region.obtain(); private final InputManager mInputManager; + private final WindowDecorViewHostSupplier<WindowDecorViewHost> mWindowDecorViewHostSupplier; private TaskOperations mTaskOperations; private FocusTransitionObserver mFocusTransitionObserver; @@ -119,8 +123,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT public CaptionWindowDecorViewModel( Context context, Handler mainHandler, + @ShellMainThread ShellExecutor shellExecutor, @ShellBackgroundThread ShellExecutor bgExecutor, - ShellExecutor shellExecutor, Choreographer mainChoreographer, IWindowManager windowManager, ShellInit shellInit, @@ -129,7 +133,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, SyncTransactionQueue syncQueue, Transitions transitions, - FocusTransitionObserver focusTransitionObserver) { + FocusTransitionObserver focusTransitionObserver, + WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier) { mContext = context; mMainExecutor = shellExecutor; mMainHandler = mainHandler; @@ -142,6 +147,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT mSyncQueue = syncQueue; mTransitions = transitions; mFocusTransitionObserver = focusTransitionObserver; + mWindowDecorViewHostSupplier = windowDecorViewHostSupplier; if (!Transitions.ENABLE_SHELL_TRANSITIONS) { mTaskOperations = new TaskOperations(null, mContext, mSyncQueue); } @@ -331,7 +337,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT mMainHandler, mBgExecutor, mMainChoreographer, - mSyncQueue); + mSyncQueue, + mWindowDecorViewHostSupplier); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); final FluidResizeTaskPositioner taskPositioner = diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 982fda0ddf36..23bb2aa616f9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -58,6 +58,8 @@ 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.shared.annotations.ShellBackgroundThread; +import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost; +import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; /** @@ -90,8 +92,10 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL Handler handler, @ShellBackgroundThread ShellExecutor bgExecutor, Choreographer choreographer, - SyncTransactionQueue syncQueue) { - super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface); + SyncTransactionQueue syncQueue, + @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier) { + super(context, userContext, displayController, taskOrganizer, taskInfo, + taskSurface, windowDecorViewHostSupplier); mHandler = handler; mBgExecutor = bgExecutor; mChoreographer = choreographer; @@ -194,6 +198,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL @VisibleForTesting static void updateRelayoutParams( RelayoutParams relayoutParams, + @NonNull Context context, ActivityManager.RunningTaskInfo taskInfo, boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop, @@ -206,9 +211,11 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL relayoutParams.mRunningTaskInfo = taskInfo; relayoutParams.mLayoutResId = R.layout.caption_window_decor; relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode()); - relayoutParams.mShadowRadiusId = hasGlobalFocus - ? R.dimen.freeform_decor_shadow_focused_thickness - : R.dimen.freeform_decor_shadow_unfocused_thickness; + relayoutParams.mShadowRadius = hasGlobalFocus + ? context.getResources().getDimensionPixelSize( + R.dimen.freeform_decor_shadow_focused_thickness) + : context.getResources().getDimensionPixelSize( + R.dimen.freeform_decor_shadow_unfocused_thickness); relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; relayoutParams.mSetTaskVisibilityPositionAndCrop = shouldSetTaskVisibilityPositionAndCrop; relayoutParams.mIsCaptionVisible = taskInfo.isFreeform() @@ -251,7 +258,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL final SurfaceControl oldDecorationSurface = mDecorationContainerSurface; final WindowContainerTransaction wct = new WindowContainerTransaction(); - updateRelayoutParams(mRelayoutParams, taskInfo, applyStartTransactionOnDraw, + updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop, mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded, mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt index 101467df03ac..ff52a45c94e2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt @@ -32,7 +32,7 @@ import com.android.internal.annotations.VisibleForTesting import com.android.window.flags.Flags import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.common.DisplayController -import com.android.wm.shell.desktopmode.DesktopRepository +import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer @@ -51,7 +51,7 @@ class DesktopHeaderManageWindowsMenu( private val displayController: DisplayController, private val rootTdaOrganizer: RootTaskDisplayAreaOrganizer, context: Context, - private val desktopRepository: DesktopRepository, + private val desktopUserRepositories: DesktopUserRepositories, private val surfaceControlBuilderSupplier: Supplier<SurfaceControl.Builder>, private val surfaceControlTransactionSupplier: Supplier<SurfaceControl.Transaction>, snapshotList: List<Pair<Int, TaskSnapshot>>, @@ -76,6 +76,7 @@ class DesktopHeaderManageWindowsMenu( val flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH + val desktopRepository = desktopUserRepositories.getProfile(callerTaskInfo.userId) menuViewContainer = if (Flags.enableFullyImmersiveInDesktop() && desktopRepository.isTaskInFullImmersiveState(callerTaskInfo.taskId)) { // Use system view container so that forcibly shown system bars take effect in diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 04ef7c1dc461..0f5813c7807b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -79,6 +79,7 @@ import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.View; import android.view.ViewConfiguration; +import android.view.ViewRootImpl; import android.view.WindowManager; import android.window.DesktopModeFlags; import android.window.TaskSnapshot; @@ -111,13 +112,17 @@ import com.android.wm.shell.desktopmode.DesktopImmersiveController; import com.android.wm.shell.desktopmode.DesktopModeEventLogger; import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger; import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum; +import com.android.wm.shell.desktopmode.DesktopModeUtils; import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator; import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition; import com.android.wm.shell.desktopmode.DesktopTasksLimiter; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.desktopmode.DesktopWallpaperActivity; import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository; +import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction; +import com.android.wm.shell.desktopmode.common.ToggleTaskSizeUtilsKt; import com.android.wm.shell.desktopmode.education.AppHandleEducationController; import com.android.wm.shell.desktopmode.education.AppToWebEducationController; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; @@ -135,6 +140,8 @@ import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.FocusTransitionObserver; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener; +import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost; +import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier; import com.android.wm.shell.windowdecor.extension.InsetsStateKt; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder; @@ -166,7 +173,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private final ActivityTaskManager mActivityTaskManager; private final ShellCommandHandler mShellCommandHandler; private final ShellTaskOrganizer mTaskOrganizer; - private final DesktopRepository mDesktopRepository; + private final DesktopUserRepositories mDesktopUserRepositories; private final ShellController mShellController; private final Context mContext; private final @ShellMainThread Handler mMainHandler; @@ -213,6 +220,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private boolean mInImmersiveMode; private final String mSysUIPackageName; private final AssistContentRequester mAssistContentRequester; + private final WindowDecorViewHostSupplier<WindowDecorViewHost> mWindowDecorViewHostSupplier; private final DisplayChangeController.OnDisplayChangingListener mOnDisplayChangingListener; private final ISystemGestureExclusionListener mGestureExclusionListener = @@ -244,7 +252,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, ShellCommandHandler shellCommandHandler, IWindowManager windowManager, ShellTaskOrganizer taskOrganizer, - DesktopRepository desktopRepository, + DesktopUserRepositories desktopUserRepositories, DisplayController displayController, ShellController shellController, DisplayInsetsController displayInsetsController, @@ -256,6 +264,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, InteractionJankMonitor interactionJankMonitor, AppToWebGenericLinksParser genericLinksParser, AssistContentRequester assistContentRequester, + @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier, MultiInstanceHelper multiInstanceHelper, Optional<DesktopTasksLimiter> desktopTasksLimiter, AppHandleEducationController appHandleEducationController, @@ -275,7 +284,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, shellCommandHandler, windowManager, taskOrganizer, - desktopRepository, + desktopUserRepositories, displayController, shellController, displayInsetsController, @@ -285,6 +294,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, desktopImmersiveController, genericLinksParser, assistContentRequester, + windowDecorViewHostSupplier, multiInstanceHelper, new DesktopModeWindowDecoration.Factory(), new InputMonitorFactory(), @@ -315,7 +325,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, ShellCommandHandler shellCommandHandler, IWindowManager windowManager, ShellTaskOrganizer taskOrganizer, - DesktopRepository desktopRepository, + DesktopUserRepositories desktopUserRepositories, DisplayController displayController, ShellController shellController, DisplayInsetsController displayInsetsController, @@ -325,6 +335,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, DesktopImmersiveController desktopImmersiveController, AppToWebGenericLinksParser genericLinksParser, AssistContentRequester assistContentRequester, + @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier, MultiInstanceHelper multiInstanceHelper, DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory, InputMonitorFactory inputMonitorFactory, @@ -349,7 +360,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, mBgExecutor = bgExecutor; mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class); mTaskOrganizer = taskOrganizer; - mDesktopRepository = desktopRepository; + mDesktopUserRepositories = desktopUserRepositories; mShellController = shellController; mDisplayController = displayController; mDisplayInsetsController = displayInsetsController; @@ -377,6 +388,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository; mActivityOrientationChangeHandler = activityOrientationChangeHandler; mAssistContentRequester = assistContentRequester; + mWindowDecorViewHostSupplier = windowDecorViewHostSupplier; mOnDisplayChangingListener = (displayId, fromRotation, toRotation, displayAreaInfo, t) -> { DesktopModeWindowDecoration decoration; RunningTaskInfo taskInfo; @@ -576,38 +588,71 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private void openHandleMenu(int taskId) { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId); - decoration.createHandleMenu(checkNumberOfOtherInstances(decoration.mTaskInfo) - >= MANAGE_WINDOWS_MINIMUM_INSTANCES); + // TODO(b/379873022): Run the instance check and the AssistContent request in + // createHandleMenu on the same bg thread dispatch. + mBgExecutor.execute(() -> { + final int numOfInstances = checkNumberOfOtherInstances(decoration.mTaskInfo); + mMainExecutor.execute(() -> { + decoration.createHandleMenu( + numOfInstances >= MANAGE_WINDOWS_MINIMUM_INSTANCES + ); + }); + }); } - private void onMaximizeOrRestore(int taskId, String source, ResizeTrigger resizeTrigger, - MotionEvent motionEvent) { + private void onToggleSizeInteraction( + int taskId, @NonNull ToggleTaskSizeInteraction.AmbiguousSource source, + @Nullable MotionEvent motionEvent) { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId); if (decoration == null) { return; } - mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo, resizeTrigger, - DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent), () -> { - mInteractionJankMonitor.begin( - decoration.mTaskSurface, mContext, mMainHandler, - Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, source); - return null; - }, () -> { - mInteractionJankMonitor.begin( - decoration.mTaskSurface, mContext, mMainHandler, - Cuj.CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW, source); - return null; - }); + final ToggleTaskSizeInteraction interaction = + createToggleSizeInteraction(decoration, source, motionEvent); + if (interaction == null) { + return; + } + if (interaction.getCujTracing() != null) { + mInteractionJankMonitor.begin( + decoration.mTaskSurface, mContext, mMainHandler, + interaction.getCujTracing(), interaction.getJankTag()); + } + mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo, interaction); decoration.closeHandleMenu(); decoration.closeMaximizeMenu(); } - private void onEnterOrExitImmersive(int taskId) { - final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId); + private ToggleTaskSizeInteraction createToggleSizeInteraction( + @NonNull DesktopModeWindowDecoration decoration, + @NonNull ToggleTaskSizeInteraction.AmbiguousSource source, + @Nullable MotionEvent motionEvent) { + final RunningTaskInfo taskInfo = decoration.mTaskInfo; + + final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(taskInfo.displayId); + if (displayLayout == null) { + return null; + } + final Rect stableBounds = new Rect(); + displayLayout.getStableBounds(stableBounds); + boolean isMaximized = DesktopModeUtils.isTaskMaximized(taskInfo, stableBounds); + + return new ToggleTaskSizeInteraction( + isMaximized + ? ToggleTaskSizeInteraction.Direction.RESTORE + : ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeUtilsKt.toSource(source, isMaximized), + DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent) + ); + } + + private void onEnterOrExitImmersive(RunningTaskInfo taskInfo) { + final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId); if (decoration == null) { return; } - if (mDesktopRepository.isTaskInFullImmersiveState(taskId)) { + final DesktopRepository desktopRepository = mDesktopUserRepositories.getProfile( + taskInfo.userId); + if (desktopRepository.isTaskInFullImmersiveState(taskInfo.taskId)) { mDesktopModeUiEventLogger.log(decoration.mTaskInfo, DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_RESTORE); mDesktopImmersiveController.moveTaskToNonImmersive( @@ -726,12 +771,20 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, return; } decoration.closeHandleMenu(); - decoration.createManageWindowsMenu(getTaskSnapshots(decoration.mTaskInfo), - /* onIconClickListener= */(Integer requestedTaskId) -> { - decoration.closeManageWindowsMenu(); - mDesktopTasksController.openInstance(decoration.mTaskInfo, requestedTaskId); - return Unit.INSTANCE; - }); + mBgExecutor.execute(() -> { + final ArrayList<Pair<Integer, TaskSnapshot>> snapshotList = + getTaskSnapshots(decoration.mTaskInfo); + mMainExecutor.execute(() -> decoration.createManageWindowsMenu( + snapshotList, + /* onIconClickListener= */ (Integer requestedTaskId) -> { + decoration.closeManageWindowsMenu(); + mDesktopTasksController.openInstance(decoration.mTaskInfo, + requestedTaskId); + return Unit.INSTANCE; + } + ) + ); + }); } private ArrayList<Pair<Integer, TaskSnapshot>> getTaskSnapshots( @@ -802,7 +855,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private boolean mIsResizeGesture; private boolean mIsDragging; private boolean mTouchscreenInUse; - private boolean mHasLongClicked; private int mDragPointerId = -1; private MotionEvent mMotionEvent; @@ -868,12 +920,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, && TaskInfoKt.getRequestingImmersive(decoration.mTaskInfo)) { // Task is requesting immersive, so it should either enter or exit immersive, // depending on immersive state. - onEnterOrExitImmersive(decoration.mTaskInfo.taskId); + onEnterOrExitImmersive(decoration.mTaskInfo); } else { // Full immersive is disabled or task doesn't request/support it, so just // toggle between maximize/restore states. - onMaximizeOrRestore(decoration.mTaskInfo.taskId, "caption_bar_button", - ResizeTrigger.MAXIMIZE_BUTTON, mMotionEvent); + onToggleSizeInteraction(decoration.mTaskInfo.taskId, + ToggleTaskSizeInteraction.AmbiguousSource.HEADER_BUTTON, mMotionEvent); } } else if (id == R.id.minimize_window) { mDesktopTasksController.minimizeTask(decoration.mTaskInfo); @@ -937,8 +989,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, } if (mInputManager != null && !Flags.enableAccessibleCustomHeaders()) { - // Pilfer so that windows below receive cancellations for this gesture. - mInputManager.pilferPointers(v.getViewRootImpl().getInputToken()); + ViewRootImpl viewRootImpl = v.getViewRootImpl(); + if (viewRootImpl != null) { + // Pilfer so that windows below receive cancellations for this gesture. + mInputManager.pilferPointers(viewRootImpl.getInputToken()); + } } if (isUpOrCancel) { // Gesture is finished, reset state. @@ -961,7 +1016,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, if (decoration.isMaximizeMenuActive()) { decoration.closeMaximizeMenu(); } else { - mHasLongClicked = true; mDesktopModeUiEventLogger.log(decoration.mTaskInfo, DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_REVEAL_MENU); decoration.createMaximizeMenu(); @@ -1000,6 +1054,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private void moveTaskToFront(RunningTaskInfo taskInfo) { if (!mFocusTransitionObserver.hasGlobalFocus(taskInfo)) { + mDesktopModeUiEventLogger.log(taskInfo, + DesktopUiEventEnum.DESKTOP_WINDOW_HEADER_TAP_TO_REFOCUS); mDesktopTasksController.moveTaskToFront(taskInfo); } } @@ -1056,8 +1112,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, } final boolean touchingButton = (id == R.id.close_window || id == R.id.maximize_window || id == R.id.open_menu_button || id == R.id.minimize_window); + final DesktopRepository desktopRepository = mDesktopUserRepositories.getProfile( + taskInfo.userId); final boolean dragAllowed = - !mDesktopRepository.isTaskInFullImmersiveState(taskInfo.taskId); + !desktopRepository.isTaskInFullImmersiveState(taskInfo.taskId); switch (e.getActionMasked()) { case MotionEvent.ACTION_DOWN: { if (dragAllowed) { @@ -1068,7 +1126,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, updateDragStatus(e.getActionMasked()); mOnDragStartInitialBounds.set(initialBounds); } - mHasLongClicked = false; // Do not consume input event if a button is touched, otherwise it would // prevent the button's ripple effect from showing. return !touchingButton; @@ -1102,6 +1159,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, if (!wasDragging) { return false; } + mDesktopModeUiEventLogger.log(taskInfo, + DesktopUiEventEnum.DESKTOP_WINDOW_MOVE_BY_HEADER_DRAG); if (e.findPointerIndex(mDragPointerId) == -1) { mDragPointerId = e.getPointerId(0); } @@ -1123,7 +1182,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, newTaskBounds, decoration.calculateValidDragArea(), new Rect(mOnDragStartInitialBounds), e, mWindowDecorByTaskId.get(taskInfo.taskId)); - if (touchingButton && !mHasLongClicked) { + if (touchingButton) { // We need the input event to not be consumed here to end the ripple // effect on the touched button. We will reset drag state in the ensuing // onClick call that results. @@ -1165,11 +1224,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, && action != MotionEvent.ACTION_CANCEL)) { return false; } - if (mDesktopRepository.isTaskInFullImmersiveState(mTaskId)) { + final DesktopRepository desktopRepository = mDesktopUserRepositories.getCurrent(); + if (desktopRepository.isTaskInFullImmersiveState(mTaskId)) { // Disallow double-tap to resize when in full immersive. return false; } - onMaximizeOrRestore(mTaskId, "double_tap", ResizeTrigger.DOUBLE_TAP_APP_HEADER, e); + onToggleSizeInteraction(mTaskId, + ToggleTaskSizeInteraction.AmbiguousSource.DOUBLE_TAP, e); return true; } } @@ -1573,11 +1634,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, } final DesktopModeWindowDecoration windowDecoration = mDesktopModeWindowDecorFactory.create( - mContext, + Flags.enableBugFixesForSecondaryDisplay() + ? mDisplayController.getDisplayContext(taskInfo.displayId) + : mContext, mContext.createContextAsUser(UserHandle.of(taskInfo.userId), 0 /* flags */), mDisplayController, mSplitScreenController, - mDesktopRepository, + mDesktopUserRepositories, mTaskOrganizer, taskInfo, taskSurface, @@ -1589,6 +1652,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, mRootTaskDisplayAreaOrganizer, mGenericLinksParser, mAssistContentRequester, + mWindowDecorViewHostSupplier, mMultiInstanceHelper, mWindowDecorCaptionHandleRepository, mDesktopModeEventLogger); @@ -1608,12 +1672,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, final DesktopModeTouchEventListener touchEventListener = new DesktopModeTouchEventListener(taskInfo, taskPositioner); windowDecoration.setOnMaximizeOrRestoreClickListener(() -> { - onMaximizeOrRestore(taskInfo.taskId, "maximize_menu", ResizeTrigger.MAXIMIZE_MENU, + onToggleSizeInteraction(taskInfo.taskId, + ToggleTaskSizeInteraction.AmbiguousSource.MAXIMIZE_MENU, touchEventListener.mMotionEvent); return Unit.INSTANCE; }); windowDecoration.setOnImmersiveOrRestoreClickListener(() -> { - onEnterOrExitImmersive(taskInfo.taskId); + onEnterOrExitImmersive(taskInfo); return Unit.INSTANCE; }); windowDecoration.setOnLeftSnapClickListener(() -> { @@ -1767,11 +1832,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, // TODO(b/336289597): Rather than returning number of instances, return a list of valid // instances, then refer to the list's size and reuse the list for Manage Windows menu. final IActivityTaskManager activityTaskManager = ActivityTaskManager.getService(); - final IActivityManager activityManager = ActivityManager.getService(); try { return activityTaskManager.getRecentTasks(Integer.MAX_VALUE, ActivityManager.RECENT_WITH_EXCLUDED, - activityManager.getCurrentUserId()).getList().stream().filter( + info.userId).getList().stream().filter( recentTaskInfo -> (recentTaskInfo.taskId != info.taskId && recentTaskInfo.baseActivity != null && recentTaskInfo.baseActivity.getPackageName() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 96cc559a64ae..6562f38e724d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -43,7 +43,6 @@ import static com.android.wm.shell.windowdecor.DragPositioningCallbackUtility.Dr import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.WindowConfiguration.WindowingMode; import android.app.assist.AssistContent; @@ -99,13 +98,15 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.desktopmode.CaptionState; import com.android.wm.shell.desktopmode.DesktopModeEventLogger; -import com.android.wm.shell.desktopmode.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource; import com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer; import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost; +import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder; import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder; @@ -128,7 +129,6 @@ import java.util.function.Supplier; */ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> { private static final String TAG = "DesktopModeWindowDecoration"; - private static final int CAPTURED_LINK_TIMEOUT_MS = 7000; @VisibleForTesting static final long CLOSE_MAXIMIZE_MENU_DELAY_MS = 150L; @@ -158,14 +158,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private Function0<Unit> mOnMaximizeHoverListener; private DragPositioningCallback mDragPositioningCallback; private DragResizeInputListener mDragResizeListener; - private Runnable mCurrentViewHostRunnable = null; private RelayoutParams mRelayoutParams = new RelayoutParams(); private DisabledEdge mDisabledResizingEdge = NONE; private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult = new WindowDecoration.RelayoutResult<>(); - private final Runnable mViewHostRunnable = - () -> updateViewHost(mRelayoutParams, null /* onDrawTransaction */, mResult); private final Point mPositionInParent = new Point(); private HandleMenu mHandleMenu; @@ -203,17 +200,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin // being hovered. There's a small delay after stopping the hover, to allow a quick reentry // to cancel the close. private final Runnable mCloseMaximizeWindowRunnable = this::closeMaximizeMenu; - private final Runnable mCapturedLinkExpiredRunnable = this::onCapturedLinkExpired; private final MultiInstanceHelper mMultiInstanceHelper; private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository; - private final DesktopRepository mDesktopRepository; + private final DesktopUserRepositories mDesktopUserRepositories; public DesktopModeWindowDecoration( Context context, @NonNull Context userContext, DisplayController displayController, SplitScreenController splitScreenController, - DesktopRepository desktopRepository, + DesktopUserRepositories desktopUserRepositories, ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, @@ -225,17 +221,19 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, AppToWebGenericLinksParser genericLinksParser, AssistContentRequester assistContentRequester, + @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier, MultiInstanceHelper multiInstanceHelper, WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository, DesktopModeEventLogger desktopModeEventLogger) { - this (context, userContext, displayController, splitScreenController, desktopRepository, - taskOrganizer, taskInfo, taskSurface, handler, bgExecutor, choreographer, syncQueue, - appHeaderViewHolderFactory, rootTaskDisplayAreaOrganizer, genericLinksParser, - assistContentRequester, + this (context, userContext, displayController, splitScreenController, + desktopUserRepositories, taskOrganizer, taskInfo, taskSurface, handler, + bgExecutor, choreographer, syncQueue, appHeaderViewHolderFactory, + rootTaskDisplayAreaOrganizer, genericLinksParser, assistContentRequester, SurfaceControl.Builder::new, SurfaceControl.Transaction::new, WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper( context.getSystemService(WindowManager.class)), new SurfaceControlViewHostFactory() {}, + windowDecorViewHostSupplier, DefaultMaximizeMenuFactory.INSTANCE, DefaultHandleMenuFactory.INSTANCE, multiInstanceHelper, windowDecorCaptionHandleRepository, desktopModeEventLogger); @@ -246,7 +244,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin @NonNull Context userContext, DisplayController displayController, SplitScreenController splitScreenController, - DesktopRepository desktopRepository, + DesktopUserRepositories desktopUserRepositories, ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, @@ -264,15 +262,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin Supplier<SurfaceControl> surfaceControlSupplier, WindowManagerWrapper windowManagerWrapper, SurfaceControlViewHostFactory surfaceControlViewHostFactory, + @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier, MaximizeMenuFactory maximizeMenuFactory, HandleMenuFactory handleMenuFactory, MultiInstanceHelper multiInstanceHelper, WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository, DesktopModeEventLogger desktopModeEventLogger) { - super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface, - surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, + super(context, userContext, displayController, taskOrganizer, taskInfo, + taskSurface, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, windowContainerTransactionSupplier, surfaceControlSupplier, - surfaceControlViewHostFactory, desktopModeEventLogger); + surfaceControlViewHostFactory, windowDecorViewHostSupplier, desktopModeEventLogger); mSplitScreenController = splitScreenController; mHandler = handler; mBgExecutor = bgExecutor; @@ -287,7 +286,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mMultiInstanceHelper = multiInstanceHelper; mWindowManagerWrapper = windowManagerWrapper; mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository; - mDesktopRepository = desktopRepository; + mDesktopUserRepositories = desktopUserRepositories; } /** @@ -437,7 +436,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin public void updateDisabledResizingEdge( DragResizeWindowGeometry.DisabledEdge disabledResizingEdge, boolean shouldDelayUpdate) { mDisabledResizingEdge = disabledResizingEdge; - final boolean inFullImmersive = mDesktopRepository + final boolean inFullImmersive = mDesktopUserRepositories.getCurrent() .isTaskInFullImmersiveState(mTaskInfo.taskId); if (shouldDelayUpdate) { return; @@ -451,78 +450,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop, boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) { Trace.beginSection("DesktopModeWindowDecoration#relayout"); - if (taskInfo.isFreeform()) { - // The Task is in Freeform mode -> show its header in sync since it's an integral part - // of the window itself - a delayed header might cause bad UX. - relayoutInSync(taskInfo, startT, finishT, applyStartTransactionOnDraw, - shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus, displayExclusionRegion); - } else { - // The Task is outside Freeform mode -> allow the handle view to be delayed since the - // handle is just a small addition to the window. - relayoutWithDelayedViewHost(taskInfo, startT, finishT, applyStartTransactionOnDraw, - shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus, displayExclusionRegion); - } - Trace.endSection(); - } - - /** Run the whole relayout phase immediately without delay. */ - private void relayoutInSync(ActivityManager.RunningTaskInfo taskInfo, - SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, - boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop, - boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) { - // Clear the current ViewHost runnable as we will update the ViewHost here - clearCurrentViewHostRunnable(); - updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT, applyStartTransactionOnDraw, - shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus, displayExclusionRegion); - if (mResult.mRootView != null) { - updateViewHost(mRelayoutParams, startT, mResult); - } - } - /** - * Clear the current ViewHost runnable - to ensure it doesn't run once relayout params have been - * updated. - */ - private void clearCurrentViewHostRunnable() { - if (mCurrentViewHostRunnable != null) { - mHandler.removeCallbacks(mCurrentViewHostRunnable); - mCurrentViewHostRunnable = null; - } - } - - /** - * Relayout the window decoration but repost some of the work, to unblock the current callstack. - */ - private void relayoutWithDelayedViewHost(ActivityManager.RunningTaskInfo taskInfo, - SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, - boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop, - boolean hasGlobalFocus, - @NonNull Region displayExclusionRegion) { - if (applyStartTransactionOnDraw) { - throw new IllegalArgumentException( - "We cannot both sync viewhost ondraw and delay viewhost creation."); - } - // Clear the current ViewHost runnable as we will update the ViewHost here - clearCurrentViewHostRunnable(); - updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT, - false /* applyStartTransactionOnDraw */, shouldSetTaskVisibilityPositionAndCrop, - hasGlobalFocus, displayExclusionRegion); - if (mResult.mRootView == null) { - // This means something blocks the window decor from showing, e.g. the task is hidden. - // Nothing is set up in this case including the decoration surface. - return; - } - // Store the current runnable so it can be removed if we start a new relayout. - mCurrentViewHostRunnable = mViewHostRunnable; - mHandler.post(mCurrentViewHostRunnable); - } - - @SuppressLint("MissingPermission") - private void updateRelayoutParamsAndSurfaces(ActivityManager.RunningTaskInfo taskInfo, - SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, - boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop, - boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) { - Trace.beginSection("DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces"); if (Flags.enableDesktopWindowingAppToWeb()) { setCapturedLink(taskInfo.capturedLink, taskInfo.capturedLinkTimestamp); } @@ -541,11 +469,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mOpenByDefaultDialog.relayout(taskInfo); } - final boolean inFullImmersive = mDesktopRepository + final boolean inFullImmersive = mDesktopUserRepositories.getProfile(taskInfo.userId) .isTaskInFullImmersiveState(taskInfo.taskId); - updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw, - shouldSetTaskVisibilityPositionAndCrop, mIsStatusBarVisible, - mIsKeyguardVisibleAndOccluded, inFullImmersive, + updateRelayoutParams(mRelayoutParams, mContext, taskInfo, mSplitScreenController, + applyStartTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop, + mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded, inFullImmersive, mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus, displayExclusionRegion); @@ -553,9 +481,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin final SurfaceControl oldDecorationSurface = mDecorationContainerSurface; final WindowContainerTransaction wct = new WindowContainerTransaction(); - Trace.beginSection("DesktopModeWindowDecoration#relayout-updateViewsAndSurfaces"); - updateViewsAndSurfaces(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); - Trace.endSection(); + relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo Trace.beginSection("DesktopModeWindowDecoration#relayout-applyWCT"); @@ -570,7 +496,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId); disposeStatusBarInputLayer(); - Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces + Trace.endSection(); // DesktopModeWindowDecoration#relayout return; } @@ -578,7 +504,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin disposeStatusBarInputLayer(); mWindowDecorViewHolder = createViewHolder(); } - Trace.beginSection("DesktopModeWindowDecoration#relayout-binding"); final Point position = new Point(); if (isAppHandle(mWindowDecorViewHolder)) { @@ -588,6 +513,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin notifyCaptionStateChanged(); } + Trace.beginSection("DesktopModeWindowDecoration#relayout-bindData"); if (isAppHandle(mWindowDecorViewHolder)) { mWindowDecorViewHolder.bindData(new AppHandleViewHolder.HandleData( mTaskInfo, position, mResult.mCaptionWidth, mResult.mCaptionHeight, @@ -612,7 +538,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } updateDragResizeListener(oldDecorationSurface, inFullImmersive); updateMaximizeMenu(startT, inFullImmersive); - Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces + Trace.endSection(); // DesktopModeWindowDecoration#relayout } private boolean isCaptionVisible() { @@ -625,22 +551,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin return; } mCapturedLink = new CapturedLink(capturedLink, timeStamp); - mHandler.postDelayed(mCapturedLinkExpiredRunnable, CAPTURED_LINK_TIMEOUT_MS); - } - - private void onCapturedLinkExpired() { - mHandler.removeCallbacks(mCapturedLinkExpiredRunnable); - if (mCapturedLink != null) { - mCapturedLink.setExpired(); - } } @Nullable private Intent getBrowserLink() { final Uri browserLink; - // If the captured link is available and has not expired, return the captured link. - // Otherwise, return the generic link which is set to null if a generic link is unavailable. - if (mCapturedLink != null && !mCapturedLink.mExpired) { + if (isCapturedLinkAvailable()) { browserLink = mCapturedLink.mUri; } else if (mWebUri != null) { browserLink = mWebUri; @@ -752,7 +668,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } private boolean isCapturedLinkAvailable() { - return mCapturedLink != null && !mCapturedLink.mExpired; + return mCapturedLink != null && !mCapturedLink.mUsed; + } + + private void onCapturedLinkUsed() { + if (mCapturedLink != null) { + mCapturedLink.setUsed(); + } } private void notifyNoCaptionHandle() { @@ -877,6 +799,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin RelayoutParams relayoutParams, Context context, ActivityManager.RunningTaskInfo taskInfo, + SplitScreenController splitScreenController, boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop, boolean isStatusBarVisible, @@ -896,6 +819,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin relayoutParams.mCaptionWidthId = getCaptionWidthId(relayoutParams.mLayoutResId); relayoutParams.mHasGlobalFocus = hasGlobalFocus; relayoutParams.mDisplayExclusionRegion.set(displayExclusionRegion); + // Allow the handle view to be delayed since the handle is just a small addition to the + // window, whereas the header cannot be delayed because it is expected to be visible from + // the first frame. + relayoutParams.mAsyncViewHost = isAppHandle; final boolean showCaption; if (Flags.enableFullyImmersiveInDesktop()) { @@ -918,7 +845,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin || (isStatusBarVisible && !isKeyguardVisibleAndOccluded); } relayoutParams.mIsCaptionVisible = showCaption; - relayoutParams.mIsInsetSource = isAppHeader && !inFullImmersiveMode; + final boolean isBottomSplit = !splitScreenController.isLeftRightSplit() + && splitScreenController.getSplitPosition(taskInfo.taskId) + == SPLIT_POSITION_BOTTOM_OR_RIGHT; + relayoutParams.mIsInsetSource = (isAppHeader && !inFullImmersiveMode) || isBottomSplit; if (isAppHeader) { if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) { // The app is requesting to customize the caption bar, which means input on @@ -980,10 +910,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; } - if (DesktopModeStatus.useWindowShadow(/* isFocusedWindow= */ hasGlobalFocus)) { - relayoutParams.mShadowRadiusId = hasGlobalFocus - ? R.dimen.freeform_decor_shadow_focused_thickness - : R.dimen.freeform_decor_shadow_unfocused_thickness; + if (isAppHeader + && DesktopModeStatus.useWindowShadow(/* isFocusedWindow= */ hasGlobalFocus)) { + relayoutParams.mShadowRadius = hasGlobalFocus + ? context.getResources().getDimensionPixelSize( + R.dimen.freeform_decor_shadow_focused_thickness) + : context.getResources().getDimensionPixelSize( + R.dimen.freeform_decor_shadow_unfocused_thickness); + } else { + relayoutParams.mShadowRadius = INVALID_SHADOW_RADIUS; } relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; relayoutParams.mSetTaskVisibilityPositionAndCrop = shouldSetTaskVisibilityPositionAndCrop; @@ -1297,9 +1232,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mMaximizeMenu = mMaximizeMenuFactory.create(mSyncQueue, mRootTaskDisplayAreaOrganizer, mDisplayController, mTaskInfo, mContext, calculateMaximizeMenuPosition(menuWidth), mSurfaceControlTransactionSupplier); + mMaximizeMenu.show( /* isTaskInImmersiveMode= */ Flags.enableFullyImmersiveInDesktop() - && mDesktopRepository.isTaskInFullImmersiveState(mTaskInfo.taskId), + && mDesktopUserRepositories.getProfile(mTaskInfo.userId) + .isTaskInFullImmersiveState(mTaskInfo.taskId), /* menuWidth= */ menuWidth, /* showImmersiveOption= */ Flags.enableFullyImmersiveInDesktop() && TaskInfoKt.getRequestingImmersive(mTaskInfo), @@ -1389,7 +1326,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin && mMinimumInstancesFound; final boolean shouldShowChangeAspectRatioButton = HandleMenu.Companion .shouldShowChangeAspectRatioButton(mTaskInfo); - final boolean inDesktopImmersive = mDesktopRepository + final boolean inDesktopImmersive = mDesktopUserRepositories.getProfile(mTaskInfo.userId) .isTaskInFullImmersiveState(mTaskInfo.taskId); final boolean isBrowserApp = isBrowserApp(); mHandleMenu = mHandleMenuFactory.create( @@ -1427,8 +1364,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin /* onAspectRatioSettingsClickListener= */ mOnChangeAspectRatioClickListener, /* openInBrowserClickListener= */ (intent) -> { mOpenInBrowserClickListener.accept(intent); - onCapturedLinkExpired(); - if (Flags.enableDesktopWindowingAppToWebEducation()) { + onCapturedLinkUsed(); + if (Flags.enableDesktopWindowingAppToWebEducationIntegration()) { mWindowDecorCaptionHandleRepository.onAppToWebUsage(); } return Unit.INSTANCE; @@ -1469,7 +1406,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mDisplayController, mRootTaskDisplayAreaOrganizer, mContext, - mDesktopRepository, + mDesktopUserRepositories, mSurfaceControlBuilderSupplier, mSurfaceControlTransactionSupplier, snapshotList, @@ -1681,7 +1618,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin /** Returns true if at least one education flag is enabled. */ private boolean isEducationEnabled() { return Flags.enableDesktopWindowingAppHandleEducation() - || Flags.enableDesktopWindowingAppToWebEducation(); + || Flags.enableDesktopWindowingAppToWebEducationIntegration(); } @Override @@ -1692,7 +1629,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId); disposeResizeVeil(); disposeStatusBarInputLayer(); - clearCurrentViewHostRunnable(); if (canEnterDesktopMode(mContext) && isEducationEnabled()) { notifyNoCaptionHandle(); } @@ -1765,7 +1701,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin void setAnimatingTaskResizeOrReposition(boolean animatingTaskResizeOrReposition) { if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_handle) return; final boolean inFullImmersive = - mDesktopRepository.isTaskInFullImmersiveState(mTaskInfo.taskId); + mDesktopUserRepositories.getProfile(mTaskInfo.userId) + .isTaskInFullImmersiveState(mTaskInfo.taskId); asAppHeader(mWindowDecorViewHolder).bindData(new AppHeaderViewHolder.HeaderData( mTaskInfo, TaskInfoKt.getRequestingImmersive(mTaskInfo), @@ -1793,8 +1730,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin return !animatingTaskResizeOrReposition; } final boolean inImmersiveAndRequesting = - mDesktopRepository.isTaskInFullImmersiveState(mTaskInfo.taskId) - && TaskInfoKt.getRequestingImmersive(mTaskInfo); + mDesktopUserRepositories.getProfile(mTaskInfo.userId) + .isTaskInFullImmersiveState(mTaskInfo.taskId) + && TaskInfoKt.getRequestingImmersive(mTaskInfo); return !animatingTaskResizeOrReposition && !inImmersiveAndRequesting; } @@ -1815,7 +1753,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin @NonNull Context userContext, DisplayController displayController, SplitScreenController splitScreenController, - DesktopRepository desktopRepository, + DesktopUserRepositories desktopUserRepositories, ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, @@ -1827,6 +1765,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, AppToWebGenericLinksParser genericLinksParser, AssistContentRequester assistContentRequester, + @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> + windowDecorViewHostSupplier, MultiInstanceHelper multiInstanceHelper, WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository, DesktopModeEventLogger desktopModeEventLogger) { @@ -1835,7 +1775,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin userContext, displayController, splitScreenController, - desktopRepository, + desktopUserRepositories, taskOrganizer, taskInfo, taskSurface, @@ -1847,6 +1787,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin rootTaskDisplayAreaOrganizer, genericLinksParser, assistContentRequester, + windowDecorViewHostSupplier, multiInstanceHelper, windowDecorCaptionHandleRepository, desktopModeEventLogger); @@ -1857,16 +1798,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin static class CapturedLink { private final long mTimeStamp; private final Uri mUri; - private boolean mExpired; + private boolean mUsed; CapturedLink(@NonNull Uri uri, long timeStamp) { mUri = uri; mTimeStamp = timeStamp; - mExpired = false; } - void setExpired() { - mExpired = true; + private void setUsed() { + mUsed = true; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java index a1e329af1543..1f03d7568130 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java @@ -37,6 +37,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.jank.Cuj; import com.android.internal.jank.InteractionJankMonitor; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; @@ -44,6 +45,7 @@ import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.transition.Transitions; import java.util.ArrayList; +import java.util.concurrent.TimeUnit; import java.util.function.Supplier; /** @@ -53,6 +55,9 @@ import java.util.function.Supplier; * If the drag is repositioning, we update in the typical manner. */ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.TransitionHandler { + // Timeout used for resize and drag CUJs, this is longer than the default timeout to avoid + // timing out in the middle of a resize or drag action. + private static final long LONG_CUJ_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10L); private DesktopModeWindowDecoration mDesktopWindowDecoration; private ShellTaskOrganizer mTaskOrganizer; @@ -106,8 +111,8 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T mRepositionStartPoint.set(x, y); if (isResizing()) { // Capture CUJ for re-sizing window in DW mode. - mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface, - mDesktopWindowDecoration.mContext, mHandler, CUJ_DESKTOP_MODE_RESIZE_WINDOW); + mInteractionJankMonitor.begin( + createLongTimeoutJankConfigBuilder(CUJ_DESKTOP_MODE_RESIZE_WINDOW)); if (!mDesktopWindowDecoration.mHasGlobalFocus) { WindowContainerTransaction wct = new WindowContainerTransaction(); wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true /* onTop */, @@ -153,8 +158,8 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T } } else if (mCtrlType == CTRL_TYPE_UNDEFINED) { // Begin window drag CUJ instrumentation only when drag position moves. - mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface, - mDesktopWindowDecoration.mContext, mHandler, CUJ_DESKTOP_MODE_DRAG_WINDOW); + mInteractionJankMonitor.begin( + createLongTimeoutJankConfigBuilder(CUJ_DESKTOP_MODE_DRAG_WINDOW)); final SurfaceControl.Transaction t = mTransactionSupplier.get(); DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration, mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y); @@ -207,6 +212,14 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T } } + private InteractionJankMonitor.Configuration.Builder createLongTimeoutJankConfigBuilder( + @Cuj.CujType int cujType) { + return InteractionJankMonitor.Configuration.Builder + .withSurface(cujType, mDesktopWindowDecoration.mContext, + mDesktopWindowDecoration.mTaskSurface, mHandler) + .setTimeout(LONG_CUJ_TIMEOUT_MS); + } + @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index 852eee5f6672..5d1bedb85b5e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -17,7 +17,6 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED; import static android.view.WindowInsets.Type.captionBar; import static android.view.WindowInsets.Type.mandatorySystemGestures; @@ -62,6 +61,8 @@ import com.android.wm.shell.desktopmode.DesktopModeEventLogger; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement; import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer; +import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost; +import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier; import com.android.wm.shell.windowdecor.extension.InsetsStateKt; import java.util.ArrayList; @@ -89,10 +90,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> implements AutoCloseable { /** - * The Z-order of {@link #mCaptionContainerSurface}. + * The Z-order of the caption surface. * <p> * We use {@link #mDecorationContainerSurface} to define input window for task resizing; by - * layering it in front of {@link #mCaptionContainerSurface}, we can allow it to handle input + * layering it in front of the caption surface, we can allow it to handle input * prior to caption view itself, treating corner inputs as resize events rather than * repositioning. */ @@ -101,7 +102,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> * The Z-order of the task input sink in {@link DragPositioningCallback}. * <p> * This task input sink is used to prevent undesired dispatching of motion events out of task - * bounds; by layering it behind {@link #mCaptionContainerSurface}, we allow captions to handle + * bounds; by layering it behind the caption surface, we allow captions to handle * input events first. */ static final int INPUT_SINK_Z_ORDER = -2; @@ -110,6 +111,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> * Invalid corner radius that signifies that corner radius should not be set. */ static final int INVALID_CORNER_RADIUS = -1; + /** + * Invalid corner radius that signifies that shadow radius should not be set. + */ + static final int INVALID_SHADOW_RADIUS = -1; /** * System-wide context. Only used to create context with overridden configurations. @@ -123,6 +128,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier; final Supplier<WindowContainerTransaction> mWindowContainerTransactionSupplier; final SurfaceControlViewHostFactory mSurfaceControlViewHostFactory; + @NonNull private final WindowDecorViewHostSupplier<WindowDecorViewHost> + mWindowDecorViewHostSupplier; private final DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener = new DisplayController.OnDisplaysChangedListener() { @Override @@ -147,9 +154,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> Display mDisplay; SurfaceControl mDecorationContainerSurface; - SurfaceControl mCaptionContainerSurface; - private CaptionWindowlessWindowManager mCaptionWindowManager; - private SurfaceControlViewHost mViewHost; + private WindowDecorViewHost mViewHost; private Configuration mWindowDecorConfig; TaskDragResizer mTaskDragResizer; boolean mIsCaptionVisible; @@ -170,11 +175,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> DisplayController displayController, ShellTaskOrganizer taskOrganizer, RunningTaskInfo taskInfo, - SurfaceControl taskSurface) { - this(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface, - SurfaceControl.Builder::new, SurfaceControl.Transaction::new, + SurfaceControl taskSurface, + @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier) { + this(context, userContext, displayController, taskOrganizer, taskInfo, + taskSurface, SurfaceControl.Builder::new, SurfaceControl.Transaction::new, WindowContainerTransaction::new, SurfaceControl::new, - new SurfaceControlViewHostFactory() {}, new DesktopModeEventLogger()); + new SurfaceControlViewHostFactory() {}, windowDecorViewHostSupplier, + new DesktopModeEventLogger()); } WindowDecoration( @@ -189,6 +196,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> Supplier<WindowContainerTransaction> windowContainerTransactionSupplier, Supplier<SurfaceControl> surfaceControlSupplier, SurfaceControlViewHostFactory surfaceControlViewHostFactory, + @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier, @NonNull DesktopModeEventLogger desktopModeEventLogger ) { mContext = context; @@ -202,6 +210,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier; mWindowContainerTransactionSupplier = windowContainerTransactionSupplier; mSurfaceControlViewHostFactory = surfaceControlViewHostFactory; + mWindowDecorViewHostSupplier = windowDecorViewHostSupplier; mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId); final InsetsState insetsState = mDisplayController.getInsetsState(mTaskInfo.displayId); mIsStatusBarVisible = insetsState != null @@ -237,15 +246,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> void relayout(RelayoutParams params, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView, RelayoutResult<T> outResult) { - updateViewsAndSurfaces(params, startT, finishT, wct, rootView, outResult); - if (outResult.mRootView != null) { - updateViewHost(params, startT, outResult); - } - } - - protected void updateViewsAndSurfaces(RelayoutParams params, - SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, - WindowContainerTransaction wct, T rootView, RelayoutResult<T> outResult) { + Trace.beginSection("WindowDecoration#relayout"); outResult.reset(); if (params.mRunningTaskInfo != null) { mTaskInfo = params.mRunningTaskInfo; @@ -260,17 +261,21 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> if (params.mSetTaskVisibilityPositionAndCrop) { finishT.hide(mTaskSurface); } + Trace.endSection(); // WindowDecoration#relayout return; } - + Trace.beginSection("WindowDecoration#relayout-inflateIfNeeded"); inflateIfNeeded(params, wct, rootView, oldLayoutResId, outResult); - if (outResult.mRootView == null) { - // Didn't manage to create a root view, early out. + Trace.endSection(); + final boolean hasCaptionView = outResult.mRootView != null; + if (!hasCaptionView) { + Trace.endSection(); // WindowDecoration#relayout return; } - rootView = null; // Clear it just in case we use it accidentally + Trace.beginSection("WindowDecoration#relayout-updateCaptionVisibility"); updateCaptionVisibility(outResult.mRootView, params); + Trace.endSection(); final Rect taskBounds = mTaskInfo.getConfiguration().windowConfiguration.getBounds(); outResult.mWidth = taskBounds.width(); @@ -285,10 +290,65 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> outResult.mCaptionY = 0; outResult.mCaptionTopPadding = params.mCaptionTopPadding; + Trace.beginSection("relayout-createViewHostIfNeeded"); + createViewHostIfNeeded(mDecorWindowContext, mDisplay); + Trace.endSection(); + + Trace.beginSection("WindowDecoration#relayout-updateSurfacesAndInsets"); + final SurfaceControl captionSurface = mViewHost.getSurfaceControl(); updateDecorationContainerSurface(startT, outResult); - updateCaptionContainerSurface(startT, outResult); + updateCaptionContainerSurface(captionSurface, startT, outResult); updateCaptionInsets(params, wct, outResult, taskBounds); updateTaskSurface(params, startT, finishT, outResult); + Trace.endSection(); + + Trace.beginSection("WindowDecoration#relayout-updateViewHost"); + outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0); + final Rect localCaptionBounds = new Rect( + outResult.mCaptionX, + outResult.mCaptionY, + outResult.mCaptionX + outResult.mCaptionWidth, + outResult.mCaptionY + outResult.mCaptionHeight); + final Region touchableRegion = params.mLimitTouchRegionToSystemAreas + ? calculateLimitedTouchableRegion(params, localCaptionBounds) + : null; + updateViewHierarchy(params, outResult, startT, touchableRegion); + Trace.endSection(); + + Trace.endSection(); // WindowDecoration#relayout + } + + private void createViewHostIfNeeded(@NonNull Context context, @NonNull Display display) { + if (mViewHost == null) { + mViewHost = mWindowDecorViewHostSupplier.acquire(context, display); + } + } + + private void updateViewHierarchy(@NonNull RelayoutParams params, + @NonNull RelayoutResult<T> outResult, @NonNull SurfaceControl.Transaction startT, + @Nullable Region touchableRegion) { + Trace.beginSection("WindowDecoration#updateViewHierarchy"); + final WindowManager.LayoutParams lp = + new WindowManager.LayoutParams( + outResult.mCaptionWidth, + outResult.mCaptionHeight, + TYPE_APPLICATION, + FLAG_NOT_FOCUSABLE | FLAG_SPLIT_TOUCH, + PixelFormat.TRANSPARENT); + lp.setTitle("Caption of Task=" + mTaskInfo.taskId); + lp.setTrustedOverlay(); + lp.inputFeatures = params.mInputFeatures; + if (params.mAsyncViewHost) { + if (params.mApplyStartTransactionOnDraw) { + throw new IllegalArgumentException("Cannot use sync draw tx with async relayout"); + } + mViewHost.updateViewAsync(outResult.mRootView, lp, mTaskInfo.configuration, + touchableRegion); + } else { + mViewHost.updateView(outResult.mRootView, lp, mTaskInfo.configuration, + touchableRegion, params.mApplyStartTransactionOnDraw ? startT : null); + } + Trace.endSection(); } private void inflateIfNeeded(RelayoutParams params, WindowContainerTransaction wct, @@ -356,23 +416,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .show(mDecorationContainerSurface); } - private void updateCaptionContainerSurface( + private void updateCaptionContainerSurface(@NonNull SurfaceControl captionSurface, SurfaceControl.Transaction startT, RelayoutResult<T> outResult) { - if (mCaptionContainerSurface == null) { - final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get(); - mCaptionContainerSurface = builder - .setName("Caption container of Task=" + mTaskInfo.taskId) - .setContainerLayer() - .setParent(mDecorationContainerSurface) - .setCallsite("WindowDecoration.updateCaptionContainerSurface") - .build(); - } - - startT.setWindowCrop(mCaptionContainerSurface, outResult.mCaptionWidth, - outResult.mCaptionHeight) - .setPosition(mCaptionContainerSurface, outResult.mCaptionX, 0 /* y */) - .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER) - .show(mCaptionContainerSurface); + startT.reparent(captionSurface, mDecorationContainerSurface) + .setWindowCrop(captionSurface, outResult.mCaptionWidth, outResult.mCaptionHeight) + .setPosition(captionSurface, outResult.mCaptionX, 0 /* y */) + .setLayer(captionSurface, CAPTION_LAYER_Z_ORDER) + .show(captionSurface); } private void updateCaptionInsets(RelayoutParams params, WindowContainerTransaction wct, @@ -439,16 +489,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .setPosition(mTaskSurface, taskPosition.x, taskPosition.y); } - float shadowRadius; - if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { - // Shadow is not needed for fullscreen tasks - shadowRadius = 0; - } else { - shadowRadius = - loadDimension(mDecorWindowContext.getResources(), params.mShadowRadiusId); + if (params.mShadowRadius != INVALID_SHADOW_RADIUS) { + startT.setShadowRadius(mTaskSurface, params.mShadowRadius); + finishT.setShadowRadius(mTaskSurface, params.mShadowRadius); } - startT.setShadowRadius(mTaskSurface, shadowRadius); - finishT.setShadowRadius(mTaskSurface, shadowRadius); if (params.mSetTaskVisibilityPositionAndCrop) { startT.show(mTaskSurface); @@ -472,80 +516,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } } - /** - * Updates a {@link SurfaceControlViewHost} to connect the window decoration surfaces with our - * View hierarchy. - * - * @param params parameters to use from the last relayout - * @param onDrawTransaction a transaction to apply in sync with #onDraw - * @param outResult results to use from the last relayout - * - */ - protected void updateViewHost(RelayoutParams params, - SurfaceControl.Transaction onDrawTransaction, RelayoutResult<T> outResult) { - Trace.beginSection("CaptionViewHostLayout"); - if (mCaptionWindowManager == null) { - // Put caption under a container surface because ViewRootImpl sets the destination frame - // of windowless window layers and BLASTBufferQueue#update() doesn't support offset. - mCaptionWindowManager = new CaptionWindowlessWindowManager( - mTaskInfo.getConfiguration(), mCaptionContainerSurface); - } - mCaptionWindowManager.setConfiguration(mTaskInfo.getConfiguration()); - final WindowManager.LayoutParams lp = - new WindowManager.LayoutParams( - outResult.mCaptionWidth, - outResult.mCaptionHeight, - TYPE_APPLICATION, - FLAG_NOT_FOCUSABLE | FLAG_SPLIT_TOUCH, - PixelFormat.TRANSPARENT); - lp.setTitle("Caption of Task=" + mTaskInfo.taskId); - lp.setTrustedOverlay(); - lp.inputFeatures = params.mInputFeatures; - final Rect localCaptionBounds = new Rect( - outResult.mCaptionX, - outResult.mCaptionY, - outResult.mCaptionX + outResult.mCaptionWidth, - outResult.mCaptionY + outResult.mCaptionHeight); - final Region touchableRegion = params.mLimitTouchRegionToSystemAreas - ? calculateLimitedTouchableRegion(params, localCaptionBounds) - : null; - if (mViewHost == null) { - Trace.beginSection("CaptionViewHostLayout-new"); - mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay, - mCaptionWindowManager); - if (params.mApplyStartTransactionOnDraw) { - if (onDrawTransaction == null) { - throw new IllegalArgumentException("Trying to sync a null Transaction"); - } - mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction); - } - outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0); - if (params.mLimitTouchRegionToSystemAreas) { - mCaptionWindowManager.setTouchRegion(mViewHost, touchableRegion); - } - mViewHost.setView(outResult.mRootView, lp); - Trace.endSection(); - } else { - Trace.beginSection("CaptionViewHostLayout-relayout"); - if (params.mApplyStartTransactionOnDraw) { - if (onDrawTransaction == null) { - throw new IllegalArgumentException("Trying to sync a null Transaction"); - } - mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction); - } - outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0); - if (params.mLimitTouchRegionToSystemAreas) { - mCaptionWindowManager.setTouchRegion(mViewHost, touchableRegion); - } - mViewHost.relayout(lp); - Trace.endSection(); - } - if (touchableRegion != null) { - touchableRegion.recycle(); - } - Trace.endSection(); // CaptionViewHostLayout - } - @NonNull private Region calculateLimitedTouchableRegion( RelayoutParams params, @@ -694,18 +664,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } void releaseViews(WindowContainerTransaction wct) { - if (mViewHost != null) { - mViewHost.release(); - mViewHost = null; - } - - mCaptionWindowManager = null; - final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); boolean released = false; - if (mCaptionContainerSurface != null) { - t.remove(mCaptionContainerSurface); - mCaptionContainerSurface = null; + if (mViewHost != null) { + mWindowDecorViewHostSupplier.release(mViewHost, t); + mViewHost = null; released = true; } @@ -851,13 +814,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> @InsetsSource.Flags int mInsetSourceFlags; final Region mDisplayExclusionRegion = Region.obtain(); - int mShadowRadiusId; - int mCornerRadius; + int mShadowRadius = INVALID_SHADOW_RADIUS; + int mCornerRadius = INVALID_CORNER_RADIUS; int mCaptionTopPadding; boolean mIsCaptionVisible; Configuration mWindowDecorConfig; + boolean mAsyncViewHost; boolean mApplyStartTransactionOnDraw; boolean mSetTaskVisibilityPositionAndCrop; @@ -874,8 +838,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mInsetSourceFlags = 0; mDisplayExclusionRegion.setEmpty(); - mShadowRadiusId = Resources.ID_NULL; - mCornerRadius = 0; + mShadowRadius = INVALID_SHADOW_RADIUS; + mCornerRadius = INVALID_SHADOW_RADIUS; mCaptionTopPadding = 0; mIsCaptionVisible = false; @@ -883,6 +847,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mApplyStartTransactionOnDraw = false; mSetTaskVisibilityPositionAndCrop = false; mWindowDecorConfig = null; + mAsyncViewHost = false; mHasGlobalFocus = false; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt new file mode 100644 index 000000000000..a205ac662ab1 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.windowdecor.common.viewhost + +import android.content.Context +import android.content.res.Configuration +import android.graphics.Region +import android.view.Display +import android.view.SurfaceControl +import android.view.View +import android.view.WindowManager +import androidx.tracing.Trace +import com.android.internal.annotations.VisibleForTesting +import com.android.wm.shell.shared.annotations.ShellMainThread +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +/** + * A default implementation of [WindowDecorViewHost] backed by a [SurfaceControlViewHostAdapter]. + * + * It supports asynchronously updating the view hierarchy using [updateViewAsync], in which + * case the update work will be posted on the [ShellMainThread] with no delay. + */ +class DefaultWindowDecorViewHost( + context: Context, + @ShellMainThread private val mainScope: CoroutineScope, + display: Display, + @VisibleForTesting val viewHostAdapter: SurfaceControlViewHostAdapter = + SurfaceControlViewHostAdapter(context, display), +) : WindowDecorViewHost { + private var currentUpdateJob: Job? = null + + override val surfaceControl: SurfaceControl + get() = viewHostAdapter.rootSurface + + override fun updateView( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + touchableRegion: Region?, + onDrawTransaction: SurfaceControl.Transaction?, + ) { + Trace.beginSection("DefaultWindowDecorViewHost#updateView") + clearCurrentUpdateJob() + updateViewHost(view, attrs, configuration, touchableRegion, onDrawTransaction) + Trace.endSection() + } + + override fun updateViewAsync( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + touchableRegion: Region?, + ) { + Trace.beginSection("DefaultWindowDecorViewHost#updateViewAsync") + clearCurrentUpdateJob() + currentUpdateJob = + mainScope.launch { + updateViewHost( + view, + attrs, + configuration, + touchableRegion, + onDrawTransaction = null + ) + } + Trace.endSection() + } + + override fun release(t: SurfaceControl.Transaction) { + clearCurrentUpdateJob() + viewHostAdapter.release(t) + } + + private fun updateViewHost( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + touchableRegion: Region?, + onDrawTransaction: SurfaceControl.Transaction?, + ) { + Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost") + viewHostAdapter.prepareViewHost(configuration, touchableRegion) + onDrawTransaction?.let { + viewHostAdapter.applyTransactionOnDraw(it) + } + viewHostAdapter.updateView(view, attrs) + Trace.endSection() + } + + private fun clearCurrentUpdateJob() { + currentUpdateJob?.cancel() + currentUpdateJob = null + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt new file mode 100644 index 000000000000..7821619e61d7 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.windowdecor.common.viewhost + +import android.content.Context +import android.view.Display +import android.view.SurfaceControl +import com.android.wm.shell.shared.annotations.ShellMainThread +import kotlinx.coroutines.CoroutineScope + +/** + * A supplier of [DefaultWindowDecorViewHost]s. It creates a new one every time one is requested. + */ +class DefaultWindowDecorViewHostSupplier( + @ShellMainThread private val mainScope: CoroutineScope +) : WindowDecorViewHostSupplier<WindowDecorViewHost> { + + override fun acquire(context: Context, display: Display): WindowDecorViewHost { + return DefaultWindowDecorViewHost(context, mainScope, display) + } + + override fun release(viewHost: WindowDecorViewHost, t: SurfaceControl.Transaction) { + viewHost.release(t) + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplier.kt new file mode 100644 index 000000000000..adb0ba643e0d --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplier.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.windowdecor.common.viewhost + +import android.content.Context +import android.os.Trace +import android.util.Pools +import android.view.Display +import android.view.SurfaceControl +import com.android.wm.shell.shared.annotations.ShellMainThread +import kotlinx.coroutines.CoroutineScope + +/** + * A [WindowDecorViewHostSupplier] backed by a pool to allow recycling view hosts which may be + * expensive to recreate for each new or updated window decoration. + * + * Callers can obtain a [WindowDecorViewHost] using [acquire], which will return a pooled + * object if available, or create a new instance and return it if needed. When finished using a + * [WindowDecorViewHost], it must be released using [release] to allow it to be sent back + * into the pool and reused later on. + */ +class PooledWindowDecorViewHostSupplier( + @ShellMainThread private val mainScope: CoroutineScope, + maxPoolSize: Int, +) : WindowDecorViewHostSupplier<WindowDecorViewHost> { + + private val pool: Pools.Pool<WindowDecorViewHost> = Pools.SynchronizedPool(maxPoolSize) + private var nextDecorViewHostId = 0 + + override fun acquire(context: Context, display: Display): WindowDecorViewHost { + val pooledViewHost = pool.acquire() + if (pooledViewHost != null) { + return pooledViewHost + } + Trace.beginSection("PooledWindowDecorViewHostSupplier#acquire-newInstance") + val newDecorViewHost = newInstance(context, display) + Trace.endSection() + return newDecorViewHost + } + + override fun release(viewHost: WindowDecorViewHost, t: SurfaceControl.Transaction) { + val pooled = pool.release(viewHost) + if (!pooled) { + viewHost.release(t) + } + } + + private fun newInstance(context: Context, display: Display): ReusableWindowDecorViewHost { + // Use a reusable window decor view host, as it allows swapping the entire view hierarchy. + return ReusableWindowDecorViewHost( + context = context, + mainScope = mainScope, + display = display, + id = nextDecorViewHostId++ + ) + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt new file mode 100644 index 000000000000..bf0b1186254f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.windowdecor.common.viewhost + +import android.content.Context +import android.content.res.Configuration +import android.graphics.Region +import android.view.Display +import android.view.SurfaceControl +import android.view.View +import android.view.WindowManager +import android.widget.FrameLayout +import androidx.tracing.Trace +import com.android.internal.annotations.VisibleForTesting +import com.android.wm.shell.shared.annotations.ShellMainThread +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +/** + * An implementation of [WindowDecorViewHost] that supports: + * 1) Replacing the root [View], meaning [WindowDecorViewHost.updateView] maybe be called with + * different [View] instances. This is useful when reusing [WindowDecorViewHost]s instances for + * vastly different view hierarchies, such as Desktop Windowing's App Handles and App Headers. + */ +class ReusableWindowDecorViewHost( + private val context: Context, + @ShellMainThread private val mainScope: CoroutineScope, + display: Display, + val id: Int, + @VisibleForTesting + val viewHostAdapter: SurfaceControlViewHostAdapter = + SurfaceControlViewHostAdapter(context, display), +) : WindowDecorViewHost { + @VisibleForTesting val rootView = FrameLayout(context) + + private var currentUpdateJob: Job? = null + + override val surfaceControl: SurfaceControl + get() = viewHostAdapter.rootSurface + + override fun updateView( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + touchableRegion: Region?, + onDrawTransaction: SurfaceControl.Transaction?, + ) { + Trace.beginSection("ReusableWindowDecorViewHost#updateView") + clearCurrentUpdateJob() + updateViewHost(view, attrs, configuration, touchableRegion, onDrawTransaction) + Trace.endSection() + } + + override fun updateViewAsync( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + touchableRegion: Region?, + ) { + Trace.beginSection("ReusableWindowDecorViewHost#updateViewAsync") + clearCurrentUpdateJob() + currentUpdateJob = + mainScope.launch { + updateViewHost( + view, + attrs, + configuration, + touchableRegion, + onDrawTransaction = null, + ) + } + Trace.endSection() + } + + override fun release(t: SurfaceControl.Transaction) { + clearCurrentUpdateJob() + viewHostAdapter.release(t) + } + + private fun updateViewHost( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + touchableRegion: Region?, + onDrawTransaction: SurfaceControl.Transaction?, + ) { + Trace.beginSection("ReusableWindowDecorViewHost#updateViewHost") + viewHostAdapter.prepareViewHost(configuration, touchableRegion) + onDrawTransaction?.let { viewHostAdapter.applyTransactionOnDraw(it) } + rootView.removeAllViews() + rootView.addView(view) + viewHostAdapter.updateView(rootView, attrs) + Trace.endSection() + } + + private fun clearCurrentUpdateJob() { + currentUpdateJob?.cancel() + currentUpdateJob = null + } + + companion object { + private const val TAG = "ReusableWindowDecorViewHost" + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapter.kt new file mode 100644 index 000000000000..26a43f4d5291 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapter.kt @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.windowdecor.common.viewhost + +import android.content.Context +import android.content.res.Configuration +import android.graphics.Region +import android.view.AttachedSurfaceControl +import android.view.Display +import android.view.SurfaceControl +import android.view.SurfaceControlViewHost +import android.view.View +import android.view.WindowManager +import android.view.WindowlessWindowManager +import androidx.tracing.Trace +import com.android.internal.annotations.VisibleForTesting + +typealias SurfaceControlViewHostFactory = + (Context, Display, WindowlessWindowManager, String) -> SurfaceControlViewHost + +/** + * Adapter for a [SurfaceControlViewHost] and its backing [SurfaceControl]. + * + * It does not support swapping the root view added to the VRI of the [SurfaceControlViewHost], and + * any attempts to do will throw, which means that once a [View] is added using [updateView], only + * its properties and binding may be changed, children views may be added, removed or changed + * and its [WindowManager.LayoutParams] may be changed. + */ +class SurfaceControlViewHostAdapter( + private val context: Context, + private val display: Display, + private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory = { c, d, wwm, s -> + SurfaceControlViewHost(c, d, wwm, s) + }, +) { + val rootSurface: SurfaceControl = + SurfaceControl.Builder() + .setName("SurfaceControlViewHostAdapter surface") + .setContainerLayer() + .setCallsite("SurfaceControlViewHostAdapter#init") + .build() + + private var wwm: WindowDecorWindowlessWindowManager? = null + @VisibleForTesting var viewHost: SurfaceControlViewHost? = null + + /** + * Initialize or updates the [SurfaceControlViewHost]. + */ + fun prepareViewHost( + configuration: Configuration, + touchableRegion: Region? + ) { + if (wwm == null) { + wwm = WindowDecorWindowlessWindowManager(configuration, rootSurface) + } + if (viewHost == null) { + viewHost = + surfaceControlViewHostFactory.invoke( + context, + display, + requireWindowlessWindowManager(), + "SurfaceControlViewHostAdapter#prepareViewHost", + ) + } + requireWindowlessWindowManager().setConfiguration(configuration) + requireWindowlessWindowManager().setTouchRegion(requireViewHost(), touchableRegion) + } + + /** + * Request to apply the transaction atomically with the next draw of the view hierarchy. See + * [AttachedSurfaceControl.applyTransactionOnDraw]. + */ + fun applyTransactionOnDraw(t: SurfaceControl.Transaction) { + requireViewHost().rootSurfaceControl.applyTransactionOnDraw(t) + } + + /** Update the view hierarchy of the view host. */ + fun updateView(view: View, attrs: WindowManager.LayoutParams) { + if (requireViewHost().view == null) { + Trace.beginSection("SurfaceControlViewHostAdapter#updateView-setView") + requireViewHost().setView(view, attrs) + Trace.endSection() + } else { + check(requireViewHost().view == view) { "Changing view is not allowed" } + Trace.beginSection("SurfaceControlViewHostAdapter#updateView-relayout") + requireViewHost().relayout(attrs) + Trace.endSection() + } + } + + /** Release the view host and remove the backing surface. */ + fun release(t: SurfaceControl.Transaction) { + viewHost?.release() + t.remove(rootSurface) + } + + /** Whether the view host has had a view hierarchy set. */ + fun isInitialized(): Boolean = viewHost?.view != null + + private fun requireWindowlessWindowManager(): WindowDecorWindowlessWindowManager { + return wwm ?: error("Expected non-null windowless window manager") + } + + private fun requireViewHost(): SurfaceControlViewHost { + return viewHost ?: error("Expected non-null view host") + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt new file mode 100644 index 000000000000..2dcbbac4646f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.windowdecor.common.viewhost + +import android.content.res.Configuration +import android.graphics.Region +import android.view.SurfaceControl +import android.view.View +import android.view.WindowManager +import com.android.wm.shell.windowdecor.WindowDecoration + +/** + * An interface for a utility that hosts a [WindowDecoration]'s [View] hierarchy under a + * [SurfaceControl]. + */ +interface WindowDecorViewHost { + /** The surface where the underlying [View] hierarchy is being rendered. */ + val surfaceControl: SurfaceControl + + /** Synchronously update the view hierarchy of this view host. */ + fun updateView( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + touchableRegion: Region? = null, + onDrawTransaction: SurfaceControl.Transaction? = null, + ) + + /** Asynchronously update the view hierarchy of this view host. */ + fun updateViewAsync( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + touchableRegion: Region? = null, + ) + + /** Releases the underlying [View] hierarchy and removes the backing [SurfaceControl]. */ + fun release(t: SurfaceControl.Transaction) +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHostSupplier.kt new file mode 100644 index 000000000000..00e29ecaebe3 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHostSupplier.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.windowdecor.common.viewhost + +import android.content.Context +import android.view.Display +import android.view.SurfaceControl + +/** An interface for a supplier of [WindowDecorViewHost]s. */ +interface WindowDecorViewHostSupplier<T : WindowDecorViewHost> { + /** Acquire a [WindowDecorViewHost]. */ + fun acquire(context: Context, display: Display): T + + /** + * Release a [WindowDecorViewHost] when it is no longer used. + * + * @param viewHost the [WindowDecorViewHost] to release + * @param t a transaction that may be used to remove any underlying backing [SurfaceControl] + * that are hosting this [WindowDecorViewHost]. The supplier is not expected to apply the + * transaction. It should be applied by the owner of this supplier. + */ + fun release(viewHost: T, t: SurfaceControl.Transaction) +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorWindowlessWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorWindowlessWindowManager.kt new file mode 100644 index 000000000000..fbe8c6c83b5c --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorWindowlessWindowManager.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.windowdecor.common.viewhost + +import android.content.res.Configuration +import android.graphics.Region +import android.view.SurfaceControl +import android.view.SurfaceControlViewHost +import android.view.WindowlessWindowManager + +/** + * A [WindowlessWindowManager] for the window decor caption that allows customizing the touchable + * region. + */ +class WindowDecorWindowlessWindowManager( + configuration: Configuration, + rootSurface: SurfaceControl, +) : WindowlessWindowManager(configuration, rootSurface, /* hostInputTransferToken= */ null) { + + /** Set the view host's touchable region. */ + fun setTouchRegion(viewHost: SurfaceControlViewHost, region: Region?) { + setTouchRegion(viewHost.windowToken.asBinder(), region) + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt index 0e40a5350a43..9db69d5c1bc5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt @@ -33,6 +33,7 @@ import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopModeEventLogger 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.desktopmode.ReturnToDragStartAnimator import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler import com.android.wm.shell.transition.Transitions @@ -48,7 +49,7 @@ class DesktopTilingDecorViewModel( private val shellTaskOrganizer: ShellTaskOrganizer, private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler, private val returnToDragStartAnimator: ReturnToDragStartAnimator, - private val taskRepository: DesktopRepository, + private val desktopUserRepositories: DesktopUserRepositories, private val desktopModeEventLogger: DesktopModeEventLogger, ) : DisplayChangeController.OnDisplayChangingListener { @VisibleForTesting @@ -81,7 +82,7 @@ class DesktopTilingDecorViewModel( shellTaskOrganizer, toggleResizeDesktopTaskTransitionHandler, returnToDragStartAnimator, - taskRepository, + desktopUserRepositories, desktopModeEventLogger, ) tilingTransitionHandlerByDisplayId.put(displayId, newHandler) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt index 3b5c6ca2e58a..7ceac52dd2a1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt @@ -49,6 +49,7 @@ import com.android.wm.shell.desktopmode.DesktopModeEventLogger import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition +import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler import com.android.wm.shell.transition.Transitions @@ -72,7 +73,7 @@ class DesktopTilingWindowDecoration( private val shellTaskOrganizer: ShellTaskOrganizer, private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler, private val returnToDragStartAnimator: ReturnToDragStartAnimator, - private val taskRepository: DesktopRepository, + private val desktopUserRepositories: DesktopUserRepositories, private val desktopModeEventLogger: DesktopModeEventLogger, private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() }, ) : @@ -630,6 +631,7 @@ class DesktopTilingWindowDecoration( private fun allTiledTasksVisible(): Boolean { val leftTiledTask = leftTaskResizingHelper ?: return false val rightTiledTask = rightTaskResizingHelper ?: return false + val taskRepository = desktopUserRepositories.current return taskRepository.isVisibleTask(leftTiledTask.taskInfo.taskId) && taskRepository.isVisibleTask(rightTiledTask.taskInfo.taskId) } diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt index d9c36cc70790..f4f60d73c25c 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt @@ -62,6 +62,14 @@ import android.tools.traces.wm.TransitionType class DesktopModeFlickerScenarios { companion object { + // In DesktopMode, window snap can be done with just a single window. In this case, the + // divider tiling between left and right window won't be shown, and hence its states are not + // obtainable in test. + // As the test should just focus on ensuring window goes to one side of the screen, an + // acceptable approach is to ensure snapped window still fills > 95% of either side of the + // screen. + private const val SNAP_WINDOW_MAX_DIFF_THRESHOLD_RATIO = 0.05 + val END_DRAG_TO_DESKTOP = FlickerConfigEntry( scenarioId = ScenarioId("END_DRAG_TO_DESKTOP"), @@ -230,9 +238,11 @@ class DesktopModeFlickerScenarios { TaggedCujTransitionMatcher(associatedTransitionRequired = false) ) .build(), - assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + - listOf(AppWindowCoversLeftHalfScreenAtEnd(DESKTOP_MODE_APP)) - .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), + assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + listOf( + AppWindowCoversLeftHalfScreenAtEnd( + DESKTOP_MODE_APP, SNAP_WINDOW_MAX_DIFF_THRESHOLD_RATIO + ) + ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), ) val SNAP_RESIZE_RIGHT_WITH_BUTTON = @@ -245,9 +255,11 @@ class DesktopModeFlickerScenarios { TaggedCujTransitionMatcher(associatedTransitionRequired = false) ) .build(), - assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + - listOf(AppWindowCoversRightHalfScreenAtEnd(DESKTOP_MODE_APP)) - .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), + assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + listOf( + AppWindowCoversRightHalfScreenAtEnd( + DESKTOP_MODE_APP, SNAP_WINDOW_MAX_DIFF_THRESHOLD_RATIO + ) + ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), ) val SNAP_RESIZE_LEFT_WITH_DRAG = @@ -260,9 +272,11 @@ class DesktopModeFlickerScenarios { TaggedCujTransitionMatcher(associatedTransitionRequired = false) ) .build(), - assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + - listOf(AppWindowCoversLeftHalfScreenAtEnd(DESKTOP_MODE_APP)) - .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), + assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + listOf( + AppWindowCoversLeftHalfScreenAtEnd( + DESKTOP_MODE_APP, SNAP_WINDOW_MAX_DIFF_THRESHOLD_RATIO + ) + ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), ) val SNAP_RESIZE_RIGHT_WITH_DRAG = @@ -275,9 +289,11 @@ class DesktopModeFlickerScenarios { TaggedCujTransitionMatcher(associatedTransitionRequired = false) ) .build(), - assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + - listOf(AppWindowCoversRightHalfScreenAtEnd(DESKTOP_MODE_APP)) - .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), + assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + listOf( + AppWindowCoversRightHalfScreenAtEnd( + DESKTOP_MODE_APP, SNAP_WINDOW_MAX_DIFF_THRESHOLD_RATIO + ) + ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), ) val SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE = diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml index 706c63244890..1de47df78853 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml @@ -48,6 +48,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml index 7df1675f541c..34d001c858f6 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml @@ -48,6 +48,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml index 7df1675f541c..34d001c858f6 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml @@ -48,6 +48,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromAnotherApp.kt index 2ccffa85b5c1..a3d60207a8bd 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromAnotherApp.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromAnotherApp.kt @@ -66,5 +66,6 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { fun teardown() { primaryApp.exit(wmHelper) secondaryApp.exit(wmHelper) + Utils.resetFreezeRecentTaskList() } } diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromHome.kt index 8673c464ad19..9c7de05563e1 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromHome.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromHome.kt @@ -65,5 +65,6 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { fun teardown() { primaryApp.exit(wmHelper) secondaryApp.exit(wmHelper) + Utils.resetFreezeRecentTaskList() } } diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromRecent.kt index 22adf6c9ee2f..9eb29723cc7d 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromRecent.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromRecent.kt @@ -68,5 +68,6 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { fun teardown() { primaryApp.exit(wmHelper) secondaryApp.exit(wmHelper) + Utils.resetFreezeRecentTaskList() } } diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBetweenSplitPairs.kt index 4ded148f6113..d833d91c0b4b 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBetweenSplitPairs.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBetweenSplitPairs.kt @@ -68,5 +68,6 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { secondaryApp.exit(wmHelper) thirdApp.exit(wmHelper) fourthApp.exit(wmHelper) + Utils.resetFreezeRecentTaskList() } } diff --git a/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/Utils.kt b/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/Utils.kt index c0fafef96775..4a9e73b4af58 100644 --- a/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/Utils.kt +++ b/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/Utils.kt @@ -28,7 +28,10 @@ import android.tools.flicker.rules.ArtifactSaverRule import android.tools.flicker.rules.ChangeDisplayOrientationRule import android.tools.flicker.rules.LaunchAppRule import android.tools.flicker.rules.RemoveAllTasksButHomeRule +import android.util.Log import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import java.io.IOException import org.junit.rules.RuleChain object Utils { @@ -52,4 +55,17 @@ object Utils { .around(PressHomeRule()) .around(EnsureDeviceSettingsRule()) } + + /** + * Resets the frozen recent tasks list (ie. commits the quickswitch to the current task and + * reorders the current task to the end of the recents list). + */ + fun resetFreezeRecentTaskList() { + try { + UiDevice.getInstance(instrumentation) + .executeShellCommand("wm reset-freeze-recent-tasks") + } catch (e: IOException) { + Log.e("TestUtils", "Failed to reset frozen recent tasks list", e) + } + } } diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml index d87c1795cf7b..9c1a8f17aeee 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml @@ -48,6 +48,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml index 99969e71238a..02b2cec8dbdb 100644 --- a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml @@ -48,6 +48,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp index ddbc681f7cac..f40edaebec5e 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp @@ -266,5 +266,26 @@ test_module_config { test_suites: ["device-tests"], } +test_module_config { + name: "WMShellFlickerTestsPip-nonMatchParent", + base: "WMShellFlickerTestsPip", + include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.*"], + test_suites: ["device-tests"], +} + +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"], +} + // End breakdowns for WMShellFlickerTestsPip module //////////////////////////////////////////////////////////////////////////////// diff --git a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml index 19c3e4048d69..a136936c0838 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml @@ -25,7 +25,7 @@ <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on"/> <!-- Turns off Wi-fi --> - <option name="wifi" value="off"/> + <option name="wifi" value="on"/> <!-- Turns off Bluetooth --> <option name="bluetooth" value="off"/> <!-- prevents the phone from restarting --> @@ -48,6 +48,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> @@ -107,4 +109,11 @@ <option name="collect-on-run-ended-only" value="true"/> <option name="clean-up" value="true"/> </metrics_collector> + <!-- Enable mocking GPS location by the test app --> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" + value="appops set com.android.shell android:mock_location allow"/> + <option name="teardown-command" + value="appops set com.android.shell android:mock_location deny"/> + </target_preparer> </configuration> diff --git a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml index 7505860709e9..34e4e744dae7 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml @@ -48,6 +48,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt index fd4328dee0a1..609a2849f915 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt @@ -27,6 +27,7 @@ import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.subject.exceptions.ExceptionMessageBuilder import android.tools.flicker.subject.exceptions.IncorrectRegionException import android.tools.flicker.subject.layers.LayerSubject +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 @@ -65,6 +66,8 @@ import kotlin.math.abs @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 = { diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt index d4ad4ef8a401..5698023240ab 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt @@ -22,6 +22,7 @@ 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 @@ -57,6 +58,8 @@ import org.junit.runners.Parameterized @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/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt index 53725fa046c6..880e4cd4e5f7 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt @@ -21,6 +21,7 @@ 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 @@ -56,6 +57,8 @@ import org.junit.runners.Parameterized @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/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt index a1551b7924fe..4399a237bcbb 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt @@ -21,6 +21,7 @@ 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 @@ -47,6 +48,7 @@ import org.junit.runners.Parameterized @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 = { diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt index ea5b3e5b08df..49efd1d56256 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt @@ -31,6 +31,7 @@ import android.tools.traces.component.ComponentNameMatcher 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 @@ -72,6 +73,7 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(flicker) { + override val pipApp: PipAppHelper = PipAppHelper(instrumentation) private val testApp = FixedOrientationAppHelper(instrumentation) private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90) private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt index a109c4bba2b3..97cc9d29929c 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt @@ -20,6 +20,7 @@ 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 @@ -53,6 +54,8 @@ import org.junit.runners.Parameterized @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/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt index 14ec303206ee..b5b7847e205d 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt @@ -20,6 +20,7 @@ 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 @@ -56,6 +57,8 @@ import org.junit.runners.Parameterized @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/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt index 8a34b5e27fdb..f9a9df43a009 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt @@ -20,6 +20,7 @@ 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 @@ -54,6 +55,8 @@ import org.junit.runners.Parameterized @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 diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt index 12e23285ea68..79e2e4e5a82c 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt @@ -27,6 +27,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 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.Flags @@ -68,6 +69,7 @@ import org.junit.runners.Parameterized @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) 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 = diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt index 04016a93e53d..14ae93a81c6d 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt @@ -23,6 +23,7 @@ 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 @@ -37,6 +38,8 @@ import org.junit.runners.Parameterized @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(wmHelper) } } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt index 6bcaabc3b680..81162c6f53f5 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragTest.kt @@ -49,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/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt index d82bfdd6dc2f..6118d73796a1 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt @@ -59,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) diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt index dbc97d072f9b..61c59cc45504 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt @@ -25,6 +25,7 @@ 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 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 @@ -40,6 +41,8 @@ import org.junit.runners.Parameterized @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/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt index 65b60ce1022b..0867f654bcaf 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt +++ b/libs/WindowManager/Shell/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<String> @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/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt index 7b04b766a191..651c92308c04 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt +++ b/libs/WindowManager/Shell/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 @@ -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<String> = 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/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt index 691194609343..be4cd780e45e 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt @@ -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 = { @@ -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/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt index 5e54f30dae8a..3e4ff3075f73 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt @@ -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<String> = arrayOf(Manifest.permission.POST_NOTIFICATIONS) override val defaultEnterPip: FlickerBuilder.() -> Unit = { setup { - standardAppHelper.launchViaIntent( + pipApp.launchViaIntent( wmHelper, 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/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt index 159cba4a559e..2c6cb503465c 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt @@ -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("3KtWfp0UopM"), ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "") ) - standardAppHelper.enterFullscreen() - standardAppHelper.waitForVideoPlaying() + pipApp.enterFullscreen() + pipApp.waitForVideoPlaying() } } @@ -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/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt index 6dd3a175da65..a72de0f6daf4 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt +++ b/libs/WindowManager/Shell/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/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt index c37bf3579e93..7b6625ddc429 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt @@ -27,6 +27,7 @@ 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 @@ -40,7 +41,6 @@ abstract class PipTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) { @Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() - protected val pipApp = PipAppHelper(instrumentation) protected val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation) protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation) @@ -63,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 = {} @@ -85,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) } } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppTransition.kt new file mode 100644 index 000000000000..c405b664e431 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppTransition.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.pip.nonmatchparent + +import android.platform.test.annotations.Presubmit +import android.tools.flicker.legacy.LegacyFlickerTest +import android.tools.traces.component.ComponentNameMatcher +import com.android.server.wm.flicker.helpers.BottomHalfPipAppHelper +import com.android.server.wm.flicker.helpers.PipAppHelper +import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition +import org.junit.Test + +/** + * Base test class to verify PIP exit animation with an activity layout to the bottom half of + * the container. + */ +abstract class BottomHalfExitPipToAppTransition(flicker: LegacyFlickerTest) : + ExitPipToAppTransition(flicker) { + + override val pipApp: PipAppHelper = BottomHalfPipAppHelper(instrumentation) + + @Presubmit + @Test + override fun showBothAppLayersThenHidePip() { + // Disabled since the BottomHalfPipActivity just covers half of the simple activity. + } + + @Presubmit + @Test + override fun showBothAppWindowsThenHidePip() { + // Disabled since the BottomHalfPipActivity just covers half of the simple activity. + } + + @Presubmit + @Test + override fun pipAppCoversFullScreenAtEnd() { + // Disabled since the BottomHalfPipActivity just covers half of the simple activity. + } + + /** + * Checks that the [testApp] and [pipApp] are always visible since the [pipApp] only covers + * half of screen. + */ + @Presubmit + @Test + fun showBothAppLayersDuringPipTransition() { + flicker.assertLayers { + isVisible(testApp) + .isVisible(pipApp.or(ComponentNameMatcher.TRANSITION_SNAPSHOT)) + } + } + + /** + * Checks that the [testApp] and [pipApp] are always visible since the [pipApp] only covers + * half of screen. + */ + @Presubmit + @Test + fun showBothAppWindowsDuringPipTransition() { + flicker.assertWm { + isAppWindowVisible(testApp) + .isAppWindowOnTop(pipApp) + .isAppWindowVisible(pipApp) + } + } + + /** + * Verify that the [testApp] and [pipApp] covers the entire screen at the end of PIP exit + * animation since the [pipApp] will use a bottom half layout. + */ + @Presubmit + @Test + fun testPlusPipAppCoversWindowFrameAtEnd() { + flicker.assertLayersEnd { + val pipRegion = visibleRegion(pipApp).region + visibleRegion(testApp).plus(pipRegion).coversExactly(displayBounds) + } + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaExpandButtonTest.kt new file mode 100644 index 000000000000..2a3dc07037df --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaExpandButtonTest.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.android.wm.shell.flicker.pip.nonmatchparent + +import android.platform.test.annotations.RequiresDevice +import android.platform.test.annotations.RequiresFlagsDisabled +import android.platform.test.annotations.RequiresFlagsEnabled +import android.tools.flicker.junit.FlickerParametersRunnerFactory +import android.tools.flicker.legacy.FlickerBuilder +import android.tools.flicker.legacy.LegacyFlickerTest +import com.android.window.flags.Flags +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test expanding a pip window back to bottom half layout via the expand button + * + * To run this test: `atest WMShellFlickerTestsPip:BottomHalfExitPipToAppViaExpandButtonTest` + * + * Actions: + * ``` + * Launch an app in pip mode [bottomHalfPipApp], + * Launch another full screen mode [testApp] + * Expand [bottomHalfPipApp] app to bottom half layout by clicking on the pip window and + * then on the expand button + * ``` + * + * 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 + * ``` + */ +// TODO(b/380796448): re-enable tests after the support of non-match parent PIP animation for PIP2. +@RequiresFlagsDisabled(com.android.wm.shell.Flags.FLAG_ENABLE_PIP2) +@RequiresFlagsEnabled(Flags.FLAG_BETTER_SUPPORT_NON_MATCH_PARENT_ACTIVITY) +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class BottomHalfExitPipToAppViaExpandButtonTest(flicker: LegacyFlickerTest) : + BottomHalfExitPipToAppTransition(flicker) +{ + override val thisTransition: FlickerBuilder.() -> Unit = { + setup { + // launch an app behind the pip one + testApp.launchViaIntent(wmHelper) + } + transitions { + // This will bring PipApp to fullscreen + pipApp.expandPipWindowToApp(wmHelper) + // Wait until the transition idle and test and pip app still shows. + wmHelper.StateSyncBuilder().withLayerVisible(testApp).withLayerVisible(pipApp) + .withAppTransitionIdle().waitForAndVerify() + } + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaIntentTest.kt new file mode 100644 index 000000000000..8ed9cd23005b --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaIntentTest.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.pip.nonmatchparent + +import android.platform.test.annotations.RequiresDevice +import android.platform.test.annotations.RequiresFlagsDisabled +import android.platform.test.annotations.RequiresFlagsEnabled +import android.tools.flicker.junit.FlickerParametersRunnerFactory +import android.tools.flicker.legacy.FlickerBuilder +import android.tools.flicker.legacy.LegacyFlickerTest +import com.android.window.flags.Flags +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test expanding a pip window back to bottom half layout via an intent + * + * To run this test: `atest WMShellFlickerTestsPip:BottomHalfExitPipToAppViaIntentTest` + * + * Actions: + * ``` + * Launch an app in pip mode [bottomHalfPipApp], + * Launch another full screen mode [testApp] + * Expand [bottomHalfPipApp] app to bottom half layout via an intent + * ``` + * + * Notes: + * ``` + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited from [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 + * ``` + */ +// TODO(b/380796448): re-enable tests after the support of non-match parent PIP animation for PIP2. +@RequiresFlagsDisabled(com.android.wm.shell.Flags.FLAG_ENABLE_PIP2) +@RequiresFlagsEnabled(Flags.FLAG_BETTER_SUPPORT_NON_MATCH_PARENT_ACTIVITY) +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class BottomHalfExitPipToAppViaIntentTest(flicker: LegacyFlickerTest) : + BottomHalfExitPipToAppTransition(flicker) +{ + override val thisTransition: FlickerBuilder.() -> Unit = { + setup { + // launch an app behind the pip one + testApp.launchViaIntent(wmHelper) + } + transitions { + // This will bring PipApp to fullscreen + pipApp.exitPipToFullScreenViaIntent(wmHelper) + // Wait until the transition idle and test and pip app still shows. + wmHelper.StateSyncBuilder().withLayerVisible(testApp).withLayerVisible(pipApp) + .withAppTransitionIdle().waitForAndVerify() + } + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt index c4954f90179c..feb3edc9dab7 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt @@ -20,6 +20,7 @@ import android.app.Instrumentation 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.traces.component.ComponentNameMatcher @@ -102,8 +103,8 @@ object SplitScreenUtils { wmHelper: WindowManagerStateHelper, tapl: LauncherInstrumentation, device: UiDevice, - primaryApp: StandardAppHelper, - secondaryApp: StandardAppHelper, + primaryApp: IStandardAppHelper, + secondaryApp: IStandardAppHelper, rotation: Rotation ) { primaryApp.launchViaIntent(wmHelper) @@ -117,8 +118,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) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java index 310c2d725c09..ec3fe95f7bef 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java @@ -54,6 +54,7 @@ public final class TestRunningTaskInfoBuilder { private final Point mPositionInParent = new Point(); private boolean mIsVisible = false; private boolean mIsTopActivityTransparent = false; + private boolean mIsActivityStackTransparent = false; private int mNumActivities = 1; private long mLastActiveTime; @@ -158,6 +159,12 @@ public final class TestRunningTaskInfoBuilder { return this; } + public TestRunningTaskInfoBuilder setActivityStackTransparent( + boolean isActivityStackTransparent) { + mIsActivityStackTransparent = isActivityStackTransparent; + return this; + } + public TestRunningTaskInfoBuilder setNumActivities(int numActivities) { mNumActivities = numActivities; return this; @@ -187,6 +194,7 @@ public final class TestRunningTaskInfoBuilder { info.positionInParent = mPositionInParent; info.isVisible = mIsVisible; info.isTopActivityTransparent = mIsTopActivityTransparent; + info.isActivityStackTransparent = mIsActivityStackTransparent; info.numActivities = mNumActivities; info.lastActiveTime = mLastActiveTime; info.userId = mUserId; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index c3e396524da1..a2afd2c92d3d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -33,6 +33,8 @@ 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; @@ -86,7 +88,6 @@ 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.shared.ShellSharedConstants; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -254,7 +255,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 @@ -635,7 +636,7 @@ public class BackAnimationControllerTest extends ShellTestCase { releaseBackGesture(); mShellExecutor.flushAll(); - verify(mAppCallback).setHandoffHandler(any()); + verify(mAppCallback).setHandoffHandler(notNull()); } @Test @@ -655,7 +656,7 @@ public class BackAnimationControllerTest extends ShellTestCase { releaseBackGesture(); mShellExecutor.flushAll(); - verify(mAppCallback, never()).setHandoffHandler(any()); + verify(mAppCallback).setHandoffHandler(isNull()); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java index 0373bbd43043..6f3a3ec4fd20 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java @@ -172,10 +172,10 @@ public class DisplayImeControllerTest extends ShellTestCase { var mockPp = mock(DisplayImeController.ImePositionProcessor.class); mDisplayImeController.addPositionProcessor(mockPp); - mPerDisplay.setImeInputTargetRequestedVisibility(true); + mPerDisplay.setImeInputTargetRequestedVisibility(true, null /* statsToken */); verify(mockPp).onImeRequested(anyInt(), eq(true)); - mPerDisplay.setImeInputTargetRequestedVisibility(false); + mPerDisplay.setImeInputTargetRequestedVisibility(false, null /* statsToken */); verify(mockPp).onImeRequested(anyInt(), eq(false)); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/HandlerExecutorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/HandlerExecutorTest.kt new file mode 100644 index 000000000000..799b48c2504f --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/HandlerExecutorTest.kt @@ -0,0 +1,173 @@ +/* + * 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.common + +import android.os.Handler +import android.os.HandlerThread +import android.os.Looper +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito +import com.android.wm.shell.ShellTestCase +import com.google.common.truth.Truth.assertThat +import java.util.function.BiConsumer +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.MockitoSession +import org.mockito.kotlin.whenever + +/** + * Tests for HandlerExecutor. + * + * Build/Install/Run: + * atest WMShellUnitTests:HandlerExecutorTest + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +class HandlerExecutorTest : ShellTestCase() { + + class TestSetThreadPriorityFn : BiConsumer<Int, Int> { + var lastSetPriority = UNSET_THREAD_PRIORITY + private set + var callCount = 0 + private set + + override fun accept(tid: Int, priority: Int) { + lastSetPriority = priority + callCount++ + } + + fun reset() { + lastSetPriority = UNSET_THREAD_PRIORITY + callCount = 0 + } + } + + val testSetPriorityFn = TestSetThreadPriorityFn() + + @Test + fun defaultExecutorDisallowBoost() { + val executor = createTestHandlerExecutor() + + executor.setBoost() + + assertThat(executor.isBoosted()).isFalse() + } + + @Test + fun boostExecutor_resetWhenNotSet_expectNoOp() { + val executor = createTestHandlerExecutor(DEFAULT_THREAD_PRIORITY, BOOSTED_THREAD_PRIORITY) + val mockSession: MockitoSession = ExtendedMockito.mockitoSession() + .mockStatic(android.os.Process::class.java) + .startMocking() + + try { + // Try to reset and ensure we never try to set the thread priority + executor.resetBoost() + + assertThat(testSetPriorityFn.callCount).isEqualTo(0) + assertThat(executor.isBoosted()).isFalse() + } finally { + mockSession.finishMocking() + } + } + + @Test + fun boostExecutor_setResetBoost_expectThreadPriorityUpdated() { + val executor = createTestHandlerExecutor(DEFAULT_THREAD_PRIORITY, BOOSTED_THREAD_PRIORITY) + val mockSession: MockitoSession = ExtendedMockito.mockitoSession() + .mockStatic(android.os.Process::class.java) + .startMocking() + + try { + // Boost and ensure the boosted thread priority is requested + executor.setBoost() + + assertThat(testSetPriorityFn.lastSetPriority).isEqualTo(BOOSTED_THREAD_PRIORITY) + assertThat(testSetPriorityFn.callCount).isEqualTo(1) + assertThat(executor.isBoosted()).isTrue() + + // Reset and ensure the default thread priority is requested + executor.resetBoost() + + assertThat(testSetPriorityFn.lastSetPriority).isEqualTo(DEFAULT_THREAD_PRIORITY) + assertThat(testSetPriorityFn.callCount).isEqualTo(2) + assertThat(executor.isBoosted()).isFalse() + } finally { + mockSession.finishMocking() + } + } + + @Test + fun boostExecutor_overlappingBoost_expectResetOnlyWhenNotOverlapping() { + val executor = createTestHandlerExecutor(DEFAULT_THREAD_PRIORITY, BOOSTED_THREAD_PRIORITY) + val mockSession: MockitoSession = ExtendedMockito.mockitoSession() + .mockStatic(android.os.Process::class.java) + .startMocking() + + try { + // Set and ensure we only update the thread priority once + executor.setBoost() + executor.setBoost() + + assertThat(testSetPriorityFn.lastSetPriority).isEqualTo(BOOSTED_THREAD_PRIORITY) + assertThat(testSetPriorityFn.callCount).isEqualTo(1) + assertThat(executor.isBoosted()).isTrue() + + // Reset and ensure we are still boosted and the thread priority doesn't change + executor.resetBoost() + + assertThat(testSetPriorityFn.lastSetPriority).isEqualTo(BOOSTED_THREAD_PRIORITY) + assertThat(testSetPriorityFn.callCount).isEqualTo(1) + assertThat(executor.isBoosted()).isTrue() + + // Reset again and ensure we update the thread priority accordingly + executor.resetBoost() + + assertThat(testSetPriorityFn.lastSetPriority).isEqualTo(DEFAULT_THREAD_PRIORITY) + assertThat(testSetPriorityFn.callCount).isEqualTo(2) + assertThat(executor.isBoosted()).isFalse() + } finally { + mockSession.finishMocking() + } + } + + /** + * Creates a test handler executor backed by a mocked handler thread. + */ + private fun createTestHandlerExecutor( + defaultThreadPriority: Int = DEFAULT_THREAD_PRIORITY, + boostedThreadPriority: Int = DEFAULT_THREAD_PRIORITY + ) : HandlerExecutor { + val handler = mock(Handler::class.java) + val looper = mock(Looper::class.java) + val thread = mock(HandlerThread::class.java) + whenever(handler.looper).thenReturn(looper) + whenever(looper.thread).thenReturn(thread) + whenever(thread.threadId).thenReturn(1234) + val executor = HandlerExecutor(handler, defaultThreadPriority, boostedThreadPriority) + executor.replaceSetThreadPriorityFn(testSetPriorityFn) + return executor + } + + companion object { + private const val UNSET_THREAD_PRIORITY = 0 + private const val DEFAULT_THREAD_PRIORITY = 1 + private const val BOOSTED_THREAD_PRIORITY = 1000 + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java index cf69704a0470..fd3d3b5b6e2f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java @@ -56,6 +56,7 @@ 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 SplitLayout mSplitLayout; private DividerView mDividerView; @@ -67,7 +68,7 @@ public class DividerViewTest extends ShellTestCase { Configuration configuration = getConfiguration(); mSplitLayout = new SplitLayout("TestSplitLayout", mContext, configuration, mSplitLayoutHandler, mCallbacks, mDisplayController, mDisplayImeController, - mTaskOrganizer, SplitLayout.PARALLAX_NONE, mHandler); + mTaskOrganizer, SplitLayout.PARALLAX_NONE, mSplitState, mHandler); SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager", mContext, configuration, mCallbacks); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java index dc0f213338be..1904c43d78cf 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java @@ -66,6 +66,7 @@ public class SplitLayoutTests extends ShellTestCase { @Mock DisplayImeController mDisplayImeController; @Mock ShellTaskOrganizer mTaskOrganizer; @Mock WindowContainerTransaction mWct; + @Mock SplitState mSplitState; @Mock Handler mHandler; @Captor ArgumentCaptor<Runnable> mRunnableCaptor; private SplitLayout mSplitLayout; @@ -83,6 +84,7 @@ public class SplitLayoutTests extends ShellTestCase { mDisplayImeController, mTaskOrganizer, SplitLayout.PARALLAX_NONE, + mSplitState, mHandler)); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/transition/TransitionStateHolderTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/transition/TransitionStateHolderTest.kt index 7b1d27a8b823..64772d037383 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/transition/TransitionStateHolderTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/transition/TransitionStateHolderTest.kt @@ -18,7 +18,6 @@ package com.android.wm.shell.common.transition import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest -import com.android.server.testutils.any import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener @@ -35,6 +34,7 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mockito.mock import org.mockito.Mockito.verify +import org.mockito.kotlin.any import org.mockito.kotlin.never /** diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt index 1d390007d470..d52fd4fdf6c7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt @@ -37,35 +37,46 @@ import org.junit.runner.RunWith @SmallTest class AppCompatUtilsTest : ShellTestCase() { @Test - fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent() { + fun testIsTopActivityExemptFromDesktopWindowing_onlyTransparentActivitiesInStack() { assertTrue(isTopActivityExemptFromDesktopWindowing(mContext, createFreeformTask(/* displayId */ 0) .apply { - isTopActivityTransparent = true - numActivities = 1 + isActivityStackTransparent = true isTopActivityNoDisplay = false + numActivities = 1 })) } @Test - fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent_multipleActivities() { + fun testIsTopActivityExemptFromDesktopWindowing_noActivitiesInStack() { assertFalse(isTopActivityExemptFromDesktopWindowing(mContext, createFreeformTask(/* displayId */ 0) .apply { - isTopActivityTransparent = true - numActivities = 2 + isActivityStackTransparent = true isTopActivityNoDisplay = false + numActivities = 0 })) } @Test - fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent_notDisplayed() { + fun testIsTopActivityExemptFromDesktopWindowing_nonTransparentActivitiesInStack() { assertFalse(isTopActivityExemptFromDesktopWindowing(mContext, createFreeformTask(/* displayId */ 0) .apply { - isTopActivityTransparent = true + isActivityStackTransparent = false + isTopActivityNoDisplay = false numActivities = 1 + })) + } + + @Test + fun testIsTopActivityExemptFromDesktopWindowing_transparentActivityStack_notDisplayed() { + assertFalse(isTopActivityExemptFromDesktopWindowing(mContext, + createFreeformTask(/* displayId */ 0) + .apply { + isActivityStackTransparent = true isTopActivityNoDisplay = true + numActivities = 1 })) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java index d5287e742c2c..67573dabf2ec 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java @@ -30,6 +30,7 @@ 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.TaskInfo; @@ -61,12 +62,16 @@ 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.desktopmode.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import dagger.Lazy; +import java.util.Optional; + import org.junit.Assert; import org.junit.Before; import org.junit.Rule; @@ -130,6 +135,10 @@ public class CompatUIControllerTest extends ShellTestCase { private CompatUIShellCommandHandler mCompatUIShellCommandHandler; @Mock private AccessibilityManager mAccessibilityManager; + @Mock + private DesktopUserRepositories mDesktopUserRepositories; + @Mock + private DesktopRepository mDesktopRepository; @Captor ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor; @@ -137,7 +146,6 @@ public class CompatUIControllerTest extends ShellTestCase { @NonNull private CompatUIStatusManager mCompatUIStatusManager; - private boolean mInDesktopModePredicateResult; @Before public void setUp() { @@ -152,6 +160,8 @@ 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(); @@ -164,7 +174,7 @@ public class CompatUIControllerTest extends ShellTestCase { mMockDisplayController, mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader, mCompatUIConfiguration, mCompatUIShellCommandHandler, mAccessibilityManager, - mCompatUIStatusManager, i -> mInDesktopModePredicateResult) { + mCompatUIStatusManager, Optional.of(mDesktopUserRepositories)) { @Override CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) { @@ -707,13 +717,17 @@ public class CompatUIControllerTest extends ShellTestCase { @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) @EnableFlags(Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE) public void testUpdateActiveTaskInfo_removeAllComponentWhenInDesktopModeFlagEnabled() { - mInDesktopModePredicateResult = false; TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true); + when(mDesktopUserRepositories.getCurrent().getVisibleTaskCount(DISPLAY_ID)).thenReturn(0); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); + verify(mController, never()).removeLayouts(taskInfo.taskId); - mInDesktopModePredicateResult = true; + when(mDesktopUserRepositories.getCurrent().getVisibleTaskCount(DISPLAY_ID)).thenReturn(2); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); + verify(mController).removeLayouts(taskInfo.taskId); } @@ -721,13 +735,17 @@ public class CompatUIControllerTest extends ShellTestCase { @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) @DisableFlags(Flags.FLAG_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE) public void testUpdateActiveTaskInfo_removeAllComponentWhenInDesktopModeFlagDisabled() { - mInDesktopModePredicateResult = false; + when(mDesktopUserRepositories.getCurrent().getVisibleTaskCount(DISPLAY_ID)).thenReturn(0); TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); + verify(mController, never()).removeLayouts(taskInfo.taskId); - mInDesktopModePredicateResult = true; + when(mDesktopUserRepositories.getCurrent().getVisibleTaskCount(DISPLAY_ID)).thenReturn(2); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); + verify(mController, never()).removeLayouts(taskInfo.taskId); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java index 94dbd112bb75..4c97c76ae122 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java @@ -19,6 +19,7 @@ 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; @@ -42,10 +43,12 @@ 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.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.AndroidTestingRunner; import android.util.Pair; import android.view.DisplayCutout; @@ -125,6 +128,9 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { @Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnDismissCallback; @Mock private DockStateReader mDockStateReader; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private CompatUIConfiguration mCompatUIConfiguration; private TestShellExecutor mExecutor; private FakeCompatUIStatusManagerTest mCompatUIStatus; @@ -317,6 +323,7 @@ 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); @@ -346,6 +353,36 @@ 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); @@ -375,6 +412,7 @@ 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); @@ -397,6 +435,29 @@ 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); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxConfigurationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxConfigurationTest.kt index 75025d9064d3..1399600d5ab9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxConfigurationTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxConfigurationTest.kt @@ -26,6 +26,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn import com.android.internal.R import com.android.wm.shell.ShellTestCase import java.util.function.Consumer +import kotlin.test.assertEquals import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.doReturn @@ -51,6 +52,14 @@ class LetterboxConfigurationTest : ShellTestCase() { val COLOR_WHITE_RESOURCE_ID = android.R.color.white @JvmStatic val COLOR_BLACK_RESOURCE_ID = android.R.color.black + @JvmStatic + val ROUNDED_CORNER_RADIUS_DEFAULT = 32 + @JvmStatic + val ROUNDED_CORNER_RADIUS_VALID = 16 + @JvmStatic + val ROUNDED_CORNER_RADIUS_NONE = 0 + @JvmStatic + val ROUNDED_CORNER_RADIUS_INVALID = -10 } @Test @@ -112,6 +121,68 @@ class LetterboxConfigurationTest : ShellTestCase() { } } + @Test + fun `default rounded corner radius is used if override is not set`() { + runTestScenario { r -> + r.setDefaultRoundedCornerRadius(ROUNDED_CORNER_RADIUS_DEFAULT) + r.loadConfiguration() + r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_DEFAULT) + } + } + + @Test + fun `new rounded corner radius is used after setting a valid value`() { + runTestScenario { r -> + r.setDefaultRoundedCornerRadius(ROUNDED_CORNER_RADIUS_DEFAULT) + r.loadConfiguration() + r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_VALID) + r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_VALID) + } + } + + @Test + fun `no rounded corner radius is used after setting an invalid value`() { + runTestScenario { r -> + r.setDefaultRoundedCornerRadius(ROUNDED_CORNER_RADIUS_DEFAULT) + r.loadConfiguration() + r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_INVALID) + r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_NONE) + } + } + + @Test + fun `has rounded corners for different values`() { + runTestScenario { r -> + r.setDefaultRoundedCornerRadius(ROUNDED_CORNER_RADIUS_DEFAULT) + r.loadConfiguration() + r.checkIsLetterboxActivityCornersRounded(true) + + r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_INVALID) + r.checkIsLetterboxActivityCornersRounded(false) + + r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_NONE) + r.checkIsLetterboxActivityCornersRounded(false) + + r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_VALID) + r.checkIsLetterboxActivityCornersRounded(true) + } + } + + @Test + fun `reset rounded corners radius`() { + runTestScenario { r -> + r.setDefaultRoundedCornerRadius(ROUNDED_CORNER_RADIUS_DEFAULT) + r.loadConfiguration() + r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_DEFAULT) + + r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_VALID) + r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_VALID) + + r.resetRoundedCornersRadius() + r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_DEFAULT) + } + } + /** * Runs a test scenario providing a Robot. */ @@ -135,6 +206,11 @@ class LetterboxConfigurationTest : ShellTestCase() { .getColor(R.color.config_letterboxBackgroundColor, null) } + fun setDefaultRoundedCornerRadius(radius: Int) { + doReturn(radius).`when`(resources) + .getInteger(R.integer.config_letterboxActivityCornersRadius) + } + fun loadConfiguration() { letterboxConfig = LetterboxConfiguration(ctx) } @@ -147,14 +223,30 @@ class LetterboxConfigurationTest : ShellTestCase() { letterboxConfig.resetLetterboxBackgroundColor() } + fun resetRoundedCornersRadius() { + letterboxConfig.resetLetterboxActivityCornersRadius() + } + fun overrideBackgroundColorId(@ColorRes colorId: Int) { letterboxConfig.setLetterboxBackgroundColorResourceId(colorId) } + fun overrideRoundedCornersRadius(radius: Int) { + letterboxConfig.setLetterboxActivityCornersRadius(radius) + } + fun checkBackgroundColor(expected: Color) { val colorComponents = letterboxConfig.getBackgroundColorRgbArray() val expectedComponents = expected.components assert(expectedComponents.contentEquals(colorComponents)) } + + fun checkRoundedCornersRadius(expected: Int) { + assertEquals(expected, letterboxConfig.getLetterboxActivityCornersRadius()) + } + + fun checkIsLetterboxActivityCornersRounded(expected: Boolean) { + assertEquals(expected, letterboxConfig.isLetterboxActivityCornersRounded()) + } } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt new file mode 100644 index 000000000000..95a0c82c76df --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt @@ -0,0 +1,146 @@ +/* + * Copyright 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.compatui.letterbox + +import android.content.Context +import android.graphics.Rect +import android.view.SurfaceControl +import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn +import com.android.wm.shell.compatui.letterbox.LetterboxMatchers.asAnyMode +import org.mockito.kotlin.any +import org.mockito.kotlin.clearInvocations +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify + +/** + * Robot to test [LetterboxController] implementations. + */ +open class LetterboxControllerRobotTest( + ctx: Context, + controllerBuilder: (LetterboxSurfaceBuilder) -> LetterboxController +) { + + companion object { + @JvmStatic + private val DISPLAY_ID = 1 + + @JvmStatic + private val TASK_ID = 20 + } + + private val letterboxConfiguration: LetterboxConfiguration + private val surfaceBuilder: LetterboxSurfaceBuilder + private val letterboxController: LetterboxController + private val transaction: SurfaceControl.Transaction + private val parentLeash: SurfaceControl + + init { + letterboxConfiguration = LetterboxConfiguration(ctx) + surfaceBuilder = LetterboxSurfaceBuilder(letterboxConfiguration) + letterboxController = controllerBuilder(surfaceBuilder) + transaction = getTransactionMock() + parentLeash = mock<SurfaceControl>() + spyOn(surfaceBuilder) + } + + fun sendCreateSurfaceRequest( + displayId: Int = DISPLAY_ID, + taskId: Int = TASK_ID + ) = letterboxController.createLetterboxSurface( + key = LetterboxKey(displayId, taskId), + transaction = transaction, + parentLeash = parentLeash + ) + + fun sendDestroySurfaceRequest( + displayId: Int = DISPLAY_ID, + taskId: Int = TASK_ID + ) = letterboxController.destroyLetterboxSurface( + key = LetterboxKey(displayId, taskId), + transaction = transaction + ) + + fun sendUpdateSurfaceVisibilityRequest( + displayId: Int = DISPLAY_ID, + taskId: Int = TASK_ID, + visible: Boolean + ) = letterboxController.updateLetterboxSurfaceVisibility( + key = LetterboxKey(displayId, taskId), + transaction = transaction, + visible = visible + ) + + fun sendUpdateSurfaceBoundsRequest( + displayId: Int = DISPLAY_ID, + taskId: Int = TASK_ID, + taskBounds: Rect, + activityBounds: Rect + ) = letterboxController.updateLetterboxSurfaceBounds( + key = LetterboxKey(displayId, taskId), + transaction = transaction, + taskBounds = taskBounds, + activityBounds = activityBounds + ) + + fun invokeDump() { + letterboxController.dump() + } + + fun checkSurfaceBuilderInvoked(times: Int = 1, name: String = "", callSite: String = "") { + verify(surfaceBuilder, times(times)).createSurface( + eq(transaction), + eq(parentLeash), + name.asAnyMode(), + callSite.asAnyMode(), + any() + ) + } + + fun checkTransactionRemovedInvoked(times: Int = 1) { + verify(transaction, times(times)).remove(any()) + } + + fun checkVisibilityUpdated(times: Int = 1, expectedVisibility: Boolean) { + verify(transaction, times(times)).setVisibility(any(), eq(expectedVisibility)) + } + + fun checkSurfacePositionUpdated( + times: Int = 1, + expectedX: Float = -1f, + expectedY: Float = -1f + ) { + verify(transaction, times(times)).setPosition( + any(), + expectedX.asAnyMode(), + expectedY.asAnyMode() + ) + } + + fun checkSurfaceSizeUpdated(times: Int = 1, expectedWidth: Int = -1, expectedHeight: Int = -1) { + verify(transaction, times(times)).setWindowCrop( + any(), + expectedWidth.asAnyMode(), + expectedHeight.asAnyMode() + ) + } + + fun resetTransitionTest() { + clearInvocations(transaction) + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategyTest.kt new file mode 100644 index 000000000000..50fdf4510061 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategyTest.kt @@ -0,0 +1,103 @@ +/* + * Copyright 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.compatui.letterbox + +import android.content.Context +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.MULTIPLE_SURFACES +import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.SINGLE_SURFACE +import java.util.function.Consumer +import kotlin.test.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Tests for [LetterboxControllerStrategy]. + * + * Build/Install/Run: + * atest WMShellUnitTests:LetterboxControllerStrategyTest + */ +@RunWith(AndroidTestingRunner::class) +@SmallTest +class LetterboxControllerStrategyTest : ShellTestCase() { + + @Test + fun `LetterboxMode is MULTIPLE_SURFACES with rounded corners`() { + runTestScenario { r -> + r.configureRoundedCornerRadius(true) + r.configureLetterboxMode() + r.checkLetterboxModeIsSingle() + } + } + + @Test + fun `LetterboxMode is MULTIPLE_SURFACES with no rounded corners`() { + runTestScenario { r -> + r.configureRoundedCornerRadius(false) + r.configureLetterboxMode() + r.checkLetterboxModeIsMultiple() + } + } + + /** + * Runs a test scenario providing a Robot. + */ + fun runTestScenario(consumer: Consumer<LetterboxStrategyRobotTest>) { + val robot = LetterboxStrategyRobotTest(mContext) + consumer.accept(robot) + } + + class LetterboxStrategyRobotTest(val ctx: Context) { + + companion object { + @JvmStatic + private val ROUNDED_CORNERS_TRUE = 10 + @JvmStatic + private val ROUNDED_CORNERS_FALSE = 0 + } + + private val letterboxConfiguration: LetterboxConfiguration + private val letterboxStrategy: LetterboxControllerStrategy + + init { + letterboxConfiguration = LetterboxConfiguration(ctx) + letterboxStrategy = LetterboxControllerStrategy(letterboxConfiguration) + } + + fun configureRoundedCornerRadius(enabled: Boolean) { + letterboxConfiguration.setLetterboxActivityCornersRadius( + if (enabled) ROUNDED_CORNERS_TRUE else ROUNDED_CORNERS_FALSE + ) + } + + fun configureLetterboxMode() { + letterboxStrategy.configureLetterboxMode() + } + + fun checkLetterboxModeIsSingle(expected: Boolean = true) { + val expectedMode = if (expected) SINGLE_SURFACE else MULTIPLE_SURFACES + assertEquals(expectedMode, letterboxStrategy.getLetterboxImplementationMode()) + } + + fun checkLetterboxModeIsMultiple(expected: Boolean = true) { + val expectedMode = if (expected) MULTIPLE_SURFACES else SINGLE_SURFACE + assertEquals(expectedMode, letterboxStrategy.getLetterboxImplementationMode()) + } + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilderTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilderTest.kt index 68d9bf9b926f..c37913e47cca 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilderTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilderTest.kt @@ -28,11 +28,8 @@ import org.junit.runner.RunWith import org.mockito.ArgumentMatchers import org.mockito.Mockito.verify import org.mockito.kotlin.anyOrNull -import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq -import org.mockito.kotlin.never import org.mockito.kotlin.times -import org.mockito.verification.VerificationMode /** * Tests for [LetterboxSurfaceBuilder]. @@ -87,9 +84,7 @@ class LetterboxSurfaceBuilderTest : ShellTestCase() { init { letterboxConfiguration = LetterboxConfiguration(ctx) letterboxSurfaceBuilder = LetterboxSurfaceBuilder(letterboxConfiguration) - tx = org.mockito.kotlin.mock<SurfaceControl.Transaction>() - doReturn(tx).`when`(tx).setLayer(anyOrNull(), anyOrNull()) - doReturn(tx).`when`(tx).setColorSpaceAgnostic(anyOrNull(), anyOrNull()) + tx = getTransactionMock() parentLeash = org.mockito.kotlin.mock<SurfaceControl>() surfaceBuilder = SurfaceControl.Builder() spyOn(surfaceBuilder) @@ -140,7 +135,5 @@ class LetterboxSurfaceBuilderTest : ShellTestCase() { val components = letterboxConfiguration.getBackgroundColorRgbArray() verify(tx, expected.asMode()).setColor(anyOrNull(), eq(components)) } - - private fun Boolean.asMode(): VerificationMode = if (this) times(1) else never() } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTestUtils.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTestUtils.kt new file mode 100644 index 000000000000..2c06dfda7917 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTestUtils.kt @@ -0,0 +1,51 @@ +/* + * Copyright 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.compatui.letterbox + +import android.view.SurfaceControl +import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.times +import org.mockito.verification.VerificationMode + +/** + * @return A [SurfaceControl.Transaction] mock supporting chaining for some operations. Please + * add other operations if needed. + */ +fun getTransactionMock(): SurfaceControl.Transaction = mock<SurfaceControl.Transaction>().apply { + doReturn(this).`when`(this).setLayer(anyOrNull(), anyOrNull()) + doReturn(this).`when`(this).setColorSpaceAgnostic(anyOrNull(), anyOrNull()) + doReturn(this).`when`(this).setPosition(anyOrNull(), any(), any()) + doReturn(this).`when`(this).setWindowCrop(anyOrNull(), any(), any()) +} + +// Utility to make verification mode depending on a [Boolean]. +fun Boolean.asMode(): VerificationMode = if (this) times(1) else never() + +// Utility matchers to use for the main types as Mockito [VerificationMode]. +object LetterboxMatchers { + fun Int.asAnyMode() = asAnyMode { this < 0 } + fun Float.asAnyMode() = asAnyMode { this < 0f } + fun String.asAnyMode() = asAnyMode { this.isEmpty() } +} + +private inline fun <reified T : Any> T.asAnyMode(condition: () -> Boolean) = + (if (condition()) any() else eq(this)) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt index 9c6afcb8be63..78bb721d1028 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt @@ -25,9 +25,12 @@ import android.testing.AndroidTestingRunner import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_CLOSE import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn import com.android.window.flags.Flags import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.common.transition.TransitionStateHolder +import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import com.android.wm.shell.util.TransitionObserverInputBuilder @@ -37,12 +40,11 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.mock -import org.mockito.kotlin.never import org.mockito.kotlin.times import org.mockito.kotlin.verify -import org.mockito.verification.VerificationMode /** * Tests for [LetterboxTransitionObserver]. @@ -94,6 +96,7 @@ class LetterboxTransitionObserverTest : ShellTestCase() { validateOutput { r.creationEventDetected(expected = false) + r.configureStrategyInvoked(expected = false) r.visibilityEventDetected(expected = false) r.destroyEventDetected(expected = false) r.updateSurfaceBoundsEventDetected(expected = false) @@ -121,6 +124,7 @@ class LetterboxTransitionObserverTest : ShellTestCase() { validateOutput { r.creationEventDetected(expected = true) + r.configureStrategyInvoked(expected = true) r.visibilityEventDetected(expected = true, visible = true) r.destroyEventDetected(expected = false) r.updateSurfaceBoundsEventDetected( @@ -154,21 +158,38 @@ class LetterboxTransitionObserverTest : ShellTestCase() { } @Test - fun `When closing change letterbox surface destroy is triggered`() { + fun `When closing change with no recents running letterbox surfaces are destroyed`() { runTestScenario { r -> executeTransitionObserverTest(observerFactory = r.observerFactory) { r.invokeShellInit() inputBuilder { buildTransitionInfo() + r.configureRecentsState(running = false) r.createClosingChange(inputBuilder = this) } validateOutput { r.destroyEventDetected(expected = true) - r.creationEventDetected(expected = false) - r.visibilityEventDetected(expected = false, visible = false) - r.updateSurfaceBoundsEventDetected(expected = false) + } + } + } + } + + @Test + fun `When closing change and recents are running letterbox surfaces are not destroyed`() { + runTestScenario { r -> + executeTransitionObserverTest(observerFactory = r.observerFactory) { + r.invokeShellInit() + + inputBuilder { + buildTransitionInfo() + r.createClosingChange(inputBuilder = this) + r.configureRecentsState(running = true) + } + + validateOutput { + r.destroyEventDetected(expected = false) } } } @@ -197,6 +218,8 @@ class LetterboxTransitionObserverTest : ShellTestCase() { private val transitions: Transitions private val letterboxController: LetterboxController private val letterboxObserver: LetterboxTransitionObserver + private val transitionStateHolder: TransitionStateHolder + private val letterboxStrategy: LetterboxControllerStrategy val observerFactory: () -> LetterboxTransitionObserver @@ -205,8 +228,18 @@ class LetterboxTransitionObserverTest : ShellTestCase() { shellInit = ShellInit(executor) transitions = mock<Transitions>() letterboxController = mock<LetterboxController>() + letterboxStrategy = mock<LetterboxControllerStrategy>() + transitionStateHolder = + TransitionStateHolder(shellInit, mock<RecentsTransitionHandler>()) + spyOn(transitionStateHolder) letterboxObserver = - LetterboxTransitionObserver(shellInit, transitions, letterboxController) + LetterboxTransitionObserver( + shellInit, + transitions, + letterboxController, + transitionStateHolder, + letterboxStrategy + ) observerFactory = { letterboxObserver } } @@ -218,6 +251,10 @@ class LetterboxTransitionObserverTest : ShellTestCase() { verify(transitions, expected.asMode()).registerObserver(observer()) } + fun configureRecentsState(running: Boolean) { + doReturn(running).`when`(transitionStateHolder).isRecentsTransitionRunning() + } + fun creationEventDetected( expected: Boolean, displayId: Int = DISPLAY_ID, @@ -258,16 +295,21 @@ class LetterboxTransitionObserverTest : ShellTestCase() { expected: Boolean, displayId: Int = DISPLAY_ID, taskId: Int = TASK_ID, - taskBounds: Rect = Rect() + taskBounds: Rect = Rect(), + activityBounds: Rect = Rect() ) = verify( letterboxController, expected.asMode() ).updateLetterboxSurfaceBounds( eq(LetterboxKey(displayId, taskId)), any<SurfaceControl.Transaction>(), - eq(taskBounds) + eq(taskBounds), + eq(activityBounds) ) + fun configureStrategyInvoked(expected: Boolean) = + verify(letterboxStrategy, expected.asMode()).configureLetterboxMode() + fun createTopActivityChange( inputBuilder: TransitionObserverInputBuilder, isLetterboxed: Boolean = true, @@ -300,7 +342,5 @@ class LetterboxTransitionObserverTest : ShellTestCase() { this.displayId = displayId }, changeMode = TRANSIT_CLOSE) } - - private fun Boolean.asMode(): VerificationMode = if (this) times(1) else never() } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt new file mode 100644 index 000000000000..06b805233ee7 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt @@ -0,0 +1,151 @@ +/* + * Copyright 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.compatui.letterbox + +import android.content.Context +import android.graphics.Rect +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import java.util.function.Consumer +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify + +/** + * Tests for [LetterboxUtils]. + * + * Build/Install/Run: + * atest WMShellUnitTests:LetterboxUtilsTest + */ +@RunWith(AndroidTestingRunner::class) +@SmallTest +class LetterboxUtilsTest : ShellTestCase() { + + val firstLetterboxController = mock<LetterboxController>() + val secondLetterboxController = mock<LetterboxController>() + val thirdLetterboxController = mock<LetterboxController>() + + private val letterboxControllerBuilder: (LetterboxSurfaceBuilder) -> LetterboxController = + { _ -> + firstLetterboxController.append(secondLetterboxController) + .append(thirdLetterboxController) + } + + @Test + fun `Appended LetterboxController invoked creation on all the controllers`() { + runTestScenario { r -> + r.sendCreateSurfaceRequest() + + r.verifyCreateSurfaceInvokedWithRequest(target = firstLetterboxController) + r.verifyCreateSurfaceInvokedWithRequest(target = secondLetterboxController) + r.verifyCreateSurfaceInvokedWithRequest(target = thirdLetterboxController) + } + } + + @Test + fun `Appended LetterboxController invoked destroy on all the controllers`() { + runTestScenario { r -> + r.sendDestroySurfaceRequest() + r.verifyDestroySurfaceInvokedWithRequest(target = firstLetterboxController) + r.verifyDestroySurfaceInvokedWithRequest(target = secondLetterboxController) + r.verifyDestroySurfaceInvokedWithRequest(target = thirdLetterboxController) + } + } + + @Test + fun `Appended LetterboxController invoked update visibility on all the controllers`() { + runTestScenario { r -> + r.sendUpdateSurfaceVisibilityRequest(visible = true) + r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = firstLetterboxController) + r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = secondLetterboxController) + r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = thirdLetterboxController) + } + } + + @Test + fun `Appended LetterboxController invoked update bounds on all the controllers`() { + runTestScenario { r -> + r.sendUpdateSurfaceBoundsRequest(taskBounds = Rect(), activityBounds = Rect()) + r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = firstLetterboxController) + r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = secondLetterboxController) + r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = thirdLetterboxController) + } + } + + @Test + fun `Appended LetterboxController invoked update dump on all the controllers`() { + runTestScenario { r -> + r.invokeDump() + r.verifyDumpInvoked(target = firstLetterboxController) + r.verifyDumpInvoked(target = secondLetterboxController) + r.verifyDumpInvoked(target = thirdLetterboxController) + } + } + + /** + * Runs a test scenario providing a Robot. + */ + fun runTestScenario(consumer: Consumer<AppendLetterboxControllerRobotTest>) { + val robot = AppendLetterboxControllerRobotTest(mContext, letterboxControllerBuilder) + consumer.accept(robot) + } + + class AppendLetterboxControllerRobotTest( + ctx: Context, + builder: (LetterboxSurfaceBuilder) -> LetterboxController + ) : LetterboxControllerRobotTest(ctx, builder) { + + fun verifyCreateSurfaceInvokedWithRequest( + target: LetterboxController, + times: Int = 1 + ) { + verify(target, times(times)).createLetterboxSurface(any(), any(), any()) + } + + fun verifyDestroySurfaceInvokedWithRequest( + target: LetterboxController, + times: Int = 1 + ) { + verify(target, times(times)).destroyLetterboxSurface(any(), any()) + } + + fun verifyUpdateVisibilitySurfaceInvokedWithRequest( + target: LetterboxController, + times: Int = 1 + ) { + verify(target, times(times)).updateLetterboxSurfaceVisibility(any(), any(), any()) + } + + fun verifyUpdateSurfaceBoundsInvokedWithRequest( + target: LetterboxController, + times: Int = 1 + ) { + verify(target, times(times)).updateLetterboxSurfaceBounds(any(), any(), any(), any()) + } + + fun verifyDumpInvoked( + target: LetterboxController, + times: Int = 1 + ) { + verify(target, times(times)).dump() + } + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxControllerTest.kt new file mode 100644 index 000000000000..e6bff4c1ec15 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxControllerTest.kt @@ -0,0 +1,128 @@ +/* + * Copyright 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.compatui.letterbox + +import android.content.Context +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode +import java.util.function.Consumer +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify + +/** + * Tests for [MixedLetterboxController]. + * + * Build/Install/Run: + * atest WMShellUnitTests:MixedLetterboxControllerTest + */ +@RunWith(AndroidTestingRunner::class) +@SmallTest +class MixedLetterboxControllerTest : ShellTestCase() { + + @Test + fun `When strategy is SINGLE_SURFACE and a create request is sent multi are destroyed`() { + runTestScenario { r -> + r.configureStrategyFor(LetterboxMode.SINGLE_SURFACE) + r.sendCreateSurfaceRequest() + r.checkCreateInvokedOnSingleController() + r.checkDestroyInvokedOnMultiController() + } + } + + @Test + fun `When strategy is MULTIPLE_SURFACES and a create request is sent single is destroyed`() { + runTestScenario { r -> + r.configureStrategyFor(LetterboxMode.MULTIPLE_SURFACES) + r.sendCreateSurfaceRequest() + r.checkDestroyInvokedOnSingleController() + r.checkCreateInvokedOnMultiController() + } + } + + /** + * Runs a test scenario providing a Robot. + */ + fun runTestScenario(consumer: Consumer<MixedLetterboxControllerRobotTest>) { + val robot = MixedLetterboxControllerRobotTest(mContext, ObjectToTestHolder()) + consumer.accept(robot) + } + + class MixedLetterboxControllerRobotTest( + ctx: Context, + private val objectToTestHolder: ObjectToTestHolder + ) : LetterboxControllerRobotTest(ctx, objectToTestHolder.controllerBuilder) { + + fun configureStrategyFor(letterboxMode: LetterboxMode) { + doReturn(letterboxMode).`when`(objectToTestHolder.controllerStrategy) + .getLetterboxImplementationMode() + } + + fun checkCreateInvokedOnSingleController(times: Int = 1) { + verify( + objectToTestHolder.singleLetterboxController, + times(times) + ).createLetterboxSurface(any(), any(), any()) + } + + fun checkCreateInvokedOnMultiController(times: Int = 1) { + verify( + objectToTestHolder.multipleLetterboxController, + times(times) + ).createLetterboxSurface(any(), any(), any()) + } + + fun checkDestroyInvokedOnSingleController(times: Int = 1) { + verify( + objectToTestHolder.singleLetterboxController, + times(times) + ).destroyLetterboxSurface(any(), any()) + } + + fun checkDestroyInvokedOnMultiController(times: Int = 1) { + verify( + objectToTestHolder.multipleLetterboxController, + times(times) + ).destroyLetterboxSurface(any(), any()) + } + } + + data class ObjectToTestHolder( + val singleLetterboxController: SingleSurfaceLetterboxController = + mock<SingleSurfaceLetterboxController>(), + val multipleLetterboxController: MultiSurfaceLetterboxController = + mock<MultiSurfaceLetterboxController>(), + val controllerStrategy: LetterboxControllerStrategy = mock<LetterboxControllerStrategy>() + ) { + + private val mixedController = + MixedLetterboxController( + singleLetterboxController, + multipleLetterboxController, + controllerStrategy + ) + + val controllerBuilder: (LetterboxSurfaceBuilder) -> LetterboxController = + { _ -> mixedController } + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxControllerTest.kt new file mode 100644 index 000000000000..295d4edf206b --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxControllerTest.kt @@ -0,0 +1,155 @@ +/* + * Copyright 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.compatui.letterbox + +import android.graphics.Rect +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import java.util.function.Consumer +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Tests for [MultiSurfaceLetterboxController]. + * + * Build/Install/Run: + * atest WMShellUnitTests:MultiSurfaceLetterboxControllerTest + */ +@RunWith(AndroidTestingRunner::class) +@SmallTest +class MultiSurfaceLetterboxControllerTest : ShellTestCase() { + + @Test + fun `When creation is requested the surfaces are created if not present`() { + runTestScenario { r -> + r.sendCreateSurfaceRequest() + + r.checkSurfaceBuilderInvoked(times = 4) + } + } + + @Test + fun `When creation is requested multiple times the surfaces are created once`() { + runTestScenario { r -> + r.sendCreateSurfaceRequest() + r.sendCreateSurfaceRequest() + r.sendCreateSurfaceRequest() + r.sendCreateSurfaceRequest() + + r.checkSurfaceBuilderInvoked(times = 4) + } + } + + @Test + fun `Different surfaces are created for every key`() { + runTestScenario { r -> + r.sendCreateSurfaceRequest() + r.sendCreateSurfaceRequest() + r.sendCreateSurfaceRequest(displayId = 2) + r.sendCreateSurfaceRequest(displayId = 2, taskId = 2) + r.sendCreateSurfaceRequest(displayId = 2) + r.sendCreateSurfaceRequest(displayId = 2, taskId = 2) + + r.checkSurfaceBuilderInvoked(times = 12) + } + } + + @Test + fun `Created surface are removed once`() { + runTestScenario { r -> + r.sendCreateSurfaceRequest() + r.checkSurfaceBuilderInvoked(times = 4) + + r.sendDestroySurfaceRequest() + r.sendDestroySurfaceRequest() + r.sendDestroySurfaceRequest() + + r.checkTransactionRemovedInvoked(times = 4) + } + } + + @Test + fun `Only existing surfaces receive visibility update`() { + runTestScenario { r -> + r.sendCreateSurfaceRequest() + r.sendUpdateSurfaceVisibilityRequest(visible = true) + r.sendUpdateSurfaceVisibilityRequest(visible = true, displayId = 20) + + r.checkVisibilityUpdated(times = 4, expectedVisibility = true) + } + } + + @Test + fun `Only existing surfaces receive taskBounds update`() { + runTestScenario { r -> + r.sendUpdateSurfaceBoundsRequest( + taskBounds = Rect(0, 0, 2000, 1000), + activityBounds = Rect(500, 0, 1500, 1000) + ) + + r.checkSurfacePositionUpdated(times = 0) + r.checkSurfaceSizeUpdated(times = 0) + + r.sendCreateSurfaceRequest() + + // Pillarbox. + r.sendUpdateSurfaceBoundsRequest( + taskBounds = Rect(0, 0, 2000, 1000), + activityBounds = Rect(500, 0, 1500, 1000) + ) + // The Left and Top surfaces. + r.checkSurfacePositionUpdated(times = 2, expectedX = 0f, expectedY = 0f) + // The Right surface. + r.checkSurfacePositionUpdated(times = 1, expectedX = 1500f, expectedY = 0f) + // The Bottom surface. + r.checkSurfacePositionUpdated(times = 1, expectedX = 0f, expectedY = 1000f) + // Left and Right surface. + r.checkSurfaceSizeUpdated(times = 2, expectedWidth = 500, expectedHeight = 1000) + // Top and Button. + r.checkSurfaceSizeUpdated(times = 2, expectedWidth = 2000, expectedHeight = 0) + + r.resetTransitionTest() + + // Letterbox. + r.sendUpdateSurfaceBoundsRequest( + taskBounds = Rect(0, 0, 1000, 2000), + activityBounds = Rect(0, 500, 1000, 1500) + ) + // Top and Left surfaces. + r.checkSurfacePositionUpdated(times = 2, expectedX = 0f, expectedY = 0f) + // Bottom surface. + r.checkSurfacePositionUpdated(times = 1, expectedX = 0f, expectedY = 1500f) + // Right surface. + r.checkSurfacePositionUpdated(times = 1, expectedX = 1000f, expectedY = 0f) + + // Left and Right surfaces. + r.checkSurfaceSizeUpdated(times = 2, expectedWidth = 0, expectedHeight = 2000) + // Top and Button surfaces, + r.checkSurfaceSizeUpdated(times = 2, expectedWidth = 1000, expectedHeight = 500) + } + } + + /** + * Runs a test scenario providing a Robot. + */ + fun runTestScenario(consumer: Consumer<LetterboxControllerRobotTest>) { + val robot = + LetterboxControllerRobotTest(mContext, { sb -> MultiSurfaceLetterboxController(sb) }) + consumer.accept(robot) + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxControllerTest.kt new file mode 100644 index 000000000000..125e700bcd42 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxControllerTest.kt @@ -0,0 +1,128 @@ +/* + * Copyright 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.compatui.letterbox + +import android.graphics.Rect +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import java.util.function.Consumer +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Tests for [SingleSurfaceLetterboxController]. + * + * Build/Install/Run: + * atest WMShellUnitTests:SingleSurfaceLetterboxControllerTest + */ +@RunWith(AndroidTestingRunner::class) +@SmallTest +class SingleSurfaceLetterboxControllerTest : ShellTestCase() { + + @Test + fun `When creation is requested the surface is created if not present`() { + runTestScenario { r -> + r.sendCreateSurfaceRequest() + + r.checkSurfaceBuilderInvoked() + } + } + + @Test + fun `When creation is requested multiple times the surface is created once`() { + runTestScenario { r -> + r.sendCreateSurfaceRequest() + r.sendCreateSurfaceRequest() + r.sendCreateSurfaceRequest() + r.sendCreateSurfaceRequest() + + r.checkSurfaceBuilderInvoked(times = 1) + } + } + + @Test + fun `A different surface is created for every key`() { + runTestScenario { r -> + r.sendCreateSurfaceRequest() + r.sendCreateSurfaceRequest() + r.sendCreateSurfaceRequest(displayId = 2) + r.sendCreateSurfaceRequest(displayId = 2, taskId = 2) + r.sendCreateSurfaceRequest(displayId = 2) + r.sendCreateSurfaceRequest(displayId = 2, taskId = 2) + + r.checkSurfaceBuilderInvoked(times = 3) + } + } + + @Test + fun `Created surface is removed once`() { + runTestScenario { r -> + r.sendCreateSurfaceRequest() + r.checkSurfaceBuilderInvoked() + + r.sendDestroySurfaceRequest() + r.sendDestroySurfaceRequest() + r.sendDestroySurfaceRequest() + + r.checkTransactionRemovedInvoked() + } + } + + @Test + fun `Only existing surfaces receive visibility update`() { + runTestScenario { r -> + r.sendCreateSurfaceRequest() + r.sendUpdateSurfaceVisibilityRequest(visible = true) + r.sendUpdateSurfaceVisibilityRequest(visible = true, displayId = 20) + + r.checkVisibilityUpdated(expectedVisibility = true) + } + } + + @Test + fun `Only existing surfaces receive taskBounds update`() { + runTestScenario { r -> + r.sendUpdateSurfaceBoundsRequest( + taskBounds = Rect(0, 0, 2000, 1000), + activityBounds = Rect(500, 0, 1500, 1000) + ) + + r.checkSurfacePositionUpdated(times = 0) + r.checkSurfaceSizeUpdated(times = 0) + + r.resetTransitionTest() + + r.sendCreateSurfaceRequest() + r.sendUpdateSurfaceBoundsRequest( + taskBounds = Rect(0, 0, 2000, 1000), + activityBounds = Rect(500, 0, 1500, 1000) + ) + r.checkSurfacePositionUpdated(times = 1, expectedX = 0f, expectedY = 0f) + r.checkSurfaceSizeUpdated(times = 1, expectedWidth = 2000, expectedHeight = 1000) + } + } + + /** + * Runs a test scenario providing a Robot. + */ + fun runTestScenario(consumer: Consumer<LetterboxControllerRobotTest>) { + val robot = + LetterboxControllerRobotTest(mContext, { sb -> SingleSurfaceLetterboxController(sb) }) + consumer.accept(robot) + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt index 2ea0379e3bf7..41a594a3347a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt @@ -23,6 +23,7 @@ import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT import android.graphics.Rect import android.os.Binder +import android.os.UserManager import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner @@ -96,11 +97,12 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { @Mock lateinit var taskStackListener: TaskStackListenerImpl @Mock lateinit var persistentRepository: DesktopPersistentRepository @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer + @Mock lateinit var userManager: UserManager private lateinit var mockitoSession: StaticMockitoSession private lateinit var handler: DesktopActivityOrientationChangeHandler private lateinit var shellInit: ShellInit - private lateinit var taskRepository: DesktopRepository + private lateinit var userRepositories: DesktopUserRepositories private lateinit var testScope: CoroutineScope // Mock running tasks are registered here so we can get the list from mock shell task organizer. private val runningTasks = mutableListOf<RunningTaskInfo>() @@ -117,13 +119,14 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) shellInit = spy(ShellInit(testExecutor)) - taskRepository = - DesktopRepository( + userRepositories = + DesktopUserRepositories( context, shellInit, persistentRepository, repositoryInitializer, - testScope + testScope, + userManager ) whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks } whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() } @@ -132,7 +135,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { ) handler = DesktopActivityOrientationChangeHandler(context, shellInit, shellTaskOrganizer, - taskStackListener, resizeTransitionHandler, taskRepository) + taskStackListener, resizeTransitionHandler, userRepositories) shellInit.init() } @@ -156,7 +159,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { clearInvocations(shellInit) handler = DesktopActivityOrientationChangeHandler(context, shellInit, shellTaskOrganizer, - taskStackListener, resizeTransitionHandler, taskRepository) + taskStackListener, resizeTransitionHandler, userRepositories) verify(shellInit, never()).addInitCallback(any(), any<DesktopActivityOrientationChangeHandler>()) @@ -180,7 +183,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { activityInfo.screenOrientation = SCREEN_ORIENTATION_PORTRAIT task.topActivityInfo = activityInfo whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) - taskRepository.addTask(DEFAULT_DISPLAY, task.taskId, isVisible = true) + userRepositories.current.addTask(DEFAULT_DISPLAY, task.taskId, isVisible = true) runningTasks.add(task) taskStackListener.onActivityRequestedOrientationChanged(task.taskId, @@ -203,7 +206,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { @Test fun handleActivityOrientationChange_notInDesktopMode_doNothing() { val task = setUpFreeformTask(isResizeable = false) - taskRepository.updateTask(task.displayId, task.taskId, isVisible = false) + userRepositories.current.updateTask(task.displayId, task.taskId, isVisible = false) taskStackListener.onActivityRequestedOrientationChanged(task.taskId, SCREEN_ORIENTATION_LANDSCAPE) @@ -268,7 +271,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { task.topActivityInfo = activityInfo task.isResizeable = isResizeable whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) - taskRepository.addTask(displayId, task.taskId, isVisible = true) + userRepositories.current.addTask(displayId, task.taskId, isVisible = true) runningTasks.add(task) return task } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt index b57c55c4c45a..db4c7465ae48 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt @@ -76,18 +76,19 @@ class DesktopImmersiveControllerTest : ShellTestCase() { @JvmField @Rule val animatorTestRule = AnimatorTestRule(this) @Mock private lateinit var mockTransitions: Transitions - private lateinit var desktopRepository: DesktopRepository + private lateinit var userRepositories: DesktopUserRepositories @Mock private lateinit var mockDisplayController: DisplayController @Mock private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer @Mock private lateinit var mockDisplayLayout: DisplayLayout private val transactionSupplier = { StubTransaction() } private lateinit var controller: DesktopImmersiveController + private lateinit var desktopRepository: DesktopRepository @Before fun setUp() { - desktopRepository = DesktopRepository( - context, ShellInit(TestShellExecutor()), mock(), mock(), mock() + userRepositories = DesktopUserRepositories( + context, ShellInit(TestShellExecutor()), mock(), mock(), mock(), mock() ) whenever(mockDisplayController.getDisplayLayout(DEFAULT_DISPLAY)) .thenReturn(mockDisplayLayout) @@ -97,12 +98,13 @@ class DesktopImmersiveControllerTest : ShellTestCase() { controller = DesktopImmersiveController( shellInit = mock(), transitions = mockTransitions, - desktopRepository = desktopRepository, + desktopUserRepositories = userRepositories, displayController = mockDisplayController, shellTaskOrganizer = mockShellTaskOrganizer, shellCommandHandler = mock(), transactionSupplier = transactionSupplier, ) + desktopRepository = userRepositories.current } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt index 62717a32d99f..49a7e2951a7e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt @@ -21,6 +21,7 @@ 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.WindowingMode +import android.content.Intent import android.os.Binder import android.os.Handler import android.os.IBinder @@ -36,7 +37,9 @@ import android.view.WindowManager.TRANSIT_NONE import android.view.WindowManager.TRANSIT_OPEN import android.view.WindowManager.TRANSIT_TO_BACK import android.view.WindowManager.TransitionType +import android.window.IWindowContainerToken import android.window.TransitionInfo +import android.window.WindowContainerToken import android.window.WindowContainerTransaction import androidx.test.filters.SmallTest import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE @@ -60,6 +63,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock +import org.mockito.Mockito import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.kotlin.any @@ -84,7 +88,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { @Mock lateinit var transitions: Transitions @Mock - lateinit var desktopRepository: DesktopRepository + lateinit var userRepositories: DesktopUserRepositories @Mock lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler @Mock @@ -103,16 +107,21 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { lateinit var shellInit: ShellInit @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer + @Mock + private lateinit var desktopRepository: DesktopRepository private lateinit var mixedHandler: DesktopMixedTransitionHandler + @Before fun setUp() { + whenever(userRepositories.current).thenReturn(desktopRepository) + whenever(userRepositories.getProfile(Mockito.anyInt())).thenReturn(desktopRepository) mixedHandler = DesktopMixedTransitionHandler( context, transitions, - desktopRepository, + userRepositories, freeformTaskTransitionHandler, closeDesktopTaskTransitionHandler, desktopImmersiveController, @@ -146,7 +155,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { } @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS) + @DisableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX) fun startRemoveTransition_callsFreeformTaskTransitionHandler() { val wct = WindowContainerTransaction() whenever(freeformTaskTransitionHandler.startRemoveTransition(wct)) @@ -158,7 +169,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { } @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS) + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX) fun startRemoveTransition_startsCloseTransition() { val wct = WindowContainerTransaction() whenever(transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, mixedHandler)) @@ -178,7 +191,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { fun startAnimation_withoutClosingDesktopTask_returnsFalse() { val transition = mock<IBinder>() val transitionInfo = - createTransitionInfo( + createCloseTransitionInfo( changeMode = TRANSIT_OPEN, task = createTask(WINDOWING_MODE_FREEFORM) ) @@ -197,12 +210,13 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { } @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS) + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX) fun startAnimation_withClosingDesktopTask_callsCloseTaskHandler() { val wct = WindowContainerTransaction() val transition = mock<IBinder>() - val transitionInfo = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM)) - whenever(desktopRepository.getExpandedTaskCount(any())).thenReturn(2) + val transitionInfo = createCloseTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM)) whenever( closeDesktopTaskTransitionHandler.startAnimation(any(), any(), any(), any(), any()) ) @@ -225,12 +239,14 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { } @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS) + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX) fun startAnimation_withClosingLastDesktopTask_dispatchesTransition() { val wct = WindowContainerTransaction() val transition = mock<IBinder>() - val transitionInfo = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM)) - whenever(desktopRepository.getExpandedTaskCount(any())).thenReturn(1) + val transitionInfo = createCloseTransitionInfo( + task = createTask(WINDOWING_MODE_FREEFORM), withWallpaper = true) whenever(transitions.dispatchTransition(any(), any(), any(), any(), any(), any())) .thenReturn(mock()) whenever(transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, mixedHandler)) @@ -266,7 +282,8 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { @Test @DisableFlags( Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP, - Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS) + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS, + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX) fun startLaunchTransition_immersiveAndAppLaunchFlagsDisabled_doesNotUseMixedHandler() { val wct = WindowContainerTransaction() val task = createTask(WINDOWING_MODE_FREEFORM) @@ -302,7 +319,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { } @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS) + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS, + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX) fun startLaunchTransition_desktopAppLaunchEnabled_usesMixedHandler() { val wct = WindowContainerTransaction() val task = createTask(WINDOWING_MODE_FREEFORM) @@ -338,7 +357,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { val otherChange = createChange(createTask(WINDOWING_MODE_FREEFORM)) mixedHandler.startAnimation( transition, - createTransitionInfo( + createCloseTransitionInfo( TRANSIT_OPEN, listOf(launchTaskChange, otherChange) ), @@ -378,7 +397,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { val immersiveChange = createChange(immersiveTask) mixedHandler.startAnimation( transition, - createTransitionInfo( + createCloseTransitionInfo( TRANSIT_OPEN, listOf(launchTaskChange, immersiveChange) ), @@ -401,7 +420,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { } @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS) + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS, + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX) fun startAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() { val wct = WindowContainerTransaction() val launchingTask = createTask(WINDOWING_MODE_FREEFORM) @@ -418,7 +439,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { ) mixedHandler.startAnimation( transition, - createTransitionInfo( + createCloseTransitionInfo( TRANSIT_OPEN, listOf(launchTaskChange) ), @@ -431,7 +452,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { } @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS) + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS, + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX) fun startAndAnimateLaunchTransition_withMinimizeChange_reparentsMinimizeChange() { val wct = WindowContainerTransaction() val launchingTask = createTask(WINDOWING_MODE_FREEFORM) @@ -450,7 +473,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { ) mixedHandler.startAnimation( transition, - createTransitionInfo( + createCloseTransitionInfo( TRANSIT_OPEN, listOf(launchTaskChange, minimizeChange) ), @@ -463,7 +486,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { } @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS) + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS, + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX) fun startAnimation_pendingTransition_noLaunchChange_returnsFalse() { val wct = WindowContainerTransaction() val launchingTask = createTask(WINDOWING_MODE_FREEFORM) @@ -482,7 +507,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { val started = mixedHandler.startAnimation( transition, - createTransitionInfo( + createCloseTransitionInfo( TRANSIT_OPEN, listOf(nonLaunchTaskChange) ), @@ -512,7 +537,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { val started = mixedHandler.startAnimation( transition, - createTransitionInfo( + createCloseTransitionInfo( TRANSIT_OPEN, listOf(createChange(task, mode = TRANSIT_OPEN)) ), @@ -546,7 +571,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { val openingChange = createChange(openingTask, mode = TRANSIT_OPEN) val started = mixedHandler.startAnimation( transition, - createTransitionInfo( + createCloseTransitionInfo( TRANSIT_OPEN, listOf(immersiveChange, openingChange) ), @@ -560,7 +585,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { } @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS) + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS, + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX) fun addPendingAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() { val wct = WindowContainerTransaction() val launchingTask = createTask(WINDOWING_MODE_FREEFORM) @@ -579,7 +606,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { ) mixedHandler.startAnimation( transition, - createTransitionInfo( + createCloseTransitionInfo( TRANSIT_OPEN, listOf(launchTaskChange) ), @@ -592,7 +619,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { } @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS) + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS, + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX) fun addPendingAndAnimateLaunchTransition_withMinimizeChange_reparentsMinimizeChange() { val wct = WindowContainerTransaction() val launchingTask = createTask(WINDOWING_MODE_FREEFORM) @@ -613,7 +642,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { ) mixedHandler.startAnimation( transition, - createTransitionInfo( + createCloseTransitionInfo( TRANSIT_OPEN, listOf(launchTaskChange, minimizeChange) ), @@ -643,7 +672,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { val launchTaskChange = createChange(launchingTask) mixedHandler.startAnimation( transition, - createTransitionInfo( + createCloseTransitionInfo( TRANSIT_OPEN, listOf(launchTaskChange) ), @@ -700,7 +729,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { val started = mixedHandler.startAnimation( transition = transition, info = - createTransitionInfo( + createCloseTransitionInfo( TRANSIT_TO_BACK, listOf(minimizingTaskChange) ), @@ -739,7 +768,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { mixedHandler.startAnimation( transition = transition, info = - createTransitionInfo( + createCloseTransitionInfo( TRANSIT_TO_BACK, listOf(minimizingTaskChange) ), @@ -759,12 +788,12 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { ) } - private fun createTransitionInfo( - type: Int = WindowManager.TRANSIT_CLOSE, + private fun createCloseTransitionInfo( changeMode: Int = WindowManager.TRANSIT_CLOSE, - task: RunningTaskInfo + task: RunningTaskInfo, + withWallpaper: Boolean = false, ): TransitionInfo = - TransitionInfo(type, 0 /* flags */).apply { + TransitionInfo(WindowManager.TRANSIT_CLOSE, 0 /* flags */).apply { addChange( TransitionInfo.Change(mock(), closingTaskLeash).apply { mode = changeMode @@ -772,9 +801,18 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { taskInfo = task } ) + if (withWallpaper) { + addChange( + TransitionInfo.Change(/* container= */ mock(), /* leash= */ mock()).apply { + mode = WindowManager.TRANSIT_CLOSE + parent = null + taskInfo = createWallpaperTask() + } + ) + } } - private fun createTransitionInfo( + private fun createCloseTransitionInfo( @TransitionType type: Int, changes: List<TransitionInfo.Change> = emptyList() ): TransitionInfo = TransitionInfo(type, /* flags= */ 0).apply { @@ -795,4 +833,13 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { .setActivityType(ACTIVITY_TYPE_STANDARD) .setWindowingMode(windowingMode) .build() + + private fun createWallpaperTask() = + RunningTaskInfo().apply { + token = WindowContainerToken(mock<IWindowContainerToken>()) + baseIntent = + Intent().apply { + component = DesktopWallpaperActivity.wallpaperActivityComponent + } + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt index d4682c1325f2..e57ae2a86859 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt @@ -53,10 +53,10 @@ 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.StaticMockitoSession import com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS +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.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger +import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel @@ -91,7 +91,7 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() { private val rootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>() private val shellTaskOrganizer = mock<ShellTaskOrganizer>() private val focusTransitionObserver = mock<FocusTransitionObserver>() - private val testExecutor = mock<ShellExecutor>() + private val testExecutor = TestShellExecutor() private val inputManager = mock<InputManager>() private val displayController = mock<DisplayController>() private val displayLayout = mock<DisplayLayout>() @@ -137,8 +137,13 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() { desktopModeKeyGestureHandler = DesktopModeKeyGestureHandler( context, - Optional.of(desktopModeWindowDecorViewModel), Optional.of(desktopTasksController), - inputManager, shellTaskOrganizer, focusTransitionObserver, testExecutor + Optional.of(desktopModeWindowDecorViewModel), + Optional.of(desktopTasksController), + inputManager, + shellTaskOrganizer, + focusTransitionObserver, + testExecutor, + displayController ) } @@ -148,6 +153,7 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() { runningTasks.clear() testScope.cancel() + testExecutor.flushAll() } @Test @@ -177,6 +183,7 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() { .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON) .build() val result = keyGestureEventHandler.handleKeyGestureEvent(event, null) + testExecutor.flushAll() assertThat(result).isTrue() verify(desktopTasksController).moveToNextDisplay(task.taskId) @@ -199,6 +206,7 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() { .setModifierState(KeyEvent.META_META_ON) .build() val result = keyGestureEventHandler.handleKeyGestureEvent(event, null) + testExecutor.flushAll() assertThat(result).isTrue() verify(desktopModeWindowDecorViewModel).onSnapResize( @@ -226,6 +234,7 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() { .setModifierState(KeyEvent.META_META_ON) .build() val result = keyGestureEventHandler.handleKeyGestureEvent(event, null) + testExecutor.flushAll() assertThat(result).isTrue() verify(desktopModeWindowDecorViewModel).onSnapResize( @@ -253,12 +262,17 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() { .setModifierState(KeyEvent.META_META_ON) .build() val result = keyGestureEventHandler.handleKeyGestureEvent(event, null) + testExecutor.flushAll() assertThat(result).isTrue() verify(desktopTasksController).toggleDesktopTaskSize( task, - ResizeTrigger.MAXIMIZE_MENU, - DesktopModeEventLogger.Companion.InputMethod.KEYBOARD, + ToggleTaskSizeInteraction( + isMaximized = isTaskMaximized(task, displayController), + source = ToggleTaskSizeInteraction.Source.KEYBOARD_SHORTCUT, + inputMethod = + DesktopModeEventLogger.Companion.InputMethod.KEYBOARD, + ), ) } @@ -279,8 +293,10 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() { .setModifierState(KeyEvent.META_META_ON) .build() val result = keyGestureEventHandler.handleKeyGestureEvent(event, null) + testExecutor.flushAll() assertThat(result).isTrue() + verify(desktopTasksController).minimizeTask(task) } private fun setUpFreeformTask( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt index 7f790d574a7e..344140d91ab3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt @@ -30,7 +30,6 @@ import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.common.ShellExecutor 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.sysui.ShellInit import com.google.common.truth.Truth.assertThat import junit.framework.Assert.fail @@ -70,7 +69,6 @@ class DesktopRepositoryTest : ShellTestCase() { @Mock private lateinit var testExecutor: ShellExecutor @Mock private lateinit var persistentRepository: DesktopPersistentRepository - @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer @Before fun setUp() { @@ -80,11 +78,9 @@ class DesktopRepositoryTest : ShellTestCase() { repo = DesktopRepository( - context, - shellInit, persistentRepository, - repositoryInitializer, - datastoreScope + datastoreScope, + DEFAULT_USER_ID ) whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn( Desktop.getDefaultInstance() @@ -153,6 +149,14 @@ class DesktopRepositoryTest : ShellTestCase() { } @Test + fun addTask_multipleDisplays_moveToAnotherDisplay() { + repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true) + repo.addTask(SECOND_DISPLAY, taskId = 1, isVisible = true) + assertThat(repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)).isEmpty() + assertThat(repo.getFreeformTasksInZOrder(SECOND_DISPLAY)).containsExactly(1) + } + + @Test fun removeActiveTask_notifiesActiveTaskListener() { val listener = TestListener() repo.addActiveTaskListener(listener) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt index 8e323acc4e66..b4daa6637f83 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt @@ -22,6 +22,7 @@ import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION +import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.ShellTestCase import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask @@ -29,6 +30,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify @@ -47,131 +49,163 @@ class DesktopTaskChangeListenerTest : ShellTestCase() { private lateinit var desktopTaskChangeListener: DesktopTaskChangeListener + private val desktopUserRepositories = mock<DesktopUserRepositories>() private val desktopRepository = mock<DesktopRepository>() @Before fun setUp() { - desktopTaskChangeListener = DesktopTaskChangeListener(desktopRepository) + desktopTaskChangeListener = DesktopTaskChangeListener(desktopUserRepositories) + + whenever(desktopUserRepositories.current).thenReturn(desktopRepository) + whenever(desktopUserRepositories.getProfile(anyInt())).thenReturn(desktopRepository) } @Test fun onTaskOpening_fullscreenTask_notActiveDesktopTask_noop() { val task = createFullscreenTask().apply { isVisible = true } - whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(false) + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) + .thenReturn(false) desktopTaskChangeListener.onTaskOpening(task) - verify(desktopRepository, never()).addTask(task.displayId, task.taskId, task.isVisible) - verify(desktopRepository, never()).removeFreeformTask(task.displayId, task.taskId) + verify(desktopUserRepositories.current, never()) + .addTask(task.displayId, task.taskId, task.isVisible) + verify(desktopUserRepositories.current, never()) + .removeFreeformTask(task.displayId, task.taskId) } @Test fun onTaskOpening_freeformTask_activeDesktopTask_removesTaskFromRepo() { val task = createFullscreenTask().apply { isVisible = true } - whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true) + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) + .thenReturn(true) desktopTaskChangeListener.onTaskOpening(task) - verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId) + verify(desktopUserRepositories.current).removeFreeformTask(task.displayId, task.taskId) } @Test fun onTaskOpening_freeformTask_visibleDesktopTask_addsTaskToRepository() { val task = createFreeformTask().apply { isVisible = true } - whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(false) + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) + .thenReturn(false) desktopTaskChangeListener.onTaskOpening(task) - verify(desktopRepository).addTask(task.displayId, task.taskId, task.isVisible) + verify(desktopUserRepositories.current) + .addTask(task.displayId, task.taskId, task.isVisible) } @Test fun onTaskOpening_freeformTask_nonVisibleDesktopTask_addsTaskToRepository() { val task = createFreeformTask().apply { isVisible = false } - whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true) + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) + .thenReturn(true) desktopTaskChangeListener.onTaskOpening(task) - verify(desktopRepository).addTask(task.displayId, task.taskId, task.isVisible) + verify(desktopUserRepositories.current) + .addTask(task.displayId, task.taskId, task.isVisible) } @Test fun onTaskChanging_freeformTaskOutsideDesktop_removesTaskFromRepo() { val task = createFullscreenTask().apply { isVisible = true } - whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true) + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) + .thenReturn(true) desktopTaskChangeListener.onTaskChanging(task) - verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId) + verify(desktopUserRepositories.current) + .removeFreeformTask(task.displayId, task.taskId) } @Test fun onTaskChanging_visibleTaskInDesktop_updatesTaskVisibility() { val task = createFreeformTask().apply { isVisible = true } - whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true) + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) + .thenReturn(true) desktopTaskChangeListener.onTaskChanging(task) - verify(desktopRepository).updateTask(task.displayId, task.taskId, task.isVisible) + verify(desktopUserRepositories.current) + .updateTask(task.displayId, task.taskId, task.isVisible) } @Test fun onTaskChanging_nonVisibleTask_updatesTaskVisibility() { val task = createFreeformTask().apply { isVisible = false } - whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true) + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) + .thenReturn(true) desktopTaskChangeListener.onTaskChanging(task) - verify(desktopRepository).updateTask(task.displayId, task.taskId, task.isVisible) + verify(desktopUserRepositories.current) + .updateTask(task.displayId, task.taskId, task.isVisible) } @Test fun onTaskMovingToFront_freeformTaskOutsideDesktop_removesTaskFromRepo() { val task = createFullscreenTask().apply { isVisible = true } - whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true) + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) + .thenReturn(true) desktopTaskChangeListener.onTaskMovingToFront(task) - verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId) + verify(desktopUserRepositories.current) + .removeFreeformTask(task.displayId, task.taskId) } @Test @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) fun onTaskClosing_backNavEnabled_nonClosingTask_minimizesTaskInRepo() { val task = createFreeformTask().apply { isVisible = true } - whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true) - whenever(desktopRepository.isClosingTask(task.taskId)).thenReturn(false) + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) + .thenReturn(true) + whenever(desktopUserRepositories.current.isClosingTask(task.taskId)) + .thenReturn(false) desktopTaskChangeListener.onTaskClosing(task) - verify(desktopRepository).updateTask(task.displayId, task.taskId, isVisible = false) - verify(desktopRepository).minimizeTask(task.displayId, task.taskId) + verify(desktopUserRepositories.current) + .updateTask(task.displayId, task.taskId, isVisible = false) + verify(desktopUserRepositories.current) + .minimizeTask(task.displayId, task.taskId) } @Test @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) fun onTaskClosing_backNavDisabled_closingTask_removesTaskInRepo() { val task = createFreeformTask().apply { isVisible = true } - whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true) - whenever(desktopRepository.isClosingTask(task.taskId)).thenReturn(true) + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) + .thenReturn(true) + whenever(desktopUserRepositories.current.isClosingTask(task.taskId)) + .thenReturn(true) desktopTaskChangeListener.onTaskClosing(task) - verify(desktopRepository, never()).minimizeTask(task.displayId, task.taskId) - verify(desktopRepository).removeClosingTask(task.taskId) - verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId) + verify(desktopUserRepositories.current, never()) + .minimizeTask(task.displayId, task.taskId) + verify(desktopUserRepositories.current) + .removeClosingTask(task.taskId) + verify(desktopUserRepositories.current) + .removeFreeformTask(task.displayId, task.taskId) } @Test @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) fun onTaskClosing_backNavEnabled_closingTask_removesTaskFromRepo() { val task = createFreeformTask().apply { isVisible = true } - whenever(desktopRepository.isActiveTask(task.taskId)).thenReturn(true) - whenever(desktopRepository.isClosingTask(task.taskId)).thenReturn(true) + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)) + .thenReturn(true) + whenever(desktopUserRepositories.current.isClosingTask(task.taskId)) + .thenReturn(true) desktopTaskChangeListener.onTaskClosing(task) - verify(desktopRepository).removeClosingTask(task.taskId) - verify(desktopRepository).removeFreeformTask(task.displayId, task.taskId) + verify(desktopUserRepositories.current).removeClosingTask(task.taskId) + verify(desktopUserRepositories.current) + .removeFreeformTask(task.displayId, task.taskId) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 5c0027220ec9..7c9494ce7026 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -21,6 +21,7 @@ import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityOptions import android.app.KeyguardManager import android.app.PendingIntent +import android.app.PictureInPictureParams import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM @@ -44,6 +45,7 @@ import android.os.Binder import android.os.Bundle import android.os.Handler import android.os.IBinder +import android.os.UserManager import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule @@ -93,6 +95,7 @@ import com.android.wm.shell.common.DisplayLayout 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.desktopmode.common.ToggleTaskSizeInteraction 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.ResizeTrigger @@ -233,9 +236,11 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock private lateinit var resources: Resources @Mock lateinit var desktopModeEnterExitTransitionListener: DesktopModeEntryExitTransitionListener + @Mock private lateinit var userManager: UserManager 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 testScope: CoroutineScope @@ -267,12 +272,18 @@ class DesktopTasksControllerTest : ShellTestCase() { testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) shellInit = spy(ShellInit(testExecutor)) - taskRepository = - DesktopRepository(context, shellInit, persistentRepository, repositoryInitializer, testScope) + userRepositories = + DesktopUserRepositories( + context, + shellInit, + persistentRepository, + repositoryInitializer, + testScope, + userManager) desktopTasksLimiter = DesktopTasksLimiter( transitions, - taskRepository, + userRepositories, shellTaskOrganizer, MAX_TASK_LIMIT, mockInteractionJankMonitor, @@ -315,6 +326,8 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.taskbarDesktopTaskListener = taskbarDesktopTaskListener assumeTrue(ENABLE_SHELL_TRANSITIONS) + + taskRepository = userRepositories.current } private fun createController(): DesktopTasksController { @@ -338,7 +351,7 @@ class DesktopTasksControllerTest : ShellTestCase() { toggleResizeDesktopTaskTransitionHandler, dragToDesktopTransitionHandler, mMockDesktopImmersiveController, - taskRepository, + userRepositories, recentsTransitionHandler, multiInstanceHelper, shellExecutor, @@ -377,7 +390,14 @@ class DesktopTasksControllerTest : ShellTestCase() { val task1 = setUpFreeformTask() val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java) - controller.toggleDesktopTaskSize(task1, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + 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( @@ -399,21 +419,29 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfFullScreenTask_returnFalse() { + fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfMaximizedTask_returnFalse() { val stableBounds = Rect().apply { displayLayout.getStableBounds(this) } val task1 = setUpFreeformTask(bounds = stableBounds, active = true) val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java) - controller.toggleDesktopTaskSize(task1, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + 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( - ResizeTrigger.MAXIMIZE_BUTTON, - InputMethod.TOUCH, - task1, - 0, - 0, - displayController + eq(ResizeTrigger.MAXIMIZE_BUTTON), + eq(InputMethod.TOUCH), + eq(task1), + anyOrNull(), + anyOrNull(), + eq(displayController), + anyOrNull() ) assertThat(argumentCaptor.value).isFalse() } @@ -1124,7 +1152,7 @@ class DesktopTasksControllerTest : ShellTestCase() { fun moveRunningTaskToDesktop_topActivityTranslucentWithoutDisplay_taskIsMovedToDesktop() { val task = setUpFullscreenTask().apply { - isTopActivityTransparent = true + isActivityStackTransparent = true isTopActivityNoDisplay = true numActivities = 1 } @@ -1140,7 +1168,7 @@ class DesktopTasksControllerTest : ShellTestCase() { fun moveRunningTaskToDesktop_topActivityTranslucentWithDisplay_doesNothing() { val task = setUpFullscreenTask().apply { - isTopActivityTransparent = true + isActivityStackTransparent = true isTopActivityNoDisplay = false numActivities = 1 } @@ -1697,6 +1725,34 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun onDesktopWindowMinimize_pipTask_autoEnterEnabled_startPipTransition() { + val task = setUpPipTask(autoEnterEnabled = true) + val handler = mock(TransitionHandler::class.java) + whenever(freeformTaskTransitionStarter.startPipTransition(any())) + .thenReturn(Binder()) + whenever(transitions.dispatchRequest(any(), any(), anyOrNull())) + .thenReturn(android.util.Pair(handler, WindowContainerTransaction()) + ) + + controller.minimizeTask(task) + + verify(freeformTaskTransitionStarter).startPipTransition(any()) + verify(freeformTaskTransitionStarter, never()).startMinimizedModeTransition(any()) + } + + @Test + fun onDesktopWindowMinimize_pipTask_autoEnterDisabled_startMinimizeTransition() { + val task = setUpPipTask(autoEnterEnabled = false) + whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + .thenReturn(Binder()) + + controller.minimizeTask(task) + + verify(freeformTaskTransitionStarter).startMinimizedModeTransition(any()) + verify(freeformTaskTransitionStarter, never()).startPipTransition(any()) + } + + @Test fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntRemoveWallpaper() { val task = setUpFreeformTask(active = true) val transition = Binder() @@ -2233,7 +2289,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val task = setUpFullscreenTask().apply { - isTopActivityTransparent = true + isActivityStackTransparent = true isTopActivityNoDisplay = true numActivities = 1 } @@ -2251,7 +2307,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val task = setUpFreeformTask().apply { - isTopActivityTransparent = true + isActivityStackTransparent = true isTopActivityNoDisplay = false numActivities = 1 } @@ -3006,20 +3062,21 @@ class DesktopTasksControllerTest : ShellTestCase() { .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, 200f, Rect(100, 200, 500, 1000)) spyController.onDragPositioningEnd( task, mockSurface, Point(100, 50), /* position */ PointF(200f, 300f), /* inputCoordinate */ - Rect(100, 50, 500, 1000), /* currentDragBounds */ + currentDragBounds, Rect(0, 50, 2000, 2000) /* validDragArea */, Rect() /* dragStartBounds */, motionEvent, desktopWindowDecoration) // Assert bounds set to stable bounds - val wct = getLatestToggleResizeDesktopTaskWct() + val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds) assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS) // Assert event is properly logged verify(desktopModeEventLogger, times(1)).logTaskResizingStarted( @@ -3278,44 +3335,41 @@ class DesktopTasksControllerTest : ShellTestCase() { setUpLandscapeDisplay() val task = setUpFreeformTask() val taskToRequest = setUpFreeformTask() - val wctCaptor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) runOpenInstance(task, taskToRequest.taskId) - verify(transitions).startTransition(anyInt(), wctCaptor.capture(), anyOrNull()) - assertThat(ActivityOptions.fromBundle(wctCaptor.value.hierarchyOps[0].launchOptions) - .launchWindowingMode).isEqualTo(WINDOWING_MODE_FREEFORM) + verify(desktopMixedTransitionHandler).startLaunchTransition(anyInt(), any(), anyInt(), + 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) fun openInstance_fromFreeform_minimizesIfNeeded() { setUpLandscapeDisplay() - val homeTask = setUpHomeTask() val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() } val oldestTask = freeformTasks.first() val newestTask = freeformTasks.last() + val transition = Binder() + val wctCaptor = argumentCaptor<WindowContainerTransaction>() + whenever(desktopMixedTransitionHandler.startLaunchTransition(anyInt(), wctCaptor.capture(), + anyInt(), anyOrNull(), anyOrNull() + )) + .thenReturn(transition) + runOpenInstance(newestTask, freeformTasks[1].taskId) - val wct = getLatestWct(type = TRANSIT_OPEN) - // Home is moved to front of everything. - assertThat( - wct.hierarchyOps.any { hop -> - hop.container == homeTask.token.asBinder() && hop.toTop - } - ).isTrue() - // And the oldest task isn't moved in front of home, effectively minimizing it. - assertThat( - wct.hierarchyOps.none { hop -> - hop.container == oldestTask.token.asBinder() && hop.toTop - } - ).isTrue() + 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) fun openInstance_fromFreeform_exitsImmersiveIfNeeded() { setUpLandscapeDisplay() - val homeTask = setUpHomeTask() val freeformTask = setUpFreeformTask() val immersiveTask = setUpFreeformTask() taskRepository.setTaskInFullImmersiveState( @@ -3325,11 +3379,13 @@ class DesktopTasksControllerTest : ShellTestCase() { ) val runOnStartTransit = RunOnStartTransitionCallback() val transition = Binder() - whenever(transitions.startTransition(eq(TRANSIT_OPEN), any(), anyOrNull())) + whenever(desktopMixedTransitionHandler.startLaunchTransition(anyInt(), any(), anyInt(), + anyOrNull(), anyOrNull() + )) .thenReturn(transition) whenever(mMockDesktopImmersiveController .exitImmersiveIfApplicable( - any(), eq(immersiveTask.displayId), eq(freeformTask.taskId), any())) + any(), eq(DEFAULT_DISPLAY), eq(freeformTask.taskId), any())) .thenReturn( ExitResult.Exit( exitingTask = immersiveTask.taskId, @@ -3359,7 +3415,14 @@ class DesktopTasksControllerTest : ShellTestCase() { val bounds = Rect(0, 0, 100, 100) val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds) - controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, + InputMethod.TOUCH + ) + ) // Assert bounds set to stable bounds val wct = getLatestToggleResizeDesktopTaskWct() @@ -3584,7 +3647,14 @@ class DesktopTasksControllerTest : ShellTestCase() { // 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, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + controller.toggleDesktopTaskSize( + task, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, + InputMethod.TOUCH + ) + ) // Assert bounds set to stable bounds val wct = getLatestToggleResizeDesktopTaskWct() @@ -3604,7 +3674,15 @@ class DesktopTasksControllerTest : ShellTestCase() { val bounds = Rect(0, 0, 100, 100) val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds) - controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + 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(), @@ -3618,11 +3696,25 @@ class DesktopTasksControllerTest : ShellTestCase() { val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize) // Maximize - controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + 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, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + 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() @@ -3645,12 +3737,26 @@ class DesktopTasksControllerTest : ShellTestCase() { } // Maximize - controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + 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, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + 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() @@ -3673,12 +3779,26 @@ class DesktopTasksControllerTest : ShellTestCase() { } // Maximize - controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + 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, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + 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() @@ -3699,11 +3819,25 @@ class DesktopTasksControllerTest : ShellTestCase() { val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize) // Maximize - controller.toggleDesktopTaskSize(task, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + 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, ResizeTrigger.MAXIMIZE_BUTTON, InputMethod.TOUCH) + 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() @@ -4124,6 +4258,14 @@ class DesktopTasksControllerTest : ShellTestCase() { return task } + private fun setUpPipTask(autoEnterEnabled: Boolean): RunningTaskInfo { + return setUpFreeformTask().apply { + pictureInPictureParams = PictureInPictureParams.Builder() + .setAutoEnterEnabled(autoEnterEnabled) + .build() + } + } + private fun setUpHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { val task = createHomeTask(displayId) whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt index 797b12505ef2..0712d58166bb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt @@ -20,6 +20,7 @@ import android.app.ActivityManager.RunningTaskInfo import android.graphics.Rect import android.os.Binder import android.os.Handler +import android.os.UserManager import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule @@ -73,7 +74,6 @@ import org.mockito.kotlin.eq import org.mockito.kotlin.verify import org.mockito.quality.Strictness - /** * Test class for {@link DesktopTasksLimiter} * @@ -95,9 +95,11 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Mock lateinit var testExecutor: ShellExecutor @Mock lateinit var persistentRepository: DesktopPersistentRepository @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer + @Mock lateinit var userManager: UserManager private lateinit var mockitoSession: StaticMockitoSession private lateinit var desktopTasksLimiter: DesktopTasksLimiter + private lateinit var userRepositories: DesktopUserRepositories private lateinit var desktopTaskRepo: DesktopRepository private lateinit var shellInit: ShellInit private lateinit var testScope: CoroutineScope @@ -111,16 +113,18 @@ class DesktopTasksLimiterTest : ShellTestCase() { Dispatchers.setMain(StandardTestDispatcher()) testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) - desktopTaskRepo = - DesktopRepository( + userRepositories = + DesktopUserRepositories( context, shellInit, persistentRepository, repositoryInitializer, - testScope + testScope, + userManager ) + desktopTaskRepo = userRepositories.current desktopTasksLimiter = - DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT, + DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, MAX_TASK_LIMIT, interactionJankMonitor, mContext, handler) } @@ -133,7 +137,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun createDesktopTasksLimiter_withZeroLimit_shouldThrow() { assertFailsWith<IllegalArgumentException> { - DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, 0, + DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, 0, interactionJankMonitor, mContext, handler) } } @@ -141,7 +145,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun createDesktopTasksLimiter_withNegativeLimit_shouldThrow() { assertFailsWith<IllegalArgumentException> { - DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, -5, + DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, -5, interactionJankMonitor, mContext, handler) } } @@ -411,7 +415,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun getTaskToMinimize_tasksAboveLimit_otherLimit_returnsBackTask() { desktopTasksLimiter = - DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT2, + DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, MAX_TASK_LIMIT2, interactionJankMonitor, mContext, handler) val tasks = (1..MAX_TASK_LIMIT2 + 1).map { setUpFreeformTask() } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt index 7f1c1db3207a..b31a3f5fa642 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt @@ -39,6 +39,7 @@ import com.android.modules.utils.testing.ExtendedMockitoRule import com.android.window.flags.Flags import com.android.wm.shell.MockToken import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.back.BackAnimationController import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG import com.android.wm.shell.shared.desktopmode.DesktopModeStatus @@ -50,6 +51,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.eq import org.mockito.ArgumentMatchers.isA import org.mockito.Mockito @@ -66,17 +68,17 @@ class DesktopTasksTransitionObserverTest { @JvmField @Rule val extendedMockitoRule = - ExtendedMockitoRule.Builder(this) - .mockStatic(DesktopModeStatus::class.java) - .build()!! + ExtendedMockitoRule.Builder(this).mockStatic(DesktopModeStatus::class.java).build()!! private val testExecutor = mock<ShellExecutor>() private val mockShellInit = mock<ShellInit>() private val transitions = mock<Transitions>() private val context = mock<Context>() private val shellTaskOrganizer = mock<ShellTaskOrganizer>() + private val userRepositories = mock<DesktopUserRepositories>() private val taskRepository = mock<DesktopRepository>() private val mixedHandler = mock<DesktopMixedTransitionHandler>() + private val backAnimationController = mock<BackAnimationController>() private lateinit var transitionObserver: DesktopTasksTransitionObserver private lateinit var shellInit: ShellInit @@ -86,9 +88,18 @@ class DesktopTasksTransitionObserverTest { whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true) shellInit = spy(ShellInit(testExecutor)) + whenever(userRepositories.current).thenReturn(taskRepository) + whenever(userRepositories.getProfile(anyInt())).thenReturn(taskRepository) + transitionObserver = DesktopTasksTransitionObserver( - context, taskRepository, transitions, shellTaskOrganizer, mixedHandler, shellInit + context, + userRepositories, + transitions, + shellTaskOrganizer, + mixedHandler, + backAnimationController, + shellInit ) } @@ -100,8 +111,7 @@ class DesktopTasksTransitionObserverTest { transitionObserver.onTransitionReady( transition = mock(), - info = - createBackNavigationTransition(task), + info = createBackNavigationTransition(task), startTransaction = mock(), finishTransaction = mock(), ) @@ -112,14 +122,59 @@ class DesktopTasksTransitionObserverTest { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) + fun backNavigation_withCloseTransitionNotLastTask_taskMinimized() { + val task = createTaskInfo(1) + val transition = mock<IBinder>() + whenever(taskRepository.getVisibleTaskCount(any())).thenReturn(2) + whenever(taskRepository.isClosingTask(task.taskId)).thenReturn(false) + whenever(backAnimationController.latestTriggerBackTask).thenReturn(task.taskId) + + transitionObserver.onTransitionReady( + transition = transition, + info = createBackNavigationTransition(task, TRANSIT_CLOSE), + startTransaction = mock(), + finishTransaction = mock(), + ) + + verify(taskRepository).minimizeTask(task.displayId, task.taskId) + val pendingTransition = + DesktopMixedTransitionHandler.PendingMixedTransition.Minimize( + transition, task.taskId, isLastTask = false) + verify(mixedHandler).addPendingMixedTransition(pendingTransition) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) + fun backNavigation_withCloseTransitionLastTask_taskMinimized() { + val task = createTaskInfo(1) + val transition = mock<IBinder>() + whenever(taskRepository.getVisibleTaskCount(any())).thenReturn(1) + whenever(taskRepository.isClosingTask(task.taskId)).thenReturn(false) + whenever(backAnimationController.latestTriggerBackTask).thenReturn(task.taskId) + + transitionObserver.onTransitionReady( + transition = transition, + info = createBackNavigationTransition(task, TRANSIT_CLOSE, true), + startTransaction = mock(), + finishTransaction = mock(), + ) + + verify(taskRepository).minimizeTask(task.displayId, task.taskId) + val pendingTransition = + DesktopMixedTransitionHandler.PendingMixedTransition.Minimize( + transition, task.taskId, isLastTask = true) + verify(mixedHandler).addPendingMixedTransition(pendingTransition) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) fun backNavigation_nullTaskInfo_taskNotMinimized() { val task = createTaskInfo(1) whenever(taskRepository.getVisibleTaskCount(any())).thenReturn(1) transitionObserver.onTransitionReady( transition = mock(), - info = - createBackNavigationTransition(null), + info = createBackNavigationTransition(null), startTransaction = mock(), finishTransaction = mock(), ) @@ -168,7 +223,7 @@ class DesktopTasksTransitionObserverTest { val mockTransition = Mockito.mock(IBinder::class.java) val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM) val wallpaperToken = MockToken().token() - whenever(taskRepository.getVisibleTaskCount(task.displayId)).thenReturn(1) + whenever(taskRepository.getVisibleTaskCount(task.displayId)).thenReturn(0) whenever(taskRepository.wallpaperActivityToken).thenReturn(wallpaperToken) transitionObserver.onTransitionReady( @@ -185,17 +240,27 @@ class DesktopTasksTransitionObserverTest { } private fun createBackNavigationTransition( - task: RunningTaskInfo? + task: RunningTaskInfo?, + type: Int = TRANSIT_TO_BACK, + withWallpaper: Boolean = false, ): TransitionInfo { - return TransitionInfo(TRANSIT_TO_BACK, 0 /* flags */).apply { + return TransitionInfo(type, 0 /* flags */).apply { addChange( Change(mock(), mock()).apply { - mode = TRANSIT_TO_BACK + mode = type parent = null taskInfo = task flags = flags - } - ) + }) + if (withWallpaper) { + addChange( + Change(mock(), mock()).apply { + mode = TRANSIT_CLOSE + parent = null + taskInfo = createWallpaperTaskInfo() + flags = flags + }) + } } } @@ -210,14 +275,11 @@ class DesktopTasksTransitionObserverTest { parent = null taskInfo = task flags = flags - } - ) + }) } } - private fun createCloseTransition( - task: RunningTaskInfo? - ): TransitionInfo { + private fun createCloseTransition(task: RunningTaskInfo?): TransitionInfo { return TransitionInfo(TRANSIT_CLOSE, 0 /* flags */).apply { addChange( Change(mock(), mock()).apply { @@ -225,8 +287,7 @@ class DesktopTasksTransitionObserverTest { parent = null taskInfo = task flags = flags - } - ) + }) } } @@ -238,8 +299,7 @@ class DesktopTasksTransitionObserverTest { if (handlerClass == null) { Mockito.verify(transitions).startTransition(eq(type), arg.capture(), isNull()) } else { - Mockito.verify(transitions) - .startTransition(eq(type), arg.capture(), isA(handlerClass)) + Mockito.verify(transitions).startTransition(eq(type), arg.capture(), isA(handlerClass)) } return arg.value } @@ -263,8 +323,15 @@ class DesktopTasksTransitionObserverTest { displayId = DEFAULT_DISPLAY configuration.windowConfiguration.windowingMode = windowingMode token = WindowContainerToken(Mockito.mock(IWindowContainerToken::class.java)) - baseIntent = Intent().apply { - component = ComponentName("package", "component.name") - } + baseIntent = Intent().apply { component = ComponentName("package", "component.name") } + } + + private fun createWallpaperTaskInfo() = + RunningTaskInfo().apply { + token = mock<WindowContainerToken>() + baseIntent = + Intent().apply { + component = DesktopWallpaperActivity.wallpaperActivityComponent + } } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt index 866d1b3880b0..aee8821a63f6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt @@ -73,10 +73,10 @@ object DesktopTestHelpers { .setLastActiveTime(100) .build() - /** Create a new System Modal task, i.e. a task with a single transparent activity. */ + /** Create a new System Modal task, i.e. a task with only transparent activities. */ fun createSystemModalTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo = createFullscreenTaskBuilder(displayId) - .setTopActivityTransparent(true) + .setActivityStackTransparent(true) .setNumActivities(1) .build() } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt new file mode 100644 index 000000000000..5767df4c5a8e --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt @@ -0,0 +1,126 @@ +/* + * 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.desktopmode + +import android.app.ActivityManager +import android.content.pm.UserInfo +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 androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession +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_HSUM +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository +import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer +import com.android.wm.shell.sysui.ShellInit +import com.google.common.truth.Truth.assertThat +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.Mockito.spy +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@ExperimentalCoroutinesApi +class DesktopUserRepositoriesTest : ShellTestCase() { + @get:Rule val setFlagsRule = SetFlagsRule() + + private lateinit var userRepositories: DesktopUserRepositories + private lateinit var shellInit: ShellInit + private lateinit var datastoreScope: CoroutineScope + private lateinit var mockitoSession: StaticMockitoSession + + private val testExecutor = mock<ShellExecutor>() + private val persistentRepository = mock<DesktopPersistentRepository>() + private val repositoryInitializer = mock<DesktopRepositoryInitializer>() + private val userManager = mock<UserManager>() + + @Before + fun setUp() { + Dispatchers.setMain(StandardTestDispatcher()) + mockitoSession = + mockitoSession() + .strictness(Strictness.LENIENT) + .spyStatic(ActivityManager::class.java) + .startMocking() + doReturn(USER_ID_1).`when` { ActivityManager.getCurrentUser() } + + datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) + shellInit = spy(ShellInit(testExecutor)) + + val profiles: MutableList<UserInfo> = mutableListOf( + UserInfo(USER_ID_1, "User 1", 0), + UserInfo(PROFILE_ID_2, "Profile 2", 0)) + whenever(userManager.getProfiles(USER_ID_1)).thenReturn(profiles) + + userRepositories = DesktopUserRepositories( + context, shellInit, persistentRepository, repositoryInitializer, datastoreScope, + userManager) + } + + @After + fun tearDown() { + mockitoSession.finishMocking() + datastoreScope.cancel() + } + + @Test + fun getCurrent_returnsUserId() { + val desktopRepository: DesktopRepository = userRepositories.current + + assertThat(desktopRepository.userId).isEqualTo(USER_ID_1) + } + + @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_HSUM) + fun getProfile_flagEnabled_returnsProfileGroupId() { + val desktopRepository: DesktopRepository = userRepositories.getProfile(PROFILE_ID_2) + + assertThat(desktopRepository.userId).isEqualTo(USER_ID_1) + } + + @Test + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_HSUM) + fun getProfile_flagDisabled_returnsProfileId() { + val desktopRepository: DesktopRepository = userRepositories.getProfile(PROFILE_ID_2) + + assertThat(desktopRepository.userId).isEqualTo(PROFILE_ID_2) + } + + private companion object { + const val USER_ID_1 = 7 + const val PROFILE_ID_2 = 5 + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt index 79e16fea272d..13528b947609 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt @@ -607,7 +607,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { ) ) .thenReturn(token) - handler.startDragToDesktopTransition(task.taskId, dragAnimator) + handler.startDragToDesktopTransition(task, dragAnimator) return token } @@ -661,6 +661,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)) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt index 226e974d2875..c33005e7cfcc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt @@ -25,10 +25,11 @@ import android.view.WindowManager.TRANSIT_OPEN import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor -import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTaskBuilder import com.android.wm.shell.desktopmode.DesktopTestHelpers.createSystemModalTask +import com.android.wm.shell.desktopmode.DesktopRepository +import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.TransitionInfoBuilder import com.android.wm.shell.transition.Transitions @@ -42,6 +43,10 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +/** + * Tests for {@link SystemModalsTransitionHandler} + * Usage: atest WMShellUnitTests:SystemModalsTransitionHandlerTest + */ @SmallTest @RunWith(AndroidTestingRunner::class) class SystemModalsTransitionHandlerTest : ShellTestCase() { @@ -49,6 +54,7 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() { private val animExecutor = mock<ShellExecutor>() private val shellInit = mock<ShellInit>() private val transitions = mock<Transitions>() + private val desktopUserRepositories = mock<DesktopUserRepositories>() private val desktopRepository = mock<DesktopRepository>() private val startT = mock<SurfaceControl.Transaction>() private val finishT = mock<SurfaceControl.Transaction>() @@ -58,6 +64,7 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() { @Before fun setUp() { // Simulate having one Desktop task so that we see Desktop Mode as active + whenever(desktopUserRepositories.current).thenReturn(desktopRepository) whenever(desktopRepository.getVisibleTaskCount(anyInt())).thenReturn(1) transitionHandler = createTransitionHandler() } @@ -69,7 +76,7 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() { animExecutor, shellInit, transitions, - desktopRepository, + desktopUserRepositories, ) @Test @@ -79,7 +86,7 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() { @Test fun startAnimation_desktopNotActive_doesNotAnimate() { - whenever(desktopRepository.getVisibleTaskCount(anyInt())).thenReturn(1) + whenever(desktopUserRepositories.current.getVisibleTaskCount(anyInt())).thenReturn(1) val info = TransitionInfoBuilder(TRANSIT_OPEN) .addChange(TRANSIT_OPEN, createSystemModalTask()) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt index 8495580f42a5..4f7e80cf8330 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt @@ -112,7 +112,8 @@ class DesktopPersistentRepositoryTest : ShellTestCase() { datastoreRepository.addOrUpdateDesktop( visibleTasks = visibleTasks, minimizedTasks = minimizedTasks, - freeformTasksInZOrder = freeformTasksInZOrder) + freeformTasksInZOrder = freeformTasksInZOrder, + userId = DEFAULT_USER_ID) val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID) assertThat(actualDesktop?.tasksByTaskIdMap).hasSize(2) @@ -135,7 +136,8 @@ class DesktopPersistentRepositoryTest : ShellTestCase() { datastoreRepository.addOrUpdateDesktop( visibleTasks = visibleTasks, minimizedTasks = minimizedTasks, - freeformTasksInZOrder = freeformTasksInZOrder) + freeformTasksInZOrder = freeformTasksInZOrder, + userId = DEFAULT_USER_ID) val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID) assertThat(actualDesktop?.tasksByTaskIdMap?.get(task.taskId)?.desktopTaskState) @@ -158,7 +160,8 @@ class DesktopPersistentRepositoryTest : ShellTestCase() { datastoreRepository.addOrUpdateDesktop( visibleTasks = visibleTasks, minimizedTasks = minimizedTasks, - freeformTasksInZOrder = freeformTasksInZOrder) + freeformTasksInZOrder = freeformTasksInZOrder, + userId = DEFAULT_USER_ID) val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID) assertThat(actualDesktop?.tasksByTaskIdMap).isEmpty() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt index 975342902814..1c88a290d677 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt @@ -16,14 +16,17 @@ package com.android.wm.shell.desktopmode.persistence +import android.os.UserManager import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner import android.view.Display.DEFAULT_DISPLAY import androidx.test.filters.SmallTest +import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_HSUM import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor -import com.android.wm.shell.desktopmode.DesktopRepository +import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.sysui.ShellInit import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope @@ -36,26 +39,30 @@ import kotlinx.coroutines.test.runTest 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.Mockito.inOrder import org.mockito.Mockito.spy -import org.mockito.kotlin.any import org.mockito.kotlin.mock -import org.mockito.kotlin.verify import org.mockito.kotlin.whenever + @SmallTest @RunWith(AndroidTestingRunner::class) @ExperimentalCoroutinesApi class DesktopRepositoryInitializerTest : ShellTestCase() { + @JvmField + @Rule + val setFlagsRule = SetFlagsRule() + private lateinit var repositoryInitializer: DesktopRepositoryInitializer private lateinit var shellInit: ShellInit private lateinit var datastoreScope: CoroutineScope - private lateinit var desktopRepository: DesktopRepository + private lateinit var desktopUserRepositories: DesktopUserRepositories private val persistentRepository = mock<DesktopPersistentRepository>() + private val userManager = mock<UserManager>() private val testExecutor = mock<ShellExecutor>() @Before @@ -65,55 +72,193 @@ class DesktopRepositoryInitializerTest : ShellTestCase() { datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()) repositoryInitializer = DesktopRepositoryInitializerImpl(context, persistentRepository, datastoreScope) - desktopRepository = - DesktopRepository( - context, shellInit, persistentRepository, repositoryInitializer, datastoreScope) + desktopUserRepositories = + DesktopUserRepositories( + context, shellInit, persistentRepository, repositoryInitializer, datastoreScope, + userManager + ) } @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, FLAG_ENABLE_DESKTOP_WINDOWING_HSUM) + fun initWithPersistence_multipleUsers_addedCorrectly() = + runTest(StandardTestDispatcher()) { + whenever(persistentRepository.getUserDesktopRepositoryMap()).thenReturn( + mapOf( + USER_ID_1 to desktopRepositoryState1, + USER_ID_2 to desktopRepositoryState2 + ) + ) + whenever(persistentRepository.getDesktopRepositoryState(USER_ID_1)) + .thenReturn(desktopRepositoryState1) + whenever(persistentRepository.getDesktopRepositoryState(USER_ID_2)) + .thenReturn(desktopRepositoryState2) + whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_1)) + .thenReturn(desktop1) + whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_2)) + .thenReturn(desktop2) + whenever(persistentRepository.readDesktop(USER_ID_2, DESKTOP_ID_3)) + .thenReturn(desktop3) + + repositoryInitializer.initialize(desktopUserRepositories) + + // Desktop Repository currently returns all tasks across desktops for a specific user + // since the repository currently doesn't handle desktops. This test logic should be updated + // once the repository handles multiple desktops. + assertThat( + desktopUserRepositories.getProfile(USER_ID_1) + .getActiveTasks(DEFAULT_DISPLAY) + ) + .containsExactly(1, 3, 4, 5) + .inOrder() + assertThat( + desktopUserRepositories.getProfile(USER_ID_1) + .getExpandedTasksOrdered(DEFAULT_DISPLAY) + ) + .containsExactly(5, 1) + .inOrder() + assertThat( + desktopUserRepositories.getProfile(USER_ID_1) + .getMinimizedTasks(DEFAULT_DISPLAY) + ) + .containsExactly(3, 4) + .inOrder() + + assertThat( + desktopUserRepositories.getProfile(USER_ID_2) + .getActiveTasks(DEFAULT_DISPLAY) + ) + .containsExactly(7, 8) + .inOrder() + assertThat( + desktopUserRepositories.getProfile(USER_ID_2) + .getExpandedTasksOrdered(DEFAULT_DISPLAY) + ) + .contains(7) + assertThat( + desktopUserRepositories.getProfile(USER_ID_2) + .getMinimizedTasks(DEFAULT_DISPLAY) + ).containsExactly(8) + } + + @Test @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE) - fun initWithPersistence_multipleTasks_addedCorrectly() = + fun initWithPersistence_singleUser_addedCorrectly() = runTest(StandardTestDispatcher()) { - val freeformTasksInZOrder = listOf(1, 2, 3) - whenever(persistentRepository.readDesktop(any(), any())) - .thenReturn( - Desktop.newBuilder() - .setDesktopId(1) - .addAllZOrderedTasks(freeformTasksInZOrder) - .putTasksByTaskId( - 1, - DesktopTask.newBuilder() - .setTaskId(1) - .setDesktopTaskState(DesktopTaskState.VISIBLE) - .build()) - .putTasksByTaskId( - 2, - DesktopTask.newBuilder() - .setTaskId(2) - .setDesktopTaskState(DesktopTaskState.VISIBLE) - .build()) - .putTasksByTaskId( - 3, - DesktopTask.newBuilder() - .setTaskId(3) - .setDesktopTaskState(DesktopTaskState.MINIMIZED) - .build()) - .build()) - - repositoryInitializer.initialize(desktopRepository) - - verify(persistentRepository).readDesktop(any(), any()) - assertThat(desktopRepository.getActiveTasks(DEFAULT_DISPLAY)) - .containsExactly(1, 2, 3) + whenever(persistentRepository.getUserDesktopRepositoryMap()).thenReturn( + mapOf( + USER_ID_1 to desktopRepositoryState1, + ) + ) + whenever(persistentRepository.getDesktopRepositoryState(USER_ID_1)) + .thenReturn(desktopRepositoryState1) + whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_1)) + .thenReturn(desktop1) + whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_2)) + .thenReturn(desktop2) + + repositoryInitializer.initialize(desktopUserRepositories) + + // Desktop Repository currently returns all tasks across desktops for a specific user + // since the repository currently doesn't handle desktops. This test logic should be updated + // once the repository handles multiple desktops. + assertThat( + desktopUserRepositories.getProfile(USER_ID_1) + .getActiveTasks(DEFAULT_DISPLAY) + ) + .containsExactly(1, 3, 4, 5) + .inOrder() + assertThat( + desktopUserRepositories.getProfile(USER_ID_1) + .getExpandedTasksOrdered(DEFAULT_DISPLAY) + ) + .containsExactly(5, 1) .inOrder() - assertThat(desktopRepository.getExpandedTasksOrdered(DEFAULT_DISPLAY)) - .containsExactly(1, 2) + assertThat( + desktopUserRepositories.getProfile(USER_ID_1) + .getMinimizedTasks(DEFAULT_DISPLAY) + ) + .containsExactly(3, 4) .inOrder() - assertThat(desktopRepository.getMinimizedTasks(DEFAULT_DISPLAY)).containsExactly(3) } @After fun tearDown() { datastoreScope.cancel() } + + private companion object { + const val USER_ID_1 = 5 + const val USER_ID_2 = 6 + const val DESKTOP_ID_1 = 2 + const val DESKTOP_ID_2 = 3 + const val DESKTOP_ID_3 = 4 + + val freeformTasksInZOrder1 = listOf(1, 3) + val desktop1: Desktop = Desktop.newBuilder() + .setDesktopId(DESKTOP_ID_1) + .addAllZOrderedTasks(freeformTasksInZOrder1) + .putTasksByTaskId( + 1, + DesktopTask.newBuilder() + .setTaskId(1) + .setDesktopTaskState(DesktopTaskState.VISIBLE) + .build() + ) + .putTasksByTaskId( + 3, + DesktopTask.newBuilder() + .setTaskId(3) + .setDesktopTaskState(DesktopTaskState.MINIMIZED) + .build() + ) + .build() + + val freeformTasksInZOrder2 = listOf(4, 5) + val desktop2: Desktop = Desktop.newBuilder() + .setDesktopId(DESKTOP_ID_2) + .addAllZOrderedTasks(freeformTasksInZOrder2) + .putTasksByTaskId( + 4, + DesktopTask.newBuilder() + .setTaskId(4) + .setDesktopTaskState(DesktopTaskState.MINIMIZED) + .build() + ) + .putTasksByTaskId( + 5, + DesktopTask.newBuilder() + .setTaskId(5) + .setDesktopTaskState(DesktopTaskState.VISIBLE) + .build() + ) + .build() + + val freeformTasksInZOrder3 = listOf(7, 8) + val desktop3: Desktop = Desktop.newBuilder() + .setDesktopId(DESKTOP_ID_3) + .addAllZOrderedTasks(freeformTasksInZOrder3) + .putTasksByTaskId( + 7, + DesktopTask.newBuilder() + .setTaskId(7) + .setDesktopTaskState(DesktopTaskState.VISIBLE) + .build() + ) + .putTasksByTaskId( + 8, + DesktopTask.newBuilder() + .setTaskId(8) + .setDesktopTaskState(DesktopTaskState.MINIMIZED) + .build() + ) + .build() + val desktopRepositoryState1: DesktopRepositoryState = DesktopRepositoryState.newBuilder() + .putDesktop(DESKTOP_ID_1, desktop1) + .putDesktop(DESKTOP_ID_2, desktop2) + .build() + val desktopRepositoryState2: DesktopRepositoryState = DesktopRepositoryState.newBuilder() + .putDesktop(DESKTOP_ID_3, desktop3) + .build() + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java index b504a88e71fc..b8629b2f00d2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java @@ -27,6 +27,7 @@ import static com.android.window.flags.Flags.FLAG_ENABLE_WINDOWING_TRANSITION_HA 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; @@ -47,6 +48,7 @@ import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.common.LaunchAdjacentController; 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.DesktopModeStatus; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.windowdecor.WindowDecorViewModel; @@ -81,6 +83,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase { @Mock private SurfaceControl mMockSurfaceControl; @Mock + private DesktopUserRepositories mDesktopUserRepositories; + @Mock private DesktopRepository mDesktopRepository; @Mock private DesktopTasksController mDesktopTasksController; @@ -101,13 +105,14 @@ public final class FreeformTaskListenerTests extends ShellTestCase { .mockStatic(DesktopModeStatus.class) .startMocking(); doReturn(true).when(() -> DesktopModeStatus.canEnterDesktopMode(any())); - + when(mDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository); + when(mDesktopUserRepositories.getProfile(anyInt())).thenReturn(mDesktopRepository); mFreeformTaskListener = new FreeformTaskListener( mContext, mShellInit, mTaskOrganizer, - Optional.of(mDesktopRepository), + Optional.of(mDesktopUserRepositories), Optional.of(mDesktopTasksController), mLaunchAdjacentController, mWindowDecorViewModel, @@ -123,7 +128,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase { mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl); - verify(mDesktopRepository).addTask(task.displayId, task.taskId, task.isVisible = true); + verify(mDesktopUserRepositories.getCurrent()) + .addTask(task.displayId, task.taskId, task.isVisible = true); } @Test @@ -135,7 +141,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase { mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl); - verify(mDesktopRepository).addTask(task.displayId, task.taskId, task.isVisible); + verify(mDesktopUserRepositories.getCurrent()) + .addTask(task.displayId, task.taskId, task.isVisible); } @Test @@ -147,7 +154,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase { mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl); - verify(mDesktopRepository, never()).addTask(task.displayId, task.taskId, task.isVisible); + verify(mDesktopUserRepositories.getCurrent(), never()) + .addTask(task.displayId, task.taskId, task.isVisible); } @Test @@ -158,7 +166,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase { mFreeformTaskListener.onFocusTaskChanged(task); - verify(mDesktopRepository).addTask(task.displayId, task.taskId, task.isVisible); + verify(mDesktopUserRepositories.getCurrent()) + .addTask(task.displayId, task.taskId, task.isVisible); } @Test @@ -171,7 +180,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase { mFreeformTaskListener.onFocusTaskChanged(fullscreenTask); - verify(mDesktopRepository, never()) + verify(mDesktopUserRepositories.getCurrent(), never()) .addTask(fullscreenTask.displayId, fullscreenTask.taskId, fullscreenTask.isVisible); } @@ -203,10 +212,11 @@ public final class FreeformTaskListenerTests extends ShellTestCase { @Test @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) @DisableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS) - public void onTaskVanished_nonClosingTask_noTransitionObservers_isMinimized() { + 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); @@ -214,7 +224,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase { task.displayId = INVALID_DISPLAY; mFreeformTaskListener.onTaskVanished(task); - verify(mDesktopRepository).minimizeTask(task.displayId, task.taskId); + verify(mDesktopUserRepositories.getCurrent(), never()).removeFreeformTask(task.displayId, + task.taskId); } @Test @@ -227,14 +238,17 @@ public final class FreeformTaskListenerTests extends ShellTestCase { mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl); - when(mDesktopRepository.isClosingTask(task.taskId)).thenReturn(true); + when(mDesktopUserRepositories.getCurrent() + .isClosingTask(task.taskId)).thenReturn(true); task.isVisible = false; task.displayId = INVALID_DISPLAY; mFreeformTaskListener.onTaskVanished(task); - verify(mDesktopRepository, never()).minimizeTask(task.displayId, task.taskId); - verify(mDesktopRepository).removeClosingTask(task.taskId); - verify(mDesktopRepository).removeFreeformTask(task.displayId, task.taskId); + verify(mDesktopUserRepositories.getCurrent(), never()) + .minimizeTask(task.displayId, task.taskId); + verify(mDesktopUserRepositories.getCurrent()).removeClosingTask(task.taskId); + verify(mDesktopUserRepositories.getCurrent()) + .removeFreeformTask(task.displayId, task.taskId); } @Test @@ -246,9 +260,12 @@ public final class FreeformTaskListenerTests extends ShellTestCase { mFreeformTaskListener.onTaskVanished(task); - verify(mDesktopRepository, never()).minimizeTask(task.displayId, task.taskId); - verify(mDesktopRepository, never()).removeClosingTask(task.taskId); - verify(mDesktopRepository, never()).removeFreeformTask(task.displayId, task.taskId); + verify(mDesktopUserRepositories.getCurrent(), never()) + .minimizeTask(task.displayId, task.taskId); + verify(mDesktopUserRepositories.getCurrent(), never()) + .removeClosingTask(task.taskId); + verify(mDesktopUserRepositories.getCurrent(), never()) + .removeFreeformTask(task.displayId, task.taskId); } @Test @@ -274,7 +291,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase { mFreeformTaskListener.onTaskInfoChanged(task); verify(mTaskChangeListener, never()).onTaskChanging(any()); - verify(mDesktopRepository).updateTask(task.displayId, task.taskId, task.isVisible); + verify(mDesktopUserRepositories.getCurrent()) + .updateTask(task.displayId, task.taskId, task.isVisible); } @Test @@ -289,7 +307,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase { mFreeformTaskListener.onTaskInfoChanged(task); verify(mTaskChangeListener).onNonTransitionTaskChanging(any()); - verify(mDesktopRepository, never()) + verify(mDesktopUserRepositories.getCurrent(), never()) .updateTask(task.displayId, task.taskId, task.isVisible); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java index 7d063a0a773f..256ed413c2cf 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java @@ -48,7 +48,6 @@ 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.TaskStackListenerImpl; -import com.android.wm.shell.shared.ShellSharedConstants; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -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/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index 289fd2d838fd..2eb2c3b8e2f7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -65,7 +65,7 @@ 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.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.pip.phone.PhonePipMenuController; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -103,7 +103,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { @Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper; @Mock private PipUiEventLogger mMockPipUiEventLogger; @Mock private Optional<SplitScreenController> mMockOptionalSplitScreen; - @Mock private Optional<DesktopRepository> mMockOptionalDesktopRepository; + @Mock private Optional<DesktopUserRepositories> mMockOptionalDesktopUserRepositories; @Mock private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; @Mock private ShellTaskOrganizer mMockShellTaskOrganizer; @Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder; @@ -136,7 +136,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { mMockPipSurfaceTransactionHelper, mMockPipTransitionController, mMockPipParamsChangedForwarder, mMockOptionalSplitScreen, Optional.empty() /* pipPerfHintControllerOptional */, - mMockOptionalDesktopRepository, mRootTaskDisplayAreaOrganizer, + mMockOptionalDesktopUserRepositories, mRootTaskDisplayAreaOrganizer, mMockDisplayController, mMockPipUiEventLogger, mMockShellTaskOrganizer, mMainExecutor); mMainExecutor.flushAll(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index b123f4dfac9e..5ef934ce8394 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -58,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 +72,6 @@ import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; -import com.android.wm.shell.shared.ShellSharedConstants; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -178,7 +178,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 diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java index cab625216236..3fe8c109807a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java @@ -42,8 +42,10 @@ import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; import com.android.wm.shell.pip2.animation.PipAlphaAnimator; @@ -56,6 +58,8 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Optional; + /** * Unit test against {@link PipScheduler} */ @@ -79,6 +83,8 @@ public class PipSchedulerTest { @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory; @Mock private SurfaceControl.Transaction mMockTransaction; @Mock private PipAlphaAnimator mMockAlphaAnimator; + @Mock private Optional<DesktopUserRepositories> mMockOptionalDesktopUserRepositories; + @Mock private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; @Captor private ArgumentCaptor<Runnable> mRunnableArgumentCaptor; @Captor private ArgumentCaptor<WindowContainerTransaction> mWctArgumentCaptor; @@ -96,7 +102,8 @@ public class PipSchedulerTest { .thenReturn(mMockTransaction); mPipScheduler = new PipScheduler(mMockContext, mMockPipBoundsState, mMockMainExecutor, - mMockPipTransitionState); + mMockPipTransitionState, mMockOptionalDesktopUserRepositories, + mRootTaskDisplayAreaOrganizer); mPipScheduler.setPipTransitionController(mMockPipTransitionController); mPipScheduler.setSurfaceControlTransactionFactory(mMockFactory); mPipScheduler.setPipAlphaAnimatorSupplier((context, leash, tx, direction) -> diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index 68c8aab8849d..22b45e8c63af 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -22,7 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; -import static com.android.launcher3.Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL; +import static com.android.launcher3.Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK; import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50; @@ -74,9 +74,9 @@ 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.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.ShellSharedConstants; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.shared.split.SplitBounds; import com.android.wm.shell.sysui.ShellCommandHandler; @@ -113,8 +113,6 @@ public class RecentTasksControllerTest extends ShellTestCase { @Mock private ShellCommandHandler mShellCommandHandler; @Mock - private DesktopRepository mDesktopRepository; - @Mock private ActivityTaskManager mActivityTaskManager; @Mock private DisplayInsetsController mDisplayInsetsController; @@ -122,6 +120,10 @@ public class RecentTasksControllerTest extends ShellTestCase { private IRecentTasksListener mRecentTasksListener; @Mock private TaskStackTransitionObserver mTaskStackTransitionObserver; + @Mock + private DesktopUserRepositories mDesktopUserRepositories; + @Mock + private DesktopRepository mDesktopRepository; @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -142,6 +144,8 @@ public class RecentTasksControllerTest extends ShellTestCase { .when(() -> DesktopModeStatus.canEnterDesktopMode(any())); 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)); @@ -150,7 +154,7 @@ public class RecentTasksControllerTest extends ShellTestCase { mDisplayInsetsController, mMainExecutor)); mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit, mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager, - Optional.of(mDesktopRepository), mTaskStackTransitionObserver, + Optional.of(mDesktopUserRepositories), mTaskStackTransitionObserver, mMainExecutor); mRecentTasksController = spy(mRecentTasksControllerReal); mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler, @@ -178,7 +182,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 @@ -240,7 +250,7 @@ public class RecentTasksControllerTest extends ShellTestCase { t3.taskId, -1); } - @EnableFlags(FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL) + @EnableFlags(FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK) @Test public void testGetRecentTasks_removesDesktopWallpaperActivity() { RecentTaskInfo t1 = makeTaskInfo(1); @@ -323,8 +333,8 @@ public class RecentTasksControllerTest extends ShellTestCase { RecentTaskInfo t4 = makeTaskInfo(4); setRawList(t1, t2, t3, t4); - when(mDesktopRepository.isActiveTask(1)).thenReturn(true); - when(mDesktopRepository.isActiveTask(3)).thenReturn(true); + when(mDesktopUserRepositories.getCurrent().isActiveTask(1)).thenReturn(true); + when(mDesktopUserRepositories.getCurrent().isActiveTask(3)).thenReturn(true); ArrayList<GroupedTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); @@ -362,8 +372,8 @@ public class RecentTasksControllerTest extends ShellTestCase { new SplitBounds(new Rect(), new Rect(), 1, 2, SNAP_TO_2_50_50); mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, pair1Bounds); - when(mDesktopRepository.isActiveTask(3)).thenReturn(true); - when(mDesktopRepository.isActiveTask(5)).thenReturn(true); + when(mDesktopUserRepositories.getCurrent().isActiveTask(3)).thenReturn(true); + when(mDesktopUserRepositories.getCurrent().isActiveTask(5)).thenReturn(true); ArrayList<GroupedTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); @@ -402,8 +412,8 @@ public class RecentTasksControllerTest extends ShellTestCase { RecentTaskInfo t4 = makeTaskInfo(4); setRawList(t1, t2, t3, t4); - when(mDesktopRepository.isActiveTask(1)).thenReturn(true); - when(mDesktopRepository.isActiveTask(3)).thenReturn(true); + when(mDesktopUserRepositories.getCurrent().isActiveTask(1)).thenReturn(true); + when(mDesktopUserRepositories.getCurrent().isActiveTask(3)).thenReturn(true); ArrayList<GroupedTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); @@ -431,7 +441,9 @@ public class RecentTasksControllerTest extends ShellTestCase { setRawList(t1, t2, t3, t4, t5); 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); @@ -470,8 +482,8 @@ public class RecentTasksControllerTest extends ShellTestCase { t2.lastNonFullscreenBounds = new Rect(150, 250, 350, 450); setRawList(t1, t2); - when(mDesktopRepository.isActiveTask(1)).thenReturn(true); - when(mDesktopRepository.isActiveTask(2)).thenReturn(true); + when(mDesktopUserRepositories.getCurrent().isActiveTask(1)).thenReturn(true); + when(mDesktopUserRepositories.getCurrent().isActiveTask(2)).thenReturn(true); ArrayList<GroupedTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java index f0f5fe159069..894d238b7e15 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java @@ -65,6 +65,7 @@ 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.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; @@ -100,7 +101,7 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { @Mock private ShellCommandHandler mShellCommandHandler; @Mock - private DesktopRepository mDesktopRepository; + private DesktopUserRepositories mDesktopUserRepositories; @Mock private ActivityTaskManager mActivityTaskManager; @Mock @@ -112,6 +113,8 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { @Mock private Transitions mTransitions; + @Mock private DesktopRepository mDesktopRepository; + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -131,6 +134,7 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { ExtendedMockito.doReturn(true) .when(() -> DesktopModeStatus.canEnterDesktopMode(any())); + when(mDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository); mMainExecutor = new TestShellExecutor(); when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class)); when(mContext.getSystemService(KeyguardManager.class)) @@ -140,7 +144,7 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { mDisplayInsetsController, mMainExecutor)); mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit, mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager, - Optional.of(mDesktopRepository), mTaskStackTransitionObserver, + Optional.of(mDesktopUserRepositories), mTaskStackTransitionObserver, mMainExecutor); mRecentTasksController = spy(mRecentTasksControllerReal); mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java index 72a7a3f5ec99..bb9703fce2e3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java @@ -71,10 +71,10 @@ import com.android.wm.shell.common.LaunchAdjacentController; 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.common.split.SplitState; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.recents.RecentTasksController; -import com.android.wm.shell.shared.ShellSharedConstants; import com.android.wm.shell.shared.TransactionPool; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; @@ -119,6 +119,7 @@ public class SplitScreenControllerTests extends ShellTestCase { @Mock WindowDecorViewModel mWindowDecorViewModel; @Mock DesktopTasksController mDesktopTasksController; @Mock MultiInstanceHelper mMultiInstanceHelper; + @Mock SplitState mSplitState; @Captor ArgumentCaptor<Intent> mIntentCaptor; private ShellController mShellController; @@ -136,7 +137,7 @@ public class SplitScreenControllerTests extends ShellTestCase { mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool, mIconProvider, Optional.of(mRecentTasks), mLaunchAdjacentController, Optional.of(mWindowDecorViewModel), Optional.of(mDesktopTasksController), - mStageCoordinator, mMultiInstanceHelper, mMainExecutor, mMainHandler)); + mStageCoordinator, mMultiInstanceHelper, mSplitState, mMainExecutor, mMainHandler)); } @Test @@ -178,7 +179,7 @@ public class SplitScreenControllerTests extends ShellTestCase { when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout()); mSplitScreenController.onInit(); verify(mShellController, times(1)).addExternalInterface( - eq(ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN), any(), any()); + eq(ISplitScreen.DESCRIPTOR), any(), any()); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java index d13a8888edc7..1a2d60ddad3e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java @@ -35,6 +35,7 @@ import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.split.SplitLayout; +import com.android.wm.shell.common.split.SplitState; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.shared.TransactionPool; import com.android.wm.shell.transition.Transitions; @@ -80,11 +81,11 @@ public class SplitTestUtils { ShellExecutor mainExecutor, Handler mainHandler, Optional<RecentTasksController> recentTasks, LaunchAdjacentController launchAdjacentController, - Optional<WindowDecorViewModel> windowDecorViewModel) { + Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState) { super(context, displayId, syncQueue, taskOrganizer, mainStage, sideStage, displayController, imeController, insetsController, splitLayout, transitions, transactionPool, mainExecutor, mainHandler, recentTasks, - launchAdjacentController, windowDecorViewModel); + launchAdjacentController, windowDecorViewModel, splitState); // Prepare root task for testing. mRootTask = new TestRunningTaskInfoBuilder().build(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index e32cf3899a03..de77837cb0e5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -78,6 +78,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.split.SplitDecorManager; import com.android.wm.shell.common.split.SplitLayout; +import com.android.wm.shell.common.split.SplitState; import com.android.wm.shell.shared.TransactionPool; import com.android.wm.shell.transition.DefaultMixedHandler; import com.android.wm.shell.transition.TestRemoteTransition; @@ -108,6 +109,7 @@ public class SplitTransitionTests extends ShellTestCase { @Mock private Transitions mTransitions; @Mock private IconProvider mIconProvider; @Mock private WindowDecorViewModel mWindowDecorViewModel; + @Mock private SplitState mSplitState; @Mock private ShellExecutor mMainExecutor; @Mock private Handler mMainHandler; @Mock private LaunchAdjacentController mLaunchAdjacentController; @@ -144,7 +146,7 @@ public class SplitTransitionTests extends ShellTestCase { mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool, mMainExecutor, mMainHandler, Optional.empty(), - mLaunchAdjacentController, Optional.empty()); + mLaunchAdjacentController, Optional.empty(), mSplitState); mStageCoordinator.setMixedHandler(mMixedHandler); mSplitScreenTransitions = mStageCoordinator.getSplitTransitions(); doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class)) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index 1e739cd446ae..7afcce1243e3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -71,6 +71,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.split.SplitDecorManager; import com.android.wm.shell.common.split.SplitLayout; +import com.android.wm.shell.common.split.SplitState; import com.android.wm.shell.shared.TransactionPool; import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener; import com.android.wm.shell.sysui.ShellController; @@ -116,6 +117,8 @@ public class StageCoordinatorTests extends ShellTestCase { private LaunchAdjacentController mLaunchAdjacentController; @Mock private DefaultMixedHandler mDefaultMixedHandler; + @Mock + private SplitState mSplitState; private final Rect mBounds1 = new Rect(10, 20, 30, 40); private final Rect mBounds2 = new Rect(5, 10, 15, 20); @@ -139,7 +142,7 @@ public class StageCoordinatorTests extends ShellTestCase { mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool, mMainExecutor, mMainHandler, Optional.empty(), mLaunchAdjacentController, - Optional.empty())); + Optional.empty(), mSplitState)); mDividerLeash = new SurfaceControl.Builder().setName("fakeDivider").build(); when(mSplitLayout.getTopLeftBounds()).thenReturn(mBounds1); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java index 7fd1c11e61ae..17a5f5c0f3d4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java @@ -42,7 +42,6 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.shared.ShellSharedConstants; import com.android.wm.shell.shared.TransactionPool; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; @@ -99,7 +98,7 @@ public class StartingWindowControllerTests extends ShellTestCase { @Test public void instantiateController_addExternalInterface() { verify(mShellController, times(1)).addExternalInterface( - eq(ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW), any(), any()); + eq(IStartingWindow.DESCRIPTOR), any(), any()); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index 2442a55d78d0..dd645fd968e4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -110,7 +110,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.recents.IRecentsAnimationRunner; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.recents.RecentsTransitionHandler; -import com.android.wm.shell.shared.ShellSharedConstants; +import com.android.wm.shell.shared.IShellTransitions; import com.android.wm.shell.shared.TransactionPool; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -176,7 +176,7 @@ public class ShellTransitionTests extends ShellTestCase { mock(FocusTransitionObserver.class)); shellInit.init(); verify(shellController, times(1)).addExternalInterface( - eq(ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS), any(), any()); + eq(IShellTransitions.DESCRIPTOR), any(), any()); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt index 0e15668a05a7..a328b5b2bb6b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt @@ -77,6 +77,30 @@ class TransitionObserverTestContext : TransitionObserverTestStep { validateObj.validate() } + fun validateOnMerged( + validate: + TransitionObserverOnTransitionMergedValidation.() -> Unit + ) { + val validateObj = TransitionObserverOnTransitionMergedValidation() + transitionObserver.onTransitionMerged( + validateObj.playing, + validateObj.merged + ) + validateObj.validate() + } + + fun validateOnFinished( + validate: + TransitionObserverOnTransitionFinishedValidation.() -> Unit + ) { + val validateObj = TransitionObserverOnTransitionFinishedValidation() + transitionObserver.onTransitionFinished( + transitionReadyInput.transition, + validateObj.aborted + ) + validateObj.validate() + } + fun invokeObservable() { transitionObserver.onTransitionReady( transitionReadyInput.transition, @@ -162,6 +186,28 @@ class TransitionObserverInputBuilder : TransitionObserverTestStep { class TransitionObserverResultValidation : TransitionObserverTestStep /** + * Phase responsible for the execution of validation methods after the + * [TransitionObservable#onTransitionMerged] has been executed. + */ +class TransitionObserverOnTransitionMergedValidation : TransitionObserverTestStep { + val merged = mock<IBinder>() + val playing = mock<IBinder>() + + init { + spyOn(merged) + spyOn(playing) + } +} + +/** + * Phase responsible for the execution of validation methods after the + * [TransitionObservable#onTransitionFinished] has been executed. + */ +class TransitionObserverOnTransitionFinishedValidation : TransitionObserverTestStep { + var aborted: Boolean = false +} + +/** * Allows to run a test about a specific [TransitionObserver] passing the specific * implementation and input value as parameters for the [TransitionObserver#onTransitionReady] * method. diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt index 59141ca39487..b856a28e54db 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt @@ -48,6 +48,7 @@ class CaptionWindowDecorationTests : ShellTestCase() { CaptionWindowDecoration.updateRelayoutParams( relayoutParams, + mContext, taskInfo, true, false, @@ -71,6 +72,7 @@ class CaptionWindowDecorationTests : ShellTestCase() { CaptionWindowDecoration.updateRelayoutParams( relayoutParams, + mContext, taskInfo, true, false, @@ -90,6 +92,7 @@ class CaptionWindowDecorationTests : ShellTestCase() { val relayoutParams = WindowDecoration.RelayoutParams() CaptionWindowDecoration.updateRelayoutParams( relayoutParams, + mContext, taskInfo, true, false, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt index 1215c52209a5..e871711fd25e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt @@ -29,7 +29,7 @@ import com.android.wm.shell.MockToken import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder import com.android.wm.shell.TestShellExecutor -import com.android.wm.shell.desktopmode.DesktopRepository +import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer import com.google.common.truth.Truth.assertThat @@ -55,17 +55,18 @@ class DesktopHeaderManageWindowsMenuTest : ShellTestCase() { @Rule val setFlagsRule: SetFlagsRule = SetFlagsRule() - private lateinit var desktopRepository: DesktopRepository + private lateinit var userRepositories: DesktopUserRepositories private lateinit var menu: DesktopHeaderManageWindowsMenu @Before fun setUp() { - desktopRepository = DesktopRepository( + userRepositories = DesktopUserRepositories( context = context, shellInit = ShellInit(TestShellExecutor()), persistentRepository = mock(), repositoryInitializer = mock(), - mainCoroutineScope = mock() + mainCoroutineScope = mock(), + userManager = mock(), ) } @@ -78,12 +79,11 @@ class DesktopHeaderManageWindowsMenuTest : ShellTestCase() { @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) fun testShow_forImmersiveTask_usesSystemViewContainer() { val task = createFreeformTask() - desktopRepository.setTaskInFullImmersiveState( + userRepositories.getProfile(DEFAULT_USER_ID).setTaskInFullImmersiveState( displayId = task.displayId, taskId = task.taskId, immersive = true ) - menu = createMenu(task) assertThat(menu.menuViewContainer).isInstanceOf(AdditionalSystemViewContainer::class.java) @@ -96,7 +96,7 @@ class DesktopHeaderManageWindowsMenuTest : ShellTestCase() { displayController = mock(), rootTdaOrganizer = mock(), context = context, - desktopRepository = desktopRepository, + desktopUserRepositories = userRepositories, surfaceControlBuilderSupplier = { SurfaceControl.Builder() }, surfaceControlTransactionSupplier = { SurfaceControl.Transaction() }, snapshotList = emptyList(), @@ -108,5 +108,10 @@ class DesktopHeaderManageWindowsMenuTest : ShellTestCase() { .setToken(MockToken().token()) .setActivityType(ACTIVITY_TYPE_STANDARD) .setWindowingMode(WINDOWING_MODE_FREEFORM) + .setUserId(DEFAULT_USER_ID) .build() + + private companion object { + const val DEFAULT_USER_ID = 10 + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt index a15b61122713..8b4cf6d1fabe 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt @@ -125,6 +125,8 @@ class DesktopModeWindowDecorViewModelAppHandleOnlyTest : times(1) ).setAppHandleEducationTooltipCallbacks(openHandleMenuCallbackCaptor.capture(), any()) openHandleMenuCallbackCaptor.lastValue.invoke(task.taskId) + bgExecutor.flushAll() + testShellExecutor.flushAll() verify(decor, times(1)).createHandleMenu(anyBoolean()) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 153be07bd204..88f62d10913d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -59,6 +59,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.window.flags.Flags import com.android.wm.shell.R +import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction import com.android.wm.shell.desktopmode.DesktopImmersiveController import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger @@ -277,7 +278,7 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) fun testDecorationIsNotCreatedForTopTranslucentActivities() { val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply { - isTopActivityTransparent = true + isActivityStackTransparent = true isTopActivityNoDisplay = false numActivities = 1 } @@ -398,11 +399,12 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest maxOrRestoreListenerCaptor.value.invoke() verify(mockDesktopTasksController).toggleDesktopTaskSize( - eq(decor.mTaskInfo), - eq(ResizeTrigger.MAXIMIZE_MENU), - eq(InputMethod.UNKNOWN_INPUT_METHOD), - any(), - any() + decor.mTaskInfo, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeInteraction.Source.MAXIMIZE_MENU_TO_MAXIMIZE, + InputMethod.UNKNOWN_INPUT_METHOD + ) ) } @@ -778,6 +780,8 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest times(1) ).setAppHandleEducationTooltipCallbacks(openHandleMenuCallbackCaptor.capture(), any()) openHandleMenuCallbackCaptor.lastValue.invoke(task.taskId) + bgExecutor.flushAll() + testShellExecutor.flushAll() verify(decor, times(1)).createHandleMenu(anyBoolean()) } @@ -1061,11 +1065,12 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest verify(mockDesktopTasksController) .toggleDesktopTaskSize( - eq(decor.mTaskInfo), - eq(ResizeTrigger.MAXIMIZE_BUTTON), - eq(InputMethod.UNKNOWN_INPUT_METHOD), - any(), - any(), + decor.mTaskInfo, + ToggleTaskSizeInteraction( + ToggleTaskSizeInteraction.Direction.MAXIMIZE, + ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE, + InputMethod.UNKNOWN_INPUT_METHOD + ) ) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt index 080f496593cf..6be234ef5ca6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt @@ -60,6 +60,7 @@ import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.DesktopTasksLimiter +import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository import com.android.wm.shell.desktopmode.education.AppHandleEducationController import com.android.wm.shell.desktopmode.education.AppToWebEducationController @@ -73,6 +74,8 @@ import com.android.wm.shell.transition.Transitions import com.android.wm.shell.util.StubTransaction import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeKeyguardChangeListener import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener +import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost +import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder import org.junit.After import org.junit.Rule @@ -108,7 +111,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { protected val mockTaskOrganizer = mock<ShellTaskOrganizer>() protected val mockDisplayController = mock<DisplayController>() protected val mockSplitScreenController = mock<SplitScreenController>() - protected val mockDesktopRepository = mock<DesktopRepository>() + protected val mockDesktopUserRepositories = mock<DesktopUserRepositories>() protected val mockDisplayLayout = mock<DisplayLayout>() protected val displayInsetsController = mock<DisplayInsetsController>() protected val mockSyncQueue = mock<SyncTransactionQueue>() @@ -130,6 +133,8 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { protected val mockAssistContentRequester = mock<AssistContentRequester>() protected val bgExecutor = TestShellExecutor() protected val mockMultiInstanceHelper = mock<MultiInstanceHelper>() + private val mockWindowDecorViewHostSupplier = + mock<WindowDecorViewHostSupplier<WindowDecorViewHost>>() protected val mockTasksLimiter = mock<DesktopTasksLimiter>() protected val mockFreeformTaskTransitionStarter = mock<FreeformTaskTransitionStarter>() protected val mockActivityOrientationChangeHandler = @@ -142,6 +147,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { protected val mockAppToWebEducationController = mock<AppToWebEducationController>() protected val mockFocusTransitionObserver = mock<FocusTransitionObserver>() protected val mockCaptionHandleRepository = mock<WindowDecorCaptionHandleRepository>() + protected val mockDesktopRepository: DesktopRepository = mock<DesktopRepository>() protected val motionEvent = mock<MotionEvent>() val displayController = mock<DisplayController>() val displayLayout = mock<DisplayLayout>() @@ -168,6 +174,9 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { windowDecorByTaskIdSpy.clear() spyContext.addMockSystemService(InputManager::class.java, mockInputManager) desktopModeEventLogger = mock<DesktopModeEventLogger>() + whenever(mockDesktopUserRepositories.current).thenReturn(mockDesktopRepository) + whenever(mockDesktopUserRepositories.getProfile(anyInt())) + .thenReturn(mockDesktopRepository) desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel( spyContext, testShellExecutor, @@ -178,7 +187,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { mockShellCommandHandler, mockWindowManager, mockTaskOrganizer, - mockDesktopRepository, + mockDesktopUserRepositories, mockDisplayController, mockShellController, displayInsetsController, @@ -188,6 +197,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { mockDesktopImmersiveController, mockGenericLinksParser, mockAssistContentRequester, + mockWindowDecorViewHostSupplier, mockMultiInstanceHelper, mockDesktopModeWindowDecorFactory, mockInputMonitorFactory, @@ -276,6 +286,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { } else { statusBars() } + userId = context.userId } } @@ -284,7 +295,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { whenever( mockDesktopModeWindowDecorFactory.create( any(), any(), any(), any(), any(), any(), eq(task), any(), any(), any(), any(), - any(), any(), any(), any(), any(), any(), any(), any()) + any(), any(), any(), any(), any(), any(), any(), any(), any()) ).thenReturn(decoration) decoration.mTaskInfo = task whenever(decoration.user).thenReturn(mockUserHandle) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java index e390fbbd751f..5d5d1f220ae0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java @@ -29,6 +29,7 @@ import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlTransaction; +import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.CLOSE_MAXIMIZE_MENU_DELAY_MS; import static com.android.wm.shell.windowdecor.WindowDecoration.INVALID_CORNER_RADIUS; @@ -38,7 +39,6 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; @@ -50,7 +50,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.mockito.kotlin.VerificationKt.times; import android.app.ActivityManager; import android.app.assist.AssistContent; @@ -61,7 +60,6 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.PointF; import android.graphics.Rect; @@ -110,10 +108,13 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.desktopmode.CaptionState; import com.android.wm.shell.desktopmode.DesktopModeEventLogger; import com.android.wm.shell.desktopmode.DesktopRepository; +import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams; +import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost; +import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier; import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder; import kotlin.Unit; @@ -166,7 +167,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Mock private ShellTaskOrganizer mMockShellTaskOrganizer; @Mock - private DesktopRepository mMockDesktopRepository; + private DesktopUserRepositories mMockDesktopUserRepositories; @Mock private Choreographer mMockChoreographer; @Mock @@ -186,6 +187,10 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Mock private AttachedSurfaceControl mMockRootSurfaceControl; @Mock + private WindowDecorViewHostSupplier<WindowDecorViewHost> mMockWindowDecorViewHostSupplier; + @Mock + private WindowDecorViewHost mMockWindowDecorViewHost; + @Mock private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory; @Mock private TypedArray mMockRoundedCornersRadiusArray; @@ -215,6 +220,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private WindowDecorCaptionHandleRepository mMockCaptionHandleRepository; @Mock private DesktopModeEventLogger mDesktopModeEventLogger; + @Mock + private DesktopRepository mDesktopRepository; @Captor private ArgumentCaptor<Function1<Boolean, Unit>> mOnMaxMenuHoverChangeListener; @Captor @@ -271,6 +278,11 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())).thenReturn(false); when(mMockAppHeaderViewHolderFactory.create(any(), any(), any(), any(), any(), any(), any(), any())).thenReturn(mMockAppHeaderViewHolder); + when(mMockDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository); + when(mMockDesktopUserRepositories.getProfile(anyInt())).thenReturn(mDesktopRepository); + when(mMockWindowDecorViewHostSupplier.acquire(any(), eq(defaultDisplay))) + .thenReturn(mMockWindowDecorViewHost); + when(mMockWindowDecorViewHost.getSurfaceControl()).thenReturn(mock(SurfaceControl.class)); } @After @@ -295,12 +307,35 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test - public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreEnabled() { + public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreSetForFreeform() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); + RelayoutParams relayoutParams = new RelayoutParams(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, mContext, taskInfo, mMockSplitScreenController, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true, + mExclusionRegion); + + assertThat(relayoutParams.mShadowRadius) + .isNotEqualTo(WindowDecoration.INVALID_SHADOW_RADIUS); + } + + @Test + public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreNotSetForFullscreen() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); RelayoutParams relayoutParams = new RelayoutParams(); DesktopModeWindowDecoration.updateRelayoutParams( - relayoutParams, mContext, taskInfo, /* applyStartTransactionOnDraw= */ true, + relayoutParams, mContext, taskInfo, mMockSplitScreenController, + /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, /* isKeyguardVisibleAndOccluded */ false, @@ -309,7 +344,27 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* hasGlobalFocus= */ true, mExclusionRegion); - assertThat(relayoutParams.mShadowRadiusId).isNotEqualTo(Resources.ID_NULL); + assertThat(relayoutParams.mShadowRadius).isEqualTo(WindowDecoration.INVALID_SHADOW_RADIUS); + } + + @Test + public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreNotSetForSplit() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + RelayoutParams relayoutParams = new RelayoutParams(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, mContext, taskInfo, mMockSplitScreenController, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true, + mExclusionRegion); + + assertThat(relayoutParams.mShadowRadius).isEqualTo(WindowDecoration.INVALID_SHADOW_RADIUS); } @Test @@ -323,6 +378,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, @@ -346,6 +402,31 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ false, + new InsetsState(), + /* hasGlobalFocus= */ true, + mExclusionRegion); + + assertThat(relayoutParams.mCornerRadius).isEqualTo(INVALID_CORNER_RADIUS); + } + + @Test + public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersNotSetForSplit() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + fillRoundedCornersResources(/* fillValue= */ 30); + RelayoutParams relayoutParams = new RelayoutParams(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, @@ -373,6 +454,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, @@ -401,6 +483,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, @@ -426,6 +509,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, @@ -451,6 +535,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, @@ -475,6 +560,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, @@ -499,6 +585,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, @@ -522,6 +609,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, @@ -545,6 +633,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, @@ -567,6 +656,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, @@ -590,6 +680,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, @@ -613,6 +704,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, @@ -637,6 +729,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, @@ -662,6 +755,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, @@ -685,6 +779,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, @@ -710,6 +805,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, @@ -725,6 +821,31 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test + public void updateRelayoutParams_handle_bottomSplitIsInsetSource() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + final RelayoutParams relayoutParams = new RelayoutParams(); + when(mMockSplitScreenController.isLeftRightSplit()).thenReturn(false); + when(mMockSplitScreenController.getSplitPosition(taskInfo.taskId)) + .thenReturn(SPLIT_POSITION_BOTTOM_OR_RIGHT); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + mMockSplitScreenController, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false, + /* isStatusBarVisible */ true, + /* isKeyguardVisibleAndOccluded */ false, + /* inFullImmersiveMode */ true, + new InsetsState(), + /* hasGlobalFocus= */ true, + mExclusionRegion); + + assertThat(relayoutParams.mIsInsetSource).isTrue(); + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) public void updateRelayoutParams_header_addsPaddingInFullImmersive() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); @@ -741,6 +862,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, @@ -765,6 +887,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, @@ -788,6 +911,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ false, @@ -811,6 +935,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, @@ -833,6 +958,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ false, @@ -855,6 +981,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, @@ -878,6 +1005,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, @@ -893,6 +1021,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ false, @@ -916,6 +1045,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { relayoutParams, mTestableContext, taskInfo, + mMockSplitScreenController, /* applyStartTransactionOnDraw= */ true, /* shouldSetTaskPositionAndCrop */ false, /* isStatusBarVisible */ true, @@ -929,61 +1059,70 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test - public void relayout_fullscreenTask_appliesTransactionImmediately() { + public void updateRelayoutParams_handle_requestsAsyncViewHostRendering() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); - final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); + // Make the task fullscreen so that its decoration is an App Handle. taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + final RelayoutParams relayoutParams = new RelayoutParams(); - spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + mMockSplitScreenController, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop= */ false, + /* isStatusBarVisible= */ true, + /* isKeyguardVisibleAndOccluded= */ false, + /* inFullImmersiveMode= */ false, + new InsetsState(), + /* hasGlobalFocus= */ true, + mExclusionRegion); - verify(mMockTransaction).apply(); - verify(mMockRootSurfaceControl, never()).applyTransactionOnDraw(any()); + // App Handles don't need to be rendered in sync with the task animation, per UX. + assertThat(relayoutParams.mAsyncViewHost).isTrue(); } @Test - @Ignore("TODO(b/367235906): Due to MONITOR_INPUT permission error") - public void relayout_freeformTask_appliesTransactionOnDraw() { + public void updateRelayoutParams_header_requestsSyncViewHostRendering() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); - final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); + // Make the task freeform so that its decoration is an App Header. taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); - // Make non-resizable to avoid dealing with input-permissions (MONITOR_INPUT) - taskInfo.isResizeable = false; - - spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); - - verify(mMockTransaction, never()).apply(); - verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockTransaction); - } - - @Test - public void relayout_fullscreenTask_doesNotCreateViewHostImmediately() { - final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); - final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); - taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + final RelayoutParams relayoutParams = new RelayoutParams(); - spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + mMockSplitScreenController, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop= */ false, + /* isStatusBarVisible= */ true, + /* isKeyguardVisibleAndOccluded= */ false, + /* inFullImmersiveMode= */ false, + new InsetsState(), + /* hasGlobalFocus= */ true, + mExclusionRegion); - verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any()); + // App Headers must be rendered in sync with the task animation, so it cannot be delayed. + assertThat(relayoutParams.mAsyncViewHost).isFalse(); } @Test - public void relayout_fullscreenTask_postsViewHostCreation() { + public void relayout_fullscreenTask_appliesTransactionImmediately() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); - // Once for view host, the other for the AppHandle input layer. - verify(mMockHandler, times(2)).post(runnableArgument.capture()); - runnableArgument.getValue().run(); - verify(mMockSurfaceControlViewHostFactory).create(any(), any(), any()); + verify(mMockTransaction).apply(); + verify(mMockRootSurfaceControl, never()).applyTransactionOnDraw(any()); } @Test @Ignore("TODO(b/367235906): Due to MONITOR_INPUT permission error") - public void relayout_freeformTask_createsViewHostImmediately() { + public void relayout_freeformTask_appliesTransactionOnDraw() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); @@ -992,38 +1131,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); - verify(mMockSurfaceControlViewHostFactory).create(any(), any(), any()); - verify(mMockHandler, never()).post(any()); - } - - @Test - public void relayout_removesExistingHandlerCallback() { - final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); - final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); - taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); - spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); - // Once for view host, the other for the AppHandle input layer. - verify(mMockHandler, times(2)).post(runnableArgument.capture()); - - spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); - - verify(mMockHandler).removeCallbacks(runnableArgument.getValue()); - } - - @Test - public void close_removesExistingHandlerCallback() { - final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); - final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); - taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); - spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); - // Once for view host, the other for the AppHandle input layer. - verify(mMockHandler, times(2)).post(runnableArgument.capture()); - - spyWindowDecor.close(); - - verify(mMockHandler).removeCallbacks(runnableArgument.getValue()); + verify(mMockTransaction, never()).apply(); + verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockTransaction); } @Test @@ -1226,68 +1335,41 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB) - public void capturedLink_postsOnCapturedLinkExpiredRunnable() { - final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); - final DesktopModeWindowDecoration decor = createWindowDecoration( - taskInfo, TEST_URI1 /* captured link */, null /* web uri */, - null /* generic link */); - final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); - - // Run runnable to set captured link to expired - verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong()); - runnableArgument.getValue().run(); - - // Verify captured link is no longer valid by verifying link is not set as handle menu - // browser link. - createHandleMenu(decor); - verifyHandleMenuCreated(null /* uri */); - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB) public void capturedLink_capturedLinkNotResetToSameLink() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( taskInfo, TEST_URI1 /* captured link */, null /* web uri */, null /* generic link */); - final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); + final ArgumentCaptor<Function1<Intent, Unit>> openInBrowserCaptor = + ArgumentCaptor.forClass(Function1.class); - // Run runnable to set captured link to expired - verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong()); - runnableArgument.getValue().run(); + createHandleMenu(decor); + verify(mMockHandleMenu).show(any(), + any(), + any(), + any(), + any(), + any(), + openInBrowserCaptor.capture(), + any(), + any(), + any(), + anyBoolean() + ); + // Run runnable to set captured link to used + openInBrowserCaptor.getValue().invoke(new Intent(Intent.ACTION_MAIN, TEST_URI1)); // Relayout decor with same captured link decor.relayout(taskInfo, true /* hasGlobalFocus */, mExclusionRegion); - // Verify handle menu's browser link not set to captured link since link is expired + // Verify handle menu's browser link not set to captured link since link is already used createHandleMenu(decor); verifyHandleMenuCreated(null /* uri */); } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB) - public void capturedLink_capturedLinkStillUsedIfExpiredAfterHandleMenuCreation() { - final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); - final DesktopModeWindowDecoration decor = createWindowDecoration( - taskInfo, TEST_URI1 /* captured link */, null /* web uri */, - null /* generic link */); - final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); - - // Create handle menu before link expires - createHandleMenu(decor); - - // Run runnable to set captured link to expired - verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong()); - runnableArgument.getValue().run(); - - // Verify handle menu's browser link is set to captured link since menu was opened before - // captured link expired - verifyHandleMenuCreated(TEST_URI1); - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB) - public void capturedLink_capturedLinkExpiresAfterClick() { + public void capturedLink_capturedLinkSetToUsedAfterClick() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( taskInfo, TEST_URI1 /* captured link */, null /* web uri */, @@ -1407,8 +1489,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo, true /* relayout */); - when(mMockDesktopRepository.isTaskInFullImmersiveState(taskInfo.taskId)) - .thenReturn(true); + when(mMockDesktopUserRepositories.getCurrent() + .isTaskInFullImmersiveState(taskInfo.taskId)).thenReturn(true); createHandleMenu(decoration); @@ -1429,7 +1511,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Test @DisableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION, - Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION}) + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION_INTEGRATION}) public void notifyCaptionStateChanged_flagDisabled_doNoNotify() { when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true); final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); @@ -1643,13 +1725,13 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { boolean relayout) { final DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext, mContext, mMockDisplayController, mMockSplitScreenController, - mMockDesktopRepository, mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, - mMockHandler, mBgExecutor, mMockChoreographer, mMockSyncQueue, + mMockDesktopUserRepositories, mMockShellTaskOrganizer, taskInfo, + mMockSurfaceControl, mMockHandler, mBgExecutor, mMockChoreographer, mMockSyncQueue, mMockAppHeaderViewHolderFactory, mMockRootTaskDisplayAreaOrganizer, mMockGenericLinksParser, mMockAssistContentRequester, SurfaceControl.Builder::new, mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper(mMockWindowManager), mMockSurfaceControlViewHostFactory, - maximizeMenuFactory, mMockHandleMenuFactory, + mMockWindowDecorViewHostSupplier, maximizeMenuFactory, mMockHandleMenuFactory, mMockMultiInstanceHelper, mMockCaptionHandleRepository, mDesktopModeEventLogger); windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener, mMockTouchEventListener, mMockTouchEventListener); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index 534803db5fe0..d9693460008f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -61,6 +61,7 @@ import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; +import android.os.Handler; import android.platform.test.flag.junit.SetFlagsRule; import android.testing.AndroidTestingRunner; import android.util.DisplayMetrics; @@ -88,6 +89,8 @@ import com.android.wm.shell.desktopmode.DesktopModeEventLogger; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.tests.R; import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer; +import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost; +import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier; import org.junit.Before; import org.junit.Rule; @@ -114,6 +117,7 @@ public class WindowDecorationTests extends ShellTestCase { private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400); private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60); private static final int CORNER_RADIUS = 20; + private static final int SHADOW_RADIUS = 10; private static final int STATUS_BAR_INSET_SOURCE_ID = 0; @Rule @@ -129,6 +133,10 @@ public class WindowDecorationTests extends ShellTestCase { @Mock private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory; @Mock + private WindowDecorViewHostSupplier<WindowDecorViewHost> mMockWindowDecorViewHostSupplier; + @Mock + private WindowDecorViewHost mMockWindowDecorViewHost; + @Mock private SurfaceControlViewHost mMockSurfaceControlViewHost; @Mock private AttachedSurfaceControl mMockRootSurfaceControl; @@ -142,6 +150,8 @@ public class WindowDecorationTests extends ShellTestCase { private SurfaceControl mMockTaskSurface; @Mock private DesktopModeEventLogger mDesktopModeEventLogger; + @Mock + private Handler mMockHandler; private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions = new ArrayList<>(); @@ -162,7 +172,7 @@ public class WindowDecorationTests extends ShellTestCase { mRelayoutParams.mLayoutResId = 0; mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height; mCaptionMenuWidthId = R.dimen.test_freeform_decor_caption_menu_width; - mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius; + mRelayoutParams.mShadowRadius = SHADOW_RADIUS; mRelayoutParams.mCornerRadius = CORNER_RADIUS; when(mMockDisplayController.getDisplay(Display.DEFAULT_DISPLAY)) @@ -176,6 +186,10 @@ public class WindowDecorationTests extends ShellTestCase { // Add status bar inset so that WindowDecoration does not think task is in immersive mode mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars()).setVisible(true); doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt()); + + when(mMockWindowDecorViewHostSupplier.acquire(any(), any())) + .thenReturn(mMockWindowDecorViewHost); + when(mMockWindowDecorViewHost.getSurfaceControl()).thenReturn(mock(SurfaceControl.class)); } @Test @@ -232,10 +246,6 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl.Builder decorContainerSurfaceBuilder = createMockSurfaceControlBuilder(decorContainerSurface); mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); - final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); - final SurfaceControl.Builder captionContainerSurfaceBuilder = - createMockSurfaceControlBuilder(captionContainerSurface); - mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() .setDisplayId(Display.DEFAULT_DISPLAY) @@ -256,18 +266,19 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true); verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100); - verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface); - verify(captionContainerSurfaceBuilder).setContainerLayer(); + final SurfaceControl captionContainerSurface = mMockWindowDecorViewHost.getSurfaceControl(); + verify(mMockSurfaceControlStartT).reparent(captionContainerSurface, decorContainerSurface); verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64); verify(mMockSurfaceControlStartT).show(captionContainerSurface); - verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any()); - - verify(mMockSurfaceControlViewHost) - .setView(same(mMockView), - argThat(lp -> lp.height == 64 - && lp.width == 300 - && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0)); + verify(mMockWindowDecorViewHost).updateView( + same(mMockView), + argThat(lp -> lp.height == 64 + && lp.width == 300 + && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0), + eq(taskInfo.configuration), + any(), + eq(null) /* onDrawTransaction */); verify(mMockView).setTaskFocusState(true); verify(mMockWindowContainerTransaction).addInsetsSource( eq(taskInfo.token), @@ -280,7 +291,7 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS); verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS); - verify(mMockSurfaceControlStartT).setShadowRadius(mMockTaskSurface, 10); + verify(mMockSurfaceControlStartT).setShadowRadius(mMockTaskSurface, SHADOW_RADIUS); assertEquals(300, mRelayoutResult.mWidth); assertEquals(100, mRelayoutResult.mHeight); @@ -296,10 +307,6 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl.Builder decorContainerSurfaceBuilder = createMockSurfaceControlBuilder(decorContainerSurface); mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); - final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); - final SurfaceControl.Builder captionContainerSurfaceBuilder = - createMockSurfaceControlBuilder(captionContainerSurface); - mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); mMockSurfaceControlTransactions.add(t); @@ -322,7 +329,7 @@ public class WindowDecorationTests extends ShellTestCase { windowDecor.relayout(taskInfo, true /* hasGlobalFocus */); - verify(mMockSurfaceControlViewHost, never()).release(); + verify(mMockWindowDecorViewHost, never()).release(any()); verify(t, never()).apply(); verify(mMockWindowContainerTransaction, never()) .removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt()); @@ -332,9 +339,8 @@ public class WindowDecorationTests extends ShellTestCase { taskInfo.isVisible = false; windowDecor.relayout(taskInfo, false /* hasGlobalFocus */); - final InOrder releaseOrder = inOrder(t2, mMockSurfaceControlViewHost); - releaseOrder.verify(mMockSurfaceControlViewHost).release(); - releaseOrder.verify(t2).remove(captionContainerSurface); + final InOrder releaseOrder = inOrder(t2, mMockWindowDecorViewHostSupplier); + releaseOrder.verify(mMockWindowDecorViewHostSupplier).release(mMockWindowDecorViewHost, t2); releaseOrder.verify(t2).remove(decorContainerSurface); releaseOrder.verify(t2).apply(); // Expect to remove two insets sources, the caption insets and the mandatory gesture insets. @@ -382,8 +388,8 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockDisplayController).removeDisplayWindowListener(same(listener)); assertThat(mRelayoutResult.mRootView).isSameInstanceAs(mMockView); - verify(mMockSurfaceControlViewHostFactory).create(any(), eq(mockDisplay), any()); - verify(mMockSurfaceControlViewHost).setView(same(mMockView), any()); + verify(mMockWindowDecorViewHostSupplier).acquire(any(), eq(mockDisplay)); + verify(mMockWindowDecorViewHost).updateView(same(mMockView), any(), any(), any(), any()); } @Test @@ -396,10 +402,6 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl.Builder decorContainerSurfaceBuilder = createMockSurfaceControlBuilder(decorContainerSurface); mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); - final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); - final SurfaceControl.Builder captionContainerSurfaceBuilder = - createMockSurfaceControlBuilder(captionContainerSurface); - mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); mMockSurfaceControlTransactions.add(t); @@ -435,8 +437,7 @@ public class WindowDecorationTests extends ShellTestCase { windowDecor.mDecorWindowContext.getResources(), mRelayoutParams.mCaptionHeightId); verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, width, height); verify(mMockSurfaceControlAddWindowT).show(additionalWindowSurface); - verify(mMockSurfaceControlViewHostFactory, Mockito.times(2)) - .create(any(), eq(defaultDisplay), any()); + verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any()); } @Test @@ -449,10 +450,6 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl.Builder decorContainerSurfaceBuilder = createMockSurfaceControlBuilder(decorContainerSurface); mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); - final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); - final SurfaceControl.Builder captionContainerSurfaceBuilder = - createMockSurfaceControlBuilder(captionContainerSurface); - mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); mMockSurfaceControlTransactions.add(t); @@ -471,8 +468,8 @@ public class WindowDecorationTests extends ShellTestCase { windowDecor.relayout(taskInfo, true /* hasGlobalFocus */); - verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface); - verify(captionContainerSurfaceBuilder).setContainerLayer(); + final SurfaceControl captionContainerSurface = mMockWindowDecorViewHost.getSurfaceControl(); + verify(mMockSurfaceControlStartT).reparent(captionContainerSurface, decorContainerSurface); // Width of the captionContainerSurface should match the width of TASK_BOUNDS verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64); verify(mMockSurfaceControlStartT).show(captionContainerSurface); @@ -488,10 +485,6 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl.Builder decorContainerSurfaceBuilder = createMockSurfaceControlBuilder(decorContainerSurface); mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); - final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); - final SurfaceControl.Builder captionContainerSurfaceBuilder = - createMockSurfaceControlBuilder(captionContainerSurface); - mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); mMockSurfaceControlTransactions.add(t); @@ -508,10 +501,11 @@ public class WindowDecorationTests extends ShellTestCase { taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); - windowDecor.relayout(taskInfo, true /* applyStartTransactionOnDraw */, - true /* hasGlobalFocus */, Region.obtain()); + mRelayoutParams.mApplyStartTransactionOnDraw = true; + windowDecor.relayout(taskInfo, true /* hasGlobalFocus */, Region.obtain()); - verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT); + verify(mMockWindowDecorViewHost).updateView(any(), any(), any(), any(), + eq(mMockSurfaceControlStartT)); } @Test @@ -900,37 +894,69 @@ public class WindowDecorationTests extends ShellTestCase { } @Test - public void updateViewHost_applyTransactionOnDrawIsTrue_surfaceControlIsUpdated() { + public void relayout_applyTransactionOnDrawIsTrue_updatesViewWithDrawTransaction() { final TestWindowDecoration windowDecor = createWindowDecoration( - new TestRunningTaskInfoBuilder().build()); + new TestRunningTaskInfoBuilder() + .setVisible(true) + .setWindowingMode(WINDOWING_MODE_FREEFORM) + .build()); mRelayoutParams.mApplyStartTransactionOnDraw = true; mRelayoutResult.mRootView = mMockView; - windowDecor.updateViewHost(mRelayoutParams, mMockSurfaceControlStartT, mRelayoutResult); - - verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT); + windowDecor.relayout( + windowDecor.mTaskInfo, + /* hasGlobalFocus= */ true, + Region.obtain()); + + verify(mMockWindowDecorViewHost) + .updateView( + eq(mRelayoutResult.mRootView), + any(), + eq(windowDecor.mTaskInfo.configuration), + any(), + eq(mMockSurfaceControlStartT)); + windowDecor.close(); } @Test - public void updateViewHost_nullDrawTransaction_applyTransactionOnDrawIsTrue_throwsException() { + public void relayout_applyTransactionOnDrawIsTrue_asyncViewHostRendering_throwsException() { final TestWindowDecoration windowDecor = createWindowDecoration( - new TestRunningTaskInfoBuilder().build()); + new TestRunningTaskInfoBuilder() + .setVisible(true) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN) + .build()); mRelayoutParams.mApplyStartTransactionOnDraw = true; + mRelayoutParams.mAsyncViewHost = true; mRelayoutResult.mRootView = mMockView; assertThrows(IllegalArgumentException.class, - () -> windowDecor.updateViewHost( - mRelayoutParams, null /* onDrawTransaction */, mRelayoutResult)); + () -> windowDecor.relayout( + windowDecor.mTaskInfo, + /* hasGlobalFocus= */ true, + Region.obtain())); + windowDecor.close(); } @Test - public void updateViewHost_nullDrawTransaction_applyTransactionOnDrawIsFalse_doesNotThrow() { + public void relayout_asyncViewHostRendering() { final TestWindowDecoration windowDecor = createWindowDecoration( - new TestRunningTaskInfoBuilder().build()); + new TestRunningTaskInfoBuilder() + .setVisible(true) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN) + .build()); mRelayoutParams.mApplyStartTransactionOnDraw = false; + mRelayoutParams.mAsyncViewHost = true; mRelayoutResult.mRootView = mMockView; - windowDecor.updateViewHost(mRelayoutParams, null /* onDrawTransaction */, mRelayoutResult); + windowDecor.relayout( + windowDecor.mTaskInfo, + /* hasGlobalFocus= */ true, + Region.obtain()); + + verify(mMockWindowDecorViewHost) + .updateViewAsync(eq(mRelayoutResult.mRootView), any(), + eq(windowDecor.mTaskInfo.configuration), any()); + windowDecor.close(); } @Test @@ -1014,7 +1040,8 @@ public class WindowDecorationTests extends ShellTestCase { new MockObjectSupplier<>(mMockSurfaceControlTransactions, () -> mock(SurfaceControl.Transaction.class)), () -> mMockWindowContainerTransaction, () -> mMockTaskSurface, - mMockSurfaceControlViewHostFactory, mDesktopModeEventLogger); + mMockSurfaceControlViewHostFactory, mMockWindowDecorViewHostSupplier, + mDesktopModeEventLogger); } private class MockObjectSupplier<T> implements Supplier<T> { @@ -1048,18 +1075,22 @@ public class WindowDecorationTests extends ShellTestCase { private class TestWindowDecoration extends WindowDecoration<TestView> { TestWindowDecoration(Context context, @NonNull Context userContext, DisplayController displayController, - ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, + ShellTaskOrganizer taskOrganizer, + ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, Supplier<WindowContainerTransaction> windowContainerTransactionSupplier, Supplier<SurfaceControl> surfaceControlSupplier, SurfaceControlViewHostFactory surfaceControlViewHostFactory, + @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> + windowDecorViewHostSupplier, DesktopModeEventLogger desktopModeEventLogger) { - super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface, - surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, + super(context, userContext, displayController, taskOrganizer, taskInfo, + taskSurface, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, windowContainerTransactionSupplier, surfaceControlSupplier, - surfaceControlViewHostFactory, desktopModeEventLogger); + surfaceControlViewHostFactory, windowDecorViewHostSupplier, + desktopModeEventLogger); } void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus) { @@ -1070,8 +1101,12 @@ public class WindowDecorationTests extends ShellTestCase { @Override void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) { - relayout(taskInfo, false /* applyStartTransactionOnDraw */, hasGlobalFocus, - displayExclusionRegion); + mRelayoutParams.mRunningTaskInfo = taskInfo; + mRelayoutParams.mHasGlobalFocus = hasGlobalFocus; + mRelayoutParams.mDisplayExclusionRegion.set(displayExclusionRegion); + mRelayoutParams.mLayoutResId = R.layout.caption_layout; + relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT, + mMockWindowContainerTransaction, mMockView, mRelayoutResult); } @Override @@ -1095,13 +1130,8 @@ public class WindowDecorationTests extends ShellTestCase { void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean applyStartTransactionOnDraw, boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) { - mRelayoutParams.mRunningTaskInfo = taskInfo; mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; - mRelayoutParams.mLayoutResId = R.layout.caption_layout; - mRelayoutParams.mHasGlobalFocus = hasGlobalFocus; - mRelayoutParams.mDisplayExclusionRegion.set(displayExclusionRegion); - relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT, - mMockWindowContainerTransaction, mMockView, mRelayoutResult); + relayout(taskInfo, hasGlobalFocus, displayExclusionRegion); } private AdditionalViewContainer addTestViewContainer() { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostTest.kt new file mode 100644 index 000000000000..4f19f34b8370 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostTest.kt @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.windowdecor.common.viewhost + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.SurfaceControl +import android.view.View +import android.view.WindowManager +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify + +/** + * Tests for [DefaultWindowDecorViewHost]. + * + * Build/Install/Run: atest WMShellUnitTests:DefaultWindowDecorViewHostTest + */ +@SmallTest +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner::class) +class DefaultWindowDecorViewHostTest : ShellTestCase() { + + @Test + fun updateView_layoutInViewHost() = runTest { + val windowDecorViewHost = createDefaultViewHost() + val view = View(context) + + windowDecorViewHost.updateView( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + onDrawTransaction = null, + ) + + assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue() + assertThat(windowDecorViewHost.view()).isEqualTo(view) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun updateView_clearsPendingAsyncJob() = runTest { + val windowDecorViewHost = createDefaultViewHost() + val asyncView = View(context) + val syncView = View(context) + val asyncAttrs = WindowManager.LayoutParams(100, 100) + val syncAttrs = WindowManager.LayoutParams(200, 200) + + windowDecorViewHost.updateViewAsync( + view = asyncView, + attrs = asyncAttrs, + configuration = context.resources.configuration, + ) + + // No view host yet, since the coroutine hasn't run. + assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isFalse() + + windowDecorViewHost.updateView( + view = syncView, + attrs = syncAttrs, + configuration = context.resources.configuration, + onDrawTransaction = null, + ) + + // Would run coroutine if it hadn't been cancelled. + advanceUntilIdle() + + assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue() + assertThat(windowDecorViewHost.view()).isNotNull() + // View host view/attrs should match the ones from the sync call, plus, since the + // sync/async were made with different views, if the job hadn't been cancelled there + // would've been an exception thrown as replacing views isn't allowed. + assertThat(windowDecorViewHost.view()).isEqualTo(syncView) + assertThat(windowDecorViewHost.view()!!.layoutParams.width).isEqualTo(syncAttrs.width) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun updateViewAsync() = runTest { + val windowDecorViewHost = createDefaultViewHost() + val view = View(context) + val attrs = WindowManager.LayoutParams(100, 100) + + windowDecorViewHost.updateViewAsync( + view = view, + attrs = attrs, + configuration = context.resources.configuration, + ) + + assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isFalse() + + advanceUntilIdle() + + assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue() + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun updateViewAsync_clearsPendingAsyncJob() = runTest { + val windowDecorViewHost = createDefaultViewHost() + + val view = View(context) + windowDecorViewHost.updateViewAsync( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + ) + val otherView = View(context) + windowDecorViewHost.updateViewAsync( + view = otherView, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + ) + + advanceUntilIdle() + + assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue() + assertThat(windowDecorViewHost.view()).isEqualTo(otherView) + } + + @Test + fun release() = runTest { + val windowDecorViewHost = createDefaultViewHost() + + val view = View(context) + windowDecorViewHost.updateView( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + onDrawTransaction = null, + ) + + val t = mock(SurfaceControl.Transaction::class.java) + windowDecorViewHost.release(t) + + verify(windowDecorViewHost.viewHostAdapter).release(t) + } + + private fun CoroutineScope.createDefaultViewHost() = + DefaultWindowDecorViewHost( + context = context, + mainScope = this, + display = context.display, + viewHostAdapter = spy(SurfaceControlViewHostAdapter(context, context.display)), + ) + + private fun DefaultWindowDecorViewHost.view(): View? = viewHostAdapter.viewHost?.view +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplierTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplierTest.kt new file mode 100644 index 000000000000..40583f80003c --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplierTest.kt @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.windowdecor.common.viewhost + +import android.content.res.Configuration +import android.graphics.Region +import android.testing.AndroidTestingRunner +import android.view.SurfaceControl +import android.view.View +import android.view.WindowManager +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.util.StubTransaction +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.mock + +/** + * Tests for [PooledWindowDecorViewHostSupplier]. + * + * Build/Install/Run: atest WMShellUnitTests:PooledWindowDecorViewHostSupplierTest + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +class PooledWindowDecorViewHostSupplierTest : ShellTestCase() { + + private lateinit var supplier: PooledWindowDecorViewHostSupplier + + @Test + fun setUp() { + MockitoAnnotations.initMocks(this) + } + + @Test + fun acquire_poolBelowLimit_caches() = runTest { + supplier = createSupplier(maxPoolSize = 5) + + val viewHost = FakeWindowDecorViewHost() + supplier.release(viewHost, StubTransaction()) + + assertThat(supplier.acquire(context, context.display)).isEqualTo(viewHost) + } + + @Test + fun release_poolBelowLimit_doesNotReleaseViewHost() = runTest { + supplier = createSupplier(maxPoolSize = 5) + + val viewHost = FakeWindowDecorViewHost() + val mockT = mock<SurfaceControl.Transaction>() + supplier.release(viewHost, mockT) + + assertThat(viewHost.released).isFalse() + } + + @Test + fun release_poolAtLimit_doesNotCache() = runTest { + supplier = createSupplier(maxPoolSize = 1) + val viewHost = FakeWindowDecorViewHost() + supplier.release(viewHost, StubTransaction()) // Maxes pool. + + val viewHost2 = FakeWindowDecorViewHost() + supplier.release(viewHost2, StubTransaction()) // Beyond limit. + + assertThat(supplier.acquire(context, context.display)).isEqualTo(viewHost) + // Second one wasn't cached, so the acquired one should've been a new instance. + assertThat(supplier.acquire(context, context.display)).isNotEqualTo(viewHost2) + } + + @Test + fun release_poolAtLimit_releasesViewHost() = runTest { + supplier = createSupplier(maxPoolSize = 1) + val viewHost = FakeWindowDecorViewHost() + supplier.release(viewHost, StubTransaction()) // Maxes pool. + + val viewHost2 = FakeWindowDecorViewHost() + val mockT = mock<SurfaceControl.Transaction>() + supplier.release(viewHost2, mockT) // Beyond limit. + + // Second one doesn't fit, so it needs to be released. + assertThat(viewHost2.released).isTrue() + } + + private fun CoroutineScope.createSupplier(maxPoolSize: Int) = + PooledWindowDecorViewHostSupplier(this, maxPoolSize) + + private class FakeWindowDecorViewHost : WindowDecorViewHost { + var released = false + private set + + override val surfaceControl: SurfaceControl + get() = SurfaceControl() + + override fun updateView( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + touchableRegion: Region?, + onDrawTransaction: SurfaceControl.Transaction?, + ) {} + + override fun updateViewAsync( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + touchableRegion: Region?, + ) {} + + override fun release(t: SurfaceControl.Transaction) { + released = true + } + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHostTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHostTest.kt new file mode 100644 index 000000000000..245393a6d44e --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHostTest.kt @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.windowdecor.common.viewhost + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.SurfaceControl +import android.view.View +import android.view.WindowManager +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify + +/** + * Tests for [ReusableWindowDecorViewHost]. + * + * Build/Install/Run: atest WMShellUnitTests:ReusableWindowDecorViewHostTest + */ +@SmallTest +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner::class) +class ReusableWindowDecorViewHostTest : ShellTestCase() { + + @Test + fun update_differentView_replacesView() = runTest { + val view = View(context) + val lp = WindowManager.LayoutParams() + val reusableVH = createReusableViewHost() + reusableVH.updateView(view, lp, context.resources.configuration, null) + + assertThat(reusableVH.rootView.childCount).isEqualTo(1) + assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(view) + + val newView = View(context) + val newLp = WindowManager.LayoutParams() + reusableVH.updateView(newView, newLp, context.resources.configuration, null) + + assertThat(reusableVH.rootView.childCount).isEqualTo(1) + assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(newView) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun updateView_clearsPendingAsyncJob() = runTest { + val reusableVH = createReusableViewHost() + val asyncView = View(context) + val syncView = View(context) + val asyncAttrs = WindowManager.LayoutParams(100, 100) + val syncAttrs = WindowManager.LayoutParams(200, 200) + + reusableVH.updateViewAsync( + view = asyncView, + attrs = asyncAttrs, + configuration = context.resources.configuration, + ) + + // No view host yet, since the coroutine hasn't run. + assertThat(reusableVH.viewHostAdapter.isInitialized()).isFalse() + + reusableVH.updateView( + view = syncView, + attrs = syncAttrs, + configuration = context.resources.configuration, + onDrawTransaction = null, + ) + + // Would run coroutine if it hadn't been cancelled. + advanceUntilIdle() + + assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue() + // View host view/attrs should match the ones from the sync call. + assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(syncView) + assertThat(reusableVH.view()!!.layoutParams.width).isEqualTo(syncAttrs.width) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun updateViewAsync() = runTest { + val reusableVH = createReusableViewHost() + val view = View(context) + val attrs = WindowManager.LayoutParams(100, 100) + + reusableVH.updateViewAsync( + view = view, + attrs = attrs, + configuration = context.resources.configuration, + ) + + assertThat(reusableVH.viewHostAdapter.isInitialized()).isFalse() + + advanceUntilIdle() + + assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue() + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun updateViewAsync_clearsPendingAsyncJob() = runTest { + val reusableVH = createReusableViewHost() + + val view = View(context) + reusableVH.updateViewAsync( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + ) + val otherView = View(context) + reusableVH.updateViewAsync( + view = otherView, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + ) + + advanceUntilIdle() + + assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue() + assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(otherView) + } + + @Test + fun release() = runTest { + val reusableVH = createReusableViewHost() + + val view = View(context) + reusableVH.updateView( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + onDrawTransaction = null, + ) + + val t = mock(SurfaceControl.Transaction::class.java) + reusableVH.release(t) + + verify(reusableVH.viewHostAdapter).release(t) + } + + private fun CoroutineScope.createReusableViewHost() = + ReusableWindowDecorViewHost( + context = context, + mainScope = this, + display = context.display, + id = 1, + viewHostAdapter = spy(SurfaceControlViewHostAdapter(context, context.display)), + ) + + private fun ReusableWindowDecorViewHost.view(): View? = viewHostAdapter.viewHost?.view +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapterTest.kt new file mode 100644 index 000000000000..5109a7c300f6 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapterTest.kt @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.windowdecor.common.viewhost + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.SurfaceControl +import android.view.SurfaceControlViewHost +import android.view.View +import android.view.WindowManager +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertThrows +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify + +/** + * Tests for [SurfaceControlViewHostAdapter]. + * + * Build/Install/Run: + * atest WMShellUnitTests:SurfaceControlViewHostAdapterTest + */ +@SmallTest +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner::class) +class SurfaceControlViewHostAdapterTest : ShellTestCase() { + + private lateinit var adapter: SurfaceControlViewHostAdapter + + @Before + fun setUp() { + adapter = SurfaceControlViewHostAdapter( + context, + context.display, + surfaceControlViewHostFactory = { c, d, wwm, s -> + spy(SurfaceControlViewHost(c, d, wwm, s)) + } + ) + } + + @Test + fun prepareViewHost() { + adapter.prepareViewHost(context.resources.configuration, touchableRegion = null) + + assertThat(adapter.viewHost).isNotNull() + } + + @Test + fun prepareViewHost_alreadyCreated_skips() { + adapter.prepareViewHost(context.resources.configuration, touchableRegion = null) + + val viewHost = adapter.viewHost!! + + adapter.prepareViewHost(context.resources.configuration, touchableRegion = null) + + assertThat(adapter.viewHost).isEqualTo(viewHost) + } + + @Test + fun updateView_layoutInViewHost() { + val view = View(context) + adapter.prepareViewHost(context.resources.configuration, touchableRegion = null) + + adapter.updateView( + view = view, + attrs = WindowManager.LayoutParams(100, 100) + ) + + assertThat(adapter.isInitialized()).isTrue() + assertThat(adapter.view()).isEqualTo(view) + } + + @Test + fun updateView_alreadyLaidOut_relayouts() { + val view = View(context) + adapter.prepareViewHost(context.resources.configuration, touchableRegion = null) + adapter.updateView( + view = view, + attrs = WindowManager.LayoutParams(100, 100) + ) + + val otherParams = WindowManager.LayoutParams(200, 200) + adapter.updateView( + view = view, + attrs = otherParams + ) + + assertThat(adapter.view()).isEqualTo(view) + assertThat(adapter.view()!!.layoutParams.width).isEqualTo(otherParams.width) + } + + @Test + fun updateView_replacingView_throws() { + val view = View(context) + adapter.prepareViewHost(context.resources.configuration, touchableRegion = null) + adapter.updateView( + view = view, + attrs = WindowManager.LayoutParams(100, 100) + ) + + val otherView = View(context) + assertThrows(Exception::class.java) { + adapter.updateView( + view = otherView, + attrs = WindowManager.LayoutParams(100, 100) + ) + } + } + + @Test + fun release() { + adapter.prepareViewHost(context.resources.configuration, touchableRegion = null) + adapter.updateView( + view = View(context), + attrs = WindowManager.LayoutParams(100, 100) + ) + + val mockT = mock(SurfaceControl.Transaction::class.java) + adapter.release(mockT) + + verify(adapter.viewHost!!).release() + verify(mockT).remove(adapter.rootSurface) + } + + private fun SurfaceControlViewHostAdapter.view(): View? = viewHost?.view +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt index d29002199f9d..193c2c25d26d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt @@ -25,7 +25,7 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopModeEventLogger -import com.android.wm.shell.desktopmode.DesktopRepository +import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator @@ -52,7 +52,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { private val syncQueueMock: SyncTransactionQueue = mock() private val transitionsMock: Transitions = mock() private val shellTaskOrganizerMock: ShellTaskOrganizer = mock() - private val desktopRepository: DesktopRepository = mock() + private val userRepositories: DesktopUserRepositories = mock() private val desktopModeEventLogger: DesktopModeEventLogger = mock() private val toggleResizeDesktopTaskTransitionHandlerMock: ToggleResizeDesktopTaskTransitionHandler = @@ -75,7 +75,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { shellTaskOrganizerMock, toggleResizeDesktopTaskTransitionHandlerMock, returnToDragStartAnimatorMock, - desktopRepository, + userRepositories, desktopModeEventLogger, ) whenever(contextMock.createContextAsUser(any(), any())).thenReturn(contextMock) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt index 3b39f1e4a25a..95e2151be96c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt @@ -39,6 +39,7 @@ import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeT import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask +import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler import com.android.wm.shell.transition.Transitions @@ -93,10 +94,11 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { private val transition: IBinder = mock() private val info: TransitionInfo = mock() private val finishCallback: Transitions.TransitionFinishCallback = mock() - private val desktopRepository: DesktopRepository = mock() + private val userRepositories: DesktopUserRepositories = mock() private val desktopModeEventLogger: DesktopModeEventLogger = mock() private val desktopTilingDividerWindowManager: DesktopTilingDividerWindowManager = mock() private val motionEvent: MotionEvent = mock() + private val desktopRepository: DesktopRepository = mock() private lateinit var tilingDecoration: DesktopTilingWindowDecoration private val split_divider_width = 10 @@ -116,10 +118,11 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { shellTaskOrganizer, toggleResizeDesktopTaskTransitionHandler, returnToDragStartAnimator, - desktopRepository, + userRepositories, desktopModeEventLogger, ) whenever(context.createContextAsUser(any(), any())).thenReturn(context) + whenever(userRepositories.current).thenReturn(desktopRepository) } @Test @@ -275,8 +278,8 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { } whenever(context.resources).thenReturn(resources) whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width) - whenever(desktopRepository.isVisibleTask(eq(task1.taskId))).thenReturn(true) - whenever(desktopRepository.isVisibleTask(eq(task2.taskId))).thenReturn(true) + whenever(userRepositories.current.isVisibleTask(eq(task1.taskId))).thenReturn(true) + whenever(userRepositories.current.isVisibleTask(eq(task2.taskId))).thenReturn(true) tilingDecoration.onAppTiled( task1, @@ -308,7 +311,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { whenever(context.resources).thenReturn(resources) whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width) whenever(desktopWindowDecoration.getLeash()).thenReturn(surfaceControlMock) - whenever(desktopRepository.isVisibleTask(any())).thenReturn(true) + whenever(userRepositories.current.isVisibleTask(any())).thenReturn(true) tilingDecoration.onAppTiled( task1, desktopWindowDecoration, @@ -341,7 +344,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { whenever(context.resources).thenReturn(resources) whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width) whenever(desktopWindowDecoration.getLeash()).thenReturn(surfaceControlMock) - whenever(desktopRepository.isVisibleTask(any())).thenReturn(true) + whenever(userRepositories.current.isVisibleTask(any())).thenReturn(true) tilingDecoration.onAppTiled( task1, desktopWindowDecoration, @@ -614,7 +617,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { private fun createVisibleTask() = createFreeformTask().also { - whenever(desktopRepository.isVisibleTask(eq(it.taskId))).thenReturn(true) + whenever(userRepositories.current.isVisibleTask(eq(it.taskId))).thenReturn(true) } companion object { diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index 49254d1c6f6e..dbb891455ddd 100644 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -40,20 +40,21 @@ ApkAssets::ApkAssets(PrivateConstructorUtil, std::unique_ptr<Asset> resources_as } ApkAssetsPtr ApkAssets::Load(const std::string& path, package_property_t flags) { - return Load(ZipAssetsProvider::Create(path, flags), flags); + return LoadImpl(ZipAssetsProvider::Create(path, flags), flags); } ApkAssetsPtr ApkAssets::LoadFromFd(base::unique_fd fd, const std::string& debug_name, package_property_t flags, off64_t offset, off64_t len) { - return Load(ZipAssetsProvider::Create(std::move(fd), debug_name, offset, len), flags); + return LoadImpl(ZipAssetsProvider::Create(std::move(fd), debug_name, offset, len), flags); } -ApkAssetsPtr ApkAssets::Load(std::unique_ptr<AssetsProvider> assets, package_property_t flags) { +ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<AssetsProvider>&& assets, + package_property_t flags) { return LoadImpl(std::move(assets), flags, nullptr /* idmap_asset */, nullptr /* loaded_idmap */); } -ApkAssetsPtr ApkAssets::LoadTable(std::unique_ptr<Asset> resources_asset, - std::unique_ptr<AssetsProvider> assets, +ApkAssetsPtr ApkAssets::LoadTable(std::unique_ptr<Asset>&& resources_asset, + std::unique_ptr<AssetsProvider>&& assets, package_property_t flags) { if (resources_asset == nullptr) { return {}; @@ -97,10 +98,10 @@ ApkAssetsPtr ApkAssets::LoadOverlay(const std::string& idmap_path, package_prope std::move(loaded_idmap)); } -ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<AssetsProvider> assets, +ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<AssetsProvider>&& assets, package_property_t property_flags, - std::unique_ptr<Asset> idmap_asset, - std::unique_ptr<LoadedIdmap> loaded_idmap) { + std::unique_ptr<Asset>&& idmap_asset, + std::unique_ptr<LoadedIdmap>&& loaded_idmap) { if (assets == nullptr) { return {}; } @@ -119,11 +120,11 @@ ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<AssetsProvider> assets, std::move(idmap_asset), std::move(loaded_idmap)); } -ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<Asset> resources_asset, - std::unique_ptr<AssetsProvider> assets, +ApkAssetsPtr ApkAssets::LoadImpl(std::unique_ptr<Asset>&& resources_asset, + std::unique_ptr<AssetsProvider>&& assets, package_property_t property_flags, - std::unique_ptr<Asset> idmap_asset, - std::unique_ptr<LoadedIdmap> loaded_idmap) { + std::unique_ptr<Asset>&& idmap_asset, + std::unique_ptr<LoadedIdmap>&& loaded_idmap) { if (assets == nullptr ) { return {}; } diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp index 5e645cceea2d..a592749c5398 100644 --- a/libs/androidfw/CursorWindow.cpp +++ b/libs/androidfw/CursorWindow.cpp @@ -38,7 +38,7 @@ CursorWindow::CursorWindow() { } CursorWindow::~CursorWindow() { - if (mAshmemFd != -1) { + if (mAshmemFd >= 0) { ::munmap(mData, mSize); ::close(mAshmemFd); } else { @@ -155,23 +155,27 @@ status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outWindow bool isAshmem; if (parcel->readBool(&isAshmem)) goto fail; if (isAshmem) { - window->mAshmemFd = parcel->readFileDescriptor(); - if (window->mAshmemFd < 0) { + int tempFd = parcel->readFileDescriptor(); + if (tempFd < 0) { LOG(ERROR) << "Failed readFileDescriptor"; goto fail_silent; } - window->mAshmemFd = ::fcntl(window->mAshmemFd, F_DUPFD_CLOEXEC, 0); - if (window->mAshmemFd < 0) { + tempFd = ::fcntl(tempFd, F_DUPFD_CLOEXEC, 0); + if (tempFd < 0) { PLOG(ERROR) << "Failed F_DUPFD_CLOEXEC"; goto fail_silent; } - window->mData = ::mmap(nullptr, window->mSize, PROT_READ, MAP_SHARED, window->mAshmemFd, 0); + window->mData = ::mmap(nullptr, window->mSize, PROT_READ, MAP_SHARED, tempFd, 0); if (window->mData == MAP_FAILED) { + ::close(tempFd); PLOG(ERROR) << "Failed mmap"; goto fail_silent; } + + window->mAshmemFd = tempFd; + } else { window->mAshmemFd = -1; diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h index 1fa67528c78b..231808beb718 100644 --- a/libs/androidfw/include/androidfw/ApkAssets.h +++ b/libs/androidfw/include/androidfw/ApkAssets.h @@ -47,13 +47,37 @@ class ApkAssets : public RefBase { package_property_t flags = 0U, off64_t offset = 0, off64_t len = AssetsProvider::kUnknownLength); + // // Creates an ApkAssets from an AssetProvider. - // The ApkAssets will take care of destroying the AssetsProvider when it is destroyed. - static ApkAssetsPtr Load(std::unique_ptr<AssetsProvider> assets, package_property_t flags = 0U); + // The ApkAssets will take care of destroying the AssetsProvider when it is destroyed; + // the original argument is not moved from if loading fails. + // + // Note: this function takes care of the case when you pass a move(unique_ptr<Derived>) + // that would create a temporary unique_ptr<AssetsProvider> by moving your pointer into + // it before the function call, making it impossible to not move from the parameter + // on loading failure. The two overloads take care of moving the pointer back if needed. + // + + template <class T> + static ApkAssetsPtr Load(std::unique_ptr<T>&& assets, package_property_t flags = 0U) + requires(std::is_same_v<T, AssetsProvider>) { + return LoadImpl(std::move(assets), flags); + } + + template <class T> + static ApkAssetsPtr Load(std::unique_ptr<T>&& assets, package_property_t flags = 0U) + requires(!std::is_same_v<T, AssetsProvider> && std::is_base_of_v<AssetsProvider, T>) { + std::unique_ptr<AssetsProvider> base_assets(std::move(assets)); + auto res = LoadImpl(std::move(base_assets), flags); + if (!res) { + assets.reset(static_cast<T*>(base_assets.release())); + } + return res; + } // Creates an ApkAssets from the given asset file representing a resources.arsc. - static ApkAssetsPtr LoadTable(std::unique_ptr<Asset> resources_asset, - std::unique_ptr<AssetsProvider> assets, + static ApkAssetsPtr LoadTable(std::unique_ptr<Asset>&& resources_asset, + std::unique_ptr<AssetsProvider>&& assets, package_property_t flags = 0U); // Creates an ApkAssets from an IDMAP, which contains the original APK path, and the overlay @@ -94,17 +118,29 @@ class ApkAssets : public RefBase { bool IsUpToDate() const; + // DANGER! + // This is a destructive method that rips the assets provider out of ApkAssets object. + // It is only useful when one knows this assets object can't be used anymore, and they + // need the underlying assets provider back (e.g. when initialization fails for some + // reason). + std::unique_ptr<AssetsProvider> TakeAssetsProvider() && { + return std::move(assets_provider_); + } + private: - static ApkAssetsPtr LoadImpl(std::unique_ptr<AssetsProvider> assets, + static ApkAssetsPtr LoadImpl(std::unique_ptr<AssetsProvider>&& assets, package_property_t property_flags, - std::unique_ptr<Asset> idmap_asset, - std::unique_ptr<LoadedIdmap> loaded_idmap); + std::unique_ptr<Asset>&& idmap_asset, + std::unique_ptr<LoadedIdmap>&& loaded_idmap); - static ApkAssetsPtr LoadImpl(std::unique_ptr<Asset> resources_asset, - std::unique_ptr<AssetsProvider> assets, + static ApkAssetsPtr LoadImpl(std::unique_ptr<Asset>&& resources_asset, + std::unique_ptr<AssetsProvider>&& assets, package_property_t property_flags, - std::unique_ptr<Asset> idmap_asset, - std::unique_ptr<LoadedIdmap> loaded_idmap); + std::unique_ptr<Asset>&& idmap_asset, + std::unique_ptr<LoadedIdmap>&& loaded_idmap); + + static ApkAssetsPtr LoadImpl(std::unique_ptr<AssetsProvider>&& assets, + package_property_t flags = 0U); // Allows us to make it possible to call make_shared from inside the class but still keeps the // ctor 'private' for all means and purposes. diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp index 70326b71da28..c36d9908032f 100644 --- a/libs/androidfw/tests/ApkAssets_test.cpp +++ b/libs/androidfw/tests/ApkAssets_test.cpp @@ -28,6 +28,7 @@ using ::android::base::unique_fd; using ::com::android::basic::R; using ::testing::Eq; using ::testing::Ge; +using ::testing::IsNull; using ::testing::NotNull; using ::testing::SizeIs; using ::testing::StrEq; @@ -108,4 +109,26 @@ TEST(ApkAssetsTest, OpenUncompressedAssetFd) { EXPECT_THAT(buffer, StrEq("This should be uncompressed.\n\n")); } +TEST(ApkAssetsTest, TakeAssetsProviderNotCrashing) { + // Make sure the apk assets object can survive taking its assets provider and doesn't crash + // the process. + { + auto loaded_apk = ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk"); + ASSERT_THAT(loaded_apk, NotNull()); + + auto provider = std::move(*loaded_apk).TakeAssetsProvider(); + ASSERT_THAT(provider, NotNull()); + } + // If this test doesn't crash by this point we're all good. +} + +TEST(ApkAssetsTest, AssetsProviderNotMovedOnError) { + auto assets_provider + = ZipAssetsProvider::Create(GetTestDataPath() + "/bad/bad.apk", 0); + ASSERT_THAT(assets_provider, NotNull()); + auto loaded_apk = ApkAssets::Load(std::move(assets_provider)); + ASSERT_THAT(loaded_apk, IsNull()); + ASSERT_THAT(assets_provider, NotNull()); +} + } // namespace android diff --git a/libs/androidfw/tests/data/bad/bad.apk b/libs/androidfw/tests/data/bad/bad.apk Binary files differnew file mode 100644 index 000000000000..3226bcd52e99 --- /dev/null +++ b/libs/androidfw/tests/data/bad/bad.apk diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt index de402095e195..139ccfd22b0e 100644 --- a/libs/appfunctions/api/current.txt +++ b/libs/appfunctions/api/current.txt @@ -16,6 +16,7 @@ package com.android.extensions.appfunctions { field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0 field public static final int ERROR_DENIED = 1000; // 0x3e8 field public static final int ERROR_DISABLED = 1002; // 0x3ea + field public static final int ERROR_ENTERPRISE_POLICY_DISALLOWED = 2002; // 0x7d2 field public static final int ERROR_FUNCTION_NOT_FOUND = 1003; // 0x3eb field public static final int ERROR_INVALID_ARGUMENT = 1001; // 0x3e9 field public static final int ERROR_SYSTEM_ERROR = 2000; // 0x7d0 diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java index 2540236f2ce5..0c521690b165 100644 --- a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java @@ -71,6 +71,13 @@ public final class AppFunctionException extends Exception { public static final int ERROR_CANCELLED = 2001; /** + * The operation was disallowed by enterprise policy. + * + * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. + */ + public static final int ERROR_ENTERPRISE_POLICY_DISALLOWED = 2002; + + /** * An unknown error occurred while processing the call in the AppFunctionService. * * <p>This error is thrown when the service is connected in the remote application but an @@ -189,7 +196,8 @@ public final class AppFunctionException extends Exception { ERROR_SYSTEM_ERROR, ERROR_INVALID_ARGUMENT, ERROR_DISABLED, - ERROR_CANCELLED + ERROR_CANCELLED, + ERROR_ENTERPRISE_POLICY_DISALLOWED }) @Retention(RetentionPolicy.SOURCE) public @interface ErrorCode {} diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig index fa27af671be6..e497ea1f3cb4 100644 --- a/libs/hwui/aconfig/hwui_flags.aconfig +++ b/libs/hwui/aconfig/hwui_flags.aconfig @@ -6,6 +6,7 @@ flag { namespace: "core_graphics" description: "API for AGSL authored runtime color filters and blenders" bug: "358126864" + is_exported: true } flag { @@ -44,6 +45,7 @@ flag { namespace: "accessibility" description: "Draw a solid rectangle background behind text instead of a stroke outline" bug: "186567103" + is_exported: true } flag { @@ -96,6 +98,7 @@ flag { namespace: "core_graphics" description: "Add canvas#drawRegion API" bug: "318612129" + is_exported: true } flag { diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index e5fb75575ac3..7b45070af312 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -48,7 +48,14 @@ minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint, minikinPaint.localeListId = paint->getMinikinLocaleListId(); minikinPaint.fontStyle = resolvedFace->fStyle; minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings(); - minikinPaint.fontVariationSettings = paint->getFontVariationOverride(); + if (!resolvedFace->fIsVariationInstance) { + // This is an optimization for direct private API use typically done by System UI. + // In the public API surface, if Typeface is already configured for variation instance + // (Target SDK <= 35) the font variation settings of Paint is not set. + // On the other hand, if Typeface is not configured so (Target SDK >= 36), the font + // variation settings are configured dynamically. + minikinPaint.fontVariationSettings = paint->getFontVariationOverride(); + } minikinPaint.verticalText = paint->isVerticalText(); const std::optional<minikin::FamilyVariant>& familyVariant = paint->getFamilyVariant(); diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp index 2d812d675fdc..4dfe05377a48 100644 --- a/libs/hwui/hwui/Typeface.cpp +++ b/libs/hwui/hwui/Typeface.cpp @@ -76,6 +76,7 @@ Typeface* Typeface::createRelative(Typeface* src, Typeface::Style style) { result->fBaseWeight = resolvedFace->fBaseWeight; result->fAPIStyle = style; result->fStyle = computeRelativeStyle(result->fBaseWeight, style); + result->fIsVariationInstance = resolvedFace->fIsVariationInstance; } return result; } @@ -88,6 +89,7 @@ Typeface* Typeface::createAbsolute(Typeface* base, int weight, bool italic) { result->fBaseWeight = resolvedFace->fBaseWeight; result->fAPIStyle = computeAPIStyle(weight, italic); result->fStyle = computeMinikinStyle(weight, italic); + result->fIsVariationInstance = resolvedFace->fIsVariationInstance; } return result; } @@ -109,6 +111,7 @@ Typeface* Typeface::createFromTypefaceWithVariation(Typeface* src, result->fBaseWeight = resolvedFace->fBaseWeight; result->fAPIStyle = resolvedFace->fAPIStyle; result->fStyle = resolvedFace->fStyle; + result->fIsVariationInstance = true; } return result; } @@ -121,6 +124,7 @@ Typeface* Typeface::createWithDifferentBaseWeight(Typeface* src, int weight) { result->fBaseWeight = weight; result->fAPIStyle = resolvedFace->fAPIStyle; result->fStyle = computeRelativeStyle(weight, result->fAPIStyle); + result->fIsVariationInstance = resolvedFace->fIsVariationInstance; } return result; } @@ -170,6 +174,7 @@ Typeface* Typeface::createFromFamilies(std::vector<std::shared_ptr<minikin::Font result->fBaseWeight = weight; result->fAPIStyle = computeAPIStyle(weight, italic); result->fStyle = computeMinikinStyle(weight, italic); + result->fIsVariationInstance = false; return result; } diff --git a/libs/hwui/hwui/Typeface.h b/libs/hwui/hwui/Typeface.h index 2c96c1ad80fe..97d1bf4ef011 100644 --- a/libs/hwui/hwui/Typeface.h +++ b/libs/hwui/hwui/Typeface.h @@ -44,6 +44,9 @@ public: // base weight in CSS-style units, 1..1000 int fBaseWeight; + // True if the Typeface is already created for variation settings. + bool fIsVariationInstance; + static const Typeface* resolveDefault(const Typeface* src); // The following three functions create new Typeface from an existing Typeface with a different diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp index 5f693462af91..c73598960551 100644 --- a/libs/hwui/jni/text/TextShaper.cpp +++ b/libs/hwui/jni/text/TextShaper.cpp @@ -68,7 +68,7 @@ static void releaseLayout(jlong ptr) { static jlong shapeTextRun(const uint16_t* text, int textSize, int start, int count, int contextStart, int contextCount, minikin::Bidi bidiFlags, const Paint& paint, const Typeface* typeface) { - + const Typeface* resolvedFace = Typeface::resolveDefault(typeface); minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(&paint, typeface); minikin::Layout layout = MinikinUtils::doLayout(&paint, bidiFlags, typeface, @@ -103,8 +103,16 @@ static jlong shapeTextRun(const uint16_t* text, int textSize, int start, int cou fontId = it->second; // We've seen it. } else { fontId = fonts.size(); // This is new to us. Create new one. - std::shared_ptr<minikin::Font> font = std::make_shared<minikin::Font>( - fakedFont.font, fakedFont.fakery.variationSettings()); + std::shared_ptr<minikin::Font> font; + if (resolvedFace->fIsVariationInstance) { + // The optimization for target SDK 35 or before because the variation instance + // is already created and no runtime variation resolution happens on such + // environment. + font = fakedFont.font; + } else { + font = std::make_shared<minikin::Font>(fakedFont.font, + fakedFont.fakery.variationSettings()); + } fonts.push_back(reinterpret_cast<jlong>(new FontWrapper(std::move(font)))); fakedToFontIds.insert(std::make_pair(fakedFont, fontId)); } diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp index d993b8715260..65663d065b8e 100644 --- a/libs/input/MouseCursorController.cpp +++ b/libs/input/MouseCursorController.cpp @@ -28,12 +28,14 @@ #define INDENT " " #define INDENT2 " " +namespace android { + namespace { + // Time to spend fading out the pointer completely. const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms -} // namespace -namespace android { +} // namespace // --- MouseCursorController --- @@ -47,8 +49,7 @@ MouseCursorController::MouseCursorController(PointerControllerContext& context) mLocked.lastFrameUpdatedTime = 0; mLocked.pointerFadeDirection = 0; - mLocked.pointerX = 0; - mLocked.pointerY = 0; + mLocked.pointerPosition = {0, 0}; mLocked.pointerAlpha = 0.0f; // pointer is initially faded mLocked.pointerSprite = mContext.getSpriteController().createSprite(); mLocked.updatePointerIcon = false; @@ -64,50 +65,60 @@ MouseCursorController::~MouseCursorController() { mLocked.pointerSprite.clear(); } -void MouseCursorController::move(float deltaX, float deltaY) { +vec2 MouseCursorController::move(vec2 delta) { #if DEBUG_MOUSE_CURSOR_UPDATES ALOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY); #endif - if (deltaX == 0.0f && deltaY == 0.0f) { - return; + if (delta.x == 0.0f && delta.y == 0.0f) { + return {0, 0}; } + // When transition occurs, the MouseCursorController object may or may not be deleted, depending + // if there's another display on the other side of the transition. At this point we still need + // to move the cursor to the boundary. std::scoped_lock lock(mLock); - - setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY); + const vec2 targetPosition = mLocked.pointerPosition + delta; + setPositionLocked(targetPosition); + // The amount of the delta that was not consumed as a result of the cursor + // hitting the edge of the display. + return targetPosition - mLocked.pointerPosition; } -void MouseCursorController::setPosition(float x, float y) { +void MouseCursorController::setPosition(vec2 position) { #if DEBUG_MOUSE_CURSOR_UPDATES ALOGD("Set pointer position to x=%0.3f, y=%0.3f", x, y); #endif std::scoped_lock lock(mLock); - setPositionLocked(x, y); + setPositionLocked(position); } -void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) { - const auto& v = mLocked.viewport; - if (!v.isValid()) return; - +FloatRect MouseCursorController::getBoundsLocked() REQUIRES(mLock) { // The valid bounds for a mouse cursor. Since the right and bottom edges are considered outside // the display, clip the bounds by one pixel instead of letting the cursor get arbitrarily // close to the outside edge. - const FloatRect bounds{ + return FloatRect{ static_cast<float>(mLocked.viewport.logicalLeft), static_cast<float>(mLocked.viewport.logicalTop), static_cast<float>(mLocked.viewport.logicalRight - 1), static_cast<float>(mLocked.viewport.logicalBottom - 1), }; - mLocked.pointerX = std::max(bounds.left, std::min(bounds.right, x)); - mLocked.pointerY = std::max(bounds.top, std::min(bounds.bottom, y)); +} + +void MouseCursorController::setPositionLocked(vec2 position) REQUIRES(mLock) { + const auto& v = mLocked.viewport; + if (!v.isValid()) return; + + const FloatRect bounds = getBoundsLocked(); + mLocked.pointerPosition.x = std::max(bounds.left, std::min(bounds.right, position.x)); + mLocked.pointerPosition.y = std::max(bounds.top, std::min(bounds.bottom, position.y)); updatePointerLocked(); } -FloatPoint MouseCursorController::getPosition() const { +vec2 MouseCursorController::getPosition() const { std::scoped_lock lock(mLock); - return {mLocked.pointerX, mLocked.pointerY}; + return mLocked.pointerPosition; } ui::LogicalDisplayId MouseCursorController::getDisplayId() const { @@ -209,20 +220,21 @@ void MouseCursorController::setDisplayViewport(const DisplayViewport& viewport, if (viewport.isValid()) { // Use integer coordinates as the starting point for the cursor location. // We usually expect display sizes to be even numbers, so the flooring is precautionary. - mLocked.pointerX = std::floor((viewport.logicalLeft + viewport.logicalRight) / 2); - mLocked.pointerY = std::floor((viewport.logicalTop + viewport.logicalBottom) / 2); + mLocked.pointerPosition.x = + std::floor((viewport.logicalLeft + viewport.logicalRight) / 2); + mLocked.pointerPosition.y = + std::floor((viewport.logicalTop + viewport.logicalBottom) / 2); // Reload icon resources for density may be changed. loadResourcesLocked(getAdditionalMouseResources); } else { - mLocked.pointerX = 0; - mLocked.pointerY = 0; + mLocked.pointerPosition = {0, 0}; } } else if (oldViewport.orientation != viewport.orientation) { // Apply offsets to convert from the pixel top-left corner position to the pixel center. // This creates an invariant frame of reference that we can easily rotate when // taking into account that the pointer may be located at fractional pixel offsets. - float x = mLocked.pointerX + 0.5f; - float y = mLocked.pointerY + 0.5f; + float x = mLocked.pointerPosition.x + 0.5f; + float y = mLocked.pointerPosition.y + 0.5f; float temp; // Undo the previous rotation. @@ -267,8 +279,8 @@ void MouseCursorController::setDisplayViewport(const DisplayViewport& viewport, // Apply offsets to convert from the pixel center to the pixel top-left corner position // and save the results. - mLocked.pointerX = x - 0.5f; - mLocked.pointerY = y - 0.5f; + mLocked.pointerPosition.x = x - 0.5f; + mLocked.pointerPosition.y = y - 0.5f; } updatePointerLocked(); @@ -354,7 +366,7 @@ void MouseCursorController::updatePointerLocked() REQUIRES(mLock) { spriteController.openTransaction(); mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER); - mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY); + mLocked.pointerSprite->setPosition(mLocked.pointerPosition.x, mLocked.pointerPosition.y); mLocked.pointerSprite->setDisplayId(mLocked.viewport.displayId); mLocked.pointerSprite->setSkipScreenshot(mLocked.skipScreenshot); diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h index 12b31a8c531a..7c674b53d620 100644 --- a/libs/input/MouseCursorController.h +++ b/libs/input/MouseCursorController.h @@ -20,9 +20,6 @@ #include <gui/DisplayEventReceiver.h> #include <input/DisplayViewport.h> #include <input/Input.h> -#include <utils/BitSet.h> -#include <utils/Looper.h> -#include <utils/RefBase.h> #include <functional> #include <map> @@ -43,9 +40,10 @@ public: MouseCursorController(PointerControllerContext& context); ~MouseCursorController(); - void move(float deltaX, float deltaY); - void setPosition(float x, float y); - FloatPoint getPosition() const; + // Move the pointer and return unconsumed delta + vec2 move(vec2 delta); + void setPosition(vec2 position); + vec2 getPosition() const; ui::LogicalDisplayId getDisplayId() const; void fade(PointerControllerInterface::Transition transition); void unfade(PointerControllerInterface::Transition transition); @@ -83,8 +81,7 @@ private: nsecs_t lastFrameUpdatedTime; int32_t pointerFadeDirection; - float pointerX; - float pointerY; + vec2 pointerPosition; float pointerAlpha; sp<Sprite> pointerSprite; SpriteIcon pointerIcon; @@ -103,7 +100,7 @@ private: } mLocked GUARDED_BY(mLock); - void setPositionLocked(float x, float y); + void setPositionLocked(vec2 position); void updatePointerLocked(); @@ -113,6 +110,7 @@ private: bool doFadingAnimationLocked(nsecs_t timestamp); void startAnimationLocked(); + FloatRect getBoundsLocked(); }; } // namespace android diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index 78d7d3a7051b..0b81211ee666 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -138,15 +138,16 @@ std::mutex& PointerController::getLock() const { return mDisplayInfoListener->mLock; } -void PointerController::move(float deltaX, float deltaY) { +vec2 PointerController::move(float deltaX, float deltaY) { const ui::LogicalDisplayId displayId = mCursorController.getDisplayId(); - vec2 transformed; + ui::Transform transform; { std::scoped_lock lock(getLock()); - const auto& transform = getTransformForDisplayLocked(displayId); - transformed = transformWithoutTranslation(transform, {deltaX, deltaY}); + transform = getTransformForDisplayLocked(displayId); } - mCursorController.move(transformed.x, transformed.y); + + const vec2 transformed = transformWithoutTranslation(transform, {deltaX, deltaY}); + return transformWithoutTranslation(transform.inverse(), mCursorController.move(transformed)); } void PointerController::setPosition(float x, float y) { @@ -157,16 +158,15 @@ void PointerController::setPosition(float x, float y) { const auto& transform = getTransformForDisplayLocked(displayId); transformed = transform.transform(x, y); } - mCursorController.setPosition(transformed.x, transformed.y); + mCursorController.setPosition(transformed); } -FloatPoint PointerController::getPosition() const { +vec2 PointerController::getPosition() const { const ui::LogicalDisplayId displayId = mCursorController.getDisplayId(); const auto p = mCursorController.getPosition(); { std::scoped_lock lock(getLock()); - const auto& transform = getTransformForDisplayLocked(displayId); - return FloatPoint{transform.inverse().transform(p.x, p.y)}; + return getTransformForDisplayLocked(displayId).inverse().transform(p.x, p.y); } } @@ -295,6 +295,11 @@ void PointerController::clearSkipScreenshotFlags() { mCursorController.setSkipScreenshot(false); } +ui::Transform PointerController::getDisplayTransform() const { + std::scoped_lock lock(getLock()); + return getTransformForDisplayLocked(mLocked.pointerDisplayId); +} + void PointerController::doInactivityTimeout() { fade(Transition::GRADUAL); } diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index ee8d1211341f..afd7215c7fba 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -51,9 +51,9 @@ public: ~PointerController() override; - void move(float deltaX, float deltaY) override; + vec2 move(float deltaX, float deltaY) override; void setPosition(float x, float y) override; - FloatPoint getPosition() const override; + vec2 getPosition() const override; ui::LogicalDisplayId getDisplayId() const override; void fade(Transition transition) override; void unfade(Transition transition) override; @@ -67,6 +67,7 @@ public: void setCustomPointerIcon(const SpriteIcon& icon) override; void setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) override; void clearSkipScreenshotFlags() override; + ui::Transform getDisplayTransform() const override; virtual void setInactivityTimeout(InactivityTimeout inactivityTimeout); void doInactivityTimeout(); @@ -165,13 +166,13 @@ public: ~TouchPointerController() override; - void move(float, float) override { + vec2 move(float, float) override { LOG_ALWAYS_FATAL("Should not be called"); } void setPosition(float, float) override { LOG_ALWAYS_FATAL("Should not be called"); } - FloatPoint getPosition() const override { + vec2 getPosition() const override { LOG_ALWAYS_FATAL("Should not be called"); } ui::LogicalDisplayId getDisplayId() const override { diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp index 5b00fca4d857..80c934a9bd95 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -40,6 +40,8 @@ enum TestCursorType { CURSOR_TYPE_CUSTOM = -1, }; +static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION; + using ::testing::AllOf; using ::testing::Field; using ::testing::NiceMock; @@ -399,6 +401,135 @@ INSTANTIATE_TEST_SUITE_P(PointerControllerSkipScreenshotFlagTest, testing::Values(PointerControllerInterface::ControllerType::MOUSE, PointerControllerInterface::ControllerType::STYLUS)); +class MousePointerControllerTest : public PointerControllerTest { +protected: + MousePointerControllerTest() { + sp<MockSprite> testPointerSprite(new NiceMock<MockSprite>); + EXPECT_CALL(*mSpriteController, createSprite).WillOnce(Return(testPointerSprite)); + + // create a mouse pointer controller + mPointerController = + PointerController::create(mPolicy, mLooper, *mSpriteController, + PointerControllerInterface::ControllerType::MOUSE); + + // set display viewport + DisplayViewport viewport; + viewport.displayId = ui::LogicalDisplayId::DEFAULT; + viewport.logicalRight = 5; + viewport.logicalBottom = 5; + viewport.physicalRight = 5; + viewport.physicalBottom = 5; + viewport.deviceWidth = 5; + viewport.deviceHeight = 5; + mPointerController->setDisplayViewport(viewport); + } +}; + +struct MousePointerControllerTestParam { + vec2 startPosition; + vec2 delta; + vec2 endPosition; + vec2 unconsumedDelta; +}; + +class PointerControllerViewportTransitionTest + : public MousePointerControllerTest, + public testing::WithParamInterface<MousePointerControllerTestParam> {}; + +TEST_P(PointerControllerViewportTransitionTest, testPointerViewportTransition) { + const auto& params = GetParam(); + + mPointerController->setPosition(params.startPosition.x, params.startPosition.y); + auto unconsumedDelta = mPointerController->move(params.delta.x, params.delta.y); + + auto position = mPointerController->getPosition(); + EXPECT_NEAR(position.x, params.endPosition.x, EPSILON); + EXPECT_NEAR(position.y, params.endPosition.y, EPSILON); + EXPECT_NEAR(unconsumedDelta.x, params.unconsumedDelta.x, EPSILON); + EXPECT_NEAR(unconsumedDelta.y, params.unconsumedDelta.y, EPSILON); +} + +INSTANTIATE_TEST_SUITE_P(PointerControllerViewportTransitionTest, + PointerControllerViewportTransitionTest, + testing::Values( + // no transition + MousePointerControllerTestParam{{2.0f, 2.0f}, + {2.0f, 2.0f}, + {4.0f, 4.0f}, + {0.0f, 0.0f}}, + // right boundary + MousePointerControllerTestParam{{2.0f, 2.0f}, + {3.0f, 0.0f}, + {4.0f, 2.0f}, + {1.0f, 0.0f}}, + MousePointerControllerTestParam{{2.0f, 2.0f}, + {3.0f, -1.0f}, + {4.0f, 1.0f}, + {1.0f, 0.0f}}, + MousePointerControllerTestParam{{2.0f, 2.0f}, + {3.0f, 1.0f}, + {4.0f, 3.0f}, + {1.0f, 0.0f}}, + // left boundary + MousePointerControllerTestParam{{2.0f, 2.0f}, + {-3.0f, 0.0f}, + {0.0f, 2.0f}, + {-1.0f, 0.0f}}, + MousePointerControllerTestParam{{2.0f, 2.0f}, + {-3.0f, -1.0f}, + {0.0f, 1.0f}, + {-1.0f, 0.0f}}, + MousePointerControllerTestParam{{2.0f, 2.0f}, + {-3.0f, 1.0f}, + {0.0f, 3.0f}, + {-1.0f, 0.0f}}, + // bottom boundary + MousePointerControllerTestParam{{2.0f, 2.0f}, + {0.0f, 3.0f}, + {2.0f, 4.0f}, + {0.0f, 1.0f}}, + MousePointerControllerTestParam{{2.0f, 2.0f}, + {-1.0f, 3.0f}, + {1.0f, 4.0f}, + {0.0f, 1.0f}}, + MousePointerControllerTestParam{{2.0f, 2.0f}, + {1.0f, 3.0f}, + {3.0f, 4.0f}, + {0.0f, 1.0f}}, + // top boundary + MousePointerControllerTestParam{{2.0f, 2.0f}, + {0.0f, -3.0f}, + {2.0f, 0.0f}, + {0.0f, -1.0f}}, + MousePointerControllerTestParam{{2.0f, 2.0f}, + {-1.0f, -3.0f}, + {1.0f, 0.0f}, + {0.0f, -1.0f}}, + MousePointerControllerTestParam{{2.0f, 2.0f}, + {1.0f, -3.0f}, + {3.0f, 0.0f}, + {0.0f, -1.0f}}, + // top-left corner + MousePointerControllerTestParam{{2.0f, 2.0f}, + {-3.0f, -3.0f}, + {0.0f, 0.0f}, + {-1.0f, -1.0f}}, + // top-right corner + MousePointerControllerTestParam{{2.0f, 2.0f}, + {3.0f, -3.0f}, + {4.0f, 0.0f}, + {1.0f, -1.0f}}, + // bottom-right corner + MousePointerControllerTestParam{{2.0f, 2.0f}, + {3.0f, 3.0f}, + {4.0f, 4.0f}, + {1.0f, 1.0f}}, + // bottom-left corner + MousePointerControllerTestParam{{2.0f, 2.0f}, + {-3.0f, 3.0f}, + {0.0f, 4.0f}, + {-1.0f, 1.0f}})); + class PointerControllerWindowInfoListenerTest : public Test {}; TEST_F(PointerControllerWindowInfoListenerTest, |