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

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

Merge "[PiP on Desktop] Handle Desktop cleanups for all PiP transitions." into main

parents 4f3bd0d6 fed05a65
Loading
Loading
Loading
Loading
+0 −18
Original line number Diff line number Diff line
@@ -101,7 +101,6 @@ 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;
@@ -793,7 +792,6 @@ public abstract class WMShellModule {
            OverviewToDesktopTransitionObserver overviewToDesktopTransitionObserver,
            DesksOrganizer desksOrganizer,
            Optional<DesksTransitionObserver> desksTransitionObserver,
            Optional<DesktopPipTransitionObserver> desktopPipTransitionObserver,
            UserProfileContexts userProfileContexts,
            DesktopModeCompatPolicy desktopModeCompatPolicy,
            DragToDisplayTransitionHandler dragToDisplayTransitionHandler,
@@ -837,7 +835,6 @@ public abstract class WMShellModule {
                overviewToDesktopTransitionObserver,
                desksOrganizer,
                desksTransitionObserver.get(),
                desktopPipTransitionObserver,
                userProfileContexts,
                desktopModeCompatPolicy,
                dragToDisplayTransitionHandler,
@@ -1250,7 +1247,6 @@ public abstract class WMShellModule {
            Transitions transitions,
            ShellTaskOrganizer shellTaskOrganizer,
            Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler,
            Optional<DesktopPipTransitionObserver> desktopPipTransitionObserver,
            Optional<BackAnimationController> backAnimationController,
            DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider,
            ShellInit shellInit) {
@@ -1263,7 +1259,6 @@ public abstract class WMShellModule {
                                        transitions,
                                        shellTaskOrganizer,
                                        desktopMixedTransitionHandler.get(),
                                        desktopPipTransitionObserver,
                                        backAnimationController.get(),
                                        desktopWallpaperActivityTokenProvider,
                                        shellInit)));
@@ -1283,19 +1278,6 @@ 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(
+22 −1
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.wm.shell.dagger.pip;
import android.annotation.NonNull;
import android.content.Context;
import android.os.Handler;
import android.window.DesktopModeFlags;

import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
@@ -42,6 +43,8 @@ import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.desktopmode.DesktopPipTransitionController;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
import com.android.wm.shell.pip2.phone.PhonePipMenuController;
@@ -55,6 +58,7 @@ import com.android.wm.shell.pip2.phone.PipTransition;
import com.android.wm.shell.pip2.phone.PipTransitionState;
import com.android.wm.shell.pip2.phone.PipUiStateChangeController;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -91,12 +95,13 @@ public abstract class Pip2Module {
            DisplayController displayController,
            Optional<SplitScreenController> splitScreenControllerOptional,
            PipDesktopState pipDesktopState,
            Optional<DesktopPipTransitionController> desktopPipTransitionController,
            PipInteractionHandler pipInteractionHandler) {
        return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
                pipBoundsState, null, pipBoundsAlgorithm, pipTaskListener,
                pipScheduler, pipStackListenerController, pipDisplayLayoutState,
                pipUiStateChangeController, displayController, splitScreenControllerOptional,
                pipDesktopState, pipInteractionHandler);
                pipDesktopState, desktopPipTransitionController, pipInteractionHandler);
    }

    @WMSingleton
@@ -250,6 +255,22 @@ public abstract class Pip2Module {
                dragToDesktopTransitionHandlerOptional, rootTaskDisplayAreaOrganizer);
    }

    @WMSingleton
    @Provides
    static Optional<DesktopPipTransitionController> provideDesktopPipTransitionController(
            Context context, Optional<DesktopTasksController> desktopTasksControllerOptional,
            Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
            PipDesktopState pipDesktopState
    ) {
        if (DesktopModeStatus.canEnterDesktopMode(context)
                && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue()) {
            return Optional.of(
                    new DesktopPipTransitionController(desktopTasksControllerOptional.get(),
                            desktopUserRepositoriesOptional.get(), pipDesktopState));
        }
        return Optional.empty();
    }

    @BindsOptionalOf
    abstract DragToDesktopTransitionHandler optionalDragToDesktopTransitionHandler();

+115 −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.ActivityManager
import android.os.IBinder
import android.window.DesktopExperienceFlags
import android.window.WindowContainerTransaction
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.common.pip.PipDesktopState
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE

/**
 * Controller to perform extra handling to PiP transitions that are entering while in Desktop mode.
 */
class DesktopPipTransitionController(
    private val desktopTasksController: DesktopTasksController,
    private val desktopUserRepositories: DesktopUserRepositories,
    private val pipDesktopState: PipDesktopState,
) {

    /**
     * This is called by [PipTransition#handleRequest] when a request for entering PiP is received.
     *
     * @param wct WindowContainerTransaction that will apply these changes
     * @param transition that will apply this transaction
     * @param taskInfo of the task that is entering PiP
     */
    fun handlePipTransition(
        wct: WindowContainerTransaction,
        transition: IBinder,
        taskInfo: ActivityManager.RunningTaskInfo,
    ) {
        if (!pipDesktopState.isDesktopWindowingPipEnabled()) {
            return
        }

        // Early return if the transition is a synthetic transition that is not backed by a true
        // system transition.
        if (transition == DesktopTasksController.SYNTHETIC_TRANSITION) {
            logD("handlePipTransitionIfInDesktop: SYNTHETIC_TRANSITION, not a true transition")
            return
        }

        val taskId = taskInfo.taskId
        val displayId = taskInfo.displayId
        val desktopRepository = desktopUserRepositories.getProfile(taskInfo.userId)
        if (!desktopRepository.isAnyDeskActive(displayId)) {
            logD("handlePipTransitionIfInDesktop: PiP transition is not in Desktop session")
            return
        }

        val deskId =
            desktopRepository.getActiveDeskId(displayId)
                ?: if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
                    logW(
                        "handlePipTransitionIfInDesktop: " +
                            "Active desk not found for display id %d",
                        displayId,
                    )
                    return
                } else {
                    checkNotNull(desktopRepository.getDefaultDeskId(displayId)) {
                        "$TAG: handlePipTransitionIfInDesktop: " +
                            "Expected a default desk to exist in display with id $displayId"
                    }
                }

        val isLastTask =
            desktopRepository.isOnlyVisibleNonClosingTaskInDesk(
                taskId = taskId,
                deskId = deskId,
                displayId = displayId,
            )
        if (!isLastTask) {
            logD("handlePipTransitionIfInDesktop: PiP task is not last visible task in Desk")
            return
        }

        val desktopExitRunnable =
            desktopTasksController.performDesktopExitCleanUp(
                wct = wct,
                deskId = deskId,
                displayId = displayId,
                willExitDesktop = true,
            )
        desktopExitRunnable?.invoke(transition)
    }

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

    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 = "DesktopPipTransitionController"
    }
}
+0 −81
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"
    }
}
+22 −51
Original line number Diff line number Diff line
@@ -214,7 +214,6 @@ 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,
@@ -847,7 +846,6 @@ class DesktopTasksController(
            }
        val isMinimizingToPip =
            DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue &&
                desktopPipTransitionObserver.isPresent &&
                (taskInfo.pictureInPictureParams?.isAutoEnterEnabled ?: false)

        // If task is going to PiP, start a PiP transition instead of a minimize transition
@@ -861,25 +859,23 @@ class DesktopTasksController(
                    /* displayChange= */ null,
                    /* flags= */ 0,
                )
            val requestRes = transitions.dispatchRequest(Binder(), requestInfo, /* skip= */ null)
            val requestRes =
                transitions.dispatchRequest(SYNTHETIC_TRANSITION, requestInfo, /* skip= */ null)
            wct.merge(requestRes.second, true)

            desktopPipTransitionObserver
                .get()
                .addPendingPipTransition(
                    DesktopPipTransitionObserver.PendingPipTransition(
                        token = freeformTaskTransitionStarter.startPipTransition(wct),
                        taskId = taskInfo.taskId,
                        onSuccess = {
                            onDesktopTaskEnteredPip(
                                taskId = taskId,
            // If the task minimizing to PiP is the last task, modify wct to perform Desktop cleanup
            var desktopExitRunnable: RunOnTransitStart? = null
            if (isLastTask) {
                desktopExitRunnable =
                    performDesktopExitCleanUp(
                        wct = wct,
                        deskId = deskId,
                                displayId = taskInfo.displayId,
                                taskIsLastVisibleTaskBeforePip = isLastTask,
                            )
                        },
                    )
                        displayId = displayId,
                        willExitDesktop = true,
                    )
            }
            val transition = freeformTaskTransitionStarter.startPipTransition(wct)
            desktopExitRunnable?.invoke(transition)
        } else {
            snapEventHandler.removeTaskIfTiled(displayId, taskId)
            val willExitDesktop = willExitDesktop(taskId, displayId, forceExitDesktop = false)
@@ -1893,11 +1889,7 @@ class DesktopTasksController(
        displayId: Int,
        forceExitDesktop: Boolean,
    ): Boolean {
        if (
            forceExitDesktop &&
                (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue ||
                    DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue)
        ) {
        if (forceExitDesktop && DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.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
@@ -1914,33 +1906,6 @@ 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,
@@ -1964,7 +1929,7 @@ class DesktopTasksController(
    }

    /** TODO: b/394268248 - update [deskId] to be non-null. */
    private fun performDesktopExitCleanUp(
    fun performDesktopExitCleanUp(
        wct: WindowContainerTransaction,
        deskId: Int?,
        displayId: Int,
@@ -3967,6 +3932,12 @@ class DesktopTasksController(
                DesktopTaskToFrontReason.TASKBAR_MANAGE_WINDOW ->
                    UnminimizeReason.TASKBAR_MANAGE_WINDOW
            }

        @JvmField
        /**
         * A placeholder for a synthetic transition that isn't backed by a true system transition.
         */
        val SYNTHETIC_TRANSITION: IBinder = Binder()
    }

    /** Defines interface for classes that can listen to changes for task resize. */
Loading