Merge "Continue folding animation after it cancelled if necessary" into tm-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
54c3d1b82e
@@ -33,7 +33,7 @@ import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
|
|||||||
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
|
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
|
||||||
|
|
||||||
/** Maps fold updates to unfold transition progress using DynamicAnimation. */
|
/** Maps fold updates to unfold transition progress using DynamicAnimation. */
|
||||||
internal class PhysicsBasedUnfoldTransitionProgressProvider(
|
class PhysicsBasedUnfoldTransitionProgressProvider(
|
||||||
private val foldStateProvider: FoldStateProvider
|
private val foldStateProvider: FoldStateProvider
|
||||||
) : UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener {
|
) : UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener {
|
||||||
|
|
||||||
@@ -97,7 +97,17 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider(
|
|||||||
FOLD_UPDATE_START_CLOSING -> {
|
FOLD_UPDATE_START_CLOSING -> {
|
||||||
// The transition might be already running as the device might start closing several
|
// The transition might be already running as the device might start closing several
|
||||||
// times before reaching an end state.
|
// 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)
|
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