xref: /aosp_15_r20/frameworks/base/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1 /*
2  * Copyright (C) 2022 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.stylus
18 
19 import android.app.ActivityManager
20 import android.app.Notification
21 import android.content.BroadcastReceiver
22 import android.content.Context
23 import android.content.Intent
24 import android.hardware.input.InputManager
25 import android.os.Bundle
26 import android.os.Handler
27 import android.testing.AndroidTestingRunner
28 import android.view.InputDevice
29 import androidx.core.app.NotificationManagerCompat
30 import androidx.test.filters.SmallTest
31 import com.android.internal.logging.InstanceId
32 import com.android.internal.logging.UiEventLogger
33 import com.android.systemui.InstanceIdSequenceFake
34 import com.android.systemui.SysuiTestCase
35 import com.android.systemui.res.R
36 import com.android.systemui.util.mockito.any
37 import com.android.systemui.util.mockito.argumentCaptor
38 import com.android.systemui.util.mockito.eq
39 import com.android.systemui.util.mockito.whenever
40 import com.google.common.truth.Truth.assertThat
41 import junit.framework.Assert.assertEquals
42 import org.junit.Before
43 import org.junit.Test
44 import org.junit.runner.RunWith
45 import org.mockito.ArgumentCaptor
46 import org.mockito.Captor
47 import org.mockito.Mock
48 import org.mockito.Mockito.clearInvocations
49 import org.mockito.Mockito.doNothing
50 import org.mockito.Mockito.inOrder
51 import org.mockito.Mockito.never
52 import org.mockito.Mockito.spy
53 import org.mockito.Mockito.times
54 import org.mockito.Mockito.verify
55 import org.mockito.Mockito.verifyNoMoreInteractions
56 import org.mockito.MockitoAnnotations
57 
58 @RunWith(AndroidTestingRunner::class)
59 @SmallTest
60 class StylusUsiPowerUiTest : SysuiTestCase() {
61     @Mock lateinit var notificationManager: NotificationManagerCompat
62 
63     @Mock lateinit var inputManager: InputManager
64 
65     @Mock lateinit var handler: Handler
66 
67     @Mock lateinit var btStylusDevice: InputDevice
68 
69     @Mock lateinit var uiEventLogger: UiEventLogger
70     @Captor lateinit var notificationCaptor: ArgumentCaptor<Notification>
71 
72     private lateinit var stylusUsiPowerUi: StylusUsiPowerUI
73     private lateinit var broadcastReceiver: BroadcastReceiver
74     private lateinit var contextSpy: Context
75 
76     private val instanceIdSequenceFake = InstanceIdSequenceFake(10)
77 
78     private val uid = ActivityManager.getCurrentUser()
79 
80     @Before
setUpnull81     fun setUp() {
82         MockitoAnnotations.initMocks(this)
83 
84         contextSpy = spy(mContext)
85         doNothing().whenever(contextSpy).startActivity(any())
86 
87         whenever(handler.post(any())).thenAnswer {
88             (it.arguments[0] as Runnable).run()
89             true
90         }
91 
92         whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf())
93         whenever(inputManager.getInputDevice(0)).thenReturn(btStylusDevice)
94         whenever(btStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
95         whenever(btStylusDevice.bluetoothAddress).thenReturn("SO:ME:AD:DR:ES")
96 
97         stylusUsiPowerUi =
98             StylusUsiPowerUI(contextSpy, notificationManager, inputManager, handler, uiEventLogger)
99         stylusUsiPowerUi.instanceIdSequence = instanceIdSequenceFake
100 
101         broadcastReceiver = stylusUsiPowerUi.receiver
102     }
103 
104     @Test
updateBatteryState_capacityZero_noopnull105     fun updateBatteryState_capacityZero_noop() {
106         stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0f))
107 
108         verifyNoMoreInteractions(notificationManager)
109     }
110 
111     @Test
updateBatteryState_capacityNaN_cancelsNotificationnull112     fun updateBatteryState_capacityNaN_cancelsNotification() {
113         stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(Float.NaN))
114 
115         verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low_percentage)
116         verifyNoMoreInteractions(notificationManager)
117     }
118 
119     @Test
updateBatteryState_capacityBelowThreshold_notifiesnull120     fun updateBatteryState_capacityBelowThreshold_notifies() {
121         stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
122 
123         verify(notificationManager, times(1))
124             .notify(eq(R.string.stylus_battery_low_percentage), any())
125         verifyNoMoreInteractions(notificationManager)
126     }
127 
128     @Test
updateBatteryState_capacityAboveThreshold_cancelsNotificattionnull129     fun updateBatteryState_capacityAboveThreshold_cancelsNotificattion() {
130         stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.8f))
131 
132         verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low_percentage)
133         verifyNoMoreInteractions(notificationManager)
134     }
135 
136     @Test
updateBatteryState_capacitySame_inputDeviceChanges_updatesInputDeviceIdnull137     fun updateBatteryState_capacitySame_inputDeviceChanges_updatesInputDeviceId() {
138         stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
139         stylusUsiPowerUi.updateBatteryState(1, FixedCapacityBatteryState(0.1f))
140 
141         assertThat(stylusUsiPowerUi.inputDeviceId).isEqualTo(1)
142         verify(notificationManager, times(1))
143             .notify(eq(R.string.stylus_battery_low_percentage), any())
144     }
145 
146     @Test
updateBatteryState_existingNotification_capacityAboveThreshold_cancelsNotificationnull147     fun updateBatteryState_existingNotification_capacityAboveThreshold_cancelsNotification() {
148         stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
149         stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.8f))
150 
151         inOrder(notificationManager).let {
152             it.verify(notificationManager, times(1))
153                 .notify(eq(R.string.stylus_battery_low_percentage), any())
154             it.verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low_percentage)
155             it.verifyNoMoreInteractions()
156         }
157     }
158 
159     @Test
updateBatteryState_existingNotification_capacityBelowThreshold_updatesNotificationnull160     fun updateBatteryState_existingNotification_capacityBelowThreshold_updatesNotification() {
161         stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
162         stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.15f))
163 
164         verify(notificationManager, times(2))
165             .notify(eq(R.string.stylus_battery_low_percentage), notificationCaptor.capture())
166         assertEquals(
167             notificationCaptor.value.extras.getString(Notification.EXTRA_TITLE),
168             context.getString(R.string.stylus_battery_low_percentage, "15%")
169         )
170         assertEquals(
171             notificationCaptor.value.extras.getString(Notification.EXTRA_TEXT),
172             context.getString(R.string.stylus_battery_low_subtitle)
173         )
174         verifyNoMoreInteractions(notificationManager)
175     }
176 
177     @Test
updateBatteryState_capacityAboveThenBelowThreshold_hidesThenShowsNotificationnull178     fun updateBatteryState_capacityAboveThenBelowThreshold_hidesThenShowsNotification() {
179         stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
180         stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.5f))
181         stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
182 
183         inOrder(notificationManager).let {
184             it.verify(notificationManager, times(1))
185                 .notify(eq(R.string.stylus_battery_low_percentage), any())
186             it.verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low_percentage)
187             it.verify(notificationManager, times(1))
188                 .notify(eq(R.string.stylus_battery_low_percentage), any())
189             it.verifyNoMoreInteractions()
190         }
191     }
192 
193     @Test
updateSuppression_noExistingNotification_cancelsNotificationnull194     fun updateSuppression_noExistingNotification_cancelsNotification() {
195         stylusUsiPowerUi.updateSuppression(true)
196 
197         verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low_percentage)
198         verifyNoMoreInteractions(notificationManager)
199     }
200 
201     @Test
updateSuppression_existingNotification_cancelsNotificationnull202     fun updateSuppression_existingNotification_cancelsNotification() {
203         stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
204 
205         stylusUsiPowerUi.updateSuppression(true)
206 
207         inOrder(notificationManager).let {
208             it.verify(notificationManager, times(1))
209                 .notify(eq(R.string.stylus_battery_low_percentage), any())
210             it.verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low_percentage)
211             it.verifyNoMoreInteractions()
212         }
213     }
214 
215     @Test
refresh_hasConnectedBluetoothStylus_existingNotification_doesNothingnull216     fun refresh_hasConnectedBluetoothStylus_existingNotification_doesNothing() {
217         stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
218         whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(0))
219         clearInvocations(notificationManager)
220 
221         stylusUsiPowerUi.refresh()
222 
223         verifyNoMoreInteractions(notificationManager)
224     }
225 
226     @Test
updateBatteryState_showsNotification_logsNotificationShownnull227     fun updateBatteryState_showsNotification_logsNotificationShown() {
228         stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
229 
230         verify(uiEventLogger, times(1))
231             .logWithInstanceIdAndPosition(
232                 StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_SHOWN,
233                 uid,
234                 contextSpy.packageName,
235                 InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId),
236                 10
237             )
238     }
239 
240     @Test
broadcastReceiver_clicked_hasInputDeviceId_startsUsiDetailsActivitynull241     fun broadcastReceiver_clicked_hasInputDeviceId_startsUsiDetailsActivity() {
242         val intent = Intent(StylusUsiPowerUI.ACTION_CLICKED_LOW_BATTERY)
243         val activityIntentCaptor = argumentCaptor<Intent>()
244         stylusUsiPowerUi.updateBatteryState(1, FixedCapacityBatteryState(0.15f))
245         broadcastReceiver.onReceive(contextSpy, intent)
246 
247         verify(contextSpy, times(1)).startActivity(activityIntentCaptor.capture())
248         assertThat(activityIntentCaptor.value.action)
249             .isEqualTo(StylusUsiPowerUI.ACTION_STYLUS_USI_DETAILS)
250         val args =
251             activityIntentCaptor.value.getExtra(StylusUsiPowerUI.KEY_SETTINGS_FRAGMENT_ARGS)
252                 as Bundle
253         assertThat(args.getInt(StylusUsiPowerUI.KEY_DEVICE_INPUT_ID)).isEqualTo(1)
254     }
255 
256     @Test
broadcastReceiver_clicked_nullInputDeviceId_doesNotStartActivitynull257     fun broadcastReceiver_clicked_nullInputDeviceId_doesNotStartActivity() {
258         val intent = Intent(StylusUsiPowerUI.ACTION_CLICKED_LOW_BATTERY)
259         broadcastReceiver.onReceive(contextSpy, intent)
260 
261         verify(contextSpy, never()).startActivity(any())
262     }
263 
264     @Test
broadcastReceiver_clicked_logsNotificationClickednull265     fun broadcastReceiver_clicked_logsNotificationClicked() {
266         val intent = Intent(StylusUsiPowerUI.ACTION_CLICKED_LOW_BATTERY)
267         broadcastReceiver.onReceive(contextSpy, intent)
268 
269         verify(uiEventLogger, times(1))
270             .logWithInstanceIdAndPosition(
271                 StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_CLICKED,
272                 uid,
273                 contextSpy.packageName,
274                 InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId),
275                 100
276             )
277     }
278 
279     @Test
broadcastReceiver_dismissed_logsNotificationDismissednull280     fun broadcastReceiver_dismissed_logsNotificationDismissed() {
281         val intent = Intent(StylusUsiPowerUI.ACTION_DISMISSED_LOW_BATTERY)
282         broadcastReceiver.onReceive(contextSpy, intent)
283 
284         verify(uiEventLogger, times(1))
285             .logWithInstanceIdAndPosition(
286                 StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_DISMISSED,
287                 uid,
288                 contextSpy.packageName,
289                 InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId),
290                 100
291             )
292     }
293 }
294