Merge "[Media TTT] Apply ripple effect for new states" into tm-qpr-dev am: d287d1d6bb
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20668089 Change-Id: Ib89a4f05a80937b6c82e7bb3575892f6827371d4 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -169,11 +169,9 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) :
|
||||
setFloatUniform("in_progress", value)
|
||||
val curvedProg = 1 - (1 - value) * (1 - value) * (1 - value)
|
||||
|
||||
setFloatUniform(
|
||||
"in_size",
|
||||
/* width= */ maxSize.x * curvedProg,
|
||||
/* height= */ maxSize.y * curvedProg
|
||||
)
|
||||
currentWidth = maxSize.x * curvedProg
|
||||
currentHeight = maxSize.y * curvedProg
|
||||
setFloatUniform("in_size", /* width= */ currentWidth, /* height= */ currentHeight)
|
||||
setFloatUniform("in_thickness", maxSize.y * curvedProg * 0.5f)
|
||||
// radius should not exceed width and height values.
|
||||
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.
|
||||
*/
|
||||
var rippleFill: Boolean = false
|
||||
|
||||
var currentWidth: Float = 0f
|
||||
private set
|
||||
|
||||
var currentHeight: Float = 0f
|
||||
private set
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape
|
||||
*/
|
||||
open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
|
||||
|
||||
private lateinit var rippleShader: RippleShader
|
||||
protected lateinit var rippleShader: RippleShader
|
||||
lateinit var rippleShape: RippleShape
|
||||
private set
|
||||
|
||||
|
||||
@@ -315,6 +315,10 @@ object Flags {
|
||||
// TODO(b/261734857): Tracking Bug
|
||||
@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
|
||||
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
|
||||
|
||||
|
||||
@@ -26,4 +26,8 @@ import javax.inject.Inject
|
||||
class MediaTttFlags @Inject constructor(private val featureFlags: FeatureFlags) {
|
||||
/** */
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -114,6 +114,9 @@ open class MediaTttChipControllerReceiver @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private var maxRippleWidth: Float = 0f
|
||||
private var maxRippleHeight: Float = 0f
|
||||
|
||||
private fun updateMediaTapToTransferReceiverDisplay(
|
||||
@StatusBarManager.MediaTransferReceiverState displayState: Int,
|
||||
routeInfo: MediaRoute2Info,
|
||||
@@ -216,7 +219,7 @@ open class MediaTttChipControllerReceiver @Inject constructor(
|
||||
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()
|
||||
appIconView.animate()
|
||||
.translationYBy(getTranslationAmount().toFloat())
|
||||
@@ -226,7 +229,14 @@ open class MediaTttChipControllerReceiver @Inject constructor(
|
||||
.alpha(0f)
|
||||
.setDuration(ICON_ALPHA_ANIM_DURATION)
|
||||
.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) {
|
||||
@@ -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 height = windowBounds.height().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.
|
||||
rippleView.setCenter(width * 0.5f, height)
|
||||
val color = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
|
||||
@@ -286,6 +303,11 @@ open class MediaTttChipControllerReceiver @Inject constructor(
|
||||
private fun View.getAppIconView(): CachingIconView {
|
||||
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
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import com.android.systemui.surfaceeffects.ripple.RippleShader
|
||||
import com.android.systemui.surfaceeffects.ripple.RippleView
|
||||
import kotlin.math.pow
|
||||
|
||||
/**
|
||||
* 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()
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,7 +331,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
|
||||
return
|
||||
}
|
||||
|
||||
removeViewFromWindow(displayInfo)
|
||||
removeViewFromWindow(displayInfo, removalReason)
|
||||
|
||||
// Prune anything that's already timed out before determining if we should re-display a
|
||||
// different chipbar.
|
||||
@@ -358,14 +358,14 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
|
||||
removeViewFromWindow(displayInfo)
|
||||
}
|
||||
|
||||
private fun removeViewFromWindow(displayInfo: DisplayInfo) {
|
||||
private fun removeViewFromWindow(displayInfo: DisplayInfo, removalReason: String? = null) {
|
||||
val view = displayInfo.view
|
||||
if (view == null) {
|
||||
logger.logViewRemovalIgnored(displayInfo.info.id, "View is null")
|
||||
return
|
||||
}
|
||||
displayInfo.view = null // Need other places??
|
||||
animateViewOut(view) {
|
||||
animateViewOut(view, removalReason) {
|
||||
windowManager.removeView(view)
|
||||
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.
|
||||
*/
|
||||
internal open fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
|
||||
internal open fun animateViewOut(
|
||||
view: ViewGroup,
|
||||
removalReason: String? = null,
|
||||
onAnimationEnd: Runnable
|
||||
) {
|
||||
onAnimationEnd.run()
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
innerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_NONE
|
||||
ViewHierarchyAnimator.animateRemoval(
|
||||
|
||||
@@ -66,7 +66,7 @@ class FakeMediaTttChipControllerReceiver(
|
||||
wakeLockBuilder,
|
||||
systemClock,
|
||||
) {
|
||||
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
|
||||
override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
|
||||
// Just bypass the animation in tests
|
||||
onAnimationEnd.run()
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(true)
|
||||
whenever(mediaTttFlags.isMediaTttReceiverSuccessRippleEnabled()).thenReturn(true)
|
||||
|
||||
fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
|
||||
whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable)
|
||||
|
||||
@@ -64,7 +64,7 @@ class FakeChipbarCoordinator(
|
||||
wakeLockBuilder,
|
||||
systemClock,
|
||||
) {
|
||||
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
|
||||
override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
|
||||
// Just bypass the animation in tests
|
||||
onAnimationEnd.run()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user