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

Commit bf8019c0 authored by Matt Sziklay's avatar Matt Sziklay Committed by Android (Google) Code Review
Browse files

Merge "Drag task to left or right edge to enter split select." into udc-qpr-dev

parents 85220cf2 a5d9247b
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -432,6 +432,10 @@

    <dimen name="freeform_resize_corner">44dp</dimen>

    <!-- The width of the area at the sides of the screen where a freeform task will transition to
    split select if dragged until the touch input is within the range. -->
    <dimen name="desktop_mode_transition_area_width">32dp</dimen>

    <!-- The height of the area at the top of the screen where a freeform task will transition to
    fullscreen if dragged until the top bound of the task is within the area. -->
    <dimen name="desktop_mode_transition_area_height">16dp</dimen>
+144 −50
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.SurfaceControl;
@@ -47,6 +48,15 @@ import com.android.wm.shell.common.SyncTransactionQueue;
 * Animated visual indicator for Desktop Mode windowing transitions.
 */
public class DesktopModeVisualIndicator {
    public static final int INVALID_INDICATOR = -1;
    /** Indicates impending transition into desktop mode */
    public static final int TO_DESKTOP_INDICATOR = 1;
    /** Indicates impending transition into fullscreen */
    public static final int TO_FULLSCREEN_INDICATOR = 2;
    /** Indicates impending transition into split select on the left side */
    public static final int TO_SPLIT_LEFT_INDICATOR = 3;
    /** Indicates impending transition into split select on the right side */
    public static final int TO_SPLIT_RIGHT_INDICATOR = 4;

    private final Context mContext;
    private final DisplayController mDisplayController;
@@ -54,6 +64,7 @@ public class DesktopModeVisualIndicator {
    private final RootTaskDisplayAreaOrganizer mRootTdaOrganizer;
    private final ActivityManager.RunningTaskInfo mTaskInfo;
    private final SurfaceControl mTaskSurface;
    private final Rect mIndicatorRange = new Rect();
    private SurfaceControl mLeash;

    private final SyncTransactionQueue mSyncQueue;
@@ -61,11 +72,12 @@ public class DesktopModeVisualIndicator {

    private View mView;
    private boolean mIsFullscreen;
    private int mType;

    public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue,
            ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController,
            Context context, SurfaceControl taskSurface, ShellTaskOrganizer taskOrganizer,
            RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer) {
            RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer, int type) {
        mSyncQueue = syncQueue;
        mTaskInfo = taskInfo;
        mDisplayController = displayController;
@@ -73,9 +85,63 @@ public class DesktopModeVisualIndicator {
        mTaskSurface = taskSurface;
        mTaskOrganizer = taskOrganizer;
        mRootTdaOrganizer = taskDisplayAreaOrganizer;
        mType = type;
        defineIndicatorRange();
        createView();
    }

    /**
     * If an indicator is warranted based on the input and task bounds, return the type of
     * indicator that should be created.
     */
    public static int determineIndicatorType(PointF inputCoordinates, Rect taskBounds,
            DisplayLayout layout, Context context) {
        int transitionAreaHeight = context.getResources().getDimensionPixelSize(
                com.android.wm.shell.R.dimen.desktop_mode_transition_area_height);
        int transitionAreaWidth = context.getResources().getDimensionPixelSize(
                com.android.wm.shell.R.dimen.desktop_mode_transition_area_width);
        if (taskBounds.top <= transitionAreaHeight) return TO_FULLSCREEN_INDICATOR;
        if (inputCoordinates.x <= transitionAreaWidth) return TO_SPLIT_LEFT_INDICATOR;
        if (inputCoordinates.x >= layout.width() - transitionAreaWidth) {
            return TO_SPLIT_RIGHT_INDICATOR;
        }
        return INVALID_INDICATOR;
    }

    /**
     * Determine range of inputs that will keep this indicator displaying.
     */
    private void defineIndicatorRange() {
        DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
        int captionHeight = mContext.getResources().getDimensionPixelSize(
                com.android.wm.shell.R.dimen.freeform_decor_caption_height);
        int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
                com.android.wm.shell.R.dimen.desktop_mode_transition_area_height);
        int transitionAreaWidth = mContext.getResources().getDimensionPixelSize(
                com.android.wm.shell.R.dimen.desktop_mode_transition_area_width);
        switch (mType) {
            case TO_DESKTOP_INDICATOR:
                // TO_DESKTOP indicator is only dismissed on release; entire display is valid.
                mIndicatorRange.set(0, 0, layout.width(), layout.height());
                break;
            case TO_FULLSCREEN_INDICATOR:
                // If drag results in caption going above the top edge of the display, we still
                // want to transition to fullscreen.
                mIndicatorRange.set(0, -captionHeight, layout.width(), transitionAreaHeight);
                break;
            case TO_SPLIT_LEFT_INDICATOR:
                mIndicatorRange.set(0, transitionAreaHeight, transitionAreaWidth, layout.height());
                break;
            case TO_SPLIT_RIGHT_INDICATOR:
                mIndicatorRange.set(layout.width() - transitionAreaWidth, transitionAreaHeight,
                        layout.width(), layout.height());
                break;
            default:
                break;
        }
    }


    /**
     * Create a fullscreen indicator with no animation
     */
@@ -85,11 +151,30 @@ public class DesktopModeVisualIndicator {
        final DisplayMetrics metrics = resources.getDisplayMetrics();
        final int screenWidth = metrics.widthPixels;
        final int screenHeight = metrics.heightPixels;

        mView = new View(mContext);
        final SurfaceControl.Builder builder = new SurfaceControl.Builder();
        mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder);
        String description;
        switch (mType) {
            case TO_DESKTOP_INDICATOR:
                description = "Desktop indicator";
                break;
            case TO_FULLSCREEN_INDICATOR:
                description = "Fullscreen indicator";
                break;
            case TO_SPLIT_LEFT_INDICATOR:
                description = "Split Left indicator";
                break;
            case TO_SPLIT_RIGHT_INDICATOR:
                description = "Split Right indicator";
                break;
            default:
                description = "Invalid indicator";
                break;
        }
        mLeash = builder
                .setName("Fullscreen Indicator")
                .setName(description)
                .setContainerLayer()
                .build();
        t.show(mLeash);
@@ -97,14 +182,14 @@ public class DesktopModeVisualIndicator {
                new WindowManager.LayoutParams(screenWidth, screenHeight,
                        WindowManager.LayoutParams.TYPE_APPLICATION,
                        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
        lp.setTitle("Fullscreen indicator for Task=" + mTaskInfo.taskId);
        lp.setTitle(description + " for Task=" + mTaskInfo.taskId);
        lp.setTrustedOverlay();
        final WindowlessWindowManager windowManager = new WindowlessWindowManager(
                mTaskInfo.configuration, mLeash,
                null /* hostInputToken */);
        mViewHost = new SurfaceControlViewHost(mContext,
                mDisplayController.getDisplay(mTaskInfo.displayId), windowManager,
                "FullscreenVisualIndicator");
                "DesktopModeVisualIndicator");
        mViewHost.setView(mView, lp);
        // We want this indicator to be behind the dragged task, but in front of all others.
        t.setRelativeLayer(mLeash, mTaskSurface, -1);
@@ -116,24 +201,13 @@ public class DesktopModeVisualIndicator {
    }

    /**
     * Create fullscreen indicator and fades it in.
     */
    public void createFullscreenIndicator() {
        mIsFullscreen = true;
        mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background);
        final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFullscreenAnimator(
                mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
        animator.start();
    }

    /**
     * Create a fullscreen indicator. Animator fades it in while expanding the bounds outwards.
     * Create an indicator. Animator fades it in while expanding the bounds outwards.
     */
    public void createFullscreenIndicatorWithAnimatedBounds() {
        mIsFullscreen = true;
    public void createIndicatorWithAnimatedBounds() {
        mIsFullscreen = mType == TO_FULLSCREEN_INDICATOR;
        mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background);
        final VisualIndicatorAnimator animator = VisualIndicatorAnimator
                .toFullscreenAnimatorWithAnimatedBounds(mView,
                .animateBounds(mView, mType,
                        mDisplayController.getDisplayLayout(mTaskInfo.displayId));
        animator.start();
    }
@@ -143,6 +217,7 @@ public class DesktopModeVisualIndicator {
     */
    public void transitionFullscreenIndicatorToFreeform() {
        mIsFullscreen = false;
        mType = TO_DESKTOP_INDICATOR;
        final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFreeformAnimator(
                mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
        animator.start();
@@ -153,12 +228,21 @@ public class DesktopModeVisualIndicator {
     */
    public void transitionFreeformIndicatorToFullscreen() {
        mIsFullscreen = true;
        mType = TO_FULLSCREEN_INDICATOR;
        final VisualIndicatorAnimator animator =
                VisualIndicatorAnimator.toFullscreenAnimatorWithAnimatedBounds(
                mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
        animator.start();
    }

    /**
     * Determine if a MotionEvent is in the same range that enabled the indicator.
     * Used to dismiss the indicator when a transition will no longer result from releasing.
     */
    public boolean eventOutsideRange(float x, float y) {
        return !mIndicatorRange.contains((int) x, (int) y);
    }

    /**
     * Release the indicator and its components when it is no longer needed.
     */
@@ -210,32 +294,45 @@ public class DesktopModeVisualIndicator {
         * @param view the view for this indicator
         * @param displayLayout information about the display the transitioning task is currently on
         */
        public static VisualIndicatorAnimator toFullscreenAnimator(@NonNull View view,
                @NonNull DisplayLayout displayLayout) {
            final Rect bounds = getMaxBounds(displayLayout);
        public static VisualIndicatorAnimator toFullscreenAnimatorWithAnimatedBounds(
                @NonNull View view, @NonNull DisplayLayout displayLayout) {
            final int padding = displayLayout.stableInsets().top;
            Rect startBounds = new Rect(padding, padding,
                    displayLayout.width() - padding, displayLayout.height() - padding);
            view.getBackground().setBounds(startBounds);

            final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
                    view, bounds, bounds);
                    view, startBounds, getMaxBounds(startBounds));
            animator.setInterpolator(new DecelerateInterpolator());
            setupIndicatorAnimation(animator);
            return animator;
        }


        /**
         * Create animator for visual indicator of fullscreen transition
         *
         * @param view the view for this indicator
         * @param displayLayout information about the display the transitioning task is currently on
         */
        public static VisualIndicatorAnimator toFullscreenAnimatorWithAnimatedBounds(
                @NonNull View view, @NonNull DisplayLayout displayLayout) {
        public static VisualIndicatorAnimator animateBounds(
                @NonNull View view, int type, @NonNull DisplayLayout displayLayout) {
            final int padding = displayLayout.stableInsets().top;
            Rect startBounds = new Rect(padding, padding,
                    displayLayout.width() - padding, displayLayout.height() - padding);
            Rect startBounds = new Rect();
            switch (type) {
                case TO_FULLSCREEN_INDICATOR:
                    startBounds.set(padding, padding,
                            displayLayout.width() - padding,
                            displayLayout.height() - padding);
                    break;
                case TO_SPLIT_LEFT_INDICATOR:
                    startBounds.set(padding, padding,
                            displayLayout.width() / 2 - padding,
                            displayLayout.height() - padding);
                    break;
                case TO_SPLIT_RIGHT_INDICATOR:
                    startBounds.set(displayLayout.width() / 2 + padding, padding,
                            displayLayout.width() - padding,
                            displayLayout.height() - padding);
                    break;
            }
            view.getBackground().setBounds(startBounds);

            final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
                    view, startBounds, getMaxBounds(displayLayout));
                    view, startBounds, getMaxBounds(startBounds));
            animator.setInterpolator(new DecelerateInterpolator());
            setupIndicatorAnimation(animator);
            return animator;
@@ -252,12 +349,13 @@ public class DesktopModeVisualIndicator {
            final float adjustmentPercentage = 1f - FINAL_FREEFORM_SCALE;
            final int width = displayLayout.width();
            final int height = displayLayout.height();
            Rect startBounds = new Rect(0, 0, width, height);
            Rect endBounds = new Rect((int) (adjustmentPercentage * width / 2),
                    (int) (adjustmentPercentage * height / 2),
                    (int) (displayLayout.width() - (adjustmentPercentage * width / 2)),
                    (int) (displayLayout.height() - (adjustmentPercentage * height / 2)));
            final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
                    view, getMaxBounds(displayLayout), endBounds);
                    view, startBounds, endBounds);
            animator.setInterpolator(new DecelerateInterpolator());
            setupIndicatorAnimation(animator);
            return animator;
@@ -310,21 +408,17 @@ public class DesktopModeVisualIndicator {
        }

        /**
         * Return the max bounds of a fullscreen indicator
         * Return the max bounds of a visual indicator
         */
        private static Rect getMaxBounds(@NonNull DisplayLayout displayLayout) {
            final int padding = displayLayout.stableInsets().top;
            final int width = displayLayout.width() - 2 * padding;
            final int height = displayLayout.height() - 2 * padding;
            Rect endBounds = new Rect((int) (padding
                            - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * width)),
                    (int) (padding
                            - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * height)),
                    (int) (displayLayout.width() - padding
                            + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * width)),
                    (int) (displayLayout.height() - padding
                            + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * height)));
            return endBounds;
        private static Rect getMaxBounds(Rect startBounds) {
            return new Rect((int) (startBounds.left
                            - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.width())),
                    (int) (startBounds.top
                            - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.height())),
                    (int) (startBounds.right
                            + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.width())),
                    (int) (startBounds.bottom
                            + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.height())));
        }
    }
}
+63 −23
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import android.app.WindowConfiguration.WindowingMode
import android.content.Context
import android.content.res.TypedArray
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.Region
import android.os.IBinder
@@ -55,7 +56,10 @@ import com.android.wm.shell.common.SingleInstanceRemoteListener
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.common.annotations.ExternalThread
import com.android.wm.shell.common.annotations.ShellMainThread
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.TO_DESKTOP_INDICATOR
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
@@ -107,6 +111,10 @@ class DesktopTasksController(
        get() = context.resources.getDimensionPixelSize(
            com.android.wm.shell.R.dimen.desktop_mode_transition_area_height)

    private val transitionAreaWidth
        get() = context.resources.getDimensionPixelSize(
            com.android.wm.shell.R.dimen.desktop_mode_transition_area_width)

    // This is public to avoid cyclic dependency; it is set by SplitScreenController
    lateinit var splitScreenController: SplitScreenController

@@ -755,7 +763,8 @@ class DesktopTasksController(
        ) {
            val wct = WindowContainerTransaction()
            addMoveToSplitChanges(wct, taskInfo)
            splitScreenController.requestEnterSplitSelect(taskInfo, wct)
            splitScreenController.requestEnterSplitSelect(taskInfo, wct,
                SPLIT_POSITION_BOTTOM_OR_RIGHT, taskInfo.configuration.windowConfiguration.bounds)
        }
    }

@@ -779,25 +788,36 @@ class DesktopTasksController(

    /**
     * Perform checks required on drag move. Create/release fullscreen indicator as needed.
     * Different sources for x and y coordinates are used due to different needs for each:
     * We want split transitions to be based on input coordinates but fullscreen transition
     * to be based on task edge coordinate.
     *
     * @param taskInfo the task being dragged.
     * @param taskSurface SurfaceControl of dragged task.
     * @param y coordinate of dragged task. Used for checks against status bar height.
     * @param inputCoordinate coordinates of input. Used for checks against left/right edge of screen.
     * @param taskBounds bounds of dragged task. Used for checks against status bar height.
     */
    fun onDragPositioningMove(
        taskInfo: RunningTaskInfo,
        taskSurface: SurfaceControl,
            y: Float
        inputCoordinate: PointF,
        taskBounds: Rect
    ) {
        if (taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
            if (y <= transitionAreaHeight && visualIndicator == null) {
                visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
        val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
        if (taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) return
        var type = DesktopModeVisualIndicator.determineIndicatorType(inputCoordinate,
            taskBounds, displayLayout, context)
        if (type != DesktopModeVisualIndicator.INVALID_INDICATOR && visualIndicator == null) {
            visualIndicator = DesktopModeVisualIndicator(
                syncQueue, taskInfo,
                displayController, context, taskSurface, shellTaskOrganizer,
                        rootTaskDisplayAreaOrganizer)
                visualIndicator?.createFullscreenIndicatorWithAnimatedBounds()
            } else if (y > transitionAreaHeight && visualIndicator != null) {
                releaseVisualIndicator()
                rootTaskDisplayAreaOrganizer, type)
            visualIndicator?.createIndicatorWithAnimatedBounds()
            return
        }
        if (visualIndicator?.eventOutsideRange(inputCoordinate.x,
                taskBounds.top.toFloat()) == true) {
            releaseVisualIndicator()
        }
    }

@@ -806,19 +826,39 @@ class DesktopTasksController(
     *
     * @param taskInfo the task being dragged.
     * @param position position of surface when drag ends.
     * @param y the Y position of the top edge of the task
     * @param inputCoordinate the coordinates of the motion event
     * @param taskBounds the updated bounds of the task being dragged.
     * @param windowDecor the window decoration for the task being dragged
     */
    fun onDragPositioningEnd(
        taskInfo: RunningTaskInfo,
        position: Point,
            y: Float,
        inputCoordinate: PointF,
        taskBounds: Rect,
        windowDecor: DesktopModeWindowDecoration
    ) {
        if (y <= transitionAreaHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
        if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
            return
        }
        if (taskBounds.top <= transitionAreaHeight) {
            windowDecor.incrementRelayoutBlock()
            moveToFullscreenWithAnimation(taskInfo, position)
        }
        if (inputCoordinate.x <= transitionAreaWidth) {
            releaseVisualIndicator()
            var wct = WindowContainerTransaction()
            addMoveToSplitChanges(wct, taskInfo)
            splitScreenController.requestEnterSplitSelect(taskInfo, wct,
                SPLIT_POSITION_TOP_OR_LEFT, taskBounds)
        }
        if (inputCoordinate.x >= (displayController.getDisplayLayout(taskInfo.displayId)?.width()
            ?.minus(transitionAreaWidth) ?: return)) {
            releaseVisualIndicator()
            var wct = WindowContainerTransaction()
            addMoveToSplitChanges(wct, taskInfo)
            splitScreenController.requestEnterSplitSelect(taskInfo, wct,
                SPLIT_POSITION_BOTTOM_OR_RIGHT, taskBounds)
        }
    }

    /**
@@ -842,8 +882,8 @@ class DesktopTasksController(
        if (visualIndicator == null) {
            visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
                    displayController, context, taskSurface, shellTaskOrganizer,
                    rootTaskDisplayAreaOrganizer)
            visualIndicator?.createFullscreenIndicator()
                    rootTaskDisplayAreaOrganizer, TO_DESKTOP_INDICATOR)
            visualIndicator?.createIndicatorWithAnimatedBounds()
        }
        val indicator = visualIndicator ?: return
        if (y >= getFreeformTransitionStatusBarDragThreshold(taskInfo)) {
+2 −2
Original line number Diff line number Diff line
@@ -17,7 +17,7 @@
package com.android.wm.shell.splitscreen;

import android.app.ActivityManager.RunningTaskInfo;

import android.graphics.Rect;
/**
 * Listener interface that Launcher attaches to SystemUI to get split-select callbacks.
 */
@@ -25,5 +25,5 @@ interface ISplitSelectListener {
    /**
     * Called when a task requests to enter split select
     */
    boolean onRequestSplitSelect(in RunningTaskInfo taskInfo);
    boolean onRequestSplitSelect(in RunningTaskInfo taskInfo, int splitPosition, in Rect taskBounds);
}
 No newline at end of file
+2 −1
Original line number Diff line number Diff line
@@ -66,7 +66,8 @@ public interface SplitScreen {

    /** Callback interface for listening to requests to enter split select */
    interface SplitSelectListener {
        default boolean onRequestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo) {
        default boolean onRequestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
                int splitPosition, Rect taskBounds) {
            return false;
        }
    }
Loading