Merge "[Unfold animation] Adjust animation parameters" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
dd5d243b29
@@ -192,4 +192,4 @@ class UnfoldMoveFromCenterAnimator @JvmOverloads constructor(
|
||||
)
|
||||
}
|
||||
|
||||
private const val TRANSLATION_PERCENTAGE = 0.3f
|
||||
private const val TRANSLATION_PERCENTAGE = 0.08f
|
||||
|
||||
@@ -694,6 +694,12 @@ object Flags {
|
||||
val 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
|
||||
val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release")
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ import android.view.ViewGroup
|
||||
import android.view.ViewTreeObserver
|
||||
import com.android.systemui.Gefingerpoken
|
||||
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.ShadeLogger
|
||||
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
|
||||
@@ -215,6 +217,7 @@ class PhoneStatusBarViewController private constructor(
|
||||
private val unfoldComponent: Optional<SysUIUnfoldComponent>,
|
||||
@Named(UNFOLD_STATUS_BAR)
|
||||
private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>,
|
||||
private val featureFlags: FeatureFlags,
|
||||
private val userChipViewModel: StatusBarUserChipViewModel,
|
||||
private val centralSurfaces: CentralSurfaces,
|
||||
private val shadeController: ShadeController,
|
||||
@@ -224,17 +227,25 @@ class PhoneStatusBarViewController private constructor(
|
||||
) {
|
||||
fun create(
|
||||
view: PhoneStatusBarView
|
||||
) =
|
||||
PhoneStatusBarViewController(
|
||||
view,
|
||||
progressProvider.getOrNull(),
|
||||
centralSurfaces,
|
||||
shadeController,
|
||||
shadeLogger,
|
||||
unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController(),
|
||||
userChipViewModel,
|
||||
viewUtil,
|
||||
configurationController
|
||||
): PhoneStatusBarViewController {
|
||||
val statusBarMoveFromCenterAnimationController =
|
||||
if (featureFlags.isEnabled(Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS)) {
|
||||
unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
return PhoneStatusBarViewController(
|
||||
view,
|
||||
progressProvider.getOrNull(),
|
||||
centralSurfaces,
|
||||
shadeController,
|
||||
shadeLogger,
|
||||
statusBarMoveFromCenterAnimationController,
|
||||
userChipViewModel,
|
||||
viewUtil,
|
||||
configurationController
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ constructor(
|
||||
private var isFolded: Boolean = false
|
||||
private var isUnfoldHandled: Boolean = true
|
||||
private var overlayAddReason: AddOverlayReason? = null
|
||||
private var isTouchBlocked: Boolean = true
|
||||
|
||||
private var currentRotation: Int = context.display!!.rotation
|
||||
|
||||
@@ -253,7 +254,15 @@ constructor(
|
||||
params.layoutInDisplayCutoutMode =
|
||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
|
||||
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()
|
||||
|
||||
val packageName: String = context.opPackageName
|
||||
@@ -262,6 +271,24 @@ constructor(
|
||||
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 {
|
||||
val isVerticalFold =
|
||||
currentRotation == Surface.ROTATION_0 || currentRotation == Surface.ROTATION_180
|
||||
@@ -288,7 +315,10 @@ constructor(
|
||||
private inner class TransitionListener : TransitionProgressListener {
|
||||
|
||||
override fun onTransitionProgress(progress: Float) {
|
||||
executeInBackground { scrimView?.revealAmount = calculateRevealAmount(progress) }
|
||||
executeInBackground {
|
||||
scrimView?.revealAmount = calculateRevealAmount(progress)
|
||||
updateTouchBlockIfNeeded(progress)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTransitionFinished() {
|
||||
@@ -360,5 +390,7 @@ constructor(
|
||||
// constants for revealAmount.
|
||||
const val TRANSPARENT = 1f
|
||||
const val BLACK = 0f
|
||||
|
||||
private const val UNFOLD_BLOCK_TOUCHES_UNTIL_PROGRESS = 0.8f
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,8 +65,8 @@ class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() {
|
||||
// Positive translationX -> translated to the right
|
||||
// 10x10 view center is 25px from the center,
|
||||
// When progress is 0.5 it should be translated at:
|
||||
// 25 * 0.3 * (1 - 0.5) = 3.75px
|
||||
assertThat(view.translationX).isWithin(0.01f).of(3.75f)
|
||||
// 25 * 0.08 * (1 - 0.5) = 1px
|
||||
assertThat(view.translationX).isWithin(0.01f).of(1.0f)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -81,8 +81,8 @@ class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() {
|
||||
// Positive translationX -> translated to the right
|
||||
// 10x10 view center is 25px from the center,
|
||||
// When progress is 0 it should be translated at:
|
||||
// 25 * 0.3 * (1 - 0) = 7.5px
|
||||
assertThat(view.translationX).isWithin(0.01f).of(7.5f)
|
||||
// 25 * 0.08 * (1 - 0) = 7.5px
|
||||
assertThat(view.translationX).isWithin(0.01f).of(2f)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -97,7 +97,7 @@ class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() {
|
||||
// Positive translationX -> translated to the right
|
||||
// 10x10 view center is 25px from the center,
|
||||
// When progress is 1 it should be translated at:
|
||||
// 25 * 0.3 * 0 = 0px
|
||||
// 25 * 0.08 * 0 = 0px
|
||||
assertThat(view.translationX).isEqualTo(0f)
|
||||
}
|
||||
|
||||
@@ -113,8 +113,8 @@ class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() {
|
||||
// Positive translationX -> translated to the right, original translation is ignored
|
||||
// 10x10 view center is 25px from the center,
|
||||
// When progress is 0.5 it should be translated at:
|
||||
// 25 * 0.3 * (1 - 0.5) = 3.75px
|
||||
assertThat(view.translationX).isWithin(0.01f).of(3.75f)
|
||||
// 25 * 0.08 * (1 - 0.5) = 1px
|
||||
assertThat(view.translationX).isWithin(0.01f).of(1.0f)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -154,7 +154,7 @@ class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() {
|
||||
animator.onTransitionProgress(0.5f)
|
||||
|
||||
// Positive translationY -> translated to the bottom
|
||||
assertThat(view.translationY).isWithin(0.01f).of(3.75f)
|
||||
assertThat(view.translationY).isWithin(0.01f).of(1f)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -169,7 +169,7 @@ class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() {
|
||||
animator.updateViewPositions()
|
||||
|
||||
// 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(
|
||||
|
||||
@@ -25,6 +25,8 @@ import androidx.test.filters.SmallTest
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.android.systemui.R
|
||||
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.ShadeControllerImpl
|
||||
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.user.ui.viewmodel.StatusBarUserChipViewModel
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.android.systemui.util.view.ViewUtil
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
@@ -54,6 +57,8 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
|
||||
@Mock
|
||||
private lateinit var notificationPanelViewController: NotificationPanelViewController
|
||||
@Mock
|
||||
private lateinit var featureFlags: FeatureFlags
|
||||
@Mock
|
||||
private lateinit var moveFromCenterAnimation: StatusBarMoveFromCenterAnimationController
|
||||
@Mock
|
||||
private lateinit var sysuiUnfoldComponent: SysUIUnfoldComponent
|
||||
@@ -93,6 +98,8 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun onViewAttachedAndDrawn_moveFromCenterAnimationEnabled_moveFromCenterAnimationInitialized() {
|
||||
whenever(featureFlags.isEnabled(Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS))
|
||||
.thenReturn(true)
|
||||
val view = createViewMock()
|
||||
val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java)
|
||||
unfoldConfig.isEnabled = true
|
||||
@@ -107,6 +114,20 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
|
||||
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
|
||||
fun handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() {
|
||||
`when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(false)
|
||||
@@ -179,6 +200,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
|
||||
return PhoneStatusBarViewController.Factory(
|
||||
Optional.of(sysuiUnfoldComponent),
|
||||
Optional.of(progressProvider),
|
||||
featureFlags,
|
||||
userChipViewModel,
|
||||
centralSurfacesImpl,
|
||||
shadeControllerImpl,
|
||||
|
||||
@@ -40,7 +40,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
progressProvider = PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider)
|
||||
progressProvider = PhysicsBasedUnfoldTransitionProgressProvider(context, foldStateProvider)
|
||||
progressProvider.addCallback(listener)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,15 @@
|
||||
*/
|
||||
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.util.FloatProperty
|
||||
import android.util.Log
|
||||
import android.view.animation.AnimationUtils.loadInterpolator
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation
|
||||
import androidx.dynamicanimation.animation.FloatPropertyCompat
|
||||
import androidx.dynamicanimation.animation.SpringAnimation
|
||||
@@ -34,14 +41,22 @@ import com.android.systemui.unfold.updates.name
|
||||
import javax.inject.Inject
|
||||
|
||||
/** Maps fold updates to unfold transition progress using DynamicAnimation. */
|
||||
class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor(
|
||||
private val foldStateProvider: FoldStateProvider
|
||||
) : UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener {
|
||||
class PhysicsBasedUnfoldTransitionProgressProvider
|
||||
@Inject
|
||||
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 =
|
||||
SpringAnimation(this, AnimationProgressProperty).apply {
|
||||
addEndListener(this@PhysicsBasedUnfoldTransitionProgressProvider)
|
||||
}
|
||||
SpringAnimation(
|
||||
this,
|
||||
FloatPropertyCompat.createFloatPropertyCompat(AnimationProgressProperty)
|
||||
)
|
||||
.apply { addEndListener(this@PhysicsBasedUnfoldTransitionProgressProvider) }
|
||||
|
||||
private var isTransitionRunning = false
|
||||
private var isAnimatedCancelRunning = false
|
||||
@@ -76,7 +91,8 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor(
|
||||
|
||||
override fun onFoldUpdate(@FoldUpdate update: Int) {
|
||||
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.
|
||||
// This could happen when we fully unfolded the device before the screen
|
||||
// became available. In this case we start and immediately cancel the animation
|
||||
@@ -100,6 +116,14 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor(
|
||||
// the transition continues running.
|
||||
if (isAnimatedCancelRunning) {
|
||||
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 {
|
||||
startTransition(startValue = 1f)
|
||||
@@ -130,13 +154,22 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor(
|
||||
}
|
||||
|
||||
isAnimatedCancelRunning = true
|
||||
springAnimation.animateToFinalPosition(endValue)
|
||||
|
||||
if (USE_CANNED_ANIMATION) {
|
||||
startCannedCancelAnimation()
|
||||
} else {
|
||||
springAnimation.animateToFinalPosition(endValue)
|
||||
}
|
||||
} else {
|
||||
transitionProgress = endValue
|
||||
isAnimatedCancelRunning = false
|
||||
isTransitionRunning = false
|
||||
springAnimation.cancel()
|
||||
|
||||
cannedAnimator?.removeAllListeners()
|
||||
cannedAnimator?.cancel()
|
||||
cannedAnimator = null
|
||||
|
||||
listeners.forEach { it.onTransitionFinished() }
|
||||
|
||||
if (DEBUG) {
|
||||
@@ -157,7 +190,7 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor(
|
||||
}
|
||||
|
||||
private fun onStartTransition() {
|
||||
Trace.beginSection( "$TAG#onStartTransition")
|
||||
Trace.beginSection("$TAG#onStartTransition")
|
||||
listeners.forEach { it.onTransitionStarted() }
|
||||
Trace.endSection()
|
||||
|
||||
@@ -195,8 +228,39 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor(
|
||||
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 :
|
||||
FloatPropertyCompat<PhysicsBasedUnfoldTransitionProgressProvider>("animation_progress") {
|
||||
FloatProperty<PhysicsBasedUnfoldTransitionProgressProvider>("animation_progress") {
|
||||
|
||||
override fun setValue(
|
||||
provider: PhysicsBasedUnfoldTransitionProgressProvider,
|
||||
@@ -205,7 +269,7 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor(
|
||||
provider.transitionProgress = value
|
||||
}
|
||||
|
||||
override fun getValue(provider: PhysicsBasedUnfoldTransitionProgressProvider): Float =
|
||||
override fun get(provider: PhysicsBasedUnfoldTransitionProgressProvider): Float =
|
||||
provider.transitionProgress
|
||||
}
|
||||
}
|
||||
@@ -213,6 +277,8 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor(
|
||||
private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider"
|
||||
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 MINIMAL_VISIBLE_CHANGE = 0.001f
|
||||
private const val FINAL_HINGE_ANGLE_POSITION = 165f
|
||||
|
||||
Reference in New Issue
Block a user