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

Commit 822bacd5 authored by Jagrut Desai's avatar Jagrut Desai
Browse files

Extend DesktopTaskListener Functionality

This cl includes
	- support for listenting into when desktop window mode task is resizing or entering split mode.

Test: Presubmit
Bug: 348335379
Flag: com.android.window.flags.enable_desktop_windowing_mode

Change-Id: I0ee45a9ecfa24a17642223baf2ce5e29c183edb1
parent 4f2db854
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -171,6 +171,18 @@ fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float {
        minOf(appBounds.height(), appBounds.width()).toFloat()
}

/** Returns true if task's width or height is maximized else returns false. */
fun isTaskWidthOrHeightEqual(taskBounds: Rect, stableBounds: Rect): Boolean {
    return taskBounds.width() == stableBounds.width() ||
            taskBounds.height() == stableBounds.height()
}

/** Returns true if task bound is equal to stable bounds else returns false. */
fun isTaskBoundsEqual(taskBounds: Rect, stableBounds: Rect): Boolean {
    return taskBounds.width() == stableBounds.width() &&
            taskBounds.height() == stableBounds.height()
}

/**
 * Calculates the desired initial bounds for applications in desktop windowing. This is done as a
 * scale of the screen bounds.
+128 −13
Original line number Diff line number Diff line
@@ -169,6 +169,9 @@ class DesktopTasksController(
            }
        }

    @VisibleForTesting
    var taskbarDesktopTaskListener: TaskbarDesktopTaskListener? = null

    /** Task id of the task currently being dragged from fullscreen/split. */
    val draggingTaskId
        get() = dragToDesktopTransitionHandler.draggingTaskId
@@ -610,13 +613,10 @@ class DesktopTasksController(
        val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds
        val destinationBounds = Rect()

        val isMaximized = if (taskInfo.isResizeable) {
            currentTaskBounds == stableBounds
        } else {
            currentTaskBounds.width() == stableBounds.width()
                    || currentTaskBounds.height() == stableBounds.height()
        }

        val isMaximized = isTaskMaximized(taskInfo, stableBounds)
        // If the task is currently maximized, we will toggle it not to be and vice versa. This is
        // helpful to eliminate the current task from logic to calculate taskbar corner rounding.
        val willMaximize = !isMaximized
        if (isMaximized) {
            // The desktop task is at the maximized width and/or height of the stable bounds.
            // If the task's pre-maximize stable bounds were saved, toggle the task to those bounds.
@@ -651,6 +651,18 @@ class DesktopTasksController(
            }
        }



        val shouldRestoreToSnap =
            isMaximized && isTaskSnappedToHalfScreen(taskInfo, destinationBounds)

        logD("willMaximize = %s", willMaximize)
        logD("shouldRestoreToSnap = %s", shouldRestoreToSnap)

        val doesAnyTaskRequireTaskbarRounding = willMaximize || shouldRestoreToSnap ||
                doesAnyTaskRequireTaskbarRounding(taskInfo.displayId, taskInfo.taskId)

        taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding)
        val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
            toggleResizeDesktopTaskTransitionHandler.startTransition(wct)
@@ -659,6 +671,65 @@ class DesktopTasksController(
        }
    }

    private fun isTaskMaximized(
        taskInfo: RunningTaskInfo,
        stableBounds: Rect
    ): Boolean {
        val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds

        return if (taskInfo.isResizeable) {
            isTaskBoundsEqual(currentTaskBounds, stableBounds)
        } else {
            isTaskWidthOrHeightEqual(currentTaskBounds, stableBounds)
        }
    }

    private fun isMaximizedToStableBoundsEdges(
        taskInfo: RunningTaskInfo,
        stableBounds: Rect
    ): Boolean {
        val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds
        return isTaskBoundsEqual(currentTaskBounds, stableBounds)
    }

    /** Returns if current task bound is snapped to half screen */
    private fun isTaskSnappedToHalfScreen(
        taskInfo: RunningTaskInfo,
        taskBounds: Rect = taskInfo.configuration.windowConfiguration.bounds
    ): Boolean =
        getSnapBounds(taskInfo, SnapPosition.LEFT) == taskBounds ||
                getSnapBounds(taskInfo, SnapPosition.RIGHT) == taskBounds

    @VisibleForTesting
    fun doesAnyTaskRequireTaskbarRounding(
        displayId: Int,
        excludeTaskId: Int? = null,
    ): Boolean {
        val doesAnyTaskRequireTaskbarRounding =
            taskRepository.getActiveNonMinimizedOrderedTasks(displayId)
                // exclude current task since maximize/restore transition has not taken place yet.
                .filterNot { taskId -> taskId == excludeTaskId }
                .any { taskId ->
                    val taskInfo = shellTaskOrganizer.getRunningTaskInfo(taskId)!!
                    val displayLayout = displayController.getDisplayLayout(taskInfo.displayId)
                    val stableBounds = Rect().apply { displayLayout?.getStableBounds(this) }
                    logD("taskInfo = %s", taskInfo)
                    logD(
                        "isTaskSnappedToHalfScreen(taskInfo) = %s",
                        isTaskSnappedToHalfScreen(taskInfo)
                    )
                    logD(
                        "isMaximizedToStableBoundsEdges(taskInfo, stableBounds) = %s",
                        isMaximizedToStableBoundsEdges(taskInfo, stableBounds)
                    )
                    isTaskSnappedToHalfScreen(taskInfo)
                            || isMaximizedToStableBoundsEdges(taskInfo, stableBounds)
                }

        logD("doesAnyTaskRequireTaskbarRounding = %s", doesAnyTaskRequireTaskbarRounding)
        return doesAnyTaskRequireTaskbarRounding
    }

    /**
     * Quick-resize to the right or left half of the stable bounds.
     *
@@ -673,9 +744,9 @@ class DesktopTasksController(
        position: SnapPosition
    ) {
        val destinationBounds = getSnapBounds(taskInfo, position)

        if (destinationBounds == taskInfo.configuration.windowConfiguration.bounds) return

        taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(true)
        val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
            toggleResizeDesktopTaskTransitionHandler.startTransition(wct, currentDragBounds)
@@ -806,6 +877,10 @@ class DesktopTasksController(
            .mapNotNull { taskId -> shellTaskOrganizer.getRunningTaskInfo(taskId) }
            .reversed() // Start from the back so the front task is brought forward last
            .forEach { task -> wct.reorder(task.token, /* onTop= */ true) }

        taskbarDesktopTaskListener?.
            onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding(displayId))

        return taskToMinimize
    }

@@ -1156,6 +1231,12 @@ class DesktopTasksController(
        ) {
            wct.removeTask(task.token)
        }
        taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
            doesAnyTaskRequireTaskbarRounding(
                task.displayId,
                task.id
            )
        )
        return if (wct.isEmpty) null else wct
    }

@@ -1450,6 +1531,8 @@ class DesktopTasksController(
        }
        // A freeform drag-move ended, remove the indicator immediately.
        releaseVisualIndicator()
        taskbarDesktopTaskListener
            ?.onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding(taskInfo.displayId))
    }

    /**
@@ -1686,17 +1769,39 @@ class DesktopTasksController(
                }
            }

        private val mTaskbarDesktopTaskListener: TaskbarDesktopTaskListener =
                object : TaskbarDesktopTaskListener {
                    override fun onTaskbarCornerRoundingUpdate(
                        hasTasksRequiringTaskbarRounding: Boolean) {
                        ProtoLog.v(
                                WM_SHELL_DESKTOP_MODE,
                                "IDesktopModeImpl: onTaskbarCornerRoundingUpdate " +
                                        "doesAnyTaskRequireTaskbarRounding=%s",
                                hasTasksRequiringTaskbarRounding
                        )

                        remoteListener.call { l ->
                            l.onTaskbarCornerRoundingUpdate(hasTasksRequiringTaskbarRounding)
                        }
                    }
                }

        init {
            remoteListener =
                SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>(
                    controller,
                    { c ->
                        c.taskRepository.addVisibleTasksListener(
                            listener,
                            c.mainExecutor
                        )
                        run {
                            c.taskRepository.addVisibleTasksListener(listener, c.mainExecutor)
                            c.taskbarDesktopTaskListener = mTaskbarDesktopTaskListener
                        }
                    },
                    { c -> c.taskRepository.removeVisibleTasksListener(listener) }
                    { c ->
                        run {
                            c.taskRepository.removeVisibleTasksListener(listener)
                            c.taskbarDesktopTaskListener = null
                        }
                    }
                )
        }

@@ -1779,6 +1884,16 @@ class DesktopTasksController(
        private const val TAG = "DesktopTasksController"
    }

    /** Defines interface for classes that can listen to changes for task resize. */
    // TODO(b/343931111): Migrate to using TransitionObservers when ready
    interface TaskbarDesktopTaskListener {
        /**
         * [hasTasksRequiringTaskbarRounding] is true when a task is either maximized or snapped
         * left/right and rounded corners are enabled.
         */
        fun onTaskbarCornerRoundingUpdate(hasTasksRequiringTaskbarRounding: Boolean)
    }

    /** The positions on a screen that a task can snap to. */
    enum class SnapPosition {
        RIGHT,
+6 −0
Original line number Diff line number Diff line
@@ -27,4 +27,10 @@ interface IDesktopTaskListener {

    /** @deprecated this is no longer supported. */
    oneway void onStashedChanged(int displayId, boolean stashed);

    /**
     * Shows taskbar corner radius when running desktop tasks are updated if
     * [hasTasksRequiringTaskbarRounding] is true.
     */
    oneway void onTaskbarCornerRoundingUpdate(boolean hasTasksRequiringTaskbarRounding);
}
 No newline at end of file
+66 −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.graphics.Rect
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidTestingRunner::class)
class DesktopModeUtilsTest {
    @Test
    fun isTaskBoundsEqual_stableBoundsAreEqual_returnTrue() {
        assertThat(isTaskBoundsEqual(task2Bounds, stableBounds)).isTrue()
    }

    @Test
    fun isTaskBoundsEqual_stableBoundsAreNotEqual_returnFalse() {
        assertThat(isTaskBoundsEqual(task4Bounds, stableBounds)).isFalse()
    }

    @Test
    fun isTaskWidthOrHeightEqual_stableBoundsAreEqual_returnTrue() {
        assertThat(isTaskWidthOrHeightEqual(task2Bounds, stableBounds)).isTrue()
    }

    @Test
    fun isTaskWidthOrHeightEqual_stableBoundWidthIsEquals_returnTrue() {
        assertThat(isTaskWidthOrHeightEqual(task3Bounds, stableBounds)).isTrue()
    }

    @Test
    fun isTaskWidthOrHeightEqual_stableBoundHeightIsEquals_returnTrue() {
        assertThat(isTaskWidthOrHeightEqual(task3Bounds, stableBounds)).isTrue()
    }

    @Test
    fun isTaskWidthOrHeightEqual_stableBoundsWidthOrHeightAreNotEquals_returnFalse() {
        assertThat(isTaskWidthOrHeightEqual(task1Bounds, stableBounds)).isTrue()
    }

    private companion object {
        val task1Bounds = Rect(0, 0, 0, 0)
        val task2Bounds = Rect(1, 1, 1, 1)
        val task3Bounds = Rect(0, 1, 0, 1)
        val task4Bounds = Rect(1, 2, 2, 1)
        val stableBounds = Rect(1, 1, 1, 1)
    }
}
+50 −0
Original line number Diff line number Diff line
@@ -84,6 +84,7 @@ import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.desktopmode.DesktopTasksController.TaskbarDesktopTaskListener
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
@@ -175,6 +176,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
  @Mock
  private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
  @Mock private lateinit var mockSurface: SurfaceControl
  @Mock private lateinit var taskbarDesktopTaskListener: TaskbarDesktopTaskListener

  private lateinit var mockitoSession: StaticMockitoSession
  private lateinit var controller: DesktopTasksController
@@ -237,6 +239,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
    val captor = ArgumentCaptor.forClass(RecentsTransitionStateListener::class.java)
    verify(recentsTransitionHandler).addTransitionStateListener(captor.capture())
    recentsTransitionStateListener = captor.value

    controller.taskbarDesktopTaskListener = taskbarDesktopTaskListener
  }

  private fun createController(): DesktopTasksController {
@@ -281,6 +285,52 @@ class DesktopTasksControllerTest : ShellTestCase() {
    verify(shellInit).addInitCallback(any(), any<DesktopTasksController>())
  }

  @Test
  fun doesAnyTaskRequireTaskbarRounding_onlyFreeFormTaskIsRunning_returnFalse() {
    setUpFreeformTask()

    assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isFalse()
  }

  @Test
  fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfFreeFormTask_returnTrue() {
    val task1 = setUpFreeformTask()

    val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
    controller.toggleDesktopTaskSize(task1)
    verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())

    assertThat(argumentCaptor.value).isTrue()
  }

  @Test
  fun doesAnyTaskRequireTaskbarRounding_fullScreenTaskIsRunning_returnTrue() {
    val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
    setUpFreeformTask(bounds = stableBounds, active = true)
    assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isTrue()
  }

  @Test
  fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfFullScreenTask_returnFalse() {
    val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
    val task1 = setUpFreeformTask(bounds = stableBounds, active = true)

    val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
    controller.toggleDesktopTaskSize(task1)
    verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())

    assertThat(argumentCaptor.value).isFalse()
  }

  @Test
  fun doesAnyTaskRequireTaskbarRounding_splitScreenTaskIsRunning_returnTrue() {
    val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
    setUpFreeformTask(bounds = Rect(stableBounds.left, stableBounds.top, 500, stableBounds.bottom))

    assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isTrue()
  }


  @Test
  fun instantiate_cannotEnterDesktopMode_doNotAddInitCallback() {
    whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false)