Merge "[Unfold animation] Adjust animation parameters" into tm-qpr-dev

This commit is contained in:
Nick Chameyev
2023-03-20 10:51:10 +00:00
committed by Android (Google) Code Review
8 changed files with 172 additions and 35 deletions

View File

@@ -192,4 +192,4 @@ class UnfoldMoveFromCenterAnimator @JvmOverloads constructor(
) )
} }
private const val TRANSLATION_PERCENTAGE = 0.3f private const val TRANSLATION_PERCENTAGE = 0.08f

View File

@@ -694,6 +694,12 @@ object Flags {
val ENABLE_DARK_VIGNETTE_WHEN_FOLDING = val ENABLE_DARK_VIGNETTE_WHEN_FOLDING =
unreleasedFlag(2700, "enable_dark_vignette_when_folding") unreleasedFlag(2700, "enable_dark_vignette_when_folding")
// TODO(b/265764985): Tracking Bug
@Keep
@JvmField
val ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS =
unreleasedFlag(2701, "enable_unfold_status_bar_animations")
// TODO(b259590361): Tracking bug // TODO(b259590361): Tracking bug
val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release") val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release")

View File

@@ -26,6 +26,8 @@ import android.view.ViewGroup
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import com.android.systemui.Gefingerpoken import com.android.systemui.Gefingerpoken
import com.android.systemui.R import com.android.systemui.R
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.shade.ShadeController import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeLogger import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
@@ -215,6 +217,7 @@ class PhoneStatusBarViewController private constructor(
private val unfoldComponent: Optional<SysUIUnfoldComponent>, private val unfoldComponent: Optional<SysUIUnfoldComponent>,
@Named(UNFOLD_STATUS_BAR) @Named(UNFOLD_STATUS_BAR)
private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>, private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>,
private val featureFlags: FeatureFlags,
private val userChipViewModel: StatusBarUserChipViewModel, private val userChipViewModel: StatusBarUserChipViewModel,
private val centralSurfaces: CentralSurfaces, private val centralSurfaces: CentralSurfaces,
private val shadeController: ShadeController, private val shadeController: ShadeController,
@@ -224,17 +227,25 @@ class PhoneStatusBarViewController private constructor(
) { ) {
fun create( fun create(
view: PhoneStatusBarView view: PhoneStatusBarView
) = ): PhoneStatusBarViewController {
PhoneStatusBarViewController( val statusBarMoveFromCenterAnimationController =
if (featureFlags.isEnabled(Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS)) {
unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController()
} else {
null
}
return PhoneStatusBarViewController(
view, view,
progressProvider.getOrNull(), progressProvider.getOrNull(),
centralSurfaces, centralSurfaces,
shadeController, shadeController,
shadeLogger, shadeLogger,
unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController(), statusBarMoveFromCenterAnimationController,
userChipViewModel, userChipViewModel,
viewUtil, viewUtil,
configurationController configurationController
) )
} }
}
} }

View File

@@ -87,6 +87,7 @@ constructor(
private var isFolded: Boolean = false private var isFolded: Boolean = false
private var isUnfoldHandled: Boolean = true private var isUnfoldHandled: Boolean = true
private var overlayAddReason: AddOverlayReason? = null private var overlayAddReason: AddOverlayReason? = null
private var isTouchBlocked: Boolean = true
private var currentRotation: Int = context.display!!.rotation private var currentRotation: Int = context.display!!.rotation
@@ -253,7 +254,15 @@ constructor(
params.layoutInDisplayCutoutMode = params.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
params.fitInsetsTypes = 0 params.fitInsetsTypes = 0
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
val touchFlags =
if (isTouchBlocked) {
// Touchable by default, so it will block the touches
0
} else {
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
}
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or touchFlags
params.setTrustedOverlay() params.setTrustedOverlay()
val packageName: String = context.opPackageName val packageName: String = context.opPackageName
@@ -262,6 +271,24 @@ constructor(
return params return params
} }
private fun updateTouchBlockIfNeeded(progress: Float) {
// When unfolding unblock touches a bit earlier than the animation end as the
// interpolation has a long tail of very slight movement at the end which should not
// affect much the usage of the device
val shouldBlockTouches =
if (overlayAddReason == UNFOLD) {
progress < UNFOLD_BLOCK_TOUCHES_UNTIL_PROGRESS
} else {
true
}
if (isTouchBlocked != shouldBlockTouches) {
isTouchBlocked = shouldBlockTouches
traceSection("$TAG#relayoutToUpdateTouch") { root?.relayout(getLayoutParams()) }
}
}
private fun createLightRevealEffect(): LightRevealEffect { private fun createLightRevealEffect(): LightRevealEffect {
val isVerticalFold = val isVerticalFold =
currentRotation == Surface.ROTATION_0 || currentRotation == Surface.ROTATION_180 currentRotation == Surface.ROTATION_0 || currentRotation == Surface.ROTATION_180
@@ -288,7 +315,10 @@ constructor(
private inner class TransitionListener : TransitionProgressListener { private inner class TransitionListener : TransitionProgressListener {
override fun onTransitionProgress(progress: Float) { override fun onTransitionProgress(progress: Float) {
executeInBackground { scrimView?.revealAmount = calculateRevealAmount(progress) } executeInBackground {
scrimView?.revealAmount = calculateRevealAmount(progress)
updateTouchBlockIfNeeded(progress)
}
} }
override fun onTransitionFinished() { override fun onTransitionFinished() {
@@ -360,5 +390,7 @@ constructor(
// constants for revealAmount. // constants for revealAmount.
const val TRANSPARENT = 1f const val TRANSPARENT = 1f
const val BLACK = 0f const val BLACK = 0f
private const val UNFOLD_BLOCK_TOUCHES_UNTIL_PROGRESS = 0.8f
} }
} }

View File

@@ -65,8 +65,8 @@ class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() {
// Positive translationX -> translated to the right // Positive translationX -> translated to the right
// 10x10 view center is 25px from the center, // 10x10 view center is 25px from the center,
// When progress is 0.5 it should be translated at: // When progress is 0.5 it should be translated at:
// 25 * 0.3 * (1 - 0.5) = 3.75px // 25 * 0.08 * (1 - 0.5) = 1px
assertThat(view.translationX).isWithin(0.01f).of(3.75f) assertThat(view.translationX).isWithin(0.01f).of(1.0f)
} }
@Test @Test
@@ -81,8 +81,8 @@ class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() {
// Positive translationX -> translated to the right // Positive translationX -> translated to the right
// 10x10 view center is 25px from the center, // 10x10 view center is 25px from the center,
// When progress is 0 it should be translated at: // When progress is 0 it should be translated at:
// 25 * 0.3 * (1 - 0) = 7.5px // 25 * 0.08 * (1 - 0) = 7.5px
assertThat(view.translationX).isWithin(0.01f).of(7.5f) assertThat(view.translationX).isWithin(0.01f).of(2f)
} }
@Test @Test
@@ -97,7 +97,7 @@ class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() {
// Positive translationX -> translated to the right // Positive translationX -> translated to the right
// 10x10 view center is 25px from the center, // 10x10 view center is 25px from the center,
// When progress is 1 it should be translated at: // When progress is 1 it should be translated at:
// 25 * 0.3 * 0 = 0px // 25 * 0.08 * 0 = 0px
assertThat(view.translationX).isEqualTo(0f) assertThat(view.translationX).isEqualTo(0f)
} }
@@ -113,8 +113,8 @@ class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() {
// Positive translationX -> translated to the right, original translation is ignored // Positive translationX -> translated to the right, original translation is ignored
// 10x10 view center is 25px from the center, // 10x10 view center is 25px from the center,
// When progress is 0.5 it should be translated at: // When progress is 0.5 it should be translated at:
// 25 * 0.3 * (1 - 0.5) = 3.75px // 25 * 0.08 * (1 - 0.5) = 1px
assertThat(view.translationX).isWithin(0.01f).of(3.75f) assertThat(view.translationX).isWithin(0.01f).of(1.0f)
} }
@Test @Test
@@ -154,7 +154,7 @@ class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() {
animator.onTransitionProgress(0.5f) animator.onTransitionProgress(0.5f)
// Positive translationY -> translated to the bottom // Positive translationY -> translated to the bottom
assertThat(view.translationY).isWithin(0.01f).of(3.75f) assertThat(view.translationY).isWithin(0.01f).of(1f)
} }
@Test @Test
@@ -169,7 +169,7 @@ class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() {
animator.updateViewPositions() animator.updateViewPositions()
// Negative translationX -> translated to the left // Negative translationX -> translated to the left
assertThat(view.translationX).isWithin(0.1f).of(-5.25f) assertThat(view.translationX).isWithin(0.1f).of(-1.4f)
} }
private fun createView( private fun createView(

View File

@@ -25,6 +25,8 @@ import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import com.android.systemui.R import com.android.systemui.R
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.shade.NotificationPanelViewController import com.android.systemui.shade.NotificationPanelViewController
import com.android.systemui.shade.ShadeControllerImpl import com.android.systemui.shade.ShadeControllerImpl
import com.android.systemui.shade.ShadeLogger import com.android.systemui.shade.ShadeLogger
@@ -34,6 +36,7 @@ import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.view.ViewUtil import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.Before import org.junit.Before
@@ -54,6 +57,8 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
@Mock @Mock
private lateinit var notificationPanelViewController: NotificationPanelViewController private lateinit var notificationPanelViewController: NotificationPanelViewController
@Mock @Mock
private lateinit var featureFlags: FeatureFlags
@Mock
private lateinit var moveFromCenterAnimation: StatusBarMoveFromCenterAnimationController private lateinit var moveFromCenterAnimation: StatusBarMoveFromCenterAnimationController
@Mock @Mock
private lateinit var sysuiUnfoldComponent: SysUIUnfoldComponent private lateinit var sysuiUnfoldComponent: SysUIUnfoldComponent
@@ -93,6 +98,8 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
@Test @Test
fun onViewAttachedAndDrawn_moveFromCenterAnimationEnabled_moveFromCenterAnimationInitialized() { fun onViewAttachedAndDrawn_moveFromCenterAnimationEnabled_moveFromCenterAnimationInitialized() {
whenever(featureFlags.isEnabled(Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS))
.thenReturn(true)
val view = createViewMock() val view = createViewMock()
val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java) val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java)
unfoldConfig.isEnabled = true unfoldConfig.isEnabled = true
@@ -107,6 +114,20 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
verify(moveFromCenterAnimation).onViewsReady(any()) verify(moveFromCenterAnimation).onViewsReady(any())
} }
@Test
fun onViewAttachedAndDrawn_statusBarAnimationDisabled_animationNotInitialized() {
whenever(featureFlags.isEnabled(Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS))
.thenReturn(false)
val view = createViewMock()
unfoldConfig.isEnabled = true
// create the controller on main thread as it requires main looper
InstrumentationRegistry.getInstrumentation().runOnMainSync {
controller = createAndInitController(view)
}
verify(moveFromCenterAnimation, never()).onViewsReady(any())
}
@Test @Test
fun handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() { fun handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() {
`when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(false) `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(false)
@@ -179,6 +200,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
return PhoneStatusBarViewController.Factory( return PhoneStatusBarViewController.Factory(
Optional.of(sysuiUnfoldComponent), Optional.of(sysuiUnfoldComponent),
Optional.of(progressProvider), Optional.of(progressProvider),
featureFlags,
userChipViewModel, userChipViewModel,
centralSurfacesImpl, centralSurfacesImpl,
shadeControllerImpl, shadeControllerImpl,

View File

@@ -40,7 +40,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
@Before @Before
fun setUp() { fun setUp() {
progressProvider = PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider) progressProvider = PhysicsBasedUnfoldTransitionProgressProvider(context, foldStateProvider)
progressProvider.addCallback(listener) progressProvider.addCallback(listener)
} }

View File

@@ -15,8 +15,15 @@
*/ */
package com.android.systemui.unfold.progress package com.android.systemui.unfold.progress
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.content.Context
import android.os.Trace import android.os.Trace
import android.util.FloatProperty
import android.util.Log import android.util.Log
import android.view.animation.AnimationUtils.loadInterpolator
import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.FloatPropertyCompat import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringAnimation import androidx.dynamicanimation.animation.SpringAnimation
@@ -34,14 +41,22 @@ import com.android.systemui.unfold.updates.name
import javax.inject.Inject import javax.inject.Inject
/** Maps fold updates to unfold transition progress using DynamicAnimation. */ /** Maps fold updates to unfold transition progress using DynamicAnimation. */
class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor( class PhysicsBasedUnfoldTransitionProgressProvider
private val foldStateProvider: FoldStateProvider @Inject
) : UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener { constructor(context: Context, private val foldStateProvider: FoldStateProvider) :
UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener {
private val emphasizedInterpolator =
loadInterpolator(context, android.R.interpolator.fast_out_extra_slow_in)
private var cannedAnimator: ValueAnimator? = null
private val springAnimation = private val springAnimation =
SpringAnimation(this, AnimationProgressProperty).apply { SpringAnimation(
addEndListener(this@PhysicsBasedUnfoldTransitionProgressProvider) this,
} FloatPropertyCompat.createFloatPropertyCompat(AnimationProgressProperty)
)
.apply { addEndListener(this@PhysicsBasedUnfoldTransitionProgressProvider) }
private var isTransitionRunning = false private var isTransitionRunning = false
private var isAnimatedCancelRunning = false private var isAnimatedCancelRunning = false
@@ -76,7 +91,8 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor(
override fun onFoldUpdate(@FoldUpdate update: Int) { override fun onFoldUpdate(@FoldUpdate update: Int) {
when (update) { when (update) {
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
@@ -100,6 +116,14 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor(
// the transition continues running. // the transition continues running.
if (isAnimatedCancelRunning) { if (isAnimatedCancelRunning) {
isAnimatedCancelRunning = false isAnimatedCancelRunning = false
// Switching to spring animation, start the animation if it
// is not running already
springAnimation.animateToFinalPosition(1.0f)
cannedAnimator?.removeAllListeners()
cannedAnimator?.cancel()
cannedAnimator = null
} }
} else { } else {
startTransition(startValue = 1f) startTransition(startValue = 1f)
@@ -130,13 +154,22 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor(
} }
isAnimatedCancelRunning = true isAnimatedCancelRunning = true
if (USE_CANNED_ANIMATION) {
startCannedCancelAnimation()
} else {
springAnimation.animateToFinalPosition(endValue) springAnimation.animateToFinalPosition(endValue)
}
} else { } else {
transitionProgress = endValue transitionProgress = endValue
isAnimatedCancelRunning = false isAnimatedCancelRunning = false
isTransitionRunning = false isTransitionRunning = false
springAnimation.cancel() springAnimation.cancel()
cannedAnimator?.removeAllListeners()
cannedAnimator?.cancel()
cannedAnimator = null
listeners.forEach { it.onTransitionFinished() } listeners.forEach { it.onTransitionFinished() }
if (DEBUG) { if (DEBUG) {
@@ -157,7 +190,7 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor(
} }
private fun onStartTransition() { private fun onStartTransition() {
Trace.beginSection( "$TAG#onStartTransition") Trace.beginSection("$TAG#onStartTransition")
listeners.forEach { it.onTransitionStarted() } listeners.forEach { it.onTransitionStarted() }
Trace.endSection() Trace.endSection()
@@ -195,8 +228,39 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor(
listeners.remove(listener) listeners.remove(listener)
} }
private fun startCannedCancelAnimation() {
cannedAnimator?.cancel()
cannedAnimator = null
// Temporary remove listener to cancel the spring animation without
// finishing the transition
springAnimation.removeEndListener(this)
springAnimation.cancel()
springAnimation.addEndListener(this)
cannedAnimator =
ObjectAnimator.ofFloat(this, AnimationProgressProperty, transitionProgress, 1f).apply {
addListener(CannedAnimationListener())
duration =
(CANNED_ANIMATION_DURATION_MS.toFloat() * (1f - transitionProgress)).toLong()
interpolator = emphasizedInterpolator
start()
}
}
private inner class CannedAnimationListener : AnimatorListenerAdapter() {
override fun onAnimationStart(animator: Animator) {
Trace.beginAsyncSection("$TAG#cannedAnimatorRunning", 0)
}
override fun onAnimationEnd(animator: Animator) {
cancelTransition(1f, animate = false)
Trace.endAsyncSection("$TAG#cannedAnimatorRunning", 0)
}
}
private object AnimationProgressProperty : private object AnimationProgressProperty :
FloatPropertyCompat<PhysicsBasedUnfoldTransitionProgressProvider>("animation_progress") { FloatProperty<PhysicsBasedUnfoldTransitionProgressProvider>("animation_progress") {
override fun setValue( override fun setValue(
provider: PhysicsBasedUnfoldTransitionProgressProvider, provider: PhysicsBasedUnfoldTransitionProgressProvider,
@@ -205,7 +269,7 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor(
provider.transitionProgress = value provider.transitionProgress = value
} }
override fun getValue(provider: PhysicsBasedUnfoldTransitionProgressProvider): Float = override fun get(provider: PhysicsBasedUnfoldTransitionProgressProvider): Float =
provider.transitionProgress provider.transitionProgress
} }
} }
@@ -213,6 +277,8 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor(
private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider" private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider"
private const val DEBUG = true private const val DEBUG = true
private const val USE_CANNED_ANIMATION = true
private const val CANNED_ANIMATION_DURATION_MS = 1000
private const val SPRING_STIFFNESS = 600.0f private const val SPRING_STIFFNESS = 600.0f
private const val MINIMAL_VISIBLE_CHANGE = 0.001f private const val MINIMAL_VISIBLE_CHANGE = 0.001f
private const val FINAL_HINGE_ANGLE_POSITION = 165f private const val FINAL_HINGE_ANGLE_POSITION = 165f