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

Commit fa863d77 authored by mattsziklay's avatar mattsziklay
Browse files

Open correct handle menu when clicked in split screen.

Fixes a bug where the wrong handle menu would open when clicking caption
handles in split screen mode. Also fixes a crash when the "Close" handle
menu button is clicked in split screen.

Bug: 269660879
Test: atest DesktopModeWindowDecorViewModelTests
Test: Manual; click split screen menus on all sides and confirm they
open correctly.

Change-Id: I605ab470925608a6627a3a77b95d90dd880c05fd
parent fa013ccc
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -194,7 +194,8 @@ public abstract class WMShellModule {
            DisplayController displayController,
            SyncTransactionQueue syncQueue,
            Optional<DesktopModeController> desktopModeController,
            Optional<DesktopTasksController> desktopTasksController) {
            Optional<DesktopTasksController> desktopTasksController,
            Optional<SplitScreenController> splitScreenController) {
        if (DesktopModeStatus.isAnyEnabled()) {
            return new DesktopModeWindowDecorViewModel(
                    context,
@@ -204,7 +205,8 @@ public abstract class WMShellModule {
                    displayController,
                    syncQueue,
                    desktopModeController,
                    desktopTasksController);
                    desktopTasksController,
                    splitScreenController);
        }
        return new CaptionWindowDecorViewModel(
                    context,
+5 −0
Original line number Diff line number Diff line
@@ -422,6 +422,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
        mStageCoordinator.goToFullscreenFromSplit();
    }

    /** Move the specified task to fullscreen, regardless of focus state. */
    public void moveTaskToFullscreen(int taskId) {
        mStageCoordinator.moveTaskToFullscreen(taskId);
    }

    public boolean isLaunchToSplit(TaskInfo taskInfo) {
        return mStageCoordinator.isLaunchToSplit(taskInfo);
    }
+14 −0
Original line number Diff line number Diff line
@@ -2389,6 +2389,20 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT);
    }

    /** Move the specified task to fullscreen, regardless of focus state. */
    public void moveTaskToFullscreen(int taskId) {
        boolean leftOrTop;
        if (mMainStage.containsTask(taskId)) {
            leftOrTop = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
        } else if (mSideStage.containsTask(taskId)) {
            leftOrTop = (mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT);
        } else {
            return;
        }
        mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT);

    }

    boolean isLaunchToSplit(TaskInfo taskInfo) {
        return getActivateSplitPosition(taskInfo) != SPLIT_POSITION_UNDEFINED;
    }
+71 −24
Original line number Diff line number Diff line
@@ -20,10 +20,14 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;

import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;

import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
import android.content.Context;
import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.Looper;
@@ -37,7 +41,6 @@ import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;

import androidx.annotation.Nullable;

@@ -50,6 +53,7 @@ import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.splitscreen.SplitScreenController;

import java.util.Optional;

@@ -80,6 +84,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
    private final InputMonitorFactory mInputMonitorFactory;
    private TaskOperations mTaskOperations;

    private Optional<SplitScreenController> mSplitScreenController;

    public DesktopModeWindowDecorViewModel(
            Context context,
            Handler mainHandler,
@@ -88,7 +94,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
            DisplayController displayController,
            SyncTransactionQueue syncQueue,
            Optional<DesktopModeController> desktopModeController,
            Optional<DesktopTasksController> desktopTasksController) {
            Optional<DesktopTasksController> desktopTasksController,
            Optional<SplitScreenController> splitScreenController) {
        this(
                context,
                mainHandler,
@@ -98,6 +105,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
                syncQueue,
                desktopModeController,
                desktopTasksController,
                splitScreenController,
                new DesktopModeWindowDecoration.Factory(),
                new InputMonitorFactory());
    }
@@ -112,6 +120,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
            SyncTransactionQueue syncQueue,
            Optional<DesktopModeController> desktopModeController,
            Optional<DesktopTasksController> desktopTasksController,
            Optional<SplitScreenController> splitScreenController,
            DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
            InputMonitorFactory inputMonitorFactory) {
        mContext = context;
@@ -120,6 +129,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
        mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
        mTaskOrganizer = taskOrganizer;
        mDisplayController = displayController;
        mSplitScreenController = splitScreenController;
        mSyncQueue = syncQueue;
        mDesktopModeController = desktopModeController;
        mDesktopTasksController = desktopTasksController;
@@ -230,6 +240,15 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
            final int id = v.getId();
            if (id == R.id.close_window || id == R.id.close_button) {
                mTaskOperations.closeTask(mTaskToken);
                if (mSplitScreenController.isPresent()
                        && mSplitScreenController.get().isSplitScreenVisible()) {
                    int remainingTaskPosition = mTaskId == mSplitScreenController.get()
                            .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT).taskId
                            ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
                    ActivityManager.RunningTaskInfo remainingTask = mSplitScreenController.get()
                            .getTaskInfo(remainingTaskPosition);
                    mSplitScreenController.get().moveTaskToFullscreen(remainingTask.taskId);
                }
            } else if (id == R.id.back_button) {
                mTaskOperations.injectBackKey();
            } else if (id == R.id.caption_handle) {
@@ -261,9 +280,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
                    if (taskInfo.isFocused) {
                        return mDragDetector.isDragEvent();
                    }
                    final WindowContainerTransaction wct = new WindowContainerTransaction();
                    wct.reorder(mTaskToken, true /* onTop */);
                    mSyncQueue.queue(wct);
                    return false;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
@@ -401,14 +417,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
     * @param ev the {@link MotionEvent} received by {@link EventReceiver}
     */
    private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
        final DesktopModeWindowDecoration relevantDecor = getRelevantWindowDecor(ev);
        if (DesktopModeStatus.isProto2Enabled()) {
            final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
            if (focusedDecor == null
                    || focusedDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
                handleCaptionThroughStatusBar(ev);
            if (relevantDecor == null
                    || relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
                handleCaptionThroughStatusBar(ev, relevantDecor);
            }
        }
        handleEventOutsideFocusedCaption(ev);
        handleEventOutsideFocusedCaption(ev, relevantDecor);
        // Prevent status bar from reacting to a caption drag.
        if (DesktopModeStatus.isProto2Enabled()) {
            if (mTransitionDragActive) {
@@ -422,16 +438,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
    }

    // If an UP/CANCEL action is received outside of caption bounds, turn off handle menu
    private void handleEventOutsideFocusedCaption(MotionEvent ev) {
    private void handleEventOutsideFocusedCaption(MotionEvent ev,
            DesktopModeWindowDecoration relevantDecor) {
        final int action = ev.getActionMasked();
        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
            final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
            if (focusedDecor == null) {
            if (relevantDecor == null) {
                return;
            }

            if (!mTransitionDragActive) {
                focusedDecor.closeHandleMenuIfNeeded(ev);
                relevantDecor.closeHandleMenuIfNeeded(ev);
            }
        }
    }
@@ -441,39 +457,38 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
     * Perform caption actions if not able to through normal means.
     * Turn on desktop mode if handle is dragged below status bar.
     */
    private void handleCaptionThroughStatusBar(MotionEvent ev) {
    private void handleCaptionThroughStatusBar(MotionEvent ev,
            DesktopModeWindowDecoration relevantDecor) {
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_DOWN: {
                // Begin drag through status bar if applicable.
                final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
                if (focusedDecor != null) {
                if (relevantDecor != null) {
                    boolean dragFromStatusBarAllowed = false;
                    if (DesktopModeStatus.isProto2Enabled()) {
                        // In proto2 any full screen task can be dragged to freeform
                        dragFromStatusBarAllowed = focusedDecor.mTaskInfo.getWindowingMode()
                        dragFromStatusBarAllowed = relevantDecor.mTaskInfo.getWindowingMode()
                                == WINDOWING_MODE_FULLSCREEN;
                    }

                    if (dragFromStatusBarAllowed && focusedDecor.checkTouchEventInHandle(ev)) {
                    if (dragFromStatusBarAllowed && relevantDecor.checkTouchEventInHandle(ev)) {
                        mTransitionDragActive = true;
                    }
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
                if (focusedDecor == null) {
                if (relevantDecor == null) {
                    mTransitionDragActive = false;
                    return;
                }
                if (mTransitionDragActive) {
                    mTransitionDragActive = false;
                    final int statusBarHeight = mDisplayController
                            .getDisplayLayout(focusedDecor.mTaskInfo.displayId).stableInsets().top;
                            .getDisplayLayout(relevantDecor.mTaskInfo.displayId).stableInsets().top;
                    if (ev.getY() > statusBarHeight) {
                        if (DesktopModeStatus.isProto2Enabled()) {
                            mDesktopTasksController.ifPresent(
                                    c -> c.moveToDesktop(focusedDecor.mTaskInfo));
                                    c -> c.moveToDesktop(relevantDecor.mTaskInfo));
                        } else if (DesktopModeStatus.isProto1Enabled()) {
                            mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
                        }
@@ -481,7 +496,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
                        return;
                    }
                }
                focusedDecor.checkClickEvent(ev);
                relevantDecor.checkClickEvent(ev);
                break;
            }
            case MotionEvent.ACTION_CANCEL: {
@@ -490,6 +505,38 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
        }
    }

    @Nullable
    private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
        if (mSplitScreenController.isPresent()
                && mSplitScreenController.get().isSplitScreenVisible()) {
            // We can't look at focused task here as only one task will have focus.
            return getSplitScreenDecor(ev);
        } else {
            return getFocusedDecor();
        }
    }

    @Nullable
    private DesktopModeWindowDecoration getSplitScreenDecor(MotionEvent ev) {
        ActivityManager.RunningTaskInfo topOrLeftTask =
                mSplitScreenController.get().getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
        ActivityManager.RunningTaskInfo bottomOrRightTask =
                mSplitScreenController.get().getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
        if (topOrLeftTask != null && topOrLeftTask.getConfiguration()
                .windowConfiguration.getBounds().contains((int) ev.getX(), (int) ev.getY())) {
            return mWindowDecorByTaskId.get(topOrLeftTask.taskId);
        } else if (bottomOrRightTask != null && bottomOrRightTask.getConfiguration()
                .windowConfiguration.getBounds().contains((int) ev.getX(), (int) ev.getY())) {
            Rect bottomOrRightBounds = bottomOrRightTask.getConfiguration().windowConfiguration
                    .getBounds();
            ev.offsetLocation(-bottomOrRightBounds.left, -bottomOrRightBounds.top);
            return mWindowDecorByTaskId.get(bottomOrRightTask.taskId);
        } else {
            return null;
        }

    }

    @Nullable
    private DesktopModeWindowDecoration getFocusedDecor() {
        final int size = mWindowDecorByTaskId.size();
+3 −0
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController;

import org.junit.Before;
import org.junit.Test;
@@ -73,6 +74,7 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase {
    @Mock private Choreographer mMainChoreographer;
    @Mock private ShellTaskOrganizer mTaskOrganizer;
    @Mock private DisplayController mDisplayController;
    @Mock private SplitScreenController mSplitScreenController;
    @Mock private SyncTransactionQueue mSyncQueue;
    @Mock private DesktopModeController mDesktopModeController;
    @Mock private DesktopTasksController mDesktopTasksController;
@@ -98,6 +100,7 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase {
                mSyncQueue,
                Optional.of(mDesktopModeController),
                Optional.of(mDesktopTasksController),
                Optional.of(mSplitScreenController),
                mDesktopModeWindowDecorFactory,
                mMockInputMonitorFactory
            );