1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.charging
18 
19 import android.graphics.Rect
20 import android.view.Surface
21 import android.view.View
22 import android.view.WindowManager
23 import android.view.WindowMetrics
24 import androidx.test.ext.junit.runners.AndroidJUnit4
25 import androidx.test.filters.SmallTest
26 import com.android.app.viewcapture.ViewCapture
27 import com.android.app.viewcapture.ViewCaptureAwareWindowManager
28 import com.android.internal.logging.UiEventLogger
29 import com.android.systemui.SysuiTestCase
30 import com.android.systemui.flags.FeatureFlags
31 import com.android.systemui.flags.Flags
32 import com.android.systemui.res.R
33 import com.android.systemui.statusbar.commandline.CommandRegistry
34 import com.android.systemui.statusbar.policy.BatteryController
35 import com.android.systemui.statusbar.policy.ConfigurationController
36 import com.android.systemui.surfaceeffects.ripple.RippleView
37 import com.android.systemui.util.mockito.whenever
38 import com.android.systemui.util.time.FakeSystemClock
39 import org.junit.Before
40 import org.junit.Test
41 import org.junit.runner.RunWith
42 import org.mockito.ArgumentCaptor
43 import org.mockito.ArgumentMatchers
44 import org.mockito.Mock
45 import org.mockito.Mockito.any
46 import org.mockito.Mockito.eq
47 import org.mockito.Mockito.never
48 import org.mockito.Mockito.reset
49 import org.mockito.Mockito.verify
50 import org.mockito.Mockito.`when`
51 import org.mockito.MockitoAnnotations
52 
53 @SmallTest
54 @RunWith(AndroidJUnit4::class)
55 class WiredChargingRippleControllerTest : SysuiTestCase() {
56     private lateinit var controller: WiredChargingRippleController
57     @Mock private lateinit var commandRegistry: CommandRegistry
58     @Mock private lateinit var batteryController: BatteryController
59     @Mock private lateinit var featureFlags: FeatureFlags
60     @Mock private lateinit var configurationController: ConfigurationController
61     @Mock private lateinit var rippleView: RippleView
62     @Mock private lateinit var windowManager: WindowManager
63     @Mock private lateinit var uiEventLogger: UiEventLogger
64     @Mock private lateinit var windowMetrics: WindowMetrics
65     @Mock private lateinit var lazyViewCapture: Lazy<ViewCapture>
66     private val systemClock = FakeSystemClock()
67 
68     @Before
setUpnull69     fun setUp() {
70         MockitoAnnotations.initMocks(this)
71         `when`(featureFlags.isEnabled(Flags.CHARGING_RIPPLE)).thenReturn(true)
72         controller = WiredChargingRippleController(
73                 commandRegistry, batteryController, configurationController,
74                 featureFlags, context, windowManager,
75                 ViewCaptureAwareWindowManager(windowManager,
76                         lazyViewCapture, isViewCaptureEnabled = false), systemClock, uiEventLogger)
77         rippleView.setupShader()
78         controller.rippleView = rippleView // Replace the real ripple view with a mock instance
79         controller.registerCallbacks()
80 
81         `when`(windowMetrics.bounds).thenReturn(Rect(0, 0, 100, 100))
82         `when`(windowManager.currentWindowMetrics).thenReturn(windowMetrics)
83     }
84 
85     @Test
testTriggerRipple_UnlockedStatenull86     fun testTriggerRipple_UnlockedState() {
87         val captor = ArgumentCaptor
88                 .forClass(BatteryController.BatteryStateChangeCallback::class.java)
89         verify(batteryController).addCallback(captor.capture())
90 
91         // Verify ripple added to window manager.
92         captor.value.onBatteryLevelChanged(
93                 /* unusedBatteryLevel= */ 0,
94                 /* plugged in= */ true,
95                 /* charging= */ false)
96         val attachListenerCaptor =
97                 ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
98         verify(rippleView).addOnAttachStateChangeListener(attachListenerCaptor.capture())
99         verify(windowManager).addView(eq(rippleView), any<WindowManager.LayoutParams>())
100 
101         // Verify ripple started
102         val runnableCaptor =
103                 ArgumentCaptor.forClass(Runnable::class.java)
104         attachListenerCaptor.value.onViewAttachedToWindow(rippleView)
105         verify(rippleView).startRipple(runnableCaptor.capture())
106 
107         // Verify ripple removed
108         runnableCaptor.value.run()
109         verify(windowManager).removeView(rippleView)
110 
111         // Verify event logged
112         verify(uiEventLogger).log(
113                 WiredChargingRippleController.WiredChargingRippleEvent.CHARGING_RIPPLE_PLAYED)
114     }
115 
116     @Test
testUpdateRippleColornull117     fun testUpdateRippleColor() {
118         val captor = ArgumentCaptor
119                 .forClass(ConfigurationController.ConfigurationListener::class.java)
120         verify(configurationController).addCallback(captor.capture())
121 
122         reset(rippleView)
123         captor.value.onThemeChanged()
124         verify(rippleView).setColor(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())
125 
126         reset(rippleView)
127         captor.value.onUiModeChanged()
128         verify(rippleView).setColor(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())
129     }
130 
131     @Test
testDebounceRipplenull132     fun testDebounceRipple() {
133         var time: Long = 0
134         systemClock.setElapsedRealtime(time)
135 
136         controller.startRippleWithDebounce()
137         verify(rippleView).addOnAttachStateChangeListener(ArgumentMatchers.any())
138 
139         reset(rippleView)
140         // Wait a short while and trigger.
141         time += 100
142         systemClock.setElapsedRealtime(time)
143         controller.startRippleWithDebounce()
144 
145         // Verify the ripple is debounced.
146         verify(rippleView, never()).addOnAttachStateChangeListener(ArgumentMatchers.any())
147 
148         // Trigger many times.
149         for (i in 0..100) {
150             time += 100
151             systemClock.setElapsedRealtime(time)
152             controller.startRippleWithDebounce()
153         }
154         // Verify all attempts are debounced.
155         verify(rippleView, never()).addOnAttachStateChangeListener(ArgumentMatchers.any())
156 
157         // Wait a long while and trigger.
158         systemClock.setElapsedRealtime(time + 500000)
159         controller.startRippleWithDebounce()
160         // Verify that ripple is triggered.
161         verify(rippleView).addOnAttachStateChangeListener(ArgumentMatchers.any())
162     }
163 
164     @Test
testRipple_whenDocked_doesNotPlayRipplenull165     fun testRipple_whenDocked_doesNotPlayRipple() {
166         `when`(batteryController.isChargingSourceDock).thenReturn(true)
167         val captor = ArgumentCaptor
168                 .forClass(BatteryController.BatteryStateChangeCallback::class.java)
169         verify(batteryController).addCallback(captor.capture())
170 
171         captor.value.onBatteryLevelChanged(
172                 /* unusedBatteryLevel= */ 0,
173                 /* plugged in= */ true,
174                 /* charging= */ false)
175 
176         val attachListenerCaptor =
177                 ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
178         verify(rippleView, never()).addOnAttachStateChangeListener(attachListenerCaptor.capture())
179         verify(windowManager, never()).addView(eq(rippleView), any<WindowManager.LayoutParams>())
180     }
181 
182     @Test
testRipple_layoutsCorrectlynull183     fun testRipple_layoutsCorrectly() {
184         // Sets the correct ripple size.
185         val width = 100
186         val height = 200
187         whenever(windowMetrics.bounds).thenReturn(Rect(0, 0, width, height))
188 
189         // Trigger ripple.
190         val captor = ArgumentCaptor
191                 .forClass(BatteryController.BatteryStateChangeCallback::class.java)
192         verify(batteryController).addCallback(captor.capture())
193 
194         captor.value.onBatteryLevelChanged(
195                 /* unusedBatteryLevel= */ 0,
196                 /* plugged in= */ true,
197                 /* charging= */ false)
198 
199         val attachListenerCaptor =
200                 ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
201         verify(rippleView).addOnAttachStateChangeListener(attachListenerCaptor.capture())
202         verify(windowManager).addView(eq(rippleView), any<WindowManager.LayoutParams>())
203 
204         val runnableCaptor =
205                 ArgumentCaptor.forClass(Runnable::class.java)
206         attachListenerCaptor.value.onViewAttachedToWindow(rippleView)
207         verify(rippleView).startRipple(runnableCaptor.capture())
208 
209         // Verify size and center position.
210         val maxSize = 400f // Double the max value between width and height.
211         verify(rippleView).setMaxSize(maxWidth = maxSize, maxHeight = maxSize)
212 
213         val normalizedPortPosX =
214                 context.resources.getFloat(R.dimen.physical_charger_port_location_normalized_x)
215         val normalizedPortPosY =
216                 context.resources.getFloat(R.dimen.physical_charger_port_location_normalized_y)
217         val expectedCenterX: Float
218         val expectedCenterY: Float
219         when (checkNotNull(context.display).rotation) {
220             Surface.ROTATION_90 -> {
221                 expectedCenterX = width * normalizedPortPosY
222                 expectedCenterY = height * (1 - normalizedPortPosX)
223             }
224             Surface.ROTATION_180 -> {
225                 expectedCenterX = width * (1 - normalizedPortPosX)
226                 expectedCenterY = height * (1 - normalizedPortPosY)
227             }
228             Surface.ROTATION_270 -> {
229                 expectedCenterX = width * (1 - normalizedPortPosY)
230                 expectedCenterY = height * normalizedPortPosX
231             }
232             else -> { // Surface.ROTATION_0
233                 expectedCenterX = width * normalizedPortPosX
234                 expectedCenterY = height * normalizedPortPosY
235             }
236         }
237 
238         verify(rippleView).setCenter(expectedCenterX, expectedCenterY)
239     }
240 }
241