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:minWidth="@dimen/ongoing_appops_chip_min_width"
android:maxWidth="@dimen/ongoing_appops_chip_max_width"
android:clipChildren="false"
android:clipToPadding="false"
/>
</com.android.systemui.privacy.OngoingPrivacyChip>

View File

@@ -941,6 +941,9 @@
<!-- Three privacy items. This value must not be exceeded -->
<dimen name="ongoing_appops_chip_max_width">76dp</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 -->
<dimen name="ongoing_appops_dot_min_padding">20dp</dimen>

View File

@@ -16,9 +16,12 @@
package com.android.systemui.statusbar.events
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Point
import android.graphics.Rect
import android.view.Gravity
import android.view.LayoutInflater
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.window.StatusBarWindowController
import javax.inject.Inject
import kotlin.math.roundToInt
/**
* Controls the view for system event animations.
@@ -38,7 +42,7 @@ class SystemEventChipAnimationController @Inject constructor(
private val context: Context,
private val statusBarWindowController: StatusBarWindowController,
private val contentInsetsProvider: StatusBarContentInsetsProvider
) : SystemStatusChipAnimationCallback {
) : SystemStatusAnimationCallback {
private lateinit var animationWindowView: FrameLayout
@@ -49,90 +53,169 @@ class SystemEventChipAnimationController @Inject constructor(
private var chipRight = 0
private var chipLeft = 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(
R.dimen.ongoing_appops_dot_diameter)
// If the chip animates away to a persistent dot, then we modify the CHIP_OUT animation
private var isAnimatingToDot = false
// Use during animation so that multiple animators can update the drawing rect
private var animRect = Rect()
// TODO: move to dagger
private var initialized = false
override fun onChipAnimationStart(
viewCreator: ViewCreator,
@SystemAnimationState state: Int
) {
if (!initialized) init()
/**
* Give the chip controller a chance to inflate and configure the chip view before we start
* animating
*/
fun prepareChipAnimation(viewCreator: ViewCreator) {
if (!initialized) {
init()
}
animationDirection = if (animationWindowView.isLayoutRtl) RIGHT else LEFT
if (state == ANIMATING_IN) {
animationDirection = if (animationWindowView.isLayoutRtl) RIGHT else LEFT
// Initialize the animated view
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
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
// decide which direction we're animating from, and then set some screen coordinates
val contentRect = contentInsetsProvider.getStatusBarContentAreaForCurrentRotation()
when (animationDirection) {
LEFT -> {
chipRight = contentRect.right
chipLeft = contentRect.right - chipWidth
}
// decide which direction we're animating from, and then set some screen coordinates
val contentRect = contentInsetsProvider.getStatusBarContentAreaForCurrentRotation()
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
else /* RIGHT */ -> {
chipLeft = contentRect.left
chipRight = contentRect.left + chipWidth
}
}
}
override fun onChipAnimationEnd(@SystemAnimationState state: Int) {
if (state == ANIMATING_IN) {
// Finished animating in
currentAnimatedView?.apply {
updateAnimatedViewBoundsForAmount(1f, this)
}
} else {
// Finished animating away
currentAnimatedView!!.view.apply {
visibility = View.INVISIBLE
}
animationWindowView.removeView(currentAnimatedView!!.view)
override fun onSystemEventAnimationBegin(): Animator {
initializeAnimRect()
val alphaIn = ValueAnimator.ofFloat(0f, 1f).apply {
startDelay = 117
duration = 83
interpolator = null
addUpdateListener { currentAnimatedView?.view?.alpha = animatedValue as Float }
}
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(
animator: ValueAnimator,
@SystemAnimationState state: Int
) {
currentAnimatedView?.apply {
val amt = (animator.animatedValue as Float).amt()
view.alpha = (animator.animatedValue as Float)
updateAnimatedViewBoundsForAmount(amt, this)
override fun onSystemEventAnimationFinish(hasPersistentDot: Boolean): Animator {
initializeAnimRect()
val finish = if (hasPersistentDot) {
createMoveOutAnimationForDot()
} else {
createMoveOutAnimationDefault()
}
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() {
@@ -144,7 +227,6 @@ class SystemEventChipAnimationController @Inject constructor(
statusBarWindowController.addViewToWindow(animationWindowView, lp)
animationWindowView.clipToPadding = false
animationWindowView.clipChildren = false
animationWindowView.measureAllChildren = true
}
private fun layoutParamsDefault(marginEnd: Int): FrameLayout.LayoutParams =
@@ -153,29 +235,55 @@ class SystemEventChipAnimationController @Inject constructor(
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) {
LEFT -> {
chip.setBoundsForAnimation(
(chipRight - (chipWidth * amt)).toInt(),
chip.view.top,
chipRight,
chip.view.bottom)
}
else /* RIGHT */ -> {
chip.setBoundsForAnimation(
chipLeft,
chip.view.top,
(chipLeft + (chipWidth * amt)).toInt(),
chip.view.bottom)
animRect.set((chipRight - width), animRect.top, chipRight, animRect.bottom)
} else /* RIGHT */ -> {
animRect.set(chipLeft, animRect.top, (chipLeft + width), animRect.bottom)
}
}
updateCurrentAnimatedView()
}
private fun start() = if (animationWindowView.isLayoutRtl) right() else left()
private fun right() = contentInsetsProvider.getStatusBarContentAreaForCurrentRotation().right
private fun left() = contentInsetsProvider.getStatusBarContentAreaForCurrentRotation().left
private fun Float.amt() = 0.01f.coerceAtLeast(this)
/**
* To be called during an animation, updates the animation rect and sends the update to the chip
*/
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.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.annotation.IntDef
import android.os.Process
import android.provider.DeviceConfig
import android.util.Log
import android.view.animation.PathInterpolator
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.qualifiers.Main
@@ -38,6 +36,7 @@ import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.time.SystemClock
import java.io.FileDescriptor
import java.io.PrintWriter
import java.lang.IllegalStateException
import javax.inject.Inject
@@ -116,7 +115,10 @@ class SystemStatusAnimationScheduler @Inject constructor(
scheduledEvent?.updateFromEvent(event)
if (event.forceVisible) {
hasPersistentDot = true
notifyTransitionToPersistentDot()
// If we missed the chance to show the persistent dot, do it now
if (animationState == IDLE) {
notifyTransitionToPersistentDot()
}
}
} else {
if (DEBUG) {
@@ -162,68 +164,90 @@ class SystemStatusAnimationScheduler @Inject constructor(
return
}
// Schedule the animation to start after a debounce period
cancelExecutionRunnable = executor.executeDelayed({
cancelExecutionRunnable = null
animationState = ANIMATING_IN
statusBarWindowController.setForceStatusBarVisible(true)
chipAnimationController.prepareChipAnimation(scheduledEvent!!.viewCreator)
animationState = ANIMATION_QUEUED
executor.executeDelayed({
runChipAnimation()
}, DEBOUNCE_DELAY)
}
val entranceAnimator = ValueAnimator.ofFloat(1f, 0f)
entranceAnimator.duration = ENTRANCE_ANIM_LENGTH
entranceAnimator.addListener(systemAnimatorAdapter)
entranceAnimator.addUpdateListener(systemUpdateListener)
entranceAnimator.interpolator = STANDARD_ACCELERATE
/**
* 1. Define a total budget for the chip animation (1500ms)
* 2. Send out callbacks to listeners so that they can generate animations locally
* 3. Update the scheduler state so that clients know where we are
* 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)
chipAnimator.duration = CHIP_ANIM_LENGTH
chipAnimator.addListener(
ChipAnimatorAdapter(RUNNING_CHIP_ANIM, scheduledEvent!!.viewCreator))
chipAnimator.addUpdateListener(chipUpdateListener)
chipAnimator.interpolator = STANDARD_DECELERATE
val animSet = collectStartAnimations()
if (animSet.totalDuration > 500) {
throw IllegalStateException("System animation total length exceeds budget. " +
"Expected: 500, actual: ${animSet.totalDuration}")
}
animSet.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
animationState = RUNNING_CHIP_ANIM
}
})
animSet.start()
val aSet2 = AnimatorSet()
aSet2.playSequentially(entranceAnimator, chipAnimator)
aSet2.start()
executor.executeDelayed({
animationState = ANIMATING_OUT
val systemAnimator = ValueAnimator.ofFloat(0f, 1f)
systemAnimator.duration = ENTRANCE_ANIM_LENGTH
systemAnimator.addListener(systemAnimatorAdapter)
systemAnimator.addUpdateListener(systemUpdateListener)
systemAnimator.interpolator = STANDARD_DECELERATE
systemAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
statusBarWindowController.setForceStatusBarVisible(false)
executor.executeDelayed({
val animSet2 = collectFinishAnimations()
animationState = ANIMATING_OUT
animSet2.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
animationState = if (hasPersistentDot) {
SHOWING_PERSISTENT_DOT
} else {
IDLE
}
})
val chipAnimator = ValueAnimator.ofFloat(1f, 0f)
chipAnimator.duration = CHIP_ANIM_LENGTH
val endState = if (hasPersistentDot) {
SHOWING_PERSISTENT_DOT
} else {
IDLE
statusBarWindowController.setForceStatusBarVisible(false)
}
chipAnimator.addListener(
ChipAnimatorAdapter(endState, scheduledEvent!!.viewCreator))
chipAnimator.addUpdateListener(chipUpdateListener)
chipAnimator.interpolator = STANDARD_ACCELERATE
})
animSet2.start()
scheduledEvent = null
}, 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)
if (hasPersistentDot) {
val dotAnim = notifyTransitionToPersistentDot()
if (dotAnim != null) aSet2.playTogether(systemAnimator, dotAnim)
}
return animSet
}
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
}, DISPLAY_LENGTH)
}, DELAY)
return animSet
}
private fun notifyTransitionToPersistentDot(): Animator? {
@@ -257,18 +281,6 @@ class SystemStatusAnimationScheduler @Inject constructor(
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) {
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>) {
pw.println("Scheduled event: $scheduledEvent")
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
* 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
* interpolation they choose but realistically these are most likely to be simple alpha transitions
*/
interface SystemStatusAnimationCallback {
@JvmDefault fun onSystemChromeAnimationUpdate(animator: ValueAnimator) {}
@JvmDefault fun onSystemChromeAnimationStart() {}
@JvmDefault fun onSystemChromeAnimationEnd() {}
/** Implement this method to return an [Animator] or [AnimatorSet] that presents the chip */
fun onSystemEventAnimationBegin(): Animator? { return null }
/** 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
@JvmDefault
@@ -363,50 +337,61 @@ interface SystemStatusAnimationCallback {
@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)
@IntDef(
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
/** No animation is in progress */
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 */
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 */
const val RUNNING_CHIP_ANIM = 2
const val RUNNING_CHIP_ANIM = 3
/** 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 */
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 DELAY = 0L
private const val DEBOUNCE_DELAY = 100L
/**
* The total time spent animation should be 1500ms. The entrance animation is how much time
* we give to the system to animate system elements out of the way. Total chip animation length
* will be equivalent to 2*chip_anim_length + display_length
* The total time spent on the chip animation is 1500ms, broken up into 3 sections:
* - 500ms to animate the chip in (including animating system icons away)
* - 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 CHIP_ANIM_LENGTH = 250L
// 1s + entrance time + chip anim_length
private const val DISPLAY_LENGTH = 1500L
private const val DISPLAY_LENGTH = 1000L
private const val MIN_UPTIME: Long = 5 * 1000

View File

@@ -174,7 +174,7 @@ public class KeyguardStatusBarView extends RelativeLayout {
}
private void updateKeyguardStatusBarHeight() {
MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
lp.height = getStatusBarHeaderHeightKeyguard(mContext);
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
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);

View File

@@ -17,8 +17,6 @@
package com.android.systemui.statusbar.phone;
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.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.stack.AnimationProperties;
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.StatusBarUserSwitcherController;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherFeatureController;
@@ -107,6 +106,8 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
@Override
public void onDensityOrFontScaleChanged() {
mView.loadDimens();
// The animator is dependent on resources for offsets
mSystemEventAnimator = new StatusBarSystemEventAnimator(mView, getResources());
}
@Override
@@ -123,21 +124,16 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
private final SystemStatusAnimationCallback mAnimationCallback =
new SystemStatusAnimationCallback() {
@NonNull
@Override
public void onSystemChromeAnimationStart() {
mView.onSystemChromeAnimationStart(
mAnimationScheduler.getAnimationState() == ANIMATING_OUT);
public Animator onSystemEventAnimationFinish(boolean hasPersistentDot) {
return mSystemEventAnimator.onSystemEventAnimationFinish(hasPersistentDot);
}
@NonNull
@Override
public void onSystemChromeAnimationEnd() {
mView.onSystemChromeAnimationEnd(
mAnimationScheduler.getAnimationState() == ANIMATING_IN);
}
@Override
public void onSystemChromeAnimationUpdate(@NonNull ValueAnimator anim) {
mView.onSystemChromeAnimationUpdate((float) anim.getAnimatedValue());
public Animator onSystemEventAnimationBegin() {
return mSystemEventAnimator.onSystemEventAnimationBegin();
}
};
@@ -232,6 +228,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
private int mStatusBarState;
private boolean mDozing;
private boolean mShowingKeyguardHeadsUp;
private StatusBarSystemEventAnimator mSystemEventAnimator;
@Inject
public KeyguardStatusBarViewController(
@@ -302,6 +299,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
mView.setKeyguardUserAvatarEnabled(
!mFeatureController.isStatusBarUserSwitcherFeatureEnabled());
mFeatureController.addCallback(enabled -> mView.setKeyguardUserAvatarEnabled(!enabled));
mSystemEventAnimator = new StatusBarSystemEventAnimator(mView, r);
}
@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_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.SHOWING_PERSISTENT_DOT;
import android.animation.ValueAnimator;
import android.animation.Animator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -136,6 +134,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
}
};
private OperatorNameViewController mOperatorNameViewController;
private StatusBarSystemEventAnimator mSystemEventAnimator;
@SuppressLint("ValidFragment")
public CollapsedStatusBarFragment(
@@ -210,7 +209,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
initEmergencyCryptkeeperText();
initOperatorName();
initNotificationIconArea();
mAnimationScheduler.addCallback(this);
mSystemEventAnimator =
new StatusBarSystemEventAnimator(mSystemIconArea, getResources());
}
@VisibleForTesting
@@ -245,6 +245,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mCommandQueue.addCallback(this);
mStatusBarStateController.addCallback(this);
initOngoingCallChip();
mAnimationScheduler.addCallback(this);
mSecureSettings.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON),
@@ -258,6 +259,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mCommandQueue.removeCallback(this);
mStatusBarStateController.removeCallback(this);
mOngoingCallController.removeCallback(mOngoingCallListener);
mAnimationScheduler.removeCallback(this);
mSecureSettings.unregisterContentObserver(mVolumeSettingObserver);
}
@@ -265,7 +267,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
public void onDestroyView() {
super.onDestroyView();
mStatusBarIconController.removeIconGroup(mDarkIconManager);
mAnimationScheduler.removeCallback(this);
if (mNetworkController.hasEmergencyCryptKeeperText()) {
mNetworkController.removeCallback(mSignalCallback);
}
@@ -576,35 +577,16 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
disable(getContext().getDisplayId(), mDisabled1, mDisabled2, false /* animate */);
}
@Nullable
@Override
public void onSystemChromeAnimationStart() {
if (mAnimationScheduler.getAnimationState() == ANIMATING_OUT
&& !isSystemIconAreaDisabled()) {
mSystemIconArea.setVisibility(View.VISIBLE);
mSystemIconArea.setAlpha(0f);
}
public Animator onSystemEventAnimationBegin() {
return mSystemEventAnimator.onSystemEventAnimationBegin();
}
@Nullable
@Override
public void onSystemChromeAnimationEnd() {
// Make sure the system icons are out of the way
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());
public Animator onSystemEventAnimationFinish(boolean hasPersistentDot) {
return mSystemEventAnimator.onSystemEventAnimationFinish(hasPersistentDot);
}
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
}
}