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

Commit fe8af78f authored by Matt Sziklay's avatar Matt Sziklay Committed by Android (Google) Code Review
Browse files

Merge "Add tests for VisualIndicatorViewContainer." into main

parents 7d2ddd33 d091f023
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -49,7 +49,8 @@ import com.android.wm.shell.windowdecor.WindowDecoration.SurfaceControlViewHostF
 * Container for the view / viewhost of the indicator, ensuring it is created / animated off the
 * main thread.
 */
private class VisualIndicatorViewContainer
@VisibleForTesting
class VisualIndicatorViewContainer
@JvmOverloads
constructor(
    @ShellDesktopThread private val desktopExecutor: ShellExecutor,
+250 −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.app.ActivityManager.RunningTaskInfo
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.Display
import android.view.SurfaceControl
import android.view.SurfaceControlViewHost
import android.view.View
import androidx.test.filters.SmallTest
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.shared.bubbles.BubbleDropTargetBoundsProvider
import com.android.wm.shell.windowdecor.WindowDecoration.SurfaceControlViewHostFactory
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.eq
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyZeroInteractions
import org.mockito.kotlin.whenever

/**
 * Test class for [VisualIndicatorViewContainer] and [VisualIndicatorAnimator]
 *
 * Usage: atest WMShellUnitTests:VisualIndicatorViewContainerTest
 */
@SmallTest
@RunWithLooper
@RunWith(AndroidTestingRunner::class)
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
class VisualIndicatorViewContainerTest : ShellTestCase() {
    @Mock private lateinit var view: View
    @Mock private lateinit var displayLayout: DisplayLayout
    @Mock private lateinit var displayController: DisplayController
    @Mock private lateinit var taskSurface: SurfaceControl
    @Mock private lateinit var syncQueue: SyncTransactionQueue
    @Mock private lateinit var mockSurfaceControlViewHostFactory: SurfaceControlViewHostFactory
    @Mock private lateinit var mockBackground: LayerDrawable
    @Mock private lateinit var bubbleDropTargetBoundsProvider: BubbleDropTargetBoundsProvider
    private val taskInfo: RunningTaskInfo = createTaskInfo()
    private val mainExecutor = TestShellExecutor()
    private val desktopExecutor = TestShellExecutor()

    @Before
    fun setUp() {
        whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
        whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
            (i.arguments.first() as Rect).set(DISPLAY_BOUNDS)
        }
        whenever(mockSurfaceControlViewHostFactory.create(any(), any(), any()))
            .thenReturn(mock(SurfaceControlViewHost::class.java))
    }

    @Test
    fun testTransitionIndicator_sameTypeReturnsEarly() {
        val spyViewContainer = setupSpyViewContainer()
        // Test early return on startType == endType.
        spyViewContainer.transitionIndicator(
            taskInfo,
            displayController,
            DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
            DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
        )
        desktopExecutor.flushAll()
        verify(spyViewContainer)
            .transitionIndicator(
                eq(taskInfo),
                eq(displayController),
                eq(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR),
                eq(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR),
            )
        // Assert fadeIn, fadeOut, and animateIndicatorType were not called.
        verifyZeroInteractions(spyViewContainer)
    }

    @Test
    fun testTransitionIndicator_firstTypeNoIndicator_callsFadeIn() {
        val spyViewContainer = setupSpyViewContainer()
        spyViewContainer.transitionIndicator(
            taskInfo,
            displayController,
            DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR,
            DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
        )
        desktopExecutor.flushAll()
        verify(spyViewContainer).fadeInIndicator(any(), any())
    }

    @Test
    fun testTransitionIndicator_secondTypeNoIndicator_callsFadeOut() {
        val spyViewContainer = setupSpyViewContainer()
        spyViewContainer.transitionIndicator(
            taskInfo,
            displayController,
            DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
            DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR,
        )
        desktopExecutor.flushAll()
        verify(spyViewContainer)
            .fadeOutIndicator(
                any(),
                eq(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR),
                anyOrNull(),
            )
    }

    @Test
    fun testTransitionIndicator_differentTypes_callsTransitionIndicator() {
        val spyViewContainer = setupSpyViewContainer()
        spyViewContainer.transitionIndicator(
            taskInfo,
            displayController,
            DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
            DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR,
        )
        desktopExecutor.flushAll()
        verify(spyViewContainer)
            .transitionIndicator(
                any(),
                any(),
                eq(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR),
                eq(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR),
            )
    }

    @Test
    fun testFadeInBoundsCalculation() {
        val spyIndicator = setupSpyViewContainer()
        val animator =
            spyIndicator.indicatorView?.let {
                VisualIndicatorViewContainer.VisualIndicatorAnimator.fadeBoundsIn(
                    it,
                    DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
                    displayLayout,
                    bubbleDropTargetBoundsProvider,
                )
            }
        assertThat(animator?.indicatorStartBounds).isEqualTo(Rect(15, 15, 985, 985))
        assertThat(animator?.indicatorEndBounds).isEqualTo(Rect(0, 0, 1000, 1000))
    }

    @Test
    fun testFadeOutBoundsCalculation() {
        val spyIndicator = setupSpyViewContainer()
        val animator =
            spyIndicator.indicatorView?.let {
                VisualIndicatorViewContainer.VisualIndicatorAnimator.fadeBoundsOut(
                    it,
                    DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
                    displayLayout,
                    bubbleDropTargetBoundsProvider,
                )
            }
        assertThat(animator?.indicatorStartBounds).isEqualTo(Rect(0, 0, 1000, 1000))
        assertThat(animator?.indicatorEndBounds).isEqualTo(Rect(15, 15, 985, 985))
    }

    @Test
    fun testChangeIndicatorTypeBoundsCalculation() {
        // Test fullscreen to split-left bounds.
        var animator =
            VisualIndicatorViewContainer.VisualIndicatorAnimator.animateIndicatorType(
                view,
                displayLayout,
                DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
                DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR,
                bubbleDropTargetBoundsProvider,
            )
        // Test desktop to split-right bounds.
        animator =
            VisualIndicatorViewContainer.VisualIndicatorAnimator.animateIndicatorType(
                view,
                displayLayout,
                DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
                DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR,
                bubbleDropTargetBoundsProvider,
            )
    }

    private fun setupSpyViewContainer(): VisualIndicatorViewContainer {
        val viewContainer =
            VisualIndicatorViewContainer(
                desktopExecutor,
                mainExecutor,
                SurfaceControl.Builder(),
                syncQueue,
                mockSurfaceControlViewHostFactory,
                bubbleDropTargetBoundsProvider,
            )
        viewContainer.createView(
            context,
            mock(Display::class.java),
            displayLayout,
            taskInfo,
            taskSurface,
        )
        desktopExecutor.flushAll()
        viewContainer.indicatorView?.background = mockBackground
        whenever(mockBackground.findDrawableByLayerId(anyInt()))
            .thenReturn(mock(Drawable::class.java))
        return spy(viewContainer)
    }

    private fun createTaskInfo(): RunningTaskInfo {
        val taskDescriptionBuilder = ActivityManager.TaskDescription.Builder()
        return TestRunningTaskInfoBuilder()
            .setDisplayId(Display.DEFAULT_DISPLAY)
            .setTaskDescriptionBuilder(taskDescriptionBuilder)
            .setVisible(true)
            .build()
    }

    companion object {
        private val DISPLAY_BOUNDS = Rect(0, 0, 1000, 1000)
    }
}