Add a threshold to the trigger fold animation
Test: Unit tests added and manual testing Bug: 256792419 Change-Id: If414fd3b30bff705243a36b1e3e4c32cf2601609
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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) },
|
||||
{
|
||||
|
||||
@@ -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>()
|
||||
|
||||
|
||||
@@ -57,4 +57,8 @@ class TestFoldStateProvider : FoldStateProvider {
|
||||
fun sendHingeAngleUpdate(angle: Float) {
|
||||
listeners.forEach { it.onHingeAngleUpdate(angle) }
|
||||
}
|
||||
|
||||
fun sendUnfoldedScreenAvailable() {
|
||||
listeners.forEach { it.onUnfoldedScreenAvailable() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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") {
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user