Merge changes from topics "presubmit-am-7250e98934bc40cbaa91f4d3fe499617", "presubmit-am-a05bb8af1eb24cd390463ad38288dc31" into tm-dev

* changes:
  Rework system event animations to a federated model
  Don't transition to ongoing privacy dot if still animating
This commit is contained in:
Evan Laird
2022-03-16 21:56:01 +00:00
committed by Android (Google) Code Review
8 changed files with 425 additions and 289 deletions

View File

@@ -37,7 +37,5 @@
android:layout_gravity="center" android:layout_gravity="center"
android:minWidth="@dimen/ongoing_appops_chip_min_width" android:minWidth="@dimen/ongoing_appops_chip_min_width"
android:maxWidth="@dimen/ongoing_appops_chip_max_width" android:maxWidth="@dimen/ongoing_appops_chip_max_width"
android:clipChildren="false"
android:clipToPadding="false"
/> />
</com.android.systemui.privacy.OngoingPrivacyChip> </com.android.systemui.privacy.OngoingPrivacyChip>

View File

@@ -941,6 +941,9 @@
<!-- Three privacy items. This value must not be exceeded --> <!-- Three privacy items. This value must not be exceeded -->
<dimen name="ongoing_appops_chip_max_width">76dp</dimen> <dimen name="ongoing_appops_chip_max_width">76dp</dimen>
<dimen name="ongoing_appops_dot_diameter">6dp</dimen> <dimen name="ongoing_appops_dot_diameter">6dp</dimen>
<dimen name="ongoing_appops_chip_min_animation_width">10dp</dimen>
<dimen name="ongoing_appops_chip_animation_in_status_bar_translation_x">15dp</dimen>
<dimen name="ongoing_appops_chip_animation_out_status_bar_translation_x">7dp</dimen>
<!-- Total minimum padding to enforce to ensure that the dot can always show --> <!-- Total minimum padding to enforce to ensure that the dot can always show -->
<dimen name="ongoing_appops_dot_min_padding">20dp</dimen> <dimen name="ongoing_appops_dot_min_padding">20dp</dimen>

View File

@@ -16,9 +16,12 @@
package com.android.systemui.statusbar.events package com.android.systemui.statusbar.events
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.content.Context import android.content.Context
import android.graphics.Point import android.graphics.Rect
import android.view.Gravity import android.view.Gravity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@@ -30,6 +33,7 @@ import com.android.systemui.R
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.window.StatusBarWindowController import com.android.systemui.statusbar.window.StatusBarWindowController
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.roundToInt
/** /**
* Controls the view for system event animations. * Controls the view for system event animations.
@@ -38,7 +42,7 @@ class SystemEventChipAnimationController @Inject constructor(
private val context: Context, private val context: Context,
private val statusBarWindowController: StatusBarWindowController, private val statusBarWindowController: StatusBarWindowController,
private val contentInsetsProvider: StatusBarContentInsetsProvider private val contentInsetsProvider: StatusBarContentInsetsProvider
) : SystemStatusChipAnimationCallback { ) : SystemStatusAnimationCallback {
private lateinit var animationWindowView: FrameLayout private lateinit var animationWindowView: FrameLayout
@@ -49,90 +53,169 @@ class SystemEventChipAnimationController @Inject constructor(
private var chipRight = 0 private var chipRight = 0
private var chipLeft = 0 private var chipLeft = 0
private var chipWidth = 0 private var chipWidth = 0
private var dotCenter = Point(0, 0) private var chipMinWidth = context.resources.getDimensionPixelSize(
R.dimen.ongoing_appops_chip_min_animation_width)
private var dotSize = context.resources.getDimensionPixelSize( private var dotSize = context.resources.getDimensionPixelSize(
R.dimen.ongoing_appops_dot_diameter) R.dimen.ongoing_appops_dot_diameter)
// If the chip animates away to a persistent dot, then we modify the CHIP_OUT animation // Use during animation so that multiple animators can update the drawing rect
private var isAnimatingToDot = false private var animRect = Rect()
// TODO: move to dagger // TODO: move to dagger
private var initialized = false private var initialized = false
override fun onChipAnimationStart( /**
viewCreator: ViewCreator, * Give the chip controller a chance to inflate and configure the chip view before we start
@SystemAnimationState state: Int * animating
) { */
if (!initialized) init() fun prepareChipAnimation(viewCreator: ViewCreator) {
if (!initialized) {
init()
}
animationDirection = if (animationWindowView.isLayoutRtl) RIGHT else LEFT
if (state == ANIMATING_IN) { // Initialize the animated view
animationDirection = if (animationWindowView.isLayoutRtl) RIGHT else LEFT val insets = contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()
currentAnimatedView = viewCreator(context).also {
animationWindowView.addView(
it.view,
layoutParamsDefault(
if (animationWindowView.isLayoutRtl) insets.first
else insets.second))
it.view.alpha = 0f
// For some reason, the window view's measured width is always 0 here, so use the
// parent (status bar)
it.view.measure(
View.MeasureSpec.makeMeasureSpec(
(animationWindowView.parent as View).width, AT_MOST),
View.MeasureSpec.makeMeasureSpec(animationWindowView.height, AT_MOST))
chipWidth = it.chipWidth
}
// Initialize the animated view // decide which direction we're animating from, and then set some screen coordinates
val insets = contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation() val contentRect = contentInsetsProvider.getStatusBarContentAreaForCurrentRotation()
currentAnimatedView = viewCreator(context).also { when (animationDirection) {
animationWindowView.addView( LEFT -> {
it.view, chipRight = contentRect.right
layoutParamsDefault( chipLeft = contentRect.right - chipWidth
if (animationWindowView.isLayoutRtl) insets.first
else insets.second))
it.view.alpha = 0f
// For some reason, the window view's measured width is always 0 here, so use the
// parent (status bar)
it.view.measure(
View.MeasureSpec.makeMeasureSpec(
(animationWindowView.parent as View).width, AT_MOST),
View.MeasureSpec.makeMeasureSpec(animationWindowView.height, AT_MOST))
chipWidth = it.chipWidth
} }
else /* RIGHT */ -> {
// decide which direction we're animating from, and then set some screen coordinates chipLeft = contentRect.left
val contentRect = contentInsetsProvider.getStatusBarContentAreaForCurrentRotation() chipRight = contentRect.left + chipWidth
when (animationDirection) {
LEFT -> {
chipRight = contentRect.right
chipLeft = contentRect.right - chipWidth
}
else /* RIGHT */ -> {
chipLeft = contentRect.left
chipRight = contentRect.left + chipWidth
}
}
currentAnimatedView?.apply {
updateAnimatedViewBoundsForAmount(0.1f, this)
}
} else {
// We are animating away
currentAnimatedView!!.view.apply {
alpha = 1f
} }
} }
} }
override fun onChipAnimationEnd(@SystemAnimationState state: Int) { override fun onSystemEventAnimationBegin(): Animator {
if (state == ANIMATING_IN) { initializeAnimRect()
// Finished animating in
currentAnimatedView?.apply { val alphaIn = ValueAnimator.ofFloat(0f, 1f).apply {
updateAnimatedViewBoundsForAmount(1f, this) startDelay = 117
} duration = 83
} else { interpolator = null
// Finished animating away addUpdateListener { currentAnimatedView?.view?.alpha = animatedValue as Float }
currentAnimatedView!!.view.apply {
visibility = View.INVISIBLE
}
animationWindowView.removeView(currentAnimatedView!!.view)
} }
val moveIn = ValueAnimator.ofInt(chipMinWidth, chipWidth).apply {
startDelay = 117
duration = 383
interpolator = STATUS_BAR_X_MOVE_IN
addUpdateListener {
updateAnimatedViewBoundsWidth(animatedValue as Int)
}
}
val animSet = AnimatorSet()
animSet.playTogether(alphaIn, moveIn)
return animSet
} }
override fun onChipAnimationUpdate( override fun onSystemEventAnimationFinish(hasPersistentDot: Boolean): Animator {
animator: ValueAnimator, initializeAnimRect()
@SystemAnimationState state: Int val finish = if (hasPersistentDot) {
) { createMoveOutAnimationForDot()
currentAnimatedView?.apply { } else {
val amt = (animator.animatedValue as Float).amt() createMoveOutAnimationDefault()
view.alpha = (animator.animatedValue as Float)
updateAnimatedViewBoundsForAmount(amt, this)
} }
finish.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
animationWindowView.removeView(currentAnimatedView!!.view)
}
})
return finish
}
private fun createMoveOutAnimationForDot(): Animator {
val width1 = ValueAnimator.ofInt(chipWidth, chipMinWidth).apply {
duration = 150
interpolator = STATUS_CHIP_WIDTH_TO_DOT_KEYFRAME_1
addUpdateListener {
updateAnimatedViewBoundsWidth(it.animatedValue as Int)
}
}
val width2 = ValueAnimator.ofInt(chipMinWidth, dotSize).apply {
startDelay = 150
duration = 333
interpolator = STATUS_CHIP_WIDTH_TO_DOT_KEYFRAME_2
addUpdateListener {
updateAnimatedViewBoundsWidth(it.animatedValue as Int)
}
}
val keyFrame1Height = dotSize * 2
val v = currentAnimatedView!!.view
val chipVerticalCenter = v.top + v.measuredHeight / 2
val height1 = ValueAnimator.ofInt(
currentAnimatedView!!.view.measuredHeight, keyFrame1Height).apply {
startDelay = 133
duration = 100
interpolator = STATUS_CHIP_HEIGHT_TO_DOT_KEYFRAME_1
addUpdateListener {
updateAnimatedViewBoundsHeight(it.animatedValue as Int, chipVerticalCenter)
}
}
val height2 = ValueAnimator.ofInt(keyFrame1Height, dotSize).apply {
startDelay = 233
duration = 250
interpolator = STATUS_CHIP_HEIGHT_TO_DOT_KEYFRAME_2
addUpdateListener {
updateAnimatedViewBoundsHeight(it.animatedValue as Int, chipVerticalCenter)
}
}
// Move the chip view to overlap exactly with the privacy dot. The chip displays by default
// exactly adjacent to the dot, so we can just move over by the diameter of the dot itself
val moveOut = ValueAnimator.ofInt(0, dotSize).apply {
startDelay = 50
duration = 183
interpolator = STATUS_CHIP_MOVE_TO_DOT
addUpdateListener {
// If RTL, we can just invert the move
val amt = if (animationDirection == LEFT) {
animatedValue as Int
} else {
-(animatedValue as Int)
}
updateAnimatedBoundsX(amt)
}
}
val animSet = AnimatorSet()
animSet.playTogether(width1, width2, height1, height2, moveOut)
return animSet
}
private fun createMoveOutAnimationDefault(): Animator {
val moveOut = ValueAnimator.ofInt(chipWidth, chipMinWidth).apply {
duration = 383
addUpdateListener {
currentAnimatedView?.apply {
updateAnimatedViewBoundsWidth(it.animatedValue as Int)
}
}
}
return moveOut
} }
private fun init() { private fun init() {
@@ -144,7 +227,6 @@ class SystemEventChipAnimationController @Inject constructor(
statusBarWindowController.addViewToWindow(animationWindowView, lp) statusBarWindowController.addViewToWindow(animationWindowView, lp)
animationWindowView.clipToPadding = false animationWindowView.clipToPadding = false
animationWindowView.clipChildren = false animationWindowView.clipChildren = false
animationWindowView.measureAllChildren = true
} }
private fun layoutParamsDefault(marginEnd: Int): FrameLayout.LayoutParams = private fun layoutParamsDefault(marginEnd: Int): FrameLayout.LayoutParams =
@@ -153,29 +235,55 @@ class SystemEventChipAnimationController @Inject constructor(
it.marginEnd = marginEnd it.marginEnd = marginEnd
} }
private fun updateAnimatedViewBoundsForAmount(amt: Float, chip: BackgroundAnimatableView) { private fun initializeAnimRect() = animRect.set(
chipLeft,
currentAnimatedView!!.view.top,
chipRight,
currentAnimatedView!!.view.bottom)
/**
* To be called during an animation, sets the width and updates the current animated chip view
*/
private fun updateAnimatedViewBoundsWidth(width: Int) {
when (animationDirection) { when (animationDirection) {
LEFT -> { LEFT -> {
chip.setBoundsForAnimation( animRect.set((chipRight - width), animRect.top, chipRight, animRect.bottom)
(chipRight - (chipWidth * amt)).toInt(), } else /* RIGHT */ -> {
chip.view.top, animRect.set(chipLeft, animRect.top, (chipLeft + width), animRect.bottom)
chipRight,
chip.view.bottom)
}
else /* RIGHT */ -> {
chip.setBoundsForAnimation(
chipLeft,
chip.view.top,
(chipLeft + (chipWidth * amt)).toInt(),
chip.view.bottom)
} }
} }
updateCurrentAnimatedView()
} }
private fun start() = if (animationWindowView.isLayoutRtl) right() else left() /**
private fun right() = contentInsetsProvider.getStatusBarContentAreaForCurrentRotation().right * To be called during an animation, updates the animation rect and sends the update to the chip
private fun left() = contentInsetsProvider.getStatusBarContentAreaForCurrentRotation().left */
private fun Float.amt() = 0.01f.coerceAtLeast(this) private fun updateAnimatedViewBoundsHeight(height: Int, verticalCenter: Int) {
animRect.set(
animRect.left,
verticalCenter - (height.toFloat() / 2).roundToInt(),
animRect.right,
verticalCenter + (height.toFloat() / 2).roundToInt())
updateCurrentAnimatedView()
}
/**
* To be called during an animation, updates the animation rect offset and updates the chip
*/
private fun updateAnimatedBoundsX(translation: Int) {
currentAnimatedView?.view?.translationX = translation.toFloat()
}
/**
* To be called during an animation. Sets the chip rect to animRect
*/
private fun updateCurrentAnimatedView() {
currentAnimatedView?.setBoundsForAnimation(
animRect.left, animRect.top, animRect.right, animRect.bottom
)
}
} }
/** /**

View File

@@ -19,14 +19,12 @@ package com.android.systemui.statusbar.events
import android.animation.Animator import android.animation.Animator
import android.animation.AnimatorListenerAdapter import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.annotation.IntDef import android.annotation.IntDef
import android.os.Process import android.os.Process
import android.provider.DeviceConfig import android.provider.DeviceConfig
import android.util.Log import android.util.Log
import android.view.animation.PathInterpolator
import com.android.systemui.Dumpable import com.android.systemui.Dumpable
import com.android.systemui.animation.Interpolators.STANDARD_ACCELERATE
import com.android.systemui.animation.Interpolators.STANDARD_DECELERATE
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dagger.qualifiers.Main
@@ -38,6 +36,7 @@ import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.time.SystemClock import com.android.systemui.util.time.SystemClock
import java.io.FileDescriptor import java.io.FileDescriptor
import java.io.PrintWriter import java.io.PrintWriter
import java.lang.IllegalStateException
import javax.inject.Inject import javax.inject.Inject
@@ -116,7 +115,10 @@ class SystemStatusAnimationScheduler @Inject constructor(
scheduledEvent?.updateFromEvent(event) scheduledEvent?.updateFromEvent(event)
if (event.forceVisible) { if (event.forceVisible) {
hasPersistentDot = true hasPersistentDot = true
notifyTransitionToPersistentDot() // If we missed the chance to show the persistent dot, do it now
if (animationState == IDLE) {
notifyTransitionToPersistentDot()
}
} }
} else { } else {
if (DEBUG) { if (DEBUG) {
@@ -162,68 +164,90 @@ class SystemStatusAnimationScheduler @Inject constructor(
return return
} }
// Schedule the animation to start after a debounce period chipAnimationController.prepareChipAnimation(scheduledEvent!!.viewCreator)
cancelExecutionRunnable = executor.executeDelayed({ animationState = ANIMATION_QUEUED
cancelExecutionRunnable = null executor.executeDelayed({
animationState = ANIMATING_IN runChipAnimation()
statusBarWindowController.setForceStatusBarVisible(true) }, DEBOUNCE_DELAY)
}
val entranceAnimator = ValueAnimator.ofFloat(1f, 0f) /**
entranceAnimator.duration = ENTRANCE_ANIM_LENGTH * 1. Define a total budget for the chip animation (1500ms)
entranceAnimator.addListener(systemAnimatorAdapter) * 2. Send out callbacks to listeners so that they can generate animations locally
entranceAnimator.addUpdateListener(systemUpdateListener) * 3. Update the scheduler state so that clients know where we are
entranceAnimator.interpolator = STANDARD_ACCELERATE * 4. Maybe: provide scaffolding such as: dot location, margins, etc
* 5. Maybe: define a maximum animation length and enforce it. Probably only doable if we
* collect all of the animators and run them together.
*/
private fun runChipAnimation() {
statusBarWindowController.setForceStatusBarVisible(true)
animationState = ANIMATING_IN
val chipAnimator = ValueAnimator.ofFloat(0f, 1f) val animSet = collectStartAnimations()
chipAnimator.duration = CHIP_ANIM_LENGTH if (animSet.totalDuration > 500) {
chipAnimator.addListener( throw IllegalStateException("System animation total length exceeds budget. " +
ChipAnimatorAdapter(RUNNING_CHIP_ANIM, scheduledEvent!!.viewCreator)) "Expected: 500, actual: ${animSet.totalDuration}")
chipAnimator.addUpdateListener(chipUpdateListener) }
chipAnimator.interpolator = STANDARD_DECELERATE animSet.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
animationState = RUNNING_CHIP_ANIM
}
})
animSet.start()
val aSet2 = AnimatorSet() executor.executeDelayed({
aSet2.playSequentially(entranceAnimator, chipAnimator) val animSet2 = collectFinishAnimations()
aSet2.start() animationState = ANIMATING_OUT
animSet2.addListener(object : AnimatorListenerAdapter() {
executor.executeDelayed({ override fun onAnimationEnd(animation: Animator?) {
animationState = ANIMATING_OUT animationState = if (hasPersistentDot) {
SHOWING_PERSISTENT_DOT
val systemAnimator = ValueAnimator.ofFloat(0f, 1f) } else {
systemAnimator.duration = ENTRANCE_ANIM_LENGTH IDLE
systemAnimator.addListener(systemAnimatorAdapter)
systemAnimator.addUpdateListener(systemUpdateListener)
systemAnimator.interpolator = STANDARD_DECELERATE
systemAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
statusBarWindowController.setForceStatusBarVisible(false)
} }
})
val chipAnimator = ValueAnimator.ofFloat(1f, 0f) statusBarWindowController.setForceStatusBarVisible(false)
chipAnimator.duration = CHIP_ANIM_LENGTH
val endState = if (hasPersistentDot) {
SHOWING_PERSISTENT_DOT
} else {
IDLE
} }
chipAnimator.addListener( })
ChipAnimatorAdapter(endState, scheduledEvent!!.viewCreator)) animSet2.start()
chipAnimator.addUpdateListener(chipUpdateListener) scheduledEvent = null
chipAnimator.interpolator = STANDARD_ACCELERATE }, DISPLAY_LENGTH)
}
val aSet2 = AnimatorSet() private fun collectStartAnimations(): AnimatorSet {
val animators = mutableListOf<Animator>()
listeners.forEach { listener ->
listener.onSystemEventAnimationBegin()?.let { anim ->
animators.add(anim)
}
}
animators.add(chipAnimationController.onSystemEventAnimationBegin())
val animSet = AnimatorSet().also {
it.playTogether(animators)
}
aSet2.play(chipAnimator).before(systemAnimator) return animSet
if (hasPersistentDot) { }
val dotAnim = notifyTransitionToPersistentDot()
if (dotAnim != null) aSet2.playTogether(systemAnimator, dotAnim)
}
aSet2.start() private fun collectFinishAnimations(): AnimatorSet {
val animators = mutableListOf<Animator>()
listeners.forEach { listener ->
listener.onSystemEventAnimationFinish(hasPersistentDot)?.let { anim ->
animators.add(anim)
}
}
animators.add(chipAnimationController.onSystemEventAnimationFinish(hasPersistentDot))
if (hasPersistentDot) {
val dotAnim = notifyTransitionToPersistentDot()
if (dotAnim != null) {
animators.add(dotAnim)
}
}
val animSet = AnimatorSet().also {
it.playTogether(animators)
}
scheduledEvent = null return animSet
}, DISPLAY_LENGTH)
}, DELAY)
} }
private fun notifyTransitionToPersistentDot(): Animator? { private fun notifyTransitionToPersistentDot(): Animator? {
@@ -257,18 +281,6 @@ class SystemStatusAnimationScheduler @Inject constructor(
return null return null
} }
private fun notifySystemStart() {
listeners.forEach { it.onSystemChromeAnimationStart() }
}
private fun notifySystemFinish() {
listeners.forEach { it.onSystemChromeAnimationEnd() }
}
private fun notifySystemAnimationUpdate(anim: ValueAnimator) {
listeners.forEach { it.onSystemChromeAnimationUpdate(anim) }
}
override fun addCallback(listener: SystemStatusAnimationCallback) { override fun addCallback(listener: SystemStatusAnimationCallback) {
Assert.isMainThread() Assert.isMainThread()
@@ -287,24 +299,6 @@ class SystemStatusAnimationScheduler @Inject constructor(
} }
} }
private val systemUpdateListener = ValueAnimator.AnimatorUpdateListener {
anim -> notifySystemAnimationUpdate(anim)
}
private val systemAnimatorAdapter = object : AnimatorListenerAdapter() {
override fun onAnimationEnd(p0: Animator?) {
notifySystemFinish()
}
override fun onAnimationStart(p0: Animator?) {
notifySystemStart()
}
}
private val chipUpdateListener = ValueAnimator.AnimatorUpdateListener {
anim -> chipAnimationController.onChipAnimationUpdate(anim, animationState)
}
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
pw.println("Scheduled event: $scheduledEvent") pw.println("Scheduled event: $scheduledEvent")
pw.println("Has persistent privacy dot: $hasPersistentDot") pw.println("Has persistent privacy dot: $hasPersistentDot")
@@ -318,24 +312,6 @@ class SystemStatusAnimationScheduler @Inject constructor(
} }
} }
} }
inner class ChipAnimatorAdapter(
@SystemAnimationState val endState: Int,
val viewCreator: ViewCreator
) : AnimatorListenerAdapter() {
override fun onAnimationEnd(p0: Animator?) {
chipAnimationController.onChipAnimationEnd(animationState)
animationState = if (endState == SHOWING_PERSISTENT_DOT && !hasPersistentDot) {
IDLE
} else {
endState
}
}
override fun onAnimationStart(p0: Animator?) {
chipAnimationController.onChipAnimationStart(viewCreator, animationState)
}
}
} }
/** /**
@@ -344,16 +320,14 @@ class SystemStatusAnimationScheduler @Inject constructor(
* create space for the chip animation to display. This means hiding the system elements in the * create space for the chip animation to display. This means hiding the system elements in the
* status bar and keyguard. * status bar and keyguard.
* *
* TODO: the chip animation really only has one client, we can probably remove it from this
* interface
*
* The value animators themselves are simple animators from 0.0 to 1.0. Listeners can apply any * The value animators themselves are simple animators from 0.0 to 1.0. Listeners can apply any
* interpolation they choose but realistically these are most likely to be simple alpha transitions * interpolation they choose but realistically these are most likely to be simple alpha transitions
*/ */
interface SystemStatusAnimationCallback { interface SystemStatusAnimationCallback {
@JvmDefault fun onSystemChromeAnimationUpdate(animator: ValueAnimator) {} /** Implement this method to return an [Animator] or [AnimatorSet] that presents the chip */
@JvmDefault fun onSystemChromeAnimationStart() {} fun onSystemEventAnimationBegin(): Animator? { return null }
@JvmDefault fun onSystemChromeAnimationEnd() {} /** Implement this method to return an [Animator] or [AnimatorSet] that hides the chip */
fun onSystemEventAnimationFinish(hasPersistentDot: Boolean): Animator? { return null }
// Best method name, change my mind // Best method name, change my mind
@JvmDefault @JvmDefault
@@ -363,50 +337,61 @@ interface SystemStatusAnimationCallback {
@JvmDefault fun onHidePersistentDot(): Animator? { return null } @JvmDefault fun onHidePersistentDot(): Animator? { return null }
} }
interface SystemStatusChipAnimationCallback {
fun onChipAnimationUpdate(animator: ValueAnimator, @SystemAnimationState state: Int) {}
fun onChipAnimationStart(
viewCreator: ViewCreator,
@SystemAnimationState state: Int
) {}
fun onChipAnimationEnd(@SystemAnimationState state: Int) {}
}
/** /**
* Animation state IntDef
*/ */
@Retention(AnnotationRetention.SOURCE) @Retention(AnnotationRetention.SOURCE)
@IntDef( @IntDef(
value = [ value = [
IDLE, ANIMATING_IN, RUNNING_CHIP_ANIM, ANIMATING_OUT, SHOWING_PERSISTENT_DOT IDLE,
ANIMATION_QUEUED,
ANIMATING_IN,
RUNNING_CHIP_ANIM,
ANIMATING_OUT,
SHOWING_PERSISTENT_DOT
] ]
) )
annotation class SystemAnimationState annotation class SystemAnimationState
/** No animation is in progress */ /** No animation is in progress */
const val IDLE = 0 const val IDLE = 0
/** An animation is queued, and awaiting the debounce period */
const val ANIMATION_QUEUED = 1
/** System is animating out, and chip is animating in */ /** System is animating out, and chip is animating in */
const val ANIMATING_IN = 1 const val ANIMATING_IN = 2
/** Chip has animated in and is awaiting exit animation, and optionally playing its own animation */ /** Chip has animated in and is awaiting exit animation, and optionally playing its own animation */
const val RUNNING_CHIP_ANIM = 2 const val RUNNING_CHIP_ANIM = 3
/** Chip is animating away and system is animating back */ /** Chip is animating away and system is animating back */
const val ANIMATING_OUT = 3 const val ANIMATING_OUT = 4
/** Chip has animated away, and the persistent dot is showing */ /** Chip has animated away, and the persistent dot is showing */
const val SHOWING_PERSISTENT_DOT = 4 const val SHOWING_PERSISTENT_DOT = 5
/** Commonly-needed interpolators can go here */
@JvmField val STATUS_BAR_X_MOVE_OUT = PathInterpolator(0.33f, 0f, 0f, 1f)
@JvmField val STATUS_BAR_X_MOVE_IN = PathInterpolator(0f, 0f, 0f, 1f)
/**
* Status chip animation to dot have multiple stages of motion, the _1 and _2 interpolators should
* be used in succession
*/
val STATUS_CHIP_WIDTH_TO_DOT_KEYFRAME_1 = PathInterpolator(0.44f, 0f, 0.25f, 1f)
val STATUS_CHIP_WIDTH_TO_DOT_KEYFRAME_2 = PathInterpolator(0.3f, 0f, 0.26f, 1f)
val STATUS_CHIP_HEIGHT_TO_DOT_KEYFRAME_1 = PathInterpolator(0.4f, 0f, 0.17f, 1f)
val STATUS_CHIP_HEIGHT_TO_DOT_KEYFRAME_2 = PathInterpolator(0.3f, 0f, 0f, 1f)
val STATUS_CHIP_MOVE_TO_DOT = PathInterpolator(0f, 0f, 0.05f, 1f)
private const val TAG = "SystemStatusAnimationScheduler" private const val TAG = "SystemStatusAnimationScheduler"
private const val DELAY = 0L private const val DEBOUNCE_DELAY = 100L
/** /**
* The total time spent animation should be 1500ms. The entrance animation is how much time * The total time spent on the chip animation is 1500ms, broken up into 3 sections:
* we give to the system to animate system elements out of the way. Total chip animation length * - 500ms to animate the chip in (including animating system icons away)
* will be equivalent to 2*chip_anim_length + display_length * - 500ms holding the chip on screen
* - 500ms to animate the chip away (and system icons back)
*
* So DISPLAY_LENGTH should be the sum of the first 2 phases, while the final 500ms accounts for
* the actual animation
*/ */
private const val ENTRANCE_ANIM_LENGTH = 250L private const val DISPLAY_LENGTH = 1000L
private const val CHIP_ANIM_LENGTH = 250L
// 1s + entrance time + chip anim_length
private const val DISPLAY_LENGTH = 1500L
private const val MIN_UPTIME: Long = 5 * 1000 private const val MIN_UPTIME: Long = 5 * 1000

View File

@@ -174,7 +174,7 @@ public class KeyguardStatusBarView extends RelativeLayout {
} }
private void updateKeyguardStatusBarHeight() { private void updateKeyguardStatusBarHeight() {
MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams(); MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
lp.height = getStatusBarHeaderHeightKeyguard(mContext); lp.height = getStatusBarHeaderHeightKeyguard(mContext);
setLayoutParams(lp); setLayoutParams(lp);
} }
@@ -510,28 +510,6 @@ public class KeyguardStatusBarView extends RelativeLayout {
} }
} }
void onSystemChromeAnimationStart(boolean isAnimatingOut) {
if (isAnimatingOut) {
mSystemIconsContainer.setVisibility(View.VISIBLE);
mSystemIconsContainer.setAlpha(0f);
}
}
void onSystemChromeAnimationEnd(boolean isAnimatingIn) {
// Make sure the system icons are out of the way
if (isAnimatingIn) {
mSystemIconsContainer.setVisibility(View.INVISIBLE);
mSystemIconsContainer.setAlpha(0f);
} else {
mSystemIconsContainer.setAlpha(1f);
mSystemIconsContainer.setVisibility(View.VISIBLE);
}
}
void onSystemChromeAnimationUpdate(float animatedValue) {
mSystemIconsContainer.setAlpha(animatedValue);
}
@Override @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) { protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b); super.onLayout(changed, l, t, r, b);

View File

@@ -17,8 +17,6 @@
package com.android.systemui.statusbar.phone; package com.android.systemui.statusbar.phone;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN;
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
@@ -47,6 +45,7 @@ import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.fragment.StatusBarSystemEventAnimator;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserInfoTracker; import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserInfoTracker;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController; import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherFeatureController; import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherFeatureController;
@@ -107,6 +106,8 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
@Override @Override
public void onDensityOrFontScaleChanged() { public void onDensityOrFontScaleChanged() {
mView.loadDimens(); mView.loadDimens();
// The animator is dependent on resources for offsets
mSystemEventAnimator = new StatusBarSystemEventAnimator(mView, getResources());
} }
@Override @Override
@@ -123,21 +124,16 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
private final SystemStatusAnimationCallback mAnimationCallback = private final SystemStatusAnimationCallback mAnimationCallback =
new SystemStatusAnimationCallback() { new SystemStatusAnimationCallback() {
@NonNull
@Override @Override
public void onSystemChromeAnimationStart() { public Animator onSystemEventAnimationFinish(boolean hasPersistentDot) {
mView.onSystemChromeAnimationStart( return mSystemEventAnimator.onSystemEventAnimationFinish(hasPersistentDot);
mAnimationScheduler.getAnimationState() == ANIMATING_OUT);
} }
@NonNull
@Override @Override
public void onSystemChromeAnimationEnd() { public Animator onSystemEventAnimationBegin() {
mView.onSystemChromeAnimationEnd( return mSystemEventAnimator.onSystemEventAnimationBegin();
mAnimationScheduler.getAnimationState() == ANIMATING_IN);
}
@Override
public void onSystemChromeAnimationUpdate(@NonNull ValueAnimator anim) {
mView.onSystemChromeAnimationUpdate((float) anim.getAnimatedValue());
} }
}; };
@@ -232,6 +228,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
private int mStatusBarState; private int mStatusBarState;
private boolean mDozing; private boolean mDozing;
private boolean mShowingKeyguardHeadsUp; private boolean mShowingKeyguardHeadsUp;
private StatusBarSystemEventAnimator mSystemEventAnimator;
@Inject @Inject
public KeyguardStatusBarViewController( public KeyguardStatusBarViewController(
@@ -302,6 +299,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
mView.setKeyguardUserAvatarEnabled( mView.setKeyguardUserAvatarEnabled(
!mFeatureController.isStatusBarUserSwitcherFeatureEnabled()); !mFeatureController.isStatusBarUserSwitcherFeatureEnabled());
mFeatureController.addCallback(enabled -> mView.setKeyguardUserAvatarEnabled(!enabled)); mFeatureController.addCallback(enabled -> mView.setKeyguardUserAvatarEnabled(!enabled));
mSystemEventAnimator = new StatusBarSystemEventAnimator(mView, r);
} }
@Override @Override

View File

@@ -20,12 +20,10 @@ import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS;
import static android.app.StatusBarManager.DISABLE_ONGOING_CALL_CHIP; import static android.app.StatusBarManager.DISABLE_ONGOING_CALL_CHIP;
import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO; import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO;
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN;
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT;
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE; import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE;
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.SHOWING_PERSISTENT_DOT; import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.SHOWING_PERSISTENT_DOT;
import android.animation.ValueAnimator; import android.animation.Animator;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
@@ -136,6 +134,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
} }
}; };
private OperatorNameViewController mOperatorNameViewController; private OperatorNameViewController mOperatorNameViewController;
private StatusBarSystemEventAnimator mSystemEventAnimator;
@SuppressLint("ValidFragment") @SuppressLint("ValidFragment")
public CollapsedStatusBarFragment( public CollapsedStatusBarFragment(
@@ -210,7 +209,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
initEmergencyCryptkeeperText(); initEmergencyCryptkeeperText();
initOperatorName(); initOperatorName();
initNotificationIconArea(); initNotificationIconArea();
mAnimationScheduler.addCallback(this); mSystemEventAnimator =
new StatusBarSystemEventAnimator(mSystemIconArea, getResources());
} }
@VisibleForTesting @VisibleForTesting
@@ -245,6 +245,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mCommandQueue.addCallback(this); mCommandQueue.addCallback(this);
mStatusBarStateController.addCallback(this); mStatusBarStateController.addCallback(this);
initOngoingCallChip(); initOngoingCallChip();
mAnimationScheduler.addCallback(this);
mSecureSettings.registerContentObserver( mSecureSettings.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON), Settings.Secure.getUriFor(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON),
@@ -258,6 +259,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mCommandQueue.removeCallback(this); mCommandQueue.removeCallback(this);
mStatusBarStateController.removeCallback(this); mStatusBarStateController.removeCallback(this);
mOngoingCallController.removeCallback(mOngoingCallListener); mOngoingCallController.removeCallback(mOngoingCallListener);
mAnimationScheduler.removeCallback(this);
mSecureSettings.unregisterContentObserver(mVolumeSettingObserver); mSecureSettings.unregisterContentObserver(mVolumeSettingObserver);
} }
@@ -265,7 +267,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();
mStatusBarIconController.removeIconGroup(mDarkIconManager); mStatusBarIconController.removeIconGroup(mDarkIconManager);
mAnimationScheduler.removeCallback(this);
if (mNetworkController.hasEmergencyCryptKeeperText()) { if (mNetworkController.hasEmergencyCryptKeeperText()) {
mNetworkController.removeCallback(mSignalCallback); mNetworkController.removeCallback(mSignalCallback);
} }
@@ -576,35 +577,16 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
disable(getContext().getDisplayId(), mDisabled1, mDisabled2, false /* animate */); disable(getContext().getDisplayId(), mDisabled1, mDisabled2, false /* animate */);
} }
@Nullable
@Override @Override
public void onSystemChromeAnimationStart() { public Animator onSystemEventAnimationBegin() {
if (mAnimationScheduler.getAnimationState() == ANIMATING_OUT return mSystemEventAnimator.onSystemEventAnimationBegin();
&& !isSystemIconAreaDisabled()) {
mSystemIconArea.setVisibility(View.VISIBLE);
mSystemIconArea.setAlpha(0f);
}
} }
@Nullable
@Override @Override
public void onSystemChromeAnimationEnd() { public Animator onSystemEventAnimationFinish(boolean hasPersistentDot) {
// Make sure the system icons are out of the way return mSystemEventAnimator.onSystemEventAnimationFinish(hasPersistentDot);
if (mAnimationScheduler.getAnimationState() == ANIMATING_IN) {
mSystemIconArea.setVisibility(View.INVISIBLE);
mSystemIconArea.setAlpha(0f);
} else {
if (isSystemIconAreaDisabled()) {
// don't unhide
return;
}
mSystemIconArea.setAlpha(1f);
mSystemIconArea.setVisibility(View.VISIBLE);
}
}
@Override
public void onSystemChromeAnimationUpdate(@NonNull ValueAnimator animator) {
mSystemIconArea.setAlpha((float) animator.getAnimatedValue());
} }
private boolean isSystemIconAreaDisabled() { private boolean isSystemIconAreaDisabled() {

View File

@@ -0,0 +1,84 @@
/*
* 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.statusbar.phone.fragment
import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.content.res.Resources
import android.view.View
import com.android.systemui.R
import com.android.systemui.statusbar.events.STATUS_BAR_X_MOVE_IN
import com.android.systemui.statusbar.events.STATUS_BAR_X_MOVE_OUT
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback
/**
* Tied directly to [SystemStatusAnimationScheduler]. Any StatusBar-like thing (keyguard, collapsed
* status bar fragment), can just feed this an animatable view to get the default system status
* animation.
*
* This animator relies on resources, and should be recreated whenever resources are updated. While
* this class could be used directly as the animation callback, it's probably best to forward calls
* to it so that it can be recreated at any moment without needing to remove/add callback.
*/
class StatusBarSystemEventAnimator(
val animatedView: View,
resources: Resources
) : SystemStatusAnimationCallback {
private val translationXIn: Int = resources.getDimensionPixelSize(
R.dimen.ongoing_appops_chip_animation_in_status_bar_translation_x)
private val translationXOut: Int = resources.getDimensionPixelSize(
R.dimen.ongoing_appops_chip_animation_out_status_bar_translation_x)
override fun onSystemEventAnimationBegin(): Animator {
val moveOut = ValueAnimator.ofFloat(0f, 1f).setDuration(383)
moveOut.interpolator = STATUS_BAR_X_MOVE_OUT
moveOut.addUpdateListener { animation: ValueAnimator ->
animatedView.translationX = -(translationXIn * animation.animatedValue as Float)
}
val alphaOut = ValueAnimator.ofFloat(1f, 0f).setDuration(133)
alphaOut.interpolator = null
alphaOut.addUpdateListener { animation: ValueAnimator ->
animatedView.alpha = animation.animatedValue as Float
}
val animSet = AnimatorSet()
animSet.playTogether(moveOut, alphaOut)
return animSet
}
override fun onSystemEventAnimationFinish(hasPersistentDot: Boolean): Animator {
animatedView.translationX = translationXOut.toFloat()
val moveIn = ValueAnimator.ofFloat(1f, 0f).setDuration(467)
moveIn.startDelay = 33
moveIn.interpolator = STATUS_BAR_X_MOVE_IN
moveIn.addUpdateListener { animation: ValueAnimator ->
animatedView.translationX = translationXOut * animation.animatedValue as Float
}
val alphaIn = ValueAnimator.ofFloat(0f, 1f).setDuration(167)
alphaIn.startDelay = 67
alphaIn.interpolator = null
alphaIn.addUpdateListener { animation: ValueAnimator ->
animatedView.alpha = animation.animatedValue as Float
}
val animatorSet = AnimatorSet()
animatorSet.playTogether(moveIn, alphaIn)
return animatorSet
}
}