Loading packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt +4 −2 Original line number Diff line number Diff line Loading @@ -33,11 +33,11 @@ private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f * Expanding ripple effect that shows when charging begins. */ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { private var rippleInProgress: Boolean = false private val rippleShader = RippleShader() private val defaultColor: Int = 0xffffffff.toInt() private val ripplePaint = Paint() var rippleInProgress: Boolean = false var radius: Float = 0.0f set(value) { rippleShader.radius = value } var origin: PointF = PointF() Loading @@ -62,7 +62,8 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context super.onAttachedToWindow() } fun startRipple() { @JvmOverloads fun startRipple(onAnimationEnd: Runnable? = null) { if (rippleInProgress) { return // Ignore if ripple effect is already playing } Loading @@ -80,6 +81,7 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context override fun onAnimationEnd(animation: Animator?) { rippleInProgress = false visibility = View.GONE onAnimationEnd?.run() } }) animator.start() Loading packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt +36 −32 Original line number Diff line number Diff line Loading @@ -17,11 +17,11 @@ package com.android.systemui.statusbar.charging import android.content.Context import android.content.res.Configuration import android.graphics.PixelFormat import android.graphics.PointF import android.util.DisplayMetrics import android.view.View import android.view.ViewGroupOverlay import android.view.WindowManager import com.android.internal.annotations.VisibleForTesting import com.android.settingslib.Utils import com.android.systemui.dagger.SysUISingleton Loading @@ -30,9 +30,7 @@ import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import java.io.PrintWriter import java.lang.Integer.max import javax.inject.Inject /*** Loading @@ -45,11 +43,22 @@ class WiredChargingRippleController @Inject constructor( batteryController: BatteryController, configurationController: ConfigurationController, featureFlags: FeatureFlags, private val context: Context, private val keyguardStateController: KeyguardStateController private val context: Context ) { private var charging: Boolean? = null 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 var rippleView: ChargingRippleView = ChargingRippleView(context, attrs = null) Loading @@ -68,8 +77,8 @@ class WiredChargingRippleController @Inject constructor( val wasCharging = charging charging = nowCharging // Only triggers when the keyguard is active and the device is just plugged in. if (wasCharging == false && nowCharging && keyguardStateController.isShowing) { rippleView.startRipple() if ((wasCharging == null || !wasCharging) && nowCharging) { startRipple() } } } Loading @@ -85,46 +94,41 @@ class WiredChargingRippleController @Inject constructor( override fun onOverlayChanged() { updateRippleColor() } override fun onConfigChanged(newConfig: Configuration?) { layoutRippleView() } } configurationController.addCallback(configurationChangedListener) commandRegistry.registerCommand("charging-ripple") { ChargingRippleCommand() } updateRippleColor() } fun setViewHost(viewHost: View) { // Add the ripple view as an overlay of the root view so that it always // shows on top. viewHost.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { fun startRipple() { if (rippleView.rippleInProgress) { return } 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 onViewAttachedToWindow(view: View?) { (viewHost.viewRootImpl.view.overlay as ViewGroupOverlay).add(rippleView) layoutRippleView() viewHost.removeOnAttachStateChangeListener(this) layoutRipple() rippleView.startRipple(Runnable { mWM.removeView(rippleView) }) rippleView.removeOnAttachStateChangeListener(this) } }) updateRippleColor() mWM.addView(rippleView, windowLayoutParams) } private fun layoutRippleView() { // Overlays are not auto measured and laid out so we do it manually here. private fun layoutRipple() { // TODO(shanh): Set origin base on phone orientation. val displayMetrics = DisplayMetrics() context.display.getRealMetrics(displayMetrics) val width = displayMetrics.widthPixels val height = displayMetrics.heightPixels if (width != rippleView.width || height != rippleView.height) { rippleView.apply { 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() } } rippleView.origin = PointF(width / 2f, height.toFloat()) rippleView.radius = Integer.max(width, height).toFloat() } private fun updateRippleColor() { Loading @@ -134,7 +138,7 @@ class WiredChargingRippleController @Inject constructor( inner class ChargingRippleCommand : Command { override fun execute(pw: PrintWriter, args: List<String>) { rippleView.startRipple() startRipple() } override fun help(pw: PrintWriter) { Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +0 −1 Original line number Diff line number Diff line Loading @@ -1228,7 +1228,6 @@ public class StatusBar extends SystemUI implements DemoMode, mScrimController.attachViews(scrimBehind, scrimInFront, scrimForBubble); mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim); mChargingRippleAnimationController.setViewHost(mNotificationShadeWindowView); updateLightRevealScrimVisibility(); mNotificationPanelViewController.initDependencies( Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt +25 −34 Original line number Diff line number Diff line Loading @@ -16,17 +16,17 @@ package com.android.systemui.statusbar.charging import android.content.Context import android.testing.AndroidTestingRunner import android.view.View import android.view.ViewGroupOverlay import android.view.ViewRootImpl import android.view.WindowManager import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.FeatureFlags import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.statusbar.policy.BatteryController 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.Test import org.junit.runner.RunWith Loading @@ -34,7 +34,8 @@ import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers import org.mockito.Mock 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.verify import org.mockito.MockitoAnnotations Loading @@ -47,55 +48,45 @@ class WiredChargingRippleControllerTest : SysuiTestCase() { @Mock private lateinit var batteryController: BatteryController @Mock private lateinit var featureFlags: FeatureFlags @Mock private lateinit var configurationController: ConfigurationController @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var rippleView: ChargingRippleView @Mock private lateinit var viewHost: View @Mock private lateinit var viewHostRootImpl: ViewRootImpl @Mock private lateinit var viewGroupOverlay: ViewGroupOverlay @Mock private lateinit var windowManager: WindowManager @Before fun setUp() { MockitoAnnotations.initMocks(this) `when`(viewHost.viewRootImpl).thenReturn(viewHostRootImpl) `when`(viewHostRootImpl.view).thenReturn(viewHost) `when`(viewHost.overlay).thenReturn(viewGroupOverlay) `when`(featureFlags.isChargingRippleEnabled).thenReturn(true) `when`(keyguardStateController.isShowing).thenReturn(true) controller = WiredChargingRippleController( commandRegistry, batteryController, configurationController, featureFlags, context, keyguardStateController) featureFlags, context) controller.rippleView = rippleView // Replace the real ripple view with a mock instance controller.setViewHost(viewHost) context.addMockSystemService(Context.WINDOW_SERVICE, windowManager) } @Test fun testSetRippleViewAsOverlay() { 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() { fun testTriggerRipple_UnlockedState() { val captor = ArgumentCaptor .forClass(BatteryController.BatteryStateChangeCallback::class.java) verify(batteryController).addCallback(captor.capture()) val unusedBatteryLevel = 0 // Verify ripple added to window manager. captor.value.onBatteryLevelChanged( unusedBatteryLevel, false /* plugged in */, false /* charging */) verify(rippleView, never()).startRipple() captor.value.onBatteryLevelChanged( unusedBatteryLevel, 0 /* unusedBatteryLevel */, false /* plugged in */, 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 Loading Loading
packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt +4 −2 Original line number Diff line number Diff line Loading @@ -33,11 +33,11 @@ private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f * Expanding ripple effect that shows when charging begins. */ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { private var rippleInProgress: Boolean = false private val rippleShader = RippleShader() private val defaultColor: Int = 0xffffffff.toInt() private val ripplePaint = Paint() var rippleInProgress: Boolean = false var radius: Float = 0.0f set(value) { rippleShader.radius = value } var origin: PointF = PointF() Loading @@ -62,7 +62,8 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context super.onAttachedToWindow() } fun startRipple() { @JvmOverloads fun startRipple(onAnimationEnd: Runnable? = null) { if (rippleInProgress) { return // Ignore if ripple effect is already playing } Loading @@ -80,6 +81,7 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context override fun onAnimationEnd(animation: Animator?) { rippleInProgress = false visibility = View.GONE onAnimationEnd?.run() } }) animator.start() Loading
packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt +36 −32 Original line number Diff line number Diff line Loading @@ -17,11 +17,11 @@ package com.android.systemui.statusbar.charging import android.content.Context import android.content.res.Configuration import android.graphics.PixelFormat import android.graphics.PointF import android.util.DisplayMetrics import android.view.View import android.view.ViewGroupOverlay import android.view.WindowManager import com.android.internal.annotations.VisibleForTesting import com.android.settingslib.Utils import com.android.systemui.dagger.SysUISingleton Loading @@ -30,9 +30,7 @@ import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import java.io.PrintWriter import java.lang.Integer.max import javax.inject.Inject /*** Loading @@ -45,11 +43,22 @@ class WiredChargingRippleController @Inject constructor( batteryController: BatteryController, configurationController: ConfigurationController, featureFlags: FeatureFlags, private val context: Context, private val keyguardStateController: KeyguardStateController private val context: Context ) { private var charging: Boolean? = null 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 var rippleView: ChargingRippleView = ChargingRippleView(context, attrs = null) Loading @@ -68,8 +77,8 @@ class WiredChargingRippleController @Inject constructor( val wasCharging = charging charging = nowCharging // Only triggers when the keyguard is active and the device is just plugged in. if (wasCharging == false && nowCharging && keyguardStateController.isShowing) { rippleView.startRipple() if ((wasCharging == null || !wasCharging) && nowCharging) { startRipple() } } } Loading @@ -85,46 +94,41 @@ class WiredChargingRippleController @Inject constructor( override fun onOverlayChanged() { updateRippleColor() } override fun onConfigChanged(newConfig: Configuration?) { layoutRippleView() } } configurationController.addCallback(configurationChangedListener) commandRegistry.registerCommand("charging-ripple") { ChargingRippleCommand() } updateRippleColor() } fun setViewHost(viewHost: View) { // Add the ripple view as an overlay of the root view so that it always // shows on top. viewHost.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { fun startRipple() { if (rippleView.rippleInProgress) { return } 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 onViewAttachedToWindow(view: View?) { (viewHost.viewRootImpl.view.overlay as ViewGroupOverlay).add(rippleView) layoutRippleView() viewHost.removeOnAttachStateChangeListener(this) layoutRipple() rippleView.startRipple(Runnable { mWM.removeView(rippleView) }) rippleView.removeOnAttachStateChangeListener(this) } }) updateRippleColor() mWM.addView(rippleView, windowLayoutParams) } private fun layoutRippleView() { // Overlays are not auto measured and laid out so we do it manually here. private fun layoutRipple() { // TODO(shanh): Set origin base on phone orientation. val displayMetrics = DisplayMetrics() context.display.getRealMetrics(displayMetrics) val width = displayMetrics.widthPixels val height = displayMetrics.heightPixels if (width != rippleView.width || height != rippleView.height) { rippleView.apply { 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() } } rippleView.origin = PointF(width / 2f, height.toFloat()) rippleView.radius = Integer.max(width, height).toFloat() } private fun updateRippleColor() { Loading @@ -134,7 +138,7 @@ class WiredChargingRippleController @Inject constructor( inner class ChargingRippleCommand : Command { override fun execute(pw: PrintWriter, args: List<String>) { rippleView.startRipple() startRipple() } override fun help(pw: PrintWriter) { Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +0 −1 Original line number Diff line number Diff line Loading @@ -1228,7 +1228,6 @@ public class StatusBar extends SystemUI implements DemoMode, mScrimController.attachViews(scrimBehind, scrimInFront, scrimForBubble); mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim); mChargingRippleAnimationController.setViewHost(mNotificationShadeWindowView); updateLightRevealScrimVisibility(); mNotificationPanelViewController.initDependencies( Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt +25 −34 Original line number Diff line number Diff line Loading @@ -16,17 +16,17 @@ package com.android.systemui.statusbar.charging import android.content.Context import android.testing.AndroidTestingRunner import android.view.View import android.view.ViewGroupOverlay import android.view.ViewRootImpl import android.view.WindowManager import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.FeatureFlags import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.statusbar.policy.BatteryController 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.Test import org.junit.runner.RunWith Loading @@ -34,7 +34,8 @@ import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers import org.mockito.Mock 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.verify import org.mockito.MockitoAnnotations Loading @@ -47,55 +48,45 @@ class WiredChargingRippleControllerTest : SysuiTestCase() { @Mock private lateinit var batteryController: BatteryController @Mock private lateinit var featureFlags: FeatureFlags @Mock private lateinit var configurationController: ConfigurationController @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var rippleView: ChargingRippleView @Mock private lateinit var viewHost: View @Mock private lateinit var viewHostRootImpl: ViewRootImpl @Mock private lateinit var viewGroupOverlay: ViewGroupOverlay @Mock private lateinit var windowManager: WindowManager @Before fun setUp() { MockitoAnnotations.initMocks(this) `when`(viewHost.viewRootImpl).thenReturn(viewHostRootImpl) `when`(viewHostRootImpl.view).thenReturn(viewHost) `when`(viewHost.overlay).thenReturn(viewGroupOverlay) `when`(featureFlags.isChargingRippleEnabled).thenReturn(true) `when`(keyguardStateController.isShowing).thenReturn(true) controller = WiredChargingRippleController( commandRegistry, batteryController, configurationController, featureFlags, context, keyguardStateController) featureFlags, context) controller.rippleView = rippleView // Replace the real ripple view with a mock instance controller.setViewHost(viewHost) context.addMockSystemService(Context.WINDOW_SERVICE, windowManager) } @Test fun testSetRippleViewAsOverlay() { 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() { fun testTriggerRipple_UnlockedState() { val captor = ArgumentCaptor .forClass(BatteryController.BatteryStateChangeCallback::class.java) verify(batteryController).addCallback(captor.capture()) val unusedBatteryLevel = 0 // Verify ripple added to window manager. captor.value.onBatteryLevelChanged( unusedBatteryLevel, false /* plugged in */, false /* charging */) verify(rippleView, never()).startRipple() captor.value.onBatteryLevelChanged( unusedBatteryLevel, 0 /* unusedBatteryLevel */, false /* plugged in */, 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 Loading