Continue folding animation after it cancelled if necessary
When folding the device immediately after unfolding the folding animation wasn't triggering sometimes. It happenned because we force the animation to end by 'cancelling' it (setting target progress to 1f and ignoring all hinge angle events). Added resetting cancelling when we started folding again during this cancellation animation. Bug: 219962337 Test: atest com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProviderTest Change-Id: Iafd1b3e40f40debf4c09874ddb418a1f2fd2824c
This commit is contained in:
@@ -33,7 +33,7 @@ import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
|
||||
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
|
||||
|
||||
/** Maps fold updates to unfold transition progress using DynamicAnimation. */
|
||||
internal class PhysicsBasedUnfoldTransitionProgressProvider(
|
||||
class PhysicsBasedUnfoldTransitionProgressProvider(
|
||||
private val foldStateProvider: FoldStateProvider
|
||||
) : UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener {
|
||||
|
||||
@@ -97,7 +97,17 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
|
||||
FOLD_UPDATE_START_CLOSING -> {
|
||||
// The transition might be already running as the device might start closing several
|
||||
// times before reaching an end state.
|
||||
if (!isTransitionRunning) {
|
||||
if (isTransitionRunning) {
|
||||
// If we are cancelling the animation, reset that so we can resume it normally.
|
||||
// The animation could be 'cancelled' when the user stops folding/unfolding
|
||||
// for some period of time or fully unfolds the device. In this case,
|
||||
// it is forced to run to the end ignoring all further hinge angle events.
|
||||
// By resetting this flag we allow reacting to hinge angle events again, so
|
||||
// the transition continues running.
|
||||
if (isAnimatedCancelRunning) {
|
||||
isAnimatedCancelRunning = false
|
||||
}
|
||||
} else {
|
||||
startTransition(startValue = 1f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.systemui.unfold.progress
|
||||
|
||||
import android.testing.AndroidTestingRunner
|
||||
import androidx.test.filters.SmallTest
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
|
||||
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
|
||||
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.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_FINISH_CLOSED
|
||||
import com.android.systemui.unfold.util.TestFoldStateProvider
|
||||
import com.android.systemui.util.leak.ReferenceTestUtils.waitForCondition
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.google.common.truth.Truth.assertWithMessage
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@SmallTest
|
||||
class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
|
||||
|
||||
private val foldStateProvider: TestFoldStateProvider = TestFoldStateProvider()
|
||||
private val listener = TestUnfoldProgressListener()
|
||||
private lateinit var progressProvider: UnfoldTransitionProgressProvider
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
progressProvider = PhysicsBasedUnfoldTransitionProgressProvider(
|
||||
foldStateProvider
|
||||
)
|
||||
progressProvider.addCallback(listener)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUnfold_emitsIncreasingTransitionEvents() {
|
||||
runOnMainThreadWithInterval(
|
||||
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
|
||||
{ foldStateProvider.sendHingeAngleUpdate(10f) },
|
||||
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) },
|
||||
{ foldStateProvider.sendHingeAngleUpdate(90f) },
|
||||
{ foldStateProvider.sendHingeAngleUpdate(180f) },
|
||||
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) },
|
||||
)
|
||||
|
||||
with(listener.ensureTransitionFinished()) {
|
||||
assertIncreasingProgress()
|
||||
assertFinishedWithUnfold()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUnfold_screenAvailableOnlyAfterFullUnfold_emitsIncreasingTransitionEvents() {
|
||||
runOnMainThreadWithInterval(
|
||||
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
|
||||
{ foldStateProvider.sendHingeAngleUpdate(10f) },
|
||||
{ foldStateProvider.sendHingeAngleUpdate(90f) },
|
||||
{ foldStateProvider.sendHingeAngleUpdate(180f) },
|
||||
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) },
|
||||
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) },
|
||||
)
|
||||
|
||||
with(listener.ensureTransitionFinished()) {
|
||||
assertIncreasingProgress()
|
||||
assertFinishedWithUnfold()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFold_emitsDecreasingTransitionEvents() {
|
||||
runOnMainThreadWithInterval(
|
||||
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_CLOSING) },
|
||||
{ foldStateProvider.sendHingeAngleUpdate(170f) },
|
||||
{ foldStateProvider.sendHingeAngleUpdate(90f) },
|
||||
{ foldStateProvider.sendHingeAngleUpdate(10f) },
|
||||
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) },
|
||||
)
|
||||
|
||||
with(listener.ensureTransitionFinished()) {
|
||||
assertDecreasingProgress()
|
||||
assertFinishedWithFold()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUnfoldAndStopUnfolding_finishesTheUnfoldTransition() {
|
||||
runOnMainThreadWithInterval(
|
||||
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
|
||||
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) },
|
||||
{ foldStateProvider.sendHingeAngleUpdate(10f) },
|
||||
{ foldStateProvider.sendHingeAngleUpdate(90f) },
|
||||
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN) },
|
||||
)
|
||||
|
||||
with(listener.ensureTransitionFinished()) {
|
||||
assertIncreasingProgress()
|
||||
assertFinishedWithUnfold()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFoldImmediatelyAfterUnfold_runsFoldAnimation() {
|
||||
runOnMainThreadWithInterval(
|
||||
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
|
||||
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) },
|
||||
{ foldStateProvider.sendHingeAngleUpdate(10f) },
|
||||
{ foldStateProvider.sendHingeAngleUpdate(90f) },
|
||||
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) },
|
||||
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_CLOSING) },
|
||||
{ foldStateProvider.sendHingeAngleUpdate(60f) },
|
||||
{ foldStateProvider.sendHingeAngleUpdate(10f) },
|
||||
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) },
|
||||
)
|
||||
|
||||
with(listener.ensureTransitionFinished()) {
|
||||
assertHasFoldAnimationAtTheEnd()
|
||||
}
|
||||
}
|
||||
|
||||
private class TestUnfoldProgressListener : TransitionProgressListener {
|
||||
|
||||
private val recordings: MutableList<UnfoldTransitionRecording> = arrayListOf()
|
||||
private var currentRecording: UnfoldTransitionRecording? = null
|
||||
|
||||
override fun onTransitionStarted() {
|
||||
assertWithMessage("Trying to start a transition when it is already in progress")
|
||||
.that(currentRecording).isNull()
|
||||
|
||||
currentRecording = UnfoldTransitionRecording()
|
||||
}
|
||||
|
||||
override fun onTransitionProgress(progress: Float) {
|
||||
assertWithMessage("Received transition progress event when it's not started")
|
||||
.that(currentRecording).isNotNull()
|
||||
currentRecording!!.addProgress(progress)
|
||||
}
|
||||
|
||||
override fun onTransitionFinished() {
|
||||
assertWithMessage("Received transition finish event when it's not started")
|
||||
.that(currentRecording).isNotNull()
|
||||
recordings += currentRecording!!
|
||||
currentRecording = null
|
||||
}
|
||||
|
||||
fun ensureTransitionFinished(): UnfoldTransitionRecording {
|
||||
waitForCondition { recordings.size == 1 }
|
||||
return recordings.first()
|
||||
}
|
||||
|
||||
class UnfoldTransitionRecording {
|
||||
private val progressHistory: MutableList<Float> = arrayListOf()
|
||||
|
||||
fun addProgress(progress: Float) {
|
||||
assertThat(progress).isAtMost(1.0f)
|
||||
assertThat(progress).isAtLeast(0.0f)
|
||||
|
||||
progressHistory += progress
|
||||
}
|
||||
|
||||
fun assertIncreasingProgress() {
|
||||
assertThat(progressHistory.size).isGreaterThan(MIN_ANIMATION_EVENTS)
|
||||
assertThat(progressHistory).isInOrder()
|
||||
}
|
||||
|
||||
fun assertDecreasingProgress() {
|
||||
assertThat(progressHistory.size).isGreaterThan(MIN_ANIMATION_EVENTS)
|
||||
assertThat(progressHistory).isInOrder(Comparator.reverseOrder<Float>())
|
||||
}
|
||||
|
||||
fun assertFinishedWithUnfold() {
|
||||
assertThat(progressHistory).isNotEmpty()
|
||||
assertThat(progressHistory.last()).isEqualTo(1.0f)
|
||||
}
|
||||
|
||||
fun assertFinishedWithFold() {
|
||||
assertThat(progressHistory).isNotEmpty()
|
||||
assertThat(progressHistory.last()).isEqualTo(0.0f)
|
||||
}
|
||||
|
||||
fun assertHasFoldAnimationAtTheEnd() {
|
||||
// Check that there are at least a few decreasing events at the end
|
||||
assertThat(progressHistory.size).isGreaterThan(MIN_ANIMATION_EVENTS)
|
||||
assertThat(progressHistory.takeLast(MIN_ANIMATION_EVENTS))
|
||||
.isInOrder(Comparator.reverseOrder<Float>())
|
||||
assertThat(progressHistory.last()).isEqualTo(0.0f)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val MIN_ANIMATION_EVENTS = 5
|
||||
}
|
||||
}
|
||||
|
||||
private fun runOnMainThreadWithInterval(vararg blocks: () -> Unit, intervalMillis: Long = 60) {
|
||||
blocks.forEach {
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync {
|
||||
it()
|
||||
}
|
||||
Thread.sleep(intervalMillis)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.systemui.unfold.util
|
||||
|
||||
import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
|
||||
import com.android.systemui.unfold.updates.FoldStateProvider
|
||||
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
|
||||
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
|
||||
|
||||
class TestFoldStateProvider : FoldStateProvider {
|
||||
|
||||
private val listeners: MutableList<FoldUpdatesListener> = arrayListOf()
|
||||
|
||||
override fun start() {
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
listeners.clear()
|
||||
}
|
||||
|
||||
private var _isFullyOpened: Boolean = false
|
||||
|
||||
override val isFullyOpened: Boolean
|
||||
get() = _isFullyOpened
|
||||
|
||||
override fun addCallback(listener: FoldUpdatesListener) {
|
||||
listeners += listener
|
||||
}
|
||||
|
||||
override fun removeCallback(listener: FoldUpdatesListener) {
|
||||
listeners -= listener
|
||||
}
|
||||
|
||||
fun sendFoldUpdate(@FoldUpdate update: Int) {
|
||||
if (update == FOLD_UPDATE_FINISH_FULL_OPEN) {
|
||||
_isFullyOpened = true
|
||||
}
|
||||
listeners.forEach { it.onFoldUpdate(update) }
|
||||
}
|
||||
|
||||
fun sendHingeAngleUpdate(angle: Float) {
|
||||
listeners.forEach { it.onHingeAngleUpdate(angle) }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user