diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index d75474d5279d3..530db0d0304a4 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -53,6 +53,15 @@
app:layout_constraintTop_toTopOf="@id/album_art"
app:layout_constraintBottom_toBottomOf="@id/album_art" />
+
+
+
+
+
+
+
+
(R.id.album_art)
val multiRippleView = itemView.requireViewById(R.id.touch_ripple_view)
+ val turbulenceNoiseView =
+ itemView.requireViewById(R.id.turbulence_noise_view)
val appIcon = itemView.requireViewById(R.id.icon)
val titleText = itemView.requireViewById(R.id.header_title)
val artistText = itemView.requireViewById(R.id.header_artist)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
index bbf4ef17dea67..93be6a78ccd5b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
@@ -30,6 +30,7 @@ import com.android.settingslib.Utils
import com.android.systemui.media.controls.models.player.MediaViewHolder
import com.android.systemui.monet.ColorScheme
import com.android.systemui.surfaceeffects.ripple.MultiRippleController
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController
/**
* A [ColorTransition] is an object that updates the colors of views each time [updateColorScheme]
@@ -102,13 +103,21 @@ internal constructor(
private val context: Context,
private val mediaViewHolder: MediaViewHolder,
private val multiRippleController: MultiRippleController,
+ private val turbulenceNoiseController: TurbulenceNoiseController,
animatingColorTransitionFactory: AnimatingColorTransitionFactory
) {
constructor(
context: Context,
mediaViewHolder: MediaViewHolder,
multiRippleController: MultiRippleController,
- ) : this(context, mediaViewHolder, multiRippleController, ::AnimatingColorTransition)
+ turbulenceNoiseController: TurbulenceNoiseController
+ ) : this(
+ context,
+ mediaViewHolder,
+ multiRippleController,
+ turbulenceNoiseController,
+ ::AnimatingColorTransition
+ )
val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95)
val surfaceColor =
@@ -129,6 +138,7 @@ internal constructor(
mediaViewHolder.actionPlayPause.backgroundTintList = accentColorList
mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary)
multiRippleController.updateColor(accentPrimary)
+ turbulenceNoiseController.updateNoiseColor(accentPrimary)
}
val accentSecondary =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 4eb0d991bd4c0..cb5789e20c15d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -31,6 +31,7 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
+import android.graphics.BlendMode;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
@@ -64,6 +65,7 @@ import androidx.annotation.UiThread;
import androidx.constraintlayout.widget.ConstraintSet;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.ColorUtils;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.InstanceId;
import com.android.settingslib.widget.AdaptiveIcon;
@@ -101,9 +103,12 @@ import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.surfaceeffects.ripple.MultiRippleController;
+import com.android.systemui.surfaceeffects.ripple.MultiRippleView;
import com.android.systemui.surfaceeffects.ripple.RippleAnimation;
import com.android.systemui.surfaceeffects.ripple.RippleAnimationConfig;
import com.android.systemui.surfaceeffects.ripple.RippleShader;
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig;
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController;
import com.android.systemui.util.ColorUtilKt;
import com.android.systemui.util.animation.TransitionLayout;
import com.android.systemui.util.time.SystemClock;
@@ -216,7 +221,9 @@ public class MediaControlPanel {
private boolean mShowBroadcastDialogButton = false;
private String mSwitchBroadcastApp;
private MultiRippleController mMultiRippleController;
+ private TurbulenceNoiseController mTurbulenceNoiseController;
private FeatureFlags mFeatureFlags;
+ private TurbulenceNoiseAnimationConfig mTurbulenceNoiseAnimationConfig = null;
/**
* Initialize a new control panel
@@ -393,9 +400,20 @@ public class MediaControlPanel {
AnimatorSet exit = loadAnimator(R.anim.media_metadata_exit,
Interpolators.EMPHASIZED_ACCELERATE, titleText, artistText);
- mMultiRippleController = new MultiRippleController(vh.getMultiRippleView());
+ MultiRippleView multiRippleView = vh.getMultiRippleView();
+ mMultiRippleController = new MultiRippleController(multiRippleView);
+ mTurbulenceNoiseController = new TurbulenceNoiseController(vh.getTurbulenceNoiseView());
+ multiRippleView.addRipplesFinishedListener(
+ () -> {
+ if (mTurbulenceNoiseAnimationConfig == null) {
+ mTurbulenceNoiseAnimationConfig = createLingeringNoiseAnimation();
+ }
+ // Color will be correctly updated in ColorSchemeTransition.
+ mTurbulenceNoiseController.play(mTurbulenceNoiseAnimationConfig);
+ }
+ );
mColorSchemeTransition = new ColorSchemeTransition(
- mContext, mMediaViewHolder, mMultiRippleController);
+ mContext, mMediaViewHolder, mMultiRippleController, mTurbulenceNoiseController);
mMetadataAnimationHandler = new MetadataAnimationHandler(exit, enter);
}
@@ -1025,7 +1043,7 @@ public class MediaControlPanel {
/* maxWidth= */ maxSize,
/* maxHeight= */ maxSize,
/* pixelDensity= */ getContext().getResources().getDisplayMetrics().density,
- mColorSchemeTransition.getAccentPrimary().getTargetColor(),
+ mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
/* opacity= */ 100,
/* shouldFillRipple= */ false,
/* sparkleStrength= */ 0f,
@@ -1034,6 +1052,26 @@ public class MediaControlPanel {
);
}
+ private TurbulenceNoiseAnimationConfig createLingeringNoiseAnimation() {
+ return new TurbulenceNoiseAnimationConfig(
+ TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_GRID_COUNT,
+ TurbulenceNoiseAnimationConfig.DEFAULT_LUMINOSITY_MULTIPLIER,
+ /* noiseMoveSpeedX= */ 0f,
+ /* noiseMoveSpeedY= */ 0f,
+ TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z,
+ /* color= */ mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
+ // We want to add (BlendMode.PLUS) the turbulence noise on top of the album art.
+ // Thus, set the background color with alpha 0.
+ /* backgroundColor= */ ColorUtils.setAlphaComponent(Color.BLACK, 0),
+ TurbulenceNoiseAnimationConfig.DEFAULT_OPACITY,
+ /* width= */ mMediaViewHolder.getMultiRippleView().getWidth(),
+ /* height= */ mMediaViewHolder.getMultiRippleView().getHeight(),
+ TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_DURATION_IN_MILLIS,
+ this.getContext().getResources().getDisplayMetrics().density,
+ BlendMode.PLUS,
+ /* onAnimationEnd= */ null
+ );
+ }
private void clearButton(final ImageButton button) {
button.setImageDrawable(null);
button.setContentDescription(null);
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
index e44daf4d7cc20..f558fee776e6a 100644
--- a/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
@@ -31,11 +31,21 @@ import android.view.View
class MultiRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
internal val ripples = ArrayList()
+ private val listeners = ArrayList()
private val ripplePaint = Paint()
private var isWarningLogged = false
companion object {
private const val TAG = "MultiRippleView"
+
+ interface RipplesFinishedListener {
+ /** Triggered when all the ripples finish running. */
+ fun onRipplesFinish()
+ }
+ }
+
+ fun addRipplesFinishedListener(listener: RipplesFinishedListener) {
+ listeners.add(listener)
}
override fun onDraw(canvas: Canvas?) {
@@ -62,6 +72,10 @@ class MultiRippleView(context: Context?, attrs: AttributeSet?) : View(context, a
shouldInvalidate = shouldInvalidate || anim.isPlaying()
}
- if (shouldInvalidate) invalidate()
+ if (shouldInvalidate) {
+ invalidate()
+ } else { // Nothing is playing.
+ listeners.forEach { listener -> listener.onRipplesFinish() }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
index a4540686640ad..5ac3aad749fc1 100644
--- a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
@@ -43,7 +43,7 @@ data class TurbulenceNoiseAnimationConfig(
val noiseMoveSpeedZ: Float = DEFAULT_NOISE_SPEED_Z,
/** Color of the effect. */
- val color: Int = DEFAULT_COLOR,
+ var color: Int = DEFAULT_COLOR,
/** Background color of the effect. */
val backgroundColor: Int = DEFAULT_BACKGROUND_COLOR,
val opacity: Int = DEFAULT_OPACITY,
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
index d900df6417560..4c7e5f4c70937 100644
--- a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
@@ -17,6 +17,11 @@ package com.android.systemui.surfaceeffects.turbulencenoise
/** A controller that plays [TurbulenceNoiseView]. */
class TurbulenceNoiseController(private val turbulenceNoiseView: TurbulenceNoiseView) {
+ /** Updates the color of the noise. */
+ fun updateNoiseColor(color: Int) {
+ turbulenceNoiseView.updateColor(color)
+ }
+
// TODO: add cancel and/ or pause once design requirements become clear.
/** Plays [TurbulenceNoiseView] with the given config. */
fun play(turbulenceNoiseAnimationConfig: TurbulenceNoiseAnimationConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
index 5bbebcd6c799e..19c114d2693c6 100644
--- a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
@@ -104,8 +104,8 @@ class TurbulenceNoiseShader : RuntimeShader(TURBULENCE_NOISE_SHADER) {
*
* Expected value range is [1, 0].
*/
- fun setOpacity(fadeOut: Float) {
- setFloatUniform("in_opacity", fadeOut)
+ fun setOpacity(opacity: Float) {
+ setFloatUniform("in_opacity", opacity)
}
/** Sets the size of the shader. */
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
index 2d71596b2a3c5..8649d59245874 100644
--- a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
@@ -40,6 +40,7 @@ class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(contex
private val paint = Paint().apply { this.shader = turbulenceNoiseShader }
private val random = Random()
private val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f)
+ private var config: TurbulenceNoiseAnimationConfig? = null
val isPlaying: Boolean
get() = animator.isRunning
@@ -49,6 +50,14 @@ class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(contex
visibility = INVISIBLE
}
+ /** Updates the color during the animation. No-op if there's no animation playing. */
+ fun updateColor(color: Int) {
+ config?.let {
+ it.color = color
+ applyConfig(it)
+ }
+ }
+
override fun onDraw(canvas: Canvas?) {
if (canvas == null || !canvas.isHardwareAccelerated) {
// Drawing with the turbulence noise shader requires hardware acceleration, so skip
@@ -101,6 +110,7 @@ class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(contex
}
private fun applyConfig(config: TurbulenceNoiseAnimationConfig) {
+ this.config = config
with(turbulenceNoiseShader) {
setGridCount(config.gridCount)
setColor(ColorUtils.setAlphaComponent(config.color, config.opacity))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
index 9a1e6f381169b..a94374680b915 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
@@ -26,6 +26,7 @@ import com.android.systemui.media.controls.models.GutsViewHolder
import com.android.systemui.media.controls.models.player.MediaViewHolder
import com.android.systemui.monet.ColorScheme
import com.android.systemui.surfaceeffects.ripple.MultiRippleController
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController
import junit.framework.Assert.assertEquals
import org.junit.After
import org.junit.Before
@@ -62,6 +63,7 @@ class ColorSchemeTransitionTest : SysuiTestCase() {
@Mock private lateinit var mediaViewHolder: MediaViewHolder
@Mock private lateinit var gutsViewHolder: GutsViewHolder
@Mock private lateinit var multiRippleController: MultiRippleController
+ @Mock private lateinit var turbulenceNoiseController: TurbulenceNoiseController
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
@@ -76,6 +78,7 @@ class ColorSchemeTransitionTest : SysuiTestCase() {
context,
mediaViewHolder,
multiRippleController,
+ turbulenceNoiseController,
animatingColorTransitionFactory
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index dad2524837d0f..c15a2cb83d966 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -81,6 +81,7 @@ import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.surfaceeffects.ripple.MultiRippleView
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.KotlinArgumentCaptor
@@ -178,6 +179,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
private lateinit var dismiss: FrameLayout
private lateinit var dismissText: TextView
private lateinit var multiRippleView: MultiRippleView
+ private lateinit var turbulenceNoiseView: TurbulenceNoiseView
private lateinit var session: MediaSession
private lateinit var device: MediaDeviceData
@@ -382,6 +384,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
multiRippleView = MultiRippleView(context, null)
+ turbulenceNoiseView = TurbulenceNoiseView(context, null)
whenever(viewHolder.player).thenReturn(view)
whenever(viewHolder.appIcon).thenReturn(appIcon)
@@ -425,6 +428,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
whenever(viewHolder.actionsTopBarrier).thenReturn(actionsTopBarrier)
whenever(viewHolder.multiRippleView).thenReturn(multiRippleView)
+ whenever(viewHolder.turbulenceNoiseView).thenReturn(turbulenceNoiseView)
}
/** Initialize elements for the recommendation view holder */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleViewTest.kt
new file mode 100644
index 0000000000000..2024d53b0212f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleViewTest.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.surfaceeffects.ripple
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class MultiRippleViewTest : SysuiTestCase() {
+ private val fakeSystemClock = FakeSystemClock()
+ // FakeExecutor is needed to run animator.
+ private val fakeExecutor = FakeExecutor(fakeSystemClock)
+
+ @Test
+ fun onRippleFinishes_triggersRippleFinished() {
+ val multiRippleView = MultiRippleView(context, null)
+ val multiRippleController = MultiRippleController(multiRippleView)
+ val rippleAnimationConfig = RippleAnimationConfig(duration = 1000L)
+
+ var isTriggered = false
+ val listener =
+ object : MultiRippleView.Companion.RipplesFinishedListener {
+ override fun onRipplesFinish() {
+ isTriggered = true
+ }
+ }
+ multiRippleView.addRipplesFinishedListener(listener)
+
+ fakeExecutor.execute {
+ val rippleAnimation = RippleAnimation(rippleAnimationConfig)
+ multiRippleController.play(rippleAnimation)
+
+ fakeSystemClock.advanceTime(rippleAnimationConfig.duration)
+
+ assertThat(isTriggered).isTrue()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
index 00c3bb9c15cf6..d25c8c1a58991 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.surfaceeffects.turbulencenoise
+import android.graphics.Color
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -48,4 +49,23 @@ class TurbulenceNoiseControllerTest : SysuiTestCase() {
assertThat(turbulenceNoiseView.isPlaying).isFalse()
}
}
+
+ @Test
+ fun updateColor_updatesCorrectColor() {
+ val config = TurbulenceNoiseAnimationConfig(duration = 1000f, color = Color.WHITE)
+ val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+ val expectedColor = Color.RED
+
+ val turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
+
+ fakeExecutor.execute {
+ turbulenceNoiseController.play(config)
+
+ turbulenceNoiseView.updateColor(expectedColor)
+
+ fakeSystemClock.advanceTime(config.duration.toLong())
+
+ assertThat(config.color).isEqualTo(expectedColor)
+ }
+ }
}