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

Commit 75dd4b65 authored by Merissa Mitchell's avatar Merissa Mitchell Committed by Android (Google) Code Review
Browse files

Merge changes from topic "desktop-pip-shellmodule" into main

* changes:
  Fix DesktopPipTransitionObserver dependencies in WMShellModule.
  Revert^2 "[PiP on Desktop] Minimizing last task to PiP should exit Desktop"
parents c80fd5d2 07357614
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -98,6 +98,7 @@ import com.android.wm.shell.desktopmode.DesktopModeKeyGestureHandler;
import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopModeMoveToDisplayTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger;
import com.android.wm.shell.desktopmode.DesktopPipTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopTaskChangeListener;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
@@ -780,6 +781,7 @@ public abstract class WMShellModule {
            OverviewToDesktopTransitionObserver overviewToDesktopTransitionObserver,
            DesksOrganizer desksOrganizer,
            Optional<DesksTransitionObserver> desksTransitionObserver,
            Optional<DesktopPipTransitionObserver> desktopPipTransitionObserver,
            UserProfileContexts userProfileContexts,
            DesktopModeCompatPolicy desktopModeCompatPolicy,
            DragToDisplayTransitionHandler dragToDisplayTransitionHandler,
@@ -823,6 +825,7 @@ public abstract class WMShellModule {
                overviewToDesktopTransitionObserver,
                desksOrganizer,
                desksTransitionObserver.get(),
                desktopPipTransitionObserver,
                userProfileContexts,
                desktopModeCompatPolicy,
                dragToDisplayTransitionHandler,
@@ -1225,6 +1228,7 @@ public abstract class WMShellModule {
            Transitions transitions,
            ShellTaskOrganizer shellTaskOrganizer,
            Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler,
            Optional<DesktopPipTransitionObserver> desktopPipTransitionObserver,
            Optional<BackAnimationController> backAnimationController,
            DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider,
            ShellInit shellInit) {
@@ -1237,6 +1241,7 @@ public abstract class WMShellModule {
                                        transitions,
                                        shellTaskOrganizer,
                                        desktopMixedTransitionHandler.get(),
                                        desktopPipTransitionObserver,
                                        backAnimationController.get(),
                                        desktopWallpaperActivityTokenProvider,
                                        shellInit)));
@@ -1256,6 +1261,19 @@ public abstract class WMShellModule {
        return Optional.empty();
    }

    @WMSingleton
    @Provides
    static Optional<DesktopPipTransitionObserver> provideDesktopPipTransitionObserver(
            Context context
    ) {
        if (DesktopModeStatus.canEnterDesktopMode(context)
                && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue()) {
            return Optional.of(
                    new DesktopPipTransitionObserver());
        }
        return Optional.empty();
    }

    @WMSingleton
    @Provides
    static Optional<DesktopMixedTransitionHandler> provideDesktopMixedTransitionHandler(
+81 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.WindowConfiguration.WINDOWING_MODE_PINNED
import android.os.IBinder
import android.window.DesktopModeFlags
import android.window.TransitionInfo
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE

/**
 * Observer of PiP in Desktop Mode transitions. At the moment, this is specifically tracking a PiP
 * transition for a task that is entering PiP via the minimize button on the caption bar.
 */
class DesktopPipTransitionObserver {
    private val pendingPipTransitions = mutableMapOf<IBinder, PendingPipTransition>()

    /** Adds a pending PiP transition to be tracked. */
    fun addPendingPipTransition(transition: PendingPipTransition) {
        if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue) return
        pendingPipTransitions[transition.token] = transition
    }

    /**
     * Called when any transition is ready, which may include transitions not tracked by this
     * observer.
     */
    fun onTransitionReady(transition: IBinder, info: TransitionInfo) {
        if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue) return
        val pipTransition = pendingPipTransitions.remove(transition) ?: return

        logD("Desktop PiP transition ready: %s", transition)
        for (change in info.changes) {
            val taskInfo = change.taskInfo
            if (taskInfo == null || taskInfo.taskId == -1) {
                continue
            }

            if (
                taskInfo.taskId == pipTransition.taskId &&
                    taskInfo.windowingMode == WINDOWING_MODE_PINNED
            ) {
                logD("Desktop PiP transition was successful")
                pipTransition.onSuccess()
                return
            }
        }
        logD("Change with PiP task not found in Desktop PiP transition; likely failed")
    }

    /**
     * Data tracked for a pending PiP transition.
     *
     * @property token the PiP transition that is started.
     * @property taskId task id of the task entering PiP.
     * @property onSuccess callback to be invoked if the PiP transition is successful.
     */
    data class PendingPipTransition(val token: IBinder, val taskId: Int, val onSuccess: () -> Unit)

    private fun logD(msg: String, vararg arguments: Any?) {
        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
    }

    private companion object {
        private const val TAG = "DesktopPipTransitionObserver"
    }
}
+0 −58
Original line number Diff line number Diff line
@@ -68,7 +68,6 @@ class DesktopRepository(
     * @property topTransparentFullscreenTaskId the task id of any current top transparent
     *   fullscreen task launched on top of the desk. Cleared when the transparent task is closed or
     *   sent to back. (top is at index 0).
     * @property pipTaskId the task id of PiP task entered while in Desktop Mode.
     */
    private data class Desk(
        val deskId: Int,
@@ -81,7 +80,6 @@ class DesktopRepository(
        val freeformTasksInZOrder: ArrayList<Int> = ArrayList(),
        var fullImmersiveTaskId: Int? = null,
        var topTransparentFullscreenTaskId: Int? = null,
        var pipTaskId: Int? = null,
    ) {
        fun deepCopy(): Desk =
            Desk(
@@ -94,7 +92,6 @@ class DesktopRepository(
                freeformTasksInZOrder = ArrayList(freeformTasksInZOrder),
                fullImmersiveTaskId = fullImmersiveTaskId,
                topTransparentFullscreenTaskId = topTransparentFullscreenTaskId,
                pipTaskId = pipTaskId,
            )

        // TODO: b/362720497 - remove when multi-desktops is enabled where instances aren't
@@ -107,7 +104,6 @@ class DesktopRepository(
            freeformTasksInZOrder.clear()
            fullImmersiveTaskId = null
            topTransparentFullscreenTaskId = null
            pipTaskId = null
        }
    }

@@ -127,9 +123,6 @@ class DesktopRepository(
    /* Tracks last bounds of task before toggled to immersive state. */
    private val boundsBeforeFullImmersiveByTaskId = SparseArray<Rect>()

    /* Callback for when a pending PiP transition has been aborted. */
    private var onPipAbortedCallback: ((Int, Int) -> Unit)? = null

    private var desktopGestureExclusionListener: Consumer<Region>? = null
    private var desktopGestureExclusionExecutor: Executor? = null

@@ -610,57 +603,6 @@ class DesktopRepository(
        }
    }

    /**
     * Set whether the given task is the Desktop-entered PiP task in this display's active desk.
     *
     * TODO: b/389960283 - add explicit [deskId] argument.
     */
    fun setTaskInPip(displayId: Int, taskId: Int, enterPip: Boolean) {
        val activeDesk =
            desktopData.getActiveDesk(displayId)
                ?: error("Expected active desk in display: $displayId")
        if (enterPip) {
            activeDesk.pipTaskId = taskId
        } else {
            activeDesk.pipTaskId =
                if (activeDesk.pipTaskId == taskId) null
                else {
                    logW(
                        "setTaskInPip: taskId=%d did not match saved taskId=%d",
                        taskId,
                        activeDesk.pipTaskId,
                    )
                    activeDesk.pipTaskId
                }
        }
    }

    /**
     * Returns whether the given task is the Desktop-entered PiP task in this display's active desk.
     *
     * TODO: b/389960283 - add explicit [deskId] argument.
     */
    fun isTaskMinimizedPipInDisplay(displayId: Int, taskId: Int): Boolean =
        desktopData.getActiveDesk(displayId)?.pipTaskId == taskId

    /**
     * Saves callback to handle a pending PiP transition being aborted.
     *
     * TODO: b/389960283 - add explicit [deskId] argument.
     */
    fun setOnPipAbortedCallback(callbackIfPipAborted: ((displayId: Int, pipTaskId: Int) -> Unit)?) {
        onPipAbortedCallback = callbackIfPipAborted
    }

    /**
     * Invokes callback to handle a pending PiP transition with the given task id being aborted.
     *
     * TODO: b/389960283 - add explicit [deskId] argument.
     */
    fun onPipAborted(displayId: Int, pipTaskId: Int) {
        onPipAbortedCallback?.invoke(displayId, pipTaskId)
    }

    /**
     * Set whether the given task is the full-immersive task in this display's active desk.
     *
+101 −63
Original line number Diff line number Diff line
@@ -214,6 +214,7 @@ class DesktopTasksController(
    private val overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver,
    private val desksOrganizer: DesksOrganizer,
    private val desksTransitionObserver: DesksTransitionObserver,
    private val desktopPipTransitionObserver: Optional<DesktopPipTransitionObserver>,
    private val userProfileContexts: UserProfileContexts,
    private val desktopModeCompatPolicy: DesktopModeCompatPolicy,
    private val dragToDisplayTransitionHandler: DragToDisplayTransitionHandler,
@@ -793,10 +794,31 @@ class DesktopTasksController(

    fun minimizeTask(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) {
        val wct = WindowContainerTransaction()

        val taskId = taskInfo.taskId
        val displayId = taskInfo.displayId
        val deskId =
            taskRepository.getDeskIdForTask(taskInfo.taskId)
                ?: if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
                    logW("minimizeTask: desk not found for task: ${taskInfo.taskId}")
                    return
                } else {
                    getDefaultDeskId(taskInfo.displayId)
                }
        val isLastTask =
            if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
                taskRepository.isOnlyVisibleNonClosingTaskInDesk(
                    taskId = taskId,
                    deskId = checkNotNull(deskId) { "Expected non-null deskId" },
                    displayId = displayId,
                )
            } else {
                taskRepository.isOnlyVisibleNonClosingTask(taskId = taskId, displayId = displayId)
            }
        val isMinimizingToPip =
            DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue &&
                (taskInfo.pictureInPictureParams?.isAutoEnterEnabled() ?: false)
                desktopPipTransitionObserver.isPresent &&
                (taskInfo.pictureInPictureParams?.isAutoEnterEnabled ?: false)

        // If task is going to PiP, start a PiP transition instead of a minimize transition
        if (isMinimizingToPip) {
            val requestInfo =
@@ -810,28 +832,22 @@ class DesktopTasksController(
                )
            val requestRes = transitions.dispatchRequest(Binder(), requestInfo, /* skip= */ null)
            wct.merge(requestRes.second, true)
            freeformTaskTransitionStarter.startPipTransition(wct)
            taskRepository.setTaskInPip(taskInfo.displayId, taskInfo.taskId, enterPip = true)
            taskRepository.setOnPipAbortedCallback { displayId, taskId ->
                minimizeTaskInner(shellTaskOrganizer.getRunningTaskInfo(taskId)!!, minimizeReason)
                taskRepository.setTaskInPip(displayId, taskId, enterPip = false)
            }
            return
        }

        minimizeTaskInner(taskInfo, minimizeReason)
    }

    private fun minimizeTaskInner(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) {
        val taskId = taskInfo.taskId
        val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId)
        if (deskId == null && DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
            logW("minimizeTaskInner: desk not found for task: ${taskInfo.taskId}")
            return
        }
        val displayId = taskInfo.displayId
        val wct = WindowContainerTransaction()

            desktopPipTransitionObserver.get().addPendingPipTransition(
                DesktopPipTransitionObserver.PendingPipTransition(
                    token = freeformTaskTransitionStarter.startPipTransition(wct),
                    taskId = taskInfo.taskId,
                    onSuccess = {
                        onDesktopTaskEnteredPip(
                            taskId = taskId,
                            deskId = deskId,
                            displayId = taskInfo.displayId,
                            taskIsLastVisibleTaskBeforePip = isLastTask,
                        )
                    },
                )
            )
        } else {
            snapEventHandler.removeTaskIfTiled(displayId, taskId)
            val willExitDesktop = willExitDesktop(taskId, displayId, forceExitDesktop = false)
            val desktopExitRunnable =
@@ -857,16 +873,6 @@ class DesktopTasksController(
            } else {
                wct.reorder(taskInfo.token, /* onTop= */ false)
            }
        val isLastTask =
            if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
                taskRepository.isOnlyVisibleNonClosingTaskInDesk(
                    taskId = taskId,
                    deskId = checkNotNull(deskId) { "Expected non-null deskId" },
                    displayId = displayId,
                )
            } else {
                taskRepository.isOnlyVisibleNonClosingTask(taskId = taskId, displayId = displayId)
            }
            val transition =
                freeformTaskTransitionStarter.startMinimizedModeTransition(wct, taskId, isLastTask)
            desktopTasksLimiter.ifPresent {
@@ -880,6 +886,7 @@ class DesktopTasksController(
            exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
            desktopExitRunnable?.invoke(transition)
        }
    }

    /** Move a task with given `taskId` to fullscreen */
    fun moveToFullscreen(taskId: Int, transitionSource: DesktopModeTransitionSource) {
@@ -1845,7 +1852,11 @@ class DesktopTasksController(
        displayId: Int,
        forceExitDesktop: Boolean,
    ): Boolean {
        if (forceExitDesktop && DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
        if (
            forceExitDesktop &&
                (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue ||
                    DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue)
        ) {
            // |forceExitDesktop| is true when the callers knows we'll exit desktop, such as when
            // explicitly going fullscreen, so there's no point in checking the desktop state.
            return true
@@ -1862,6 +1873,33 @@ class DesktopTasksController(
        return true
    }

    /** Potentially perform Desktop cleanup after a task successfully enters PiP. */
    @VisibleForTesting
    fun onDesktopTaskEnteredPip(
        taskId: Int,
        deskId: Int,
        displayId: Int,
        taskIsLastVisibleTaskBeforePip: Boolean,
    ) {
        if (
            !willExitDesktop(taskId, displayId, forceExitDesktop = taskIsLastVisibleTaskBeforePip)
        ) {
            return
        }

        val wct = WindowContainerTransaction()
        val desktopExitRunnable =
            performDesktopExitCleanUp(
                wct = wct,
                deskId = deskId,
                displayId = displayId,
                willExitDesktop = true,
            )

        val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
        desktopExitRunnable?.invoke(transition)
    }

    private fun performDesktopExitCleanupIfNeeded(
        taskId: Int,
        deskId: Int? = null,
+3 −44
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ import android.os.IBinder
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_PIP
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.DesktopExperienceFlags
import android.window.DesktopModeFlags
@@ -43,8 +42,7 @@ import com.android.wm.shell.shared.TransitionUtil.isOpeningMode
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP
import com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP
import java.util.Optional

/**
 * A [Transitions.TransitionObserver] that observes shell transitions and updates the
@@ -57,6 +55,7 @@ class DesktopTasksTransitionObserver(
    private val transitions: Transitions,
    private val shellTaskOrganizer: ShellTaskOrganizer,
    private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler,
    private val desktopPipTransitionObserver: Optional<DesktopPipTransitionObserver>,
    private val backAnimationController: BackAnimationController,
    private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider,
    shellInit: ShellInit,
@@ -65,8 +64,6 @@ class DesktopTasksTransitionObserver(
    data class CloseWallpaperTransition(val transition: IBinder, val displayId: Int)

    private var transitionToCloseWallpaper: CloseWallpaperTransition? = null
    /* Pending PiP transition and its associated display id and task id. */
    private var pendingPipTransitionAndPipTask: Triple<IBinder, Int, Int>? = null
    private var currentProfileId: Int

    init {
@@ -100,33 +97,7 @@ class DesktopTasksTransitionObserver(
            removeTaskIfNeeded(info)
        }
        removeWallpaperOnLastTaskClosingIfNeeded(transition, info)

        val desktopRepository = desktopUserRepositories.getProfile(currentProfileId)
        info.changes.forEach { change ->
            change.taskInfo?.let { taskInfo ->
                if (
                    DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue &&
                        desktopRepository.isTaskMinimizedPipInDisplay(
                            taskInfo.displayId,
                            taskInfo.taskId,
                        )
                ) {
                    when (info.type) {
                        TRANSIT_PIP ->
                            pendingPipTransitionAndPipTask =
                                Triple(transition, taskInfo.displayId, taskInfo.taskId)

                        TRANSIT_EXIT_PIP,
                        TRANSIT_REMOVE_PIP ->
                            desktopRepository.setTaskInPip(
                                taskInfo.displayId,
                                taskInfo.taskId,
                                enterPip = false,
                            )
                    }
                }
            }
        }
        desktopPipTransitionObserver.ifPresent { it.onTransitionReady(transition, info) }
    }

    private fun removeTaskIfNeeded(info: TransitionInfo) {
@@ -301,18 +272,6 @@ class DesktopTasksTransitionObserver(
                    }
                }
            transitionToCloseWallpaper = null
        } else if (pendingPipTransitionAndPipTask?.first == transition) {
            val desktopRepository = desktopUserRepositories.getProfile(currentProfileId)
            if (aborted) {
                pendingPipTransitionAndPipTask?.let {
                    desktopRepository.onPipAborted(
                        /*displayId=*/ it.second,
                        /* taskId=*/ it.third,
                    )
                }
            }
            desktopRepository.setOnPipAbortedCallback(null)
            pendingPipTransitionAndPipTask = null
        }
    }

Loading