Merge "[Media TTT] Add animation out for receiver chip" into tm-qpr-dev
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 691953a..cc5e256 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -56,7 +56,7 @@
* TODO(b/245610654): Re-name this to be MediaTttReceiverCoordinator.
*/
@SysUISingleton
-class MediaTttChipControllerReceiver @Inject constructor(
+open class MediaTttChipControllerReceiver @Inject constructor(
private val commandQueue: CommandQueue,
context: Context,
@MediaTttReceiverLogger logger: MediaTttLogger,
@@ -183,15 +183,28 @@
val appIconView = view.getAppIconView()
appIconView.animate()
.translationYBy(-1 * getTranslationAmount().toFloat())
- .setDuration(30.frames)
+ .setDuration(ICON_TRANSLATION_ANIM_DURATION)
.start()
appIconView.animate()
.alpha(1f)
- .setDuration(5.frames)
+ .setDuration(ICON_ALPHA_ANIM_DURATION)
.start()
// Using withEndAction{} doesn't apply a11y focus when screen is unlocked.
appIconView.postOnAnimation { view.requestAccessibilityFocus() }
- startRipple(view.requireViewById(R.id.ripple))
+ expandRipple(view.requireViewById(R.id.ripple))
+ }
+
+ override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ val appIconView = view.getAppIconView()
+ appIconView.animate()
+ .translationYBy(getTranslationAmount().toFloat())
+ .setDuration(ICON_TRANSLATION_ANIM_DURATION)
+ .start()
+ appIconView.animate()
+ .alpha(0f)
+ .setDuration(ICON_ALPHA_ANIM_DURATION)
+ .start()
+ (view.requireViewById(R.id.ripple) as ReceiverChipRippleView).collapseRipple(onAnimationEnd)
}
override fun getTouchableRegion(view: View, outRect: Rect) {
@@ -205,11 +218,22 @@
return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation)
}
- private fun startRipple(rippleView: ReceiverChipRippleView) {
+ private fun expandRipple(rippleView: ReceiverChipRippleView) {
if (rippleView.rippleInProgress()) {
// Skip if ripple is still playing
return
}
+
+ // In case the device orientation changes, we need to reset the layout.
+ rippleView.addOnLayoutChangeListener (
+ View.OnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
+ if (v == null) return@OnLayoutChangeListener
+
+ val layoutChangedRippleView = v as ReceiverChipRippleView
+ layoutRipple(layoutChangedRippleView)
+ layoutChangedRippleView.invalidate()
+ }
+ )
rippleView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewDetachedFromWindow(view: View?) {}
@@ -219,7 +243,7 @@
}
val attachedRippleView = view as ReceiverChipRippleView
layoutRipple(attachedRippleView)
- attachedRippleView.startRipple()
+ attachedRippleView.expandRipple()
attachedRippleView.removeOnAttachStateChangeListener(this)
}
})
@@ -242,6 +266,9 @@
}
}
+val ICON_TRANSLATION_ANIM_DURATION = 30.frames
+val ICON_ALPHA_ANIM_DURATION = 5.frames
+
data class ChipReceiverInfo(
val routeInfo: MediaRoute2Info,
val appIconDrawableOverride: Drawable?,
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index 1ea2025..6e9fc5c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -16,6 +16,8 @@
package com.android.systemui.media.taptotransfer.receiver
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
import android.content.Context
import android.util.AttributeSet
import com.android.systemui.surfaceeffects.ripple.RippleShader
@@ -25,10 +27,36 @@
* An expanding ripple effect for the media tap-to-transfer receiver chip.
*/
class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleView(context, attrs) {
+
+ // Indicates whether the ripple started expanding.
+ private var isStarted: Boolean
+
init {
setupShader(RippleShader.RippleShape.ELLIPSE)
setRippleFill(true)
setSparkleStrength(0f)
duration = 3000L
+ isStarted = false
+ }
+
+ fun expandRipple(onAnimationEnd: Runnable? = null) {
+ isStarted = true
+ super.startRipple(onAnimationEnd)
+ }
+
+ /** Used to animate out the ripple. No-op if the ripple was never started via [startRipple]. */
+ fun collapseRipple(onAnimationEnd: Runnable? = null) {
+ if (!isStarted) {
+ return // Ignore if ripple is not started yet.
+ }
+ // Reset all listeners to animator.
+ animator.removeAllListeners()
+ animator.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ onAnimationEnd?.run()
+ isStarted = false
+ }
+ })
+ animator.reverse()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
index 2ad8243..ae28a8b 100644
--- a/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
@@ -41,7 +41,7 @@
private set
private val ripplePaint = Paint()
- private val animator = ValueAnimator.ofFloat(0f, 1f)
+ protected val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f)
var duration: Long = 1750
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
new file mode 100644
index 0000000..4aa982e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.taptotransfer.receiver
+
+import android.content.Context
+import android.os.Handler
+import android.os.PowerManager
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
+import com.android.systemui.media.taptotransfer.MediaTttFlags
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.view.ViewUtil
+import com.android.systemui.util.wakelock.WakeLock
+
+class FakeMediaTttChipControllerReceiver(
+ commandQueue: CommandQueue,
+ context: Context,
+ logger: MediaTttLogger,
+ windowManager: WindowManager,
+ mainExecutor: DelayableExecutor,
+ accessibilityManager: AccessibilityManager,
+ configurationController: ConfigurationController,
+ powerManager: PowerManager,
+ mainHandler: Handler,
+ mediaTttFlags: MediaTttFlags,
+ uiEventLogger: MediaTttReceiverUiEventLogger,
+ viewUtil: ViewUtil,
+ wakeLockBuilder: WakeLock.Builder,
+) :
+ MediaTttChipControllerReceiver(
+ commandQueue,
+ context,
+ logger,
+ windowManager,
+ mainExecutor,
+ accessibilityManager,
+ configurationController,
+ powerManager,
+ mainHandler,
+ mediaTttFlags,
+ uiEventLogger,
+ viewUtil,
+ wakeLockBuilder,
+ ) {
+ override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ // Just bypass the animation in tests
+ onAnimationEnd.run()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index 885cc54..23f7cdb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -114,7 +114,7 @@
fakeWakeLockBuilder = WakeLockFake.Builder(context)
fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
- controllerReceiver = MediaTttChipControllerReceiver(
+ controllerReceiver = FakeMediaTttChipControllerReceiver(
commandQueue,
context,
logger,