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,
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user