diff --git a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml index 2b3d11b0e1912..e079fd3c5e8f0 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" > + + - diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt index 378ae14f0327d..fef7383634f00 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.View 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 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at 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 99% rename from packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt rename to packages/SystemUI/src/com/android/systemui/biometrics/DwellRippleShader.kt index 236129f8eb50a..979fe33fb25b3 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 558bcac681d90..8292e52b1ffdc 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.R 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 @@ class WiredChargingRippleController @Inject constructor( 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 0d3e2ae9a776d..f6368ee19e8fb 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 android.widget.TextView; 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 @@ public class WirelessChargingLayout extends FrameLayout { 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 0f1cdcc3fa5b1..c9fce794f57fb 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 @@ import com.android.systemui.util.view.ViewUtil abstract class MediaTttChipControllerCommon( 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 f9818f0ad8bee..99a5b8b2d4505 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.View 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 @@ class MediaTttChipControllerReceiver @Inject constructor( 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 @@ class MediaTttChipControllerReceiver @Inject constructor( .alpha(1f) .setDuration(5.frames) .start() - + startRipple(chipView.requireViewById(R.id.ripple)) } override fun getIconSize(isAppIcon: Boolean): Int? = @@ -161,16 +171,44 @@ class MediaTttChipControllerReceiver @Inject constructor( } ) - 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 0000000000000..fed546bf52c2a --- /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 97% rename from packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt rename to packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt index bdad36c584809..93a2efc4e96d7 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 @@ class RippleShader internal constructor() : RuntimeShader(SHADER) { 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 @@ class RippleShader internal constructor() : RuntimeShader(SHADER) { 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 10e90fee14698..fc52464ecf85e 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 @@ import android.view.View 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 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context } 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 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context 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 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context // 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 5181af773d9a5..55dc01d3afd24 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.assist.AssistManager; 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.PowerButtonReveal; 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 d0cf792b698db..6978490a1252e 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.internal.logging.UiEventLogger 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 @@ class WiredChargingRippleControllerTest : SysuiTestCase() { @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 2faff0ced70a6..1eccf9f3128a3 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.accessibility.floatingmenu.AccessibilityFloatingMenu 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.OperatorNameViewController; 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;