Merge "Add a threshold to the trigger fold animation" into tm-qpr-dev

This commit is contained in:
Uwais Ashraf
2023-02-24 17:19:32 +00:00
committed by Android (Google) Code Review
8 changed files with 174 additions and 81 deletions

View File

@@ -61,8 +61,6 @@ class FoldStateLoggingProviderImpl(
foldStateProvider.stop()
}
override fun onHingeAngleUpdate(angle: Float) {}
override fun onFoldUpdate(@FoldUpdate update: Int) {
val now = clock.elapsedRealtime()
when (update) {
@@ -77,6 +75,10 @@ class FoldStateLoggingProviderImpl(
}
}
override fun onUnfoldedScreenAvailable() {
Log.d(TAG, "Unfolded screen available")
}
private fun dispatchState(@LoggedFoldedStates current: Int) {
val now = clock.elapsedRealtime()
val previous = lastState

View File

@@ -25,7 +25,6 @@ import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING
import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE
import com.android.systemui.unfold.util.TestFoldStateProvider
import org.junit.Before
import org.junit.Test
@@ -50,7 +49,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
runOnMainThreadWithInterval(
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
{ foldStateProvider.sendHingeAngleUpdate(10f) },
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) },
{ foldStateProvider.sendUnfoldedScreenAvailable() },
{ foldStateProvider.sendHingeAngleUpdate(90f) },
{ foldStateProvider.sendHingeAngleUpdate(180f) },
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) },
@@ -67,7 +66,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
runOnMainThreadWithInterval(
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
{ foldStateProvider.sendHingeAngleUpdate(10f) },
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) },
{ foldStateProvider.sendUnfoldedScreenAvailable() },
{ foldStateProvider.sendHingeAngleUpdate(90f) },
{ foldStateProvider.sendHingeAngleUpdate(180f) },
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) },
@@ -84,7 +83,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
{ foldStateProvider.sendHingeAngleUpdate(90f) },
{ foldStateProvider.sendHingeAngleUpdate(180f) },
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) },
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) },
{ foldStateProvider.sendUnfoldedScreenAvailable() },
)
with(listener.ensureTransitionFinished()) {
@@ -113,7 +112,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
fun testUnfoldAndStopUnfolding_finishesTheUnfoldTransition() {
runOnMainThreadWithInterval(
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) },
{ foldStateProvider.sendUnfoldedScreenAvailable() },
{ foldStateProvider.sendHingeAngleUpdate(10f) },
{ foldStateProvider.sendHingeAngleUpdate(90f) },
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN) },
@@ -129,7 +128,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
fun testFoldImmediatelyAfterUnfold_runsFoldAnimation() {
runOnMainThreadWithInterval(
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) },
{ foldStateProvider.sendUnfoldedScreenAvailable() },
{ foldStateProvider.sendHingeAngleUpdate(10f) },
{ foldStateProvider.sendHingeAngleUpdate(90f) },
{

View File

@@ -26,6 +26,7 @@ import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.system.ActivityManagerActivityTypeProvider
import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener
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 com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
@@ -71,6 +72,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
private val foldUpdates: MutableList<Int> = arrayListOf()
private val hingeAngleUpdates: MutableList<Float> = arrayListOf()
private val unfoldedScreenAvailabilityUpdates: MutableList<Unit> = arrayListOf()
private var scheduledRunnable: Runnable? = null
private var scheduledRunnableDelay: Long? = null
@@ -106,6 +108,10 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
override fun onFoldUpdate(update: Int) {
foldUpdates.add(update)
}
override fun onUnfoldedScreenAvailable() {
unfoldedScreenAvailabilityUpdates.add(Unit)
}
})
foldStateProvider.start()
@@ -156,8 +162,8 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
sendHingeAngleEvent(10)
screenOnStatusProvider.notifyScreenTurnedOn()
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING,
FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE)
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING)
assertThat(unfoldedScreenAvailabilityUpdates).hasSize(1)
}
@Test
@@ -174,8 +180,9 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
sendHingeAngleEvent(40)
sendHingeAngleEvent(10)
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING,
FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE, FOLD_UPDATE_START_CLOSING)
assertThat(foldUpdates)
.containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING)
assertThat(unfoldedScreenAvailabilityUpdates).hasSize(1)
}
@Test
@@ -223,7 +230,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
fireScreenOnEvent()
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE)
assertThat(unfoldedScreenAvailabilityUpdates).hasSize(1)
}
@Test
@@ -277,7 +284,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test
fun startClosingEvent_afterTimeout_finishHalfOpenEventEmitted() {
sendHingeAngleEvent(90)
setInitialHingeAngle(90)
sendHingeAngleEvent(80)
simulateTimeout(HALF_OPENED_TIMEOUT_MILLIS)
@@ -288,7 +295,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test
fun startClosingEvent_beforeTimeout_abortNotEmitted() {
sendHingeAngleEvent(90)
setInitialHingeAngle(90)
sendHingeAngleEvent(80)
simulateTimeout(HALF_OPENED_TIMEOUT_MILLIS - 1)
@@ -298,7 +305,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test
fun startClosingEvent_eventBeforeTimeout_oneEventEmitted() {
sendHingeAngleEvent(180)
setInitialHingeAngle(180)
sendHingeAngleEvent(90)
simulateTimeout(HALF_OPENED_TIMEOUT_MILLIS - 1)
@@ -309,7 +316,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test
fun startClosingEvent_timeoutAfterTimeoutRescheduled_finishHalfOpenStateEmitted() {
sendHingeAngleEvent(180)
setInitialHingeAngle(180)
sendHingeAngleEvent(90)
// The timeout should not trigger here.
@@ -323,7 +330,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test
fun startClosingEvent_shortTimeBetween_emitsOnlyOneEvents() {
sendHingeAngleEvent(180)
setInitialHingeAngle(180)
sendHingeAngleEvent(90)
sendHingeAngleEvent(80)
@@ -334,20 +341,19 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test
fun startClosingEvent_whileClosing_emittedDespiteInitialAngle() {
val maxAngle = 180 - FULLY_OPEN_THRESHOLD_DEGREES.toInt()
for (i in 1..maxAngle) {
foldUpdates.clear()
simulateFolding(startAngle = i)
val minAngle = Math.ceil(HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toDouble()).toInt() + 1
for (startAngle in minAngle..maxAngle) {
setInitialHingeAngle(startAngle)
sendHingeAngleEvent(startAngle - HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() - 1)
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
simulateTimeout() // Timeout to set the state to aborted.
}
}
@Test
fun startClosingEvent_whileNotOnLauncher_doesNotTriggerBeforeThreshold() {
setupForegroundActivityType(isHomeActivity = false)
sendHingeAngleEvent(180)
setInitialHingeAngle(180)
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
@@ -357,7 +363,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test
fun startClosingEvent_whileActivityTypeNotAvailable_triggerBeforeThreshold() {
setupForegroundActivityType(isHomeActivity = null)
sendHingeAngleEvent(180)
setInitialHingeAngle(180)
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
@@ -367,7 +373,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test
fun startClosingEvent_whileOnLauncher_doesTriggerBeforeThreshold() {
setupForegroundActivityType(isHomeActivity = true)
sendHingeAngleEvent(180)
setInitialHingeAngle(180)
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
@@ -377,9 +383,11 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test
fun startClosingEvent_whileNotOnLauncher_triggersAfterThreshold() {
setupForegroundActivityType(isHomeActivity = false)
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES)
setInitialHingeAngle(START_CLOSING_ON_APPS_THRESHOLD_DEGREES)
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES - 1)
sendHingeAngleEvent(
START_CLOSING_ON_APPS_THRESHOLD_DEGREES -
HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() - 1)
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
}
@@ -388,7 +396,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
fun startClosingEvent_whileNotOnKeyguardAndNotOnLauncher_doesNotTriggerBeforeThreshold() {
setKeyguardVisibility(visible = false)
setupForegroundActivityType(isHomeActivity = false)
sendHingeAngleEvent(180)
setInitialHingeAngle(180)
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
@@ -398,7 +406,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test
fun startClosingEvent_whileKeyguardStateNotAvailable_triggerBeforeThreshold() {
setKeyguardVisibility(visible = null)
sendHingeAngleEvent(180)
setInitialHingeAngle(180)
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
@@ -408,7 +416,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test
fun startClosingEvent_whileonKeyguard_doesTriggerBeforeThreshold() {
setKeyguardVisibility(visible = true)
sendHingeAngleEvent(180)
setInitialHingeAngle(180)
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
@@ -418,9 +426,59 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test
fun startClosingEvent_whileNotOnKeyguard_triggersAfterThreshold() {
setKeyguardVisibility(visible = false)
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES)
setInitialHingeAngle(START_CLOSING_ON_APPS_THRESHOLD_DEGREES)
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES - 1)
sendHingeAngleEvent(
START_CLOSING_ON_APPS_THRESHOLD_DEGREES -
HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() - 1)
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
}
@Test
fun startClosingEvent_doesNotTriggerBelowThreshold() {
val thresholdAngle = (FULLY_OPEN_DEGREES - FULLY_OPEN_THRESHOLD_DEGREES).toInt()
setInitialHingeAngle(180)
sendHingeAngleEvent(thresholdAngle + 1)
assertThat(foldUpdates).isEmpty()
}
@Test
fun startClosingEvent_triggersAfterThreshold() {
val thresholdAngle = (FULLY_OPEN_DEGREES - FULLY_OPEN_THRESHOLD_DEGREES).toInt()
setInitialHingeAngle(180)
sendHingeAngleEvent(thresholdAngle + 1)
sendHingeAngleEvent(thresholdAngle - 1)
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
}
@Test
fun startClosingEvent_triggersAfterThreshold_fromHalfOpen() {
setInitialHingeAngle(120)
sendHingeAngleEvent((120 - HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES + 1).toInt())
assertThat(foldUpdates).isEmpty()
sendHingeAngleEvent((120 - HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES - 1).toInt())
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
}
@Test
fun startOpeningAndClosingEvents_triggerWithOpenAndClose() {
setInitialHingeAngle(120)
sendHingeAngleEvent(130)
sendHingeAngleEvent(120)
assertThat(foldUpdates)
.containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING)
}
@Test
fun startClosingEvent_notInterrupted_whenAngleIsSlightlyIncreased() {
setInitialHingeAngle(120)
sendHingeAngleEvent(110)
sendHingeAngleEvent(111)
sendHingeAngleEvent(100)
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
}
@@ -504,11 +562,6 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
}
}
private fun simulateFolding(startAngle: Int) {
sendHingeAngleEvent(startAngle)
sendHingeAngleEvent(startAngle - 1)
}
private fun setFoldState(folded: Boolean) {
foldProvider.notifyFolded(folded)
}
@@ -521,6 +574,17 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
testHingeAngleProvider.notifyAngle(angle.toFloat())
}
private fun setInitialHingeAngle(angle: Int) {
setFoldState(angle == 0)
sendHingeAngleEvent(angle)
if (scheduledRunnableDelay != null) {
simulateTimeout()
}
hingeAngleUpdates.clear()
foldUpdates.clear()
unfoldedScreenAvailabilityUpdates.clear()
}
private class TestFoldProvider : FoldProvider {
private val callbacks = arrayListOf<FoldCallback>()

View File

@@ -57,4 +57,8 @@ class TestFoldStateProvider : FoldStateProvider {
fun sendHingeAngleUpdate(angle: Float) {
listeners.forEach { it.onHingeAngleUpdate(angle) }
}
fun sendUnfoldedScreenAvailable() {
listeners.forEach { it.onUnfoldedScreenAvailable() }
}
}

View File

@@ -21,7 +21,6 @@ import android.util.FloatProperty
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
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
import javax.inject.Inject
@@ -59,12 +58,15 @@ constructor(private val foldStateProvider: FoldStateProvider) :
}
override fun onFoldUpdate(@FoldUpdate update: Int) {
when (update) {
FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> animator.start()
FOLD_UPDATE_FINISH_CLOSED -> animator.cancel()
if (update == FOLD_UPDATE_FINISH_CLOSED) {
animator.cancel()
}
}
override fun onUnfoldedScreenAvailable() {
animator.start()
}
override fun addCallback(listener: TransitionProgressListener) {
listeners.add(listener)
}
@@ -73,8 +75,6 @@ constructor(private val foldStateProvider: FoldStateProvider) :
listeners.remove(listener)
}
override fun onHingeAngleUpdate(angle: Float) {}
private object AnimationProgressProperty :
FloatProperty<FixedTimingTransitionProgressProvider>("animation_progress") {

View File

@@ -28,7 +28,6 @@ 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_FINISH_HALF_OPEN
import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
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
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
@@ -78,21 +77,11 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor(
override fun onFoldUpdate(@FoldUpdate update: Int) {
when (update) {
FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> {
startTransition(startValue = 0f)
// Stop the animation if the device has already opened by the time when
// the display is available as we won't receive the full open event anymore
if (foldStateProvider.isFinishedOpening) {
cancelTransition(endValue = 1f, animate = true)
}
}
FOLD_UPDATE_FINISH_FULL_OPEN, FOLD_UPDATE_FINISH_HALF_OPEN -> {
// 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
// in FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE event handler, so we don't need to
// cancel it here.
// in onUnfoldedScreenAvailable event handler, so we don't need to cancel it here.
if (isTransitionRunning) {
cancelTransition(endValue = 1f, animate = true)
}
@@ -125,6 +114,16 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor(
}
}
override fun onUnfoldedScreenAvailable() {
startTransition(startValue = 0f)
// Stop the animation if the device has already opened by the time when
// the display is available as we won't receive the full open event anymore
if (foldStateProvider.isFinishedOpening) {
cancelTransition(endValue = 1f, animate = true)
}
}
private fun cancelTransition(endValue: Float, animate: Boolean) {
if (isTransitionRunning && animate) {
if (endValue == 1.0f && !isAnimatedCancelRunning) {

View File

@@ -54,6 +54,7 @@ constructor(
@FoldUpdate private var lastFoldUpdate: Int? = null
@FloatRange(from = 0.0, to = 180.0) private var lastHingeAngle: Float = 0f
@FloatRange(from = 0.0, to = 180.0) private var lastHingeAngleBeforeTransition: Float = 0f
private val hingeAngleListener = HingeAngleListener()
private val screenListener = ScreenStatusListener()
@@ -112,29 +113,45 @@ constructor(
private fun onHingeAngle(angle: Float) {
if (DEBUG) {
Log.d(TAG, "Hinge angle: $angle, lastHingeAngle: $lastHingeAngle")
Log.d(
TAG,
"Hinge angle: $angle, " +
"lastHingeAngle: $lastHingeAngle, " +
"lastHingeAngleBeforeTransition: $lastHingeAngleBeforeTransition"
)
Trace.traceCounter(Trace.TRACE_TAG_APP, "hinge_angle", angle.toInt())
}
val isClosing = angle < lastHingeAngle
val currentDirection =
if (angle < lastHingeAngle) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING
if (isTransitionInProgress && currentDirection != lastFoldUpdate) {
lastHingeAngleBeforeTransition = lastHingeAngle
}
val isClosing = angle < lastHingeAngleBeforeTransition
val transitionUpdate =
if (isClosing) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING
val angleChangeSurpassedThreshold =
Math.abs(angle - lastHingeAngleBeforeTransition) > HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES
val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES
val closingEventDispatched = lastFoldUpdate == FOLD_UPDATE_START_CLOSING
val eventNotAlreadyDispatched = lastFoldUpdate != transitionUpdate
val screenAvailableEventSent = isUnfoldHandled
if (isClosing // hinge angle should be decreasing since last update
&& !closingEventDispatched // we haven't sent closing event already
&& !isFullyOpened // do not send closing event if we are in fully opened hinge
if (
angleChangeSurpassedThreshold && // Do not react immediately to small changes in angle
eventNotAlreadyDispatched && // we haven't sent transition event already
!isFullyOpened && // do not send transition event if we are in fully opened hinge
// angle range as closing threshold could overlap this range
&& screenAvailableEventSent // do not send closing event if we are still in
// the process of turning on the inner display
&& isClosingThresholdMet(angle) // hinge angle is below certain threshold.
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.
) {
notifyFoldUpdate(FOLD_UPDATE_START_CLOSING)
notifyFoldUpdate(transitionUpdate, lastHingeAngle)
}
if (isTransitionInProgress) {
if (isFullyOpened) {
notifyFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN)
notifyFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN, angle)
cancelTimeout()
} else {
// The timeout will trigger some constant time after the last angle update.
@@ -146,7 +163,7 @@ constructor(
outputListeners.forEach { it.onHingeAngleUpdate(angle) }
}
private fun isClosingThresholdMet(currentAngle: Float) : Boolean {
private fun isClosingThresholdMet(currentAngle: Float): Boolean {
val closingThreshold = getClosingThreshold()
return closingThreshold == null || currentAngle < closingThreshold
}
@@ -179,23 +196,29 @@ constructor(
if (isFolded) {
hingeAngleProvider.stop()
notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED, lastHingeAngle)
cancelTimeout()
isUnfoldHandled = false
} else {
notifyFoldUpdate(FOLD_UPDATE_START_OPENING)
notifyFoldUpdate(FOLD_UPDATE_START_OPENING, lastHingeAngle)
rescheduleAbortAnimationTimeout()
hingeAngleProvider.start()
}
}
}
private fun notifyFoldUpdate(@FoldUpdate update: Int) {
private fun notifyFoldUpdate(@FoldUpdate update: Int, angle: Float) {
if (DEBUG) {
Log.d(TAG, update.name())
}
val previouslyTransitioning = isTransitionInProgress
outputListeners.forEach { it.onFoldUpdate(update) }
lastFoldUpdate = update
if (previouslyTransitioning != isTransitionInProgress) {
lastHingeAngleBeforeTransition = angle
}
}
private fun rescheduleAbortAnimationTimeout() {
@@ -209,7 +232,8 @@ constructor(
handler.removeCallbacks(timeoutRunnable)
}
private fun cancelAnimation(): Unit = notifyFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN)
private fun cancelAnimation(): Unit =
notifyFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN, lastHingeAngle)
private inner class ScreenStatusListener : ScreenStatusProvider.ScreenListener {
@@ -221,7 +245,7 @@ constructor(
// receive 'folded' event. If SystemUI started when device is already folded it will
// still receive 'folded' event on startup.
if (!isFolded && !isUnfoldHandled) {
outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) }
outputListeners.forEach { it.onUnfoldedScreenAvailable() }
isUnfoldHandled = true
}
}
@@ -257,7 +281,6 @@ fun @receiver:FoldUpdate Int.name() =
when (this) {
FOLD_UPDATE_START_OPENING -> "START_OPENING"
FOLD_UPDATE_START_CLOSING -> "START_CLOSING"
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"
@@ -270,5 +293,8 @@ private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
/** Threshold after which we consider the device fully unfolded. */
@VisibleForTesting const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
/** Threshold after which hinge angle updates are considered. This is to eliminate noise. */
@VisibleForTesting const val HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES = 7.5f
/** Fold animation on top of apps only when the angle exceeds this threshold. */
@VisibleForTesting const val START_CLOSING_ON_APPS_THRESHOLD_DEGREES = 60

View File

@@ -31,8 +31,9 @@ interface FoldStateProvider : CallbackController<FoldUpdatesListener> {
val isFinishedOpening: Boolean
interface FoldUpdatesListener {
fun onHingeAngleUpdate(@FloatRange(from = 0.0, to = 180.0) angle: Float)
fun onFoldUpdate(@FoldUpdate update: Int)
@JvmDefault fun onHingeAngleUpdate(@FloatRange(from = 0.0, to = 180.0) angle: Float) {}
@JvmDefault fun onFoldUpdate(@FoldUpdate update: Int) {}
@JvmDefault fun onUnfoldedScreenAvailable() {}
}
@IntDef(
@@ -40,7 +41,6 @@ interface FoldStateProvider : CallbackController<FoldUpdatesListener> {
[
FOLD_UPDATE_START_OPENING,
FOLD_UPDATE_START_CLOSING,
FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE,
FOLD_UPDATE_FINISH_HALF_OPEN,
FOLD_UPDATE_FINISH_FULL_OPEN,
FOLD_UPDATE_FINISH_CLOSED])
@@ -50,7 +50,6 @@ interface FoldStateProvider : CallbackController<FoldUpdatesListener> {
const val FOLD_UPDATE_START_OPENING = 0
const val FOLD_UPDATE_START_CLOSING = 1
const val FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE = 2
const val FOLD_UPDATE_FINISH_HALF_OPEN = 3
const val FOLD_UPDATE_FINISH_FULL_OPEN = 4
const val FOLD_UPDATE_FINISH_CLOSED = 5
const val FOLD_UPDATE_FINISH_HALF_OPEN = 2
const val FOLD_UPDATE_FINISH_FULL_OPEN = 3
const val FOLD_UPDATE_FINISH_CLOSED = 4