summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt59
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