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