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)
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user