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

Commit 68ebf467 authored by Yein Jo's avatar Yein Jo
Browse files

Fix RippleFinished being called on first draw.

Bug: 261492904
Test: MultiRippleControllerTest
Change-Id: I6d56a901f898485578ae5acf6d8cae24a269652d
parent 71d3dbf2
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()
        }
    }
}