summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Evan Laird <evanlaird@google.com> 2022-03-14 14:53:21 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2022-03-14 14:53:21 +0000
commitbe26fbdfdc5b2d09fd87795c958c2699ad89c699 (patch)
tree65982b00ebb47b0e9f8dc3c54fd6624a5781d4d0
parent97bf21f804722b21555c9057c7d8b846d0a97bd3 (diff)
parent519192a01eba4440c2285923e9b36574a7c7c2dc (diff)
Merge "System status event animation polish (1/n)" into tm-dev
-rw-r--r--packages/SystemUI/res/layout/ongoing_privacy_chip.xml7
-rw-r--r--packages/SystemUI/res/layout/system_event_animation_window.xml14
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt151
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt24
7 files changed, 172 insertions, 77 deletions
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
index 812277634d09..ad2bc79a5c96 100644
--- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
@@ -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> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/system_event_animation_window.xml b/packages/SystemUI/res/layout/system_event_animation_window.xml
index c92dec9dd643..e6868b34df02 100644
--- a/packages/SystemUI/res/layout/system_event_animation_window.xml
+++ b/packages/SystemUI/res/layout/system_event_animation_window.xml
@@ -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> \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index 9cd97ff8e343..2a6ca1acb38e 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -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) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
index d4d84c138b20..4e1404d0637b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
@@ -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 {
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 d5a0467c9b9e..9795dcf1fcd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -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 start() = if (animationWindowView.isLayoutRtl) right() else left()
- private fun right() = locationPublisher.marginRight
- private fun left() = locationPublisher.marginLeft
+ 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 layoutParamsDefault(): FrameLayout.LayoutParams =
- FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).also {
- it.gravity = Gravity.END or Gravity.CENTER_VERTICAL
- it.marginStart = start()
+ 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() = 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
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
index 04f7492e8562..fde5d39db7e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
@@ -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)
}
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 5a273294e87c..947f3eb2ee12 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
@@ -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