Start fold animation only when the large screen is visible

Before, when there was a device state override, it was possible to trigger the fold animation on the smaller screen as well.

Now, the fold (closing) animation is triggered only when we're on the large display.

Bug: 268343352
Test: DeviceFoldStateProviderTest + manual test
Change-Id: Ib573b308edfb1954b36718722ffe1eafefa568ce
This commit is contained in:
Nicolo' Mazzucato
2023-03-20 23:19:34 +00:00
parent dd5d243b29
commit d40289686c
3 changed files with 97 additions and 27 deletions

View File

@@ -16,6 +16,9 @@
package com.android.systemui.unfold.updates
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.core.util.Consumer
@@ -33,6 +36,7 @@ import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenLis
import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityProvider
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Executor
import org.junit.Before
@@ -49,20 +53,19 @@ import org.mockito.MockitoAnnotations
@SmallTest
class DeviceFoldStateProviderTest : SysuiTestCase() {
@Mock
private lateinit var activityTypeProvider: ActivityManagerActivityTypeProvider
@Mock private lateinit var activityTypeProvider: ActivityManagerActivityTypeProvider
@Mock
private lateinit var handler: Handler
@Mock private lateinit var handler: Handler
@Mock
private lateinit var rotationChangeProvider: RotationChangeProvider
@Mock private lateinit var rotationChangeProvider: RotationChangeProvider
@Mock
private lateinit var unfoldKeyguardVisibilityProvider: UnfoldKeyguardVisibilityProvider
@Mock private lateinit var unfoldKeyguardVisibilityProvider: UnfoldKeyguardVisibilityProvider
@Captor
private lateinit var rotationListener: ArgumentCaptor<RotationListener>
@Mock private lateinit var resources: Resources
@Mock private lateinit var context: Context
@Captor private lateinit var rotationListener: ArgumentCaptor<RotationListener>
private val foldProvider = TestFoldProvider()
private val screenOnStatusProvider = TestScreenOnStatusProvider()
@@ -81,10 +84,13 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
val config = object : UnfoldTransitionConfig by ResourceUnfoldTransitionConfig() {
override val halfFoldedTimeoutMillis: Int
get() = HALF_OPENED_TIMEOUT_MILLIS.toInt()
}
val config =
object : UnfoldTransitionConfig by ResourceUnfoldTransitionConfig() {
override val halfFoldedTimeoutMillis: Int
get() = HALF_OPENED_TIMEOUT_MILLIS.toInt()
}
whenever(context.resources).thenReturn(resources)
whenever(context.mainExecutor).thenReturn(mContext.mainExecutor)
foldStateProvider =
DeviceFoldStateProvider(
@@ -95,6 +101,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
activityTypeProvider,
unfoldKeyguardVisibilityProvider,
rotationChangeProvider,
context,
context.mainExecutor,
handler
)
@@ -112,7 +119,8 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
override fun onUnfoldedScreenAvailable() {
unfoldedScreenAvailabilityUpdates.add(Unit)
}
})
}
)
foldStateProvider.start()
verify(rotationChangeProvider).addCallback(capture(rotationListener))
@@ -134,6 +142,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
// By default, we're on launcher.
setupForegroundActivityType(isHomeActivity = true)
setIsLargeScreen(true)
}
@Test
@@ -181,7 +190,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
sendHingeAngleEvent(10)
assertThat(foldUpdates)
.containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING)
.containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING)
assertThat(unfoldedScreenAvailabilityUpdates).hasSize(1)
}
@@ -386,8 +395,10 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
setInitialHingeAngle(START_CLOSING_ON_APPS_THRESHOLD_DEGREES)
sendHingeAngleEvent(
START_CLOSING_ON_APPS_THRESHOLD_DEGREES -
HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() - 1)
START_CLOSING_ON_APPS_THRESHOLD_DEGREES -
HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() -
1
)
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
}
@@ -429,8 +440,10 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
setInitialHingeAngle(START_CLOSING_ON_APPS_THRESHOLD_DEGREES)
sendHingeAngleEvent(
START_CLOSING_ON_APPS_THRESHOLD_DEGREES -
HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() - 1)
START_CLOSING_ON_APPS_THRESHOLD_DEGREES -
HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() -
1
)
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
}
@@ -470,7 +483,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
sendHingeAngleEvent(130)
sendHingeAngleEvent(120)
assertThat(foldUpdates)
.containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING)
.containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING)
}
@Test
@@ -531,8 +544,8 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
rotationListener.value.onRotationChanged(1)
assertThat(foldUpdates).containsExactly(
FOLD_UPDATE_START_OPENING, FOLD_UPDATE_FINISH_HALF_OPEN)
assertThat(foldUpdates)
.containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_FINISH_HALF_OPEN)
}
@Test
@@ -545,6 +558,45 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_FINISH_CLOSED)
}
@Test
fun onFolding_onSmallScreen_tansitionDoesNotStart() {
setIsLargeScreen(false)
setInitialHingeAngle(120)
sendHingeAngleEvent(110)
sendHingeAngleEvent(100)
assertThat(foldUpdates).isEmpty()
}
@Test
fun onFolding_onLargeScreen_tansitionStarts() {
setIsLargeScreen(true)
setInitialHingeAngle(120)
sendHingeAngleEvent(110)
sendHingeAngleEvent(100)
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
}
@Test
fun onUnfold_onSmallScreen_emitsStartOpening() {
// the new display state might arrive later, so it shouldn't be used to decide to send the
// start opening event, but only for the closing.
setFoldState(folded = true)
setIsLargeScreen(false)
foldUpdates.clear()
setFoldState(folded = false)
screenOnStatusProvider.notifyScreenTurningOn()
sendHingeAngleEvent(10)
sendHingeAngleEvent(20)
screenOnStatusProvider.notifyScreenTurnedOn()
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING)
}
private fun setupForegroundActivityType(isHomeActivity: Boolean?) {
whenever(activityTypeProvider.isHomeActivity).thenReturn(isHomeActivity)
}
@@ -566,6 +618,13 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
foldProvider.notifyFolded(folded)
}
private fun setIsLargeScreen(isLargeScreen: Boolean) {
val smallestScreenWidth = if (isLargeScreen) { 601 } else { 10 }
val configuration = Configuration()
configuration.smallestScreenWidthDp = smallestScreenWidth
whenever(resources.configuration).thenReturn(configuration)
}
private fun fireScreenOnEvent() {
screenOnStatusProvider.notifyScreenTurnedOn()
}

View File

@@ -53,4 +53,4 @@ class ScreenSizeFoldProvider(private val context: Context) : FoldProvider {
}
}
private const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600
internal const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600

View File

@@ -15,12 +15,14 @@
*/
package com.android.systemui.unfold.updates
import android.content.Context
import android.os.Handler
import android.os.Trace
import android.util.Log
import androidx.annotation.FloatRange
import androidx.annotation.VisibleForTesting
import androidx.core.util.Consumer
import com.android.systemui.unfold.compat.INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.dagger.UnfoldMain
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
@@ -45,6 +47,7 @@ constructor(
private val activityTypeProvider: CurrentActivityTypeProvider,
private val unfoldKeyguardVisibilityProvider: UnfoldKeyguardVisibilityProvider,
private val rotationChangeProvider: RotationChangeProvider,
private val context: Context,
@UnfoldMain private val mainExecutor: Executor,
@UnfoldMain private val handler: Handler
) : FoldStateProvider {
@@ -119,7 +122,7 @@ constructor(
"lastHingeAngle: $lastHingeAngle, " +
"lastHingeAngleBeforeTransition: $lastHingeAngleBeforeTransition"
)
Trace.setCounter( "hinge_angle", angle.toLong())
Trace.setCounter("hinge_angle", angle.toLong())
}
val currentDirection =
@@ -136,6 +139,7 @@ constructor(
val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES
val eventNotAlreadyDispatched = lastFoldUpdate != transitionUpdate
val screenAvailableEventSent = isUnfoldHandled
val isOnLargeScreen = isOnLargeScreen()
if (
angleChangeSurpassedThreshold && // Do not react immediately to small changes in angle
@@ -144,7 +148,9 @@ constructor(
// angle range as closing threshold could overlap this range
screenAvailableEventSent && // do not send transition event if we are still in the
// process of turning on the inner display
isClosingThresholdMet(angle) // hinge angle is below certain threshold.
isClosingThresholdMet(angle) && // hinge angle is below certain threshold.
isOnLargeScreen // Avoids sending closing event when on small screen.
// Start event is sent regardless due to hall sensor.
) {
notifyFoldUpdate(transitionUpdate, lastHingeAngle)
}
@@ -233,7 +239,7 @@ constructor(
}
private fun cancelAnimation(): Unit =
notifyFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN, lastHingeAngle)
notifyFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN, lastHingeAngle)
private inner class ScreenStatusListener : ScreenStatusProvider.ScreenListener {
@@ -261,6 +267,11 @@ constructor(
}
}
private fun isOnLargeScreen(): Boolean {
return context.resources.configuration.smallestScreenWidthDp >
INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP
}
/** While the screen is off or the device is folded, hinge angle updates are not needed. */
private fun updateHingeAngleProviderState() {
if (isScreenOn && !isFolded) {