Merge "[Media TTT] Apply ripple effect for new states" into tm-qpr-dev

This commit is contained in:
Michael Mikhail
2022-12-21 19:50:30 +00:00
committed by Android (Google) Code Review
11 changed files with 97 additions and 17 deletions

View File

@@ -169,11 +169,9 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) :
setFloatUniform("in_progress", value) setFloatUniform("in_progress", value)
val curvedProg = 1 - (1 - value) * (1 - value) * (1 - value) val curvedProg = 1 - (1 - value) * (1 - value) * (1 - value)
setFloatUniform( currentWidth = maxSize.x * curvedProg
"in_size", currentHeight = maxSize.y * curvedProg
/* width= */ maxSize.x * curvedProg, setFloatUniform("in_size", /* width= */ currentWidth, /* height= */ currentHeight)
/* height= */ maxSize.y * curvedProg
)
setFloatUniform("in_thickness", maxSize.y * curvedProg * 0.5f) setFloatUniform("in_thickness", maxSize.y * curvedProg * 0.5f)
// radius should not exceed width and height values. // radius should not exceed width and height values.
setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * curvedProg) setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * curvedProg)
@@ -237,4 +235,10 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) :
* False for a ring effect. * False for a ring effect.
*/ */
var rippleFill: Boolean = false var rippleFill: Boolean = false
var currentWidth: Float = 0f
private set
var currentHeight: Float = 0f
private set
} }

View File

@@ -36,7 +36,7 @@ import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape
*/ */
open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
private lateinit var rippleShader: RippleShader protected lateinit var rippleShader: RippleShader
lateinit var rippleShape: RippleShape lateinit var rippleShape: RippleShape
private set private set

View File

@@ -315,6 +315,10 @@ object Flags {
// TODO(b/261734857): Tracking Bug // TODO(b/261734857): Tracking Bug
@JvmField val UMO_TURBULENCE_NOISE = unreleasedFlag(909, "umo_turbulence_noise") @JvmField val UMO_TURBULENCE_NOISE = unreleasedFlag(909, "umo_turbulence_noise")
// TODO(b/263272731): Tracking Bug
val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE =
unreleasedFlag(910, "media_ttt_receiver_success_ripple", teamfood = true)
// 1000 - dock // 1000 - dock
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging") val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")

View File

@@ -26,4 +26,8 @@ import javax.inject.Inject
class MediaTttFlags @Inject constructor(private val featureFlags: FeatureFlags) { class MediaTttFlags @Inject constructor(private val featureFlags: FeatureFlags) {
/** */ /** */
fun isMediaTttEnabled(): Boolean = featureFlags.isEnabled(Flags.MEDIA_TAP_TO_TRANSFER) fun isMediaTttEnabled(): Boolean = featureFlags.isEnabled(Flags.MEDIA_TAP_TO_TRANSFER)
/** Check whether the flag for the receiver success state is enabled. */
fun isMediaTttReceiverSuccessRippleEnabled(): Boolean =
featureFlags.isEnabled(Flags.MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE)
} }

View File

@@ -114,6 +114,9 @@ open class MediaTttChipControllerReceiver @Inject constructor(
} }
} }
private var maxRippleWidth: Float = 0f
private var maxRippleHeight: Float = 0f
private fun updateMediaTapToTransferReceiverDisplay( private fun updateMediaTapToTransferReceiverDisplay(
@StatusBarManager.MediaTransferReceiverState displayState: Int, @StatusBarManager.MediaTransferReceiverState displayState: Int,
routeInfo: MediaRoute2Info, routeInfo: MediaRoute2Info,
@@ -216,7 +219,7 @@ open class MediaTttChipControllerReceiver @Inject constructor(
expandRipple(view.requireViewById(R.id.ripple)) expandRipple(view.requireViewById(R.id.ripple))
} }
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) { override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
val appIconView = view.getAppIconView() val appIconView = view.getAppIconView()
appIconView.animate() appIconView.animate()
.translationYBy(getTranslationAmount().toFloat()) .translationYBy(getTranslationAmount().toFloat())
@@ -226,7 +229,14 @@ open class MediaTttChipControllerReceiver @Inject constructor(
.alpha(0f) .alpha(0f)
.setDuration(ICON_ALPHA_ANIM_DURATION) .setDuration(ICON_ALPHA_ANIM_DURATION)
.start() .start()
(view.requireViewById(R.id.ripple) as ReceiverChipRippleView).collapseRipple(onAnimationEnd)
val rippleView: ReceiverChipRippleView = view.requireViewById(R.id.ripple)
if (removalReason == ChipStateReceiver.TRANSFER_TO_RECEIVER_SUCCEEDED.name &&
mediaTttFlags.isMediaTttReceiverSuccessRippleEnabled()) {
expandRippleToFull(rippleView, onAnimationEnd)
} else {
rippleView.collapseRipple(onAnimationEnd)
}
} }
override fun getTouchableRegion(view: View, outRect: Rect) { override fun getTouchableRegion(view: View, outRect: Rect) {
@@ -271,12 +281,19 @@ open class MediaTttChipControllerReceiver @Inject constructor(
}) })
} }
private fun layoutRipple(rippleView: ReceiverChipRippleView) { private fun layoutRipple(rippleView: ReceiverChipRippleView, isFullScreen: Boolean = false) {
val windowBounds = windowManager.currentWindowMetrics.bounds val windowBounds = windowManager.currentWindowMetrics.bounds
val height = windowBounds.height().toFloat() val height = windowBounds.height().toFloat()
val width = windowBounds.width().toFloat() val width = windowBounds.width().toFloat()
rippleView.setMaxSize(width / 2f, height / 2f) if (isFullScreen) {
maxRippleHeight = height * 2f
maxRippleWidth = width * 2f
} else {
maxRippleHeight = height / 2f
maxRippleWidth = width / 2f
}
rippleView.setMaxSize(maxRippleWidth, maxRippleHeight)
// Center the ripple on the bottom of the screen in the middle. // Center the ripple on the bottom of the screen in the middle.
rippleView.setCenter(width * 0.5f, height) rippleView.setCenter(width * 0.5f, height)
val color = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent) val color = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
@@ -286,6 +303,11 @@ open class MediaTttChipControllerReceiver @Inject constructor(
private fun View.getAppIconView(): CachingIconView { private fun View.getAppIconView(): CachingIconView {
return this.requireViewById(R.id.app_icon) return this.requireViewById(R.id.app_icon)
} }
private fun expandRippleToFull(rippleView: ReceiverChipRippleView, onAnimationEnd: Runnable?) {
layoutRipple(rippleView, true)
rippleView.expandToFull(maxRippleHeight, onAnimationEnd)
}
} }
val ICON_TRANSLATION_ANIM_DURATION = 30.frames val ICON_TRANSLATION_ANIM_DURATION = 30.frames

View File

@@ -22,6 +22,7 @@ import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import com.android.systemui.surfaceeffects.ripple.RippleShader import com.android.systemui.surfaceeffects.ripple.RippleShader
import com.android.systemui.surfaceeffects.ripple.RippleView import com.android.systemui.surfaceeffects.ripple.RippleView
import kotlin.math.pow
/** /**
* An expanding ripple effect for the media tap-to-transfer receiver chip. * An expanding ripple effect for the media tap-to-transfer receiver chip.
@@ -59,4 +60,44 @@ class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleVi
}) })
animator.reverse() animator.reverse()
} }
// Expands the ripple to cover full screen.
fun expandToFull(newHeight: Float, onAnimationEnd: Runnable? = null) {
if (!isStarted) {
return
}
// Reset all listeners to animator.
animator.removeAllListeners()
animator.removeAllUpdateListeners()
// Only show the outline as ripple expands and disappears when animation ends.
setRippleFill(false)
val startingPercentage = calculateStartingPercentage(newHeight)
animator.addUpdateListener { updateListener ->
val now = updateListener.currentPlayTime
val progress = updateListener.animatedValue as Float
rippleShader.progress = startingPercentage + (progress * (1 - startingPercentage))
rippleShader.distortionStrength = 1 - rippleShader.progress
rippleShader.pixelDensity = 1 - rippleShader.progress
rippleShader.time = now.toFloat()
invalidate()
}
animator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
animation?.let { visibility = GONE }
onAnimationEnd?.run()
isStarted = false
}
})
animator.start()
}
// Calculates the actual starting percentage according to ripple shader progress set method.
// Check calculations in [RippleShader.progress]
fun calculateStartingPercentage(newHeight: Float): Float {
val ratio = rippleShader.currentHeight / newHeight
val remainingPercentage = (1 - ratio).toDouble().pow(1 / 3.toDouble()).toFloat()
return 1 - remainingPercentage
}
} }

View File

@@ -331,7 +331,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
return return
} }
removeViewFromWindow(displayInfo) removeViewFromWindow(displayInfo, removalReason)
// Prune anything that's already timed out before determining if we should re-display a // Prune anything that's already timed out before determining if we should re-display a
// different chipbar. // different chipbar.
@@ -358,14 +358,14 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
removeViewFromWindow(displayInfo) removeViewFromWindow(displayInfo)
} }
private fun removeViewFromWindow(displayInfo: DisplayInfo) { private fun removeViewFromWindow(displayInfo: DisplayInfo, removalReason: String? = null) {
val view = displayInfo.view val view = displayInfo.view
if (view == null) { if (view == null) {
logger.logViewRemovalIgnored(displayInfo.info.id, "View is null") logger.logViewRemovalIgnored(displayInfo.info.id, "View is null")
return return
} }
displayInfo.view = null // Need other places?? displayInfo.view = null // Need other places??
animateViewOut(view) { animateViewOut(view, removalReason) {
windowManager.removeView(view) windowManager.removeView(view)
displayInfo.wakeLock?.release(displayInfo.info.wakeReason) displayInfo.wakeLock?.release(displayInfo.info.wakeReason)
} }
@@ -428,7 +428,11 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
* *
* @param onAnimationEnd an action that *must* be run once the animation finishes successfully. * @param onAnimationEnd an action that *must* be run once the animation finishes successfully.
*/ */
internal open fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) { internal open fun animateViewOut(
view: ViewGroup,
removalReason: String? = null,
onAnimationEnd: Runnable
) {
onAnimationEnd.run() onAnimationEnd.run()
} }

View File

@@ -211,7 +211,7 @@ open class ChipbarCoordinator @Inject constructor(
) )
} }
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) { override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
val innerView = view.getInnerView() val innerView = view.getInnerView()
innerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_NONE innerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_NONE
ViewHierarchyAnimator.animateRemoval( ViewHierarchyAnimator.animateRemoval(

View File

@@ -66,7 +66,7 @@ class FakeMediaTttChipControllerReceiver(
wakeLockBuilder, wakeLockBuilder,
systemClock, systemClock,
) { ) {
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) { override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
// Just bypass the animation in tests // Just bypass the animation in tests
onAnimationEnd.run() onAnimationEnd.run()
} }

View File

@@ -98,6 +98,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(true) whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(true)
whenever(mediaTttFlags.isMediaTttReceiverSuccessRippleEnabled()).thenReturn(true)
fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!! fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable) whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable)

View File

@@ -64,7 +64,7 @@ class FakeChipbarCoordinator(
wakeLockBuilder, wakeLockBuilder,
systemClock, systemClock,
) { ) {
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) { override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
// Just bypass the animation in tests // Just bypass the animation in tests
onAnimationEnd.run() onAnimationEnd.run()
} }