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

Commit c57bf4cf authored by Shan Huang's avatar Shan Huang Committed by Automerger Merge Worker
Browse files

Merge "Show ripple in a window when phone is unlocked." into sc-dev am: ef198391

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

Change-Id: I8f7d2f661c07c73baa708dc10792f9fedafe2293
parents 1f459d57 ef198391
Loading
Loading
Loading
Loading
+4 −2
Original line number Original line Diff line number Diff line
@@ -33,11 +33,11 @@ private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
 * Expanding ripple effect that shows when charging begins.
 * Expanding ripple effect that shows when charging begins.
 */
 */
class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
    private var rippleInProgress: Boolean = false
    private val rippleShader = RippleShader()
    private val rippleShader = RippleShader()
    private val defaultColor: Int = 0xffffffff.toInt()
    private val defaultColor: Int = 0xffffffff.toInt()
    private val ripplePaint = Paint()
    private val ripplePaint = Paint()


    var rippleInProgress: Boolean = false
    var radius: Float = 0.0f
    var radius: Float = 0.0f
        set(value) { rippleShader.radius = value }
        set(value) { rippleShader.radius = value }
    var origin: PointF = PointF()
    var origin: PointF = PointF()
@@ -62,7 +62,8 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context
        super.onAttachedToWindow()
        super.onAttachedToWindow()
    }
    }


    fun startRipple() {
    @JvmOverloads
    fun startRipple(onAnimationEnd: Runnable? = null) {
        if (rippleInProgress) {
        if (rippleInProgress) {
            return // Ignore if ripple effect is already playing
            return // Ignore if ripple effect is already playing
        }
        }
@@ -80,6 +81,7 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context
            override fun onAnimationEnd(animation: Animator?) {
            override fun onAnimationEnd(animation: Animator?) {
                rippleInProgress = false
                rippleInProgress = false
                visibility = View.GONE
                visibility = View.GONE
                onAnimationEnd?.run()
            }
            }
        })
        })
        animator.start()
        animator.start()
+36 −32
Original line number Original line Diff line number Diff line
@@ -17,11 +17,11 @@
package com.android.systemui.statusbar.charging
package com.android.systemui.statusbar.charging


import android.content.Context
import android.content.Context
import android.content.res.Configuration
import android.graphics.PixelFormat
import android.graphics.PointF
import android.graphics.PointF
import android.util.DisplayMetrics
import android.util.DisplayMetrics
import android.view.View
import android.view.View
import android.view.ViewGroupOverlay
import android.view.WindowManager
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.annotations.VisibleForTesting
import com.android.settingslib.Utils
import com.android.settingslib.Utils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.SysUISingleton
@@ -30,9 +30,7 @@ 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
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import java.io.PrintWriter
import java.io.PrintWriter
import java.lang.Integer.max
import javax.inject.Inject
import javax.inject.Inject


/***
/***
@@ -45,11 +43,22 @@ class WiredChargingRippleController @Inject constructor(
    batteryController: BatteryController,
    batteryController: BatteryController,
    configurationController: ConfigurationController,
    configurationController: ConfigurationController,
    featureFlags: FeatureFlags,
    featureFlags: FeatureFlags,
    private val context: Context,
    private val context: Context
    private val keyguardStateController: KeyguardStateController
) {
) {
    private var charging: Boolean? = null
    private var charging: Boolean? = null
    private val rippleEnabled: Boolean = featureFlags.isChargingRippleEnabled
    private val rippleEnabled: Boolean = featureFlags.isChargingRippleEnabled
    private val windowLayoutParams = WindowManager.LayoutParams().apply {
        width = WindowManager.LayoutParams.MATCH_PARENT
        height = WindowManager.LayoutParams.MATCH_PARENT
        layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
        format = PixelFormat.TRANSLUCENT
        type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY
        fitInsetsTypes = 0 // Ignore insets from all system bars
        title = "Wired Charging Animation"
        flags = (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
    }

    @VisibleForTesting
    @VisibleForTesting
    var rippleView: ChargingRippleView = ChargingRippleView(context, attrs = null)
    var rippleView: ChargingRippleView = ChargingRippleView(context, attrs = null)


@@ -68,8 +77,8 @@ class WiredChargingRippleController @Inject constructor(
                val wasCharging = charging
                val wasCharging = charging
                charging = nowCharging
                charging = nowCharging
                // Only triggers when the keyguard is active and the device is just plugged in.
                // Only triggers when the keyguard is active and the device is just plugged in.
                if (wasCharging == false && nowCharging && keyguardStateController.isShowing) {
                if ((wasCharging == null || !wasCharging) && nowCharging) {
                    rippleView.startRipple()
                    startRipple()
                }
                }
            }
            }
        }
        }
@@ -85,46 +94,41 @@ class WiredChargingRippleController @Inject constructor(
            override fun onOverlayChanged() {
            override fun onOverlayChanged() {
                updateRippleColor()
                updateRippleColor()
            }
            }
            override fun onConfigChanged(newConfig: Configuration?) {
                layoutRippleView()
            }
        }
        }
        configurationController.addCallback(configurationChangedListener)
        configurationController.addCallback(configurationChangedListener)


        commandRegistry.registerCommand("charging-ripple") { ChargingRippleCommand() }
        commandRegistry.registerCommand("charging-ripple") { ChargingRippleCommand() }
        updateRippleColor()
    }
    }


    fun setViewHost(viewHost: View) {
    fun startRipple() {
        // Add the ripple view as an overlay of the root view so that it always
        if (rippleView.rippleInProgress) {
        // shows on top.
            return
        viewHost.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
        }
        val mWM = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
        windowLayoutParams.packageName = context.opPackageName
        rippleView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
            override fun onViewDetachedFromWindow(view: View?) {}
            override fun onViewDetachedFromWindow(view: View?) {}


            override fun onViewAttachedToWindow(view: View?) {
            override fun onViewAttachedToWindow(view: View?) {
                (viewHost.viewRootImpl.view.overlay as ViewGroupOverlay).add(rippleView)
                layoutRipple()
                layoutRippleView()
                rippleView.startRipple(Runnable {
                viewHost.removeOnAttachStateChangeListener(this)
                    mWM.removeView(rippleView)
                })
                rippleView.removeOnAttachStateChangeListener(this)
            }
            }
        })
        })

        mWM.addView(rippleView, windowLayoutParams)
        updateRippleColor()
    }
    }


    private fun layoutRippleView() {
    private fun layoutRipple() {
        // Overlays are not auto measured and laid out so we do it manually here.
        // TODO(shanh): Set origin base on phone orientation.
        val displayMetrics = DisplayMetrics()
        val displayMetrics = DisplayMetrics()
        context.display.getRealMetrics(displayMetrics)
        context.display.getRealMetrics(displayMetrics)
        val width = displayMetrics.widthPixels
        val width = displayMetrics.widthPixels
        val height = displayMetrics.heightPixels
        val height = displayMetrics.heightPixels
        if (width != rippleView.width || height != rippleView.height) {
        rippleView.origin = PointF(width / 2f, height.toFloat())
            rippleView.apply {
        rippleView.radius = Integer.max(width, height).toFloat()
                measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
                        View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY))
                layout(0, 0, width, height)
                origin = PointF(width / 2f, height.toFloat())
                radius = max(width, height).toFloat()
            }
        }
    }
    }


    private fun updateRippleColor() {
    private fun updateRippleColor() {
@@ -134,7 +138,7 @@ class WiredChargingRippleController @Inject constructor(


    inner class ChargingRippleCommand : Command {
    inner class ChargingRippleCommand : Command {
        override fun execute(pw: PrintWriter, args: List<String>) {
        override fun execute(pw: PrintWriter, args: List<String>) {
            rippleView.startRipple()
            startRipple()
        }
        }


        override fun help(pw: PrintWriter) {
        override fun help(pw: PrintWriter) {
+0 −1
Original line number Original line Diff line number Diff line
@@ -1228,7 +1228,6 @@ public class StatusBar extends SystemUI implements DemoMode,
        mScrimController.attachViews(scrimBehind, scrimInFront, scrimForBubble);
        mScrimController.attachViews(scrimBehind, scrimInFront, scrimForBubble);


        mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim);
        mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim);
        mChargingRippleAnimationController.setViewHost(mNotificationShadeWindowView);
        updateLightRevealScrimVisibility();
        updateLightRevealScrimVisibility();


        mNotificationPanelViewController.initDependencies(
        mNotificationPanelViewController.initDependencies(
+25 −34
Original line number Original line Diff line number Diff line
@@ -16,17 +16,17 @@


package com.android.systemui.statusbar.charging
package com.android.systemui.statusbar.charging


import android.content.Context
import android.testing.AndroidTestingRunner
import android.testing.AndroidTestingRunner
import android.view.View
import android.view.View
import android.view.ViewGroupOverlay
import android.view.WindowManager
import android.view.ViewRootImpl
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.FeatureFlags
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
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.capture
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
@@ -34,7 +34,8 @@ import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers
import org.mockito.Mock
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
import org.mockito.Mockito.any
import org.mockito.Mockito.eq
import org.mockito.Mockito.reset
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.MockitoAnnotations
@@ -47,55 +48,45 @@ class WiredChargingRippleControllerTest : SysuiTestCase() {
    @Mock private lateinit var batteryController: BatteryController
    @Mock private lateinit var batteryController: BatteryController
    @Mock private lateinit var featureFlags: FeatureFlags
    @Mock private lateinit var featureFlags: FeatureFlags
    @Mock private lateinit var configurationController: ConfigurationController
    @Mock private lateinit var configurationController: ConfigurationController
    @Mock private lateinit var keyguardStateController: KeyguardStateController
    @Mock private lateinit var rippleView: ChargingRippleView
    @Mock private lateinit var rippleView: ChargingRippleView
    @Mock private lateinit var viewHost: View
    @Mock private lateinit var windowManager: WindowManager
    @Mock private lateinit var viewHostRootImpl: ViewRootImpl
    @Mock private lateinit var viewGroupOverlay: ViewGroupOverlay


    @Before
    @Before
    fun setUp() {
    fun setUp() {
        MockitoAnnotations.initMocks(this)
        MockitoAnnotations.initMocks(this)
        `when`(viewHost.viewRootImpl).thenReturn(viewHostRootImpl)
        `when`(viewHostRootImpl.view).thenReturn(viewHost)
        `when`(viewHost.overlay).thenReturn(viewGroupOverlay)
        `when`(featureFlags.isChargingRippleEnabled).thenReturn(true)
        `when`(featureFlags.isChargingRippleEnabled).thenReturn(true)
        `when`(keyguardStateController.isShowing).thenReturn(true)
        controller = WiredChargingRippleController(
        controller = WiredChargingRippleController(
                commandRegistry, batteryController, configurationController,
                commandRegistry, batteryController, configurationController,
                featureFlags, context, keyguardStateController)
                featureFlags, context)
        controller.rippleView = rippleView // Replace the real ripple view with a mock instance
        controller.rippleView = rippleView // Replace the real ripple view with a mock instance
        controller.setViewHost(viewHost)
        context.addMockSystemService(Context.WINDOW_SERVICE, windowManager)
    }
    }


    @Test
    @Test
    fun testSetRippleViewAsOverlay() {
    fun testTriggerRipple_UnlockedState() {
        val listenerCaptor = ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
        verify(viewHost).addOnAttachStateChangeListener(listenerCaptor.capture())

        // Fake attach to window
        listenerCaptor.value.onViewAttachedToWindow(viewHost)
        verify(viewGroupOverlay).add(rippleView)
    }

    @Test
    fun testTriggerRipple() {
        val captor = ArgumentCaptor
        val captor = ArgumentCaptor
                .forClass(BatteryController.BatteryStateChangeCallback::class.java)
                .forClass(BatteryController.BatteryStateChangeCallback::class.java)
        verify(batteryController).addCallback(captor.capture())
        verify(batteryController).addCallback(captor.capture())


        val unusedBatteryLevel = 0
        // Verify ripple added to window manager.
        captor.value.onBatteryLevelChanged(
        captor.value.onBatteryLevelChanged(
                unusedBatteryLevel,
                0 /* unusedBatteryLevel */,
                false /* plugged in */,
                false /* charging */)
        verify(rippleView, never()).startRipple()

        captor.value.onBatteryLevelChanged(
                unusedBatteryLevel,
                false /* plugged in */,
                false /* plugged in */,
                true /* charging */)
                true /* charging */)
        verify(rippleView).startRipple()
        val attachListenerCaptor =
                ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
        verify(rippleView).addOnAttachStateChangeListener(attachListenerCaptor.capture())
        verify(windowManager).addView(eq(rippleView), any<WindowManager.LayoutParams>())

        // Verify ripple started
        val runnableCaptor =
                ArgumentCaptor.forClass(Runnable::class.java)
        attachListenerCaptor.value.onViewAttachedToWindow(rippleView)
        verify(rippleView).startRipple(runnableCaptor.capture())

        // Verify ripple removed
        runnableCaptor.value.run()
        verify(windowManager).removeView(rippleView)
    }
    }


    @Test
    @Test