diff options
4 files changed, 65 insertions, 69 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt index 77b418670ca6..3196eba7d33a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt @@ -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() @@ -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 } @@ -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() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt index 2900462c6924..71af271bc214 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt @@ -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 @@ -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 /*** @@ -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) @@ -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() } } } @@ -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() { @@ -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) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 8ed9cd66aed1..7c70edfc1208 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1226,7 +1226,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( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt index 9ce72414d751..5e783a53a1e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt @@ -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 @@ -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 @@ -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 |