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

Commit df7aaca9 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes I44b48979,Ic209a812 into main

* changes:
  Add an infix `to` as a shortcut for showing overlays.
  [bc25] Add Dual Shade invocation zone detection.
parents fbda5031 19b01af0
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -128,7 +128,11 @@ fun SceneContainer(
                }
            },
    ) {
        SceneTransitionLayout(state = state, modifier = modifier.fillMaxSize()) {
        SceneTransitionLayout(
            state = state,
            modifier = modifier.fillMaxSize(),
            swipeSourceDetector = viewModel.edgeDetector,
        ) {
            sceneByKey.forEach { (sceneKey, scene) ->
                scene(
                    key = sceneKey,
+4 −0
Original line number Diff line number Diff line
@@ -379,6 +379,10 @@ sealed class UserAction {
        return this to UserActionResult(toScene = scene)
    }

    infix fun to(overlay: OverlayKey): Pair<UserAction, UserActionResult> {
        return this to UserActionResult(toOverlay = overlay)
    }

    /** Resolve this into a [Resolved] user action given [layoutDirection]. */
    internal abstract fun resolve(layoutDirection: LayoutDirection): Resolved

+54 −0
Original line number Diff line number Diff line
@@ -18,9 +18,12 @@

package com.android.systemui.scene.ui.viewmodel

import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.MotionEvent
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.DefaultEdgeDetector
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.domain.interactor.falsingInteractor
import com.android.systemui.classifier.fakeFalsingManager
@@ -37,6 +40,10 @@ import com.android.systemui.scene.shared.logger.sceneLogger
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -60,6 +67,7 @@ class SceneContainerViewModelTest : SysuiTestCase() {
    private val testScope by lazy { kosmos.testScope }
    private val sceneInteractor by lazy { kosmos.sceneInteractor }
    private val fakeSceneDataSource by lazy { kosmos.fakeSceneDataSource }
    private val fakeShadeRepository by lazy { kosmos.fakeShadeRepository }
    private val sceneContainerConfig by lazy { kosmos.sceneContainerConfig }
    private val falsingManager by lazy { kosmos.fakeFalsingManager }

@@ -75,6 +83,8 @@ class SceneContainerViewModelTest : SysuiTestCase() {
                sceneInteractor = sceneInteractor,
                falsingInteractor = kosmos.falsingInteractor,
                powerInteractor = kosmos.powerInteractor,
                shadeInteractor = kosmos.shadeInteractor,
                splitEdgeDetector = kosmos.splitEdgeDetector,
                logger = kosmos.sceneLogger,
                motionEventHandlerReceiver = { motionEventHandler ->
                    this@SceneContainerViewModelTest.motionEventHandler = motionEventHandler
@@ -287,4 +297,48 @@ class SceneContainerViewModelTest : SysuiTestCase() {

            assertThat(actionableContentKey).isEqualTo(Overlays.QuickSettingsShade)
        }

    @Test
    @DisableFlags(DualShade.FLAG_NAME)
    fun edgeDetector_singleShade_usesDefaultEdgeDetector() =
        testScope.runTest {
            val shadeMode by collectLastValue(kosmos.shadeInteractor.shadeMode)
            fakeShadeRepository.setShadeLayoutWide(false)
            assertThat(shadeMode).isEqualTo(ShadeMode.Single)

            assertThat(underTest.edgeDetector).isEqualTo(DefaultEdgeDetector)
        }

    @Test
    @DisableFlags(DualShade.FLAG_NAME)
    fun edgeDetector_splitShade_usesDefaultEdgeDetector() =
        testScope.runTest {
            val shadeMode by collectLastValue(kosmos.shadeInteractor.shadeMode)
            fakeShadeRepository.setShadeLayoutWide(true)
            assertThat(shadeMode).isEqualTo(ShadeMode.Split)

            assertThat(underTest.edgeDetector).isEqualTo(DefaultEdgeDetector)
        }

    @Test
    @EnableFlags(DualShade.FLAG_NAME)
    fun edgeDetector_dualShade_narrowScreen_usesSplitEdgeDetector() =
        testScope.runTest {
            val shadeMode by collectLastValue(kosmos.shadeInteractor.shadeMode)
            fakeShadeRepository.setShadeLayoutWide(false)

            assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
            assertThat(underTest.edgeDetector).isEqualTo(kosmos.splitEdgeDetector)
        }

    @Test
    @EnableFlags(DualShade.FLAG_NAME)
    fun edgeDetector_dualShade_wideScreen_usesSplitEdgeDetector() =
        testScope.runTest {
            val shadeMode by collectLastValue(kosmos.shadeInteractor.shadeMode)
            fakeShadeRepository.setShadeLayoutWide(true)

            assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
            assertThat(underTest.edgeDetector).isEqualTo(kosmos.splitEdgeDetector)
        }
}
+274 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.systemui.scene.ui.viewmodel

import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge.Bottom
import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge.End
import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge.Left
import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge.Right
import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge.Start
import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge.TopEnd
import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge.TopLeft
import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge.TopRight
import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge.TopStart
import com.google.common.truth.Truth.assertThat
import kotlin.test.assertFailsWith
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class SplitEdgeDetectorTest : SysuiTestCase() {

    private val edgeSize = 40
    private val screenWidth = 800
    private val screenHeight = 600

    private var edgeSplitFraction = 0.7f

    private val underTest =
        SplitEdgeDetector(
            topEdgeSplitFraction = { edgeSplitFraction },
            edgeSize = edgeSize.dp,
        )

    @Test
    fun source_noEdge_detectsNothing() {
        val detectedEdge =
            swipeVerticallyFrom(
                x = screenWidth / 2,
                y = screenHeight / 2,
            )
        assertThat(detectedEdge).isNull()
    }

    @Test
    fun source_swipeVerticallyOnTopLeft_detectsTopLeft() {
        val detectedEdge =
            swipeVerticallyFrom(
                x = 1,
                y = edgeSize - 1,
            )
        assertThat(detectedEdge).isEqualTo(TopLeft)
    }

    @Test
    fun source_swipeHorizontallyOnTopLeft_detectsLeft() {
        val detectedEdge =
            swipeHorizontallyFrom(
                x = 1,
                y = edgeSize - 1,
            )
        assertThat(detectedEdge).isEqualTo(Left)
    }

    @Test
    fun source_swipeVerticallyOnTopRight_detectsTopRight() {
        val detectedEdge =
            swipeVerticallyFrom(
                x = screenWidth - 1,
                y = edgeSize - 1,
            )
        assertThat(detectedEdge).isEqualTo(TopRight)
    }

    @Test
    fun source_swipeHorizontallyOnTopRight_detectsRight() {
        val detectedEdge =
            swipeHorizontallyFrom(
                x = screenWidth - 1,
                y = edgeSize - 1,
            )
        assertThat(detectedEdge).isEqualTo(Right)
    }

    @Test
    fun source_swipeVerticallyToLeftOfSplit_detectsTopLeft() {
        val detectedEdge =
            swipeVerticallyFrom(
                x = (screenWidth * edgeSplitFraction).toInt() - 1,
                y = edgeSize - 1,
            )
        assertThat(detectedEdge).isEqualTo(TopLeft)
    }

    @Test
    fun source_swipeVerticallyToRightOfSplit_detectsTopRight() {
        val detectedEdge =
            swipeVerticallyFrom(
                x = (screenWidth * edgeSplitFraction).toInt() + 1,
                y = edgeSize - 1,
            )
        assertThat(detectedEdge).isEqualTo(TopRight)
    }

    @Test
    fun source_edgeSplitFractionUpdatesDynamically() {
        val middleX = (screenWidth * 0.5f).toInt()
        val topY = 0

        // Split closer to the right; middle of screen is considered "left".
        edgeSplitFraction = 0.6f
        assertThat(swipeVerticallyFrom(x = middleX, y = topY)).isEqualTo(TopLeft)

        // Split closer to the left; middle of screen is considered "right".
        edgeSplitFraction = 0.4f
        assertThat(swipeVerticallyFrom(x = middleX, y = topY)).isEqualTo(TopRight)

        // Illegal fraction.
        edgeSplitFraction = 1.2f
        assertFailsWith<IllegalArgumentException> { swipeVerticallyFrom(x = middleX, y = topY) }

        // Illegal fraction.
        edgeSplitFraction = -0.3f
        assertFailsWith<IllegalArgumentException> { swipeVerticallyFrom(x = middleX, y = topY) }
    }

    @Test
    fun source_swipeVerticallyOnBottom_detectsBottom() {
        val detectedEdge =
            swipeVerticallyFrom(
                x = screenWidth / 3,
                y = screenHeight - (edgeSize / 2),
            )
        assertThat(detectedEdge).isEqualTo(Bottom)
    }

    @Test
    fun source_swipeHorizontallyOnBottom_detectsNothing() {
        val detectedEdge =
            swipeHorizontallyFrom(
                x = screenWidth / 3,
                y = screenHeight - (edgeSize - 1),
            )
        assertThat(detectedEdge).isNull()
    }

    @Test
    fun source_swipeHorizontallyOnLeft_detectsLeft() {
        val detectedEdge =
            swipeHorizontallyFrom(
                x = edgeSize - 1,
                y = screenHeight / 2,
            )
        assertThat(detectedEdge).isEqualTo(Left)
    }

    @Test
    fun source_swipeVerticallyOnLeft_detectsNothing() {
        val detectedEdge =
            swipeVerticallyFrom(
                x = edgeSize - 1,
                y = screenHeight / 2,
            )
        assertThat(detectedEdge).isNull()
    }

    @Test
    fun source_swipeHorizontallyOnRight_detectsRight() {
        val detectedEdge =
            swipeHorizontallyFrom(
                x = screenWidth - edgeSize + 1,
                y = screenHeight / 2,
            )
        assertThat(detectedEdge).isEqualTo(Right)
    }

    @Test
    fun source_swipeVerticallyOnRight_detectsNothing() {
        val detectedEdge =
            swipeVerticallyFrom(
                x = screenWidth - edgeSize + 1,
                y = screenHeight / 2,
            )
        assertThat(detectedEdge).isNull()
    }

    @Test
    fun resolve_startInLtr_resolvesLeft() {
        val resolvedEdge = Start.resolve(LayoutDirection.Ltr)
        assertThat(resolvedEdge).isEqualTo(SceneContainerEdge.Resolved.Left)
    }

    @Test
    fun resolve_startInRtl_resolvesRight() {
        val resolvedEdge = Start.resolve(LayoutDirection.Rtl)
        assertThat(resolvedEdge).isEqualTo(SceneContainerEdge.Resolved.Right)
    }

    @Test
    fun resolve_endInLtr_resolvesRight() {
        val resolvedEdge = End.resolve(LayoutDirection.Ltr)
        assertThat(resolvedEdge).isEqualTo(SceneContainerEdge.Resolved.Right)
    }

    @Test
    fun resolve_endInRtl_resolvesLeft() {
        val resolvedEdge = End.resolve(LayoutDirection.Rtl)
        assertThat(resolvedEdge).isEqualTo(SceneContainerEdge.Resolved.Left)
    }

    @Test
    fun resolve_topStartInLtr_resolvesTopLeft() {
        val resolvedEdge = TopStart.resolve(LayoutDirection.Ltr)
        assertThat(resolvedEdge).isEqualTo(SceneContainerEdge.Resolved.TopLeft)
    }

    @Test
    fun resolve_topStartInRtl_resolvesTopRight() {
        val resolvedEdge = TopStart.resolve(LayoutDirection.Rtl)
        assertThat(resolvedEdge).isEqualTo(SceneContainerEdge.Resolved.TopRight)
    }

    @Test
    fun resolve_topEndInLtr_resolvesTopRight() {
        val resolvedEdge = TopEnd.resolve(LayoutDirection.Ltr)
        assertThat(resolvedEdge).isEqualTo(SceneContainerEdge.Resolved.TopRight)
    }

    @Test
    fun resolve_topEndInRtl_resolvesTopLeft() {
        val resolvedEdge = TopEnd.resolve(LayoutDirection.Rtl)
        assertThat(resolvedEdge).isEqualTo(SceneContainerEdge.Resolved.TopLeft)
    }

    private fun swipeVerticallyFrom(x: Int, y: Int): SceneContainerEdge? {
        return swipeFrom(x, y, Orientation.Vertical)
    }

    private fun swipeHorizontallyFrom(x: Int, y: Int): SceneContainerEdge? {
        return swipeFrom(x, y, Orientation.Horizontal)
    }

    private fun swipeFrom(x: Int, y: Int, orientation: Orientation): SceneContainerEdge? {
        return underTest.source(
            layoutSize = IntSize(width = screenWidth, height = screenHeight),
            position = IntOffset(x, y),
            density = Density(1f),
            orientation = orientation,
        )
    }
}
+33 −13
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@ import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.parameterizeSceneContainerFlag
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -39,6 +38,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.shade.shared.flag.DualShade
@@ -66,18 +66,18 @@ import platform.test.runner.parameterized.Parameters
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
class ShadeInteractorImplTest(flags: FlagsParameterization) : SysuiTestCase() {
    val kosmos = testKosmos()
    val testScope = kosmos.testScope
    val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
    val deviceProvisioningRepository by lazy { kosmos.fakeDeviceProvisioningRepository }
    val disableFlagsRepository by lazy { kosmos.fakeDisableFlagsRepository }
    val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
    val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
    val powerRepository by lazy { kosmos.fakePowerRepository }
    val shadeTestUtil by lazy { kosmos.shadeTestUtil }
    val userRepository by lazy { kosmos.fakeUserRepository }
    val userSetupRepository by lazy { kosmos.fakeUserSetupRepository }
    val dozeParameters by lazy { kosmos.dozeParameters }
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val deviceProvisioningRepository by lazy { kosmos.fakeDeviceProvisioningRepository }
    private val disableFlagsRepository by lazy { kosmos.fakeDisableFlagsRepository }
    private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
    private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
    private val powerRepository by lazy { kosmos.fakePowerRepository }
    private val shadeRepository by lazy { kosmos.fakeShadeRepository }
    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
    private val userRepository by lazy { kosmos.fakeUserRepository }
    private val userSetupRepository by lazy { kosmos.fakeUserSetupRepository }
    private val dozeParameters by lazy { kosmos.dozeParameters }

    lateinit var underTest: ShadeInteractorImpl

@@ -497,4 +497,24 @@ class ShadeInteractorImplTest(flags: FlagsParameterization) : SysuiTestCase() {

            assertThat(shadeMode).isEqualTo(ShadeMode.Dual)
        }

    @Test
    fun getTopEdgeSplitFraction_narrowScreen_splitInHalf() =
        testScope.runTest {
            // Ensure isShadeLayoutWide is collected.
            val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide)
            shadeRepository.setShadeLayoutWide(false)

            assertThat(underTest.getTopEdgeSplitFraction()).isEqualTo(0.5f)
        }

    @Test
    fun getTopEdgeSplitFraction_wideScreen_leftSideLarger() =
        testScope.runTest {
            // Ensure isShadeLayoutWide is collected.
            val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide)
            shadeRepository.setShadeLayoutWide(true)

            assertThat(underTest.getTopEdgeSplitFraction()).isGreaterThan(0.5f)
        }
}
Loading