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() foldStateProvider.stop()
} }
override fun onHingeAngleUpdate(angle: Float) {}
override fun onFoldUpdate(@FoldUpdate update: Int) { override fun onFoldUpdate(@FoldUpdate update: Int) {
val now = clock.elapsedRealtime() val now = clock.elapsedRealtime()
when (update) { when (update) {
@@ -77,6 +75,10 @@ class FoldStateLoggingProviderImpl(
} }
} }
override fun onUnfoldedScreenAvailable() {
Log.d(TAG, "Unfolded screen available")
}
private fun dispatchState(@LoggedFoldedStates current: Int) { private fun dispatchState(@LoggedFoldedStates current: Int) {
val now = clock.elapsedRealtime() val now = clock.elapsedRealtime()
val previous = lastState 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_FINISH_HALF_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_START_OPENING 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 com.android.systemui.unfold.util.TestFoldStateProvider
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@@ -50,7 +49,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
runOnMainThreadWithInterval( runOnMainThreadWithInterval(
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) }, { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
{ foldStateProvider.sendHingeAngleUpdate(10f) }, { foldStateProvider.sendHingeAngleUpdate(10f) },
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) }, { foldStateProvider.sendUnfoldedScreenAvailable() },
{ foldStateProvider.sendHingeAngleUpdate(90f) }, { foldStateProvider.sendHingeAngleUpdate(90f) },
{ foldStateProvider.sendHingeAngleUpdate(180f) }, { foldStateProvider.sendHingeAngleUpdate(180f) },
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) }, { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) },
@@ -67,7 +66,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
runOnMainThreadWithInterval( runOnMainThreadWithInterval(
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) }, { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
{ foldStateProvider.sendHingeAngleUpdate(10f) }, { foldStateProvider.sendHingeAngleUpdate(10f) },
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) }, { foldStateProvider.sendUnfoldedScreenAvailable() },
{ foldStateProvider.sendHingeAngleUpdate(90f) }, { foldStateProvider.sendHingeAngleUpdate(90f) },
{ foldStateProvider.sendHingeAngleUpdate(180f) }, { foldStateProvider.sendHingeAngleUpdate(180f) },
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) }, { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) },
@@ -84,7 +83,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
{ foldStateProvider.sendHingeAngleUpdate(90f) }, { foldStateProvider.sendHingeAngleUpdate(90f) },
{ foldStateProvider.sendHingeAngleUpdate(180f) }, { foldStateProvider.sendHingeAngleUpdate(180f) },
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) }, { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) },
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) }, { foldStateProvider.sendUnfoldedScreenAvailable() },
) )
with(listener.ensureTransitionFinished()) { with(listener.ensureTransitionFinished()) {
@@ -113,7 +112,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
fun testUnfoldAndStopUnfolding_finishesTheUnfoldTransition() { fun testUnfoldAndStopUnfolding_finishesTheUnfoldTransition() {
runOnMainThreadWithInterval( runOnMainThreadWithInterval(
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) }, { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) }, { foldStateProvider.sendUnfoldedScreenAvailable() },
{ foldStateProvider.sendHingeAngleUpdate(10f) }, { foldStateProvider.sendHingeAngleUpdate(10f) },
{ foldStateProvider.sendHingeAngleUpdate(90f) }, { foldStateProvider.sendHingeAngleUpdate(90f) },
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN) }, { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN) },
@@ -129,7 +128,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
fun testFoldImmediatelyAfterUnfold_runsFoldAnimation() { fun testFoldImmediatelyAfterUnfold_runsFoldAnimation() {
runOnMainThreadWithInterval( runOnMainThreadWithInterval(
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) }, { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) }, { foldStateProvider.sendUnfoldedScreenAvailable() },
{ foldStateProvider.sendHingeAngleUpdate(10f) }, { foldStateProvider.sendHingeAngleUpdate(10f) },
{ foldStateProvider.sendHingeAngleUpdate(90f) }, { 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.system.ActivityManagerActivityTypeProvider
import com.android.systemui.unfold.updates.FoldProvider.FoldCallback import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener 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.hinge.HingeAngleProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
@@ -71,6 +72,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
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 unfoldedScreenAvailabilityUpdates: MutableList<Unit> = arrayListOf()
private var scheduledRunnable: Runnable? = null private var scheduledRunnable: Runnable? = null
private var scheduledRunnableDelay: Long? = null private var scheduledRunnableDelay: Long? = null
@@ -106,6 +108,10 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
override fun onFoldUpdate(update: Int) { override fun onFoldUpdate(update: Int) {
foldUpdates.add(update) foldUpdates.add(update)
} }
override fun onUnfoldedScreenAvailable() {
unfoldedScreenAvailabilityUpdates.add(Unit)
}
}) })
foldStateProvider.start() foldStateProvider.start()
@@ -156,8 +162,8 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
sendHingeAngleEvent(10) sendHingeAngleEvent(10)
screenOnStatusProvider.notifyScreenTurnedOn() screenOnStatusProvider.notifyScreenTurnedOn()
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING, assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING)
FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) assertThat(unfoldedScreenAvailabilityUpdates).hasSize(1)
} }
@Test @Test
@@ -174,8 +180,9 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
sendHingeAngleEvent(40) sendHingeAngleEvent(40)
sendHingeAngleEvent(10) sendHingeAngleEvent(10)
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING, assertThat(foldUpdates)
FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE, FOLD_UPDATE_START_CLOSING) .containsExactly(FOLD_UPDATE_START_OPENING, FOLD_UPDATE_START_CLOSING)
assertThat(unfoldedScreenAvailabilityUpdates).hasSize(1)
} }
@Test @Test
@@ -223,7 +230,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
fireScreenOnEvent() fireScreenOnEvent()
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) assertThat(unfoldedScreenAvailabilityUpdates).hasSize(1)
} }
@Test @Test
@@ -277,7 +284,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test @Test
fun startClosingEvent_afterTimeout_finishHalfOpenEventEmitted() { fun startClosingEvent_afterTimeout_finishHalfOpenEventEmitted() {
sendHingeAngleEvent(90) setInitialHingeAngle(90)
sendHingeAngleEvent(80) sendHingeAngleEvent(80)
simulateTimeout(HALF_OPENED_TIMEOUT_MILLIS) simulateTimeout(HALF_OPENED_TIMEOUT_MILLIS)
@@ -288,7 +295,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test @Test
fun startClosingEvent_beforeTimeout_abortNotEmitted() { fun startClosingEvent_beforeTimeout_abortNotEmitted() {
sendHingeAngleEvent(90) setInitialHingeAngle(90)
sendHingeAngleEvent(80) sendHingeAngleEvent(80)
simulateTimeout(HALF_OPENED_TIMEOUT_MILLIS - 1) simulateTimeout(HALF_OPENED_TIMEOUT_MILLIS - 1)
@@ -298,7 +305,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test @Test
fun startClosingEvent_eventBeforeTimeout_oneEventEmitted() { fun startClosingEvent_eventBeforeTimeout_oneEventEmitted() {
sendHingeAngleEvent(180) setInitialHingeAngle(180)
sendHingeAngleEvent(90) sendHingeAngleEvent(90)
simulateTimeout(HALF_OPENED_TIMEOUT_MILLIS - 1) simulateTimeout(HALF_OPENED_TIMEOUT_MILLIS - 1)
@@ -309,7 +316,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test @Test
fun startClosingEvent_timeoutAfterTimeoutRescheduled_finishHalfOpenStateEmitted() { fun startClosingEvent_timeoutAfterTimeoutRescheduled_finishHalfOpenStateEmitted() {
sendHingeAngleEvent(180) setInitialHingeAngle(180)
sendHingeAngleEvent(90) sendHingeAngleEvent(90)
// The timeout should not trigger here. // The timeout should not trigger here.
@@ -323,7 +330,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test @Test
fun startClosingEvent_shortTimeBetween_emitsOnlyOneEvents() { fun startClosingEvent_shortTimeBetween_emitsOnlyOneEvents() {
sendHingeAngleEvent(180) setInitialHingeAngle(180)
sendHingeAngleEvent(90) sendHingeAngleEvent(90)
sendHingeAngleEvent(80) sendHingeAngleEvent(80)
@@ -334,20 +341,19 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test @Test
fun startClosingEvent_whileClosing_emittedDespiteInitialAngle() { fun startClosingEvent_whileClosing_emittedDespiteInitialAngle() {
val maxAngle = 180 - FULLY_OPEN_THRESHOLD_DEGREES.toInt() val maxAngle = 180 - FULLY_OPEN_THRESHOLD_DEGREES.toInt()
for (i in 1..maxAngle) { val minAngle = Math.ceil(HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toDouble()).toInt() + 1
foldUpdates.clear() for (startAngle in minAngle..maxAngle) {
setInitialHingeAngle(startAngle)
simulateFolding(startAngle = i) sendHingeAngleEvent(startAngle - HINGE_ANGLE_CHANGE_THRESHOLD_DEGREES.toInt() - 1)
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING) assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
simulateTimeout() // Timeout to set the state to aborted.
} }
} }
@Test @Test
fun startClosingEvent_whileNotOnLauncher_doesNotTriggerBeforeThreshold() { fun startClosingEvent_whileNotOnLauncher_doesNotTriggerBeforeThreshold() {
setupForegroundActivityType(isHomeActivity = false) setupForegroundActivityType(isHomeActivity = false)
sendHingeAngleEvent(180) setInitialHingeAngle(180)
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1) sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
@@ -357,7 +363,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test @Test
fun startClosingEvent_whileActivityTypeNotAvailable_triggerBeforeThreshold() { fun startClosingEvent_whileActivityTypeNotAvailable_triggerBeforeThreshold() {
setupForegroundActivityType(isHomeActivity = null) setupForegroundActivityType(isHomeActivity = null)
sendHingeAngleEvent(180) setInitialHingeAngle(180)
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1) sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
@@ -367,7 +373,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test @Test
fun startClosingEvent_whileOnLauncher_doesTriggerBeforeThreshold() { fun startClosingEvent_whileOnLauncher_doesTriggerBeforeThreshold() {
setupForegroundActivityType(isHomeActivity = true) setupForegroundActivityType(isHomeActivity = true)
sendHingeAngleEvent(180) setInitialHingeAngle(180)
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1) sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
@@ -377,9 +383,11 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test @Test
fun startClosingEvent_whileNotOnLauncher_triggersAfterThreshold() { fun startClosingEvent_whileNotOnLauncher_triggersAfterThreshold() {
setupForegroundActivityType(isHomeActivity = false) 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) assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
} }
@@ -388,7 +396,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
fun startClosingEvent_whileNotOnKeyguardAndNotOnLauncher_doesNotTriggerBeforeThreshold() { fun startClosingEvent_whileNotOnKeyguardAndNotOnLauncher_doesNotTriggerBeforeThreshold() {
setKeyguardVisibility(visible = false) setKeyguardVisibility(visible = false)
setupForegroundActivityType(isHomeActivity = false) setupForegroundActivityType(isHomeActivity = false)
sendHingeAngleEvent(180) setInitialHingeAngle(180)
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1) sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
@@ -398,7 +406,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test @Test
fun startClosingEvent_whileKeyguardStateNotAvailable_triggerBeforeThreshold() { fun startClosingEvent_whileKeyguardStateNotAvailable_triggerBeforeThreshold() {
setKeyguardVisibility(visible = null) setKeyguardVisibility(visible = null)
sendHingeAngleEvent(180) setInitialHingeAngle(180)
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1) sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
@@ -408,7 +416,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test @Test
fun startClosingEvent_whileonKeyguard_doesTriggerBeforeThreshold() { fun startClosingEvent_whileonKeyguard_doesTriggerBeforeThreshold() {
setKeyguardVisibility(visible = true) setKeyguardVisibility(visible = true)
sendHingeAngleEvent(180) setInitialHingeAngle(180)
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1) sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
@@ -418,9 +426,59 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test @Test
fun startClosingEvent_whileNotOnKeyguard_triggersAfterThreshold() { fun startClosingEvent_whileNotOnKeyguard_triggersAfterThreshold() {
setKeyguardVisibility(visible = false) 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) 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) { private fun setFoldState(folded: Boolean) {
foldProvider.notifyFolded(folded) foldProvider.notifyFolded(folded)
} }
@@ -521,6 +574,17 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
testHingeAngleProvider.notifyAngle(angle.toFloat()) 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 class TestFoldProvider : FoldProvider {
private val callbacks = arrayListOf<FoldCallback>() private val callbacks = arrayListOf<FoldCallback>()

View File

@@ -57,4 +57,8 @@ class TestFoldStateProvider : FoldStateProvider {
fun sendHingeAngleUpdate(angle: Float) { fun sendHingeAngleUpdate(angle: Float) {
listeners.forEach { it.onHingeAngleUpdate(angle) } 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
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener 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_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
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
import javax.inject.Inject import javax.inject.Inject
@@ -59,12 +58,15 @@ constructor(private val foldStateProvider: FoldStateProvider) :
} }
override fun onFoldUpdate(@FoldUpdate update: Int) { override fun onFoldUpdate(@FoldUpdate update: Int) {
when (update) { if (update == FOLD_UPDATE_FINISH_CLOSED) {
FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> animator.start() animator.cancel()
FOLD_UPDATE_FINISH_CLOSED -> animator.cancel()
} }
} }
override fun onUnfoldedScreenAvailable() {
animator.start()
}
override fun addCallback(listener: TransitionProgressListener) { override fun addCallback(listener: TransitionProgressListener) {
listeners.add(listener) listeners.add(listener)
} }
@@ -73,8 +75,6 @@ constructor(private val foldStateProvider: FoldStateProvider) :
listeners.remove(listener) listeners.remove(listener)
} }
override fun onHingeAngleUpdate(angle: Float) {}
private object AnimationProgressProperty : private object AnimationProgressProperty :
FloatProperty<FixedTimingTransitionProgressProvider>("animation_progress") { 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_FULL_OPEN
import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_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_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
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
@@ -78,21 +77,11 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor(
override fun onFoldUpdate(@FoldUpdate update: Int) { override fun onFoldUpdate(@FoldUpdate update: Int) {
when (update) { 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 -> { FOLD_UPDATE_FINISH_FULL_OPEN, FOLD_UPDATE_FINISH_HALF_OPEN -> {
// 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
// in FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE event handler, so we don't need to // in onUnfoldedScreenAvailable event handler, so we don't need to cancel it here.
// cancel it here.
if (isTransitionRunning) { if (isTransitionRunning) {
cancelTransition(endValue = 1f, animate = true) 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) { private fun cancelTransition(endValue: Float, animate: Boolean) {
if (isTransitionRunning && animate) { if (isTransitionRunning && animate) {
if (endValue == 1.0f && !isAnimatedCancelRunning) { if (endValue == 1.0f && !isAnimatedCancelRunning) {

View File

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

View File

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