1 /*
2  * Copyright (C) 2023 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.settingslib.fuelgague
18 
19 import android.content.Context
20 import android.content.Intent
21 import android.os.BatteryManager
22 import android.os.BatteryManager.BATTERY_PLUGGED_AC
23 import android.os.BatteryManager.BATTERY_PLUGGED_DOCK
24 import android.os.BatteryManager.BATTERY_PLUGGED_USB
25 import android.os.BatteryManager.BATTERY_PLUGGED_WIRELESS
26 import android.os.BatteryManager.BATTERY_STATUS_FULL
27 import android.os.BatteryManager.BATTERY_STATUS_UNKNOWN
28 import android.os.BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE
29 import android.os.BatteryManager.CHARGING_POLICY_DEFAULT
30 import android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT
31 import android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE
32 import android.os.OsProtoEnums.BATTERY_PLUGGED_NONE
33 import androidx.test.core.app.ApplicationProvider
34 import androidx.test.ext.junit.runners.AndroidJUnit4
35 import com.android.settingslib.fuelgauge.BatteryStatus
36 import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_FAST
37 import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_REGULAR
38 import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_SLOWLY
39 import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_UNKNOWN
40 import com.android.settingslib.fuelgauge.BatteryStatus.isBatteryDefender
41 import com.android.settingslib.fuelgauge.BatteryUtils
42 import com.google.common.truth.Truth.assertThat
43 import com.google.common.truth.Truth.assertWithMessage
44 import java.util.Optional
45 import org.junit.Test
46 import org.junit.runner.RunWith
47 import org.junit.runners.Parameterized
48 import org.junit.runners.Suite
49 import org.junit.runners.Suite.SuiteClasses
50 
51 @RunWith(Suite::class)
52 @SuiteClasses(
53     BatteryStatusTest.NonParameterizedTest::class,
54     BatteryStatusTest.IsPluggedInTest::class,
55     BatteryStatusTest.IsChargedTest::class,
56     BatteryStatusTest.GetChargingSpeedTest::class,
57     BatteryStatusTest.IsPluggedInDockTest::class,
58 )
59 open class BatteryStatusTest {
60 
61     @RunWith(AndroidJUnit4::class)
62     class NonParameterizedTest : BatteryStatusTest() {
63         @Test
isLowBattery_20Percent_returnsTruenull64         fun isLowBattery_20Percent_returnsTrue() {
65             val level = 20
66             val intent = createIntent(batteryLevel = level)
67 
68             assertWithMessage("failed by isLowBattery(Intent), level=$level")
69                 .that(BatteryStatus.isLowBattery(intent))
70                 .isTrue()
71             assertWithMessage("failed by isLowBattery($level)")
72                 .that(BatteryStatus.isLowBattery(level))
73                 .isTrue()
74         }
75 
76         @Test
isLowBattery_21Percent_returnsFalsenull77         fun isLowBattery_21Percent_returnsFalse() {
78             val level = 21
79             val intent = createIntent(batteryLevel = level)
80 
81             assertWithMessage("failed by isLowBattery(intent), level=$level")
82                 .that(BatteryStatus.isLowBattery(intent))
83                 .isFalse()
84             assertWithMessage("failed by isLowBattery($level)")
85                 .that(BatteryStatus.isLowBattery(intent))
86                 .isFalse()
87         }
88 
89         @Test
isSevereLowBattery_10Percent_returnsTruenull90         fun isSevereLowBattery_10Percent_returnsTrue() {
91             val batteryChangedIntent = createIntent(batteryLevel = 10)
92 
93             assertThat(BatteryStatus.isSevereLowBattery(batteryChangedIntent)).isTrue()
94         }
95 
96         @Test
isSevereLowBattery_11Percent_returnFalsenull97         fun isSevereLowBattery_11Percent_returnFalse() {
98             val batteryChangedIntent = createIntent(batteryLevel = 11)
99 
100             assertThat(BatteryStatus.isSevereLowBattery(batteryChangedIntent)).isFalse()
101         }
102 
103         @Test
isExtremeLowBattery_3Percent_returnsTruenull104         fun isExtremeLowBattery_3Percent_returnsTrue() {
105             val batteryChangedIntent = createIntent(batteryLevel = 3)
106 
107             assertThat(BatteryStatus.isExtremeLowBattery(batteryChangedIntent)).isTrue()
108         }
109 
110         @Test
isExtremeLowBattery_4Percent_returnsFalsenull111         fun isExtremeLowBattery_4Percent_returnsFalse() {
112             val batteryChangedIntent = createIntent(batteryLevel = 4)
113 
114             assertThat(BatteryStatus.isExtremeLowBattery(batteryChangedIntent)).isFalse()
115         }
116 
117         @Test
isBatteryDefender_chargingLongLife_returnsTruenull118         fun isBatteryDefender_chargingLongLife_returnsTrue() {
119             val chargingStatus = CHARGING_POLICY_ADAPTIVE_LONGLIFE
120             val batteryChangedIntent = createIntent(chargingStatus = chargingStatus)
121 
122             assertIsBatteryDefender(chargingStatus, batteryChangedIntent).isTrue()
123         }
124 
125         @Test
isBatteryDefender_nonChargingLongLife_returnsFalsenull126         fun isBatteryDefender_nonChargingLongLife_returnsFalse() {
127             val chargingStatus = CHARGING_POLICY_DEFAULT
128             val batteryChangedIntent = createIntent(chargingStatus = chargingStatus)
129 
130             assertIsBatteryDefender(chargingStatus, batteryChangedIntent).isFalse()
131         }
132 
assertIsBatteryDefendernull133         private fun assertIsBatteryDefender(chargingStatus: Int, batteryChangedIntent: Intent) =
134             object {
135                 val assertions =
136                     listOf(
137                         "failed by isBatteryDefender(Intent), chargingStatus=$chargingStatus".let {
138                             assertWithMessage(it).that(isBatteryDefender(batteryChangedIntent))
139                         },
140                         "failed by isBatteryDefender($chargingStatus)".let {
141                             assertWithMessage(it).that(isBatteryDefender(chargingStatus))
142                         },
143                     )
144 
145                 fun isTrue() = assertions.forEach { it.isTrue() }
146 
147                 fun isFalse() = assertions.forEach { it.isFalse() }
148             }
149     }
150 
151     @RunWith(Parameterized::class)
152     class IsPluggedInTest(
153         private val name: String,
154         private val plugged: Int,
155         val expected: Boolean
156     ) : BatteryStatusTest() {
157 
158         @Test
isPluggedIn_null159         fun isPluggedIn_() {
160             val batteryChangedIntent = createIntent(plugged = plugged)
161 
162             assertWithMessage("failed by isPluggedIn(plugged=$plugged)")
163                 .that(BatteryStatus.isPluggedIn(plugged))
164                 .isEqualTo(expected)
165             assertWithMessage("failed by isPlugged(Intent), which plugged=$plugged")
166                 .that(BatteryStatus.isPluggedIn(batteryChangedIntent))
167                 .isEqualTo(expected)
168         }
169 
170         companion object {
171             @Parameterized.Parameters(name = "{0}")
172             @JvmStatic
parametersnull173             fun parameters() =
174                 arrayListOf(
175                     arrayOf("withAC_returnsTrue", BATTERY_PLUGGED_AC, true),
176                     arrayOf("withDock_returnsTrue", BATTERY_PLUGGED_DOCK, true),
177                     arrayOf("withUSB_returnsTrue", BATTERY_PLUGGED_USB, true),
178                     arrayOf("withWireless_returnsTrue", BATTERY_PLUGGED_WIRELESS, true),
179                     arrayOf("pluggedNone_returnsTrue", BATTERY_PLUGGED_NONE, false),
180                 )
181         }
182     }
183 
184     @RunWith(Parameterized::class)
185     class IsPluggedInDockTest(
186         private val name: String,
187         private val plugged: Int,
188         val expected: Boolean
189     ) : BatteryStatusTest() {
190 
191         @Test
192         fun isPluggedDockIn_() {
193             val batteryChangedIntent = createIntent(plugged = plugged)
194 
195             assertWithMessage("failed by isPluggedInDock(plugged=$plugged)")
196                 .that(BatteryStatus.isPluggedInDock(plugged))
197                 .isEqualTo(expected)
198             assertWithMessage("failed by isPluggedInDock(Intent), which plugged=$plugged")
199                 .that(BatteryStatus.isPluggedInDock(batteryChangedIntent))
200                 .isEqualTo(expected)
201         }
202 
203         companion object {
204             @Parameterized.Parameters(name = "{0}")
205             @JvmStatic
206             fun parameters() =
207                 arrayListOf(
208                     arrayOf("withAC_returnsTrue", BATTERY_PLUGGED_AC, false),
209                     arrayOf("withDock_returnsTrue", BATTERY_PLUGGED_DOCK, true),
210                     arrayOf("withUSB_returnsTrue", BATTERY_PLUGGED_USB, false),
211                     arrayOf("withWireless_returnsTrue", BATTERY_PLUGGED_WIRELESS, false),
212                     arrayOf("pluggedNone_returnsTrue", BATTERY_PLUGGED_NONE, false),
213                 )
214         }
215     }
216 
217     @RunWith(Parameterized::class)
218     class IsChargedTest(
219         private val status: Int,
220         private val batteryLevel: Int,
221         private val expected: Boolean
222     ) : BatteryStatusTest() {
223 
224         @Test
isCharged_null225         fun isCharged_() {
226             val batteryChangedIntent = createIntent(batteryLevel = batteryLevel, status = status)
227 
228             assertWithMessage(
229                     "failed by isCharged(Intent), status=$status, batteryLevel=$batteryLevel"
230                 )
231                 .that(BatteryStatus.isCharged(batteryChangedIntent))
232                 .isEqualTo(expected)
233             assertWithMessage("failed by isCharged($status, $batteryLevel)")
234                 .that(BatteryStatus.isCharged(status, batteryLevel))
235                 .isEqualTo(expected)
236         }
237 
238         companion object {
239             @Parameterized.Parameters(name = "status{0}_level{1}_returns-{2}")
240             @JvmStatic
parametersnull241             fun parameters() =
242                 arrayListOf(
243                     arrayOf(BATTERY_STATUS_FULL, 99, true),
244                     arrayOf(BATTERY_STATUS_UNKNOWN, 100, true),
245                     arrayOf(BATTERY_STATUS_FULL, 100, true),
246                     arrayOf(BATTERY_STATUS_UNKNOWN, 99, false),
247                 )
248         }
249     }
250 
251     @RunWith(Parameterized::class)
252     class GetChargingSpeedTest(
253         private val name: String,
254         private val maxChargingCurrent: Optional<Int>,
255         private val maxChargingVoltage: Optional<Int>,
256         private val expectedChargingSpeed: Int,
257         private val chargingStringV2Enabled: Boolean,
258     ) {
259 
260         val context: Context = ApplicationProvider.getApplicationContext()
261 
262         @Test
263         fun getChargingSpeed_() {
264             BatteryUtils.setChargingStringV2Enabled(
265                 chargingStringV2Enabled,
266                 false /* updateProperty */
267             )
268             val batteryChangedIntent =
269                 Intent(Intent.ACTION_BATTERY_CHANGED).apply {
270                     maxChargingCurrent.ifPresent { putExtra(EXTRA_MAX_CHARGING_CURRENT, it) }
271                     maxChargingVoltage.ifPresent { putExtra(EXTRA_MAX_CHARGING_VOLTAGE, it) }
272                 }
273 
274             assertThat(BatteryStatus.getChargingSpeed(context, batteryChangedIntent))
275                 .isEqualTo(expectedChargingSpeed)
276         }
277 
278         companion object {
279             @Parameterized.Parameters(name = "{0}")
280             @JvmStatic
281             fun parameters() =
282                 arrayListOf(
283                     arrayOf(
284                         "maxCurrent=n/a, maxVoltage=n/a -> UNKNOWN",
285                         Optional.empty<Int>(),
286                         Optional.empty<Int>(),
287                         CHARGING_UNKNOWN,
288                         false /* chargingStringV2Enabled */
289                     ),
290                     arrayOf(
291                         "maxCurrent=0, maxVoltage=9000000 -> UNKNOWN",
292                         Optional.of(0),
293                         Optional.of(0),
294                         CHARGING_UNKNOWN,
295                         false /* chargingStringV2Enabled */
296                     ),
297                     arrayOf(
298                         "maxCurrent=1500000, maxVoltage=5000000 -> CHARGING_REGULAR",
299                         Optional.of(1500000),
300                         Optional.of(5000000),
301                         CHARGING_REGULAR,
302                         false /* chargingStringV2Enabled */
303                     ),
304                     arrayOf(
305                         "maxCurrent=1000000, maxVoltage=5000000 -> CHARGING_REGULAR",
306                         Optional.of(1000000),
307                         Optional.of(5000000),
308                         CHARGING_REGULAR,
309                         false /* chargingStringV2Enabled */
310                     ),
311                     arrayOf(
312                         "maxCurrent=1500001, maxVoltage=5000000 -> CHARGING_FAST",
313                         Optional.of(1501000),
314                         Optional.of(5000000),
315                         CHARGING_FAST,
316                         false /* chargingStringV2Enabled */
317                     ),
318                     arrayOf(
319                         "maxCurrent=999999, maxVoltage=5000000 -> CHARGING_SLOWLY",
320                         Optional.of(999999),
321                         Optional.of(5000000),
322                         CHARGING_SLOWLY,
323                         false /* chargingStringV2Enabled */
324                     ),
325                     arrayOf(
326                         "maxCurrent=3000000, maxVoltage=9000000 -> CHARGING_FAST",
327                         Optional.of(3000000),
328                         Optional.of(9000000),
329                         CHARGING_FAST,
330                         true /* chargingStringV2Enabled */
331                     ),
332                     arrayOf(
333                         "maxCurrent=2200000, maxVoltage=9000000 -> CHARGING_REGULAR",
334                         Optional.of(2200000),
335                         Optional.of(9000000),
336                         CHARGING_REGULAR,
337                         true /* chargingStringV2Enabled */
338                     ),
339                 )
340         }
341     }
342 
createIntentnull343     protected fun createIntent(
344         batteryLevel: Int = 50,
345         chargingStatus: Int = CHARGING_POLICY_DEFAULT,
346         plugged: Int = BATTERY_PLUGGED_NONE,
347         status: Int = BatteryManager.BATTERY_STATUS_CHARGING,
348     ): Intent =
349         Intent(Intent.ACTION_BATTERY_CHANGED).apply {
350             putExtra(BatteryManager.EXTRA_STATUS, status)
351             putExtra(BatteryManager.EXTRA_LEVEL, batteryLevel)
352             putExtra(BatteryManager.EXTRA_SCALE, 100)
353             putExtra(BatteryManager.EXTRA_CHARGING_STATUS, chargingStatus)
354             putExtra(BatteryManager.EXTRA_PLUGGED, plugged)
355         }
356 }
357