Merge "Improve fold state provider logic handling posture" into sc-v2-dev

This commit is contained in:
TreeHugger Robot
2021-12-08 16:28:45 +00:00
committed by Android (Google) Code Review
5 changed files with 229 additions and 58 deletions

View File

@@ -60,14 +60,12 @@ fun createUnfoldTransitionProgressProvider(
hingeAngleProvider, hingeAngleProvider,
screenStatusProvider, screenStatusProvider,
deviceStateManager, deviceStateManager,
mainExecutor mainExecutor,
mainHandler
) )
val unfoldTransitionProgressProvider = if (config.isHingeAngleEnabled) { val unfoldTransitionProgressProvider = if (config.isHingeAngleEnabled) {
PhysicsBasedUnfoldTransitionProgressProvider( PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider)
mainHandler,
foldStateProvider
)
} else { } else {
FixedTimingTransitionProgressProvider(foldStateProvider) FixedTimingTransitionProgressProvider(foldStateProvider)
} }

View File

@@ -15,7 +15,6 @@
*/ */
package com.android.systemui.unfold.progress package com.android.systemui.unfold.progress
import android.os.Handler
import android.util.Log import android.util.Log
import android.util.MathUtils.saturate import android.util.MathUtils.saturate
import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.DynamicAnimation
@@ -24,9 +23,10 @@ import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce import androidx.dynamicanimation.animation.SpringForce
import com.android.systemui.unfold.UnfoldTransitionProgressProvider import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.updates.FOLD_UPDATE_ABORTED
import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE
import com.android.systemui.unfold.updates.FoldStateProvider import com.android.systemui.unfold.updates.FoldStateProvider
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
@@ -39,7 +39,6 @@ import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
* - doesn't handle postures * - doesn't handle postures
*/ */
internal class PhysicsBasedUnfoldTransitionProgressProvider( internal class PhysicsBasedUnfoldTransitionProgressProvider(
private val handler: Handler,
private val foldStateProvider: FoldStateProvider private val foldStateProvider: FoldStateProvider
) : ) :
UnfoldTransitionProgressProvider, UnfoldTransitionProgressProvider,
@@ -51,8 +50,6 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
addEndListener(this@PhysicsBasedUnfoldTransitionProgressProvider) addEndListener(this@PhysicsBasedUnfoldTransitionProgressProvider)
} }
private val timeoutRunnable = TimeoutRunnable()
private var isTransitionRunning = false private var isTransitionRunning = false
private var isAnimatedCancelRunning = false private var isAnimatedCancelRunning = false
@@ -92,7 +89,7 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
cancelTransition(endValue = 1f, animate = true) cancelTransition(endValue = 1f, animate = true)
} }
} }
FOLD_UPDATE_FINISH_FULL_OPEN -> { FOLD_UPDATE_FINISH_FULL_OPEN, FOLD_UPDATE_ABORTED -> {
// Do not cancel if we haven't started the transition yet. // Do not cancel if we haven't started the transition yet.
// This could happen when we fully unfolded the device before the screen // This could happen when we fully unfolded the device before the screen
// became available. In this case we start and immediately cancel the animation // became available. In this case we start and immediately cancel the animation
@@ -106,7 +103,11 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
cancelTransition(endValue = 0f, animate = false) cancelTransition(endValue = 0f, animate = false)
} }
FOLD_UPDATE_START_CLOSING -> { FOLD_UPDATE_START_CLOSING -> {
startTransition(startValue = 1f) // The transition might be already running as the device might start closing several
// times before reaching an end state.
if (!isTransitionRunning) {
startTransition(startValue = 1f)
}
} }
} }
@@ -116,8 +117,6 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
} }
private fun cancelTransition(endValue: Float, animate: Boolean) { private fun cancelTransition(endValue: Float, animate: Boolean) {
handler.removeCallbacks(timeoutRunnable)
if (isTransitionRunning && animate) { if (isTransitionRunning && animate) {
isAnimatedCancelRunning = true isAnimatedCancelRunning = true
springAnimation.animateToFinalPosition(endValue) springAnimation.animateToFinalPosition(endValue)
@@ -175,8 +174,6 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
} }
springAnimation.start() springAnimation.start()
handler.postDelayed(timeoutRunnable, TRANSITION_TIMEOUT_MILLIS)
} }
override fun addCallback(listener: TransitionProgressListener) { override fun addCallback(listener: TransitionProgressListener) {
@@ -187,13 +184,6 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
listeners.remove(listener) listeners.remove(listener)
} }
private inner class TimeoutRunnable : Runnable {
override fun run() {
cancelTransition(endValue = 1f, animate = true)
}
}
private object AnimationProgressProperty : private object AnimationProgressProperty :
FloatPropertyCompat<PhysicsBasedUnfoldTransitionProgressProvider>("animation_progress") { FloatPropertyCompat<PhysicsBasedUnfoldTransitionProgressProvider>("animation_progress") {
@@ -212,7 +202,6 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider" private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider"
private const val DEBUG = true private const val DEBUG = true
private const val TRANSITION_TIMEOUT_MILLIS = 2000L
private const val SPRING_STIFFNESS = 200.0f private const val SPRING_STIFFNESS = 200.0f
private const val MINIMAL_VISIBLE_CHANGE = 0.001f private const val MINIMAL_VISIBLE_CHANGE = 0.001f
private const val FINAL_HINGE_ANGLE_POSITION = 165f private const val FINAL_HINGE_ANGLE_POSITION = 165f

View File

@@ -15,14 +15,19 @@
*/ */
package com.android.systemui.unfold.updates package com.android.systemui.unfold.updates
import android.annotation.FloatRange
import android.content.Context import android.content.Context
import android.hardware.devicestate.DeviceStateManager import android.hardware.devicestate.DeviceStateManager
import android.os.Handler
import android.util.Log
import androidx.annotation.VisibleForTesting
import androidx.core.util.Consumer import androidx.core.util.Consumer
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
import com.android.systemui.unfold.updates.hinge.FULLY_CLOSED_DEGREES
import com.android.systemui.unfold.updates.hinge.FULLY_OPEN_DEGREES import com.android.systemui.unfold.updates.hinge.FULLY_OPEN_DEGREES
import com.android.systemui.unfold.updates.hinge.HingeAngleProvider import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import java.util.concurrent.Executor import java.util.concurrent.Executor
class DeviceFoldStateProvider( class DeviceFoldStateProvider(
@@ -30,7 +35,8 @@ class DeviceFoldStateProvider(
private val hingeAngleProvider: HingeAngleProvider, private val hingeAngleProvider: HingeAngleProvider,
private val screenStatusProvider: ScreenStatusProvider, private val screenStatusProvider: ScreenStatusProvider,
private val deviceStateManager: DeviceStateManager, private val deviceStateManager: DeviceStateManager,
private val mainExecutor: Executor private val mainExecutor: Executor,
private val handler: Handler
) : FoldStateProvider { ) : FoldStateProvider {
private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf() private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf()
@@ -38,9 +44,13 @@ class DeviceFoldStateProvider(
@FoldUpdate @FoldUpdate
private var lastFoldUpdate: Int? = null private var lastFoldUpdate: Int? = null
@FloatRange(from = 0.0, to = 180.0)
private var lastHingeAngle: Float = 0f
private val hingeAngleListener = HingeAngleListener() private val hingeAngleListener = HingeAngleListener()
private val screenListener = ScreenStatusListener() private val screenListener = ScreenStatusListener()
private val foldStateListener = FoldStateListener(context) private val foldStateListener = FoldStateListener(context)
private val timeoutRunnable = TimeoutRunnable()
private var isFolded = false private var isFolded = false
private var isUnfoldHandled = true private var isUnfoldHandled = true
@@ -72,47 +82,69 @@ class DeviceFoldStateProvider(
override val isFullyOpened: Boolean override val isFullyOpened: Boolean
get() = !isFolded && lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN get() = !isFolded && lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN
private val isTransitionInProgess: Boolean
get() = lastFoldUpdate == FOLD_UPDATE_START_OPENING ||
lastFoldUpdate == FOLD_UPDATE_START_CLOSING
private fun onHingeAngle(angle: Float) { private fun onHingeAngle(angle: Float) {
when (lastFoldUpdate) { if (DEBUG) { Log.d(TAG, "Hinge angle: $angle, lastHingeAngle: $lastHingeAngle") }
FOLD_UPDATE_FINISH_FULL_OPEN -> {
if (FULLY_OPEN_DEGREES - angle > START_CLOSING_THRESHOLD_DEGREES) { val isClosing = angle < lastHingeAngle
lastFoldUpdate = FOLD_UPDATE_START_CLOSING val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES
outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_START_CLOSING) } val closingEventDispatched = lastFoldUpdate == FOLD_UPDATE_START_CLOSING
}
} if (isClosing && !closingEventDispatched && !isFullyOpened) {
FOLD_UPDATE_START_OPENING -> { notifyFoldUpdate(FOLD_UPDATE_START_CLOSING)
if (FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES) { }
lastFoldUpdate = FOLD_UPDATE_FINISH_FULL_OPEN
outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) } if (isTransitionInProgess) {
} if (isFullyOpened) {
} notifyFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN)
FOLD_UPDATE_START_CLOSING -> { cancelTimeout()
if (FULLY_OPEN_DEGREES - angle < START_CLOSING_THRESHOLD_DEGREES) { } else {
lastFoldUpdate = FOLD_UPDATE_FINISH_FULL_OPEN // The timeout will trigger some constant time after the last angle update.
outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) } rescheduleAbortAnimationTimeout()
}
} }
} }
lastHingeAngle = angle
outputListeners.forEach { it.onHingeAngleUpdate(angle) } outputListeners.forEach { it.onHingeAngleUpdate(angle) }
} }
private inner class FoldStateListener(context: Context) : private inner class FoldStateListener(context: Context) :
DeviceStateManager.FoldStateListener(context, { folded: Boolean -> DeviceStateManager.FoldStateListener(context, { folded: Boolean ->
isFolded = folded isFolded = folded
lastHingeAngle = FULLY_CLOSED_DEGREES
if (folded) { if (folded) {
lastFoldUpdate = FOLD_UPDATE_FINISH_CLOSED
outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) }
hingeAngleProvider.stop() hingeAngleProvider.stop()
notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
cancelTimeout()
isUnfoldHandled = false isUnfoldHandled = false
} else { } else {
lastFoldUpdate = FOLD_UPDATE_START_OPENING notifyFoldUpdate(FOLD_UPDATE_START_OPENING)
outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_START_OPENING) } rescheduleAbortAnimationTimeout()
hingeAngleProvider.start() hingeAngleProvider.start()
} }
}) })
private fun notifyFoldUpdate(@FoldUpdate update: Int) {
if (DEBUG) { Log.d(TAG, stateToString(update)) }
outputListeners.forEach { it.onFoldUpdate(update) }
lastFoldUpdate = update
}
private fun rescheduleAbortAnimationTimeout() {
if (isTransitionInProgess) {
cancelTimeout()
}
handler.postDelayed(timeoutRunnable, ABORT_CLOSING_MILLIS)
}
private fun cancelTimeout() {
handler.removeCallbacks(timeoutRunnable)
}
private inner class ScreenStatusListener : private inner class ScreenStatusListener :
ScreenStatusProvider.ScreenListener { ScreenStatusProvider.ScreenListener {
@@ -136,7 +168,39 @@ class DeviceFoldStateProvider(
onHingeAngle(angle) onHingeAngle(angle)
} }
} }
private inner class TimeoutRunnable : Runnable {
override fun run() {
notifyFoldUpdate(FOLD_UPDATE_ABORTED)
}
}
} }
private const val START_CLOSING_THRESHOLD_DEGREES = 95f private fun stateToString(@FoldUpdate update: Int): String {
private const val FULLY_OPEN_THRESHOLD_DEGREES = 15f return when (update) {
FOLD_UPDATE_START_OPENING -> "START_OPENING"
FOLD_UPDATE_HALF_OPEN -> "HALF_OPEN"
FOLD_UPDATE_START_CLOSING -> "START_CLOSING"
FOLD_UPDATE_ABORTED -> "ABORTED"
FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> "UNFOLDED_SCREEN_AVAILABLE"
FOLD_UPDATE_FINISH_HALF_OPEN -> "FINISH_HALF_OPEN"
FOLD_UPDATE_FINISH_FULL_OPEN -> "FINISH_FULL_OPEN"
FOLD_UPDATE_FINISH_CLOSED -> "FINISH_CLOSED"
else -> "UNKNOWN"
}
}
private const val TAG = "DeviceFoldProvider"
private const val DEBUG = false
/**
* Time after which [FOLD_UPDATE_ABORTED] is emitted following a [FOLD_UPDATE_START_CLOSING] or
* [FOLD_UPDATE_START_OPENING] event, if an end state is not reached.
*/
@VisibleForTesting
const val ABORT_CLOSING_MILLIS = 1000L
/** Threshold after which we consider the device fully unfolded. */
@VisibleForTesting
const val FULLY_OPEN_THRESHOLD_DEGREES = 15f

View File

@@ -39,6 +39,7 @@ interface FoldStateProvider : CallbackController<FoldUpdatesListener> {
FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_OPENING,
FOLD_UPDATE_HALF_OPEN, FOLD_UPDATE_HALF_OPEN,
FOLD_UPDATE_START_CLOSING, FOLD_UPDATE_START_CLOSING,
FOLD_UPDATE_ABORTED,
FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE, FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE,
FOLD_UPDATE_FINISH_HALF_OPEN, FOLD_UPDATE_FINISH_HALF_OPEN,
FOLD_UPDATE_FINISH_FULL_OPEN, FOLD_UPDATE_FINISH_FULL_OPEN,
@@ -51,7 +52,8 @@ interface FoldStateProvider : CallbackController<FoldUpdatesListener> {
const val FOLD_UPDATE_START_OPENING = 0 const val FOLD_UPDATE_START_OPENING = 0
const val FOLD_UPDATE_HALF_OPEN = 1 const val FOLD_UPDATE_HALF_OPEN = 1
const val FOLD_UPDATE_START_CLOSING = 2 const val FOLD_UPDATE_START_CLOSING = 2
const val FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE = 3 const val FOLD_UPDATE_ABORTED = 3
const val FOLD_UPDATE_FINISH_HALF_OPEN = 4 const val FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE = 4
const val FOLD_UPDATE_FINISH_FULL_OPEN = 5 const val FOLD_UPDATE_FINISH_HALF_OPEN = 5
const val FOLD_UPDATE_FINISH_CLOSED = 6 const val FOLD_UPDATE_FINISH_FULL_OPEN = 6
const val FOLD_UPDATE_FINISH_CLOSED = 7

View File

@@ -18,7 +18,9 @@ package com.android.systemui.unfold.updates
import android.hardware.devicestate.DeviceStateManager import android.hardware.devicestate.DeviceStateManager
import android.hardware.devicestate.DeviceStateManager.FoldStateListener import android.hardware.devicestate.DeviceStateManager.FoldStateListener
import android.os.Handler
import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner
import androidx.core.util.Consumer
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.updates.hinge.HingeAngleProvider import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
@@ -31,9 +33,12 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
import java.lang.Exception
@RunWith(AndroidTestingRunner::class) @RunWith(AndroidTestingRunner::class)
@SmallTest @SmallTest
@@ -48,16 +53,28 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Mock @Mock
private lateinit var deviceStateManager: DeviceStateManager private lateinit var deviceStateManager: DeviceStateManager
private lateinit var foldStateProvider: FoldStateProvider @Mock
private lateinit var handler: Handler
@Captor
private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
@Captor
private lateinit var screenOnListenerCaptor: ArgumentCaptor<ScreenListener>
@Captor
private lateinit var hingeAngleCaptor: ArgumentCaptor<Consumer<Float>>
private lateinit var foldStateProvider: DeviceFoldStateProvider
private val foldUpdates: MutableList<Int> = arrayListOf() private val foldUpdates: MutableList<Int> = arrayListOf()
private val hingeAngleUpdates: MutableList<Float> = arrayListOf() private val hingeAngleUpdates: MutableList<Float> = arrayListOf()
private val foldStateListenerCaptor = ArgumentCaptor.forClass(FoldStateListener::class.java)
private var foldedDeviceState: Int = 0 private var foldedDeviceState: Int = 0
private var unfoldedDeviceState: Int = 0 private var unfoldedDeviceState: Int = 0
private val screenOnListenerCaptor = ArgumentCaptor.forClass(ScreenListener::class.java) private var scheduledRunnable: Runnable? = null
private var scheduledRunnableDelay: Long? = null
@Before @Before
fun setUp() { fun setUp() {
@@ -75,7 +92,8 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
hingeAngleProvider, hingeAngleProvider,
screenStatusProvider, screenStatusProvider,
deviceStateManager, deviceStateManager,
context.mainExecutor context.mainExecutor,
handler
) )
foldStateProvider.addCallback(object : FoldStateProvider.FoldUpdatesListener { foldStateProvider.addCallback(object : FoldStateProvider.FoldUpdatesListener {
@@ -91,6 +109,22 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture()) verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
verify(screenStatusProvider).addCallback(screenOnListenerCaptor.capture()) verify(screenStatusProvider).addCallback(screenOnListenerCaptor.capture())
verify(hingeAngleProvider).addCallback(hingeAngleCaptor.capture())
whenever(handler.postDelayed(any<Runnable>(), any())).then { invocationOnMock ->
scheduledRunnable = invocationOnMock.getArgument<Runnable>(0)
scheduledRunnableDelay = invocationOnMock.getArgument<Long>(1)
null
}
whenever(handler.removeCallbacks(any<Runnable>())).then { invocationOnMock ->
val removedRunnable = invocationOnMock.getArgument<Runnable>(0)
if (removedRunnable == scheduledRunnable) {
scheduledRunnableDelay = null
scheduledRunnable = null
}
null
}
} }
@Test @Test
@@ -167,6 +201,86 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
assertThat(foldUpdates).isEmpty() assertThat(foldUpdates).isEmpty()
} }
@Test
fun startClosingEvent_afterTimeout_abortEmitted() {
sendHingeAngleEvent(90)
sendHingeAngleEvent(80)
simulateTimeout(ABORT_CLOSING_MILLIS)
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING, FOLD_UPDATE_ABORTED)
}
@Test
fun startClosingEvent_beforeTimeout_abortNotEmitted() {
sendHingeAngleEvent(90)
sendHingeAngleEvent(80)
simulateTimeout(ABORT_CLOSING_MILLIS - 1)
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
}
@Test
fun startClosingEvent_eventBeforeTimeout_oneEventEmitted() {
sendHingeAngleEvent(180)
sendHingeAngleEvent(90)
simulateTimeout(ABORT_CLOSING_MILLIS - 1)
sendHingeAngleEvent(80)
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
}
@Test
fun startClosingEvent_timeoutAfterTimeoutRescheduled_abortEmitted() {
sendHingeAngleEvent(180)
sendHingeAngleEvent(90)
simulateTimeout(ABORT_CLOSING_MILLIS - 1) // The timeout should not trigger here.
sendHingeAngleEvent(80)
simulateTimeout(ABORT_CLOSING_MILLIS) // The timeout should trigger here.
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING, FOLD_UPDATE_ABORTED)
}
@Test
fun startClosingEvent_shortTimeBetween_emitsOnlyOneEvents() {
sendHingeAngleEvent(180)
sendHingeAngleEvent(90)
sendHingeAngleEvent(80)
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
}
@Test
fun startClosingEvent_whileClosing_emittedDespiteInitialAngle() {
val maxAngle = 180 - FULLY_OPEN_THRESHOLD_DEGREES.toInt()
for (i in 1..maxAngle) {
foldUpdates.clear()
simulateFolding(startAngle = i)
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
simulateTimeout() // Timeout to set the state to aborted.
}
}
private fun simulateTimeout(waitTime: Long = ABORT_CLOSING_MILLIS) {
val runnableDelay = scheduledRunnableDelay ?: throw Exception("No runnable scheduled.")
if (waitTime >= runnableDelay) {
scheduledRunnable?.run()
scheduledRunnable = null
scheduledRunnableDelay = null
}
}
private fun simulateFolding(startAngle: Int) {
sendHingeAngleEvent(startAngle)
sendHingeAngleEvent(startAngle - 1)
}
private fun setFoldState(folded: Boolean) { private fun setFoldState(folded: Boolean) {
val state = if (folded) foldedDeviceState else unfoldedDeviceState val state = if (folded) foldedDeviceState else unfoldedDeviceState
foldStateListenerCaptor.value.onStateChanged(state) foldStateListenerCaptor.value.onStateChanged(state)
@@ -175,4 +289,8 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
private fun fireScreenOnEvent() { private fun fireScreenOnEvent() {
screenOnListenerCaptor.value.onScreenTurnedOn() screenOnListenerCaptor.value.onScreenTurnedOn()
} }
private fun sendHingeAngleEvent(angle: Int) {
hingeAngleCaptor.value.accept(angle.toFloat())
}
} }