diff options
8 files changed, 426 insertions, 290 deletions
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml index ad2bc79a5c96..5aa608084510 100644 --- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml +++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml @@ -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>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index ffae6015c732..f73010e2a39a 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -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> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt index 9795dcf1fcd8..b74140da99d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt @@ -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() - - 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 - } + /** + * 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 - // 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 - } - } + // 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 + } - currentAnimatedView?.apply { - updateAnimatedViewBoundsForAmount(0.1f, this) + // 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 { - // 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) + 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 onSystemEventAnimationFinish(hasPersistentDot: Boolean): Animator { + initializeAnimRect() + val finish = if (hasPersistentDot) { + createMoveOutAnimationForDot() } else { - // Finished animating away - currentAnimatedView!!.view.apply { - visibility = View.INVISIBLE + 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) } - animationWindowView.removeView(currentAnimatedView!!.view) } + + 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 } - 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) + 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 + ) + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt index 947f3eb2ee12..36233e4e3eab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt @@ -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) - - val entranceAnimator = ValueAnimator.ofFloat(1f, 0f) - entranceAnimator.duration = ENTRANCE_ANIM_LENGTH - entranceAnimator.addListener(systemAnimatorAdapter) - entranceAnimator.addUpdateListener(systemUpdateListener) - entranceAnimator.interpolator = STANDARD_ACCELERATE - - 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 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) + chipAnimationController.prepareChipAnimation(scheduledEvent!!.viewCreator) + animationState = ANIMATION_QUEUED + executor.executeDelayed({ + runChipAnimation() + }, DEBOUNCE_DELAY) + } + + /** + * 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 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() + + 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 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index 65173a230871..cb332bdf59b1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -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); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java index 1df1aff38593..af4fb8e2cf49 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 2c84219dbfd0..33bc40119745 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -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); - } - } - - @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); - } + public Animator onSystemEventAnimationBegin() { + return mSystemEventAnimator.onSystemEventAnimationBegin(); } + @Nullable @Override - public void onSystemChromeAnimationUpdate(@NonNull ValueAnimator animator) { - mSystemIconArea.setAlpha((float) animator.getAnimatedValue()); + public Animator onSystemEventAnimationFinish(boolean hasPersistentDot) { + return mSystemEventAnimator.onSystemEventAnimationFinish(hasPersistentDot); } private boolean isSystemIconAreaDisabled() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt new file mode 100644 index 000000000000..f530ec83aec5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt @@ -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 + } +}
\ No newline at end of file |