diff options
| author | 2022-03-14 14:53:21 +0000 | |
|---|---|---|
| committer | 2022-03-14 14:53:21 +0000 | |
| commit | be26fbdfdc5b2d09fd87795c958c2699ad89c699 (patch) | |
| tree | 65982b00ebb47b0e9f8dc3c54fd6624a5781d4d0 | |
| parent | 97bf21f804722b21555c9057c7d8b846d0a97bd3 (diff) | |
| parent | 519192a01eba4440c2285923e9b36574a7c7c2dc (diff) | |
Merge "System status event animation polish (1/n)" into tm-dev
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 |