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,