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

Commit e472a3f7 authored by Merissa Mitchell's avatar Merissa Mitchell
Browse files

Convert PipDesktopState and PipDesktopStateTest to Kotlin.

Bug: 350475854
Test: atest PipDesktopStateTest
Flag: EXEMPT refactor
Change-Id: I1e2c7aa15fce70e5c95af506c48292c96cc45e2e
parent 2666d613
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
    }
}