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

Commit e29f11f8 authored by Ats Jenk's avatar Ats Jenk
Browse files

Implement move to desktop and fullscreen

Add methods to move a task to fullscreen of freeform in proto2
controller.
Use these methods in window captions to move a task to desktop and back
to fullscreen.

Bug: 261234251
Test: atest DesktopTasksControllerTest
Change-Id: I9907038ad3882240b1f70b6f0c29c677cb4f4a51
parent 99156bd0
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -190,7 +190,8 @@ public abstract class WMShellModule {
            ShellTaskOrganizer taskOrganizer,
            DisplayController displayController,
            SyncTransactionQueue syncQueue,
            Optional<DesktopModeController> desktopModeController) {
            Optional<DesktopModeController> desktopModeController,
            Optional<DesktopTasksController> desktopTasksController) {
        return new CaptionWindowDecorViewModel(
                    context,
                    mainHandler,
@@ -198,7 +199,8 @@ public abstract class WMShellModule {
                    taskOrganizer,
                    displayController,
                    syncQueue,
                    desktopModeController);
                    desktopModeController,
                    desktopTasksController);
    }

    //
+57 −0
Original line number Diff line number Diff line
@@ -16,7 +16,11 @@

package com.android.wm.shell.desktopmode

import android.app.ActivityManager
import android.app.WindowConfiguration
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.WindowingMode
import android.content.Context
import android.view.WindowManager
import android.window.WindowContainerTransaction
@@ -84,6 +88,59 @@ class DesktopTasksController(
        }
    }

    /** Move a task with given `taskId` to desktop */
    fun moveToDesktop(taskId: Int) {
        shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToDesktop(task) }
    }

    /** Move a task to desktop */
    fun moveToDesktop(task: ActivityManager.RunningTaskInfo) {
        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToDesktop: %d", task.taskId)

        val wct = WindowContainerTransaction()
        // Bring other apps to front first
        bringDesktopAppsToFront(wct)

        wct.setWindowingMode(task.getToken(), WindowConfiguration.WINDOWING_MODE_FREEFORM)
        wct.reorder(task.getToken(), true /* onTop */)

        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
            transitions.startTransition(WindowManager.TRANSIT_CHANGE, wct, null /* handler */)
        } else {
            shellTaskOrganizer.applyTransaction(wct)
        }
    }

    /** Move a task with given `taskId` to fullscreen */
    fun moveToFullscreen(taskId: Int) {
        shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToFullscreen(task) }
    }

    /** Move a task to fullscreen */
    fun moveToFullscreen(task: ActivityManager.RunningTaskInfo) {
        ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToFullscreen: %d", task.taskId)

        val wct = WindowContainerTransaction()
        wct.setWindowingMode(task.getToken(), WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
        wct.setBounds(task.getToken(), null)
        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
            transitions.startTransition(WindowManager.TRANSIT_CHANGE, wct, null /* handler */)
        } else {
            shellTaskOrganizer.applyTransaction(wct)
        }
    }

    /**
     * Get windowing move for a given `taskId`
     *
     * @return [WindowingMode] for the task or [WINDOWING_MODE_UNDEFINED] if task is not found
     */
    @WindowingMode
    fun getTaskWindowingMode(taskId: Int): Int {
        return shellTaskOrganizer.getRunningTaskInfo(taskId)?.windowingMode
            ?: WINDOWING_MODE_UNDEFINED
    }

    private fun bringDesktopAppsToFront(wct: WindowContainerTransaction) {
        val activeTasks = desktopModeTaskRepository.getActiveTasks()

+70 −15
Original line number Diff line number Diff line
@@ -52,6 +52,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.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.transition.Transitions;

@@ -76,6 +77,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
    private final SyncTransactionQueue mSyncQueue;
    private FreeformTaskTransitionStarter mTransitionStarter;
    private Optional<DesktopModeController> mDesktopModeController;
    private Optional<DesktopTasksController> mDesktopTasksController;
    private boolean mTransitionDragActive;

    private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
@@ -91,7 +93,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
            ShellTaskOrganizer taskOrganizer,
            DisplayController displayController,
            SyncTransactionQueue syncQueue,
            Optional<DesktopModeController> desktopModeController) {
            Optional<DesktopModeController> desktopModeController,
            Optional<DesktopTasksController> desktopTasksController) {
        this(
                context,
                mainHandler,
@@ -100,6 +103,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
                displayController,
                syncQueue,
                desktopModeController,
                desktopTasksController,
                new CaptionWindowDecoration.Factory(),
                InputManager::getInstance);
    }
@@ -112,6 +116,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
            DisplayController displayController,
            SyncTransactionQueue syncQueue,
            Optional<DesktopModeController> desktopModeController,
            Optional<DesktopTasksController> desktopTasksController,
            CaptionWindowDecoration.Factory captionWindowDecorFactory,
            Supplier<InputManager> inputManagerSupplier) {

@@ -123,6 +128,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
        mDisplayController = displayController;
        mSyncQueue = syncQueue;
        mDesktopModeController = desktopModeController;
        mDesktopTasksController = desktopTasksController;

        mCaptionWindowDecorFactory = captionWindowDecorFactory;
        mInputManagerSupplier = inputManagerSupplier;
@@ -248,11 +254,13 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
                decoration.createHandleMenu();
            } else if (id == R.id.desktop_button) {
                mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
                mDesktopTasksController.ifPresent(c -> c.moveToDesktop(mTaskId));
                decoration.closeHandleMenu();
            } else if (id == R.id.fullscreen_button) {
                mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
                mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
                decoration.closeHandleMenu();
                decoration.setButtonVisibility();
                decoration.setButtonVisibility(false);
            }
        }

@@ -305,8 +313,13 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
         */
        private void handleEventForMove(MotionEvent e) {
            RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
            if (mDesktopModeController.isPresent()
                    && mDesktopModeController.get().getDisplayAreaWindowingMode(taskInfo.displayId)
            if (DesktopModeStatus.isProto2Enabled()
                    && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
                return;
            }
            if (DesktopModeStatus.isProto1Enabled() && mDesktopModeController.isPresent()
                    && mDesktopModeController.get().getDisplayAreaWindowingMode(
                    taskInfo.displayId)
                    == WINDOWING_MODE_FULLSCREEN) {
                return;
            }
@@ -330,9 +343,20 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
                            .stableInsets().top;
                    mDragResizeCallback.onDragResizeEnd(
                            e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                    if (e.getRawY(dragPointerIdx) <= statusBarHeight
                            && DesktopModeStatus.isActive(mContext)) {
                        mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
                    if (e.getRawY(dragPointerIdx) <= statusBarHeight) {
                        if (DesktopModeStatus.isProto2Enabled()) {
                            if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
                                // Switch a single task to fullscreen
                                mDesktopTasksController.ifPresent(
                                        c -> c.moveToFullscreen(taskInfo));
                            }
                        } else if (DesktopModeStatus.isProto1Enabled()) {
                            if (DesktopModeStatus.isActive(mContext)) {
                                // Turn off desktop mode
                                mDesktopModeController.ifPresent(
                                        c -> c.setDesktopModeActive(false));
                            }
                        }
                    }
                    break;
                }
@@ -420,15 +444,29 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
     * @param ev the {@link MotionEvent} received by {@link EventReceiver}
     */
    private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
        if (DesktopModeStatus.isProto2Enabled()) {
            CaptionWindowDecoration focusedDecor = getFocusedDecor();
            if (focusedDecor == null
                    || focusedDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
                handleCaptionThroughStatusBar(ev);
            }
        } else if (DesktopModeStatus.isProto1Enabled()) {
            if (!DesktopModeStatus.isActive(mContext)) {
                handleCaptionThroughStatusBar(ev);
            }
        }
        handleEventOutsideFocusedCaption(ev);
        // Prevent status bar from reacting to a caption drag.
        if (DesktopModeStatus.isProto2Enabled()) {
            if (mTransitionDragActive) {
                inputMonitor.pilferPointers();
            }
        } else if (DesktopModeStatus.isProto1Enabled()) {
            if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) {
                inputMonitor.pilferPointers();
            }
        }
    }

    // If an UP/CANCEL action is received outside of caption bounds, turn off handle menu
    private void handleEventOutsideFocusedCaption(MotionEvent ev) {
@@ -455,10 +493,21 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
            case MotionEvent.ACTION_DOWN: {
                // Begin drag through status bar if applicable.
                CaptionWindowDecoration focusedDecor = getFocusedDecor();
                if (focusedDecor != null && !DesktopModeStatus.isActive(mContext)
                        && focusedDecor.checkTouchEventInHandle(ev)) {
                if (focusedDecor != null) {
                    boolean dragFromStatusBarAllowed = false;
                    if (DesktopModeStatus.isProto2Enabled()) {
                        // In proto2 any full screen task can be dragged to freeform
                        dragFromStatusBarAllowed = focusedDecor.mTaskInfo.getWindowingMode()
                                == WINDOWING_MODE_FULLSCREEN;
                    } else if (DesktopModeStatus.isProto1Enabled()) {
                        // In proto1 task can be dragged to freeform when not in desktop mode
                        dragFromStatusBarAllowed = !DesktopModeStatus.isActive(mContext);
                    }

                    if (dragFromStatusBarAllowed && focusedDecor.checkTouchEventInHandle(ev)) {
                        mTransitionDragActive = true;
                    }
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
@@ -472,7 +521,13 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
                    int statusBarHeight = mDisplayController
                            .getDisplayLayout(focusedDecor.mTaskInfo.displayId).stableInsets().top;
                    if (ev.getY() > statusBarHeight) {
                        if (DesktopModeStatus.isProto2Enabled()) {
                            mDesktopTasksController.ifPresent(
                                    c -> c.moveToDesktop(focusedDecor.mTaskInfo));
                        } else if (DesktopModeStatus.isProto1Enabled()) {
                            mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
                        }

                        return;
                    }
                }
+32 −14
Original line number Diff line number Diff line
@@ -16,8 +16,9 @@

package com.android.wm.shell.windowdecor;

import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;

import android.app.ActivityManager;
import android.app.WindowConfiguration;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -117,7 +118,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
                ? R.dimen.freeform_decor_shadow_focused_thickness
                : R.dimen.freeform_decor_shadow_unfocused_thickness;
        final boolean isFreeform =
                taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM;
                taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
        final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;

        WindowDecorLinearLayout oldRootView = mResult.mRootView;
@@ -167,11 +168,17 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
        // If this task is not focused, do not show caption.
        setCaptionVisibility(mTaskInfo.isFocused);

        if (mTaskInfo.isFocused) {
            if (DesktopModeStatus.isProto2Enabled()) {
                updateButtonVisibility();
            } else if (DesktopModeStatus.isProto1Enabled()) {
                // Only handle should show if Desktop Mode is inactive.
                boolean desktopCurrentStatus = DesktopModeStatus.isActive(mContext);
        if (mDesktopActive != desktopCurrentStatus && mTaskInfo.isFocused) {
                if (mDesktopActive != desktopCurrentStatus) {
                    mDesktopActive = desktopCurrentStatus;
            setButtonVisibility();
                    setButtonVisibility(mDesktopActive);
                }
            }
        }

        if (!isDragResizeable) {
@@ -214,7 +221,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
        View handle = caption.findViewById(R.id.caption_handle);
        handle.setOnTouchListener(mOnCaptionTouchListener);
        handle.setOnClickListener(mOnCaptionButtonClickListener);
        setButtonVisibility();
        updateButtonVisibility();
    }

    private void setupHandleMenu() {
@@ -244,14 +251,25 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
    /**
     * Sets the visibility of buttons and color of caption based on desktop mode status
     */
    void setButtonVisibility() {
    void updateButtonVisibility() {
        if (DesktopModeStatus.isProto2Enabled()) {
            setButtonVisibility(mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM);
        } else if (DesktopModeStatus.isProto1Enabled()) {
            mDesktopActive = DesktopModeStatus.isActive(mContext);
        int v = mDesktopActive ? View.VISIBLE : View.GONE;
            setButtonVisibility(mDesktopActive);
        }
    }

    /**
     * Show or hide buttons
     */
    void setButtonVisibility(boolean visible) {
        int visibility = visible ? View.VISIBLE : View.GONE;
        View caption = mResult.mRootView.findViewById(R.id.caption);
        View back = caption.findViewById(R.id.back_button);
        View close = caption.findViewById(R.id.close_window);
        back.setVisibility(v);
        close.setVisibility(v);
        back.setVisibility(visibility);
        close.setVisibility(visibility);
        int buttonTintColorRes =
                mDesktopActive ? R.color.decor_button_dark_color
                        : R.color.decor_button_light_color;
@@ -260,7 +278,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
        View handle = caption.findViewById(R.id.caption_handle);
        VectorDrawable handleBackground = (VectorDrawable) handle.getBackground();
        handleBackground.setTintList(buttonTintColor);
        caption.getBackground().setTint(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT);
        caption.getBackground().setTint(visible ? Color.WHITE : Color.TRANSPARENT);
    }

    boolean isHandleMenuActive() {
+69 −12
Original line number Diff line number Diff line
@@ -17,6 +17,9 @@
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_UNDEFINED
import android.testing.AndroidTestingRunner
import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
@@ -29,6 +32,7 @@ import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
@@ -118,8 +122,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
    @Test
    fun showDesktopApps_allAppsInvisible_bringsToFront() {
        val homeTask = setUpHomeTask()
        val task1 = setUpDesktopTask()
        val task2 = setUpDesktopTask()
        val task1 = setUpFreeformTask()
        val task2 = setUpFreeformTask()
        markTaskHidden(task1)
        markTaskHidden(task2)

@@ -136,25 +140,21 @@ class DesktopTasksControllerTest : ShellTestCase() {
    @Test
    fun showDesktopApps_appsAlreadyVisible_doesNothing() {
        setUpHomeTask()
        val task1 = setUpDesktopTask()
        val task2 = setUpDesktopTask()
        val task1 = setUpFreeformTask()
        val task2 = setUpFreeformTask()
        markTaskVisible(task1)
        markTaskVisible(task2)

        controller.showDesktopApps()

        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
            verify(transitions, never()).startTransition(anyInt(), any(), isNull())
        } else {
            verify(shellTaskOrganizer, never()).applyTransaction(any())
        }
        verifyWCTNotExecuted()
    }

    @Test
    fun showDesktopApps_someAppsInvisible_reordersAll() {
        val homeTask = setUpHomeTask()
        val task1 = setUpDesktopTask()
        val task2 = setUpDesktopTask()
        val task1 = setUpFreeformTask()
        val task2 = setUpFreeformTask()
        markTaskHidden(task1)
        markTaskVisible(task2)

@@ -179,7 +179,49 @@ class DesktopTasksControllerTest : ShellTestCase() {
        wct.assertReorderAt(index = 0, homeTask)
    }

    private fun setUpDesktopTask(): RunningTaskInfo {
    @Test
    fun moveToDesktop() {
        val task = setUpFullscreenTask()
        controller.moveToDesktop(task)
        val wct = getLatestWct()
        assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
            .isEqualTo(WINDOWING_MODE_FREEFORM)
    }

    @Test
    fun moveToDesktop_nonExistentTask_doesNothing() {
        controller.moveToDesktop(999)
        verifyWCTNotExecuted()
    }

    @Test
    fun moveToFullscreen() {
        val task = setUpFreeformTask()
        controller.moveToFullscreen(task)
        val wct = getLatestWct()
        assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
            .isEqualTo(WINDOWING_MODE_FULLSCREEN)
    }

    @Test
    fun moveToFullscreen_nonExistentTask_doesNothing() {
        controller.moveToFullscreen(999)
        verifyWCTNotExecuted()
    }

    @Test
    fun getTaskWindowingMode() {
        val fullscreenTask = setUpFullscreenTask()
        val freeformTask = setUpFreeformTask()

        assertThat(controller.getTaskWindowingMode(fullscreenTask.taskId))
            .isEqualTo(WINDOWING_MODE_FULLSCREEN)
        assertThat(controller.getTaskWindowingMode(freeformTask.taskId))
            .isEqualTo(WINDOWING_MODE_FREEFORM)
        assertThat(controller.getTaskWindowingMode(999)).isEqualTo(WINDOWING_MODE_UNDEFINED)
    }

    private fun setUpFreeformTask(): RunningTaskInfo {
        val task = createFreeformTask()
        whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
        desktopModeTaskRepository.addActiveTask(task.taskId)
@@ -195,6 +237,13 @@ class DesktopTasksControllerTest : ShellTestCase() {
        return task
    }

    private fun setUpFullscreenTask(): RunningTaskInfo {
        val task = createFullscreenTask()
        whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
        runningTasks.add(task)
        return task
    }

    private fun markTaskVisible(task: RunningTaskInfo) {
        desktopModeTaskRepository.updateVisibleFreeformTasks(task.taskId, visible = true)
    }
@@ -212,6 +261,14 @@ class DesktopTasksControllerTest : ShellTestCase() {
        }
        return arg.value
    }

    private fun verifyWCTNotExecuted() {
        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
            verify(transitions, never()).startTransition(anyInt(), any(), isNull())
        } else {
            verify(shellTaskOrganizer, never()).applyTransaction(any())
        }
    }
}

private fun WindowContainerTransaction.assertReorderAt(index: Int, task: RunningTaskInfo) {
Loading