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

Commit d1eccd27 authored by Orhan Uysal's avatar Orhan Uysal
Browse files

Minimize tasks moving back with back nav.

Observe for TO_BACK transitions that are moving tasks TO_BACK. Mark the
freeform tasks that moved back as minimized.

Bug: 367672910
Test: atest DesktopTasksTransitionObserverTest
Test: atest DesktopTasksControllerTest
Flag: com.android.window.flags.enable_desktop_windowing_back_navigation

Change-Id: I42b4ec8a9d8f5e5715d0eed4125d873b1f4c9fdd
parent caa2c9be
Loading
Loading
Loading
Loading
+12 −4
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import android.view.Display.DEFAULT_DISPLAY
import android.view.DragEvent
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
@@ -1061,7 +1062,10 @@ class DesktopTasksController(
                    // Check if freeform task launch during recents should be handled
                    shouldHandleMidRecentsFreeformLaunch -> handleMidRecentsFreeformTaskLaunch(task)
                    // Check if the closing task needs to be handled
                    TransitionUtil.isClosingType(request.type) -> handleTaskClosing(task)
                    TransitionUtil.isClosingType(request.type) -> handleTaskClosing(
                        task,
                        request.type
                    )
                    // Check if the top task shouldn't be allowed to enter desktop mode
                    isIncompatibleTask(task) -> handleIncompatibleTaskLaunch(task)
                    // Check if fullscreen task should be updated
@@ -1288,7 +1292,10 @@ class DesktopTasksController(
    }

    /** Handle task closing by removing wallpaper activity if it's the last active task */
    private fun handleTaskClosing(task: RunningTaskInfo): WindowContainerTransaction? {
    private fun handleTaskClosing(
        task: RunningTaskInfo,
        transitionType: Int
    ): WindowContainerTransaction? {
        logV("handleTaskClosing")
        if (!isDesktopModeShowing(task.displayId))
            return null
@@ -1301,9 +1308,10 @@ class DesktopTasksController(
            removeWallpaperActivity(wct)
        }
        taskRepository.addClosingTask(task.displayId, task.taskId)
        // If a CLOSE or TO_BACK is triggered on a desktop task, remove the task.
        // If a CLOSE is triggered on a desktop task, remove the task.
        if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue() &&
            taskRepository.isVisibleTask(task.taskId)
            taskRepository.isVisibleTask(task.taskId) &&
            transitionType == TRANSIT_CLOSE
        ) {
            wct.removeTask(task.token)
        }
+28 −1
Original line number Diff line number Diff line
@@ -16,16 +16,19 @@

package com.android.wm.shell.desktopmode

import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.Context
import android.os.IBinder
import android.view.SurfaceControl
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
import android.window.flags.DesktopModeFlags
import android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
@@ -64,6 +67,30 @@ class DesktopTasksTransitionObserver(
    ) {
        // TODO: b/332682201 Update repository state
        updateWallpaperToken(info)

        if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
            handleBackNavigation(info)
        }
    }

    private fun handleBackNavigation(info: TransitionInfo) {
        // When default back navigation happens, transition type is TO_BACK and the change is
        // TO_BACK. Mark the task going to back as minimized.
        if (info.type == TRANSIT_TO_BACK) {
            for (change in info.changes) {
                val taskInfo = change.taskInfo
                if (taskInfo == null || taskInfo.taskId == -1) {
                    continue
                }

                if (desktopModeTaskRepository.getVisibleTaskCount(taskInfo.displayId) > 0 &&
                    change.mode == TRANSIT_TO_BACK &&
                    taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
                ) {
                    desktopModeTaskRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
                }
            }
        }
    }

    override fun onTransitionStarting(transition: IBinder) {
+4 −76
Original line number Diff line number Diff line
@@ -2086,16 +2086,13 @@ class DesktopTasksControllerTest : ShellTestCase() {
  }

  @Test
  @EnableFlags(
    Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
    Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
  )
  fun handleRequest_backTransition_singleTaskNoToken_withWallpaper_withBackNav_removesTask() {
  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
  fun handleRequest_backTransition_singleTaskNoToken_withWallpaper_removesTask() {
    val task = setUpFreeformTask()

    val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))

    assertNotNull(result, "Should handle request").assertRemoveAt(0, task.token)
    assertNull(result, "Should not handle request")
  }

  @Test
@@ -2136,27 +2133,9 @@ class DesktopTasksControllerTest : ShellTestCase() {
    assertNull(result, "Should not handle request")
  }

  @Test
  @EnableFlags(
    Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
    Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
  )
  fun handleRequest_backTransition_singleTask_withWallpaper_withBackNav_removesWallpaperAndTask() {
    val task = setUpFreeformTask()
    val wallpaperToken = MockToken().token()

    taskRepository.wallpaperActivityToken = wallpaperToken
    val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))

    // Should create remove wallpaper transaction
    assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
    result.assertRemoveAt(index = 1, task.token)
  }

  @Test
  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
  @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
  fun handleRequest_backTransition_singleTaskWithToken_noBackNav_removesWallpaper() {
  fun handleRequest_backTransition_singleTaskWithToken_removesWallpaper() {
    val task = setUpFreeformTask()
    val wallpaperToken = MockToken().token()

@@ -2182,24 +2161,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
    assertNull(result, "Should not handle request")
  }

  @Test
  @EnableFlags(
    Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
    Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
  )
  fun handleRequest_backTransition_multipleTasks_withWallpaper_withBackNav_removesTask() {
    val task1 = setUpFreeformTask()
    setUpFreeformTask()

    taskRepository.wallpaperActivityToken = MockToken().token()
    val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))

    assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, task1.token)
  }

  @Test
  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
  @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
  fun handleRequest_backTransition_multipleTasks_noBackNav_doesNotHandle() {
    val task1 = setUpFreeformTask()
    setUpFreeformTask()
@@ -2224,23 +2187,6 @@ class DesktopTasksControllerTest : ShellTestCase() {
    taskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
    val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))

    // Should create remove wallpaper transaction
    assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
    result.assertRemoveAt(index = 1, task1.token)
  }

  @Test
  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
  @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
  fun handleRequest_backTransition_multipleTasksSingleNonClosing_noBackNav_removesWallpaper() {
    val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
    val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
    val wallpaperToken = MockToken().token()

    taskRepository.wallpaperActivityToken = wallpaperToken
    taskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
    val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))

    // Should create remove wallpaper transaction
    assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
  }
@@ -2248,7 +2194,6 @@ class DesktopTasksControllerTest : ShellTestCase() {
  @Test
  @EnableFlags(
    Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
    Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
  )
  fun handleRequest_backTransition_multipleTasksSingleNonMinimized_removesWallpaperAndTask() {
    val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
@@ -2259,23 +2204,6 @@ class DesktopTasksControllerTest : ShellTestCase() {
    taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
    val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))

    // Should create remove wallpaper transaction
    assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
    result.assertRemoveAt(index = 1, task1.token)
  }

  @Test
  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
  @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
  fun handleRequest_backTransition_multipleTasksSingleNonMinimized_noBackNav_removesWallpaper() {
    val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
    val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
    val wallpaperToken = MockToken().token()

    taskRepository.wallpaperActivityToken = wallpaperToken
    taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
    val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))

    // Should create remove wallpaper transaction
    assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
  }
+138 −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.content.ComponentName
import android.content.Context
import android.content.Intent
import android.platform.test.annotations.EnableFlags
import android.view.Display.DEFAULT_DISPLAY
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.IWindowContainerToken
import android.window.TransitionInfo
import android.window.TransitionInfo.Change
import android.window.WindowContainerToken
import com.android.modules.utils.testing.ExtendedMockitoRule
import com.android.window.flags.Flags
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

class DesktopTasksTransitionObserverTest {

    @JvmField
    @Rule
    val extendedMockitoRule =
        ExtendedMockitoRule.Builder(this)
            .mockStatic(DesktopModeStatus::class.java)
            .build()!!

    private val testExecutor = mock<ShellExecutor>()
    private val mockShellInit = mock<ShellInit>()
    private val transitions = mock<Transitions>()
    private val context = mock<Context>()
    private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
    private val taskRepository = mock<DesktopModeTaskRepository>()

    private lateinit var transitionObserver: DesktopTasksTransitionObserver
    private lateinit var shellInit: ShellInit

    @Before
    fun setup() {
        whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true)
        shellInit = spy(ShellInit(testExecutor))

        transitionObserver =
            DesktopTasksTransitionObserver(
                context, taskRepository, transitions, shellTaskOrganizer, shellInit
            )
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
    fun backNavigation_taskMinimized() {
        val task = createTaskInfo(1)
        whenever(taskRepository.getVisibleTaskCount(any())).thenReturn(1)

        transitionObserver.onTransitionReady(
            transition = mock(),
            info =
            createBackNavigationTransition(task),
            startTransaction = mock(),
            finishTransaction = mock(),
        )

        verify(taskRepository).minimizeTask(task.displayId, task.taskId)
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
    fun backNavigation_nullTaskInfo_taskNotMinimized() {
        val task = createTaskInfo(1)
        whenever(taskRepository.getVisibleTaskCount(any())).thenReturn(1)

        transitionObserver.onTransitionReady(
            transition = mock(),
            info =
            createBackNavigationTransition(null),
            startTransaction = mock(),
            finishTransaction = mock(),
        )

        verify(taskRepository, never()).minimizeTask(task.displayId, task.taskId)
    }

    private fun createBackNavigationTransition(
        task: RunningTaskInfo?
    ): TransitionInfo {
        return TransitionInfo(TRANSIT_TO_BACK, 0 /* flags */).apply {
            addChange(
                Change(mock(), mock()).apply {
                    mode = TRANSIT_TO_BACK
                    parent = null
                    taskInfo = task
                    flags = flags
                }
            )
        }
    }

    private fun createTaskInfo(id: Int) =
        RunningTaskInfo().apply {
            taskId = id
            displayId = DEFAULT_DISPLAY
            configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
            token = WindowContainerToken(Mockito.mock(IWindowContainerToken::class.java))
            baseIntent = Intent().apply {
                component = ComponentName("package", "component.name")
            }
        }
}