Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9919e3b7 authored by Yein Jo's avatar Yein Jo Committed by Automerger Merge Worker
Browse files

Merge "Fix RippleFinished being called on first draw." into tm-qpr-dev am:...

Merge "Fix RippleFinished being called on first draw." into tm-qpr-dev am: 801a4b98 am: 3f7a9f81

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20665370



Change-Id: Ic01e80f6c0409861c55ecae4d27b9144ba2e4c7e
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents c7353295 3f7a9f81
Loading
Loading
Loading
Loading
+18 −2
Original line number Diff line number Diff line
@@ -21,9 +21,20 @@ import androidx.annotation.VisibleForTesting
/** Controller that handles playing [RippleAnimation]. */
class MultiRippleController(private val multipleRippleView: MultiRippleView) {

    private val ripplesFinishedListeners = ArrayList<RipplesFinishedListener>()

    companion object {
        /** Max number of ripple animations at a time. */
        @VisibleForTesting const val MAX_RIPPLE_NUMBER = 10

        interface RipplesFinishedListener {
            /** Triggered when all the ripples finish running. */
            fun onRipplesFinish()
        }
    }

    fun addRipplesFinishedListener(listener: RipplesFinishedListener) {
        ripplesFinishedListeners.add(listener)
    }

    /** Updates all the ripple colors during the animation. */
@@ -38,8 +49,13 @@ class MultiRippleController(private val multipleRippleView: MultiRippleView) {

        multipleRippleView.ripples.add(rippleAnimation)

        rippleAnimation.play {
            // Remove ripple once the animation is done
        rippleAnimation.play { multipleRippleView.ripples.remove(rippleAnimation) }
            multipleRippleView.ripples.remove(rippleAnimation)
            if (multipleRippleView.ripples.isEmpty()) {
                ripplesFinishedListeners.forEach { listener -> listener.onRipplesFinish() }
            }
        }

        // Trigger drawing
        multipleRippleView.invalidate()
+0 −12
Original line number Diff line number Diff line
@@ -33,21 +33,11 @@ class MultiRippleView(context: Context?, attrs: AttributeSet?) : View(context, a

    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
    val ripples = ArrayList<RippleAnimation>()
    private val listeners = ArrayList<RipplesFinishedListener>()
    private val ripplePaint = Paint()
    private var isWarningLogged = false

    companion object {
        private const val TAG = "MultiRippleView"

        interface RipplesFinishedListener {
            /** Triggered when all the ripples finish running. */
            fun onRipplesFinish()
        }
    }

    fun addRipplesFinishedListener(listener: RipplesFinishedListener) {
        listeners.add(listener)
    }

    override fun onDraw(canvas: Canvas?) {
@@ -76,8 +66,6 @@ class MultiRippleView(context: Context?, attrs: AttributeSet?) : View(context, a

        if (shouldInvalidate) {
            invalidate()
        } else { // Nothing is playing.
            listeners.forEach { listener -> listener.onRipplesFinish() }
        }
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -404,7 +404,7 @@ public class MediaControlPanel {
        MultiRippleView multiRippleView = vh.getMultiRippleView();
        mMultiRippleController = new MultiRippleController(multiRippleView);
        mTurbulenceNoiseController = new TurbulenceNoiseController(vh.getTurbulenceNoiseView());
        multiRippleView.addRipplesFinishedListener(
        mMultiRippleController.addRipplesFinishedListener(
                () -> {
                    if (mTurbulenceNoiseAnimationConfig == null) {
                        mTurbulenceNoiseAnimationConfig = createLingeringNoiseAnimation();
+48 −0
Original line number Diff line number Diff line
@@ -101,4 +101,52 @@ class MultiRippleControllerTest : SysuiTestCase() {
            assertThat(multiRippleView.ripples.size).isEqualTo(0)
        }
    }

    @Test
    fun play_onFinishesAllRipples_triggersRipplesFinished() {
        var isTriggered = false
        val listener =
            object : MultiRippleController.Companion.RipplesFinishedListener {
                override fun onRipplesFinish() {
                    isTriggered = true
                }
            }
        multiRippleController.addRipplesFinishedListener(listener)

        fakeExecutor.execute {
            multiRippleController.play(RippleAnimation(RippleAnimationConfig(duration = 1000)))
            multiRippleController.play(RippleAnimation(RippleAnimationConfig(duration = 2000)))

            assertThat(multiRippleView.ripples.size).isEqualTo(2)

            fakeSystemClock.advanceTime(2000L)

            assertThat(multiRippleView.ripples.size).isEqualTo(0)
            assertThat(isTriggered).isTrue()
        }
    }

    @Test
    fun play_notAllRipplesFinished_doesNotTriggerRipplesFinished() {
        var isTriggered = false
        val listener =
            object : MultiRippleController.Companion.RipplesFinishedListener {
                override fun onRipplesFinish() {
                    isTriggered = true
                }
            }
        multiRippleController.addRipplesFinishedListener(listener)

        fakeExecutor.execute {
            multiRippleController.play(RippleAnimation(RippleAnimationConfig(duration = 1000)))
            multiRippleController.play(RippleAnimation(RippleAnimationConfig(duration = 2000)))

            assertThat(multiRippleView.ripples.size).isEqualTo(2)

            fakeSystemClock.advanceTime(1000L)

            assertThat(multiRippleView.ripples.size).isEqualTo(1)
            assertThat(isTriggered).isFalse()
        }
    }
}
+0 −58
Original line number Diff line number Diff line
/*
 * 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()
        }
    }
}