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.internal.logging.UiEventLogger
27 import com.android.systemui.res.R
28 import com.android.systemui.SysuiTestCase
29 import com.android.systemui.flags.FeatureFlags
30 import com.android.systemui.flags.Flags
31 import com.android.systemui.statusbar.commandline.CommandRegistry
32 import com.android.systemui.statusbar.policy.BatteryController
33 import com.android.systemui.statusbar.policy.ConfigurationController
34 import com.android.systemui.surfaceeffects.ripple.RippleView
35 import com.android.systemui.util.mockito.whenever
36 import com.android.systemui.util.time.FakeSystemClock
37 import org.junit.Before
38 import org.junit.Test
39 import org.junit.runner.RunWith
40 import org.mockito.ArgumentCaptor
41 import org.mockito.ArgumentMatchers
42 import org.mockito.Mock
43 import org.mockito.Mockito.`when`
44 import org.mockito.Mockito.any
45 import org.mockito.Mockito.eq
46 import org.mockito.Mockito.never
47 import org.mockito.Mockito.reset
48 import org.mockito.Mockito.verify
49 import org.mockito.MockitoAnnotations
50 
51 @SmallTest
52 @RunWith(AndroidJUnit4::class)
53 class WiredChargingRippleControllerTest : SysuiTestCase() {
54     private lateinit var controller: WiredChargingRippleController
55     @Mock private lateinit var commandRegistry: CommandRegistry
56     @Mock private lateinit var batteryController: BatteryController
57     @Mock private lateinit var featureFlags: FeatureFlags
58     @Mock private lateinit var configurationController: ConfigurationController
59     @Mock private lateinit var rippleView: RippleView
60     @Mock private lateinit var windowManager: WindowManager
61     @Mock private lateinit var uiEventLogger: UiEventLogger
62     @Mock private lateinit var windowMetrics: WindowMetrics
63     private val systemClock = FakeSystemClock()
64 
65     @Before
setUpnull66     fun setUp() {
67         MockitoAnnotations.initMocks(this)
68         `when`(featureFlags.isEnabled(Flags.CHARGING_RIPPLE)).thenReturn(true)
69         controller = WiredChargingRippleController(
70                 commandRegistry, batteryController, configurationController,
71                 featureFlags, context, windowManager, systemClock, uiEventLogger)
72         rippleView.setupShader()
73         controller.rippleView = rippleView // Replace the real ripple view with a mock instance
74         controller.registerCallbacks()
75 
76         `when`(windowMetrics.bounds).thenReturn(Rect(0, 0, 100, 100))
77         `when`(windowManager.currentWindowMetrics).thenReturn(windowMetrics)
78     }
79 
80     @Test
testTriggerRipple_UnlockedStatenull81     fun testTriggerRipple_UnlockedState() {
82         val captor = ArgumentCaptor
83                 .forClass(BatteryController.BatteryStateChangeCallback::class.java)
84         verify(batteryController).addCallback(captor.capture())
85 
86         // Verify ripple added to window manager.
87         captor.value.onBatteryLevelChanged(
88                 /* unusedBatteryLevel= */ 0,
89                 /* plugged in= */ true,
90                 /* charging= */ false)
91         val attachListenerCaptor =
92                 ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
93         verify(rippleView).addOnAttachStateChangeListener(attachListenerCaptor.capture())
94         verify(windowManager).addView(eq(rippleView), any<WindowManager.LayoutParams>())
95 
96         // Verify ripple started
97         val runnableCaptor =
98                 ArgumentCaptor.forClass(Runnable::class.java)
99         attachListenerCaptor.value.onViewAttachedToWindow(rippleView)
100         verify(rippleView).startRipple(runnableCaptor.capture())
101 
102         // Verify ripple removed
103         runnableCaptor.value.run()
104         verify(windowManager).removeView(rippleView)
105 
106         // Verify event logged
107         verify(uiEventLogger).log(
108                 WiredChargingRippleController.WiredChargingRippleEvent.CHARGING_RIPPLE_PLAYED)
109     }
110 
111     @Test
testUpdateRippleColornull112     fun testUpdateRippleColor() {
113         val captor = ArgumentCaptor
114                 .forClass(ConfigurationController.ConfigurationListener::class.java)
115         verify(configurationController).addCallback(captor.capture())
116 
117         reset(rippleView)
118         captor.value.onThemeChanged()
119         verify(rippleView).setColor(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())
120 
121         reset(rippleView)
122         captor.value.onUiModeChanged()
123         verify(rippleView).setColor(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())
124     }
125 
126     @Test
testDebounceRipplenull127     fun testDebounceRipple() {
128         var time: Long = 0
129         systemClock.setElapsedRealtime(time)
130 
131         controller.startRippleWithDebounce()
132         verify(rippleView).addOnAttachStateChangeListener(ArgumentMatchers.any())
133 
134         reset(rippleView)
135         // Wait a short while and trigger.
136         time += 100
137         systemClock.setElapsedRealtime(time)
138         controller.startRippleWithDebounce()
139 
140         // Verify the ripple is debounced.
141         verify(rippleView, never()).addOnAttachStateChangeListener(ArgumentMatchers.any())
142 
143         // Trigger many times.
144         for (i in 0..100) {
145             time += 100
146             systemClock.setElapsedRealtime(time)
147             controller.startRippleWithDebounce()
148         }
149         // Verify all attempts are debounced.
150         verify(rippleView, never()).addOnAttachStateChangeListener(ArgumentMatchers.any())
151 
152         // Wait a long while and trigger.
153         systemClock.setElapsedRealtime(time + 500000)
154         controller.startRippleWithDebounce()
155         // Verify that ripple is triggered.
156         verify(rippleView).addOnAttachStateChangeListener(ArgumentMatchers.any())
157     }
158 
159     @Test
testRipple_whenDocked_doesNotPlayRipplenull160     fun testRipple_whenDocked_doesNotPlayRipple() {
161         `when`(batteryController.isChargingSourceDock).thenReturn(true)
162         val captor = ArgumentCaptor
163                 .forClass(BatteryController.BatteryStateChangeCallback::class.java)
164         verify(batteryController).addCallback(captor.capture())
165 
166         captor.value.onBatteryLevelChanged(
167                 /* unusedBatteryLevel= */ 0,
168                 /* plugged in= */ true,
169                 /* charging= */ false)
170 
171         val attachListenerCaptor =
172                 ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
173         verify(rippleView, never()).addOnAttachStateChangeListener(attachListenerCaptor.capture())
174         verify(windowManager, never()).addView(eq(rippleView), any<WindowManager.LayoutParams>())
175     }
176 
177     @Test
testRipple_layoutsCorrectlynull178     fun testRipple_layoutsCorrectly() {
179         // Sets the correct ripple size.
180         val width = 100
181         val height = 200
182         whenever(windowMetrics.bounds).thenReturn(Rect(0, 0, width, height))
183 
184         // Trigger ripple.
185         val captor = ArgumentCaptor
186                 .forClass(BatteryController.BatteryStateChangeCallback::class.java)
187         verify(batteryController).addCallback(captor.capture())
188 
189         captor.value.onBatteryLevelChanged(
190                 /* unusedBatteryLevel= */ 0,
191                 /* plugged in= */ true,
192                 /* charging= */ false)
193 
194         val attachListenerCaptor =
195                 ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
196         verify(rippleView).addOnAttachStateChangeListener(attachListenerCaptor.capture())
197         verify(windowManager).addView(eq(rippleView), any<WindowManager.LayoutParams>())
198 
199         val runnableCaptor =
200                 ArgumentCaptor.forClass(Runnable::class.java)
201         attachListenerCaptor.value.onViewAttachedToWindow(rippleView)
202         verify(rippleView).startRipple(runnableCaptor.capture())
203 
204         // Verify size and center position.
205         val maxSize = 400f // Double the max value between width and height.
206         verify(rippleView).setMaxSize(maxWidth = maxSize, maxHeight = maxSize)
207 
208         val normalizedPortPosX =
209                 context.resources.getFloat(R.dimen.physical_charger_port_location_normalized_x)
210         val normalizedPortPosY =
211                 context.resources.getFloat(R.dimen.physical_charger_port_location_normalized_y)
212         val expectedCenterX: Float
213         val expectedCenterY: Float
214         when (checkNotNull(context.display).rotation) {
215             Surface.ROTATION_90 -> {
216                 expectedCenterX = width * normalizedPortPosY
217                 expectedCenterY = height * (1 - normalizedPortPosX)
218             }
219             Surface.ROTATION_180 -> {
220                 expectedCenterX = width * (1 - normalizedPortPosX)
221                 expectedCenterY = height * (1 - normalizedPortPosY)
222             }
223             Surface.ROTATION_270 -> {
224                 expectedCenterX = width * (1 - normalizedPortPosY)
225                 expectedCenterY = height * normalizedPortPosX
226             }
227             else -> { // Surface.ROTATION_0
228                 expectedCenterX = width * normalizedPortPosX
229                 expectedCenterY = height * normalizedPortPosY
230             }
231         }
232 
233         verify(rippleView).setCenter(expectedCenterX, expectedCenterY)
234     }
235 }
236