summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/res/layout/ongoing_privacy_chip.xml2
-rw-r--r--packages/SystemUI/res/values/dimens.xml3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt276
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt261
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt84
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