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

Commit 33b797b9 authored by Merissa Mitchell's avatar Merissa Mitchell
Browse files

[PiP on Desktop] Add PipDesktopState for PiP checks in Desktop Mode.

This is a refactoring work to provide a centralized place to check PiP
in Desktop Mode status.

Bug: 350475854
Test: atest DesktopTasksTransitionObserverTest DesktopRepositoryTest
DesktopTasksControllerTest PipSchedulerTest PipDesktopStateTest
Test: Manually verify all PiP on Desktop CUJs are WAI.
Flag: com.android.window.flags.enable_desktop_windowing_pip

Change-Id: I06114e0bb4194fefa8a38cc2b4fc6c94c47f3907
parent 0af50eb3
Loading
Loading
Loading
Loading
+159 −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.common.pip;

import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;

import android.app.ActivityManager;
import android.window.DisplayAreaInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;

import com.android.window.flags.Flags;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
import com.android.wm.shell.pip2.phone.PipTransition;

import java.util.Optional;

/** Helper class for PiP on Desktop Mode. */
public class PipDesktopState {
    private final PipDisplayLayoutState mPipDisplayLayoutState;
    private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
    private final Optional<DesktopWallpaperActivityTokenProvider>
            mDesktopWallpaperActivityTokenProviderOptional;
    private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;

    public PipDesktopState(PipDisplayLayoutState pipDisplayLayoutState,
            Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
            Optional<DesktopWallpaperActivityTokenProvider>
                    desktopWallpaperActivityTokenProviderOptional,
            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
        mPipDisplayLayoutState = pipDisplayLayoutState;
        mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
        mDesktopWallpaperActivityTokenProviderOptional =
                desktopWallpaperActivityTokenProviderOptional;
        mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
    }

    /**
     * Returns whether PiP in Desktop Windowing is enabled by checking the following:
     * - Desktop Windowing in PiP flag is enabled
     * - DesktopWallpaperActivityTokenProvider is injected
     * - DesktopUserRepositories is injected
     */
    public boolean isDesktopWindowingPipEnabled() {
        return Flags.enableDesktopWindowingPip()
                && mDesktopWallpaperActivityTokenProviderOptional.isPresent()
                && mDesktopUserRepositoriesOptional.isPresent();
    }

    /** Returns whether PiP in Connected Displays is enabled by checking the flag. */
    public boolean isConnectedDisplaysPipEnabled() {
        return Flags.enableConnectedDisplaysPip();
    }

    /** Returns whether the display with the PiP task is in freeform windowing mode. */
    private boolean isDisplayInFreeform() {
        final DisplayAreaInfo tdaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
                mPipDisplayLayoutState.getDisplayId());
        if (tdaInfo != null) {
            return tdaInfo.configuration.windowConfiguration.getWindowingMode()
                    == WINDOWING_MODE_FREEFORM;
        }
        return false;
    }

    /** Returns whether PiP is exiting while we're in a Desktop Mode session. */
    private boolean isPipExitingToDesktopMode() {
        // Early return if PiP in Desktop Windowing is not supported.
        if (!isDesktopWindowingPipEnabled()) {
            return false;
        }
        final int displayId = mPipDisplayLayoutState.getDisplayId();
        return getDesktopRepository().getVisibleTaskCount(displayId) > 0
                || getDesktopWallpaperActivityTokenProvider().isWallpaperActivityVisible(displayId)
                || isDisplayInFreeform();
    }

    /** Returns whether {@param pipTask} would be entering in a Desktop Mode session. */
    public boolean isPipEnteringInDesktopMode(ActivityManager.RunningTaskInfo pipTask) {
        // Early return if PiP in Desktop Windowing is not supported.
        if (!isDesktopWindowingPipEnabled()) {
            return false;
        }
        final DesktopRepository desktopRepository = getDesktopRepository();
        return desktopRepository.getVisibleTaskCount(pipTask.getDisplayId()) > 0
                || desktopRepository.isMinimizedPipPresentInDisplay(pipTask.getDisplayId());
    }

    /**
     * Invoked when an EXIT_PiP transition is detected in {@link PipTransition}.
     * Returns whether the PiP exiting should also trigger the active Desktop Mode session to exit.
     */
    public boolean shouldExitPipExitDesktopMode() {
        // Early return if PiP in Desktop Windowing is not supported.
        if (!isDesktopWindowingPipEnabled()) {
            return false;
        }
        final int displayId = mPipDisplayLayoutState.getDisplayId();
        return getDesktopRepository().getVisibleTaskCount(displayId) == 0
                && getDesktopWallpaperActivityTokenProvider().isWallpaperActivityVisible(displayId);
    }

    /**
     * Returns a {@link WindowContainerTransaction} that reorders the {@link WindowContainerToken}
     * of the DesktopWallpaperActivity for the display with the given {@param displayId}.
     */
    public WindowContainerTransaction getWallpaperActivityTokenWct(int displayId) {
        return new WindowContainerTransaction().reorder(
                getDesktopWallpaperActivityTokenProvider().getToken(displayId), /* onTop= */ false);
    }

    /**
     * The windowing mode to restore to when resizing out of PIP direction.
     * Defaults to undefined and can be overridden to restore to an alternate windowing mode.
     */
    public int getOutPipWindowingMode() {
        // If we are exiting PiP while the device is in Desktop mode (the task should expand to
        // freeform windowing mode):
        // 1) If the display windowing mode is freeform, set windowing mode to UNDEFINED so it will
        //    resolve the windowing mode to the display's windowing mode.
        // 2) If the display windowing mode is not FREEFORM, set windowing mode to FREEFORM.
        if (isPipExitingToDesktopMode()) {
            if (isDisplayInFreeform()) {
                return WINDOWING_MODE_UNDEFINED;
            } else {
                return WINDOWING_MODE_FREEFORM;
            }
        }

        // By default, or if the task is going to fullscreen, reset the windowing mode to undefined.
        return WINDOWING_MODE_UNDEFINED;
    }

    private DesktopRepository getDesktopRepository() {
        return mDesktopUserRepositoriesOptional.get().getCurrent();
    }

    private DesktopWallpaperActivityTokenProvider getDesktopWallpaperActivityTokenProvider() {
        return mDesktopWallpaperActivityTokenProviderOptional.get();
    }
}
+18 −11
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDesktopState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
import com.android.wm.shell.common.pip.PipPerfHintController;
@@ -84,14 +85,11 @@ public abstract class Pip2Module {
            @NonNull PipDisplayLayoutState pipDisplayLayoutState,
            @NonNull PipUiStateChangeController pipUiStateChangeController,
            DisplayController displayController,
            Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
            Optional<DesktopWallpaperActivityTokenProvider>
                    desktopWallpaperActivityTokenProviderOptional) {
            PipDesktopState pipDesktopState) {
        return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
                pipBoundsState, null, pipBoundsAlgorithm, pipTaskListener,
                pipScheduler, pipStackListenerController, pipDisplayLayoutState,
                pipUiStateChangeController, displayController, desktopUserRepositoriesOptional,
                desktopWallpaperActivityTokenProviderOptional);
                pipUiStateChangeController, displayController, pipDesktopState);
    }

    @WMSingleton
@@ -142,13 +140,9 @@ public abstract class Pip2Module {
            PipBoundsState pipBoundsState,
            @ShellMainThread ShellExecutor mainExecutor,
            PipTransitionState pipTransitionState,
            Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
            Optional<DesktopWallpaperActivityTokenProvider>
                    desktopWallpaperActivityTokenProviderOptional,
            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
            PipDesktopState pipDesktopState) {
        return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState,
                desktopUserRepositoriesOptional, desktopWallpaperActivityTokenProviderOptional,
                rootTaskDisplayAreaOrganizer);
                pipDesktopState);
    }

    @WMSingleton
@@ -233,4 +227,17 @@ public abstract class Pip2Module {
        return new PipTaskListener(context, shellTaskOrganizer, pipTransitionState,
                pipScheduler, pipBoundsState, pipBoundsAlgorithm, mainExecutor);
    }

    @WMSingleton
    @Provides
    static PipDesktopState providePipDesktopState(
            PipDisplayLayoutState pipDisplayLayoutState,
            Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
            Optional<DesktopWallpaperActivityTokenProvider>
                    desktopWallpaperActivityTokenProviderOptional,
            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
    ) {
        return new PipDesktopState(pipDisplayLayoutState, desktopUserRepositoriesOptional,
                desktopWallpaperActivityTokenProviderOptional, rootTaskDisplayAreaOrganizer);
    }
}
+5 −73
Original line number Diff line number Diff line
@@ -16,14 +16,10 @@

package com.android.wm.shell.pip2.phone;

import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;

import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.view.SurfaceControl;
import android.window.DisplayAreaInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;

@@ -32,20 +28,14 @@ import androidx.annotation.Nullable;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.window.flags.Flags;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
import com.android.wm.shell.common.pip.PipDesktopState;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
import com.android.wm.shell.protolog.ShellProtoLogGroup;

import java.util.Objects;
import java.util.Optional;

/**
 * Scheduler for Shell initiated PiP transitions and animations.
 */
@@ -56,10 +46,7 @@ public class PipScheduler {
    private final PipBoundsState mPipBoundsState;
    private final ShellExecutor mMainExecutor;
    private final PipTransitionState mPipTransitionState;
    private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
    private final Optional<DesktopWallpaperActivityTokenProvider>
            mDesktopWallpaperActivityTokenProviderOptional;
    private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
    private final PipDesktopState mPipDesktopState;
    private PipTransitionController mPipTransitionController;
    private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
            mSurfaceControlTransactionFactory;
@@ -72,18 +59,12 @@ public class PipScheduler {
            PipBoundsState pipBoundsState,
            ShellExecutor mainExecutor,
            PipTransitionState pipTransitionState,
            Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
            Optional<DesktopWallpaperActivityTokenProvider>
                    desktopWallpaperActivityTokenProviderOptional,
            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
            PipDesktopState pipDesktopState) {
        mContext = context;
        mPipBoundsState = pipBoundsState;
        mMainExecutor = mainExecutor;
        mPipTransitionState = pipTransitionState;
        mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
        mDesktopWallpaperActivityTokenProviderOptional =
                desktopWallpaperActivityTokenProviderOptional;
        mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
        mPipDesktopState = pipDesktopState;

        mSurfaceControlTransactionFactory =
                new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
@@ -105,7 +86,7 @@ public class PipScheduler {
        wct.setBounds(pipTaskToken, null);
        // if we are hitting a multi-activity case
        // windowing mode change will reparent to original host task
        wct.setWindowingMode(pipTaskToken, getOutPipWindowingMode());
        wct.setWindowingMode(pipTaskToken, mPipDesktopState.getOutPipWindowingMode());
        return wct;
    }

@@ -235,55 +216,6 @@ public class PipScheduler {
        maybeUpdateMovementBounds();
    }

    /** Returns whether the display is in freeform windowing mode. */
    private boolean isDisplayInFreeform() {
        final DisplayAreaInfo tdaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
                Objects.requireNonNull(mPipTransitionState.getPipTaskInfo()).displayId);
        if (tdaInfo != null) {
            return tdaInfo.configuration.windowConfiguration.getWindowingMode()
                    == WINDOWING_MODE_FREEFORM;
        }
        return false;
    }

    /** Returns whether PiP is exiting while we're in desktop mode. */
    private boolean isPipExitingToDesktopMode() {
        // Early return if PiP in Desktop Windowing is not supported.
        if (!Flags.enableDesktopWindowingPip() || mDesktopUserRepositoriesOptional.isEmpty()
                || mDesktopWallpaperActivityTokenProviderOptional.isEmpty()) {
            return false;
        }
        final int displayId = Objects.requireNonNull(
                mPipTransitionState.getPipTaskInfo()).displayId;
        return mDesktopUserRepositoriesOptional.get().getCurrent().getVisibleTaskCount(displayId)
                > 0
                || mDesktopWallpaperActivityTokenProviderOptional.get().isWallpaperActivityVisible(
                displayId)
                || isDisplayInFreeform();
    }

    /**
     * The windowing mode to restore to when resizing out of PIP direction. Defaults to undefined
     * and can be overridden to restore to an alternate windowing mode.
     */
    private int getOutPipWindowingMode() {
        // If we are exiting PiP while the device is in Desktop mode (the task should expand to
        // freeform windowing mode):
        // 1) If the display windowing mode is freeform, set windowing mode to undefined so it will
        //    resolve the windowing mode to the display's windowing mode.
        // 2) If the display windowing mode is not freeform, set windowing mode to freeform.
        if (isPipExitingToDesktopMode()) {
            if (isDisplayInFreeform()) {
                return WINDOWING_MODE_UNDEFINED;
            } else {
                return WINDOWING_MODE_FREEFORM;
            }
        }

        // By default, or if the task is going to fullscreen, reset the windowing mode to undefined.
        return WINDOWING_MODE_UNDEFINED;
    }

    @VisibleForTesting
    void setSurfaceControlTransactionFactory(
            @NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
+14 −43
Original line number Diff line number Diff line
@@ -56,19 +56,16 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;

import com.android.internal.util.Preconditions;
import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ComponentUtils;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDesktopState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
@@ -79,8 +76,6 @@ import com.android.wm.shell.shared.pip.PipContentOverlay;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;

import java.util.Optional;

/**
 * Implementation of transitions for PiP on phone.
 */
@@ -117,9 +112,7 @@ public class PipTransition extends PipTransitionController implements
    private final PipDisplayLayoutState mPipDisplayLayoutState;
    private final DisplayController mDisplayController;
    private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
    private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
    private final Optional<DesktopWallpaperActivityTokenProvider>
            mDesktopWallpaperActivityTokenProviderOptional;
    private final PipDesktopState mPipDesktopState;

    //
    // Transition caches
@@ -159,9 +152,7 @@ public class PipTransition extends PipTransitionController implements
            PipDisplayLayoutState pipDisplayLayoutState,
            PipUiStateChangeController pipUiStateChangeController,
            DisplayController displayController,
            Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
            Optional<DesktopWallpaperActivityTokenProvider>
                    desktopWallpaperActivityTokenProviderOptional) {
            PipDesktopState pipDesktopState) {
        super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
                pipBoundsAlgorithm);

@@ -174,9 +165,7 @@ public class PipTransition extends PipTransitionController implements
        mPipDisplayLayoutState = pipDisplayLayoutState;
        mDisplayController = displayController;
        mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(mContext);
        mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
        mDesktopWallpaperActivityTokenProviderOptional =
                desktopWallpaperActivityTokenProviderOptional;
        mPipDesktopState = pipDesktopState;
    }

    @Override
@@ -800,7 +789,7 @@ public class PipTransition extends PipTransitionController implements
                    && getFixedRotationDelta(info, pipTaskChange) == ROTATION_90) {
                adjustedSourceRectHint.offset(cutoutInsets.left, cutoutInsets.top);
            }
            if (Flags.enableDesktopWindowingPip()) {
            if (mPipDesktopState.isDesktopWindowingPipEnabled()) {
                adjustedSourceRectHint.offset(-pipActivityChange.getStartAbsBounds().left,
                        -pipActivityChange.getStartAbsBounds().top);
            }
@@ -860,7 +849,7 @@ public class PipTransition extends PipTransitionController implements

        // If PiP is enabled on Connected Displays, update PipDisplayLayoutState to have the correct
        // display info that PiP is entering in.
        if (Flags.enableConnectedDisplaysPip()) {
        if (mPipDesktopState.isConnectedDisplaysPipEnabled()) {
            final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(
                    pipTask.displayId);
            if (displayLayout != null) {
@@ -908,12 +897,7 @@ public class PipTransition extends PipTransitionController implements
        // Since opening a new task while in Desktop Mode always first open in Fullscreen
        // until DesktopMode Shell code resolves it to Freeform, PipTransition will get a
        // possibility to handle it also. In this case return false to not have it enter PiP.
        final boolean isInDesktopSession = !mDesktopUserRepositoriesOptional.isEmpty()
                && (mDesktopUserRepositoriesOptional.get().getCurrent().getVisibleTaskCount(
                        pipTask.displayId) > 0
                    || mDesktopUserRepositoriesOptional.get().getCurrent()
                        .isMinimizedPipPresentInDisplay(pipTask.displayId));
        if (isInDesktopSession) {
        if (mPipDesktopState.isPipEnteringInDesktopMode(pipTask)) {
            return false;
        }

@@ -1087,27 +1071,14 @@ public class PipTransition extends PipTransitionController implements
                        "Unexpected bundle for " + mPipTransitionState);
                break;
            case PipTransitionState.EXITED_PIP:
                final TaskInfo pipTask = mPipTransitionState.getPipTaskInfo();
                final boolean desktopPipEnabled = Flags.enableDesktopWindowingPip()
                        && mDesktopUserRepositoriesOptional.isPresent()
                        && mDesktopWallpaperActivityTokenProviderOptional.isPresent();
                if (desktopPipEnabled && pipTask != null) {
                    final DesktopRepository desktopRepository =
                            mDesktopUserRepositoriesOptional.get().getCurrent();
                    final boolean wallpaperIsVisible =
                            mDesktopWallpaperActivityTokenProviderOptional.get()
                                    .isWallpaperActivityVisible(pipTask.displayId);
                    if (desktopRepository.getVisibleTaskCount(pipTask.displayId) == 0
                            && wallpaperIsVisible) {
                if (mPipDesktopState.shouldExitPipExitDesktopMode()) {
                    mTransitions.startTransition(
                            TRANSIT_TO_BACK,
                                new WindowContainerTransaction().reorder(
                                        mDesktopWallpaperActivityTokenProviderOptional.get()
                                                .getToken(pipTask.displayId), /* onTop= */ false),
                                null
                            mPipDesktopState.getWallpaperActivityTokenWct(
                                    mPipTransitionState.getPipTaskInfo().getDisplayId()),
                            null /* firstHandler */
                    );
                }
                }
                mPipTransitionState.setPinnedTaskLeash(null);
                mPipTransitionState.setPipTaskInfo(null);
                mPendingRemoveWithFadeout = false;
+204 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading