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

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

Merge "Convert PipDesktopState and PipDesktopStateTest to Kotlin." into main

parents 9ec9a261 e472a3f7
Loading
Loading
Loading
Loading
+96 −0
Original line number Diff line number Diff line
@@ -13,110 +13,84 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.wm.shell.common.pip

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.window.DesktopExperienceFlags;
import android.window.DesktopModeFlags;
import android.window.DisplayAreaInfo;

import com.android.wm.shell.Flags;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;

import java.util.Optional;
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.window.DesktopExperienceFlags
import android.window.DesktopModeFlags
import com.android.wm.shell.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.desktopmode.DesktopUserRepositories
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler
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<DragToDesktopTransitionHandler> mDragToDesktopTransitionHandlerOptional;
    private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;

    public PipDesktopState(PipDisplayLayoutState pipDisplayLayoutState,
            Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
            Optional<DragToDesktopTransitionHandler> dragToDesktopTransitionHandlerOptional,
            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
        mPipDisplayLayoutState = pipDisplayLayoutState;
        mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
        mDragToDesktopTransitionHandlerOptional = dragToDesktopTransitionHandlerOptional;
        mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
    }

class PipDesktopState(
    private val pipDisplayLayoutState: PipDisplayLayoutState,
    private val desktopUserRepositoriesOptional: Optional<DesktopUserRepositories>,
    private val dragToDesktopTransitionHandlerOptional: Optional<DragToDesktopTransitionHandler>,
    private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
) {
    /**
     * Returns whether PiP in Desktop Windowing is enabled by checking the following:
     * - PiP in Desktop Windowing flag is enabled
     * - DesktopUserRepositories is injected
     * - DragToDesktopTransitionHandler is injected
     * - DesktopUserRepositories is present
     * - DragToDesktopTransitionHandler is present
     */
    public boolean isDesktopWindowingPipEnabled() {
        return DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue()
                && mDesktopUserRepositoriesOptional.isPresent()
                && mDragToDesktopTransitionHandlerOptional.isPresent();
    }
    fun isDesktopWindowingPipEnabled(): Boolean =
        DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue &&
                desktopUserRepositoriesOptional.isPresent &&
                dragToDesktopTransitionHandlerOptional.isPresent

    /**
     * Returns whether PiP in Connected Displays is enabled by checking the following:
     * - PiP in Connected Displays flag is enabled
     * - PiP2 flag is enabled
     */
    public boolean isConnectedDisplaysPipEnabled() {
        return DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue() && Flags.enablePip2();
    }
    fun isConnectedDisplaysPipEnabled(): Boolean =
        DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue && Flags.enablePip2()

    /** 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;
    private fun isDisplayInFreeform(): Boolean {
        val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
            pipDisplayLayoutState.displayId
        )

        return tdaInfo?.configuration?.windowConfiguration?.windowingMode == WINDOWING_MODE_FREEFORM
    }

    /** Returns whether PiP is active in a display that is in active Desktop Mode session. */
    public boolean isPipInDesktopMode() {
        // Early return if PiP in Desktop Windowing is not supported.
    fun isPipInDesktopMode(): Boolean {
        if (!isDesktopWindowingPipEnabled()) {
            return false;
            return false
        }
        final int displayId = mPipDisplayLayoutState.getDisplayId();
        return mDesktopUserRepositoriesOptional.get().getCurrent().isAnyDeskActive(displayId);

        val displayId = pipDisplayLayoutState.displayId
        return desktopUserRepositoriesOptional.get().current.isAnyDeskActive(displayId)
    }

    /**
     * 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):
    /** Returns the windowing mode to restore to when resizing out of PIP direction. */
    // TODO(b/403345629): Update this for Multi-Desktop.
    fun getOutPipWindowingMode(): Int {
        // 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 (isPipInDesktopMode()) {
            if (isDisplayInFreeform()) {
                return WINDOWING_MODE_UNDEFINED;
            return if (isDisplayInFreeform()) {
                WINDOWING_MODE_UNDEFINED
            } else {
                return WINDOWING_MODE_FREEFORM;
                WINDOWING_MODE_FREEFORM
            }
        }

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

    /** Returns whether there is a drag-to-desktop transition in progress. */
    public boolean isDragToDesktopInProgress() {
        // Early return if PiP in Desktop Windowing is not supported.
        if (!isDesktopWindowingPipEnabled()) {
            return false;
        }
        return mDragToDesktopTransitionHandlerOptional.get().getInProgress();
    }
    fun isDragToDesktopInProgress(): Boolean =
        isDesktopWindowingPipEnabled() && dragToDesktopTransitionHandlerOptional.get().inProgress
}
+0 −184
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_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;

import static com.android.window.flags.Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_PIP;
import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP;
import static com.android.wm.shell.Flags.FLAG_ENABLE_PIP2;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;

import static org.mockito.Mockito.when;

import android.app.ActivityManager;
import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.window.DisplayAreaInfo;
import android.window.WindowContainerToken;

import androidx.test.filters.SmallTest;

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.DragToDesktopTransitionHandler;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

import java.util.Optional;

/**
 * Unit test against {@link PipDesktopState}.
 */
@SmallTest
@TestableLooper.RunWithLooper
@RunWith(AndroidTestingRunner.class)
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
public class PipDesktopStateTest {
    @Mock private PipDisplayLayoutState mMockPipDisplayLayoutState;
    @Mock private Optional<DesktopUserRepositories> mMockDesktopUserRepositoriesOptional;
    @Mock private DesktopUserRepositories mMockDesktopUserRepositories;
    @Mock private DesktopRepository mMockDesktopRepository;
    @Mock
    private Optional<DragToDesktopTransitionHandler> mMockDragToDesktopTransitionHandlerOptional;
    @Mock private DragToDesktopTransitionHandler mMockDragToDesktopTransitionHandler;

    @Mock private RootTaskDisplayAreaOrganizer mMockRootTaskDisplayAreaOrganizer;
    @Mock private ActivityManager.RunningTaskInfo mMockTaskInfo;

    private static final int DISPLAY_ID = 1;
    private DisplayAreaInfo mDefaultTda;
    private PipDesktopState mPipDesktopState;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        when(mMockDesktopUserRepositoriesOptional.get()).thenReturn(mMockDesktopUserRepositories);
        when(mMockDesktopUserRepositories.getCurrent()).thenReturn(mMockDesktopRepository);
        when(mMockDesktopUserRepositoriesOptional.isPresent()).thenReturn(true);

        when(mMockDragToDesktopTransitionHandlerOptional.get()).thenReturn(
                mMockDragToDesktopTransitionHandler);
        when(mMockDragToDesktopTransitionHandlerOptional.isPresent()).thenReturn(true);

        when(mMockTaskInfo.getDisplayId()).thenReturn(DISPLAY_ID);
        when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(DISPLAY_ID);

        mDefaultTda = new DisplayAreaInfo(Mockito.mock(WindowContainerToken.class), DISPLAY_ID, 0);
        when(mMockRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DISPLAY_ID)).thenReturn(
                mDefaultTda);

        mPipDesktopState = new PipDesktopState(mMockPipDisplayLayoutState,
                mMockDesktopUserRepositoriesOptional,
                mMockDragToDesktopTransitionHandlerOptional,
                mMockRootTaskDisplayAreaOrganizer);
    }

    @Test
    public void isDesktopWindowingPipEnabled_returnsTrue() {
        assertTrue(mPipDesktopState.isDesktopWindowingPipEnabled());
    }

    @Test
    public void isDesktopWindowingPipEnabled_desktopRepositoryEmpty_returnsFalse() {
        when(mMockDesktopUserRepositoriesOptional.isPresent()).thenReturn(false);

        assertFalse(mPipDesktopState.isDesktopWindowingPipEnabled());
    }

    @Test
    public void isDesktopWindowingPipEnabled_dragToDesktopTransitionHandlerEmpty_returnsFalse() {
        when(mMockDragToDesktopTransitionHandlerOptional.isPresent()).thenReturn(false);

        assertFalse(mPipDesktopState.isDesktopWindowingPipEnabled());
    }

    @Test
    @EnableFlags({
            FLAG_ENABLE_CONNECTED_DISPLAYS_PIP, FLAG_ENABLE_PIP2
    })
    public void isConnectedDisplaysPipEnabled_returnsTrue() {
        assertTrue(mPipDesktopState.isConnectedDisplaysPipEnabled());
    }

    @Test
    public void isPipInDesktopMode_anyDeskActive_returnsTrue() {
        when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true);

        assertTrue(mPipDesktopState.isPipInDesktopMode());
    }

    @Test
    public void isPipInDesktopMode_noDeskActive_returnsFalse() {
        when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(false);

        assertFalse(mPipDesktopState.isPipInDesktopMode());
    }

    @Test
    public void getOutPipWindowingMode_exitToDesktop_displayFreeform_returnsUndefined() {
        when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true);
        setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);

        assertEquals(WINDOWING_MODE_UNDEFINED, mPipDesktopState.getOutPipWindowingMode());
    }

    @Test
    public void getOutPipWindowingMode_exitToDesktop_displayFullscreen_returnsFreeform() {
        when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true);
        setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN);

        assertEquals(WINDOWING_MODE_FREEFORM, mPipDesktopState.getOutPipWindowingMode());
    }

    @Test
    public void getOutPipWindowingMode_exitToFullscreen_displayFullscreen_returnsUndefined() {
        setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN);

        assertEquals(WINDOWING_MODE_UNDEFINED, mPipDesktopState.getOutPipWindowingMode());
    }

    @Test
    public void isDragToDesktopInProgress_inProgress_returnsTrue() {
        when(mMockDragToDesktopTransitionHandler.getInProgress()).thenReturn(true);

        assertTrue(mPipDesktopState.isDragToDesktopInProgress());
    }

    @Test
    public void isDragToDesktopInProgress_notInProgress_returnsFalse() {
        when(mMockDragToDesktopTransitionHandler.getInProgress()).thenReturn(false);

        assertFalse(mPipDesktopState.isDragToDesktopInProgress());
    }

    private void setDisplayWindowingMode(int windowingMode) {
        mDefaultTda.configuration.windowConfiguration.setWindowingMode(windowingMode);
    }
}
+153 −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 android.app.ActivityManager
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.window.DisplayAreaInfo
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
import com.android.window.flags.Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_PIP
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP
import com.android.wm.shell.Flags.FLAG_ENABLE_PIP2
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopUserRepositories
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever

/**
 * Unit test against [PipDesktopState].
 */
@SmallTest
@RunWithLooper
@RunWith(AndroidTestingRunner::class)
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
class PipDesktopStateTest : ShellTestCase() {
    private val mockPipDisplayLayoutState = mock<PipDisplayLayoutState>()
    private val mockDesktopUserRepositories = mock<DesktopUserRepositories>()
    private val mockDesktopRepository = mock<DesktopRepository>()
    private val mockDragToDesktopTransitionHandler = mock<DragToDesktopTransitionHandler>()
    private val mockRootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>()
    private val mockTaskInfo = mock<ActivityManager.RunningTaskInfo>()
    private lateinit var defaultTda: DisplayAreaInfo
    private lateinit var pipDesktopState: PipDesktopState

    @Before
    fun setUp() {
        whenever(mockDesktopUserRepositories.current).thenReturn(mockDesktopRepository)
        whenever(mockTaskInfo.getDisplayId()).thenReturn(DISPLAY_ID)
        whenever(mockPipDisplayLayoutState.displayId).thenReturn(DISPLAY_ID)

        defaultTda = DisplayAreaInfo(mock<WindowContainerToken>(), DISPLAY_ID, /* featureId = */ 0)
        whenever(mockRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DISPLAY_ID)).thenReturn(
            defaultTda
        )

        pipDesktopState =
            PipDesktopState(
                mockPipDisplayLayoutState,
                Optional.of(mockDesktopUserRepositories),
                Optional.of(mockDragToDesktopTransitionHandler),
                mockRootTaskDisplayAreaOrganizer
            )
    }

    @Test
    fun isDesktopWindowingPipEnabled_returnsTrue() {
        assertThat(pipDesktopState.isDesktopWindowingPipEnabled()).isTrue()
    }

    @Test
    @EnableFlags(
        FLAG_ENABLE_CONNECTED_DISPLAYS_PIP,
        FLAG_ENABLE_PIP2
    )
    fun isConnectedDisplaysPipEnabled_returnsTrue() {
        assertThat(pipDesktopState.isConnectedDisplaysPipEnabled()).isTrue()
    }

    @Test
    fun isPipInDesktopMode_anyDeskActive_returnsTrue() {
        whenever(mockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true)

        assertThat(pipDesktopState.isPipInDesktopMode()).isTrue()
    }

    @Test
    fun isPipInDesktopMode_noDeskActive_returnsFalse() {
        whenever(mockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(false)

        assertThat(pipDesktopState.isPipInDesktopMode()).isFalse()
    }

    @Test
    fun outPipWindowingMode_exitToDesktop_displayFreeform_returnsUndefined() {
        whenever(mockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true)
        setDisplayWindowingMode(WINDOWING_MODE_FREEFORM)

        assertThat(pipDesktopState.getOutPipWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED)
    }

    @Test
    fun outPipWindowingMode_exitToDesktop_displayFullscreen_returnsFreeform() {
        whenever(mockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true)
        setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN)

        assertThat(pipDesktopState.getOutPipWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM)
    }

    @Test
    fun outPipWindowingMode_exitToFullscreen_displayFullscreen_returnsUndefined() {
        setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN)

        assertThat(pipDesktopState.getOutPipWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED)
    }

    @Test
    fun isDragToDesktopInProgress_inProgress_returnsTrue() {
        whenever(mockDragToDesktopTransitionHandler.inProgress).thenReturn(true)

        assertThat(pipDesktopState.isDragToDesktopInProgress()).isTrue()
    }

    @Test
    fun isDragToDesktopInProgress_notInProgress_returnsFalse() {
        whenever(mockDragToDesktopTransitionHandler.inProgress).thenReturn(false)

        assertThat(pipDesktopState.isDragToDesktopInProgress()).isFalse()
    }

    private fun setDisplayWindowingMode(windowingMode: Int) {
        defaultTda.configuration.windowConfiguration.windowingMode = windowingMode
    }

    companion object {
        private const val DISPLAY_ID = 1
    }
}