diff options
5 files changed, 175 insertions, 79 deletions
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/PaintDrawCallback.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/PaintDrawCallback.kt new file mode 100644 index 000000000000..d50979ccd01d --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/PaintDrawCallback.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 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 + +import android.graphics.Paint +import android.graphics.RenderEffect + +/** + * A callback with a [Paint] object that contains shader info, which is triggered every frame while + * animation is playing. Note that the [Paint] object here is always the same instance. + * + * This approach is more performant than other ones because [RenderEffect] forces an intermediate + * render pass of the View to a texture to feed into it. + * + * The usage of this callback is as follows: + * <pre>{@code + * private var paint: Paint? = null + * // Override [View.onDraw]. + * override fun onDraw(canvas: Canvas) { + * // RuntimeShader requires hardwareAcceleration. + * if (!canvas.isHardwareAccelerated) return + * + * paint?.let { canvas.drawPaint(it) } + * } + * + * // Given that this is called [PaintDrawCallback.onDraw] + * fun draw(paint: Paint) { + * this.paint = paint + * + * // Must call invalidate to trigger View#onDraw + * invalidate() + * } + * }</pre> + * + * Please refer to [RenderEffectDrawCallback] for alternative approach. + */ +interface PaintDrawCallback { + fun onDraw(paint: Paint) +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/RenderEffectDrawCallback.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/RenderEffectDrawCallback.kt new file mode 100644 index 000000000000..db7ee58090a9 --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/RenderEffectDrawCallback.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 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 + +import android.graphics.RenderEffect + +/** + * A callback with a [RenderEffect] object that contains shader info, which is triggered every frame + * while animation is playing. Note that the [RenderEffect] instance is different each time to + * update shader uniforms. + * + * The usage of this callback is as follows: + * <pre>{@code + * private val xEffectDrawingCallback = RenderEffectDrawCallback() { + * val myOtherRenderEffect = createOtherRenderEffect() + * val chainEffect = RenderEffect.createChainEffect(renderEffect, myOtherRenderEffect) + * myView.setRenderEffect(chainEffect) + * } + * + * private val xEffect = XEffect(config, xEffectDrawingCallback) + * }</pre> + */ +interface RenderEffectDrawCallback { + fun onDraw(renderEffect: RenderEffect) +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt index 1c763e8c6108..211b84f25369 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt @@ -22,6 +22,8 @@ import android.animation.ValueAnimator import android.graphics.Paint import android.graphics.RenderEffect import android.view.View +import com.android.systemui.surfaceeffects.PaintDrawCallback +import com.android.systemui.surfaceeffects.RenderEffectDrawCallback import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader @@ -334,52 +336,31 @@ private constructor( ) } - companion object { + /** + * States of the loading effect animation. + * + * <p>The state is designed to be follow the order below: [AnimationState.EASE_IN], + * [AnimationState.MAIN], [AnimationState.EASE_OUT]. Note that ease in and out don't necessarily + * mean the acceleration and deceleration in the animation curve. They simply mean each stage of + * the animation. (i.e. Intro, core, and rest) + */ + enum class AnimationState { + EASE_IN, + MAIN, + EASE_OUT, + NOT_PLAYING + } + + /** Optional callback that is triggered when the animation state changes. */ + interface AnimationStateChangedCallback { /** - * States of the loading effect animation. - * - * <p>The state is designed to be follow the order below: [AnimationState.EASE_IN], - * [AnimationState.MAIN], [AnimationState.EASE_OUT]. Note that ease in and out don't - * necessarily mean the acceleration and deceleration in the animation curve. They simply - * mean each stage of the animation. (i.e. Intro, core, and rest) + * A callback that's triggered when the [AnimationState] changes. Example usage is + * performing a cleanup when [AnimationState] becomes [NOT_PLAYING]. */ - enum class AnimationState { - EASE_IN, - MAIN, - EASE_OUT, - NOT_PLAYING - } - - /** Client must implement one of the draw callbacks. */ - interface PaintDrawCallback { - /** - * A callback with a [Paint] object that contains shader info, which is triggered every - * frame while animation is playing. Note that the [Paint] object here is always the - * same instance. - */ - fun onDraw(loadingPaint: Paint) - } - - interface RenderEffectDrawCallback { - /** - * A callback with a [RenderEffect] object that contains shader info, which is triggered - * every frame while animation is playing. Note that the [RenderEffect] instance is - * different each time to update shader uniforms. - */ - fun onDraw(loadingRenderEffect: RenderEffect) - } - - /** Optional callback that is triggered when the animation state changes. */ - interface AnimationStateChangedCallback { - /** - * A callback that's triggered when the [AnimationState] changes. Example usage is - * performing a cleanup when [AnimationState] becomes [NOT_PLAYING]. - */ - fun onStateChanged(oldState: AnimationState, newState: AnimationState) {} - } + fun onStateChanged(oldState: AnimationState, newState: AnimationState) {} + } + private companion object { private const val MS_TO_SEC = 0.001f - - private val TAG = LoadingEffect::class.java.simpleName } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java index 899b9ed103cd..ca898e675f53 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java @@ -118,8 +118,9 @@ import com.android.systemui.res.R; 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.PaintDrawCallback; import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect; -import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState; +import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.AnimationState; import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView; import com.android.systemui.surfaceeffects.ripple.MultiRippleController; import com.android.systemui.surfaceeffects.ripple.MultiRippleView; @@ -264,15 +265,15 @@ public class MediaControlPanel { private boolean mWasPlaying = false; private boolean mButtonClicked = false; - private final LoadingEffect.Companion.PaintDrawCallback mNoiseDrawCallback = - new LoadingEffect.Companion.PaintDrawCallback() { + private final PaintDrawCallback mNoiseDrawCallback = + new PaintDrawCallback() { @Override - public void onDraw(@NonNull Paint loadingPaint) { - mMediaViewHolder.getLoadingEffectView().draw(loadingPaint); + public void onDraw(@NonNull Paint paint) { + mMediaViewHolder.getLoadingEffectView().draw(paint); } }; - private final LoadingEffect.Companion.AnimationStateChangedCallback mStateChangedCallback = - new LoadingEffect.Companion.AnimationStateChangedCallback() { + private final LoadingEffect.AnimationStateChangedCallback mStateChangedCallback = + new LoadingEffect.AnimationStateChangedCallback() { @Override public void onStateChanged(@NonNull AnimationState oldState, @NonNull AnimationState newState) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt index 7a83cfe852d6..6f589418cf1e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt @@ -23,14 +23,8 @@ import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.animation.AnimatorTestRule import com.android.systemui.model.SysUiStateTest -import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState -import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState.EASE_IN -import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState.EASE_OUT -import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState.MAIN -import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState.NOT_PLAYING -import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationStateChangedCallback -import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.PaintDrawCallback -import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.RenderEffectDrawCallback +import com.android.systemui.surfaceeffects.PaintDrawCallback +import com.android.systemui.surfaceeffects.RenderEffectDrawCallback import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader import com.google.common.truth.Truth.assertThat @@ -50,8 +44,8 @@ class LoadingEffectTest : SysUiStateTest() { var paintFromCallback: Paint? = null val drawCallback = object : PaintDrawCallback { - override fun onDraw(loadingPaint: Paint) { - paintFromCallback = loadingPaint + override fun onDraw(paint: Paint) { + paintFromCallback = paint } } val loadingEffect = @@ -75,8 +69,8 @@ class LoadingEffectTest : SysUiStateTest() { var renderEffectFromCallback: RenderEffect? = null val drawCallback = object : RenderEffectDrawCallback { - override fun onDraw(loadingRenderEffect: RenderEffect) { - renderEffectFromCallback = loadingRenderEffect + override fun onDraw(renderEffect: RenderEffect) { + renderEffectFromCallback = renderEffect } } val loadingEffect = @@ -98,16 +92,19 @@ class LoadingEffectTest : SysUiStateTest() { @Test fun play_animationStateChangesInOrder() { val config = TurbulenceNoiseAnimationConfig() - val states = mutableListOf(NOT_PLAYING) + val states = mutableListOf(LoadingEffect.AnimationState.NOT_PLAYING) val stateChangedCallback = - object : AnimationStateChangedCallback { - override fun onStateChanged(oldState: AnimationState, newState: AnimationState) { + object : LoadingEffect.AnimationStateChangedCallback { + override fun onStateChanged( + oldState: LoadingEffect.AnimationState, + newState: LoadingEffect.AnimationState + ) { states.add(newState) } } val drawCallback = object : PaintDrawCallback { - override fun onDraw(loadingPaint: Paint) {} + override fun onDraw(paint: Paint) {} } val loadingEffect = LoadingEffect( @@ -125,7 +122,14 @@ class LoadingEffectTest : SysUiStateTest() { animatorTestRule.advanceTimeBy(config.easeOutDuration.toLong()) animatorTestRule.advanceTimeBy(500) - assertThat(states).containsExactly(NOT_PLAYING, EASE_IN, MAIN, EASE_OUT, NOT_PLAYING) + assertThat(states) + .containsExactly( + LoadingEffect.AnimationState.NOT_PLAYING, + LoadingEffect.AnimationState.EASE_IN, + LoadingEffect.AnimationState.MAIN, + LoadingEffect.AnimationState.EASE_OUT, + LoadingEffect.AnimationState.NOT_PLAYING + ) } @Test @@ -133,16 +137,22 @@ class LoadingEffectTest : SysUiStateTest() { val config = TurbulenceNoiseAnimationConfig() var numPlay = 0 val stateChangedCallback = - object : AnimationStateChangedCallback { - override fun onStateChanged(oldState: AnimationState, newState: AnimationState) { - if (oldState == NOT_PLAYING && newState == EASE_IN) { + object : LoadingEffect.AnimationStateChangedCallback { + override fun onStateChanged( + oldState: LoadingEffect.AnimationState, + newState: LoadingEffect.AnimationState + ) { + if ( + oldState == LoadingEffect.AnimationState.NOT_PLAYING && + newState == LoadingEffect.AnimationState.EASE_IN + ) { numPlay++ } } } val drawCallback = object : PaintDrawCallback { - override fun onDraw(loadingPaint: Paint) {} + override fun onDraw(paint: Paint) {} } val loadingEffect = LoadingEffect( @@ -172,9 +182,15 @@ class LoadingEffectTest : SysUiStateTest() { } var isFinished = false val stateChangedCallback = - object : AnimationStateChangedCallback { - override fun onStateChanged(oldState: AnimationState, newState: AnimationState) { - if (oldState == EASE_OUT && newState == NOT_PLAYING) { + object : LoadingEffect.AnimationStateChangedCallback { + override fun onStateChanged( + oldState: LoadingEffect.AnimationState, + newState: LoadingEffect.AnimationState + ) { + if ( + oldState == LoadingEffect.AnimationState.EASE_OUT && + newState == LoadingEffect.AnimationState.NOT_PLAYING + ) { isFinished = true } } @@ -205,13 +221,19 @@ class LoadingEffectTest : SysUiStateTest() { val config = TurbulenceNoiseAnimationConfig(maxDuration = 1000f) val drawCallback = object : PaintDrawCallback { - override fun onDraw(loadingPaint: Paint) {} + override fun onDraw(paint: Paint) {} } var isFinished = false val stateChangedCallback = - object : AnimationStateChangedCallback { - override fun onStateChanged(oldState: AnimationState, newState: AnimationState) { - if (oldState == MAIN && newState == NOT_PLAYING) { + object : LoadingEffect.AnimationStateChangedCallback { + override fun onStateChanged( + oldState: LoadingEffect.AnimationState, + newState: LoadingEffect.AnimationState + ) { + if ( + oldState == LoadingEffect.AnimationState.MAIN && + newState == LoadingEffect.AnimationState.NOT_PLAYING + ) { isFinished = true } } @@ -242,7 +264,7 @@ class LoadingEffectTest : SysUiStateTest() { ) val drawCallback = object : PaintDrawCallback { - override fun onDraw(loadingPaint: Paint) {} + override fun onDraw(paint: Paint) {} } val loadingEffect = LoadingEffect( |