Merge changes from topic "noise-ripple" into tm-qpr-dev
* changes: Add turbulence noise effects after ripple in UMO. Add a turbulence noise shader for media player and ghost loading effects.
This commit is contained in:
@@ -44,7 +44,7 @@
|
|||||||
android:background="@drawable/qs_media_outline_album_bg"
|
android:background="@drawable/qs_media_outline_album_bg"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<com.android.systemui.ripple.MultiRippleView
|
<com.android.systemui.surfaceeffects.ripple.MultiRippleView
|
||||||
android:id="@+id/touch_ripple_view"
|
android:id="@+id/touch_ripple_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/qs_media_session_height_expanded"
|
android:layout_height="@dimen/qs_media_session_height_expanded"
|
||||||
@@ -53,6 +53,15 @@
|
|||||||
app:layout_constraintTop_toTopOf="@id/album_art"
|
app:layout_constraintTop_toTopOf="@id/album_art"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/album_art" />
|
app:layout_constraintBottom_toBottomOf="@id/album_art" />
|
||||||
|
|
||||||
|
<com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
|
||||||
|
android:id="@+id/turbulence_noise_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/qs_media_session_height_expanded"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/album_art"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/album_art"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/album_art"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/album_art" />
|
||||||
|
|
||||||
<!-- Guideline for output switcher -->
|
<!-- Guideline for output switcher -->
|
||||||
<androidx.constraintlayout.widget.Guideline
|
<androidx.constraintlayout.widget.Guideline
|
||||||
android:id="@+id/center_vertical_guideline"
|
android:id="@+id/center_vertical_guideline"
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<com.android.systemui.ripple.RippleView
|
<com.android.systemui.surfaceeffects.ripple.RippleView
|
||||||
android:id="@+id/wireless_charging_ripple"
|
android:id="@+id/wireless_charging_ripple"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"/>
|
android:layout_height="match_parent"/>
|
||||||
|
|||||||
@@ -44,6 +44,16 @@
|
|||||||
app:layout_constraintTop_toTopOf="@+id/album_art"
|
app:layout_constraintTop_toTopOf="@+id/album_art"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/album_art" />
|
app:layout_constraintBottom_toBottomOf="@+id/album_art" />
|
||||||
|
|
||||||
|
<!-- Turbulence noise must have the same constraint as the album art. -->
|
||||||
|
<Constraint
|
||||||
|
android:id="@+id/turbulence_noise_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/qs_media_session_height_collapsed"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/album_art"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/album_art"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/album_art"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/album_art" />
|
||||||
|
|
||||||
<Constraint
|
<Constraint
|
||||||
android:id="@+id/header_title"
|
android:id="@+id/header_title"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|||||||
@@ -37,6 +37,16 @@
|
|||||||
app:layout_constraintTop_toTopOf="@+id/album_art"
|
app:layout_constraintTop_toTopOf="@+id/album_art"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/album_art" />
|
app:layout_constraintBottom_toBottomOf="@+id/album_art" />
|
||||||
|
|
||||||
|
<!-- Turbulence noise must have the same constraint as the album art. -->
|
||||||
|
<Constraint
|
||||||
|
android:id="@+id/turbulence_noise_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/qs_media_session_height_expanded"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/album_art"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/album_art"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/album_art"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/album_art" />
|
||||||
|
|
||||||
<Constraint
|
<Constraint
|
||||||
android:id="@+id/header_title"
|
android:id="@+id/header_title"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import android.view.View
|
|||||||
import android.view.animation.PathInterpolator
|
import android.view.animation.PathInterpolator
|
||||||
import com.android.internal.graphics.ColorUtils
|
import com.android.internal.graphics.ColorUtils
|
||||||
import com.android.systemui.animation.Interpolators
|
import com.android.systemui.animation.Interpolators
|
||||||
import com.android.systemui.ripple.RippleShader
|
import com.android.systemui.surfaceeffects.ripple.RippleShader
|
||||||
|
|
||||||
private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
|
private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import com.android.systemui.R
|
|||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.flags.FeatureFlags
|
import com.android.systemui.flags.FeatureFlags
|
||||||
import com.android.systemui.flags.Flags
|
import com.android.systemui.flags.Flags
|
||||||
import com.android.systemui.ripple.RippleView
|
import com.android.systemui.surfaceeffects.ripple.RippleView
|
||||||
import com.android.systemui.statusbar.commandline.Command
|
import com.android.systemui.statusbar.commandline.Command
|
||||||
import com.android.systemui.statusbar.commandline.CommandRegistry
|
import com.android.systemui.statusbar.commandline.CommandRegistry
|
||||||
import com.android.systemui.statusbar.policy.BatteryController
|
import com.android.systemui.statusbar.policy.BatteryController
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import android.view.WindowManager;
|
|||||||
|
|
||||||
import com.android.internal.logging.UiEvent;
|
import com.android.internal.logging.UiEvent;
|
||||||
import com.android.internal.logging.UiEventLogger;
|
import com.android.internal.logging.UiEventLogger;
|
||||||
import com.android.systemui.ripple.RippleShader.RippleShape;
|
import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A WirelessChargingAnimation is a view containing view + animation for wireless charging.
|
* A WirelessChargingAnimation is a view containing view + animation for wireless charging.
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ import android.widget.TextView;
|
|||||||
import com.android.settingslib.Utils;
|
import com.android.settingslib.Utils;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.animation.Interpolators;
|
import com.android.systemui.animation.Interpolators;
|
||||||
import com.android.systemui.ripple.RippleAnimationConfig;
|
import com.android.systemui.surfaceeffects.ripple.RippleAnimationConfig;
|
||||||
import com.android.systemui.ripple.RippleShader.RippleShape;
|
import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
|
||||||
import com.android.systemui.ripple.RippleView;
|
import com.android.systemui.surfaceeffects.ripple.RippleView;
|
||||||
|
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ import android.widget.TextView
|
|||||||
import androidx.constraintlayout.widget.Barrier
|
import androidx.constraintlayout.widget.Barrier
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.media.controls.models.GutsViewHolder
|
import com.android.systemui.media.controls.models.GutsViewHolder
|
||||||
import com.android.systemui.ripple.MultiRippleView
|
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.animation.TransitionLayout
|
||||||
|
|
||||||
private const val TAG = "MediaViewHolder"
|
private const val TAG = "MediaViewHolder"
|
||||||
@@ -38,6 +39,8 @@ class MediaViewHolder constructor(itemView: View) {
|
|||||||
// Player information
|
// Player information
|
||||||
val albumView = itemView.requireViewById<ImageView>(R.id.album_art)
|
val albumView = itemView.requireViewById<ImageView>(R.id.album_art)
|
||||||
val multiRippleView = itemView.requireViewById<MultiRippleView>(R.id.touch_ripple_view)
|
val multiRippleView = itemView.requireViewById<MultiRippleView>(R.id.touch_ripple_view)
|
||||||
|
val turbulenceNoiseView =
|
||||||
|
itemView.requireViewById<TurbulenceNoiseView>(R.id.turbulence_noise_view)
|
||||||
val appIcon = itemView.requireViewById<ImageView>(R.id.icon)
|
val appIcon = itemView.requireViewById<ImageView>(R.id.icon)
|
||||||
val titleText = itemView.requireViewById<TextView>(R.id.header_title)
|
val titleText = itemView.requireViewById<TextView>(R.id.header_title)
|
||||||
val artistText = itemView.requireViewById<TextView>(R.id.header_artist)
|
val artistText = itemView.requireViewById<TextView>(R.id.header_artist)
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ import com.android.internal.annotations.VisibleForTesting
|
|||||||
import com.android.settingslib.Utils
|
import com.android.settingslib.Utils
|
||||||
import com.android.systemui.media.controls.models.player.MediaViewHolder
|
import com.android.systemui.media.controls.models.player.MediaViewHolder
|
||||||
import com.android.systemui.monet.ColorScheme
|
import com.android.systemui.monet.ColorScheme
|
||||||
import com.android.systemui.ripple.MultiRippleController
|
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]
|
* 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 context: Context,
|
||||||
private val mediaViewHolder: MediaViewHolder,
|
private val mediaViewHolder: MediaViewHolder,
|
||||||
private val multiRippleController: MultiRippleController,
|
private val multiRippleController: MultiRippleController,
|
||||||
|
private val turbulenceNoiseController: TurbulenceNoiseController,
|
||||||
animatingColorTransitionFactory: AnimatingColorTransitionFactory
|
animatingColorTransitionFactory: AnimatingColorTransitionFactory
|
||||||
) {
|
) {
|
||||||
constructor(
|
constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
mediaViewHolder: MediaViewHolder,
|
mediaViewHolder: MediaViewHolder,
|
||||||
multiRippleController: MultiRippleController,
|
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 bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95)
|
||||||
val surfaceColor =
|
val surfaceColor =
|
||||||
@@ -129,6 +138,7 @@ internal constructor(
|
|||||||
mediaViewHolder.actionPlayPause.backgroundTintList = accentColorList
|
mediaViewHolder.actionPlayPause.backgroundTintList = accentColorList
|
||||||
mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary)
|
mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary)
|
||||||
multiRippleController.updateColor(accentPrimary)
|
multiRippleController.updateColor(accentPrimary)
|
||||||
|
turbulenceNoiseController.updateNoiseColor(accentPrimary)
|
||||||
}
|
}
|
||||||
|
|
||||||
val accentSecondary =
|
val accentSecondary =
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import android.content.Intent;
|
|||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.ColorStateList;
|
import android.content.res.ColorStateList;
|
||||||
|
import android.graphics.BlendMode;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.ColorMatrix;
|
import android.graphics.ColorMatrix;
|
||||||
import android.graphics.ColorMatrixColorFilter;
|
import android.graphics.ColorMatrixColorFilter;
|
||||||
@@ -64,6 +65,7 @@ import androidx.annotation.UiThread;
|
|||||||
import androidx.constraintlayout.widget.ConstraintSet;
|
import androidx.constraintlayout.widget.ConstraintSet;
|
||||||
|
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
|
import com.android.internal.graphics.ColorUtils;
|
||||||
import com.android.internal.jank.InteractionJankMonitor;
|
import com.android.internal.jank.InteractionJankMonitor;
|
||||||
import com.android.internal.logging.InstanceId;
|
import com.android.internal.logging.InstanceId;
|
||||||
import com.android.settingslib.widget.AdaptiveIcon;
|
import com.android.settingslib.widget.AdaptiveIcon;
|
||||||
@@ -97,13 +99,16 @@ import com.android.systemui.monet.ColorScheme;
|
|||||||
import com.android.systemui.monet.Style;
|
import com.android.systemui.monet.Style;
|
||||||
import com.android.systemui.plugins.ActivityStarter;
|
import com.android.systemui.plugins.ActivityStarter;
|
||||||
import com.android.systemui.plugins.FalsingManager;
|
import com.android.systemui.plugins.FalsingManager;
|
||||||
import com.android.systemui.ripple.MultiRippleController;
|
|
||||||
import com.android.systemui.ripple.RippleAnimation;
|
|
||||||
import com.android.systemui.ripple.RippleAnimationConfig;
|
|
||||||
import com.android.systemui.ripple.RippleShader;
|
|
||||||
import com.android.systemui.shared.system.SysUiStatsLog;
|
import com.android.systemui.shared.system.SysUiStatsLog;
|
||||||
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
|
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
|
||||||
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
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.ColorUtilKt;
|
||||||
import com.android.systemui.util.animation.TransitionLayout;
|
import com.android.systemui.util.animation.TransitionLayout;
|
||||||
import com.android.systemui.util.time.SystemClock;
|
import com.android.systemui.util.time.SystemClock;
|
||||||
@@ -216,7 +221,9 @@ public class MediaControlPanel {
|
|||||||
private boolean mShowBroadcastDialogButton = false;
|
private boolean mShowBroadcastDialogButton = false;
|
||||||
private String mSwitchBroadcastApp;
|
private String mSwitchBroadcastApp;
|
||||||
private MultiRippleController mMultiRippleController;
|
private MultiRippleController mMultiRippleController;
|
||||||
|
private TurbulenceNoiseController mTurbulenceNoiseController;
|
||||||
private FeatureFlags mFeatureFlags;
|
private FeatureFlags mFeatureFlags;
|
||||||
|
private TurbulenceNoiseAnimationConfig mTurbulenceNoiseAnimationConfig = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a new control panel
|
* Initialize a new control panel
|
||||||
@@ -394,9 +401,20 @@ public class MediaControlPanel {
|
|||||||
AnimatorSet exit = loadAnimator(R.anim.media_metadata_exit,
|
AnimatorSet exit = loadAnimator(R.anim.media_metadata_exit,
|
||||||
Interpolators.EMPHASIZED_ACCELERATE, titleText, artistText);
|
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(
|
mColorSchemeTransition = new ColorSchemeTransition(
|
||||||
mContext, mMediaViewHolder, mMultiRippleController);
|
mContext, mMediaViewHolder, mMultiRippleController, mTurbulenceNoiseController);
|
||||||
mMetadataAnimationHandler = new MetadataAnimationHandler(exit, enter);
|
mMetadataAnimationHandler = new MetadataAnimationHandler(exit, enter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1027,7 +1045,7 @@ public class MediaControlPanel {
|
|||||||
/* maxWidth= */ maxSize,
|
/* maxWidth= */ maxSize,
|
||||||
/* maxHeight= */ maxSize,
|
/* maxHeight= */ maxSize,
|
||||||
/* pixelDensity= */ getContext().getResources().getDisplayMetrics().density,
|
/* pixelDensity= */ getContext().getResources().getDisplayMetrics().density,
|
||||||
mColorSchemeTransition.getAccentPrimary().getTargetColor(),
|
mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
|
||||||
/* opacity= */ 100,
|
/* opacity= */ 100,
|
||||||
/* shouldFillRipple= */ false,
|
/* shouldFillRipple= */ false,
|
||||||
/* sparkleStrength= */ 0f,
|
/* sparkleStrength= */ 0f,
|
||||||
@@ -1036,6 +1054,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) {
|
private void clearButton(final ImageButton button) {
|
||||||
button.setImageDrawable(null);
|
button.setImageDrawable(null);
|
||||||
button.setContentDescription(null);
|
button.setContentDescription(null);
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ package com.android.systemui.media.taptotransfer.receiver
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import com.android.systemui.ripple.RippleShader
|
import com.android.systemui.surfaceeffects.ripple.RippleShader
|
||||||
import com.android.systemui.ripple.RippleView
|
import com.android.systemui.surfaceeffects.ripple.RippleView
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An expanding ripple effect for the media tap-to-transfer receiver chip.
|
* An expanding ripple effect for the media tap-to-transfer receiver chip.
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.ripple
|
|
||||||
|
|
||||||
/** A common utility functions that are used for computing [RippleShader]. */
|
|
||||||
class RippleShaderUtilLibrary {
|
|
||||||
//language=AGSL
|
|
||||||
companion object {
|
|
||||||
const val SHADER_LIB = """
|
|
||||||
float triangleNoise(vec2 n) {
|
|
||||||
n = fract(n * vec2(5.3987, 5.4421));
|
|
||||||
n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
|
|
||||||
float xy = n.x * n.y;
|
|
||||||
return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
|
|
||||||
}
|
|
||||||
const float PI = 3.1415926535897932384626;
|
|
||||||
|
|
||||||
float sparkles(vec2 uv, float t) {
|
|
||||||
float n = triangleNoise(uv);
|
|
||||||
float s = 0.0;
|
|
||||||
for (float i = 0; i < 4; i += 1) {
|
|
||||||
float l = i * 0.01;
|
|
||||||
float h = l + 0.1;
|
|
||||||
float o = smoothstep(n - l, h, n);
|
|
||||||
o *= abs(sin(PI * o * (t + 0.55 * i)));
|
|
||||||
s += o;
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
vec2 distort(vec2 p, float time, float distort_amount_radial,
|
|
||||||
float distort_amount_xy) {
|
|
||||||
float angle = atan(p.y, p.x);
|
|
||||||
return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
|
|
||||||
cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
|
|
||||||
+ vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
|
|
||||||
cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
|
|
||||||
}"""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -174,7 +174,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
|||||||
import com.android.systemui.qs.QSFragment;
|
import com.android.systemui.qs.QSFragment;
|
||||||
import com.android.systemui.qs.QSPanelController;
|
import com.android.systemui.qs.QSPanelController;
|
||||||
import com.android.systemui.recents.ScreenPinningRequest;
|
import com.android.systemui.recents.ScreenPinningRequest;
|
||||||
import com.android.systemui.ripple.RippleShader.RippleShape;
|
|
||||||
import com.android.systemui.scrim.ScrimView;
|
import com.android.systemui.scrim.ScrimView;
|
||||||
import com.android.systemui.settings.brightness.BrightnessSliderController;
|
import com.android.systemui.settings.brightness.BrightnessSliderController;
|
||||||
import com.android.systemui.shade.CameraLauncher;
|
import com.android.systemui.shade.CameraLauncher;
|
||||||
@@ -234,6 +233,7 @@ import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
|
|||||||
import com.android.systemui.statusbar.policy.UserSwitcherController;
|
import com.android.systemui.statusbar.policy.UserSwitcherController;
|
||||||
import com.android.systemui.statusbar.window.StatusBarWindowController;
|
import com.android.systemui.statusbar.window.StatusBarWindowController;
|
||||||
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
|
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
|
||||||
|
import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
|
||||||
import com.android.systemui.util.DumpUtilsKt;
|
import com.android.systemui.util.DumpUtilsKt;
|
||||||
import com.android.systemui.util.WallpaperController;
|
import com.android.systemui.util.WallpaperController;
|
||||||
import com.android.systemui.util.concurrency.DelayableExecutor;
|
import com.android.systemui.util.concurrency.DelayableExecutor;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.android.systemui.ripple
|
package com.android.systemui.surfaceeffects.ripple
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.android.systemui.ripple
|
package com.android.systemui.surfaceeffects.ripple
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
@@ -31,11 +31,21 @@ import android.view.View
|
|||||||
class MultiRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
|
class MultiRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
|
||||||
|
|
||||||
internal val ripples = ArrayList<RippleAnimation>()
|
internal val ripples = ArrayList<RippleAnimation>()
|
||||||
|
private val listeners = ArrayList<RipplesFinishedListener>()
|
||||||
private val ripplePaint = Paint()
|
private val ripplePaint = Paint()
|
||||||
private var isWarningLogged = false
|
private var isWarningLogged = false
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "MultiRippleView"
|
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?) {
|
override fun onDraw(canvas: Canvas?) {
|
||||||
@@ -62,6 +72,10 @@ class MultiRippleView(context: Context?, attrs: AttributeSet?) : View(context, a
|
|||||||
shouldInvalidate = shouldInvalidate || anim.isPlaying()
|
shouldInvalidate = shouldInvalidate || anim.isPlaying()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldInvalidate) invalidate()
|
if (shouldInvalidate) {
|
||||||
|
invalidate()
|
||||||
|
} else { // Nothing is playing.
|
||||||
|
listeners.forEach { listener -> listener.onRipplesFinish() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.android.systemui.ripple
|
package com.android.systemui.surfaceeffects.ripple
|
||||||
|
|
||||||
import android.animation.Animator
|
import android.animation.Animator
|
||||||
import android.animation.AnimatorListenerAdapter
|
import android.animation.AnimatorListenerAdapter
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.android.systemui.ripple
|
package com.android.systemui.surfaceeffects.ripple
|
||||||
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
|
||||||
@@ -13,11 +13,13 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.android.systemui.ripple
|
package com.android.systemui.surfaceeffects.ripple
|
||||||
|
|
||||||
import android.graphics.PointF
|
import android.graphics.PointF
|
||||||
import android.graphics.RuntimeShader
|
import android.graphics.RuntimeShader
|
||||||
import android.util.MathUtils
|
import android.util.MathUtils
|
||||||
|
import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary
|
||||||
|
import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shader class that renders an expanding ripple effect. The ripple contains three elements:
|
* Shader class that renders an expanding ripple effect. The ripple contains three elements:
|
||||||
@@ -41,7 +43,9 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C
|
|||||||
}
|
}
|
||||||
// language=AGSL
|
// language=AGSL
|
||||||
companion object {
|
companion object {
|
||||||
private const val SHADER_UNIFORMS = """uniform vec2 in_center;
|
private const val SHADER_UNIFORMS =
|
||||||
|
"""
|
||||||
|
uniform vec2 in_center;
|
||||||
uniform vec2 in_size;
|
uniform vec2 in_size;
|
||||||
uniform float in_progress;
|
uniform float in_progress;
|
||||||
uniform float in_cornerRadius;
|
uniform float in_cornerRadius;
|
||||||
@@ -55,9 +59,12 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C
|
|||||||
uniform float in_blur;
|
uniform float in_blur;
|
||||||
uniform float in_pixelDensity;
|
uniform float in_pixelDensity;
|
||||||
layout(color) uniform vec4 in_color;
|
layout(color) uniform vec4 in_color;
|
||||||
uniform float in_sparkle_strength;"""
|
uniform float in_sparkle_strength;
|
||||||
|
"""
|
||||||
|
|
||||||
private const val SHADER_CIRCLE_MAIN = """vec4 main(vec2 p) {
|
private const val SHADER_CIRCLE_MAIN =
|
||||||
|
"""
|
||||||
|
vec4 main(vec2 p) {
|
||||||
vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
|
vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
|
||||||
float radius = in_size.x * 0.5;
|
float radius = in_size.x * 0.5;
|
||||||
float sparkleRing = soften(circleRing(p_distorted-in_center, radius), in_blur);
|
float sparkleRing = soften(circleRing(p_distorted-in_center, radius), in_blur);
|
||||||
@@ -73,7 +80,9 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
private const val SHADER_ROUNDED_BOX_MAIN = """vec4 main(vec2 p) {
|
private const val SHADER_ROUNDED_BOX_MAIN =
|
||||||
|
"""
|
||||||
|
vec4 main(vec2 p) {
|
||||||
float sparkleRing = soften(roundedBoxRing(p-in_center, in_size, in_cornerRadius,
|
float sparkleRing = soften(roundedBoxRing(p-in_center, in_size, in_cornerRadius,
|
||||||
in_thickness), in_blur);
|
in_thickness), in_blur);
|
||||||
float inside = soften(sdRoundedBox(p-in_center, in_size * 1.2, in_cornerRadius),
|
float inside = soften(sdRoundedBox(p-in_center, in_size * 1.2, in_cornerRadius),
|
||||||
@@ -89,7 +98,9 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
private const val SHADER_ELLIPSE_MAIN = """vec4 main(vec2 p) {
|
private const val SHADER_ELLIPSE_MAIN =
|
||||||
|
"""
|
||||||
|
vec4 main(vec2 p) {
|
||||||
vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
|
vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
|
||||||
|
|
||||||
float sparkleRing = soften(ellipseRing(p_distorted-in_center, in_size), in_blur);
|
float sparkleRing = soften(ellipseRing(p_distorted-in_center, in_size), in_blur);
|
||||||
@@ -105,14 +116,23 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
private const val CIRCLE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB +
|
private const val CIRCLE_SHADER =
|
||||||
SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SdfShaderLibrary.CIRCLE_SDF +
|
SHADER_UNIFORMS +
|
||||||
|
ShaderUtilLibrary.SHADER_LIB +
|
||||||
|
SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
|
||||||
|
SdfShaderLibrary.CIRCLE_SDF +
|
||||||
SHADER_CIRCLE_MAIN
|
SHADER_CIRCLE_MAIN
|
||||||
private const val ROUNDED_BOX_SHADER = SHADER_UNIFORMS +
|
private const val ROUNDED_BOX_SHADER =
|
||||||
RippleShaderUtilLibrary.SHADER_LIB + SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
|
SHADER_UNIFORMS +
|
||||||
SdfShaderLibrary.ROUNDED_BOX_SDF + SHADER_ROUNDED_BOX_MAIN
|
ShaderUtilLibrary.SHADER_LIB +
|
||||||
private const val ELLIPSE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB +
|
SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
|
||||||
SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SdfShaderLibrary.ELLIPSE_SDF +
|
SdfShaderLibrary.ROUNDED_BOX_SDF +
|
||||||
|
SHADER_ROUNDED_BOX_MAIN
|
||||||
|
private const val ELLIPSE_SHADER =
|
||||||
|
SHADER_UNIFORMS +
|
||||||
|
ShaderUtilLibrary.SHADER_LIB +
|
||||||
|
SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
|
||||||
|
SdfShaderLibrary.ELLIPSE_SDF +
|
||||||
SHADER_ELLIPSE_MAIN
|
SHADER_ELLIPSE_MAIN
|
||||||
|
|
||||||
private fun buildShader(rippleShape: RippleShape): String =
|
private fun buildShader(rippleShape: RippleShape): String =
|
||||||
@@ -130,9 +150,7 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Sets the center position of the ripple. */
|
||||||
* Sets the center position of the ripple.
|
|
||||||
*/
|
|
||||||
fun setCenter(x: Float, y: Float) {
|
fun setCenter(x: Float, y: Float) {
|
||||||
setFloatUniform("in_center", x, y)
|
setFloatUniform("in_center", x, y)
|
||||||
}
|
}
|
||||||
@@ -144,21 +162,21 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C
|
|||||||
maxSize.y = height
|
maxSize.y = height
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Progress of the ripple. Float value between [0, 1]. */
|
||||||
* Progress of the ripple. Float value between [0, 1].
|
|
||||||
*/
|
|
||||||
var progress: Float = 0.0f
|
var progress: Float = 0.0f
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
setFloatUniform("in_progress", value)
|
setFloatUniform("in_progress", value)
|
||||||
val curvedProg = 1 - (1 - value) * (1 - value) * (1 - value)
|
val curvedProg = 1 - (1 - value) * (1 - value) * (1 - value)
|
||||||
|
|
||||||
setFloatUniform("in_size", /* width= */ maxSize.x * curvedProg,
|
setFloatUniform(
|
||||||
/* height= */ maxSize.y * curvedProg)
|
"in_size",
|
||||||
|
/* width= */ maxSize.x * curvedProg,
|
||||||
|
/* height= */ maxSize.y * curvedProg
|
||||||
|
)
|
||||||
setFloatUniform("in_thickness", maxSize.y * curvedProg * 0.5f)
|
setFloatUniform("in_thickness", maxSize.y * curvedProg * 0.5f)
|
||||||
// radius should not exceed width and height values.
|
// radius should not exceed width and height values.
|
||||||
setFloatUniform("in_cornerRadius",
|
setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * curvedProg)
|
||||||
Math.min(maxSize.x, maxSize.y) * curvedProg)
|
|
||||||
|
|
||||||
setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value))
|
setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value))
|
||||||
|
|
||||||
@@ -175,18 +193,14 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C
|
|||||||
setFloatUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
|
setFloatUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Play time since the start of the effect. */
|
||||||
* Play time since the start of the effect.
|
|
||||||
*/
|
|
||||||
var time: Float = 0.0f
|
var time: Float = 0.0f
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
setFloatUniform("in_time", value)
|
setFloatUniform("in_time", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** A hex value representing the ripple color, in the format of ARGB */
|
||||||
* A hex value representing the ripple color, in the format of ARGB
|
|
||||||
*/
|
|
||||||
var color: Int = 0xffffff
|
var color: Int = 0xffffff
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
@@ -194,9 +208,9 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Noise sparkle intensity. Expected value between [0, 1]. The sparkle is white, and thus
|
* Noise sparkle intensity. Expected value between [0, 1]. The sparkle is white, and thus with
|
||||||
* with strength 0 it's transparent, leaving the ripple fully smooth, while with strength 1
|
* strength 0 it's transparent, leaving the ripple fully smooth, while with strength 1 it's
|
||||||
* it's opaque white and looks the most grainy.
|
* opaque white and looks the most grainy.
|
||||||
*/
|
*/
|
||||||
var sparkleStrength: Float = 0.0f
|
var sparkleStrength: Float = 0.0f
|
||||||
set(value) {
|
set(value) {
|
||||||
@@ -204,9 +218,7 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C
|
|||||||
setFloatUniform("in_sparkle_strength", value)
|
setFloatUniform("in_sparkle_strength", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Distortion strength of the ripple. Expected value between[0, 1]. */
|
||||||
* Distortion strength of the ripple. Expected value between[0, 1].
|
|
||||||
*/
|
|
||||||
var distortionStrength: Float = 0.0f
|
var distortionStrength: Float = 0.0f
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.android.systemui.ripple
|
package com.android.systemui.surfaceeffects.ripple
|
||||||
|
|
||||||
import android.animation.Animator
|
import android.animation.Animator
|
||||||
import android.animation.AnimatorListenerAdapter
|
import android.animation.AnimatorListenerAdapter
|
||||||
@@ -26,7 +26,7 @@ import android.graphics.Paint
|
|||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.graphics.ColorUtils
|
import androidx.core.graphics.ColorUtils
|
||||||
import com.android.systemui.ripple.RippleShader.RippleShape
|
import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic expanding ripple effect.
|
* A generic expanding ripple effect.
|
||||||
@@ -98,15 +98,18 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a
|
|||||||
rippleShader.time = now.toFloat()
|
rippleShader.time = now.toFloat()
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
animator.addListener(object : AnimatorListenerAdapter() {
|
animator.addListener(
|
||||||
|
object : AnimatorListenerAdapter() {
|
||||||
override fun onAnimationEnd(animation: Animator?) {
|
override fun onAnimationEnd(animation: Animator?) {
|
||||||
onAnimationEnd?.run()
|
onAnimationEnd?.run()
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
animator.start()
|
animator.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Set the color to be used for the ripple.
|
/**
|
||||||
|
* Set the color to be used for the ripple.
|
||||||
*
|
*
|
||||||
* The alpha value of the color will be applied to the ripple. The alpha range is [0-100].
|
* The alpha value of the color will be applied to the ripple. The alpha range is [0-100].
|
||||||
*/
|
*/
|
||||||
@@ -123,9 +126,7 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a
|
|||||||
rippleShader.rippleFill = rippleFill
|
rippleShader.rippleFill = rippleFill
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Set the intensity of the sparkles. */
|
||||||
* Set the intensity of the sparkles.
|
|
||||||
*/
|
|
||||||
fun setSparkleStrength(strength: Float) {
|
fun setSparkleStrength(strength: Float) {
|
||||||
rippleShader.sparkleStrength = strength
|
rippleShader.sparkleStrength = strength
|
||||||
}
|
}
|
||||||
@@ -143,20 +144,30 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a
|
|||||||
// active effect area. Values here should be kept in sync with the animation implementation
|
// active effect area. Values here should be kept in sync with the animation implementation
|
||||||
// in the ripple shader.
|
// in the ripple shader.
|
||||||
if (rippleShape == RippleShape.CIRCLE) {
|
if (rippleShape == RippleShape.CIRCLE) {
|
||||||
val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
|
val maskRadius =
|
||||||
|
(1 -
|
||||||
|
(1 - rippleShader.progress) *
|
||||||
|
(1 - rippleShader.progress) *
|
||||||
(1 - rippleShader.progress)) * maxWidth
|
(1 - rippleShader.progress)) * maxWidth
|
||||||
canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint)
|
canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint)
|
||||||
} else {
|
} else {
|
||||||
val maskWidth = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
|
val maskWidth =
|
||||||
|
(1 -
|
||||||
|
(1 - rippleShader.progress) *
|
||||||
|
(1 - rippleShader.progress) *
|
||||||
(1 - rippleShader.progress)) * maxWidth * 2
|
(1 - rippleShader.progress)) * maxWidth * 2
|
||||||
val maskHeight = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
|
val maskHeight =
|
||||||
|
(1 -
|
||||||
|
(1 - rippleShader.progress) *
|
||||||
|
(1 - rippleShader.progress) *
|
||||||
(1 - rippleShader.progress)) * maxHeight * 2
|
(1 - rippleShader.progress)) * maxHeight * 2
|
||||||
canvas.drawRect(
|
canvas.drawRect(
|
||||||
/* left= */ centerX - maskWidth,
|
/* left= */ centerX - maskWidth,
|
||||||
/* top= */ centerY - maskHeight,
|
/* top= */ centerY - maskHeight,
|
||||||
/* right= */ centerX + maskWidth,
|
/* right= */ centerX + maskWidth,
|
||||||
/* bottom= */ centerY + maskHeight,
|
/* bottom= */ centerY + maskHeight,
|
||||||
ripplePaint)
|
ripplePaint
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,13 +13,14 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.android.systemui.ripple
|
package com.android.systemui.surfaceeffects.shaderutil
|
||||||
|
|
||||||
/** Library class that contains 2D signed distance functions. */
|
/** Library class that contains 2D signed distance functions. */
|
||||||
class SdfShaderLibrary {
|
class SdfShaderLibrary {
|
||||||
// language=AGSL
|
// language=AGSL
|
||||||
companion object {
|
companion object {
|
||||||
const val CIRCLE_SDF = """
|
const val CIRCLE_SDF =
|
||||||
|
"""
|
||||||
float sdCircle(vec2 p, float r) {
|
float sdCircle(vec2 p, float r) {
|
||||||
return (length(p)-r) / r;
|
return (length(p)-r) / r;
|
||||||
}
|
}
|
||||||
@@ -34,7 +35,8 @@ class SdfShaderLibrary {
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
const val ROUNDED_BOX_SDF = """
|
const val ROUNDED_BOX_SDF =
|
||||||
|
"""
|
||||||
float sdRoundedBox(vec2 p, vec2 size, float cornerRadius) {
|
float sdRoundedBox(vec2 p, vec2 size, float cornerRadius) {
|
||||||
size *= 0.5;
|
size *= 0.5;
|
||||||
cornerRadius *= 0.5;
|
cornerRadius *= 0.5;
|
||||||
@@ -58,7 +60,8 @@ class SdfShaderLibrary {
|
|||||||
// Used non-trigonometry parametrization and Halley's method (iterative) for root finding.
|
// Used non-trigonometry parametrization and Halley's method (iterative) for root finding.
|
||||||
// This is more expensive than the regular circle SDF, recommend to use the circle SDF if
|
// This is more expensive than the regular circle SDF, recommend to use the circle SDF if
|
||||||
// possible.
|
// possible.
|
||||||
const val ELLIPSE_SDF = """float sdEllipse(vec2 p, vec2 wh) {
|
const val ELLIPSE_SDF =
|
||||||
|
"""float sdEllipse(vec2 p, vec2 wh) {
|
||||||
wh *= 0.5;
|
wh *= 0.5;
|
||||||
|
|
||||||
// symmetry
|
// symmetry
|
||||||
@@ -98,7 +101,8 @@ class SdfShaderLibrary {
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
const val SHADER_SDF_OPERATION_LIB = """
|
const val SHADER_SDF_OPERATION_LIB =
|
||||||
|
"""
|
||||||
float soften(float d, float blur) {
|
float soften(float d, float blur) {
|
||||||
float blurHalf = blur * 0.5;
|
float blurHalf = blur * 0.5;
|
||||||
return smoothstep(-blurHalf, blurHalf, d);
|
return smoothstep(-blurHalf, blurHalf, d);
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* 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.shaderutil
|
||||||
|
|
||||||
|
/** A common utility functions that are used for computing shaders. */
|
||||||
|
class ShaderUtilLibrary {
|
||||||
|
// language=AGSL
|
||||||
|
companion object {
|
||||||
|
const val SHADER_LIB =
|
||||||
|
"""
|
||||||
|
float triangleNoise(vec2 n) {
|
||||||
|
n = fract(n * vec2(5.3987, 5.4421));
|
||||||
|
n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
|
||||||
|
float xy = n.x * n.y;
|
||||||
|
// compute in [0..2[ and remap to [-1.0..1.0[
|
||||||
|
return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float PI = 3.1415926535897932384626;
|
||||||
|
|
||||||
|
float sparkles(vec2 uv, float t) {
|
||||||
|
float n = triangleNoise(uv);
|
||||||
|
float s = 0.0;
|
||||||
|
for (float i = 0; i < 4; i += 1) {
|
||||||
|
float l = i * 0.01;
|
||||||
|
float h = l + 0.1;
|
||||||
|
float o = smoothstep(n - l, h, n);
|
||||||
|
o *= abs(sin(PI * o * (t + 0.55 * i)));
|
||||||
|
s += o;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 distort(vec2 p, float time, float distort_amount_radial,
|
||||||
|
float distort_amount_xy) {
|
||||||
|
float angle = atan(p.y, p.x);
|
||||||
|
return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
|
||||||
|
cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
|
||||||
|
+ vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
|
||||||
|
cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return range [-1, 1].
|
||||||
|
vec3 hash(vec3 p) {
|
||||||
|
p = fract(p * vec3(.3456, .1234, .9876));
|
||||||
|
p += dot(p, p.yxz + 43.21);
|
||||||
|
p = (p.xxy + p.yxx) * p.zyx;
|
||||||
|
return (fract(sin(p) * 4567.1234567) - .5) * 2.;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skew factors (non-uniform).
|
||||||
|
const float SKEW = 0.3333333; // 1/3
|
||||||
|
const float UNSKEW = 0.1666667; // 1/6
|
||||||
|
|
||||||
|
// Return range roughly [-1,1].
|
||||||
|
// It's because the hash function (that returns a random gradient vector) returns
|
||||||
|
// different magnitude of vectors. Noise doesn't have to be in the precise range thus
|
||||||
|
// skipped normalize.
|
||||||
|
float simplex3d(vec3 p) {
|
||||||
|
// Skew the input coordinate, so that we get squashed cubical grid
|
||||||
|
vec3 s = floor(p + (p.x + p.y + p.z) * SKEW);
|
||||||
|
|
||||||
|
// Unskew back
|
||||||
|
vec3 u = s - (s.x + s.y + s.z) * UNSKEW;
|
||||||
|
|
||||||
|
// Unskewed coordinate that is relative to p, to compute the noise contribution
|
||||||
|
// based on the distance.
|
||||||
|
vec3 c0 = p - u;
|
||||||
|
|
||||||
|
// We have six simplices (in this case tetrahedron, since we are in 3D) that we
|
||||||
|
// could possibly in.
|
||||||
|
// Here, we are finding the correct tetrahedron (simplex shape), and traverse its
|
||||||
|
// four vertices (c0..3) when computing noise contribution.
|
||||||
|
// The way we find them is by comparing c0's x,y,z values.
|
||||||
|
// For example in 2D, we can find the triangle (simplex shape in 2D) that we are in
|
||||||
|
// by comparing x and y values. i.e. x>y lower, x<y, upper triangle.
|
||||||
|
// Same applies in 3D.
|
||||||
|
//
|
||||||
|
// Below indicates the offsets (or offset directions) when c0=(x0,y0,z0)
|
||||||
|
// x0>y0>z0: (1,0,0), (1,1,0), (1,1,1)
|
||||||
|
// x0>z0>y0: (1,0,0), (1,0,1), (1,1,1)
|
||||||
|
// z0>x0>y0: (0,0,1), (1,0,1), (1,1,1)
|
||||||
|
// z0>y0>x0: (0,0,1), (0,1,1), (1,1,1)
|
||||||
|
// y0>z0>x0: (0,1,0), (0,1,1), (1,1,1)
|
||||||
|
// y0>x0>z0: (0,1,0), (1,1,0), (1,1,1)
|
||||||
|
//
|
||||||
|
// The rule is:
|
||||||
|
// * For offset1, set 1 at the max component, otherwise 0.
|
||||||
|
// * For offset2, set 0 at the min component, otherwise 1.
|
||||||
|
// * For offset3, set 1 for all.
|
||||||
|
//
|
||||||
|
// Encode x0-y0, y0-z0, z0-x0 in a vec3
|
||||||
|
vec3 en = c0 - c0.yzx;
|
||||||
|
// Each represents whether x0>y0, y0>z0, z0>x0
|
||||||
|
en = step(vec3(0.), en);
|
||||||
|
// en.zxy encodes z0>x0, x0>y0, y0>x0
|
||||||
|
vec3 offset1 = en * (1. - en.zxy); // find max
|
||||||
|
vec3 offset2 = 1. - en.zxy * (1. - en); // 1-(find min)
|
||||||
|
vec3 offset3 = vec3(1.);
|
||||||
|
|
||||||
|
vec3 c1 = c0 - offset1 + UNSKEW;
|
||||||
|
vec3 c2 = c0 - offset2 + UNSKEW * 2.;
|
||||||
|
vec3 c3 = c0 - offset3 + UNSKEW * 3.;
|
||||||
|
|
||||||
|
// Kernel summation: dot(max(0, r^2-d^2))^4, noise contribution)
|
||||||
|
//
|
||||||
|
// First compute d^2, squared distance to the point.
|
||||||
|
vec4 w; // w = max(0, r^2 - d^2))
|
||||||
|
w.x = dot(c0, c0);
|
||||||
|
w.y = dot(c1, c1);
|
||||||
|
w.z = dot(c2, c2);
|
||||||
|
w.w = dot(c3, c3);
|
||||||
|
|
||||||
|
// Noise contribution should decay to zero before they cross the simplex boundary.
|
||||||
|
// Usually r^2 is 0.5 or 0.6;
|
||||||
|
// 0.5 ensures continuity but 0.6 increases the visual quality for the application
|
||||||
|
// where discontinuity isn't noticeable.
|
||||||
|
w = max(0.6 - w, 0.);
|
||||||
|
|
||||||
|
// Noise contribution from each point.
|
||||||
|
vec4 nc;
|
||||||
|
nc.x = dot(hash(s), c0);
|
||||||
|
nc.y = dot(hash(s + offset1), c1);
|
||||||
|
nc.z = dot(hash(s + offset2), c2);
|
||||||
|
nc.w = dot(hash(s + offset3), c3);
|
||||||
|
|
||||||
|
nc *= w*w*w*w;
|
||||||
|
|
||||||
|
// Add all the noise contributions.
|
||||||
|
// Should multiply by the possible max contribution to adjust the range in [-1,1].
|
||||||
|
return dot(vec4(32.), nc);
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.surfaceeffects.turbulencenoise
|
||||||
|
|
||||||
|
import android.graphics.BlendMode
|
||||||
|
import android.graphics.Color
|
||||||
|
|
||||||
|
/** Turbulence noise animation configuration. */
|
||||||
|
data class TurbulenceNoiseAnimationConfig(
|
||||||
|
/** The number of grids that is used to generate noise. */
|
||||||
|
val gridCount: Float = DEFAULT_NOISE_GRID_COUNT,
|
||||||
|
|
||||||
|
/** Multiplier for the noise luma matte. Increase this for brighter effects. */
|
||||||
|
val luminosityMultiplier: Float = DEFAULT_LUMINOSITY_MULTIPLIER,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Noise move speed variables.
|
||||||
|
*
|
||||||
|
* Its sign determines the direction; magnitude determines the speed. <ul>
|
||||||
|
* ```
|
||||||
|
* <li> [noiseMoveSpeedX] positive: right to left; negative: left to right.
|
||||||
|
* <li> [noiseMoveSpeedY] positive: bottom to top; negative: top to bottom.
|
||||||
|
* <li> [noiseMoveSpeedZ] its sign doesn't matter much, as it moves in Z direction. Use it
|
||||||
|
* to add turbulence in place.
|
||||||
|
* ```
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
val noiseMoveSpeedX: Float = 0f,
|
||||||
|
val noiseMoveSpeedY: Float = 0f,
|
||||||
|
val noiseMoveSpeedZ: Float = DEFAULT_NOISE_SPEED_Z,
|
||||||
|
|
||||||
|
/** Color of the effect. */
|
||||||
|
var color: Int = DEFAULT_COLOR,
|
||||||
|
/** Background color of the effect. */
|
||||||
|
val backgroundColor: Int = DEFAULT_BACKGROUND_COLOR,
|
||||||
|
val opacity: Int = DEFAULT_OPACITY,
|
||||||
|
val width: Float = 0f,
|
||||||
|
val height: Float = 0f,
|
||||||
|
val duration: Float = DEFAULT_NOISE_DURATION_IN_MILLIS,
|
||||||
|
val pixelDensity: Float = 1f,
|
||||||
|
val blendMode: BlendMode = DEFAULT_BLEND_MODE,
|
||||||
|
val onAnimationEnd: Runnable? = null
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
const val DEFAULT_NOISE_DURATION_IN_MILLIS = 7500F
|
||||||
|
const val DEFAULT_LUMINOSITY_MULTIPLIER = 1f
|
||||||
|
const val DEFAULT_NOISE_GRID_COUNT = 1.2f
|
||||||
|
const val DEFAULT_NOISE_SPEED_Z = 0.3f
|
||||||
|
const val DEFAULT_OPACITY = 150 // full opacity is 255.
|
||||||
|
const val DEFAULT_COLOR = Color.WHITE
|
||||||
|
const val DEFAULT_BACKGROUND_COLOR = Color.BLACK
|
||||||
|
val DEFAULT_BLEND_MODE = BlendMode.SRC_OVER
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* 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.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) {
|
||||||
|
turbulenceNoiseView.play(turbulenceNoiseAnimationConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* 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.turbulencenoise
|
||||||
|
|
||||||
|
import android.graphics.RuntimeShader
|
||||||
|
import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
|
||||||
|
import java.lang.Float.max
|
||||||
|
|
||||||
|
/** Shader that renders turbulence simplex noise, with no octave. */
|
||||||
|
class TurbulenceNoiseShader : RuntimeShader(TURBULENCE_NOISE_SHADER) {
|
||||||
|
// language=AGSL
|
||||||
|
companion object {
|
||||||
|
private const val UNIFORMS =
|
||||||
|
"""
|
||||||
|
uniform float in_gridNum;
|
||||||
|
uniform vec3 in_noiseMove;
|
||||||
|
uniform vec2 in_size;
|
||||||
|
uniform float in_aspectRatio;
|
||||||
|
uniform float in_opacity;
|
||||||
|
uniform float in_pixelDensity;
|
||||||
|
layout(color) uniform vec4 in_color;
|
||||||
|
layout(color) uniform vec4 in_backgroundColor;
|
||||||
|
"""
|
||||||
|
|
||||||
|
private const val SHADER_LIB =
|
||||||
|
"""
|
||||||
|
float getLuminosity(vec3 c) {
|
||||||
|
return 0.3*c.r + 0.59*c.g + 0.11*c.b;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 maskLuminosity(vec3 dest, float lum) {
|
||||||
|
dest.rgb *= vec3(lum);
|
||||||
|
// Clip back into the legal range
|
||||||
|
dest = clamp(dest, vec3(0.), vec3(1.0));
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
private const val MAIN_SHADER =
|
||||||
|
"""
|
||||||
|
vec4 main(vec2 p) {
|
||||||
|
vec2 uv = p / in_size.xy;
|
||||||
|
uv.x *= in_aspectRatio;
|
||||||
|
|
||||||
|
vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
|
||||||
|
float luma = simplex3d(noiseP) * in_opacity;
|
||||||
|
vec3 mask = maskLuminosity(in_color.rgb, luma);
|
||||||
|
vec3 color = in_backgroundColor.rgb + mask * 0.6;
|
||||||
|
|
||||||
|
// Add dither with triangle distribution to avoid color banding. Ok to dither in the
|
||||||
|
// shader here as we are in gamma space.
|
||||||
|
float dither = triangleNoise(p * in_pixelDensity) / 255.;
|
||||||
|
|
||||||
|
// The result color should be pre-multiplied, i.e. [R*A, G*A, B*A, A], thus need to
|
||||||
|
// multiply rgb with a to get the correct result.
|
||||||
|
color = (color + dither.rrr) * in_color.a;
|
||||||
|
return vec4(color, in_color.a);
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
private const val TURBULENCE_NOISE_SHADER =
|
||||||
|
ShaderUtilLibrary.SHADER_LIB + UNIFORMS + SHADER_LIB + MAIN_SHADER
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the number of grid for generating noise. */
|
||||||
|
fun setGridCount(gridNumber: Float = 1.0f) {
|
||||||
|
setFloatUniform("in_gridNum", gridNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the pixel density of the screen.
|
||||||
|
*
|
||||||
|
* Used it for noise dithering.
|
||||||
|
*/
|
||||||
|
fun setPixelDensity(pixelDensity: Float) {
|
||||||
|
setFloatUniform("in_pixelDensity", pixelDensity)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the noise color of the effect. */
|
||||||
|
fun setColor(color: Int) {
|
||||||
|
setColorUniform("in_color", color)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the background color of the effect. */
|
||||||
|
fun setBackgroundColor(color: Int) {
|
||||||
|
setColorUniform("in_backgroundColor", color)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the opacity to achieve fade in/ out of the animation.
|
||||||
|
*
|
||||||
|
* Expected value range is [1, 0].
|
||||||
|
*/
|
||||||
|
fun setOpacity(opacity: Float) {
|
||||||
|
setFloatUniform("in_opacity", opacity)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the size of the shader. */
|
||||||
|
fun setSize(width: Float, height: Float) {
|
||||||
|
setFloatUniform("in_size", width, height)
|
||||||
|
setFloatUniform("in_aspectRatio", width / max(height, 0.001f))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets noise move speed in x, y, and z direction. */
|
||||||
|
fun setNoiseMove(x: Float, y: Float, z: Float) {
|
||||||
|
setFloatUniform("in_noiseMove", x, y, z)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* 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.turbulencenoise
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.AnimatorListenerAdapter
|
||||||
|
import android.animation.ValueAnimator
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import androidx.core.graphics.ColorUtils
|
||||||
|
import java.util.Random
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
|
/** View that renders turbulence noise effect. */
|
||||||
|
class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val MS_TO_SEC = 0.001f
|
||||||
|
private const val TWO_PI = Math.PI.toFloat() * 2f
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting val turbulenceNoiseShader = TurbulenceNoiseShader()
|
||||||
|
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
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Only visible during the animation.
|
||||||
|
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
|
||||||
|
// if it's unsupported.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.drawPaint(paint)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun play(config: TurbulenceNoiseAnimationConfig) {
|
||||||
|
if (isPlaying) {
|
||||||
|
return // Ignore if the animation is playing.
|
||||||
|
}
|
||||||
|
visibility = VISIBLE
|
||||||
|
applyConfig(config)
|
||||||
|
|
||||||
|
// Add random offset to avoid same patterned noise.
|
||||||
|
val offsetX = random.nextFloat()
|
||||||
|
val offsetY = random.nextFloat()
|
||||||
|
|
||||||
|
animator.duration = config.duration.toLong()
|
||||||
|
animator.addUpdateListener { updateListener ->
|
||||||
|
val timeInSec = updateListener.currentPlayTime * MS_TO_SEC
|
||||||
|
// Remap [0,1] to [0, 2*PI]
|
||||||
|
val progress = TWO_PI * updateListener.animatedValue as Float
|
||||||
|
|
||||||
|
turbulenceNoiseShader.setNoiseMove(
|
||||||
|
offsetX + timeInSec * config.noiseMoveSpeedX,
|
||||||
|
offsetY + timeInSec * config.noiseMoveSpeedY,
|
||||||
|
timeInSec * config.noiseMoveSpeedZ
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fade in and out the noise as the animation progress.
|
||||||
|
// TODO: replace it with a better curve
|
||||||
|
turbulenceNoiseShader.setOpacity(sin(TWO_PI - progress) * config.luminosityMultiplier)
|
||||||
|
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
animator.addListener(
|
||||||
|
object : AnimatorListenerAdapter() {
|
||||||
|
override fun onAnimationEnd(animation: Animator) {
|
||||||
|
visibility = INVISIBLE
|
||||||
|
config.onAnimationEnd?.run()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
animator.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyConfig(config: TurbulenceNoiseAnimationConfig) {
|
||||||
|
this.config = config
|
||||||
|
with(turbulenceNoiseShader) {
|
||||||
|
setGridCount(config.gridCount)
|
||||||
|
setColor(ColorUtils.setAlphaComponent(config.color, config.opacity))
|
||||||
|
setBackgroundColor(config.backgroundColor)
|
||||||
|
setSize(config.width, config.height)
|
||||||
|
setPixelDensity(config.pixelDensity)
|
||||||
|
}
|
||||||
|
paint.blendMode = config.blendMode
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ import com.android.internal.logging.UiEventLogger
|
|||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.flags.FeatureFlags
|
import com.android.systemui.flags.FeatureFlags
|
||||||
import com.android.systemui.flags.Flags
|
import com.android.systemui.flags.Flags
|
||||||
import com.android.systemui.ripple.RippleView
|
import com.android.systemui.surfaceeffects.ripple.RippleView
|
||||||
import com.android.systemui.statusbar.commandline.CommandRegistry
|
import com.android.systemui.statusbar.commandline.CommandRegistry
|
||||||
import com.android.systemui.statusbar.policy.BatteryController
|
import com.android.systemui.statusbar.policy.BatteryController
|
||||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
import com.android.systemui.statusbar.policy.ConfigurationController
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ import com.android.systemui.SysuiTestCase
|
|||||||
import com.android.systemui.media.controls.models.GutsViewHolder
|
import com.android.systemui.media.controls.models.GutsViewHolder
|
||||||
import com.android.systemui.media.controls.models.player.MediaViewHolder
|
import com.android.systemui.media.controls.models.player.MediaViewHolder
|
||||||
import com.android.systemui.monet.ColorScheme
|
import com.android.systemui.monet.ColorScheme
|
||||||
import com.android.systemui.ripple.MultiRippleController
|
import com.android.systemui.surfaceeffects.ripple.MultiRippleController
|
||||||
|
import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController
|
||||||
import junit.framework.Assert.assertEquals
|
import junit.framework.Assert.assertEquals
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@@ -62,6 +63,7 @@ class ColorSchemeTransitionTest : SysuiTestCase() {
|
|||||||
@Mock private lateinit var mediaViewHolder: MediaViewHolder
|
@Mock private lateinit var mediaViewHolder: MediaViewHolder
|
||||||
@Mock private lateinit var gutsViewHolder: GutsViewHolder
|
@Mock private lateinit var gutsViewHolder: GutsViewHolder
|
||||||
@Mock private lateinit var multiRippleController: MultiRippleController
|
@Mock private lateinit var multiRippleController: MultiRippleController
|
||||||
|
@Mock private lateinit var turbulenceNoiseController: TurbulenceNoiseController
|
||||||
|
|
||||||
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
|
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
|
||||||
|
|
||||||
@@ -76,6 +78,7 @@ class ColorSchemeTransitionTest : SysuiTestCase() {
|
|||||||
context,
|
context,
|
||||||
mediaViewHolder,
|
mediaViewHolder,
|
||||||
multiRippleController,
|
multiRippleController,
|
||||||
|
turbulenceNoiseController,
|
||||||
animatingColorTransitionFactory
|
animatingColorTransitionFactory
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -78,9 +78,10 @@ import com.android.systemui.media.controls.util.MediaUiEventLogger
|
|||||||
import com.android.systemui.media.dialog.MediaOutputDialogFactory
|
import com.android.systemui.media.dialog.MediaOutputDialogFactory
|
||||||
import com.android.systemui.plugins.ActivityStarter
|
import com.android.systemui.plugins.ActivityStarter
|
||||||
import com.android.systemui.plugins.FalsingManager
|
import com.android.systemui.plugins.FalsingManager
|
||||||
import com.android.systemui.ripple.MultiRippleView
|
|
||||||
import com.android.systemui.statusbar.NotificationLockscreenUserManager
|
import com.android.systemui.statusbar.NotificationLockscreenUserManager
|
||||||
import com.android.systemui.statusbar.policy.KeyguardStateController
|
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.animation.TransitionLayout
|
||||||
import com.android.systemui.util.concurrency.FakeExecutor
|
import com.android.systemui.util.concurrency.FakeExecutor
|
||||||
import com.android.systemui.util.mockito.KotlinArgumentCaptor
|
import com.android.systemui.util.mockito.KotlinArgumentCaptor
|
||||||
@@ -178,6 +179,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
private lateinit var dismiss: FrameLayout
|
private lateinit var dismiss: FrameLayout
|
||||||
private lateinit var dismissText: TextView
|
private lateinit var dismissText: TextView
|
||||||
private lateinit var multiRippleView: MultiRippleView
|
private lateinit var multiRippleView: MultiRippleView
|
||||||
|
private lateinit var turbulenceNoiseView: TurbulenceNoiseView
|
||||||
|
|
||||||
private lateinit var session: MediaSession
|
private lateinit var session: MediaSession
|
||||||
private lateinit var device: MediaDeviceData
|
private lateinit var device: MediaDeviceData
|
||||||
@@ -382,6 +384,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
multiRippleView = MultiRippleView(context, null)
|
multiRippleView = MultiRippleView(context, null)
|
||||||
|
turbulenceNoiseView = TurbulenceNoiseView(context, null)
|
||||||
|
|
||||||
whenever(viewHolder.player).thenReturn(view)
|
whenever(viewHolder.player).thenReturn(view)
|
||||||
whenever(viewHolder.appIcon).thenReturn(appIcon)
|
whenever(viewHolder.appIcon).thenReturn(appIcon)
|
||||||
@@ -425,6 +428,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
whenever(viewHolder.actionsTopBarrier).thenReturn(actionsTopBarrier)
|
whenever(viewHolder.actionsTopBarrier).thenReturn(actionsTopBarrier)
|
||||||
|
|
||||||
whenever(viewHolder.multiRippleView).thenReturn(multiRippleView)
|
whenever(viewHolder.multiRippleView).thenReturn(multiRippleView)
|
||||||
|
whenever(viewHolder.turbulenceNoiseView).thenReturn(turbulenceNoiseView)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Initialize elements for the recommendation view holder */
|
/** Initialize elements for the recommendation view holder */
|
||||||
|
|||||||
@@ -14,13 +14,13 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.android.systemui.ripple
|
package com.android.systemui.surfaceeffects.ripple
|
||||||
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.ripple.MultiRippleController.Companion.MAX_RIPPLE_NUMBER
|
import com.android.systemui.surfaceeffects.ripple.MultiRippleController.Companion.MAX_RIPPLE_NUMBER
|
||||||
import com.android.systemui.util.concurrency.FakeExecutor
|
import com.android.systemui.util.concurrency.FakeExecutor
|
||||||
import com.android.systemui.util.time.FakeSystemClock
|
import com.android.systemui.util.time.FakeSystemClock
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.android.systemui.ripple
|
package com.android.systemui.surfaceeffects.ripple
|
||||||
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.android.systemui.ripple
|
package com.android.systemui.surfaceeffects.ripple
|
||||||
|
|
||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
@@ -21,12 +21,10 @@ import com.android.systemui.SysuiTestCase
|
|||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.mockito.Mock
|
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@RunWith(AndroidTestingRunner::class)
|
@RunWith(AndroidTestingRunner::class)
|
||||||
class RippleViewTest : SysuiTestCase() {
|
class RippleViewTest : SysuiTestCase() {
|
||||||
@Mock
|
|
||||||
private lateinit var rippleView: RippleView
|
private lateinit var rippleView: RippleView
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* 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.turbulencenoise
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
|
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 TurbulenceNoiseControllerTest : SysuiTestCase() {
|
||||||
|
private val fakeSystemClock = FakeSystemClock()
|
||||||
|
// FakeExecutor is needed to run animator.
|
||||||
|
private val fakeExecutor = FakeExecutor(fakeSystemClock)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun play_playsTurbulenceNoise() {
|
||||||
|
val config = TurbulenceNoiseAnimationConfig(duration = 1000f)
|
||||||
|
val turbulenceNoiseView = TurbulenceNoiseView(context, null)
|
||||||
|
|
||||||
|
val turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
|
||||||
|
|
||||||
|
fakeExecutor.execute {
|
||||||
|
turbulenceNoiseController.play(config)
|
||||||
|
|
||||||
|
assertThat(turbulenceNoiseView.isPlaying).isTrue()
|
||||||
|
|
||||||
|
fakeSystemClock.advanceTime(config.duration.toLong())
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* 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.turbulencenoise
|
||||||
|
|
||||||
|
import android.testing.AndroidTestingRunner
|
||||||
|
import android.view.View
|
||||||
|
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 TurbulenceNoiseViewTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
private val fakeSystemClock = FakeSystemClock()
|
||||||
|
// FakeExecutor is needed to run animator.
|
||||||
|
private val fakeExecutor = FakeExecutor(fakeSystemClock)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun play_viewHasCorrectVisibility() {
|
||||||
|
val config = TurbulenceNoiseAnimationConfig(duration = 1000f)
|
||||||
|
val turbulenceNoiseView = TurbulenceNoiseView(context, null)
|
||||||
|
|
||||||
|
assertThat(turbulenceNoiseView.visibility).isEqualTo(View.INVISIBLE)
|
||||||
|
|
||||||
|
fakeExecutor.execute {
|
||||||
|
turbulenceNoiseView.play(config)
|
||||||
|
|
||||||
|
assertThat(turbulenceNoiseView.visibility).isEqualTo(View.VISIBLE)
|
||||||
|
|
||||||
|
fakeSystemClock.advanceTime(config.duration.toLong())
|
||||||
|
|
||||||
|
assertThat(turbulenceNoiseView.visibility).isEqualTo(View.INVISIBLE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun play_playsAnimation() {
|
||||||
|
val config = TurbulenceNoiseAnimationConfig(duration = 1000f)
|
||||||
|
val turbulenceNoiseView = TurbulenceNoiseView(context, null)
|
||||||
|
|
||||||
|
fakeExecutor.execute {
|
||||||
|
turbulenceNoiseView.play(config)
|
||||||
|
|
||||||
|
assertThat(turbulenceNoiseView.isPlaying).isTrue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun play_onEnd_triggersOnAnimationEnd() {
|
||||||
|
var animationEnd = false
|
||||||
|
val config =
|
||||||
|
TurbulenceNoiseAnimationConfig(
|
||||||
|
duration = 1000f,
|
||||||
|
onAnimationEnd = { animationEnd = true }
|
||||||
|
)
|
||||||
|
val turbulenceNoiseView = TurbulenceNoiseView(context, null)
|
||||||
|
|
||||||
|
fakeExecutor.execute {
|
||||||
|
turbulenceNoiseView.play(config)
|
||||||
|
|
||||||
|
assertThat(turbulenceNoiseView.isPlaying).isTrue()
|
||||||
|
|
||||||
|
fakeSystemClock.advanceTime(config.duration.toLong())
|
||||||
|
|
||||||
|
assertThat(animationEnd).isTrue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user