diff options
Diffstat (limited to 'libs')
47 files changed, 1012 insertions, 614 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index ea60b1531a3f..f1e7ef5ce123 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -1359,4 +1359,16 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { return new ParentContainerInfo(taskProperties.getTaskMetrics(), configuration, windowLayoutInfo); } + + @VisibleForTesting + @NonNull + static String positionToString(@ContainerPosition int position) { + return switch (position) { + case CONTAINER_POSITION_LEFT -> "left"; + case CONTAINER_POSITION_TOP -> "top"; + case CONTAINER_POSITION_RIGHT -> "right"; + case CONTAINER_POSITION_BOTTOM -> "bottom"; + default -> "Unknown position:" + position; + }; + } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/Android.bp b/libs/WindowManager/Jetpack/tests/unittest/Android.bp index 61ea51a35f58..139ddda5af3c 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/Android.bp +++ b/libs/WindowManager/Jetpack/tests/unittest/Android.bp @@ -32,6 +32,7 @@ android_test { ], static_libs: [ + "TestParameterInjector", "androidx.window.extensions", "androidx.window.extensions.core_core", "junit", diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java index 325750243744..1c4c8870b26f 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java @@ -35,6 +35,7 @@ import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSI import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_RIGHT; import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_TOP; import static androidx.window.extensions.embedding.SplitPresenter.getOverlayPosition; +import static androidx.window.extensions.embedding.SplitPresenter.positionToString; import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds; import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK; @@ -78,7 +79,6 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; import androidx.window.extensions.layout.WindowLayoutComponentImpl; @@ -86,6 +86,9 @@ import androidx.window.extensions.layout.WindowLayoutInfo; import com.android.window.flags.Flags; +import com.google.testing.junit.testparameterinjector.TestParameter; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -108,7 +111,7 @@ import java.util.List; @SuppressWarnings("GuardedBy") @Presubmit @SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(TestParameterInjector.class) public class OverlayPresentationTest { @Rule public MockitoRule rule = MockitoJUnit.rule(); @@ -875,57 +878,70 @@ public class OverlayPresentationTest { eq(overlayContainer.getTaskFragmentToken()), eq(activityToken)); } - // TODO(b/243518738): Rewrite with TestParameter. - @Test - public void testGetOverlayPosition() { - assertWithMessage("It must be position left for left overlay.") - .that(getOverlayPosition(new Rect( - TASK_BOUNDS.left, - TASK_BOUNDS.top, - TASK_BOUNDS.right / 2, - TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_LEFT); - assertWithMessage("It must be position left for shrunk left overlay.") - .that(getOverlayPosition(new Rect( - TASK_BOUNDS.left, - TASK_BOUNDS.top + 20, - TASK_BOUNDS.right / 2, - TASK_BOUNDS.bottom - 20), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_LEFT); - assertWithMessage("It must be position left for top overlay.") - .that(getOverlayPosition(new Rect( - TASK_BOUNDS.left, - TASK_BOUNDS.top, - TASK_BOUNDS.right, - TASK_BOUNDS.bottom / 2), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_TOP); - assertWithMessage("It must be position left for shrunk top overlay.") - .that(getOverlayPosition(new Rect( - TASK_BOUNDS.left + 20, - TASK_BOUNDS.top, - TASK_BOUNDS.right - 20, - TASK_BOUNDS.bottom / 2), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_TOP); - assertWithMessage("It must be position left for right overlay.") - .that(getOverlayPosition(new Rect( - TASK_BOUNDS.right / 2, - TASK_BOUNDS.top, - TASK_BOUNDS.right, - TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_RIGHT); - assertWithMessage("It must be position left for shrunk right overlay.") - .that(getOverlayPosition(new Rect( - TASK_BOUNDS.right / 2, - TASK_BOUNDS.top + 20, - TASK_BOUNDS.right, - TASK_BOUNDS.bottom - 20), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_RIGHT); - assertWithMessage("It must be position left for bottom overlay.") - .that(getOverlayPosition(new Rect( - TASK_BOUNDS.left, - TASK_BOUNDS.bottom / 2, - TASK_BOUNDS.right, - TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_BOTTOM); - assertWithMessage("It must be position left for shrunk bottom overlay.") - .that(getOverlayPosition(new Rect( - TASK_BOUNDS.left + 20, - TASK_BOUNDS.bottom / 20, - TASK_BOUNDS.right - 20, - TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_BOTTOM); + @Test + public void testGetOverlayPosition(@TestParameter OverlayPositionTestParams params) { + final Rect taskBounds = new Rect(TASK_BOUNDS); + final Rect overlayBounds = params.toOverlayBounds(); + final int overlayPosition = getOverlayPosition(overlayBounds, taskBounds); + + assertWithMessage("The overlay position must be " + + positionToString(params.mPosition) + ", but is " + + positionToString(overlayPosition) + + ", parent bounds=" + taskBounds + ", overlay bounds=" + overlayBounds) + .that(overlayPosition).isEqualTo(params.mPosition); + } + + private enum OverlayPositionTestParams { + LEFT_OVERLAY(CONTAINER_POSITION_LEFT, false /* shouldBeShrunk */), + LEFT_SHRUNK_OVERLAY(CONTAINER_POSITION_LEFT, true /* shouldBeShrunk */), + TOP_OVERLAY(CONTAINER_POSITION_TOP, false /* shouldBeShrunk */), + TOP_SHRUNK_OVERLAY(CONTAINER_POSITION_TOP, true /* shouldBeShrunk */), + RIGHT_OVERLAY(CONTAINER_POSITION_RIGHT, false /* shouldBeShrunk */), + RIGHT_SHRUNK_OVERLAY(CONTAINER_POSITION_RIGHT, true /* shouldBeShrunk */), + BOTTOM_OVERLAY(CONTAINER_POSITION_BOTTOM, false /* shouldBeShrunk */), + BOTTOM_SHRUNK_OVERLAY(CONTAINER_POSITION_BOTTOM, true /* shouldBeShrunk */); + + @SplitPresenter.ContainerPosition + private final int mPosition; + + private final boolean mShouldBeShrunk; + + OverlayPositionTestParams( + @SplitPresenter.ContainerPosition int position, boolean shouldBeShrunk) { + mPosition = position; + mShouldBeShrunk = shouldBeShrunk; + } + + @NonNull + private Rect toOverlayBounds() { + Rect r = new Rect(TASK_BOUNDS); + final int offset = mShouldBeShrunk ? 20 : 0; + switch (mPosition) { + case CONTAINER_POSITION_LEFT: + r.top += offset; + r.right /= 2; + r.bottom -= offset; + break; + case CONTAINER_POSITION_TOP: + r.left += offset; + r.right -= offset; + r.bottom /= 2; + break; + case CONTAINER_POSITION_RIGHT: + r.left = r.right / 2; + r.top += offset; + r.bottom -= offset; + break; + case CONTAINER_POSITION_BOTTOM: + r.left += offset; + r.right -= offset; + r.top = r.bottom / 2; + break; + default: + throw new IllegalArgumentException("Invalid position: " + mPosition); + } + return r; + } } /** diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index e6cb3a08119a..5135e9ee14bc 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -239,6 +239,9 @@ android_library { "wmshell.protolog.json.gz", "wmshell.protolog.pb", ], + flags_packages: [ + "com_android_wm_shell_flags", + ], kotlincflags: ["-Xjvm-default=all"], manifest: "AndroidManifest.xml", plugins: ["dagger2-compiler"], diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml index 52ae93f5ebf1..bbbc23e8b922 100644 --- a/libs/WindowManager/Shell/AndroidManifest.xml +++ b/libs/WindowManager/Shell/AndroidManifest.xml @@ -34,6 +34,7 @@ <activity android:name=".bubbles.shortcut.CreateBubbleShortcutActivity" + android:featureFlag="com.android.wm.shell.enable_retrievable_bubbles" android:exported="true" android:excludeFromRecents="true" android:theme="@android:style/Theme.NoDisplay" @@ -47,6 +48,7 @@ <activity android:name=".bubbles.shortcut.ShowBubblesActivity" + android:featureFlag="com.android.wm.shell.enable_retrievable_bubbles" android:exported="true" android:excludeFromRecents="true" android:theme="@android:style/Theme.NoDisplay" > diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt index f0d80a02243a..259101a9b84f 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt @@ -17,16 +17,20 @@ package com.android.wm.shell.shared.desktopmode import android.content.Context +import android.os.SystemProperties import android.provider.Settings import android.util.Log import com.android.window.flags.Flags -/* - * A shared class to check desktop mode flags state. +/** + * Util to check desktop mode flags state. + * + * This utility is used to allow developer option toggles to override flags related to Desktop + * Windowing. * - * The class computes whether a Desktop Windowing flag should be enabled by using the aconfig flag + * Computes whether Desktop Windowing related flags should be enabled by using the aconfig flag * value and the developer option override state (if applicable). - **/ + */ enum class DesktopModeFlags( // Function called to obtain aconfig flag value. private val flagFunction: () -> Boolean, @@ -67,37 +71,54 @@ enum class DesktopModeFlags( // Cache toggle override the first time we encounter context. Override does not change // with context, as context is just used to fetch System Property and Settings.Global cachedToggleOverride = override - Log.d(TAG, "Toggle override initialized to: $override") + Log.d(TAG, "Local toggle override initialized to: $override") override } return override } + /** + * Returns [ToggleOverride] from a non-persistent system property if present. Otherwise + * initializes the system property by reading Settings.Global. + */ private fun getToggleOverrideFromSystem(context: Context): ToggleOverride { // A non-persistent System Property is used to store override to ensure it remains // constant till reboot. val overrideFromSystemProperties: ToggleOverride? = - System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, null).convertToToggleOverride() + SystemProperties.getInt(SYSTEM_PROPERTY_OVERRIDE_KEY, INVALID_TOGGLE_OVERRIDE_SETTING) + .convertToToggleOverride() + return overrideFromSystemProperties ?: run { - // Read Setting Global if System Property is not present (just after reboot) - // or not valid (user manually changed the value) - val overrideFromSettingsGlobal = - convertToToggleOverrideWithFallback( - Settings.Global.getInt( - context.contentResolver, - Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, - ToggleOverride.OVERRIDE_UNSET.setting), - ToggleOverride.OVERRIDE_UNSET) - // Initialize System Property - System.setProperty( - SYSTEM_PROPERTY_OVERRIDE_KEY, overrideFromSettingsGlobal.setting.toString()) - - overrideFromSettingsGlobal + // TODO(b/348193756): Remove the initialization after adding it in core. If + // SystemProperty is still not present then return OVERRIDE_UNSET + // Initialize System Property if not present (just after reboot) + initializeOverrideInSystemProperty(context) } } + /** Initializes Override System property based on Settings.Global set by toggle. */ + private fun initializeOverrideInSystemProperty(context: Context): ToggleOverride { + val settingValue = + Settings.Global.getInt( + context.contentResolver, + Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, + ToggleOverride.OVERRIDE_UNSET.setting) + try { + SystemProperties.set(SYSTEM_PROPERTY_OVERRIDE_KEY, settingValue.toString()) + Log.d(TAG, "Initialized system property with override setting: $settingValue") + } catch (e: RuntimeException) { + // Thrown in device tests that don't mock SystemProperties + Log.w( + TAG, + "Failed to set a system property: key=$SYSTEM_PROPERTY_OVERRIDE_KEY, " + + "value=$settingValue, message=${e.message}") + } + + return convertToToggleOverrideWithFallback(settingValue, ToggleOverride.OVERRIDE_UNSET) + } + /** * Override state of desktop mode developer option toggle. * @@ -113,11 +134,10 @@ enum class DesktopModeFlags( OVERRIDE_ON(1) } - private fun String?.convertToToggleOverride(): ToggleOverride? { - val intValue = this?.toIntOrNull() ?: return null - return settingToToggleOverrideMap[intValue] + private fun Int.convertToToggleOverride(): ToggleOverride? { + return settingToToggleOverrideMap[this] ?: run { - Log.w(TAG, "Unknown toggleOverride int $intValue") + Log.w(TAG, "Unknown toggleOverride int $this") null } } @@ -131,6 +151,8 @@ enum class DesktopModeFlags( */ private const val SYSTEM_PROPERTY_OVERRIDE_KEY = "sys.wmshell.desktopmode.dev_toggle_override" + private const val INVALID_TOGGLE_OVERRIDE_SETTING = -2 + /** * Local cache for toggle override, which is initialized once on its first access. It needs to * be refreshed only on reboots as overridden state takes effect on reboots. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index f7a5c271a729..d4d9d003bc0d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -16,7 +16,7 @@ package com.android.wm.shell.bubbles; -import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; @@ -225,8 +225,7 @@ public class BubbleExpandedView extends LinearLayout { options.setTaskAlwaysOnTop(true); options.setLaunchedFromBubble(true); options.setPendingIntentBackgroundActivityStartMode( - MODE_BACKGROUND_ACTIVITY_START_ALLOWED); - options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true); + MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS); Intent fillInIntent = new Intent(); // Apply flags to make behaviour match documentLaunchMode=always. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java index c79d9c4942bf..5e2141aa639e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java @@ -15,7 +15,7 @@ */ package com.android.wm.shell.bubbles; -import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; @@ -103,8 +103,7 @@ public class BubbleTaskViewHelper { options.setTaskAlwaysOnTop(true); options.setLaunchedFromBubble(true); options.setPendingIntentBackgroundActivityStartMode( - MODE_BACKGROUND_ACTIVITY_START_ALLOWED); - options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true); + MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS); Intent fillInIntent = new Intent(); // Apply flags to make behaviour match documentLaunchMode=always. 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 e713af6a5311..80f6a637ba1c 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 @@ -211,6 +211,7 @@ public abstract class WMShellModule { @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler, @ShellMainThread Choreographer mainChoreographer, + @ShellBackgroundThread ShellExecutor bgExecutor, ShellInit shellInit, IWindowManager windowManager, ShellCommandHandler shellCommandHandler, @@ -229,6 +230,7 @@ public abstract class WMShellModule { mainExecutor, mainHandler, mainChoreographer, + bgExecutor, shellInit, shellCommandHandler, windowManager, @@ -246,6 +248,7 @@ public abstract class WMShellModule { context, mainHandler, mainExecutor, + bgExecutor, mainChoreographer, windowManager, shellInit, @@ -366,13 +369,14 @@ public abstract class WMShellModule { Optional<WindowDecorViewModel> windowDecorViewModel, Optional<DesktopTasksController> desktopTasksController, MultiInstanceHelper multiInstanceHelper, - @ShellMainThread ShellExecutor mainExecutor) { + @ShellMainThread ShellExecutor mainExecutor, + @ShellMainThread Handler mainHandler) { return new SplitScreenController(context, shellInit, shellCommandHandler, shellController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, displayController, displayImeController, displayInsetsController, dragAndDropController, transitions, transactionPool, iconProvider, recentTasks, launchAdjacentController, windowDecorViewModel, desktopTasksController, null /* stageCoordinator */, - multiInstanceHelper, mainExecutor); + multiInstanceHelper, mainExecutor, mainHandler); } // 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 066b5ad39d0f..73aa7ceea68d 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 @@ -347,7 +347,7 @@ class DesktopModeLoggerTransitionObserver( else -> { ProtoLog.w( WM_SHELL_DESKTOP_MODE, - "Unknown enter reason for transition type ${transitionInfo.type}", + "Unknown enter reason for transition type: %s", transitionInfo.type ) EnterReason.UNKNOWN_ENTER @@ -368,7 +368,7 @@ class DesktopModeLoggerTransitionObserver( else -> { ProtoLog.w( WM_SHELL_DESKTOP_MODE, - "Unknown exit reason for transition type ${transitionInfo.type}", + "Unknown exit reason for transition type: %s", transitionInfo.type ) ExitReason.UNKNOWN_EXIT 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 1bf125938e6f..da212e704b24 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 @@ -66,7 +66,7 @@ fun calculateInitialBounds( idealSize } } else { - maximumSizeMaintainingAspectRatio(taskInfo, idealSize, appAspectRatio) + maximizeSizeGivenAspectRatio(taskInfo, idealSize, appAspectRatio) } } ORIENTATION_PORTRAIT -> { @@ -85,13 +85,13 @@ fun calculateInitialBounds( } else { if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) { // Apply custom app width and calculate maximum size - maximumSizeMaintainingAspectRatio( + maximizeSizeGivenAspectRatio( taskInfo, Size(customPortraitWidthForLandscapeApp, idealSize.height), appAspectRatio ) } else { - maximumSizeMaintainingAspectRatio(taskInfo, idealSize, appAspectRatio) + maximizeSizeGivenAspectRatio(taskInfo, idealSize, appAspectRatio) } } } @@ -107,7 +107,7 @@ fun calculateInitialBounds( * Calculates the largest size that can fit in a given area while maintaining a specific aspect * ratio. */ -fun maximumSizeMaintainingAspectRatio( +fun maximizeSizeGivenAspectRatio( taskInfo: RunningTaskInfo, targetArea: Size, aspectRatio: Float 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 9e6099f2e4cc..de901b5dabd8 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 @@ -69,6 +69,7 @@ import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOT import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener +import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener import com.android.wm.shell.draganddrop.DragAndDropController import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE @@ -671,7 +672,7 @@ class DesktopTasksController( } else { // if non-resizable then calculate max bounds according to aspect ratio val activityAspectRatio = calculateAspectRatio(taskInfo) - val newSize = maximumSizeMaintainingAspectRatio(taskInfo, + val newSize = maximizeSizeGivenAspectRatio(taskInfo, Size(stableBounds.width(), stableBounds.height()), activityAspectRatio) val newBounds = centerInArea( newSize, stableBounds, stableBounds.left, stableBounds.top) @@ -818,9 +819,8 @@ class DesktopTasksController( val intent = Intent(context, DesktopWallpaperActivity::class.java) val options = ActivityOptions.makeBasic().apply { - isPendingIntentBackgroundActivityLaunchAllowedByPermission = true pendingIntentBackgroundActivityStartMode = - ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS } val pendingIntent = PendingIntent.getActivity( @@ -1080,7 +1080,6 @@ class DesktopTasksController( wct: WindowContainerTransaction, taskInfo: RunningTaskInfo ) { - val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!! val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode val targetWindowingMode = @@ -1090,9 +1089,6 @@ class DesktopTasksController( } else { WINDOWING_MODE_FREEFORM } - if (Flags.enableWindowingDynamicInitialBounds()) { - wct.setBounds(taskInfo.token, calculateInitialBounds(displayLayout, taskInfo)) - } wct.setWindowingMode(taskInfo.token, targetWindowingMode) wct.reorder(taskInfo.token, true /* onTop */) if (useDesktopOverrideDensity()) { @@ -1339,33 +1335,36 @@ class DesktopTasksController( * * @param taskInfo the task being dragged. * @param y height of drag, to be checked against status bar height. + * @return the [IndicatorType] used for the resulting transition */ fun onDragPositioningEndThroughStatusBar( inputCoordinates: PointF, taskInfo: RunningTaskInfo, - ) { - val indicator = getVisualIndicator() ?: return + ): IndicatorType { + val indicator = getVisualIndicator() ?: return IndicatorType.NO_INDICATOR val indicatorType = indicator.updateIndicatorType(inputCoordinates, taskInfo.windowingMode) when (indicatorType) { - DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> { - val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return + IndicatorType.TO_DESKTOP_INDICATOR -> { + val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) + ?: return IndicatorType.NO_INDICATOR if (Flags.enableWindowingDynamicInitialBounds()) { finalizeDragToDesktop(taskInfo, calculateInitialBounds(displayLayout, taskInfo)) } else { finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout)) } } - DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR, - DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> { + IndicatorType.NO_INDICATOR, + IndicatorType.TO_FULLSCREEN_INDICATOR -> { cancelDragToDesktop(taskInfo) } - DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR -> { + IndicatorType.TO_SPLIT_LEFT_INDICATOR -> { requestSplit(taskInfo, leftOrTop = true) } - DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> { + IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> { requestSplit(taskInfo, leftOrTop = false) } } + return indicatorType } /** Update the exclusion region for a specified task */ @@ -1426,7 +1425,6 @@ class DesktopTasksController( setPendingIntentBackgroundActivityStartMode( ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED ) - isPendingIntentBackgroundActivityLaunchAllowedByPermission = true } val wct = WindowContainerTransaction() wct.sendPendingIntent(launchIntent, null, opts.toBundle()) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java index 95fe8b6f1f4e..7e0362475f21 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java @@ -16,7 +16,7 @@ package com.android.wm.shell.draganddrop; -import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; @@ -280,8 +280,7 @@ public class DragAndDropPolicy { baseActivityOpts.setDisallowEnterPictureInPictureWhileLaunching(true); // Put BAL flags to avoid activity start aborted. baseActivityOpts.setPendingIntentBackgroundActivityStartMode( - MODE_BACKGROUND_ACTIVITY_START_ALLOWED); - baseActivityOpts.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true); + MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS); final Bundle opts = baseActivityOpts.toBundle(); if (session.appData.hasExtra(EXTRA_ACTIVITY_OPTIONS)) { opts.putAll(session.appData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS)); 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 e5633de2a3a2..1a3201822c00 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 @@ -1173,7 +1173,12 @@ public class PipTransition extends PipTransitionController { .setLayer(swipePipToHomeOverlay, Integer.MAX_VALUE); } - final Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds(); + // Both Shell and Launcher calculate their own "adjusted" source-rect-hint values based on + // appBounds being source bounds when entering PiP. + final Rect sourceBounds = swipePipToHomeOverlay == null + ? pipTaskInfo.configuration.windowConfiguration.getBounds() + : mPipOrganizer.mAppBounds; + final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index 8df287d12cbc..06c57bd7092d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -113,6 +113,9 @@ public interface SplitScreen { /** Called when device waking up finished. */ void onFinishedWakingUp(); + /** Called when device starts going to sleep (screen off). */ + void onStartedGoingToSleep(); + /** Called when requested to go to fullscreen from the current active split app. */ void goToFullscreenFromSplit(); 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 e659151fee7f..b8575565ef8a 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 @@ -50,6 +50,7 @@ import android.content.pm.LauncherApps; import android.content.pm.ShortcutInfo; import android.graphics.Rect; import android.os.Bundle; +import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; import android.util.ArrayMap; @@ -180,6 +181,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, private final LauncherApps mLauncherApps; private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer; private final ShellExecutor mMainExecutor; + private final Handler mMainHandler; private final SplitScreenImpl mImpl = new SplitScreenImpl(); private final DisplayController mDisplayController; private final DisplayImeController mDisplayImeController; @@ -227,7 +229,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, Optional<DesktopTasksController> desktopTasksController, @Nullable StageCoordinator stageCoordinator, MultiInstanceHelper multiInstanceHelper, - ShellExecutor mainExecutor) { + ShellExecutor mainExecutor, + Handler mainHandler) { mShellCommandHandler = shellCommandHandler; mShellController = shellController; mTaskOrganizer = shellTaskOrganizer; @@ -236,6 +239,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mLauncherApps = context.getSystemService(LauncherApps.class); mRootTDAOrganizer = rootTDAOrganizer; mMainExecutor = mainExecutor; + mMainHandler = mainHandler; mDisplayController = displayController; mDisplayImeController = displayImeController; mDisplayInsetsController = displayInsetsController; @@ -292,7 +296,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, mTaskOrganizer, mDisplayController, mDisplayImeController, mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider, - mMainExecutor, mRecentTasksOptional, mLaunchAdjacentController, + mMainExecutor, mMainHandler, mRecentTasksOptional, mLaunchAdjacentController, mWindowDecorViewModel); } @@ -448,13 +452,17 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Override public void onKeyguardVisibilityChanged(boolean visible, boolean occluded, boolean animatingDismiss) { - mStageCoordinator.onKeyguardVisibilityChanged(visible); + mStageCoordinator.onKeyguardStateChanged(visible, occluded); } public void onFinishedWakingUp() { mStageCoordinator.onFinishedWakingUp(); } + public void onStartedGoingToSleep() { + mStageCoordinator.onStartedGoingToSleep(); + } + public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) { mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide); } @@ -1201,6 +1209,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } @Override + public void onStartedGoingToSleep() { + mMainExecutor.execute(SplitScreenController.this::onStartedGoingToSleep); + } + + @Override public void goToFullscreenFromSplit() { mMainExecutor.execute(SplitScreenController.this::goToFullscreenFromSplit); } 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 9bcd9b0a11c8..a4f32c45c0a9 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 @@ -16,7 +16,7 @@ package com.android.wm.shell.splitscreen; -import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; @@ -92,6 +92,7 @@ import android.graphics.Rect; import android.hardware.devicestate.DeviceStateManager; import android.os.Bundle; import android.os.Debug; +import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; @@ -119,6 +120,7 @@ import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; +import com.android.internal.policy.FoldLockSettingsObserver; import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ArrayUtils; import com.android.launcher3.icons.IconProvider; @@ -191,7 +193,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private SplitLayout mSplitLayout; private ValueAnimator mDividerFadeInAnimator; private boolean mDividerVisible; - private boolean mKeyguardShowing; + private boolean mKeyguardActive; private boolean mShowDecorImmediately; private final SyncTransactionQueue mSyncQueue; private final ShellTaskOrganizer mTaskOrganizer; @@ -205,6 +207,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private SplitScreenTransitions mSplitTransitions; private final SplitscreenEventLogger mLogger; private final ShellExecutor mMainExecutor; + private final Handler mMainHandler; // Cache live tile tasks while entering recents, evict them from stages in finish transaction // if user is opening another task(s). private final ArrayList<Integer> mPausingTasks = new ArrayList<>(); @@ -233,7 +236,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private boolean mIsExiting; private boolean mIsRootTranslucent; @VisibleForTesting - int mTopStageAfterFoldDismiss; + @StageType int mLastActiveStage; + private boolean mBreakOnNextWake; + /** Used to get the Settings value for "Continue using apps on fold". */ + private FoldLockSettingsObserver mFoldLockSettingsObserver; private DefaultMixedHandler mMixedHandler; private final Toast mSplitUnsupportedToast; @@ -313,9 +319,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, ShellTaskOrganizer taskOrganizer, DisplayController displayController, DisplayImeController displayImeController, DisplayInsetsController displayInsetsController, Transitions transitions, - TransactionPool transactionPool, - IconProvider iconProvider, ShellExecutor mainExecutor, - Optional<RecentTasksController> recentTasks, + TransactionPool transactionPool, IconProvider iconProvider, ShellExecutor mainExecutor, + Handler mainHandler, Optional<RecentTasksController> recentTasks, LaunchAdjacentController launchAdjacentController, Optional<WindowDecorViewModel> windowDecorViewModel) { mContext = context; @@ -324,6 +329,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mTaskOrganizer = taskOrganizer; mLogger = new SplitscreenEventLogger(); mMainExecutor = mainExecutor; + mMainHandler = mainHandler; mRecentTasks = recentTasks; mLaunchAdjacentController = launchAdjacentController; mWindowDecorViewModel = windowDecorViewModel; @@ -366,6 +372,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // With shell transition, we should update recents tile each callback so set this to true by // default. mShouldUpdateRecents = ENABLE_SHELL_TRANSITIONS; + mFoldLockSettingsObserver = + new FoldLockSettingsObserver(mainHandler, context); + mFoldLockSettingsObserver.register(); } @VisibleForTesting @@ -373,9 +382,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, ShellTaskOrganizer taskOrganizer, MainStage mainStage, SideStage sideStage, DisplayController displayController, DisplayImeController displayImeController, DisplayInsetsController displayInsetsController, SplitLayout splitLayout, - Transitions transitions, TransactionPool transactionPool, - ShellExecutor mainExecutor, - Optional<RecentTasksController> recentTasks, + Transitions transitions, TransactionPool transactionPool, ShellExecutor mainExecutor, + Handler mainHandler, Optional<RecentTasksController> recentTasks, LaunchAdjacentController launchAdjacentController, Optional<WindowDecorViewModel> windowDecorViewModel) { mContext = context; @@ -393,6 +401,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, this::onTransitionAnimationComplete, this); mLogger = new SplitscreenEventLogger(); mMainExecutor = mainExecutor; + mMainHandler = mainHandler; mRecentTasks = recentTasks; mLaunchAdjacentController = launchAdjacentController; mWindowDecorViewModel = windowDecorViewModel; @@ -400,6 +409,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, transitions.addHandler(this); mSplitUnsupportedToast = Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT); + mFoldLockSettingsObserver = + new FoldLockSettingsObserver(context.getMainThreadHandler(), context); + mFoldLockSettingsObserver.register(); } public void setMixedHandler(DefaultMixedHandler mixedHandler) { @@ -1504,51 +1516,80 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } - void onKeyguardVisibilityChanged(boolean showing) { - mKeyguardShowing = showing; + /** + * Runs when keyguard state changes. The booleans here are a bit complicated, so for reference: + * @param active {@code true} if we are in a state where the keyguard *should* be shown + * -- still true when keyguard is "there" but is behind an app, or + * screen is off. + * @param occludingTaskRunning {@code true} when there is a running task that has + * FLAG_SHOW_WHEN_LOCKED -- also true when the task is + * just running on its own and keyguard is not active + * at all. + */ + void onKeyguardStateChanged(boolean active, boolean occludingTaskRunning) { + mKeyguardActive = active; if (!mMainStage.isActive()) { return; } - ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onKeyguardVisibilityChanged: showing=%b", showing); - setDividerVisibility(!mKeyguardShowing, null); + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, + "onKeyguardVisibilityChanged: active=%b occludingTaskRunning=%b", + active, occludingTaskRunning); + setDividerVisibility(!mKeyguardActive, null); + + if (active && occludingTaskRunning) { + dismissSplitKeepingLastActiveStage(EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); + } } void onFinishedWakingUp() { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinishedWakingUp"); - if (!mMainStage.isActive()) { + if (mBreakOnNextWake) { + dismissSplitKeepingLastActiveStage(EXIT_REASON_DEVICE_FOLDED); + } + } + + void onStartedGoingToSleep() { + recordLastActiveStage(); + } + + /** + * Records the user's last focused stage -- main stage or side stage. Used to determine which + * stage of a split pair should be kept, in cases where system focus has moved elsewhere. + */ + void recordLastActiveStage() { + if (!isSplitActive() || !isSplitScreenVisible()) { + mLastActiveStage = STAGE_TYPE_UNDEFINED; + } else if (mMainStage.isFocused()) { + mLastActiveStage = STAGE_TYPE_MAIN; + } else if (mSideStage.isFocused()) { + mLastActiveStage = STAGE_TYPE_SIDE; + } + } + + /** + * Dismisses split, keeping the app that the user focused last in split screen. If the user was + * not in split screen, {@link #mLastActiveStage} should be set to STAGE_TYPE_UNDEFINED, and we + * will do a no-op. + */ + void dismissSplitKeepingLastActiveStage(@ExitReason int reason) { + if (!mMainStage.isActive() || mLastActiveStage == STAGE_TYPE_UNDEFINED) { + // no-op return; } - // Check if there's only one stage visible while keyguard occluded. - final boolean mainStageVisible = mMainStage.mRootTaskInfo.isVisible; - final boolean oneStageVisible = - mMainStage.mRootTaskInfo.isVisible != mSideStage.mRootTaskInfo.isVisible; - if (oneStageVisible && !ENABLE_SHELL_TRANSITIONS) { - // Dismiss split because there's show-when-locked activity showing on top of keyguard. - // Also make sure the task contains show-when-locked activity remains on top after split - // dismissed. - final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage; - exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); - } - - // Dismiss split if the flag record any side of stages. - if (mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) { - if (ENABLE_SHELL_TRANSITIONS) { - // Need manually clear here due to this transition might be aborted due to keyguard - // on top and lead to no visible change. - clearSplitPairedInRecents(EXIT_REASON_DEVICE_FOLDED); - final WindowContainerTransaction wct = new WindowContainerTransaction(); - prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct); - mSplitTransitions.startDismissTransition(wct, this, - mTopStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED); - setSplitsVisible(false); - } else { - exitSplitScreen( - mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage, - EXIT_REASON_DEVICE_FOLDED); - } - mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; + if (ENABLE_SHELL_TRANSITIONS) { + // Need manually clear here due to this transition might be aborted due to keyguard + // on top and lead to no visible change. + clearSplitPairedInRecents(reason); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + prepareExitSplitScreen(mLastActiveStage, wct); + mSplitTransitions.startDismissTransition(wct, this, mLastActiveStage, reason); + setSplitsVisible(false); + } else { + exitSplitScreen(mLastActiveStage == STAGE_TYPE_MAIN ? mMainStage : mSideStage, reason); } + + mBreakOnNextWake = false; } void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) { @@ -1909,8 +1950,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } // Put BAL flags to avoid activity start aborted. Otherwise, flows like shortcut to split // will be canceled. - options.setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED); - options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true); + options.setPendingIntentBackgroundActivityStartMode( + MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS); // TODO (b/336477473): Disallow enter PiP when launching a task in split by default; // this might have to be changed as more split-to-pip cujs are defined. @@ -2223,11 +2264,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setDividerVisibility: visible=%b keyguardShowing=%b dividerAnimating=%b caller=%s", - visible, mKeyguardShowing, mIsDividerRemoteAnimating, Debug.getCaller()); + visible, mKeyguardActive, mIsDividerRemoteAnimating, Debug.getCaller()); // Defer showing divider bar after keyguard dismissed, so it won't interfere with keyguard // dismissing animation. - if (visible && mKeyguardShowing) { + if (visible && mKeyguardActive) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, " Defer showing divider bar due to keyguard showing."); return; @@ -2597,21 +2638,24 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @VisibleForTesting void onFoldedStateChanged(boolean folded) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFoldedStateChanged: folded=%b", folded); - mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; - if (!folded) return; - - if (!isSplitActive() || !isSplitScreenVisible()) return; - // To avoid split dismiss when user fold the device and unfold to use later, we only - // record the flag here and try to dismiss on wakeUp callback to ensure split dismiss - // when user interact on phone folded. - if (mMainStage.isFocused()) { - mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN; - } else if (mSideStage.isFocused()) { - mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE; + if (folded) { + recordLastActiveStage(); + // If user folds and has the setting "Continue using apps on fold = NEVER", we assume + // they don't want to continue using split on the outer screen (i.e. we break split if + // they wake the device in its folded state). + mBreakOnNextWake = willSleepOnFold(); + } else { + mBreakOnNextWake = false; } } + /** Returns true if the phone will sleep when it folds. */ + @VisibleForTesting + boolean willSleepOnFold() { + return mFoldLockSettingsObserver != null && mFoldLockSettingsObserver.isSleepOnFold(); + } + private Rect getSideStageBounds() { return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSplitLayout.getBounds1() : mSplitLayout.getBounds2(); @@ -3739,8 +3783,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); Log.w(TAG, splitFailureMessage("onNoLongerSupportMultiWindow", - "app package " + taskInfo.baseActivity.getPackageName() - + " does not support splitscreen, or is a controlled activity type")); + "app package " + taskInfo.baseIntent.getComponent() + + " does not support splitscreen, or is a controlled activity" + + " type")); if (splitScreenVisible) { handleUnsupportedSplitStart(); } 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 e330f3ab65ab..b65e97899f3e 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 @@ -33,7 +33,6 @@ 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.TransactionPool; -import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.splitscreen.StageCoordinator; @@ -88,7 +87,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); + Optional.empty(), null /* stageCoordinator */, multiInstanceHelper, mainExecutor, + mainHandler); mTaskOrganizer = shellTaskOrganizer; mSyncQueue = syncQueue; 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 79476919221e..81ca48fa6b3d 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 @@ -56,7 +56,7 @@ public class TvStageCoordinator extends StageCoordinator SystemWindows systemWindows) { super(context, displayId, syncQueue, taskOrganizer, displayController, displayImeController, displayInsetsController, transitions, transactionPool, iconProvider, - mainExecutor, recentTasks, launchAdjacentController, Optional.empty()); + mainExecutor, mainHandler, recentTasks, launchAdjacentController, Optional.empty()); mTvSplitMenuController = new TvSplitMenuController(context, this, systemWindows, mainHandler); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java index 5c814dcc9b16..bad5baf24651 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java @@ -73,7 +73,7 @@ class WindowlessSnapshotWindowCreator { final Display display = mDisplayManager.getDisplay(runningTaskInfo.displayId); final StartingSurfaceDrawer.WindowlessStartingWindow wlw = new StartingSurfaceDrawer.WindowlessStartingWindow( - runningTaskInfo.configuration, rootSurface); + mContext.getResources().getConfiguration(), rootSurface); final SurfaceControlViewHost mViewHost = new SurfaceControlViewHost( mContext, display, wlw, "WindowlessSnapshotWindowCreator"); final Rect windowBounds = runningTaskInfo.configuration.windowConfiguration.getBounds(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java index 98a803128587..f3725579bf48 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java @@ -76,7 +76,7 @@ class WindowlessSplashWindowCreator extends AbsSplashWindowCreator { } final StartingSurfaceDrawer.WindowlessStartingWindow wlw = new StartingSurfaceDrawer.WindowlessStartingWindow( - taskInfo.configuration, rootSurface); + mContext.getResources().getConfiguration(), rootSurface); final SurfaceControlViewHost viewHost = new SurfaceControlViewHost( myContext, display, wlw, "WindowlessSplashWindowCreator"); final String title = "Windowless Splash " + taskInfo.taskId; @@ -95,7 +95,7 @@ class WindowlessSplashWindowCreator extends AbsSplashWindowCreator { } final FrameLayout rootLayout = new FrameLayout( - mSplashscreenContentDrawer.createViewContextWrapper(mContext)); + mSplashscreenContentDrawer.createViewContextWrapper(myContext)); viewHost.setView(rootLayout, lp); final int bgColor = taskDescription.getBackgroundColor(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java index dd4595a70211..287e779d8e24 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java @@ -48,6 +48,7 @@ public class ShellInit { public ShellInit(ShellExecutor mainExecutor) { mMainExecutor = mainExecutor; + ProtoLog.registerGroups(ShellProtoLogGroup.values()); } /** @@ -76,7 +77,6 @@ public class ShellInit { */ @VisibleForTesting public void init() { - ProtoLog.registerGroups(ShellProtoLogGroup.values()); ProtoLog.v(WM_SHELL_INIT, "Initializing Shell Components: %d", mInitCallbacks.size()); SurfaceControl.setDebugUsageAfterRelease(true); // Init in order of registration 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 b9cb6d3d5007..5c230c0a505c 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 @@ -56,6 +56,7 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; +import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; @@ -72,6 +73,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { private final IWindowManager mWindowManager; private final Context mContext; private final Handler mMainHandler; + private final @ShellBackgroundThread ShellExecutor mBgExecutor; private final ShellExecutor mMainExecutor; private final Choreographer mMainChoreographer; private final DisplayController mDisplayController; @@ -108,6 +110,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { public CaptionWindowDecorViewModel( Context context, Handler mainHandler, + @ShellBackgroundThread ShellExecutor bgExecutor, ShellExecutor shellExecutor, Choreographer mainChoreographer, IWindowManager windowManager, @@ -120,6 +123,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mContext = context; mMainExecutor = shellExecutor; mMainHandler = mainHandler; + mBgExecutor = bgExecutor; mWindowManager = windowManager; mMainChoreographer = mainChoreographer; mTaskOrganizer = taskOrganizer; @@ -289,6 +293,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { taskInfo, taskSurface, mMainHandler, + mBgExecutor, mMainChoreographer, mSyncQueue); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); 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 7e1b973a98f4..cf42a49f7103 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 @@ -21,6 +21,7 @@ import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLarge import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize; import android.annotation.NonNull; +import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.WindowConfiguration; @@ -48,7 +49,9 @@ import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.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.extension.TaskInfoKt; /** @@ -58,6 +61,7 @@ import com.android.wm.shell.windowdecor.extension.TaskInfoKt; */ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> { private final Handler mHandler; + private final @ShellBackgroundThread ShellExecutor mBgExecutor; private final Choreographer mChoreographer; private final SyncTransactionQueue mSyncQueue; @@ -78,10 +82,12 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL RunningTaskInfo taskInfo, SurfaceControl taskSurface, Handler handler, + @ShellBackgroundThread ShellExecutor bgExecutor, Choreographer choreographer, SyncTransactionQueue syncQueue) { super(context, displayController, taskOrganizer, taskInfo, taskSurface); mHandler = handler; + mBgExecutor = bgExecutor; mChoreographer = choreographer; mSyncQueue = syncQueue; } @@ -218,6 +224,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL relayoutParams.mOccludingCaptionElements.add(controlsElement); } + @SuppressLint("MissingPermission") void relayout(RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, boolean applyStartTransactionOnDraw, boolean setTaskCropAndPosition) { @@ -235,7 +242,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo - mTaskOrganizer.applyTransaction(wct); + mBgExecutor.execute(() -> mTaskOrganizer.applyTransaction(wct)); if (mResult.mRootView == null) { // This means something blocks the window decor from showing, e.g. the task is hidden. 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 53976251e31d..8312aef110b9 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 @@ -22,6 +22,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.content.Intent.ACTION_MAIN; +import static android.content.Intent.CATEGORY_APP_BROWSER; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.view.InputDevice.SOURCE_TOUCHSCREEN; import static android.view.MotionEvent.ACTION_CANCEL; @@ -36,6 +38,8 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing; import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR; +import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR; +import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; @@ -43,11 +47,8 @@ import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; @@ -98,6 +99,7 @@ import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition; import com.android.wm.shell.desktopmode.DesktopWallpaperActivity; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; +import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreen.StageType; @@ -132,6 +134,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final ShellController mShellController; private final Context mContext; private final Handler mMainHandler; + private final @ShellBackgroundThread ShellExecutor mBgExecutor; private final Choreographer mMainChoreographer; private final DisplayController mDisplayController; private final SyncTransactionQueue mSyncQueue; @@ -183,6 +186,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { ShellExecutor shellExecutor, Handler mainHandler, Choreographer mainChoreographer, + @ShellBackgroundThread ShellExecutor bgExecutor, ShellInit shellInit, ShellCommandHandler shellCommandHandler, IWindowManager windowManager, @@ -201,6 +205,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { shellExecutor, mainHandler, mainChoreographer, + bgExecutor, shellInit, shellCommandHandler, windowManager, @@ -225,6 +230,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { ShellExecutor shellExecutor, Handler mainHandler, Choreographer mainChoreographer, + @ShellBackgroundThread ShellExecutor bgExecutor, ShellInit shellInit, ShellCommandHandler shellCommandHandler, IWindowManager windowManager, @@ -245,6 +251,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mMainExecutor = shellExecutor; mMainHandler = mainHandler; mMainChoreographer = mainChoreographer; + mBgExecutor = bgExecutor; mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class); mTaskOrganizer = taskOrganizer; mShellController = shellController; @@ -321,7 +328,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { if (decoration == null) return; final RunningTaskInfo oldTaskInfo = decoration.mTaskInfo; - if (taskInfo.displayId != oldTaskInfo.displayId) { + if (taskInfo.displayId != oldTaskInfo.displayId + && !Flags.enableAdditionalWindowsAboveStatusBar()) { removeTaskFromEventReceiver(oldTaskInfo.displayId); incrementEventReceiverTasks(taskInfo.displayId); } @@ -385,7 +393,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { decoration.close(); final int displayId = taskInfo.displayId; - if (mEventReceiversByDisplay.contains(displayId)) { + if (mEventReceiversByDisplay.contains(displayId) + && !Flags.enableAdditionalWindowsAboveStatusBar()) { removeTaskFromEventReceiver(displayId); } // Remove the decoration from the cache last because WindowDecoration#close could still @@ -424,19 +433,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } private void openInBrowser(Uri uri) { - final Intent intent = new Intent(Intent.ACTION_VIEW, uri) - .setComponent(getDefaultBrowser()) + final Intent intent = Intent.makeMainSelectorActivity(ACTION_MAIN, CATEGORY_APP_BROWSER) + .setData(uri) .addFlags(FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(intent); } - private ComponentName getDefaultBrowser() { - final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://")); - final ResolveInfo info = mContext.getPackageManager() - .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); - return info.getComponentInfo().getComponentName(); - } - private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener, View.OnGenericMotionListener, DragDetector.MotionEventHandler { @@ -517,6 +519,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } } else if (id == R.id.split_screen_button) { decoration.closeHandleMenu(); + // When the app enters split-select, the handle will no longer be visible, meaning + // we shouldn't receive input for it any longer. + decoration.disposeStatusBarInputLayer(); mDesktopTasksController.requestSplit(decoration.mTaskInfo); } else if (id == R.id.open_in_browser_button) { // TODO(b/346441962): let the decoration handle the click gesture and only call back @@ -653,13 +658,38 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); final RunningTaskInfo taskInfo = decoration.mTaskInfo; if (DesktopModeStatus.canEnterDesktopMode(mContext) - && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { - return false; + && !taskInfo.isFreeform()) { + return handleNonFreeformMotionEvent(decoration, v, e); + } else { + return handleFreeformMotionEvent(decoration, taskInfo, v, e); } + } + + private boolean handleNonFreeformMotionEvent(DesktopModeWindowDecoration decoration, + View v, MotionEvent e) { + final int id = v.getId(); + if (id == R.id.caption_handle) { + if (e.getActionMasked() == MotionEvent.ACTION_DOWN) { + // Caption handle is located within the status bar region, meaning the + // DisplayPolicy will attempt to transfer this input to status bar if it's + // a swipe down. Pilfer here to keep the gesture in handle alone. + mInputManager.pilferPointers(v.getViewRootImpl().getInputToken()); + } + handleCaptionThroughStatusBar(e, decoration); + final boolean wasDragging = mIsDragging; + updateDragStatus(e.getActionMasked()); + // Only prevent onClick from receiving this event if it's a drag. + return wasDragging; + } + return false; + } + + private boolean handleFreeformMotionEvent(DesktopModeWindowDecoration decoration, + RunningTaskInfo taskInfo, View v, MotionEvent e) { + final int id = v.getId(); if (mGestureDetector.onTouchEvent(e)) { return true; } - final int id = v.getId(); final boolean touchingButton = (id == R.id.close_window || id == R.id.maximize_window || id == R.id.open_menu_button); switch (e.getActionMasked()) { @@ -668,7 +698,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDragPositioningCallback.onDragPositioningStart( 0 /* ctrlType */, e.getRawX(0), e.getRawY(0)); - mIsDragging = false; + updateDragStatus(e.getActionMasked()); mHasLongClicked = false; // Do not consume input event if a button is touched, otherwise it would // prevent the button's ripple effect from showing. @@ -688,7 +718,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { decoration.mTaskSurface, e.getRawX(dragPointerIdx), newTaskBounds); - mIsDragging = true; + updateDragStatus(e.getActionMasked()); return true; } case MotionEvent.ACTION_UP: @@ -718,7 +748,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { // onClick call that results. return false; } else { - mIsDragging = false; + updateDragStatus(e.getActionMasked()); return true; } } @@ -726,6 +756,21 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return true; } + private void updateDragStatus(int eventAction) { + switch (eventAction) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: { + mIsDragging = false; + break; + } + case MotionEvent.ACTION_MOVE: { + mIsDragging = true; + break; + } + } + } + /** * Perform a task size toggle on release of the double-tap, assuming no drag event * was handled during the double-tap. @@ -850,6 +895,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { * * @param relevantDecor the window decoration of the focused task's caption. This method only * handles motion events outside this caption's bounds. + * TODO(b/349135068): Outside-touch detection no longer works with the + * enableAdditionalWindowsAboveStatusBar flag enabled. This + * will be fixed once we can add FLAG_WATCH_OUTSIDE_TOUCH to relevant menus, + * at which point, all EventReceivers and external touch logic should be removed. */ private void handleEventOutsideCaption(MotionEvent ev, DesktopModeWindowDecoration relevantDecor) { @@ -902,9 +951,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { dragFromStatusBarAllowed = windowingMode == WINDOWING_MODE_FULLSCREEN || windowingMode == WINDOWING_MODE_MULTI_WINDOW; } - - if (dragFromStatusBarAllowed - && relevantDecor.checkTouchEventInFocusedCaptionHandle(ev)) { + final boolean shouldStartTransitionDrag = + relevantDecor.checkTouchEventInFocusedCaptionHandle(ev) + || Flags.enableAdditionalWindowsAboveStatusBar(); + if (dragFromStatusBarAllowed && shouldStartTransitionDrag) { mTransitionDragActive = true; } break; @@ -918,8 +968,15 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { // Though this isn't a hover event, we need to update handle's hover state // as it likely will change. relevantDecor.updateHoverAndPressStatus(ev); - mDesktopTasksController.onDragPositioningEndThroughStatusBar( + DesktopModeVisualIndicator.IndicatorType resultType = + mDesktopTasksController.onDragPositioningEndThroughStatusBar( new PointF(ev.getRawX(), ev.getRawY()), relevantDecor.mTaskInfo); + // If we are entering split select, handle will no longer be visible and + // should not be receiving any input. + if (resultType == TO_SPLIT_LEFT_INDICATOR + || resultType != TO_SPLIT_RIGHT_INDICATOR) { + relevantDecor.disposeStatusBarInputLayer(); + } mMoveToDesktopAnimator = null; return; } else { @@ -931,7 +988,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { relevantDecor.checkTouchEvent(ev); break; } - case ACTION_MOVE: { if (relevantDecor == null) { return; @@ -1091,10 +1147,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDesktopModeWindowDecorFactory.create( mContext, mDisplayController, + mSplitScreenController, mTaskOrganizer, taskInfo, taskSurface, mMainHandler, + mBgExecutor, mMainChoreographer, mSyncQueue, mRootTaskDisplayAreaOrganizer); @@ -1132,7 +1190,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { windowDecoration.setDragDetector(touchEventListener.mDragDetector); windowDecoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */); - incrementEventReceiverTasks(taskInfo.displayId); + if (!Flags.enableAdditionalWindowsAboveStatusBar()) { + incrementEventReceiverTasks(taskInfo.displayId); + } } private RunningTaskInfo getOtherSplitTask(int taskId) { 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 5d662b20ebb9..529def7ca3d7 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 @@ -24,11 +24,13 @@ import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize; import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize; import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize; import android.annotation.NonNull; +import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.WindowConfiguration.WindowingMode; import android.content.ComponentName; @@ -68,7 +70,9 @@ 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.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.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener; @@ -95,8 +99,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin static final long CLOSE_MAXIMIZE_MENU_DELAY_MS = 150L; private final Handler mHandler; + private final @ShellBackgroundThread ShellExecutor mBgExecutor; private final Choreographer mChoreographer; private final SyncTransactionQueue mSyncQueue; + private final SplitScreenController mSplitScreenController; private WindowDecorationViewHolder mWindowDecorViewHolder; private View.OnClickListener mOnCaptionButtonClickListener; @@ -147,27 +153,32 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin DesktopModeWindowDecoration( Context context, DisplayController displayController, + SplitScreenController splitScreenController, ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, Handler handler, + @ShellBackgroundThread ShellExecutor bgExecutor, Choreographer choreographer, SyncTransactionQueue syncQueue, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { - this (context, displayController, taskOrganizer, taskInfo, taskSurface, - handler, choreographer, syncQueue, rootTaskDisplayAreaOrganizer, - SurfaceControl.Builder::new, SurfaceControl.Transaction::new, - WindowContainerTransaction::new, SurfaceControl::new, - new SurfaceControlViewHostFactory() {}, DefaultMaximizeMenuFactory.INSTANCE); + this (context, displayController, splitScreenController, taskOrganizer, taskInfo, + taskSurface, handler, bgExecutor, choreographer, syncQueue, + rootTaskDisplayAreaOrganizer, SurfaceControl.Builder::new, + SurfaceControl.Transaction::new, WindowContainerTransaction::new, + SurfaceControl::new, new SurfaceControlViewHostFactory() {}, + DefaultMaximizeMenuFactory.INSTANCE); } DesktopModeWindowDecoration( Context context, DisplayController displayController, + SplitScreenController splitScreenController, ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, Handler handler, + @ShellBackgroundThread ShellExecutor bgExecutor, Choreographer choreographer, SyncTransactionQueue syncQueue, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, @@ -181,7 +192,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, windowContainerTransactionSupplier, surfaceControlSupplier, surfaceControlViewHostFactory); + mSplitScreenController = splitScreenController; mHandler = handler; + mBgExecutor = bgExecutor; mChoreographer = choreographer; mSyncQueue = syncQueue; mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; @@ -327,6 +340,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mHandler.post(mCurrentViewHostRunnable); } + @SuppressLint("MissingPermission") private void updateRelayoutParamsAndSurfaces(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) { @@ -337,7 +351,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } if (isHandleMenuActive()) { - mHandleMenu.relayout(startT); + mHandleMenu.relayout(startT, mResult.mCaptionX); } updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw, @@ -353,33 +367,47 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo Trace.beginSection("DesktopModeWindowDecoration#relayout-applyWCT"); - mTaskOrganizer.applyTransaction(wct); + mBgExecutor.execute(() -> mTaskOrganizer.applyTransaction(wct)); Trace.endSection(); 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. + disposeStatusBarInputLayer(); Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces return; } if (oldRootView != mResult.mRootView) { + disposeStatusBarInputLayer(); mWindowDecorViewHolder = createViewHolder(); } Trace.beginSection("DesktopModeWindowDecoration#relayout-binding"); - mWindowDecorViewHolder.bindData(mTaskInfo); + + final Point position = new Point(); + if (isAppHandle(mWindowDecorViewHolder)) { + position.set(determineHandlePosition()); + } + mWindowDecorViewHolder.bindData(mTaskInfo, + position, + mResult.mCaptionWidth, + mResult.mCaptionHeight, + isCaptionVisible()); Trace.endSection(); if (!mTaskInfo.isFocused) { closeHandleMenu(); closeMaximizeMenu(); } - updateDragResizeListener(oldDecorationSurface); updateMaximizeMenu(startT); Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces } + private boolean isCaptionVisible() { + return mTaskInfo.isVisible && mIsCaptionVisible; + } + private void setCapturedLink(Uri capturedLink, long timeStamp) { if (capturedLink == null || (mCapturedLink != null && mCapturedLink.mTimeStamp == timeStamp)) { @@ -461,12 +489,42 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } } + private Point determineHandlePosition() { + final Point position = new Point(mResult.mCaptionX, 0); + if (mSplitScreenController.getSplitPosition(mTaskInfo.taskId) + == SPLIT_POSITION_BOTTOM_OR_RIGHT + && mDisplayController.getDisplayLayout(mTaskInfo.displayId).isLandscape() + ) { + // If this is the right split task, add left stage's width. + final Rect leftStageBounds = new Rect(); + mSplitScreenController.getStageBounds(leftStageBounds, new Rect()); + position.x += leftStageBounds.width(); + } + return position; + } + + /** + * Dispose of the view used to forward inputs in status bar region. Intended to be + * used any time handle is no longer visible. + */ + void disposeStatusBarInputLayer() { + if (!isAppHandle(mWindowDecorViewHolder) + || !Flags.enableAdditionalWindowsAboveStatusBar()) { + return; + } + ((AppHandleViewHolder) mWindowDecorViewHolder).disposeStatusBarInputLayer(); + } + private WindowDecorationViewHolder createViewHolder() { if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_handle) { return new AppHandleViewHolder( mResult.mRootView, mOnCaptionTouchListener, - mOnCaptionButtonClickListener + mOnCaptionButtonClickListener, + (v, event) -> { + updateHoverAndPressStatus(event); + return true; + } ); } else if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_header) { @@ -489,6 +547,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin throw new IllegalArgumentException("Unexpected layout resource id"); } + private boolean isAppHandle(WindowDecorationViewHolder viewHolder) { + return viewHolder instanceof AppHandleViewHolder; + } + @VisibleForTesting static void updateRelayoutParams( RelayoutParams relayoutParams, @@ -863,7 +925,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin splitScreenController, DesktopModeStatus.canEnterDesktopMode(mContext), browserLinkAvailable(), - mResult.mCaptionHeight + mResult.mCaptionWidth, + mResult.mCaptionHeight, + mResult.mCaptionX ); mWindowDecorViewHolder.onHandleMenuOpened(); mHandleMenu.show(); @@ -955,10 +1019,17 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin * @return {@code true} if event is inside caption handle view, {@code false} if not */ boolean checkTouchEventInFocusedCaptionHandle(MotionEvent ev) { - if (isHandleMenuActive() || !(mWindowDecorViewHolder - instanceof AppHandleViewHolder)) { + if (isHandleMenuActive() || !isAppHandle(mWindowDecorViewHolder) + || Flags.enableAdditionalWindowsAboveStatusBar()) { return false; } + // The status bar input layer can only receive input in handle coordinates to begin with, + // so checking coordinates is unnecessary as input is always within handle bounds. + if (isAppHandle(mWindowDecorViewHolder) + && Flags.enableAdditionalWindowsAboveStatusBar() + && isCaptionVisible()) { + return true; + } return checkTouchEventInCaption(ev); } @@ -992,7 +1063,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin * @param ev the MotionEvent to compare */ void checkTouchEvent(MotionEvent ev) { - if (mResult.mRootView == null) return; + if (mResult.mRootView == null || Flags.enableAdditionalWindowsAboveStatusBar()) return; final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption); final View handle = caption.findViewById(R.id.caption_handle); final boolean inHandle = !isHandleMenuActive() @@ -1014,7 +1085,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin * @param ev the MotionEvent to compare against. */ void updateHoverAndPressStatus(MotionEvent ev) { - if (mResult.mRootView == null) return; + if (mResult.mRootView == null || Flags.enableAdditionalWindowsAboveStatusBar()) return; final View handle = mResult.mRootView.findViewById(R.id.caption_handle); final boolean inHandle = !isHandleMenuActive() && checkTouchEventInFocusedCaptionHandle(ev); @@ -1024,15 +1095,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin // We want handle to remain pressed if the pointer moves outside of it during a drag. handle.setPressed((inHandle && action == ACTION_DOWN) || (handle.isPressed() && action != ACTION_UP && action != ACTION_CANCEL)); - if (isHandleMenuActive() && !isHandleMenuAboveStatusBar()) { + if (isHandleMenuActive()) { mHandleMenu.checkMotionEvent(ev); } } - private boolean isHandleMenuAboveStatusBar() { - return Flags.enableAdditionalWindowsAboveStatusBar() && !mTaskInfo.isFreeform(); - } - private boolean pointInView(View v, float x, float y) { return v != null && v.getLeft() <= x && v.getRight() >= x && v.getTop() <= y && v.getBottom() >= y; @@ -1044,6 +1111,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin closeHandleMenu(); mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId); disposeResizeVeil(); + disposeStatusBarInputLayer(); clearCurrentViewHostRunnable(); super.close(); } @@ -1143,20 +1211,24 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin DesktopModeWindowDecoration create( Context context, DisplayController displayController, + SplitScreenController splitScreenController, ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, Handler handler, + @ShellBackgroundThread ShellExecutor bgExecutor, Choreographer choreographer, SyncTransactionQueue syncQueue, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { return new DesktopModeWindowDecoration( context, displayController, + splitScreenController, taskOrganizer, taskInfo, taskSurface, handler, + bgExecutor, choreographer, syncQueue, rootTaskDisplayAreaOrganizer); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt index bce233fb0b52..e174e83d11c9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt @@ -15,19 +15,15 @@ */ package com.android.wm.shell.windowdecor +import android.annotation.ColorInt import android.annotation.DimenRes import android.app.ActivityManager -import android.app.WindowConfiguration -import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM -import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN -import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW -import android.app.WindowConfiguration.WINDOWING_MODE_PINNED import android.content.Context import android.content.res.ColorStateList -import android.content.res.Configuration import android.content.res.Resources import android.graphics.Bitmap -import android.graphics.Color +import android.graphics.BlendMode +import android.graphics.BlendModeColorFilter import android.graphics.Point import android.graphics.PointF import android.graphics.Rect @@ -40,6 +36,8 @@ import android.widget.ImageView import android.widget.TextView import android.window.SurfaceSyncGroup import androidx.annotation.VisibleForTesting +import androidx.compose.ui.graphics.toArgb +import androidx.core.view.isGone import com.android.window.flags.Flags import com.android.wm.shell.R import com.android.wm.shell.common.DisplayController @@ -47,7 +45,10 @@ import com.android.wm.shell.common.split.SplitScreenConstants import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer +import com.android.wm.shell.windowdecor.common.DecorThemeUtil import com.android.wm.shell.windowdecor.extension.isFullscreen +import com.android.wm.shell.windowdecor.extension.isMultiWindow +import com.android.wm.shell.windowdecor.extension.isPinned /** * Handle menu opened when the appropriate button is clicked on. @@ -68,10 +69,13 @@ class HandleMenu( private val splitScreenController: SplitScreenController, private val shouldShowWindowingPill: Boolean, private val shouldShowBrowserPill: Boolean, - private val captionHeight: Int + private val captionWidth: Int, + private val captionHeight: Int, + captionX: Int ) { private val context: Context = parentDecor.mDecorWindowContext private val taskInfo: ActivityManager.RunningTaskInfo = parentDecor.mTaskInfo + private val decorThemeUtil = DecorThemeUtil(context) private val isViewAboveStatusBar: Boolean get() = (Flags.enableAdditionalWindowsAboveStatusBar() && !taskInfo.isFreeform) @@ -102,33 +106,8 @@ class HandleMenu( // those as well. private val globalMenuPosition: Point = Point() - /** - * An a array of windowing icon color based on current UI theme. First element of the - * array is for inactive icons and the second is for active icons. - */ - private val windowingIconColor: Array<ColorStateList> - get() { - val mode = (context.resources.configuration.uiMode - and Configuration.UI_MODE_NIGHT_MASK) - val isNightMode = (mode == Configuration.UI_MODE_NIGHT_YES) - val typedArray = context.obtainStyledAttributes( - intArrayOf( - com.android.internal.R.attr.materialColorOnSurface, - com.android.internal.R.attr.materialColorPrimary - ) - ) - val inActiveColor = - typedArray.getColor(0, if (isNightMode) Color.WHITE else Color.BLACK) - val activeColor = typedArray.getColor(1, if (isNightMode) Color.WHITE else Color.BLACK) - typedArray.recycle() - return arrayOf( - ColorStateList.valueOf(inActiveColor), - ColorStateList.valueOf(activeColor) - ) - } - init { - updateHandleMenuPillPositions() + updateHandleMenuPillPositions(captionX) } fun show() { @@ -175,9 +154,8 @@ class HandleMenu( * Animates the appearance of the handle menu and its three pills. */ private fun animateHandleMenu() { - when (taskInfo.windowingMode) { - WindowConfiguration.WINDOWING_MODE_FULLSCREEN, - WINDOWING_MODE_MULTI_WINDOW -> { + when { + taskInfo.isFullscreen || taskInfo.isMultiWindow -> { handleMenuAnimator?.animateCaptionHandleExpandToOpen() } else -> { @@ -193,95 +171,104 @@ class HandleMenu( private fun setupHandleMenu() { val handleMenu = handleMenuViewContainer?.view ?: return handleMenu.setOnTouchListener(onTouchListener) - setupAppInfoPill(handleMenu) + + val style = calculateMenuStyle() + setupAppInfoPill(handleMenu, style) if (shouldShowWindowingPill) { - setupWindowingPill(handleMenu) + setupWindowingPill(handleMenu, style) } - setupMoreActionsPill(handleMenu) - setupOpenInBrowserPill(handleMenu) + setupMoreActionsPill(handleMenu, style) + setupOpenInBrowserPill(handleMenu, style) } /** * Set up interactive elements of handle menu's app info pill. */ - private fun setupAppInfoPill(handleMenu: View) { - val collapseBtn = handleMenu.findViewById<HandleMenuImageButton>(R.id.collapse_menu_button) - val appIcon = handleMenu.findViewById<ImageView>(R.id.application_icon) - val appName = handleMenu.findViewById<TextView>(R.id.application_name) - collapseBtn.setOnClickListener(onClickListener) - collapseBtn.taskInfo = taskInfo - appIcon.setImageBitmap(appIconBitmap) - appName.text = this.appName + private fun setupAppInfoPill(handleMenu: View, style: MenuStyle) { + val pill = handleMenu.requireViewById<View>(R.id.app_info_pill).apply { + background.colorFilter = BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY) + } + + pill.requireViewById<HandleMenuImageButton>(R.id.collapse_menu_button) + .let { collapseBtn -> + collapseBtn.imageTintList = ColorStateList.valueOf(style.textColor) + collapseBtn.setOnClickListener(onClickListener) + collapseBtn.taskInfo = taskInfo + } + pill.requireViewById<ImageView>(R.id.application_icon).let { appIcon -> + appIcon.setImageBitmap(appIconBitmap) + } + pill.requireViewById<TextView>(R.id.application_name).let { appNameView -> + appNameView.text = appName + appNameView.setTextColor(style.textColor) + } } /** * Set up interactive elements and color of handle menu's windowing pill. */ - private fun setupWindowingPill(handleMenu: View) { - val fullscreenBtn = handleMenu.findViewById<ImageButton>(R.id.fullscreen_button) - val splitscreenBtn = handleMenu.findViewById<ImageButton>(R.id.split_screen_button) - val floatingBtn = handleMenu.findViewById<ImageButton>(R.id.floating_button) + private fun setupWindowingPill(handleMenu: View, style: MenuStyle) { + val pill = handleMenu.requireViewById<View>(R.id.windowing_pill).apply { + background.colorFilter = BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY) + } + + val fullscreenBtn = pill.requireViewById<ImageButton>(R.id.fullscreen_button) + val splitscreenBtn = pill.requireViewById<ImageButton>(R.id.split_screen_button) + val floatingBtn = pill.requireViewById<ImageButton>(R.id.floating_button) // TODO: Remove once implemented. floatingBtn.visibility = View.GONE + val desktopBtn = handleMenu.requireViewById<ImageButton>(R.id.desktop_button) - val desktopBtn = handleMenu.findViewById<ImageButton>(R.id.desktop_button) fullscreenBtn.setOnClickListener(onClickListener) splitscreenBtn.setOnClickListener(onClickListener) floatingBtn.setOnClickListener(onClickListener) desktopBtn.setOnClickListener(onClickListener) - // The button corresponding to the windowing mode that the task is currently in uses a - // different color than the others. - val iconColors = windowingIconColor - val inActiveColorStateList = iconColors[0] - val activeColorStateList = iconColors[1] - fullscreenBtn.imageTintList = if (taskInfo.isFullscreen) { - activeColorStateList - } else { - inActiveColorStateList - } - splitscreenBtn.imageTintList = if (taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) { - activeColorStateList - } else { - inActiveColorStateList - } - floatingBtn.imageTintList = if (taskInfo.windowingMode == WINDOWING_MODE_PINNED) { - activeColorStateList - } else { - inActiveColorStateList - } - desktopBtn.imageTintList = if (taskInfo.isFreeform) { - activeColorStateList - } else { - inActiveColorStateList - } + + fullscreenBtn.isSelected = taskInfo.isFullscreen + fullscreenBtn.imageTintList = style.windowingButtonColor + splitscreenBtn.isSelected = taskInfo.isMultiWindow + splitscreenBtn.imageTintList = style.windowingButtonColor + floatingBtn.isSelected = taskInfo.isPinned + floatingBtn.imageTintList = style.windowingButtonColor + desktopBtn.isSelected = taskInfo.isFreeform + desktopBtn.imageTintList = style.windowingButtonColor } /** * Set up interactive elements & height of handle menu's more actions pill */ - private fun setupMoreActionsPill(handleMenu: View) { - if (!SHOULD_SHOW_MORE_ACTIONS_PILL) { - handleMenu.findViewById<View>(R.id.more_actions_pill).visibility = View.GONE + private fun setupMoreActionsPill(handleMenu: View, style: MenuStyle) { + val pill = handleMenu.requireViewById<View>(R.id.more_actions_pill).apply { + isGone = !SHOULD_SHOW_MORE_ACTIONS_PILL + background.colorFilter = BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY) + } + pill.requireViewById<Button>(R.id.screenshot_button).let { screenshotBtn -> + screenshotBtn.setTextColor(style.textColor) + screenshotBtn.compoundDrawableTintList = ColorStateList.valueOf(style.textColor) } } - private fun setupOpenInBrowserPill(handleMenu: View) { - if (!shouldShowBrowserPill) { - handleMenu.findViewById<View>(R.id.open_in_browser_pill).visibility = View.GONE - return + private fun setupOpenInBrowserPill(handleMenu: View, style: MenuStyle) { + val pill = handleMenu.requireViewById<View>(R.id.open_in_browser_pill).apply { + isGone = !shouldShowBrowserPill + background.colorFilter = BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY) + } + + pill.requireViewById<Button>(R.id.open_in_browser_button).let { browserButton -> + browserButton.setOnClickListener(onClickListener) + browserButton.setTextColor(style.textColor) + browserButton.compoundDrawableTintList = ColorStateList.valueOf(style.textColor) } - val browserButton = handleMenu.findViewById<Button>(R.id.open_in_browser_button) - browserButton.setOnClickListener(onClickListener) } /** * Updates handle menu's position variables to reflect its next position. */ - private fun updateHandleMenuPillPositions() { + private fun updateHandleMenuPillPositions(captionX: Int) { val menuX: Int val menuY: Int val taskBounds = taskInfo.getConfiguration().windowConfiguration.bounds - updateGlobalMenuPosition(taskBounds) + updateGlobalMenuPosition(taskBounds, captionX) if (layoutResId == R.layout.desktop_mode_app_header) { // Align the handle menu to the left side of the caption. menuX = marginMenuStart @@ -302,21 +289,22 @@ class HandleMenu( handleMenuPosition.set(menuX.toFloat(), menuY.toFloat()) } - private fun updateGlobalMenuPosition(taskBounds: Rect) { - when (taskInfo.windowingMode) { - WINDOWING_MODE_FREEFORM -> { + private fun updateGlobalMenuPosition(taskBounds: Rect, captionX: Int) { + val nonFreeformX = captionX + (captionWidth / 2) - (menuWidth / 2) + when { + taskInfo.isFreeform -> { globalMenuPosition.set( /* x = */ taskBounds.left + marginMenuStart, /* y = */ taskBounds.top + marginMenuTop ) } - WINDOWING_MODE_FULLSCREEN -> { + taskInfo.isFullscreen -> { globalMenuPosition.set( - /* x = */ taskBounds.width() / 2 - (menuWidth / 2), + /* x = */ nonFreeformX, /* y = */ marginMenuTop ) } - WINDOWING_MODE_MULTI_WINDOW -> { + taskInfo.isMultiWindow -> { val splitPosition = splitScreenController.getSplitPosition(taskInfo.taskId) val leftOrTopStageBounds = Rect() val rightOrBottomStageBounds = Rect() @@ -326,16 +314,13 @@ class HandleMenu( when (splitPosition) { SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT -> { globalMenuPosition.set( - /* x = */ leftOrTopStageBounds.width() - + (rightOrBottomStageBounds.width() / 2) - - (menuWidth / 2), + /* x = */ leftOrTopStageBounds.width() + nonFreeformX, /* y = */ marginMenuTop ) } SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT -> { globalMenuPosition.set( - /* x = */ (leftOrTopStageBounds.width() / 2) - - (menuWidth / 2), + /* x = */ nonFreeformX, /* y = */ marginMenuTop ) } @@ -347,9 +332,12 @@ class HandleMenu( /** * Update pill layout, in case task changes have caused positioning to change. */ - fun relayout(t: SurfaceControl.Transaction) { + fun relayout( + t: SurfaceControl.Transaction, + captionX: Int + ) { handleMenuViewContainer?.let { container -> - updateHandleMenuPillPositions() + updateHandleMenuPillPositions(captionX) container.setPosition(t, handleMenuPosition.x, handleMenuPosition.y) } } @@ -469,14 +457,41 @@ class HandleMenu( handleMenuViewContainer?.releaseView() handleMenuViewContainer = null } - if (taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN || - taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) { + if (taskInfo.isFullscreen || taskInfo.isMultiWindow) { handleMenuAnimator?.animateCollapseIntoHandleClose(after) } else { handleMenuAnimator?.animateClose(after) } } + private fun calculateMenuStyle(): MenuStyle { + val colorScheme = decorThemeUtil.getColorScheme(taskInfo) + return MenuStyle( + backgroundColor = colorScheme.surfaceBright.toArgb(), + textColor = colorScheme.onSurface.toArgb(), + windowingButtonColor = ColorStateList( + arrayOf( + intArrayOf(android.R.attr.state_pressed), + intArrayOf(android.R.attr.state_focused), + intArrayOf(android.R.attr.state_selected), + intArrayOf(), + ), + intArrayOf( + colorScheme.onSurface.toArgb(), + colorScheme.onSurface.toArgb(), + colorScheme.primary.toArgb(), + colorScheme.onSurface.toArgb(), + ) + ), + ) + } + + private data class MenuStyle( + @ColorInt val backgroundColor: Int, + @ColorInt val textColor: Int, + val windowingButtonColor: ColorStateList, + ) + companion object { private const val TAG = "HandleMenu" private const val SHOULD_SHOW_MORE_ACTIONS_PILL = false 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 d212f2131ed4..a691f59a2155 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 @@ -139,7 +139,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> private SurfaceControlViewHost mViewHost; private Configuration mWindowDecorConfig; TaskDragResizer mTaskDragResizer; - private boolean mIsCaptionVisible; + boolean mIsCaptionVisible; /** The most recent set of insets applied to this window decoration. */ private WindowDecorationInsets mWindowDecorationInsets; @@ -508,6 +508,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mTaskDragResizer = taskDragResizer; } + // TODO(b/346441962): Move these three methods closer to implementing or View-level classes to + // keep implementation details more encapsulated. private void setCaptionVisibility(View rootView, boolean visible) { if (rootView == null) { return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt index 4897f76a20cf..6a354f10049b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt @@ -30,17 +30,22 @@ import android.view.WindowManager */ class AdditionalSystemViewContainer( private val context: Context, - layoutId: Int, taskId: Int, x: Int, y: Int, width: Int, - height: Int + height: Int, + layoutId: Int? = null ) : AdditionalViewContainer() { override val view: View + val windowManager: WindowManager? = context.getSystemService(WindowManager::class.java) init { - view = LayoutInflater.from(context).inflate(layoutId, null) + if (layoutId != null) { + view = LayoutInflater.from(context).inflate(layoutId, null) + } else { + view = View(context) + } val lp = WindowManager.LayoutParams( width, height, x, y, WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL, @@ -51,12 +56,11 @@ class AdditionalSystemViewContainer( gravity = Gravity.LEFT or Gravity.TOP setTrustedOverlay() } - val wm: WindowManager? = context.getSystemService(WindowManager::class.java) - wm?.addView(view, lp) + windowManager?.addView(view, lp) } override fun releaseView() { - context.getSystemService(WindowManager::class.java)?.removeViewImmediate(view) + windowManager?.removeViewImmediate(view) } override fun setPosition(t: SurfaceControl.Transaction, x: Float, y: Float) { @@ -64,6 +68,6 @@ class AdditionalSystemViewContainer( this.x = x.toInt() this.y = y.toInt() } - view.layoutParams = lp + windowManager?.updateViewLayout(view, lp) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt index 7ade9876d28a..6f8e00143848 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt @@ -18,6 +18,8 @@ package com.android.wm.shell.windowdecor.extension import android.app.TaskInfo import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN +import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW +import android.app.WindowConfiguration.WINDOWING_MODE_PINNED import android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS import android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND @@ -33,5 +35,14 @@ val TaskInfo.isLightCaptionBarAppearance: Boolean return (appearance and APPEARANCE_LIGHT_CAPTION_BARS) != 0 } +/** Whether the task is in fullscreen windowing mode. */ val TaskInfo.isFullscreen: Boolean get() = windowingMode == WINDOWING_MODE_FULLSCREEN + +/** Whether the task is in pinned windowing mode. */ +val TaskInfo.isPinned: Boolean + get() = windowingMode == WINDOWING_MODE_PINNED + +/** Whether the task is in multi-window windowing mode. */ +val TaskInfo.isMultiWindow: Boolean + get() = windowingMode == WINDOWING_MODE_MULTI_WINDOW diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt index 8d822c252288..76dfe37c7cc0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt @@ -20,37 +20,68 @@ import android.app.ActivityManager.RunningTaskInfo import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.content.res.ColorStateList import android.graphics.Color +import android.graphics.Point +import android.view.SurfaceControl import android.view.View +import android.view.View.OnClickListener +import android.view.View.OnHoverListener import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS +import android.view.WindowManager import android.widget.ImageButton +import com.android.window.flags.Flags import com.android.wm.shell.R import com.android.wm.shell.animation.Interpolators +import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer /** * A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen/split). * It hosts a simple handle bar from which to initiate a drag motion to enter desktop mode. */ internal class AppHandleViewHolder( - rootView: View, - onCaptionTouchListener: View.OnTouchListener, - onCaptionButtonClickListener: View.OnClickListener + rootView: View, + private val onCaptionTouchListener: View.OnTouchListener, + private val onCaptionButtonClickListener: OnClickListener, + private val onCaptionHoverListener: OnHoverListener, ) : WindowDecorationViewHolder(rootView) { companion object { private const val CAPTION_HANDLE_ANIMATION_DURATION: Long = 100 } - + private lateinit var taskInfo: RunningTaskInfo + private val windowManager = context.getSystemService(WindowManager::class.java) private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption) private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle) + // An invisible View that takes up the same coordinates as captionHandle but is layered + // above the status bar. The purpose of this View is to receive input intended for + // captionHandle. + private var statusBarInputLayer: AdditionalSystemViewContainer? = null + init { captionView.setOnTouchListener(onCaptionTouchListener) captionHandle.setOnTouchListener(onCaptionTouchListener) captionHandle.setOnClickListener(onCaptionButtonClickListener) + captionHandle.setOnHoverListener(onCaptionHoverListener) } - override fun bindData(taskInfo: RunningTaskInfo) { + override fun bindData( + taskInfo: RunningTaskInfo, + position: Point, + width: Int, + height: Int, + isCaptionVisible: Boolean + ) { captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo)) + this.taskInfo = taskInfo + if (!isCaptionVisible && hasStatusBarInputLayer()) { + disposeStatusBarInputLayer() + return + } + if (hasStatusBarInputLayer()) { + updateStatusBarInputLayer(position) + } else { + createStatusBarInputLayer(position, width, height) + } } override fun onHandleMenuOpened() { @@ -61,6 +92,45 @@ internal class AppHandleViewHolder( animateCaptionHandleAlpha(startValue = 0f, endValue = 1f) } + private fun createStatusBarInputLayer(handlePosition: Point, + handleWidth: Int, + handleHeight: Int) { + if (!Flags.enableAdditionalWindowsAboveStatusBar()) return + statusBarInputLayer = AdditionalSystemViewContainer(context, taskInfo.taskId, + handlePosition.x, handlePosition.y, handleWidth, handleHeight) + val view = statusBarInputLayer?.view + val lp = view?.layoutParams as WindowManager.LayoutParams + lp.title = "Handle Input Layer of task " + taskInfo.taskId + lp.setTrustedOverlay() + // Make this window a spy window to enable it to pilfer pointers from the system-wide + // gesture listener that receives events before window. This is to prevent notification + // shade gesture when we swipe down to enter desktop. + lp.inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY + view.id = R.id.caption_handle + view.setOnClickListener(onCaptionButtonClickListener) + view.setOnTouchListener(onCaptionTouchListener) + view.setOnHoverListener(onCaptionHoverListener) + windowManager.updateViewLayout(view, lp) + } + + private fun updateStatusBarInputLayer(globalPosition: Point) { + statusBarInputLayer?.setPosition(SurfaceControl.Transaction(), globalPosition.x.toFloat(), + globalPosition.y.toFloat()) ?: return + } + + private fun hasStatusBarInputLayer(): Boolean { + return statusBarInputLayer != null + } + + /** + * Remove the input layer from [WindowManager]. Should be used when caption handle + * is not visible. + */ + fun disposeStatusBarInputLayer() { + statusBarInputLayer?.releaseView() + statusBarInputLayer = null + } + private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int { return if (shouldUseLightCaptionColors(taskInfo)) { context.getColor(R.color.desktop_mode_caption_handle_bar_light) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt index 46127b177bc3..b704d9c001c8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt @@ -21,6 +21,7 @@ import android.content.res.ColorStateList import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.Color +import android.graphics.Point import android.graphics.drawable.LayerDrawable import android.graphics.drawable.RippleDrawable import android.graphics.drawable.ShapeDrawable @@ -136,7 +137,13 @@ internal class AppHeaderViewHolder( onMaximizeHoverAnimationFinishedListener } - override fun bindData(taskInfo: RunningTaskInfo) { + override fun bindData( + taskInfo: RunningTaskInfo, + position: Point, + width: Int, + height: Int, + isCaptionVisible: Boolean + ) { if (Flags.enableThemedAppHeaders()) { bindDataWithThemedHeaders(taskInfo) } else { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt index 5ae8d252a908..2341b099699f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.windowdecor.viewholder import android.app.ActivityManager.RunningTaskInfo import android.content.Context +import android.graphics.Point import android.view.View /** @@ -30,7 +31,13 @@ internal abstract class WindowDecorationViewHolder(rootView: View) { * A signal to the view holder that new data is available and that the views should be updated to * reflect it. */ - abstract fun bindData(taskInfo: RunningTaskInfo) + abstract fun bindData( + taskInfo: RunningTaskInfo, + position: Point, + width: Int, + height: Int, + isCaptionVisible: Boolean + ) /** Callback when the handle menu is opened. */ abstract fun onHandleMenuOpened() diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/SwitchToOverviewFromDesktop.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/SwitchToOverviewFromDesktop.kt new file mode 100644 index 000000000000..b4cadf4f300b --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/SwitchToOverviewFromDesktop.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.flicker.service.desktopmode.scenarios + +import android.app.Instrumentation +import android.tools.NavBar +import android.tools.Rotation +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.DesktopModeAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.window.flags.Flags +import com.android.wm.shell.flicker.service.common.Utils +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +/** +* Base test for opening recent apps overview from desktop mode. +* +* Navigation mode can be passed as a constructor parameter, by default it is set to gesture navigation. +*/ +@Ignore("Base Test Class") +abstract class SwitchToOverviewFromDesktop +@JvmOverloads +constructor(val navigationMode: NavBar = NavBar.MODE_GESTURAL) { + + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(navigationMode, Rotation.ROTATION_0) + + @Before + fun setup() { + Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet) + testApp.enterDesktopWithDrag(wmHelper, device) + } + + @Test + open fun switchToOverview() { + tapl.getLaunchedAppState().switchToOverview() + } + + @After + fun teardown() { + testApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt index db962e717a3b..2406bdeebdf2 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt @@ -48,7 +48,10 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { @Before fun setup() { - tapl.workspace.switchToOverview().dismissAllTasks() + val overview = tapl.workspace.switchToOverview() + if (overview.hasTasks()) { + overview.dismissAllTasks() + } tapl.setEnableRotation(true) tapl.setExpectedRotation(rotation.value) diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp index 6b6954289a34..a0408652a29b 100644 --- a/libs/WindowManager/Shell/tests/unittest/Android.bp +++ b/libs/WindowManager/Shell/tests/unittest/Android.bp @@ -37,6 +37,7 @@ android_test { ], static_libs: [ + "TestParameterInjector", "WindowManager-Shell", "junit", "flag-junit", diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java index 55b6bd278f20..bba9418db66a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java @@ -46,12 +46,14 @@ import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.window.TransitionInfo; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.window.flags.Flags; import com.android.wm.shell.transition.TransitionInfoBuilder; +import com.google.testing.junit.testparameterinjector.TestParameter; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -59,6 +61,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import java.util.ArrayList; +import java.util.Arrays; /** * Tests for {@link ActivityEmbeddingAnimationRunner}. @@ -67,7 +70,7 @@ import java.util.ArrayList; * atest WMShellUnitTests:ActivityEmbeddingAnimationRunnerTests */ @SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(TestParameterInjector.class) public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnimationTestBase { @Rule @@ -204,15 +207,13 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim // TODO(b/243518738): Rewrite with TestParameter @EnableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG) @Test - public void testCalculateParentBounds_flagEnabled() { + public void testCalculateParentBounds_flagEnabled_emptyParentSize() { TransitionInfo.Change change; final TransitionInfo.Change stubChange = createChange(0 /* flags */); final Rect actualParentBounds = new Rect(); - Rect parentBounds = new Rect(0, 0, 2000, 2000); - Rect endAbsBounds = new Rect(0, 0, 2000, 2000); change = prepareChangeForParentBoundsCalculationTest( new Point(0, 0) /* endRelOffset */, - endAbsBounds, + new Rect(0, 0, 2000, 2000), new Point() /* endParentSize */ ); @@ -220,69 +221,80 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim assertTrue("Parent bounds must be empty because end parent size is not set.", actualParentBounds.isEmpty()); + } - String testString = "Parent start with (0, 0)"; - change = prepareChangeForParentBoundsCalculationTest( - new Point(endAbsBounds.left - parentBounds.left, - endAbsBounds.top - parentBounds.top), - endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); - - calculateParentBounds(change, stubChange, actualParentBounds); - - assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, - actualParentBounds); - - testString = "Container not start with (0, 0)"; - parentBounds = new Rect(0, 0, 2000, 2000); - endAbsBounds = new Rect(1000, 500, 2000, 1500); - change = prepareChangeForParentBoundsCalculationTest( - new Point(endAbsBounds.left - parentBounds.left, - endAbsBounds.top - parentBounds.top), - endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); - - calculateParentBounds(change, stubChange, actualParentBounds); - - assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, - actualParentBounds); - - testString = "Parent container on the right"; - parentBounds = new Rect(1000, 0, 2000, 2000); - endAbsBounds = new Rect(1000, 500, 1500, 1500); - change = prepareChangeForParentBoundsCalculationTest( - new Point(endAbsBounds.left - parentBounds.left, - endAbsBounds.top - parentBounds.top), - endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); - - calculateParentBounds(change, stubChange, actualParentBounds); - - assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, - actualParentBounds); - - testString = "Parent container on the bottom"; - parentBounds = new Rect(0, 1000, 2000, 2000); - endAbsBounds = new Rect(500, 1500, 1500, 2000); - change = prepareChangeForParentBoundsCalculationTest( + @EnableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG) + @Test + public void testCalculateParentBounds_flagEnabled( + @TestParameter ParentBoundsTestParameters params) { + final TransitionInfo.Change stubChange = createChange(0 /*flags*/); + final Rect parentBounds = params.getParentBounds(); + final Rect endAbsBounds = params.getEndAbsBounds(); + final TransitionInfo.Change change = prepareChangeForParentBoundsCalculationTest( new Point(endAbsBounds.left - parentBounds.left, endAbsBounds.top - parentBounds.top), endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); + final Rect actualParentBounds = new Rect(); calculateParentBounds(change, stubChange, actualParentBounds); - assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, - actualParentBounds); - - testString = "Parent container in the middle"; - parentBounds = new Rect(500, 500, 1500, 1500); - endAbsBounds = new Rect(1000, 500, 1500, 1000); - change = prepareChangeForParentBoundsCalculationTest( - new Point(endAbsBounds.left - parentBounds.left, - endAbsBounds.top - parentBounds.top), - endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); - - calculateParentBounds(change, stubChange, actualParentBounds); + assertEquals(parentBounds, actualParentBounds); + } - assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, - actualParentBounds); + private enum ParentBoundsTestParameters { + PARENT_START_WITH_0_0( + new int[]{0, 0, 2000, 2000}, + new int[]{0, 0, 2000, 2000}), + CONTAINER_NOT_START_WITH_0_0( + new int[] {0, 0, 2000, 2000}, + new int[] {1000, 500, 1500, 1500}), + PARENT_ON_THE_RIGHT( + new int[] {1000, 0, 2000, 2000}, + new int[] {1000, 500, 1500, 1500}), + PARENT_ON_THE_BOTTOM( + new int[] {0, 1000, 2000, 2000}, + new int[] {500, 1500, 1500, 2000}), + PARENT_IN_THE_MIDDLE( + new int[] {500, 500, 1500, 1500}, + new int[] {1000, 500, 1500, 1000}); + + /** + * An int array to present {left, top, right, bottom} of the parent {@link Rect bounds}. + */ + @NonNull + private final int[] mParentBounds; + + /** + * An int array to present {left, top, right, bottom} of the absolute container + * {@link Rect bounds} after the transition finishes. + */ + @NonNull + private final int[] mEndAbsBounds; + + ParentBoundsTestParameters( + @NonNull int[] parentBounds, @NonNull int[] endAbsBounds) { + mParentBounds = parentBounds; + mEndAbsBounds = endAbsBounds; + } + + @NonNull + private Rect getParentBounds() { + return asRect(mParentBounds); + } + + @NonNull + private Rect getEndAbsBounds() { + return asRect(mEndAbsBounds); + } + + @NonNull + private static Rect asRect(@NonNull int[] bounds) { + if (bounds.length != 4) { + throw new IllegalArgumentException("There must be exactly 4 elements in bounds, " + + "but found " + bounds.length + ": " + Arrays.toString(bounds)); + } + return new Rect(bounds[0], bounds[1], bounds[2], bounds[3]); + } } @Test 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 6002c21ccb24..8421365e594d 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 @@ -580,138 +580,6 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun moveToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() { - val task = setUpFullscreenTask() - setUpLandscapeDisplay() - - controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestEnterDesktopWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun moveToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() { - val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE) - setUpLandscapeDisplay() - - controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestEnterDesktopWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun moveToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() { - val task = - setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true) - setUpLandscapeDisplay() - - controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestEnterDesktopWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun moveToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() { - val task = - setUpFullscreenTask(isResizable = false, screenOrientation = SCREEN_ORIENTATION_LANDSCAPE) - setUpLandscapeDisplay() - - controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestEnterDesktopWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun moveToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() { - val task = - setUpFullscreenTask( - isResizable = false, - screenOrientation = SCREEN_ORIENTATION_PORTRAIT, - shouldLetterbox = true) - setUpLandscapeDisplay() - - controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestEnterDesktopWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun moveToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() { - val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT) - setUpPortraitDisplay() - - controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestEnterDesktopWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun moveToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() { - val task = - setUpFullscreenTask( - deviceOrientation = ORIENTATION_PORTRAIT, - screenOrientation = SCREEN_ORIENTATION_PORTRAIT) - setUpPortraitDisplay() - - controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestEnterDesktopWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun moveToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() { - val task = - setUpFullscreenTask( - deviceOrientation = ORIENTATION_PORTRAIT, - screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, - shouldLetterbox = true) - setUpPortraitDisplay() - - controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestEnterDesktopWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun moveToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() { - val task = - setUpFullscreenTask( - isResizable = false, - deviceOrientation = ORIENTATION_PORTRAIT, - screenOrientation = SCREEN_ORIENTATION_PORTRAIT) - setUpPortraitDisplay() - - controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestEnterDesktopWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun moveToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() { - val task = - setUpFullscreenTask( - isResizable = false, - deviceOrientation = ORIENTATION_PORTRAIT, - screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, - shouldLetterbox = true) - setUpPortraitDisplay() - - controller.moveToDesktop(task, transitionSource = UNKNOWN) - val wct = getLatestEnterDesktopWct() - assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS) - } - - @Test fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() { val task = setUpFullscreenTask() val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt index b1d62f485a2a..805ebbc6a6da 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt @@ -16,12 +16,15 @@ package com.android.wm.shell.shared.desktopmode +import android.os.SystemProperties import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import android.provider.Settings import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito +import com.android.modules.utils.testing.ExtendedMockitoRule import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY import com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION @@ -37,6 +40,9 @@ 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.any +import org.mockito.kotlin.eq /** * Test class for [DesktopModeFlags] @@ -47,7 +53,14 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) class DesktopModeFlagsTest : ShellTestCase() { - @JvmField @Rule val setFlagsRule = SetFlagsRule() + @Rule(order = 1) + @JvmField + val setFlagsRule = SetFlagsRule() + + @Rule(order = 2) + @JvmField + val extendedMockitoRule: ExtendedMockitoRule = + ExtendedMockitoRule.Builder(this).mockStatic(SystemProperties::class.java).build() @Before fun setUp() { @@ -119,7 +132,7 @@ class DesktopModeFlagsTest : ShellTestCase() { @Test @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) fun isEnabled_unrecognizableOverride_featureFlagOn_returnsTrue() { - setOverride(-2) + setOverride(INVALID_TOGGLE_OVERRIDE_SETTING) // For overridableFlag, for recognizable overrides, follow flag assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() @@ -129,7 +142,7 @@ class DesktopModeFlagsTest : ShellTestCase() { @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) fun isEnabled_unrecognizableOverride_featureFlagOff_returnsFalse() { - setOverride(-2) + setOverride(INVALID_TOGGLE_OVERRIDE_SETTING) // For overridableFlag, for recognizable overrides, follow flag assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() @@ -187,102 +200,82 @@ class DesktopModeFlagsTest : ShellTestCase() { @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) fun isEnabled_noSystemProperty_overrideOn_featureFlagOff_returnsTrueAndStoresPropertyOn() { - System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY) + setSystemProperty(INVALID_TOGGLE_OVERRIDE_SETTING) setOverride(OVERRIDE_ON.setting) assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() // Store System Property if not present - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_ON.setting.toString()) + verifySystemPropertySet(OVERRIDE_ON.setting) } @Test @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) fun isEnabled_noSystemProperty_overrideUnset_featureFlagOn_returnsTrueAndStoresPropertyUnset() { - System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY) + setSystemProperty(INVALID_TOGGLE_OVERRIDE_SETTING) setOverride(OVERRIDE_UNSET.setting) assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() // Store System Property if not present - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_UNSET.setting.toString()) + verifySystemPropertySet(OVERRIDE_UNSET.setting) } @Test @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) fun isEnabled_noSystemProperty_overrideUnset_featureFlagOff_returnsFalseAndStoresPropertyUnset() { - System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY) + setSystemProperty(INVALID_TOGGLE_OVERRIDE_SETTING) setOverride(OVERRIDE_UNSET.setting) assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() // Store System Property if not present - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_UNSET.setting.toString()) - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - @Suppress("ktlint:standard:max-line-length") - fun isEnabled_systemPropertyNotInteger_overrideOff_featureFlagOn_returnsFalseAndStoresPropertyOff() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "abc") - setOverride(OVERRIDE_OFF.setting) - - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() - // Store System Property if currently invalid - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_OFF.setting.toString()) + verifySystemPropertySet(OVERRIDE_UNSET.setting) } @Test @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) @Suppress("ktlint:standard:max-line-length") fun isEnabled_systemPropertyInvalidInteger_overrideOff_featureFlagOn_returnsFalseAndStoresPropertyOff() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "-2") + setSystemProperty(INVALID_TOGGLE_OVERRIDE_SETTING) setOverride(OVERRIDE_OFF.setting) assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() // Store System Property if currently invalid - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_OFF.setting.toString()) + verifySystemPropertySet(OVERRIDE_OFF.setting) } @Test @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) fun isEnabled_systemPropertyOff_overrideOn_featureFlagOn_returnsFalseAndDoesNotUpdateProperty() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_OFF.setting.toString()) + setSystemProperty(OVERRIDE_OFF.setting) setOverride(OVERRIDE_ON.setting) // Have a consistent override until reboot assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_OFF.setting.toString()) + verifySystemPropertyNotUpdated() } @Test @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) fun isEnabled_systemPropertyOn_overrideOff_featureFlagOff_returnsTrueAndDoesNotUpdateProperty() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_ON.setting.toString()) + setSystemProperty(OVERRIDE_ON.setting) setOverride(OVERRIDE_OFF.setting) // Have a consistent override until reboot assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_ON.setting.toString()) + verifySystemPropertyNotUpdated() } @Test @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) @Suppress("ktlint:standard:max-line-length") fun isEnabled_systemPropertyUnset_overrideOff_featureFlagOn_returnsTrueAndDoesNotUpdateProperty() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_UNSET.setting.toString()) + setSystemProperty(OVERRIDE_UNSET.setting) setOverride(OVERRIDE_OFF.setting) // Have a consistent override until reboot assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_UNSET.setting.toString()) + verifySystemPropertyNotUpdated() } @Test @@ -441,16 +434,33 @@ class DesktopModeFlagsTest : ShellTestCase() { } private fun resetCache() { - val cachedToggleOverride = - DesktopModeFlags::class.java.getDeclaredField("cachedToggleOverride") + val cachedToggleOverride = DesktopModeFlags::class.java.getDeclaredField("cachedToggleOverride") cachedToggleOverride.isAccessible = true cachedToggleOverride.set(null, null) - // Clear override cache stored in System property - System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY) + setSystemProperty(INVALID_TOGGLE_OVERRIDE_SETTING) + } + + private fun setSystemProperty(systemProperty: Int) { + ExtendedMockito.doReturn(systemProperty).`when` { + SystemProperties.getInt(eq(SYSTEM_PROPERTY_OVERRIDE_KEY), any()) + } + } + + private fun verifySystemPropertySet(systemProperty: Int) { + ExtendedMockito.verify { + SystemProperties.set(eq(SYSTEM_PROPERTY_OVERRIDE_KEY), eq(systemProperty.toString())) + } + } + + private fun verifySystemPropertyNotUpdated() { + ExtendedMockito.verify( + { SystemProperties.set(eq(SYSTEM_PROPERTY_OVERRIDE_KEY), any()) }, Mockito.never()) } private companion object { const val SYSTEM_PROPERTY_OVERRIDE_KEY = "sys.wmshell.desktopmode.dev_toggle_override" + + const val INVALID_TOGGLE_OVERRIDE_SETTING = -2 } } 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 5b95b1588814..1c5d5e963156 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 @@ -50,7 +50,7 @@ import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.Bundle; -import android.os.IBinder; +import android.os.Handler; import android.window.IWindowContainerToken; import android.window.WindowContainerToken; @@ -104,6 +104,7 @@ public class SplitScreenControllerTests extends ShellTestCase { @Mock SyncTransactionQueue mSyncQueue; @Mock RootTaskDisplayAreaOrganizer mRootTDAOrganizer; @Mock ShellExecutor mMainExecutor; + @Mock Handler mMainHandler; @Mock DisplayController mDisplayController; @Mock DisplayImeController mDisplayImeController; @Mock DisplayInsetsController mDisplayInsetsController; @@ -134,7 +135,7 @@ public class SplitScreenControllerTests extends ShellTestCase { mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool, mIconProvider, Optional.of(mRecentTasks), mLaunchAdjacentController, Optional.of(mWindowDecorViewModel), Optional.of(mDesktopTasksController), - mStageCoordinator, mMultiInstanceHelper, mMainExecutor)); + mStageCoordinator, mMultiInstanceHelper, mMainExecutor, mMainHandler)); } @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 a3009a55198f..29d3fb4cc04e 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 @@ -22,6 +22,7 @@ import static org.mockito.Mockito.mock; import android.app.ActivityManager; import android.content.Context; import android.graphics.Rect; +import android.os.Handler; import android.view.SurfaceControl; import android.view.SurfaceSession; @@ -77,13 +78,13 @@ public class SplitTestUtils { DisplayController displayController, DisplayImeController imeController, DisplayInsetsController insetsController, SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool, - ShellExecutor mainExecutor, + ShellExecutor mainExecutor, Handler mainHandler, Optional<RecentTasksController> recentTasks, LaunchAdjacentController launchAdjacentController, Optional<WindowDecorViewModel> windowDecorViewModel) { super(context, displayId, syncQueue, taskOrganizer, mainStage, sideStage, displayController, imeController, insetsController, splitLayout, - transitions, transactionPool, mainExecutor, recentTasks, + transitions, transactionPool, mainExecutor, mainHandler, recentTasks, launchAdjacentController, windowDecorViewModel); // Prepare root task for testing. 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 34b2eebb15a1..37ef7881bde7 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 @@ -49,6 +49,7 @@ import static org.mockito.Mockito.verify; import android.annotation.NonNull; import android.app.ActivityManager; +import android.os.Handler; import android.os.IBinder; import android.view.SurfaceControl; import android.view.SurfaceSession; @@ -107,6 +108,7 @@ public class SplitTransitionTests extends ShellTestCase { @Mock private IconProvider mIconProvider; @Mock private WindowDecorViewModel mWindowDecorViewModel; @Mock private ShellExecutor mMainExecutor; + @Mock private Handler mMainHandler; @Mock private LaunchAdjacentController mLaunchAdjacentController; @Mock private DefaultMixedHandler mMixedHandler; @Mock private SplitScreen.SplitInvocationListener mInvocationListener; @@ -140,7 +142,7 @@ public class SplitTransitionTests extends ShellTestCase { mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions, - mTransactionPool, mMainExecutor, Optional.empty(), + mTransactionPool, mMainExecutor, mMainHandler, Optional.empty(), mLaunchAdjacentController, Optional.empty()); mStageCoordinator.setMixedHandler(mMixedHandler); mSplitScreenTransitions = mStageCoordinator.getSplitTransitions(); 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 d18fec2f24ad..eaef704b7d78 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 @@ -138,7 +138,8 @@ public class StageCoordinatorTests extends ShellTestCase { mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool, - mMainExecutor, Optional.empty(), mLaunchAdjacentController, Optional.empty())); + mMainExecutor, mMainHandler, Optional.empty(), mLaunchAdjacentController, + Optional.empty())); mDividerLeash = new SurfaceControl.Builder(mSurfaceSession).setName("fakeDivider").build(); when(mSplitLayout.getBounds1()).thenReturn(mBounds1); @@ -347,8 +348,7 @@ public class StageCoordinatorTests extends ShellTestCase { assertThat(options.getLaunchRootTask()).isEqualTo(mMainStage.mRootTaskInfo.token); assertThat(options.getPendingIntentBackgroundActivityStartMode()) - .isEqualTo(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); - assertThat(options.isPendingIntentBackgroundActivityLaunchAllowedByPermission()).isTrue(); + .isEqualTo(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS); } @Test @@ -359,10 +359,11 @@ public class StageCoordinatorTests extends ShellTestCase { mMainStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().setVisible(true).build(); when(mStageCoordinator.isSplitActive()).thenReturn(true); when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true); + when(mStageCoordinator.willSleepOnFold()).thenReturn(true); mStageCoordinator.onFoldedStateChanged(true); - assertEquals(mStageCoordinator.mTopStageAfterFoldDismiss, STAGE_TYPE_MAIN); + assertEquals(mStageCoordinator.mLastActiveStage, STAGE_TYPE_MAIN); mStageCoordinator.onFinishedWakingUp(); 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 b1803e97b107..aeae0bebb697 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 @@ -30,6 +30,7 @@ import android.hardware.display.DisplayManager import android.hardware.display.VirtualDisplay import android.hardware.input.InputManager import android.os.Handler +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.annotations.RequiresFlagsEnabled import android.platform.test.flag.junit.CheckFlagsRule @@ -62,6 +63,7 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder +import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayInsetsController import com.android.wm.shell.common.DisplayLayout @@ -71,6 +73,7 @@ import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition import com.android.wm.shell.freeform.FreeformTaskTransitionStarter import com.android.wm.shell.shared.desktopmode.DesktopModeStatus +import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.sysui.KeyguardChangeListener import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController @@ -78,8 +81,6 @@ import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener -import java.util.Optional -import java.util.function.Supplier import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule @@ -99,6 +100,8 @@ import org.mockito.kotlin.eq import org.mockito.kotlin.spy import org.mockito.kotlin.whenever import org.mockito.quality.Strictness +import java.util.Optional +import java.util.function.Supplier /** * Tests of [DesktopModeWindowDecorViewModel] @@ -122,6 +125,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Mock private lateinit var mockMainChoreographer: Choreographer @Mock private lateinit var mockTaskOrganizer: ShellTaskOrganizer @Mock private lateinit var mockDisplayController: DisplayController + @Mock private lateinit var mockSplitScreenController: SplitScreenController @Mock private lateinit var mockDisplayLayout: DisplayLayout @Mock private lateinit var displayInsetsController: DisplayInsetsController @Mock private lateinit var mockSyncQueue: SyncTransactionQueue @@ -136,6 +140,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Mock private lateinit var mockShellCommandHandler: ShellCommandHandler @Mock private lateinit var mockWindowManager: IWindowManager @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor + private val bgExecutor = TestShellExecutor() private val transactionFactory = Supplier<SurfaceControl.Transaction> { SurfaceControl.Transaction() @@ -155,6 +160,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { mockShellExecutor, mockMainHandler, mockMainChoreographer, + bgExecutor, shellInit, mockShellCommandHandler, mockWindowManager, @@ -171,7 +177,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { mockRootTaskDisplayAreaOrganizer, windowDecorByTaskIdSpy, mockInteractionJankMonitor ) - + desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController) whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout) whenever(mockDisplayLayout.stableInsets()).thenReturn(STABLE_INSETS) whenever(mockInputMonitorFactory.create(any(), any())).thenReturn(mockInputMonitor) @@ -204,10 +210,12 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { verify(mockDesktopModeWindowDecorFactory).create( mContext, mockDisplayController, + mockSplitScreenController, mockTaskOrganizer, task, taskSurface, mockMainHandler, + bgExecutor, mockMainChoreographer, mockSyncQueue, mockRootTaskDisplayAreaOrganizer @@ -228,10 +236,12 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { verify(mockDesktopModeWindowDecorFactory, never()).create( mContext, mockDisplayController, + mockSplitScreenController, mockTaskOrganizer, task, taskSurface, mockMainHandler, + bgExecutor, mockMainChoreographer, mockSyncQueue, mockRootTaskDisplayAreaOrganizer @@ -243,10 +253,12 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { verify(mockDesktopModeWindowDecorFactory, times(1)).create( mContext, mockDisplayController, + mockSplitScreenController, mockTaskOrganizer, task, taskSurface, mockMainHandler, + bgExecutor, mockMainChoreographer, mockSyncQueue, mockRootTaskDisplayAreaOrganizer @@ -254,6 +266,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { } @Test + @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR) fun testCreateAndDisposeEventReceiver() { val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM) setUpMockDecorationForTask(task) @@ -266,6 +279,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { } @Test + @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR) fun testEventReceiversOnMultipleDisplays() { val secondaryDisplay = createVirtualDisplay() ?: return val secondaryDisplayId = secondaryDisplay.display.displayId @@ -344,7 +358,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { onTaskChanging(task) verify(mockDesktopModeWindowDecorFactory, never()) - .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any()) + .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(), + any()) } @Test @@ -365,7 +380,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { onTaskOpening(task) verify(mockDesktopModeWindowDecorFactory) - .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any()) + .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), + any(), any()) } finally { mockitoSession.finishMocking() } @@ -382,7 +398,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { onTaskOpening(task) verify(mockDesktopModeWindowDecorFactory, never()) - .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any()) + .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(), + any()) } @Test @@ -399,7 +416,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { onTaskOpening(task) verify(mockDesktopModeWindowDecorFactory, never()) - .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any()) + .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), + any(), any()) } @Test @@ -496,7 +514,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { onTaskOpening(task) verify(mockDesktopModeWindowDecorFactory, never()) - .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any()) + .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(), + any()) } finally { mockitoSession.finishMocking() } @@ -520,7 +539,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { onTaskOpening(task) verify(mockDesktopModeWindowDecorFactory) - .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any()) + .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(), + any()) } finally { mockitoSession.finishMocking() } @@ -543,7 +563,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { onTaskOpening(task) verify(mockDesktopModeWindowDecorFactory) - .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any()) + .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(), + any()) } finally { mockitoSession.finishMocking() } @@ -682,7 +703,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { val decoration = mock(DesktopModeWindowDecoration::class.java) whenever( mockDesktopModeWindowDecorFactory.create( - any(), any(), any(), eq(task), any(), any(), any(), any(), any()) + any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any()) ).thenReturn(decoration) decoration.mTaskInfo = task whenever(decoration.isFocused).thenReturn(task.isFocused) 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 d8606093ac5c..412fef30d4fb 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 @@ -82,9 +82,12 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; +import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; 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.OnTaskActionClickListener; @@ -127,6 +130,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Mock private DisplayController mMockDisplayController; @Mock + private SplitScreenController mMockSplitScreenController; + @Mock private ShellTaskOrganizer mMockShellTaskOrganizer; @Mock private Choreographer mMockChoreographer; @@ -165,6 +170,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private SurfaceControl.Transaction mMockTransaction; private StaticMockitoSession mMockitoSession; private TestableContext mTestableContext; + private ShellExecutor mBgExecutor = new TestShellExecutor(); /** Set up run before test class. */ @BeforeClass @@ -392,6 +398,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test + @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR) public void relayout_fullscreenTask_appliesTransactionImmediately() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); @@ -418,6 +425,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test + @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR) public void relayout_fullscreenTask_doesNotCreateViewHostImmediately() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); @@ -429,6 +437,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test + @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR) public void relayout_fullscreenTask_postsViewHostCreation() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); @@ -457,6 +466,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test + @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR) public void relayout_removesExistingHandlerCallback() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); @@ -471,6 +481,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test + @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR) public void close_removesExistingHandlerCallback() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); @@ -656,8 +667,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { ActivityManager.RunningTaskInfo taskInfo, MaximizeMenuFactory maximizeMenuFactory) { final DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext, - mMockDisplayController, mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, - mMockHandler, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer, + mMockDisplayController, mMockSplitScreenController, mMockShellTaskOrganizer, + taskInfo, mMockSurfaceControl, mMockHandler, mBgExecutor, mMockChoreographer, + mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer, SurfaceControl.Builder::new, mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new, mMockSurfaceControlViewHostFactory, maximizeMenuFactory); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt index adda9a688172..e548f8f1eb2a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt @@ -130,7 +130,7 @@ class HandleMenuTest : ShellTestCase() { @EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR) fun testFullscreenMenuUsesSystemViewContainer() { createTaskInfo(WINDOWING_MODE_FULLSCREEN, SPLIT_POSITION_UNDEFINED) - val handleMenu = createAndShowHandleMenu() + val handleMenu = createAndShowHandleMenu(SPLIT_POSITION_UNDEFINED) assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer) // Verify menu is created at coordinates that, when added to WindowManager, // show at the top-center of display. @@ -142,7 +142,7 @@ class HandleMenuTest : ShellTestCase() { @EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR) fun testFreeformMenu_usesViewHostViewContainer() { createTaskInfo(WINDOWING_MODE_FREEFORM, SPLIT_POSITION_UNDEFINED) - handleMenu = createAndShowHandleMenu() + handleMenu = createAndShowHandleMenu(SPLIT_POSITION_UNDEFINED) assertTrue(handleMenu.handleMenuViewContainer is AdditionalViewHostViewContainer) // Verify menu is created near top-left of task. val expected = Point(MENU_START_MARGIN, MENU_TOP_MARGIN) @@ -153,7 +153,7 @@ class HandleMenuTest : ShellTestCase() { @EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR) fun testSplitLeftMenu_usesSystemViewContainer() { createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, SPLIT_POSITION_TOP_OR_LEFT) - handleMenu = createAndShowHandleMenu() + handleMenu = createAndShowHandleMenu(SPLIT_POSITION_TOP_OR_LEFT) assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer) // Verify menu is created at coordinates that, when added to WindowManager, // show at the top-center of split left task. @@ -168,7 +168,7 @@ class HandleMenuTest : ShellTestCase() { @EnableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR) fun testSplitRightMenu_usesSystemViewContainer() { createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, SPLIT_POSITION_BOTTOM_OR_RIGHT) - handleMenu = createAndShowHandleMenu() + handleMenu = createAndShowHandleMenu(SPLIT_POSITION_BOTTOM_OR_RIGHT) assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer) // Verify menu is created at coordinates that, when added to WindowManager, // show at the top-center of split right task. @@ -208,16 +208,30 @@ class HandleMenuTest : ShellTestCase() { } } - private fun createAndShowHandleMenu(): HandleMenu { + private fun createAndShowHandleMenu(splitPosition: Int): HandleMenu { val layoutId = if (mockDesktopWindowDecoration.mTaskInfo.isFreeform) { R.layout.desktop_mode_app_header } else { R.layout.desktop_mode_app_handle } + val captionX = when (mockDesktopWindowDecoration.mTaskInfo.windowingMode) { + WINDOWING_MODE_FULLSCREEN -> (DISPLAY_BOUNDS.width() / 2) - (HANDLE_WIDTH / 2) + WINDOWING_MODE_FREEFORM -> 0 + WINDOWING_MODE_MULTI_WINDOW -> { + if (splitPosition == SPLIT_POSITION_TOP_OR_LEFT) { + (SPLIT_LEFT_BOUNDS.width() / 2) - (HANDLE_WIDTH / 2) + } else { + (SPLIT_RIGHT_BOUNDS.width() / 2) - (HANDLE_WIDTH / 2) + } + } + else -> error("Invalid windowing mode") + } val handleMenu = HandleMenu(mockDesktopWindowDecoration, layoutId, onClickListener, onTouchListener, appIcon, appName, displayController, - splitScreenController, true /* shouldShowWindowingPill */, - true /* shouldShowBrowserPill */, 50 /* captionHeight */) + splitScreenController, shouldShowWindowingPill = true, + shouldShowBrowserPill = true, captionWidth = HANDLE_WIDTH, captionHeight = 50, + captionX = captionX + ) handleMenu.show() return handleMenu } @@ -233,5 +247,6 @@ class HandleMenuTest : ShellTestCase() { private const val MENU_START_MARGIN = 20 private const val MENU_PILL_ELEVATION = 2 private const val MENU_PILL_SPACING_MARGIN = 4 + private const val HANDLE_WIDTH = 80 } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt index d3e996b12e1f..3b490550ab61 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt @@ -68,12 +68,12 @@ class AdditionalSystemViewContainerTest : ShellTestCase() { fun testReleaseView_ViewRemoved() { viewContainer = AdditionalSystemViewContainer( mockContext, - R.layout.desktop_mode_window_decor_handle_menu, TASK_ID, X, Y, WIDTH, - HEIGHT + HEIGHT, + R.layout.desktop_mode_window_decor_handle_menu ) verify(mockWindowManager).addView(eq(mockView), any()) viewContainer.releaseView() |