[Media TTT] Add the ripple behind the receiver chip.

This mostly just creates a generic ripple/ package with the relevant
classes and uses it for both the charging ripple and ttt ripple.

The full specs of the ripple (size, duration, interpolation, etc. etc.)
aren't finished yet -- I'll polish these with UX next week.

Bug: 203800342
Test: manual (see video attached to bug)
Test: atest SystemUITests
Change-Id: Icdae0cc189121b90ad88e507135876ce4b1979fa
diff --git a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
index 2b3d11b..e079fd3 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
@@ -21,6 +21,12 @@
     android:layout_height="wrap_content"
     >
 
+    <com.android.systemui.media.taptotransfer.receiver.ReceiverChipRippleView
+        android:id="@+id/ripple"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        />
+
     <com.android.internal.widget.CachingIconView
         android:id="@+id/app_icon"
         android:layout_width="@dimen/media_ttt_icon_size_receiver"
diff --git a/packages/SystemUI/res/layout/wireless_charging_layout.xml b/packages/SystemUI/res/layout/wireless_charging_layout.xml
index 1312b41..887e3e7 100644
--- a/packages/SystemUI/res/layout/wireless_charging_layout.xml
+++ b/packages/SystemUI/res/layout/wireless_charging_layout.xml
@@ -22,7 +22,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
-    <com.android.systemui.statusbar.charging.ChargingRippleView
+    <com.android.systemui.ripple.RippleView
         android:id="@+id/wireless_charging_ripple"
         android:layout_width="match_parent"
         android:layout_height="match_parent"/>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 378ae14..fef7383 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -29,8 +29,7 @@
 import android.view.animation.PathInterpolator
 import com.android.internal.graphics.ColorUtils
 import com.android.systemui.animation.Interpolators
-import com.android.systemui.statusbar.charging.DwellRippleShader
-import com.android.systemui.statusbar.charging.RippleShader
+import com.android.systemui.ripple.RippleShader
 
 private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
 
@@ -298,7 +297,7 @@
             addListener(object : AnimatorListenerAdapter() {
                 override fun onAnimationStart(animation: Animator?) {
                     unlockedRippleInProgress = true
-                    rippleShader.shouldFadeOutRipple = true
+                    rippleShader.rippleFill = false
                     drawRipple = true
                     visibility = VISIBLE
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt b/packages/SystemUI/src/com/android/systemui/biometrics/DwellRippleShader.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt
rename to packages/SystemUI/src/com/android/systemui/biometrics/DwellRippleShader.kt
index 236129f..979fe33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/DwellRippleShader.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.charging
+package com.android.systemui.biometrics
 
 import android.graphics.PointF
 import android.graphics.RuntimeShader
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
rename to packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
index 558bcac..8292e52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.charging
+package com.android.systemui.charging
 
 import android.content.Context
 import android.content.res.Configuration
@@ -32,6 +32,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.ripple.RippleView
 import com.android.systemui.statusbar.commandline.Command
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.statusbar.policy.BatteryController
@@ -84,7 +85,7 @@
     private var debounceLevel = 0
 
     @VisibleForTesting
-    var rippleView: ChargingRippleView = ChargingRippleView(context, attrs = null)
+    var rippleView: RippleView = RippleView(context, attrs = null)
 
     init {
         pluggedIn = batteryController.isPluggedIn
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index 0d3e2ae..f6368ee 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -34,7 +34,7 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
-import com.android.systemui.statusbar.charging.ChargingRippleView;
+import com.android.systemui.ripple.RippleView;
 
 import java.text.NumberFormat;
 
@@ -46,7 +46,7 @@
     private static final long RIPPLE_ANIMATION_DURATION = 1500;
     private static final int SCRIM_COLOR = 0x4C000000;
     private static final int SCRIM_FADE_DURATION = 300;
-    private ChargingRippleView mRippleView;
+    private RippleView mRippleView;
 
     public WirelessChargingLayout(Context context) {
         super(context);
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index 0f1cdcc..c9fce79 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -54,7 +54,7 @@
 abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
     internal val context: Context,
     internal val logger: MediaTttLogger,
-    private val windowManager: WindowManager,
+    internal val windowManager: WindowManager,
     private val viewUtil: ViewUtil,
     @Main private val mainExecutor: DelayableExecutor,
     private val accessibilityManager: AccessibilityManager,
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 f9818f0..99a5b8b 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
@@ -16,8 +16,10 @@
 
 package com.android.systemui.media.taptotransfer.receiver
 
+import android.annotation.SuppressLint
 import android.app.StatusBarManager
 import android.content.Context
+import android.graphics.PointF
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.Icon
 import android.media.MediaRoute2Info
@@ -29,6 +31,8 @@
 import android.view.ViewGroup
 import android.view.WindowManager
 import android.view.accessibility.AccessibilityManager
+import androidx.core.graphics.ColorUtils
+import com.android.settingslib.Utils
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
@@ -72,9 +76,15 @@
     powerManager,
     R.layout.media_ttt_chip_receiver
 ) {
+    @SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
     override val windowLayoutParams = commonWindowLayoutParams.apply {
-        height = getWindowHeight()
         gravity = Gravity.BOTTOM.or(Gravity.CENTER_HORIZONTAL)
+        // Params below are needed for the ripple to work correctly
+        width = WindowManager.LayoutParams.MATCH_PARENT
+        height = WindowManager.LayoutParams.MATCH_PARENT
+        layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+        fitInsetsTypes = 0 // Ignore insets from all system bars
+        flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
     }
 
     private val commandQueueCallbacks = object : CommandQueue.Callbacks {
@@ -149,7 +159,7 @@
                 .alpha(1f)
                 .setDuration(5.frames)
                 .start()
-
+        startRipple(chipView.requireViewById(R.id.ripple))
     }
 
     override fun getIconSize(isAppIcon: Boolean): Int? =
@@ -161,16 +171,44 @@
             }
         )
 
-    private fun getWindowHeight(): Int {
-        return context.resources.getDimensionPixelSize(R.dimen.media_ttt_icon_size_receiver) +
-                // Make the window large enough to accommodate the animation amount
-                getTranslationAmount()
-    }
-
     /** Returns the amount that the chip will be translated by in its intro animation. */
     private fun getTranslationAmount(): Int {
         return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation)
     }
+
+    private fun startRipple(rippleView: ReceiverChipRippleView) {
+        if (rippleView.rippleInProgress) {
+            // Skip if ripple is still playing
+            return
+        }
+        rippleView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
+            override fun onViewDetachedFromWindow(view: View?) {}
+
+            override fun onViewAttachedToWindow(view: View?) {
+                if (view == null) {
+                    return
+                }
+                val attachedRippleView = view as ReceiverChipRippleView
+                layoutRipple(attachedRippleView)
+                attachedRippleView.startRipple()
+                attachedRippleView.removeOnAttachStateChangeListener(this)
+            }
+        })
+    }
+
+    private fun layoutRipple(rippleView: ReceiverChipRippleView) {
+        val windowBounds = windowManager.currentWindowMetrics.bounds
+        val height = windowBounds.height()
+        val width = windowBounds.width()
+
+        rippleView.radius = height / 5f
+        // Center the ripple on the bottom of the screen in the middle.
+        rippleView.origin = PointF(/* x= */ width / 2f, /* y= */ height.toFloat())
+
+        val color = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
+        val colorWithAlpha = ColorUtils.setAlphaComponent(color, 70)
+        rippleView.setColor(colorWithAlpha)
+    }
 }
 
 data class ChipReceiverInfo(
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
new file mode 100644
index 0000000..fed546bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.util.AttributeSet
+import com.android.systemui.ripple.RippleView
+
+/**
+ * An expanding ripple effect for the media tap-to-transfer receiver chip.
+ */
+class ReceiverChipRippleView(
+        context: Context?, attrs: AttributeSet?
+) : RippleView(context, attrs) {
+    init {
+        setRippleFill(true)
+        duration = 3000L
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
rename to packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
index bdad36c..93a2efc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.statusbar.charging
+package com.android.systemui.ripple
 
 import android.graphics.PointF
 import android.graphics.RuntimeShader
@@ -148,7 +148,7 @@
             val fadeOutNoise = subProgress(0.4f, 1f, value)
             var fadeOutRipple = 0f
             var fadeCircle = 0f
-            if (shouldFadeOutRipple) {
+            if (!rippleFill) {
                 fadeCircle = subProgress(0f, 0.2f, value)
                 fadeOutRipple = subProgress(0.3f, 1f, value)
             }
@@ -202,5 +202,9 @@
             setFloatUniform("in_pixelDensity", value)
         }
 
-    var shouldFadeOutRipple: Boolean = true
+    /**
+     * True if the ripple should stayed filled in as it expands to give a filled-in circle effect.
+     * False for a ring effect.
+     */
+    var rippleFill: Boolean = false
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
similarity index 81%
rename from packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
rename to packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
index 10e90fe..fc52464 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.charging
+package com.android.systemui.ripple
 
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
@@ -30,9 +30,10 @@
 private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
 
 /**
- * Expanding ripple effect that shows when charging begins.
+ * A generic expanding ripple effect. To trigger the ripple expansion, set [radius] and [origin],
+ * then call [startRipple].
  */
-class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
+open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
     private val rippleShader = RippleShader()
     private val defaultColor: Int = 0xffffffff.toInt()
     private val ripplePaint = Paint()
@@ -74,9 +75,9 @@
         }
         val animator = ValueAnimator.ofFloat(0f, 1f)
         animator.duration = duration
-        animator.addUpdateListener { animator ->
-            val now = animator.currentPlayTime
-            val progress = animator.animatedValue as Float
+        animator.addUpdateListener { updateListener ->
+            val now = updateListener.currentPlayTime
+            val progress = updateListener.animatedValue as Float
             rippleShader.progress = progress
             rippleShader.distortionStrength = 1 - progress
             rippleShader.time = now.toFloat()
@@ -92,10 +93,20 @@
         rippleInProgress = true
     }
 
+    /** Set the color to be used for the ripple. */
     fun setColor(color: Int) {
         rippleShader.color = color
     }
 
+    /**
+     * Set whether the ripple should remain filled as the ripple expands.
+     *
+     * See [RippleShader.rippleFill].
+     */
+    fun setRippleFill(rippleFill: Boolean) {
+        rippleShader.rippleFill = rippleFill
+    }
+
     override fun onDraw(canvas: Canvas?) {
         if (canvas == null || !canvas.isHardwareAccelerated) {
             // Drawing with the ripple shader requires hardware acceleration, so skip
@@ -107,6 +118,6 @@
         // animation implementation in the ripple shader.
         val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
                 (1 - rippleShader.progress)) * radius * 2
-        canvas?.drawCircle(origin.x, origin.y, maskRadius, ripplePaint)
+        canvas.drawCircle(origin.x, origin.y, maskRadius, ripplePaint)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 5181af7..55dc01d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -139,6 +139,7 @@
 import com.android.systemui.biometrics.AuthRippleController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.camera.CameraIntents;
+import com.android.systemui.charging.WiredChargingRippleController;
 import com.android.systemui.charging.WirelessChargingAnimation;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -197,7 +198,6 @@
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.charging.WiredChargingRippleController;
 import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.core.StatusBarInitializer;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
index d0cf792..6978490 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.charging
+package com.android.systemui.charging
 
 import android.testing.AndroidTestingRunner
 import android.view.View
@@ -24,6 +24,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.ripple.RippleView
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -50,7 +51,7 @@
     @Mock private lateinit var batteryController: BatteryController
     @Mock private lateinit var featureFlags: FeatureFlags
     @Mock private lateinit var configurationController: ConfigurationController
-    @Mock private lateinit var rippleView: ChargingRippleView
+    @Mock private lateinit var rippleView: RippleView
     @Mock private lateinit var windowManager: WindowManager
     @Mock private lateinit var uiEventLogger: UiEventLogger
     private val systemClock = FakeSystemClock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 2faff0c..1eccf9f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -84,6 +84,7 @@
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.charging.WiredChargingRippleController;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -117,7 +118,6 @@
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
-import com.android.systemui.statusbar.charging.WiredChargingRippleController;
 import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;