Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 236bda4b authored by mattsziklay's avatar mattsziklay
Browse files

Implement new drag zone indicator regions.

Updates the drag zone indicators to Regions based on the task's pre-drag
windowing mode. Drag zone targets for freeform tasks have been updated
while other windowing modes remain the same.

Also, changes the coordinates fetched in handleCaptionThroughStatusBar
to raw x/y coordinates. This is due to each split stage having its own
coordinate space. Split left/right indicators from a drag from
splitscreen would not work properly otherwise.

Note these are visually inaccurate; transitions will be tied to these
regions in a future CL.

Bug: 280828642
Test: Manual
Change-Id: Ia58aae83e22d1576ef71172ce78323c0ae538b80
parent b43432bc
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -501,6 +501,17 @@
    fullscreen if dragged until the top bound of the task is within the area. -->
    <dimen name="desktop_mode_transition_area_height">16dp</dimen>

    <!-- The width of the area where a desktop task will transition to fullscreen. -->
    <dimen name="desktop_mode_fullscreen_from_desktop_width">80dp</dimen>

    <!-- The height of the area where a desktop task will transition to fullscreen. -->
    <dimen name="desktop_mode_fullscreen_from_desktop_height">40dp</dimen>

    <!-- The height on the screen where drag to the left or right edge will result in a
    desktop task snapping to split size. The empty space between this and the top is to allow
    for corner drags without transition. -->
    <dimen name="desktop_mode_split_from_desktop_height">100dp</dimen>

    <!-- The acceptable area ratio of fg icon area/bg icon area, i.e. (72 x 72) / (108 x 108) -->
    <item type="dimen" format="float" name="splash_icon_enlarge_foreground_threshold">0.44</item>
    <!-- Scaling factor applied to splash icons without provided background i.e. (192 / 160) -->
+102 −11
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package com.android.wm.shell.desktopmode;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;

import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;

@@ -28,11 +30,13 @@ import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.util.DisplayMetrics;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
@@ -41,6 +45,8 @@ import android.view.WindowManager;
import android.view.WindowlessWindowManager;
import android.view.animation.DecelerateInterpolator;

import androidx.annotation.VisibleForTesting;

import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -93,28 +99,114 @@ public class DesktopModeVisualIndicator {
    /**
     * Based on the coordinates of the current drag event, determine which indicator type we should
     * display, including no visible indicator.
     * TODO(b/280828642): Update drag zones per starting windowing mode.
     */
    IndicatorType updateIndicatorType(PointF inputCoordinates) {
    IndicatorType updateIndicatorType(PointF inputCoordinates, int windowingMode) {
        final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
        // If we are in freeform, we don't want a visible indicator in the "freeform" drag zone.
        IndicatorType result = mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
                ? IndicatorType.NO_INDICATOR : IndicatorType.TO_DESKTOP_INDICATOR;
        int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
                com.android.wm.shell.R.dimen.desktop_mode_transition_area_height);
        int transitionAreaWidth = mContext.getResources().getDimensionPixelSize(
        IndicatorType result = IndicatorType.NO_INDICATOR;
        final int transitionAreaWidth = mContext.getResources().getDimensionPixelSize(
                com.android.wm.shell.R.dimen.desktop_mode_transition_area_width);
        if (inputCoordinates.y <= transitionAreaHeight) {
        // Because drags in freeform use task position for indicator calculation, we need to
        // account for the possibility of the task going off the top of the screen by captionHeight
        final int captionHeight = mContext.getResources().getDimensionPixelSize(
                com.android.wm.shell.R.dimen.desktop_mode_freeform_decor_caption_height);
        final Region fullscreenRegion = calculateFullscreenRegion(layout, windowingMode,
                captionHeight);
        final Region splitLeftRegion = calculateSplitLeftRegion(layout, windowingMode,
                transitionAreaWidth, captionHeight);
        final Region splitRightRegion = calculateSplitRightRegion(layout, windowingMode,
                transitionAreaWidth, captionHeight);
        final Region toDesktopRegion = calculateToDesktopRegion(layout, windowingMode,
                splitLeftRegion, splitRightRegion, fullscreenRegion);
        if (fullscreenRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
            result = IndicatorType.TO_FULLSCREEN_INDICATOR;
        } else if (inputCoordinates.x <= transitionAreaWidth) {
        }
        if (splitLeftRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
            result = IndicatorType.TO_SPLIT_LEFT_INDICATOR;
        } else if (inputCoordinates.x >= layout.width() - transitionAreaWidth) {
        }
        if (splitRightRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
            result = IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
        }
        if (toDesktopRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
            result = IndicatorType.TO_DESKTOP_INDICATOR;
        }
        transitionIndicator(result);
        return result;
    }

    @VisibleForTesting
    Region calculateFullscreenRegion(DisplayLayout layout,
            @WindowConfiguration.WindowingMode int windowingMode, int captionHeight) {
        final Region region = new Region();
        int edgeTransitionHeight = mContext.getResources().getDimensionPixelSize(
                com.android.wm.shell.R.dimen.desktop_mode_transition_area_height);
        // A thin, short Rect at the top of the screen.
        if (windowingMode == WINDOWING_MODE_FREEFORM) {
            int fromFreeformWidth = mContext.getResources().getDimensionPixelSize(
                    com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_width);
            int fromFreeformHeight = mContext.getResources().getDimensionPixelSize(
                    com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height);
            region.union(new Rect((layout.width() / 2) - (fromFreeformWidth / 2),
                    -captionHeight,
                    (layout.width() / 2) + (fromFreeformWidth / 2),
                    fromFreeformHeight));
        }
        // A screen-wide, shorter Rect if the task is in fullscreen or split.
        if (windowingMode == WINDOWING_MODE_FULLSCREEN
                || windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
            region.union(new Rect(0,
                    -captionHeight,
                    layout.width(),
                    edgeTransitionHeight));
        }
        return region;
    }

    @VisibleForTesting
    Region calculateToDesktopRegion(DisplayLayout layout,
            @WindowConfiguration.WindowingMode int windowingMode,
            Region splitLeftRegion, Region splitRightRegion,
            Region toFullscreenRegion) {
        final Region region = new Region();
        // If in desktop, we need no region. Otherwise it's the same for all windowing modes.
        if (windowingMode != WINDOWING_MODE_FREEFORM) {
            region.union(new Rect(0, 0, layout.width(), layout.height()));
            region.op(splitLeftRegion, Region.Op.DIFFERENCE);
            region.op(splitRightRegion, Region.Op.DIFFERENCE);
            region.op(toFullscreenRegion, Region.Op.DIFFERENCE);
        }
        return region;
    }

    @VisibleForTesting
    Region calculateSplitLeftRegion(DisplayLayout layout,
            @WindowConfiguration.WindowingMode int windowingMode,
            int transitionEdgeWidth, int captionHeight) {
        final Region region = new Region();
        // In freeform, keep the top corners clear.
        int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
                ? mContext.getResources().getDimensionPixelSize(
                        com.android.wm.shell.R.dimen.desktop_mode_split_from_desktop_height) :
                -captionHeight;
        region.union(new Rect(0, transitionHeight, transitionEdgeWidth, layout.height()));
        return region;
    }

    @VisibleForTesting
    Region calculateSplitRightRegion(DisplayLayout layout,
            @WindowConfiguration.WindowingMode int windowingMode,
            int transitionEdgeWidth, int captionHeight) {
        final Region region = new Region();
        // In freeform, keep the top corners clear.
        int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
                ? mContext.getResources().getDimensionPixelSize(
                com.android.wm.shell.R.dimen.desktop_mode_split_from_desktop_height) :
                -captionHeight;
        region.union(new Rect(layout.width() - transitionEdgeWidth, transitionHeight,
                layout.width(), layout.height()));
        return region;
    }

    /**
     * Create a fullscreen indicator with no animation
     */
@@ -175,7 +267,6 @@ public class DesktopModeVisualIndicator {
                        mDisplayController.getDisplayLayout(mTaskInfo.displayId));
        animator.start();
        mCurrentType = IndicatorType.NO_INDICATOR;

    }

    /**
+1 −1
Original line number Diff line number Diff line
@@ -893,7 +893,7 @@ class DesktopTasksController(
        }
        // Then, update the indicator type.
        val indicator = visualIndicator ?: return
        indicator.updateIndicatorType(PointF(inputX, taskTop))
        indicator.updateIndicatorType(PointF(inputX, taskTop), taskInfo.windowingMode)
    }

    /**
+3 −3
Original line number Diff line number Diff line
@@ -726,7 +726,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
                    mTransitionDragActive = false;
                    final int statusBarHeight = getStatusBarHeight(
                            relevantDecor.mTaskInfo.displayId);
                    if (ev.getY() > 2 * statusBarHeight) {
                    if (ev.getRawY() > 2 * statusBarHeight) {
                        if (DesktopModeStatus.isEnabled()) {
                            animateToDesktop(relevantDecor, ev);
                        }
@@ -751,10 +751,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
                    mDesktopTasksController.ifPresent(
                            c -> c.updateVisualIndicator(
                                    relevantDecor.mTaskInfo,
                                    relevantDecor.mTaskSurface, ev.getX(), ev.getY()));
                                    relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY()));
                    final int statusBarHeight = getStatusBarHeight(
                            relevantDecor.mTaskInfo.displayId);
                    if (ev.getY() > statusBarHeight) {
                    if (ev.getRawY() > statusBarHeight) {
                        if (mMoveToDesktopAnimator == null) {
                            mMoveToDesktopAnimator = new MoveToDesktopAnimator(
                                    mContext, mDragToDesktopAnimationStartBounds,
+139 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.wm.shell.desktopmode

import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.graphics.Rect
import android.graphics.Region
import android.testing.AndroidTestingRunner
import android.view.SurfaceControl
import androidx.test.filters.SmallTest
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.SyncTransactionQueue
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.kotlin.whenever

@SmallTest
@RunWith(AndroidTestingRunner::class)
class DesktopModeVisualIndicatorTest : ShellTestCase() {
    @Mock private lateinit var taskInfo: RunningTaskInfo
    @Mock private lateinit var syncQueue: SyncTransactionQueue
    @Mock private lateinit var displayController: DisplayController
    @Mock private lateinit var taskSurface: SurfaceControl
    @Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
    @Mock private lateinit var displayLayout: DisplayLayout

    private lateinit var visualIndicator: DesktopModeVisualIndicator

    @Before
    fun setUp() {
        visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo, displayController,
            context, taskSurface, taskDisplayAreaOrganizer)
        whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
        whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height())
    }

    @Test
    fun testFullscreenRegionCalculation() {
        val transitionHeight = context.resources.getDimensionPixelSize(
            R.dimen.desktop_mode_transition_area_height)
        val fromFreeformWidth = mContext.resources.getDimensionPixelSize(
            R.dimen.desktop_mode_fullscreen_from_desktop_width
        )
        val fromFreeformHeight = mContext.resources.getDimensionPixelSize(
            R.dimen.desktop_mode_fullscreen_from_desktop_height
        )
        var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
            WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT)
        assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
        testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
            WINDOWING_MODE_FREEFORM, CAPTION_HEIGHT)
        assertThat(testRegion.bounds).isEqualTo(Rect(
            DISPLAY_BOUNDS.width() / 2 - fromFreeformWidth / 2,
            -50,
            DISPLAY_BOUNDS.width() / 2 + fromFreeformWidth / 2,
            fromFreeformHeight))
        testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
            WINDOWING_MODE_MULTI_WINDOW, CAPTION_HEIGHT)
        assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
    }

    @Test
    fun testSplitLeftRegionCalculation() {
        val transitionHeight = context.resources.getDimensionPixelSize(
            R.dimen.desktop_mode_split_from_desktop_height)
        var testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
            WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
        assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 32, 1600))
        testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
            WINDOWING_MODE_FREEFORM, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
        assertThat(testRegion.bounds).isEqualTo(Rect(0, transitionHeight, 32, 1600))
        testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
            WINDOWING_MODE_MULTI_WINDOW, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
        assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 32, 1600))
    }

    @Test
    fun testSplitRightRegionCalculation() {
        val transitionHeight = context.resources.getDimensionPixelSize(
            R.dimen.desktop_mode_split_from_desktop_height)
        var testRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
            WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
        assertThat(testRegion.bounds).isEqualTo(Rect(2368, -50, 2400, 1600))
        testRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
            WINDOWING_MODE_FREEFORM, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
        assertThat(testRegion.bounds).isEqualTo(Rect(2368, transitionHeight, 2400, 1600))
        testRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
            WINDOWING_MODE_MULTI_WINDOW, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
        assertThat(testRegion.bounds).isEqualTo(Rect(2368, -50, 2400, 1600))
    }

    @Test
    fun testToDesktopRegionCalculation() {
        val fullscreenRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
            WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT)
        val splitLeftRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
            WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
        val splitRightRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
            WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
        val desktopRegion = visualIndicator.calculateToDesktopRegion(displayLayout,
            WINDOWING_MODE_FULLSCREEN, splitLeftRegion, splitRightRegion, fullscreenRegion)
        var testRegion = Region()
        testRegion.union(DISPLAY_BOUNDS)
        testRegion.op(splitLeftRegion, Region.Op.DIFFERENCE)
        testRegion.op(splitRightRegion, Region.Op.DIFFERENCE)
        testRegion.op(fullscreenRegion, Region.Op.DIFFERENCE)
        assertThat(desktopRegion).isEqualTo(testRegion)
    }

    companion object {
        private const val TRANSITION_AREA_WIDTH = 32
        private const val CAPTION_HEIGHT = 50
        private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
    }
}
 No newline at end of file