Merge "System status event animation polish (1/n)" into tm-dev

This commit is contained in:
Evan Laird
2022-03-14 14:53:21 +00:00
committed by Android (Google) Code Review
7 changed files with 173 additions and 78 deletions

View File

@@ -22,7 +22,10 @@
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_gravity="center_vertical|end"
android:focusable="true" >
android:focusable="true"
android:clipChildren="false"
android:clipToPadding="false"
>
<LinearLayout
android:id="@+id/icons_container"
@@ -34,5 +37,7 @@
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

@@ -19,17 +19,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical|end"
android:paddingTop="@dimen/status_bar_padding_top"
android:paddingEnd="8dp"
android:clipChildren="false"
android:clipToPadding="false"
>
<ImageView
android:id="@+id/dot_view"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_gravity="center_vertical|end"
android:src="@drawable/system_animation_ongoing_dot"
android:visibility="invisible"
/>
</FrameLayout>

View File

@@ -22,13 +22,14 @@ import android.widget.ImageView
import android.widget.LinearLayout
import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.statusbar.events.BackgroundAnimatableView
class OngoingPrivacyChip @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttrs: Int = 0,
defStyleRes: Int = 0
) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes) {
) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
private var iconMargin = 0
private var iconSize = 0
@@ -50,6 +51,16 @@ class OngoingPrivacyChip @JvmOverloads constructor(
updateResources()
}
/**
* When animating as a chip in the status bar, we want to animate the width for the container
* of the privacy items. We have to subtract our own top and left offset because the bounds
* come to us as absolute on-screen bounds, and `iconsContainer` is laid out relative to the
* frame layout's bounds.
*/
override fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int) {
iconsContainer.setLeftTopRightBottom(l - left, t - top, r - left, b - top)
}
// Should only be called if the builder icons or app changed
private fun updateView(builder: PrivacyChipBuilder) {
fun setIcons(chipBuilder: PrivacyChipBuilder, iconsContainer: ViewGroup) {

View File

@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.events
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
@@ -27,13 +28,15 @@ import com.android.systemui.R
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.privacy.PrivacyItem
typealias ViewCreator = (context: Context) -> BackgroundAnimatableView
interface StatusEvent {
val priority: Int
// Whether or not to force the status bar open and show a dot
val forceVisible: Boolean
// Whether or not to show an animation for this event
val showAnimation: Boolean
val viewCreator: (context: Context) -> View
val viewCreator: ViewCreator
var contentDescription: String?
// Update this event with values from another event.
@@ -47,14 +50,37 @@ interface StatusEvent {
}
}
class BGView(
context: Context
) : View(context), BackgroundAnimatableView {
override val view: View
get() = this
override fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int) {
setLeftTopRightBottom(l, t, r, b)
}
}
@SuppressLint("AppCompatCustomView")
class BGImageView(
context: Context
) : ImageView(context), BackgroundAnimatableView {
override val view: View
get() = this
override fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int) {
setLeftTopRightBottom(l, t, r, b)
}
}
class BatteryEvent : StatusEvent {
override val priority = 50
override val forceVisible = false
override val showAnimation = true
override var contentDescription: String? = ""
override val viewCreator: (context: Context) -> View = { context ->
val iv = ImageView(context)
override val viewCreator: (context: Context) -> BGImageView = { context ->
val iv = BGImageView(context)
iv.setImageDrawable(ThemedBatteryDrawable(context, Color.WHITE))
iv.setBackgroundDrawable(ColorDrawable(Color.GREEN))
iv
@@ -72,7 +98,7 @@ class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEvent {
var privacyItems: List<PrivacyItem> = listOf()
private var privacyChip: OngoingPrivacyChip? = null
override val viewCreator: (context: Context) -> View = { context ->
override val viewCreator: ViewCreator = { context ->
val v = LayoutInflater.from(context)
.inflate(R.layout.ongoing_privacy_chip, null) as OngoingPrivacyChip
v.privacyList = privacyItems
@@ -82,7 +108,7 @@ class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEvent {
}
override fun toString(): String {
return javaClass.simpleName
return "${javaClass.simpleName}(forceVisible=$forceVisible, privacyItems=$privacyItems)"
}
override fun shouldUpdateFromEvent(other: StatusEvent?): Boolean {

View File

@@ -18,14 +18,16 @@ package com.android.systemui.statusbar.events
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Point
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.View.MeasureSpec.AT_MOST
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.FrameLayout
import com.android.systemui.R
import com.android.systemui.statusbar.phone.StatusBarLocationPublisher
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.window.StatusBarWindowController
import javax.inject.Inject
@@ -35,44 +37,73 @@ import javax.inject.Inject
class SystemEventChipAnimationController @Inject constructor(
private val context: Context,
private val statusBarWindowController: StatusBarWindowController,
private val locationPublisher: StatusBarLocationPublisher
private val contentInsetsProvider: StatusBarContentInsetsProvider
) : SystemStatusChipAnimationCallback {
var showPersistentDot = false
set(value) {
field = value
statusBarWindowController.setForceStatusBarVisible(value)
maybeUpdateShowDot()
}
private lateinit var animationWindowView: FrameLayout
private lateinit var animationDotView: View
private var currentAnimatedView: View? = null
private var currentAnimatedView: BackgroundAnimatableView? = null
// Left for LTR, Right for RTL
private var animationDirection = LEFT
private var chipRight = 0
private var chipLeft = 0
private var chipWidth = 0
private var dotCenter = Point(0, 0)
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
// TODO: move to dagger
private var initialized = false
override fun onChipAnimationStart(
viewCreator: (context: Context) -> View,
viewCreator: ViewCreator,
@SystemAnimationState state: Int
) {
if (!initialized) init()
if (state == ANIMATING_IN) {
currentAnimatedView = viewCreator(context)
animationWindowView.addView(currentAnimatedView, layoutParamsDefault())
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
}
// 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
}
}
// We are animating IN; chip comes in from View.END
currentAnimatedView?.apply {
val translation = width.toFloat()
translationX = if (isLayoutRtl) -translation else translation
alpha = 0f
visibility = View.VISIBLE
setPadding(locationPublisher.marginLeft, 0, locationPublisher.marginRight, 0)
updateAnimatedViewBoundsForAmount(0.1f, this)
}
} else {
// We are animating away
currentAnimatedView?.apply {
translationX = 0f
currentAnimatedView!!.view.apply {
alpha = 1f
}
}
@@ -82,15 +113,14 @@ class SystemEventChipAnimationController @Inject constructor(
if (state == ANIMATING_IN) {
// Finished animating in
currentAnimatedView?.apply {
translationX = 0f
alpha = 1f
updateAnimatedViewBoundsForAmount(1f, this)
}
} else {
// Finished animating away
currentAnimatedView?.apply {
currentAnimatedView!!.view.apply {
visibility = View.INVISIBLE
}
animationWindowView.removeView(currentAnimatedView)
animationWindowView.removeView(currentAnimatedView!!.view)
}
}
@@ -98,22 +128,10 @@ class SystemEventChipAnimationController @Inject constructor(
animator: ValueAnimator,
@SystemAnimationState state: Int
) {
// Alpha is parameterized 0,1, and translation from (width, 0)
currentAnimatedView?.apply {
val amt = animator.animatedValue as Float
alpha = amt
val w = width
val translation = (1 - amt) * w
translationX = if (isLayoutRtl) -translation else translation
}
}
private fun maybeUpdateShowDot() {
if (!initialized) return
if (!showPersistentDot && currentAnimatedView == null) {
animationDotView.visibility = View.INVISIBLE
val amt = (animator.animatedValue as Float).amt()
view.alpha = (animator.animatedValue as Float)
updateAnimatedViewBoundsForAmount(amt, this)
}
}
@@ -121,19 +139,56 @@ class SystemEventChipAnimationController @Inject constructor(
initialized = true
animationWindowView = LayoutInflater.from(context)
.inflate(R.layout.system_event_animation_window, null) as FrameLayout
animationDotView = animationWindowView.findViewById(R.id.dot_view)
val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
lp.gravity = Gravity.END or Gravity.CENTER_VERTICAL
statusBarWindowController.addViewToWindow(animationWindowView, lp)
animationWindowView.clipToPadding = false
animationWindowView.clipChildren = false
animationWindowView.measureAllChildren = true
}
private fun layoutParamsDefault(marginEnd: Int): FrameLayout.LayoutParams =
FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).also {
it.gravity = Gravity.END or Gravity.CENTER_VERTICAL
it.marginEnd = marginEnd
}
private fun updateAnimatedViewBoundsForAmount(amt: Float, chip: BackgroundAnimatableView) {
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)
}
}
}
private fun start() = if (animationWindowView.isLayoutRtl) right() else left()
private fun right() = locationPublisher.marginRight
private fun left() = locationPublisher.marginLeft
private fun layoutParamsDefault(): FrameLayout.LayoutParams =
FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).also {
it.gravity = Gravity.END or Gravity.CENTER_VERTICAL
it.marginStart = start()
}
private fun right() = contentInsetsProvider.getStatusBarContentAreaForCurrentRotation().right
private fun left() = contentInsetsProvider.getStatusBarContentAreaForCurrentRotation().left
private fun Float.amt() = 0.01f.coerceAtLeast(this)
}
/**
* Chips should provide a view that can be animated with something better than a fade-in
*/
interface BackgroundAnimatableView {
val view: View // Since this can't extend View, add a view prop
get() = this as View
val chipWidth: Int
get() = view.measuredWidth
fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int)
}
// Animation directions
private const val LEFT = 1
private const val RIGHT = 2

View File

@@ -70,11 +70,11 @@ class SystemEventCoordinator @Inject constructor(
fun notifyPrivacyItemsChanged(showAnimation: Boolean = true) {
val event = PrivacyEvent(showAnimation)
event.privacyItems = privacyStateListener.currentPrivacyItems
event.contentDescription = {
event.contentDescription = run {
val items = PrivacyChipBuilder(context, event.privacyItems).joinTypes()
context.getString(
R.string.ongoing_privacy_chip_content_multiple_apps, items)
}()
}
scheduler.onStatusEvent(event)
}

View File

@@ -21,12 +21,12 @@ import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.annotation.IntDef
import android.content.Context
import android.os.Process
import android.provider.DeviceConfig
import android.util.Log
import android.view.View
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
@@ -45,7 +45,7 @@ import javax.inject.Inject
* Dead-simple scheduler for system status events. Obeys the following principles (all values TBD):
* - Avoiding log spam by only allowing 12 events per minute (1event/5s)
* - Waits 100ms to schedule any event for debouncing/prioritization
* - Simple prioritization: Privacy > Battery > connectivity (encoded in StatusEvent)
* - Simple prioritization: Privacy > Battery > connectivity (encoded in [StatusEvent])
* - Only schedules a single event, and throws away lowest priority events
*
* There are 4 basic stages of animation at play here:
@@ -111,7 +111,7 @@ class SystemStatusAnimationScheduler @Inject constructor(
scheduleEvent(event)
} else if (scheduledEvent?.shouldUpdateFromEvent(event) == true) {
if (DEBUG) {
Log.d(TAG, "updating current event from: $event")
Log.d(TAG, "updating current event from: $event. animationState=$animationState")
}
scheduledEvent?.updateFromEvent(event)
if (event.forceVisible) {
@@ -172,12 +172,14 @@ class SystemStatusAnimationScheduler @Inject constructor(
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)
@@ -190,6 +192,12 @@ class SystemStatusAnimationScheduler @Inject constructor(
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)
}
})
val chipAnimator = ValueAnimator.ofFloat(1f, 0f)
chipAnimator.duration = CHIP_ANIM_LENGTH
@@ -201,6 +209,7 @@ class SystemStatusAnimationScheduler @Inject constructor(
chipAnimator.addListener(
ChipAnimatorAdapter(endState, scheduledEvent!!.viewCreator))
chipAnimator.addUpdateListener(chipUpdateListener)
chipAnimator.interpolator = STANDARD_ACCELERATE
val aSet2 = AnimatorSet()
@@ -212,7 +221,6 @@ class SystemStatusAnimationScheduler @Inject constructor(
aSet2.start()
statusBarWindowController.setForceStatusBarVisible(false)
scheduledEvent = null
}, DISPLAY_LENGTH)
}, DELAY)
@@ -313,7 +321,7 @@ class SystemStatusAnimationScheduler @Inject constructor(
inner class ChipAnimatorAdapter(
@SystemAnimationState val endState: Int,
val viewCreator: (context: Context) -> View
val viewCreator: ViewCreator
) : AnimatorListenerAdapter() {
override fun onAnimationEnd(p0: Animator?) {
chipAnimationController.onChipAnimationEnd(animationState)
@@ -359,7 +367,7 @@ interface SystemStatusChipAnimationCallback {
fun onChipAnimationUpdate(animator: ValueAnimator, @SystemAnimationState state: Int) {}
fun onChipAnimationStart(
viewCreator: (context: Context) -> View,
viewCreator: ViewCreator,
@SystemAnimationState state: Int
) {}
@@ -371,7 +379,7 @@ interface SystemStatusChipAnimationCallback {
@Retention(AnnotationRetention.SOURCE)
@IntDef(
value = [
IDLE, ANIMATING_IN, RUNNING_CHIP_ANIM, ANIMATING_OUT
IDLE, ANIMATING_IN, RUNNING_CHIP_ANIM, ANIMATING_OUT, SHOWING_PERSISTENT_DOT
]
)
annotation class SystemAnimationState