Merge "Improve fold state provider logic handling posture" into sc-v2-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
9a33c9de70
@@ -60,14 +60,12 @@ fun createUnfoldTransitionProgressProvider(
|
||||
hingeAngleProvider,
|
||||
screenStatusProvider,
|
||||
deviceStateManager,
|
||||
mainExecutor
|
||||
mainExecutor,
|
||||
mainHandler
|
||||
)
|
||||
|
||||
val unfoldTransitionProgressProvider = if (config.isHingeAngleEnabled) {
|
||||
PhysicsBasedUnfoldTransitionProgressProvider(
|
||||
mainHandler,
|
||||
foldStateProvider
|
||||
)
|
||||
PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider)
|
||||
} else {
|
||||
FixedTimingTransitionProgressProvider(foldStateProvider)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package com.android.systemui.unfold.progress
|
||||
|
||||
import android.os.Handler
|
||||
import android.util.Log
|
||||
import android.util.MathUtils.saturate
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation
|
||||
@@ -24,9 +23,10 @@ import androidx.dynamicanimation.animation.SpringAnimation
|
||||
import androidx.dynamicanimation.animation.SpringForce
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
|
||||
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_FULL_OPEN
|
||||
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.FoldStateProvider
|
||||
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
|
||||
@@ -39,7 +39,6 @@ import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
|
||||
* - doesn't handle postures
|
||||
*/
|
||||
internal class PhysicsBasedUnfoldTransitionProgressProvider(
|
||||
private val handler: Handler,
|
||||
private val foldStateProvider: FoldStateProvider
|
||||
) :
|
||||
UnfoldTransitionProgressProvider,
|
||||
@@ -51,8 +50,6 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
|
||||
addEndListener(this@PhysicsBasedUnfoldTransitionProgressProvider)
|
||||
}
|
||||
|
||||
private val timeoutRunnable = TimeoutRunnable()
|
||||
|
||||
private var isTransitionRunning = false
|
||||
private var isAnimatedCancelRunning = false
|
||||
|
||||
@@ -92,7 +89,7 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
|
||||
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.
|
||||
// This could happen when we fully unfolded the device before the screen
|
||||
// became available. In this case we start and immediately cancel the animation
|
||||
@@ -106,7 +103,11 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
|
||||
cancelTransition(endValue = 0f, animate = false)
|
||||
}
|
||||
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) {
|
||||
handler.removeCallbacks(timeoutRunnable)
|
||||
|
||||
if (isTransitionRunning && animate) {
|
||||
isAnimatedCancelRunning = true
|
||||
springAnimation.animateToFinalPosition(endValue)
|
||||
@@ -175,8 +174,6 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
|
||||
}
|
||||
|
||||
springAnimation.start()
|
||||
|
||||
handler.postDelayed(timeoutRunnable, TRANSITION_TIMEOUT_MILLIS)
|
||||
}
|
||||
|
||||
override fun addCallback(listener: TransitionProgressListener) {
|
||||
@@ -187,13 +184,6 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
private inner class TimeoutRunnable : Runnable {
|
||||
|
||||
override fun run() {
|
||||
cancelTransition(endValue = 1f, animate = true)
|
||||
}
|
||||
}
|
||||
|
||||
private object AnimationProgressProperty :
|
||||
FloatPropertyCompat<PhysicsBasedUnfoldTransitionProgressProvider>("animation_progress") {
|
||||
|
||||
@@ -212,7 +202,6 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
|
||||
private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider"
|
||||
private const val DEBUG = true
|
||||
|
||||
private const val TRANSITION_TIMEOUT_MILLIS = 2000L
|
||||
private const val SPRING_STIFFNESS = 200.0f
|
||||
private const val MINIMAL_VISIBLE_CHANGE = 0.001f
|
||||
private const val FINAL_HINGE_ANGLE_POSITION = 165f
|
||||
|
||||
@@ -15,14 +15,19 @@
|
||||
*/
|
||||
package com.android.systemui.unfold.updates
|
||||
|
||||
import android.annotation.FloatRange
|
||||
import android.content.Context
|
||||
import android.hardware.devicestate.DeviceStateManager
|
||||
import android.os.Handler
|
||||
import android.util.Log
|
||||
import androidx.annotation.VisibleForTesting
|
||||
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.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.HingeAngleProvider
|
||||
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
class DeviceFoldStateProvider(
|
||||
@@ -30,7 +35,8 @@ class DeviceFoldStateProvider(
|
||||
private val hingeAngleProvider: HingeAngleProvider,
|
||||
private val screenStatusProvider: ScreenStatusProvider,
|
||||
private val deviceStateManager: DeviceStateManager,
|
||||
private val mainExecutor: Executor
|
||||
private val mainExecutor: Executor,
|
||||
private val handler: Handler
|
||||
) : FoldStateProvider {
|
||||
|
||||
private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf()
|
||||
@@ -38,9 +44,13 @@ class DeviceFoldStateProvider(
|
||||
@FoldUpdate
|
||||
private var lastFoldUpdate: Int? = null
|
||||
|
||||
@FloatRange(from = 0.0, to = 180.0)
|
||||
private var lastHingeAngle: Float = 0f
|
||||
|
||||
private val hingeAngleListener = HingeAngleListener()
|
||||
private val screenListener = ScreenStatusListener()
|
||||
private val foldStateListener = FoldStateListener(context)
|
||||
private val timeoutRunnable = TimeoutRunnable()
|
||||
|
||||
private var isFolded = false
|
||||
private var isUnfoldHandled = true
|
||||
@@ -72,47 +82,69 @@ class DeviceFoldStateProvider(
|
||||
override val isFullyOpened: Boolean
|
||||
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) {
|
||||
when (lastFoldUpdate) {
|
||||
FOLD_UPDATE_FINISH_FULL_OPEN -> {
|
||||
if (FULLY_OPEN_DEGREES - angle > START_CLOSING_THRESHOLD_DEGREES) {
|
||||
lastFoldUpdate = FOLD_UPDATE_START_CLOSING
|
||||
outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_START_CLOSING) }
|
||||
}
|
||||
}
|
||||
FOLD_UPDATE_START_OPENING -> {
|
||||
if (FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES) {
|
||||
lastFoldUpdate = FOLD_UPDATE_FINISH_FULL_OPEN
|
||||
outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) }
|
||||
}
|
||||
}
|
||||
FOLD_UPDATE_START_CLOSING -> {
|
||||
if (FULLY_OPEN_DEGREES - angle < START_CLOSING_THRESHOLD_DEGREES) {
|
||||
lastFoldUpdate = FOLD_UPDATE_FINISH_FULL_OPEN
|
||||
outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) }
|
||||
}
|
||||
if (DEBUG) { Log.d(TAG, "Hinge angle: $angle, lastHingeAngle: $lastHingeAngle") }
|
||||
|
||||
val isClosing = angle < lastHingeAngle
|
||||
val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES
|
||||
val closingEventDispatched = lastFoldUpdate == FOLD_UPDATE_START_CLOSING
|
||||
|
||||
if (isClosing && !closingEventDispatched && !isFullyOpened) {
|
||||
notifyFoldUpdate(FOLD_UPDATE_START_CLOSING)
|
||||
}
|
||||
|
||||
if (isTransitionInProgess) {
|
||||
if (isFullyOpened) {
|
||||
notifyFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN)
|
||||
cancelTimeout()
|
||||
} else {
|
||||
// The timeout will trigger some constant time after the last angle update.
|
||||
rescheduleAbortAnimationTimeout()
|
||||
}
|
||||
}
|
||||
|
||||
lastHingeAngle = angle
|
||||
outputListeners.forEach { it.onHingeAngleUpdate(angle) }
|
||||
}
|
||||
|
||||
private inner class FoldStateListener(context: Context) :
|
||||
DeviceStateManager.FoldStateListener(context, { folded: Boolean ->
|
||||
isFolded = folded
|
||||
lastHingeAngle = FULLY_CLOSED_DEGREES
|
||||
|
||||
if (folded) {
|
||||
lastFoldUpdate = FOLD_UPDATE_FINISH_CLOSED
|
||||
outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) }
|
||||
hingeAngleProvider.stop()
|
||||
notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
|
||||
cancelTimeout()
|
||||
isUnfoldHandled = false
|
||||
} else {
|
||||
lastFoldUpdate = FOLD_UPDATE_START_OPENING
|
||||
outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_START_OPENING) }
|
||||
notifyFoldUpdate(FOLD_UPDATE_START_OPENING)
|
||||
rescheduleAbortAnimationTimeout()
|
||||
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 :
|
||||
ScreenStatusProvider.ScreenListener {
|
||||
|
||||
@@ -136,7 +168,39 @@ class DeviceFoldStateProvider(
|
||||
onHingeAngle(angle)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class TimeoutRunnable : Runnable {
|
||||
|
||||
override fun run() {
|
||||
notifyFoldUpdate(FOLD_UPDATE_ABORTED)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val START_CLOSING_THRESHOLD_DEGREES = 95f
|
||||
private const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
|
||||
private fun stateToString(@FoldUpdate update: Int): String {
|
||||
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
|
||||
@@ -39,6 +39,7 @@ interface FoldStateProvider : CallbackController<FoldUpdatesListener> {
|
||||
FOLD_UPDATE_START_OPENING,
|
||||
FOLD_UPDATE_HALF_OPEN,
|
||||
FOLD_UPDATE_START_CLOSING,
|
||||
FOLD_UPDATE_ABORTED,
|
||||
FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE,
|
||||
FOLD_UPDATE_FINISH_HALF_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_HALF_OPEN = 1
|
||||
const val FOLD_UPDATE_START_CLOSING = 2
|
||||
const val FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE = 3
|
||||
const val FOLD_UPDATE_FINISH_HALF_OPEN = 4
|
||||
const val FOLD_UPDATE_FINISH_FULL_OPEN = 5
|
||||
const val FOLD_UPDATE_FINISH_CLOSED = 6
|
||||
const val FOLD_UPDATE_ABORTED = 3
|
||||
const val FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE = 4
|
||||
const val FOLD_UPDATE_FINISH_HALF_OPEN = 5
|
||||
const val FOLD_UPDATE_FINISH_FULL_OPEN = 6
|
||||
const val FOLD_UPDATE_FINISH_CLOSED = 7
|
||||
|
||||
@@ -18,7 +18,9 @@ package com.android.systemui.unfold.updates
|
||||
|
||||
import android.hardware.devicestate.DeviceStateManager
|
||||
import android.hardware.devicestate.DeviceStateManager.FoldStateListener
|
||||
import android.os.Handler
|
||||
import android.testing.AndroidTestingRunner
|
||||
import androidx.core.util.Consumer
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
|
||||
@@ -31,9 +33,12 @@ import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
import org.mockito.MockitoAnnotations
|
||||
import java.lang.Exception
|
||||
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@SmallTest
|
||||
@@ -48,16 +53,28 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
|
||||
@Mock
|
||||
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 hingeAngleUpdates: MutableList<Float> = arrayListOf()
|
||||
|
||||
private val foldStateListenerCaptor = ArgumentCaptor.forClass(FoldStateListener::class.java)
|
||||
private var foldedDeviceState: 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
|
||||
fun setUp() {
|
||||
@@ -75,7 +92,8 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
|
||||
hingeAngleProvider,
|
||||
screenStatusProvider,
|
||||
deviceStateManager,
|
||||
context.mainExecutor
|
||||
context.mainExecutor,
|
||||
handler
|
||||
)
|
||||
|
||||
foldStateProvider.addCallback(object : FoldStateProvider.FoldUpdatesListener {
|
||||
@@ -91,6 +109,22 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
|
||||
|
||||
verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.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
|
||||
@@ -167,6 +201,86 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
|
||||
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) {
|
||||
val state = if (folded) foldedDeviceState else unfoldedDeviceState
|
||||
foldStateListenerCaptor.value.onStateChanged(state)
|
||||
@@ -175,4 +289,8 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
|
||||
private fun fireScreenOnEvent() {
|
||||
screenOnListenerCaptor.value.onScreenTurnedOn()
|
||||
}
|
||||
|
||||
private fun sendHingeAngleEvent(angle: Int) {
|
||||
hingeAngleCaptor.value.accept(angle.toFloat())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user