1 /* 2 * Copyright (C) 2017 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.settings.fuelgauge; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static com.google.common.truth.Truth.assertWithMessage; 21 22 import static org.mockito.ArgumentMatchers.anyInt; 23 import static org.mockito.ArgumentMatchers.anyLong; 24 import static org.mockito.ArgumentMatchers.anyString; 25 import static org.mockito.ArgumentMatchers.eq; 26 import static org.mockito.Mockito.any; 27 import static org.mockito.Mockito.doAnswer; 28 import static org.mockito.Mockito.doReturn; 29 import static org.mockito.Mockito.mock; 30 import static org.mockito.Mockito.never; 31 import static org.mockito.Mockito.spy; 32 import static org.mockito.Mockito.times; 33 import static org.mockito.Mockito.verify; 34 import static org.mockito.Mockito.when; 35 36 import android.content.Context; 37 import android.content.Intent; 38 import android.os.BatteryManager; 39 import android.os.BatteryStats; 40 import android.os.BatteryUsageStats; 41 import android.os.SystemClock; 42 import android.os.SystemProperties; 43 import android.provider.Settings; 44 import android.util.SparseIntArray; 45 46 import com.android.internal.os.BatteryStatsHistoryIterator; 47 import com.android.settings.testutils.BatteryTestUtils; 48 import com.android.settings.testutils.FakeFeatureFactory; 49 import com.android.settings.widget.UsageView; 50 import com.android.settingslib.fuelgauge.Estimate; 51 52 import org.junit.After; 53 import org.junit.Before; 54 import org.junit.Ignore; 55 import org.junit.Test; 56 import org.junit.runner.RunWith; 57 import org.mockito.ArgumentCaptor; 58 import org.mockito.Mock; 59 import org.mockito.MockitoAnnotations; 60 import org.robolectric.RobolectricTestRunner; 61 import org.robolectric.RuntimeEnvironment; 62 63 import java.time.Duration; 64 import java.time.Instant; 65 import java.util.Locale; 66 import java.util.Map; 67 import java.util.TimeZone; 68 import java.util.concurrent.TimeUnit; 69 70 @RunWith(RobolectricTestRunner.class) 71 public class BatteryInfoTest { 72 73 private static final String STATUS_CHARGING_NO_TIME = "50% - charging"; 74 private static final String STATUS_CHARGING_TIME = "50% - 0 min left until full"; 75 private static final String STATUS_NOT_CHARGING = "Not charging"; 76 private static final String STATUS_CHARGING_FUTURE_BYPASS = "50% - Charging"; 77 private static final String STATUS_CHARGING_PAUSED = "50% - Charging optimized"; 78 private static final long REMAINING_TIME_NULL = -1; 79 private static final long REMAINING_TIME = 2; 80 // Strings are defined in frameworks/base/packages/SettingsLib/res/values/strings.xml 81 private static final String ENHANCED_STRING_SUFFIX = "based on your usage"; 82 private static final String BATTERY_RUN_OUT_PREFIX = "Battery may run out by"; 83 private static final long TEST_CHARGE_TIME_REMAINING = TimeUnit.MINUTES.toMicros(1); 84 private static final String TEST_CHARGE_TIME_REMAINING_STRINGIFIED = "1 min left until full"; 85 private static final String TEST_BATTERY_LEVEL_10 = "10%"; 86 private static final String FIFTEEN_MIN_FORMATTED = "15 min"; 87 private static final Estimate MOCK_ESTIMATE = 88 new Estimate( 89 1000, /* estimateMillis */ 90 false, /* isBasedOnUsage */ 91 1000 /* averageDischargeTime */); 92 private static final Map<ChargingType, Integer> CHARGING_TYPE_MAP = 93 Map.of( 94 ChargingType.WIRED, BatteryManager.BATTERY_PLUGGED_AC, 95 ChargingType.WIRELESS, BatteryManager.BATTERY_PLUGGED_WIRELESS, 96 ChargingType.DOCKED, BatteryManager.BATTERY_PLUGGED_DOCK); 97 private static final Map<ChargingSpeed, Integer> CHARGING_SPEED_MAP = 98 Map.of( 99 ChargingSpeed.FAST, 1501000, 100 ChargingSpeed.REGULAR, 1500000, 101 ChargingSpeed.SLOW, 999999); 102 private static final long UNUSED_TIME_MS = -1L; 103 104 private Intent mDisChargingBatteryBroadcast; 105 private Intent mChargingBatteryBroadcast; 106 private Context mContext; 107 private FakeFeatureFactory mFeatureFactory; 108 private TimeZone mOriginalTimeZone; 109 110 @Mock private BatteryUsageStats mBatteryUsageStats; 111 112 @Before setUp()113 public void setUp() { 114 MockitoAnnotations.initMocks(this); 115 mContext = spy(RuntimeEnvironment.application); 116 mFeatureFactory = FakeFeatureFactory.setupForTest(); 117 118 mDisChargingBatteryBroadcast = BatteryTestUtils.getDischargingIntent(); 119 120 mChargingBatteryBroadcast = BatteryTestUtils.getChargingIntent(); 121 122 doReturn(false).when(mFeatureFactory.powerUsageFeatureProvider).isExtraDefend(); 123 Settings.Global.putInt( 124 mContext.getContentResolver(), 125 BatteryUtils.SETTINGS_GLOBAL_DOCK_DEFENDER_BYPASS, 126 0); 127 128 // Reset static cache for testing purpose. 129 com.android.settingslib.fuelgauge.BatteryUtils.setChargingStringV2Enabled(null); 130 131 mOriginalTimeZone = TimeZone.getDefault(); 132 } 133 134 @After tearDown()135 public void tearDown() throws Exception { 136 TimeZone.setDefault(mOriginalTimeZone); 137 } 138 139 @Test getBatteryInfo_hasStatusLabel()140 public void getBatteryInfo_hasStatusLabel() { 141 doReturn(REMAINING_TIME_NULL).when(mBatteryUsageStats).getBatteryTimeRemainingMs(); 142 BatteryInfo info = 143 BatteryInfo.getBatteryInfoOld( 144 mContext, 145 mDisChargingBatteryBroadcast, 146 mBatteryUsageStats, 147 SystemClock.elapsedRealtime() * 1000, 148 true /* shortString */); 149 150 assertThat(info.statusLabel).isEqualTo(STATUS_NOT_CHARGING); 151 } 152 153 @Test getBatteryInfo_doNotShowChargingMethod_hasRemainingTime()154 public void getBatteryInfo_doNotShowChargingMethod_hasRemainingTime() { 155 doReturn(REMAINING_TIME).when(mBatteryUsageStats).getChargeTimeRemainingMs(); 156 BatteryInfo info = 157 BatteryInfo.getBatteryInfoOld( 158 mContext, 159 mChargingBatteryBroadcast, 160 mBatteryUsageStats, 161 SystemClock.elapsedRealtime() * 1000, 162 false /* shortString */); 163 164 assertThat(info.chargeLabel.toString()).isEqualTo(STATUS_CHARGING_TIME); 165 } 166 167 @Test getBatteryInfo_doNotShowChargingMethod_noRemainingTime()168 public void getBatteryInfo_doNotShowChargingMethod_noRemainingTime() { 169 doReturn(REMAINING_TIME_NULL).when(mBatteryUsageStats).getChargeTimeRemainingMs(); 170 BatteryInfo info = 171 BatteryInfo.getBatteryInfoOld( 172 mContext, 173 mChargingBatteryBroadcast, 174 mBatteryUsageStats, 175 SystemClock.elapsedRealtime() * 1000, 176 false /* shortString */); 177 178 assertThat(info.chargeLabel.toString()).ignoringCase().isEqualTo(STATUS_CHARGING_NO_TIME); 179 } 180 181 @Test getBatteryInfo_pluggedInUsingShortString_usesCorrectData()182 public void getBatteryInfo_pluggedInUsingShortString_usesCorrectData() { 183 doReturn(TEST_CHARGE_TIME_REMAINING / 1000) 184 .when(mBatteryUsageStats) 185 .getChargeTimeRemainingMs(); 186 BatteryInfo info = 187 BatteryInfo.getBatteryInfoOld( 188 mContext, 189 mChargingBatteryBroadcast, 190 mBatteryUsageStats, 191 SystemClock.elapsedRealtime() * 1000, 192 true /* shortString */); 193 194 assertThat(info.discharging).isEqualTo(false); 195 assertThat(info.chargeLabel.toString()).isEqualTo("50% - 1 min left until full"); 196 } 197 198 @Test getBatteryInfo_basedOnUsageTrueMoreThanFifteenMinutes_usesCorrectString()199 public void getBatteryInfo_basedOnUsageTrueMoreThanFifteenMinutes_usesCorrectString() { 200 Estimate estimate = 201 new Estimate( 202 Duration.ofHours(4).toMillis(), 203 true /* isBasedOnUsage */, 204 1000 /* averageDischargeTime */); 205 BatteryInfo info = 206 BatteryInfo.getBatteryInfo( 207 mContext, 208 mDisChargingBatteryBroadcast, 209 mBatteryUsageStats, 210 estimate, 211 SystemClock.elapsedRealtime() * 1000, 212 false /* shortString */); 213 BatteryInfo info2 = 214 BatteryInfo.getBatteryInfo( 215 mContext, 216 mDisChargingBatteryBroadcast, 217 mBatteryUsageStats, 218 estimate, 219 SystemClock.elapsedRealtime() * 1000, 220 true /* shortString */); 221 222 // Both long and short strings should not have extra text 223 assertThat(info.remainingLabel.toString()).doesNotContain(ENHANCED_STRING_SUFFIX); 224 assertThat(info.suggestionLabel).contains(BATTERY_RUN_OUT_PREFIX); 225 assertThat(info2.remainingLabel.toString()).doesNotContain(ENHANCED_STRING_SUFFIX); 226 assertThat(info2.suggestionLabel).contains(BATTERY_RUN_OUT_PREFIX); 227 } 228 229 @Test 230 @Ignore getBatteryInfo_MoreThanOneDay_suggestionLabelIsCorrectString()231 public void getBatteryInfo_MoreThanOneDay_suggestionLabelIsCorrectString() { 232 Estimate estimate = 233 new Estimate( 234 Duration.ofDays(3).toMillis(), 235 true /* isBasedOnUsage */, 236 1000 /* averageDischargeTime */); 237 BatteryInfo info = 238 BatteryInfo.getBatteryInfo( 239 mContext, 240 mDisChargingBatteryBroadcast, 241 mBatteryUsageStats, 242 estimate, 243 SystemClock.elapsedRealtime() * 1000, 244 false /* shortString */); 245 246 assertThat(info.suggestionLabel).doesNotContain(BATTERY_RUN_OUT_PREFIX); 247 } 248 249 @Test getBatteryInfo_basedOnUsageFalse_usesDefaultString()250 public void getBatteryInfo_basedOnUsageFalse_usesDefaultString() { 251 BatteryInfo info = 252 BatteryInfo.getBatteryInfo( 253 mContext, 254 mDisChargingBatteryBroadcast, 255 mBatteryUsageStats, 256 MOCK_ESTIMATE, 257 SystemClock.elapsedRealtime() * 1000, 258 false /* shortString */); 259 BatteryInfo info2 = 260 BatteryInfo.getBatteryInfo( 261 mContext, 262 mDisChargingBatteryBroadcast, 263 mBatteryUsageStats, 264 MOCK_ESTIMATE, 265 SystemClock.elapsedRealtime() * 1000, 266 true /* shortString */); 267 268 assertThat(info.remainingLabel.toString()).doesNotContain(ENHANCED_STRING_SUFFIX); 269 assertThat(info2.remainingLabel.toString()).doesNotContain(ENHANCED_STRING_SUFFIX); 270 } 271 272 @Test getBatteryInfo_charging_usesChargeTime()273 public void getBatteryInfo_charging_usesChargeTime() { 274 doReturn(TEST_CHARGE_TIME_REMAINING / 1000) 275 .when(mBatteryUsageStats) 276 .getChargeTimeRemainingMs(); 277 278 BatteryInfo info = 279 BatteryInfo.getBatteryInfo( 280 mContext, 281 mChargingBatteryBroadcast, 282 mBatteryUsageStats, 283 MOCK_ESTIMATE, 284 SystemClock.elapsedRealtime() * 1000, 285 false /* shortString */); 286 287 assertThat(info.remainingTimeUs).isEqualTo(TEST_CHARGE_TIME_REMAINING); 288 assertThat(info.remainingLabel.toString()) 289 .isEqualTo(TEST_CHARGE_TIME_REMAINING_STRINGIFIED); 290 } 291 292 @Test getBatteryInfo_pluggedInWithFullBattery_onlyShowBatteryLevel()293 public void getBatteryInfo_pluggedInWithFullBattery_onlyShowBatteryLevel() { 294 mChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_LEVEL, 100); 295 296 BatteryInfo info = 297 BatteryInfo.getBatteryInfo( 298 mContext, 299 mChargingBatteryBroadcast, 300 mBatteryUsageStats, 301 MOCK_ESTIMATE, 302 SystemClock.elapsedRealtime() * 1000, 303 false /* shortString */); 304 305 assertThat(info.chargeLabel).isEqualTo("100%"); 306 } 307 308 @Test getBatteryInfo_chargingWithDefender_updateChargeLabel()309 public void getBatteryInfo_chargingWithDefender_updateChargeLabel() { 310 doReturn(TEST_CHARGE_TIME_REMAINING).when(mBatteryUsageStats).getChargeTimeRemainingMs(); 311 doReturn(true) 312 .when(mFeatureFactory.powerUsageFeatureProvider) 313 .isBatteryDefend(any(BatteryInfo.class)); 314 mChargingBatteryBroadcast.putExtra( 315 BatteryManager.EXTRA_CHARGING_STATUS, 316 BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE); 317 318 BatteryInfo info = 319 BatteryInfo.getBatteryInfo( 320 mContext, 321 mChargingBatteryBroadcast, 322 mBatteryUsageStats, 323 MOCK_ESTIMATE, 324 SystemClock.elapsedRealtime() * 1000, 325 false /* shortString */); 326 327 assertThat(info.isBatteryDefender).isTrue(); 328 assertThat(info.chargeLabel.toString()).contains(STATUS_CHARGING_PAUSED); 329 } 330 331 @Test getBatteryInfo_getChargeTimeRemaining_updateSettingsGlobal()332 public void getBatteryInfo_getChargeTimeRemaining_updateSettingsGlobal() { 333 doReturn(TEST_CHARGE_TIME_REMAINING).when(mBatteryUsageStats).getChargeTimeRemainingMs(); 334 335 BatteryInfo.getBatteryInfo( 336 mContext, 337 mChargingBatteryBroadcast, 338 mBatteryUsageStats, 339 MOCK_ESTIMATE, 340 SystemClock.elapsedRealtime() * 1000, 341 false /* shortString */); 342 343 assertThat(BatteryInfo.getSettingsChargeTimeRemaining(mContext)) 344 .isEqualTo(TEST_CHARGE_TIME_REMAINING); 345 } 346 347 @Test getBatteryInfo_differentChargeTimeRemaining_updateSettingsGlobal()348 public void getBatteryInfo_differentChargeTimeRemaining_updateSettingsGlobal() { 349 doReturn(TEST_CHARGE_TIME_REMAINING).when(mBatteryUsageStats).getChargeTimeRemainingMs(); 350 final long newTimeToFull = 300L; 351 doReturn(newTimeToFull).when(mBatteryUsageStats).getChargeTimeRemainingMs(); 352 353 BatteryInfo.getBatteryInfo( 354 mContext, 355 mChargingBatteryBroadcast, 356 mBatteryUsageStats, 357 MOCK_ESTIMATE, 358 SystemClock.elapsedRealtime() * 1000, 359 false /* shortString */); 360 361 assertThat(BatteryInfo.getSettingsChargeTimeRemaining(mContext)).isEqualTo(newTimeToFull); 362 } 363 364 @Test getBatteryInfo_dockDefenderActive_updateChargeString()365 public void getBatteryInfo_dockDefenderActive_updateChargeString() { 366 doReturn(TEST_CHARGE_TIME_REMAINING / 1000) 367 .when(mBatteryUsageStats) 368 .getChargeTimeRemainingMs(); 369 doReturn(true).when(mFeatureFactory.powerUsageFeatureProvider).isExtraDefend(); 370 doReturn(true) 371 .when(mFeatureFactory.powerUsageFeatureProvider) 372 .isBatteryDefend(any(BatteryInfo.class)); 373 Intent intent = 374 createBatteryIntent( 375 BatteryManager.BATTERY_PLUGGED_DOCK, 376 /* level= */ 50, 377 BatteryManager.BATTERY_STATUS_CHARGING) 378 .putExtra( 379 BatteryManager.EXTRA_CHARGING_STATUS, 380 BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE); 381 382 BatteryInfo info = 383 BatteryInfo.getBatteryInfo( 384 mContext, 385 intent, 386 mBatteryUsageStats, 387 MOCK_ESTIMATE, 388 SystemClock.elapsedRealtime() * 1000, 389 false /* shortString */); 390 391 assertThat(info.chargeLabel.toString()).contains(STATUS_CHARGING_PAUSED); 392 } 393 394 @Test getBatteryInfo_dockDefenderTemporarilyBypassed_updateChargeLabel()395 public void getBatteryInfo_dockDefenderTemporarilyBypassed_updateChargeLabel() { 396 doReturn(REMAINING_TIME).when(mBatteryUsageStats).getChargeTimeRemainingMs(); 397 mChargingBatteryBroadcast.putExtra( 398 BatteryManager.EXTRA_CHARGING_STATUS, BatteryManager.CHARGING_POLICY_DEFAULT); 399 Settings.Global.putInt( 400 mContext.getContentResolver(), 401 BatteryUtils.SETTINGS_GLOBAL_DOCK_DEFENDER_BYPASS, 402 1); 403 404 BatteryInfo info = 405 BatteryInfo.getBatteryInfo( 406 mContext, 407 createBatteryIntent( 408 BatteryManager.BATTERY_PLUGGED_DOCK, 409 /* level= */ 50, 410 BatteryManager.BATTERY_STATUS_CHARGING), 411 mBatteryUsageStats, 412 MOCK_ESTIMATE, 413 SystemClock.elapsedRealtime() * 1000, 414 false /* shortString */); 415 416 assertThat(info.chargeLabel.toString()).contains(STATUS_CHARGING_TIME); 417 } 418 419 @Test getBatteryInfo_dockDefenderFutureBypass_updateChargeLabel()420 public void getBatteryInfo_dockDefenderFutureBypass_updateChargeLabel() { 421 doReturn(false).when(mFeatureFactory.powerUsageFeatureProvider).isExtraDefend(); 422 mChargingBatteryBroadcast.putExtra( 423 BatteryManager.EXTRA_CHARGING_STATUS, BatteryManager.CHARGING_POLICY_DEFAULT); 424 425 BatteryInfo info = 426 BatteryInfo.getBatteryInfo( 427 mContext, 428 createBatteryIntent( 429 BatteryManager.BATTERY_PLUGGED_DOCK, 430 /* level= */ 50, 431 BatteryManager.BATTERY_STATUS_CHARGING), 432 mBatteryUsageStats, 433 MOCK_ESTIMATE, 434 SystemClock.elapsedRealtime() * 1000, 435 false /* shortString */); 436 437 assertThat(info.chargeLabel.toString()).contains(STATUS_CHARGING_FUTURE_BYPASS); 438 } 439 440 @Test getBatteryInfo_fastCharging_updateRemainingLabelAndStatusLabel()441 public void getBatteryInfo_fastCharging_updateRemainingLabelAndStatusLabel() { 442 prepareTestGetBatteryInfoEnvironment( 443 /* remainingTimeMs= */ Duration.ofMinutes(90).toMillis(), 444 /* chargingStringV2Enabled= */ false); 445 Intent batteryIntent = 446 createIntentForGetBatteryInfoTest( 447 ChargingType.WIRED, ChargingSpeed.FAST, /* batteryLevel= */ 61); 448 var expectedStatusLabel = "Charging rapidly"; 449 var expectedRemainingLabel = "1 hr, 30 min left until full"; 450 var expectedChargeLabel = "61% - " + expectedRemainingLabel; 451 452 assertGetBatteryInfo( 453 batteryIntent, 454 /* currentTimeMillis= */ UNUSED_TIME_MS, 455 expectedStatusLabel, 456 expectedRemainingLabel, 457 expectedChargeLabel); 458 } 459 460 @Test getBatteryInfo_regularCharging_updateRemainingLabelAndStatusLabel()461 public void getBatteryInfo_regularCharging_updateRemainingLabelAndStatusLabel() { 462 prepareTestGetBatteryInfoEnvironment( 463 /* remainingTimeMs= */ Duration.ofMinutes(80).toMillis(), 464 /* chargingStringV2Enabled= */ false); 465 Intent batteryIntent = 466 createIntentForGetBatteryInfoTest( 467 ChargingType.WIRED, ChargingSpeed.REGULAR, /* batteryLevel= */ 33); 468 var expectedStatusLabel = "Charging"; 469 var expectedRemainingLabel = "1 hr, 20 min left until full"; 470 var expectedChargeLabel = "33% - " + expectedRemainingLabel; 471 472 assertGetBatteryInfo( 473 batteryIntent, 474 /* currentTimeMillis= */ UNUSED_TIME_MS, 475 expectedStatusLabel, 476 expectedRemainingLabel, 477 expectedChargeLabel); 478 } 479 480 @Test getBatteryInfo_slowCharging_updateRemainingLabelAndStatusLabel()481 public void getBatteryInfo_slowCharging_updateRemainingLabelAndStatusLabel() { 482 prepareTestGetBatteryInfoEnvironment( 483 /* remainingTimeMs= */ Duration.ofMinutes(100).toMillis(), 484 /* chargingStringV2Enabled= */ false); 485 Intent batteryIntent = 486 createIntentForGetBatteryInfoTest( 487 ChargingType.WIRED, ChargingSpeed.SLOW, /* batteryLevel= */ 53); 488 var expectedStatusLabel = "Charging slowly"; 489 var expectedRemainingLabel = "1 hr, 40 min left until full"; 490 var expectedChargeLabel = "53% - " + expectedRemainingLabel; 491 492 assertGetBatteryInfo( 493 batteryIntent, 494 /* currentTimeMillis= */ UNUSED_TIME_MS, 495 expectedStatusLabel, 496 expectedRemainingLabel, 497 expectedChargeLabel); 498 } 499 500 @Test getBatteryInfo_wirelessCharging_updateRemainingLabelAndStatusLabel()501 public void getBatteryInfo_wirelessCharging_updateRemainingLabelAndStatusLabel() { 502 prepareTestGetBatteryInfoEnvironment( 503 /* remainingTimeMs= */ Duration.ofMinutes(130).toMillis(), 504 /* chargingStringV2Enabled= */ false); 505 Intent batteryIntent = 506 createIntentForGetBatteryInfoTest( 507 ChargingType.WIRELESS, ChargingSpeed.REGULAR, /* batteryLevel= */ 10); 508 var expectedStatusLabel = "Charging wirelessly"; 509 var expectedRemainingLabel = "2 hr, 10 min left until full"; 510 var expectedChargeLabel = "10% - " + expectedRemainingLabel; 511 512 assertGetBatteryInfo( 513 batteryIntent, 514 /* currentTimeMillis= */ UNUSED_TIME_MS, 515 expectedStatusLabel, 516 expectedRemainingLabel, 517 expectedChargeLabel); 518 } 519 520 @Test getBatteryInfo_dockedCharging_updateRemainingLabelAndStatusLabel()521 public void getBatteryInfo_dockedCharging_updateRemainingLabelAndStatusLabel() { 522 prepareTestGetBatteryInfoEnvironment( 523 /* remainingTimeMs= */ Duration.ofMinutes(30).toMillis(), 524 /* chargingStringV2Enabled= */ false); 525 Intent batteryIntent = 526 createIntentForGetBatteryInfoTest( 527 ChargingType.DOCKED, ChargingSpeed.REGULAR, /* batteryLevel= */ 51); 528 var expectedStatusLabel = "Charging"; 529 var expectedRemainingLabel = "30 min left until full"; 530 var expectedChargeLabel = "51% - " + expectedRemainingLabel; 531 532 assertGetBatteryInfo( 533 batteryIntent, 534 /* currentTimeMillis= */ UNUSED_TIME_MS, 535 expectedStatusLabel, 536 expectedRemainingLabel, 537 expectedChargeLabel); 538 } 539 540 @Test 541 @Ignore getBatteryInfo_fastChargingV2_updateRemainingLabelAndStatusLabel()542 public void getBatteryInfo_fastChargingV2_updateRemainingLabelAndStatusLabel() { 543 prepareTestGetBatteryInfoEnvironment( 544 /* remainingTimeMs= */ Duration.ofMinutes(30).toMillis(), 545 /* chargingStringV2Enabled= */ true); 546 Intent batteryIntent = 547 createIntentForGetBatteryInfoTest( 548 ChargingType.WIRED, ChargingSpeed.FAST, /* batteryLevel= */ 56); 549 var expectedStatusLabel = "Fast charging"; 550 var expectedRemainingLabel = "Full by "; 551 var expectedChargeLabel = "56% - " + expectedStatusLabel + " - " + expectedRemainingLabel; 552 var currentTimeMillis = Instant.parse("2024-04-01T13:00:00Z").toEpochMilli(); 553 554 assertGetBatteryInfo( 555 batteryIntent, 556 currentTimeMillis, 557 expectedStatusLabel, 558 expectedRemainingLabel, 559 expectedChargeLabel); 560 } 561 562 @Test getBatteryInfo_regularChargingV2_updateRemainingLabelAndStatusLabel()563 public void getBatteryInfo_regularChargingV2_updateRemainingLabelAndStatusLabel() { 564 prepareTestGetBatteryInfoEnvironment( 565 /* remainingTimeMs= */ Duration.ofHours(1).toMillis(), 566 /* chargingStringV2Enabled= */ true); 567 Intent batteryIntent = 568 createIntentForGetBatteryInfoTest( 569 ChargingType.WIRED, ChargingSpeed.REGULAR, /* batteryLevel= */ 12); 570 var expectedStatusLabel = "Charging"; 571 var expectedRemainingLabel = "Fully charged by "; 572 var expectedChargeLabel = "12% - " + expectedRemainingLabel; 573 var currentTimeMillis = Instant.parse("2024-04-01T13:00:00Z").toEpochMilli(); 574 575 assertGetBatteryInfo( 576 batteryIntent, 577 currentTimeMillis, 578 expectedStatusLabel, 579 expectedRemainingLabel, 580 expectedChargeLabel); 581 } 582 583 @Test getBatteryInfo_slowChargingV2_updateRemainingLabelAndStatusLabel()584 public void getBatteryInfo_slowChargingV2_updateRemainingLabelAndStatusLabel() { 585 prepareTestGetBatteryInfoEnvironment( 586 /* remainingTimeMs= */ Duration.ofHours(2).toMillis(), 587 /* chargingStringV2Enabled= */ true); 588 Intent batteryIntent = 589 createIntentForGetBatteryInfoTest( 590 ChargingType.WIRED, ChargingSpeed.SLOW, /* batteryLevel= */ 18); 591 var expectedStatusLabel = "Charging"; 592 var expectedRemainingLabel = "Fully charged by"; 593 var expectedChargeLabel = "18% - " + expectedRemainingLabel; 594 var currentTimeMillis = Instant.parse("2024-04-01T13:00:00Z").toEpochMilli(); 595 596 assertGetBatteryInfo( 597 batteryIntent, 598 currentTimeMillis, 599 expectedStatusLabel, 600 expectedRemainingLabel, 601 expectedChargeLabel); 602 } 603 604 @Test getBatteryInfo_wirelessChargingV2_updateRemainingLabelAndStatusLabel()605 public void getBatteryInfo_wirelessChargingV2_updateRemainingLabelAndStatusLabel() { 606 prepareTestGetBatteryInfoEnvironment( 607 /* remainingTimeMs= */ Duration.ofHours(1).toMillis(), 608 /* chargingStringV2Enabled= */ true); 609 Intent batteryIntent = 610 createIntentForGetBatteryInfoTest( 611 ChargingType.WIRELESS, ChargingSpeed.REGULAR, /* batteryLevel= */ 45); 612 var expectedStatusLabel = "Charging"; 613 var expectedRemainingLabel = "Fully charged by"; 614 var expectedChargeLabel = "45% - " + expectedRemainingLabel; 615 var currentTimeMillis = Instant.parse("2024-04-01T15:00:00Z").toEpochMilli(); 616 617 assertGetBatteryInfo( 618 batteryIntent, 619 currentTimeMillis, 620 expectedStatusLabel, 621 expectedRemainingLabel, 622 expectedChargeLabel); 623 } 624 625 @Test getBatteryInfo_dockedChargingV2_updateRemainingLabelAndStatusLabel()626 public void getBatteryInfo_dockedChargingV2_updateRemainingLabelAndStatusLabel() { 627 prepareTestGetBatteryInfoEnvironment( 628 /* remainingTimeMs= */ Duration.ofHours(1).toMillis(), 629 /* chargingStringV2Enabled= */ true); 630 Intent batteryIntent = 631 createIntentForGetBatteryInfoTest( 632 ChargingType.DOCKED, ChargingSpeed.REGULAR, /* batteryLevel= */ 66); 633 var expectedStatusLabel = "Charging"; 634 var expectedRemainingLabel = "Fully charged by"; 635 var expectedChargeLabel = "66% - " + expectedRemainingLabel; 636 var currentTimeMillis = Instant.parse("2021-02-09T13:00:00.00Z").toEpochMilli(); 637 638 assertGetBatteryInfo( 639 batteryIntent, 640 currentTimeMillis, 641 expectedStatusLabel, 642 expectedRemainingLabel, 643 expectedChargeLabel); 644 } 645 646 @Test getBatteryInfo_customizedWLCLabel_updateRemainingLabelAndStatusLabel()647 public void getBatteryInfo_customizedWLCLabel_updateRemainingLabelAndStatusLabel() { 648 prepareTestGetBatteryInfoEnvironment( 649 /* remainingTimeMs= */ Duration.ofHours(1).toMillis(), 650 /* chargingStringV2Enabled= */ true); 651 Intent batteryIntent = 652 createIntentForGetBatteryInfoTest( 653 ChargingType.WIRELESS, ChargingSpeed.REGULAR, /* batteryLevel= */ 45); 654 var expectedLabel = "Full by 8:00 AM"; 655 when(mFeatureFactory.batterySettingsFeatureProvider.getWirelessChargingRemainingLabel( 656 eq(mContext), anyLong(), anyLong())) 657 .thenReturn(expectedLabel); 658 var currentTimeMillis = Instant.parse("2021-02-09T13:00:00.00Z").toEpochMilli(); 659 var info = 660 BatteryInfo.getBatteryInfo( 661 mContext, 662 batteryIntent, 663 mBatteryUsageStats, 664 MOCK_ESTIMATE, 665 /* elapsedRealtimeUs= */ UNUSED_TIME_MS, 666 /* shortString= */ false, 667 /* currentTimeMillis= */ currentTimeMillis); 668 669 assertThat(info.remainingLabel).isEqualTo(expectedLabel); 670 } 671 672 @Test getBatteryInfo_noCustomizedWLCLabel_updateRemainingLabelAndStatusLabel()673 public void getBatteryInfo_noCustomizedWLCLabel_updateRemainingLabelAndStatusLabel() { 674 prepareTestGetBatteryInfoEnvironment( 675 /* remainingTimeMs= */ Duration.ofHours(1).toMillis(), 676 /* chargingStringV2Enabled= */ true); 677 Intent batteryIntent = 678 createIntentForGetBatteryInfoTest( 679 ChargingType.WIRELESS, ChargingSpeed.REGULAR, /* batteryLevel= */ 45); 680 when(mFeatureFactory.batterySettingsFeatureProvider.getWirelessChargingRemainingLabel( 681 eq(mContext), anyLong(), anyLong())) 682 .thenReturn(null); 683 var expectedStatusLabel = "Charging"; 684 var expectedRemainingLabel = "Fully charged by"; 685 var expectedChargeLabel = "45% - " + expectedRemainingLabel; 686 var currentTimeMillis = Instant.parse("2024-04-01T15:00:00Z").toEpochMilli(); 687 688 assertGetBatteryInfo( 689 batteryIntent, 690 currentTimeMillis, 691 expectedStatusLabel, 692 expectedRemainingLabel, 693 expectedChargeLabel); 694 } 695 696 @Test getBatteryInfo_noCustomWirelessChargingLabelWithV1_updateRemainingAndStatusLabel()697 public void getBatteryInfo_noCustomWirelessChargingLabelWithV1_updateRemainingAndStatusLabel() { 698 prepareTestGetBatteryInfoEnvironment( 699 /* remainingTimeMs= */ Duration.ofMinutes(130).toMillis(), 700 /* chargingStringV2Enabled= */ false); 701 Intent batteryIntent = 702 createIntentForGetBatteryInfoTest( 703 ChargingType.WIRELESS, ChargingSpeed.REGULAR, /* batteryLevel= */ 10); 704 when(mFeatureFactory.batterySettingsFeatureProvider.getWirelessChargingRemainingLabel( 705 eq(mContext), anyLong(), anyLong())) 706 .thenReturn(null); 707 var expectedStatusLabel = "Charging wirelessly"; 708 var expectedRemainingLabel = "2 hr, 10 min left until full"; 709 var expectedChargeLabel = "10% - " + expectedRemainingLabel; 710 711 assertGetBatteryInfo( 712 batteryIntent, 713 /* currentTimeMillis= */ UNUSED_TIME_MS, 714 expectedStatusLabel, 715 expectedRemainingLabel, 716 expectedChargeLabel); 717 } 718 719 @Test getBatteryInfo_chargeOptimizationMode_updateRemainingAndStatusLabel()720 public void getBatteryInfo_chargeOptimizationMode_updateRemainingAndStatusLabel() { 721 prepareTestGetBatteryInfoEnvironment( 722 /* remainingTimeMs= */ Duration.ofMinutes(130).toMillis(), 723 /* chargingStringV2Enabled= */ false); 724 Intent batteryIntent = 725 createIntentForGetBatteryInfoTest( 726 ChargingType.WIRED, ChargingSpeed.REGULAR, /* batteryLevel= */ 65); 727 var expectedRemainingLabel = "Expected remaining label"; 728 var expectedChargeLabel = "65% - " + expectedRemainingLabel; 729 when(mFeatureFactory.batterySettingsFeatureProvider.isChargingOptimizationMode(mContext)) 730 .thenReturn(true); 731 when(mFeatureFactory.batterySettingsFeatureProvider.getChargingOptimizationRemainingLabel( 732 eq(mContext), anyInt(), anyInt(), anyLong(), anyLong())) 733 .thenReturn(expectedRemainingLabel); 734 when(mFeatureFactory.batterySettingsFeatureProvider.getChargingOptimizationChargeLabel( 735 eq(mContext), anyInt(), anyString(), anyLong(), anyLong())) 736 .thenReturn(expectedChargeLabel); 737 var expectedStatusLabel = "Charging"; 738 739 assertGetBatteryInfo( 740 batteryIntent, 741 /* currentTimeMillis= */ UNUSED_TIME_MS, 742 expectedStatusLabel, 743 expectedRemainingLabel, 744 expectedChargeLabel); 745 } 746 747 @Test getBatteryInfo_notChargeOptimizationModeWithV1_updateRemainingAndStatusLabel()748 public void getBatteryInfo_notChargeOptimizationModeWithV1_updateRemainingAndStatusLabel() { 749 prepareTestGetBatteryInfoEnvironment( 750 /* remainingTimeMs= */ Duration.ofMinutes(130).toMillis(), 751 /* chargingStringV2Enabled= */ false); 752 Intent batteryIntent = 753 createIntentForGetBatteryInfoTest( 754 ChargingType.WIRED, ChargingSpeed.REGULAR, /* batteryLevel= */ 65); 755 when(mFeatureFactory.batterySettingsFeatureProvider.isChargingOptimizationMode(mContext)) 756 .thenReturn(false); 757 var expectedStatusLabel = "Charging"; 758 var expectedRemainingLabel = "2 hr, 10 min left until full"; 759 var expectedChargeLabel = "65% - " + expectedRemainingLabel; 760 761 assertGetBatteryInfo( 762 batteryIntent, 763 /* currentTimeMillis= */ UNUSED_TIME_MS, 764 expectedStatusLabel, 765 expectedRemainingLabel, 766 expectedChargeLabel); 767 } 768 769 @Test getBatteryInfo_notChargeOptimizationModeWithV2_updateRemainingAndStatusLabel()770 public void getBatteryInfo_notChargeOptimizationModeWithV2_updateRemainingAndStatusLabel() { 771 prepareTestGetBatteryInfoEnvironment( 772 /* remainingTimeMs= */ Duration.ofMinutes(130).toMillis(), 773 /* chargingStringV2Enabled= */ true); 774 Intent batteryIntent = 775 createIntentForGetBatteryInfoTest( 776 ChargingType.WIRED, ChargingSpeed.REGULAR, /* batteryLevel= */ 65); 777 when(mFeatureFactory.batterySettingsFeatureProvider.isChargingOptimizationMode(mContext)) 778 .thenReturn(false); 779 var expectedStatusLabel = "Charging"; 780 var expectedRemainingLabel = "Fully charged by"; 781 var expectedChargeLabel = "65% - " + expectedRemainingLabel; 782 var currentTimeMillis = Instant.parse("2024-04-01T15:00:00Z").toEpochMilli(); 783 784 assertGetBatteryInfo( 785 batteryIntent, 786 currentTimeMillis, 787 expectedStatusLabel, 788 expectedRemainingLabel, 789 expectedChargeLabel); 790 } 791 792 private enum ChargingSpeed { 793 FAST, 794 REGULAR, 795 SLOW 796 } 797 798 private enum ChargingType { 799 WIRED, 800 WIRELESS, 801 DOCKED 802 } 803 createIntentForGetBatteryInfoTest( ChargingType chargingType, ChargingSpeed chargingSpeed, int batteryLevel)804 private Intent createIntentForGetBatteryInfoTest( 805 ChargingType chargingType, ChargingSpeed chargingSpeed, int batteryLevel) { 806 return createBatteryIntent( 807 CHARGING_TYPE_MAP.get(chargingType), 808 batteryLevel, 809 BatteryManager.BATTERY_STATUS_CHARGING) 810 .putExtra( 811 BatteryManager.EXTRA_MAX_CHARGING_CURRENT, 812 CHARGING_SPEED_MAP.get(chargingSpeed)) 813 .putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, 5000000); 814 } 815 prepareTestGetBatteryInfoEnvironment( long remainingTimeMs, boolean chargingStringV2Enabled)816 private void prepareTestGetBatteryInfoEnvironment( 817 long remainingTimeMs, boolean chargingStringV2Enabled) { 818 when(mBatteryUsageStats.getChargeTimeRemainingMs()).thenReturn(remainingTimeMs); 819 SystemProperties.set( 820 com.android.settingslib.fuelgauge.BatteryUtils.PROPERTY_CHARGING_STRING_V2_KEY, 821 String.valueOf(chargingStringV2Enabled)); 822 Settings.Global.putInt( 823 mContext.getContentResolver(), 824 BatteryUtils.SETTINGS_GLOBAL_DOCK_DEFENDER_BYPASS, 825 1); 826 } 827 assertGetBatteryInfo( Intent batteryIntent, long currentTimeMillis, String expectedStatusLabel, String expectedRemainingLabel, String expectedChargeLabel)828 private void assertGetBatteryInfo( 829 Intent batteryIntent, 830 long currentTimeMillis, 831 String expectedStatusLabel, 832 String expectedRemainingLabel, 833 String expectedChargeLabel) { 834 mContext.getResources().getConfiguration().setLocale(Locale.US); 835 TimeZone.setDefault(TimeZone.getTimeZone("UTC")); 836 var info = 837 BatteryInfo.getBatteryInfo( 838 mContext, 839 batteryIntent, 840 mBatteryUsageStats, 841 MOCK_ESTIMATE, 842 /* elapsedRealtimeUs= */ UNUSED_TIME_MS, 843 /* shortString= */ false, 844 /* currentTimeMillis= */ currentTimeMillis); 845 846 assertWithMessage("statusLabel is incorrect") 847 .that(info.statusLabel) 848 .isEqualTo(expectedStatusLabel); 849 assertWithMessage("remainingLabel is incorrect") 850 .that(info.remainingLabel.toString()) 851 .contains(expectedRemainingLabel); 852 assertWithMessage("chargeLabel is incorrect") 853 .that(info.chargeLabel.toString()) 854 .contains(expectedChargeLabel); 855 } 856 createBatteryIntent(int plugged, int level, int status)857 private static Intent createBatteryIntent(int plugged, int level, int status) { 858 return new Intent() 859 .putExtra(BatteryManager.EXTRA_PLUGGED, plugged) 860 .putExtra(BatteryManager.EXTRA_LEVEL, level) 861 .putExtra(BatteryManager.EXTRA_SCALE, 100) 862 .putExtra(BatteryManager.EXTRA_STATUS, status); 863 } 864 865 // Make our battery stats return a sequence of battery events. mockBatteryStatsHistory()866 private void mockBatteryStatsHistory() { 867 // Mock out new data every time iterateBatteryStatsHistory is called. 868 doAnswer( 869 invocation -> { 870 BatteryStatsHistoryIterator iterator = 871 mock(BatteryStatsHistoryIterator.class); 872 when(iterator.next()) 873 .thenReturn( 874 makeHistoryIterm(1000, 99), 875 makeHistoryIterm(1500, 98), 876 makeHistoryIterm(2000, 97), 877 null); 878 return iterator; 879 }) 880 .when(mBatteryUsageStats) 881 .iterateBatteryStatsHistory(); 882 } 883 makeHistoryIterm(long time, int batteryLevel)884 private BatteryStats.HistoryItem makeHistoryIterm(long time, int batteryLevel) { 885 BatteryStats.HistoryItem record = new BatteryStats.HistoryItem(); 886 record.cmd = BatteryStats.HistoryItem.CMD_UPDATE; 887 record.time = time; 888 record.batteryLevel = (byte) batteryLevel; 889 return record; 890 } 891 assertOnlyHistory(BatteryInfo info)892 private void assertOnlyHistory(BatteryInfo info) { 893 mockBatteryStatsHistory(); 894 UsageView view = mock(UsageView.class); 895 when(view.getContext()).thenReturn(mContext); 896 897 info.bindHistory(view); 898 verify(view, times(1)).configureGraph(anyInt(), anyInt()); 899 verify(view, times(1)).addPath(any(SparseIntArray.class)); 900 verify(view, never()).addProjectedPath(any(SparseIntArray.class)); 901 } 902 assertHistoryAndLinearProjection(BatteryInfo info)903 private void assertHistoryAndLinearProjection(BatteryInfo info) { 904 mockBatteryStatsHistory(); 905 UsageView view = mock(UsageView.class); 906 when(view.getContext()).thenReturn(mContext); 907 908 info.bindHistory(view); 909 verify(view, times(2)).configureGraph(anyInt(), anyInt()); 910 verify(view, times(1)).addPath(any(SparseIntArray.class)); 911 ArgumentCaptor<SparseIntArray> pointsActual = ArgumentCaptor.forClass(SparseIntArray.class); 912 verify(view, times(1)).addProjectedPath(pointsActual.capture()); 913 914 // Check that we have two points and the first is correct. 915 assertThat(pointsActual.getValue().size()).isEqualTo(2); 916 assertThat(pointsActual.getValue().keyAt(0)).isEqualTo(2000); 917 assertThat(pointsActual.getValue().valueAt(0)).isEqualTo(97); 918 } 919 assertHistoryAndEnhancedProjection(BatteryInfo info)920 private void assertHistoryAndEnhancedProjection(BatteryInfo info) { 921 mockBatteryStatsHistory(); 922 UsageView view = mock(UsageView.class); 923 when(view.getContext()).thenReturn(mContext); 924 SparseIntArray pointsExpected = new SparseIntArray(); 925 pointsExpected.append(2000, 96); 926 pointsExpected.append(2500, 95); 927 pointsExpected.append(3000, 94); 928 doReturn(pointsExpected) 929 .when(mFeatureFactory.powerUsageFeatureProvider) 930 .getEnhancedBatteryPredictionCurve(any(Context.class), anyLong()); 931 932 info.bindHistory(view); 933 verify(view, times(2)).configureGraph(anyInt(), anyInt()); 934 verify(view, times(1)).addPath(any(SparseIntArray.class)); 935 ArgumentCaptor<SparseIntArray> pointsActual = ArgumentCaptor.forClass(SparseIntArray.class); 936 verify(view, times(1)).addProjectedPath(pointsActual.capture()); 937 assertThat(pointsActual.getValue()).isEqualTo(pointsExpected); 938 } 939 getBatteryInfo(boolean charging, boolean enhanced, boolean estimate)940 private BatteryInfo getBatteryInfo(boolean charging, boolean enhanced, boolean estimate) { 941 if (charging && estimate) { 942 doReturn(1000L).when(mBatteryUsageStats).getChargeTimeRemainingMs(); 943 } else { 944 doReturn(0L).when(mBatteryUsageStats).getChargeTimeRemainingMs(); 945 } 946 Estimate batteryEstimate = 947 new Estimate( 948 estimate ? 1000 : 0, 949 false /* isBasedOnUsage */, 950 1000 /* averageDischargeTime */); 951 BatteryInfo info = 952 BatteryInfo.getBatteryInfo( 953 mContext, 954 charging ? mChargingBatteryBroadcast : mDisChargingBatteryBroadcast, 955 mBatteryUsageStats, 956 batteryEstimate, 957 SystemClock.elapsedRealtime() * 1000, 958 false); 959 doReturn(enhanced) 960 .when(mFeatureFactory.powerUsageFeatureProvider) 961 .isEnhancedBatteryPredictionEnabled(mContext); 962 return info; 963 } 964 965 @Test testBindHistory()966 public void testBindHistory() { 967 BatteryInfo info; 968 969 info = getBatteryInfo(false /* charging */, false /* enhanced */, false /* estimate */); 970 assertOnlyHistory(info); 971 972 info = getBatteryInfo(false /* charging */, false /* enhanced */, true /* estimate */); 973 assertHistoryAndLinearProjection(info); 974 975 info = getBatteryInfo(false /* charging */, true /* enhanced */, false /* estimate */); 976 assertOnlyHistory(info); 977 978 info = getBatteryInfo(false /* charging */, true /* enhanced */, true /* estimate */); 979 assertHistoryAndEnhancedProjection(info); 980 981 info = getBatteryInfo(true /* charging */, false /* enhanced */, false /* estimate */); 982 assertOnlyHistory(info); 983 984 info = getBatteryInfo(true /* charging */, false /* enhanced */, true /* estimate */); 985 assertHistoryAndLinearProjection(info); 986 987 info = getBatteryInfo(true /* charging */, true /* enhanced */, false /* estimate */); 988 assertOnlyHistory(info); 989 990 // Linear projection for charging even in enhanced mode. 991 info = getBatteryInfo(true /* charging */, true /* enhanced */, true /* estimate */); 992 assertHistoryAndLinearProjection(info); 993 } 994 } 995