From 6f3c97f60a966a8ec357ba4d15b19d9d7a04b34d Mon Sep 17 00:00:00 2001 From: Pajace Chen Date: Sat, 6 Apr 2024 09:17:59 +0800 Subject: [PATCH] Add charging string V2 for settings Apply charging string V2 for settings Bug: 328546483 Test: Manual test Change-Id: Ic68bf4231da81d865faa285bca97a929abe26a42 --- .../BatteryHeaderPreferenceController.java | 8 + .../settings/fuelgauge/BatteryInfo.java | 135 +++++-- ...BatteryHeaderPreferenceControllerTest.java | 65 ++++ .../settings/fuelgauge/BatteryInfoTest.java | 342 ++++++++++++++++-- 4 files changed, 504 insertions(+), 46 deletions(-) diff --git a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java index 907c89faec0..d77ec31084c 100644 --- a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java @@ -86,6 +86,14 @@ public class BatteryHeaderPreferenceController extends BasePreferenceController return info.statusLabel; } else if (info.statusLabel != null && !info.discharging) { // Charging state + if (com.android.settingslib.fuelgauge.BatteryUtils.isChargingStringV2Enabled()) { + return info.isFastCharging + ? mContext.getString( + R.string.battery_state_and_duration, + info.statusLabel, + info.remainingLabel) + : info.remainingLabel; + } return mContext.getString( R.string.battery_state_and_duration, info.statusLabel, info.remainingLabel); } else if (mPowerManager.isPowerSaveMode()) { diff --git a/src/com/android/settings/fuelgauge/BatteryInfo.java b/src/com/android/settings/fuelgauge/BatteryInfo.java index ea8ef8d9a8e..904923aecdf 100644 --- a/src/com/android/settings/fuelgauge/BatteryInfo.java +++ b/src/com/android/settings/fuelgauge/BatteryInfo.java @@ -37,6 +37,8 @@ import com.android.internal.os.BatteryStatsHistoryIterator; import com.android.settings.Utils; import com.android.settings.overlay.FeatureFactory; import com.android.settings.widget.UsageView; +import com.android.settingslib.R; +import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.settingslib.fuelgauge.Estimate; import com.android.settingslib.fuelgauge.EstimateKt; import com.android.settingslib.utils.PowerUtil; @@ -52,6 +54,7 @@ public class BatteryInfo { public int pluggedStatus; public boolean discharging = true; public boolean isBatteryDefender; + public boolean isFastCharging; public long remainingTimeUs = 0; public long averageTimeToDischarge = EstimateKt.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN; public String batteryPercentString; @@ -143,13 +146,13 @@ public class BatteryInfo { parseBatteryHistory(parserList); String timeString = context.getString( - com.android.settingslib.R.string.charge_length_format, + R.string.charge_length_format, Formatter.formatShortElapsedTime(context, timePeriod)); String remaining = ""; if (remainingTimeUs != 0) { remaining = context.getString( - com.android.settingslib.R.string.remaining_length_format, + R.string.remaining_length_format, Formatter.formatShortElapsedTime(context, remainingTimeUs / 1000)); } view.setBottomLabels(new CharSequence[] {timeString, remaining}); @@ -291,8 +294,8 @@ public class BatteryInfo { @NonNull BatteryUsageStats batteryUsageStats, Estimate estimate, long elapsedRealtimeUs, - boolean shortString) { - final long startTime = System.currentTimeMillis(); + boolean shortString, + long currentTimeMs) { final boolean isCompactStatus = context.getResources() .getBoolean(com.android.settings.R.bool.config_use_compact_battery_status); @@ -313,22 +316,51 @@ public class BatteryInfo { info.batteryStatus = batteryBroadcast.getIntExtra( BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN); + info.isFastCharging = + BatteryStatus.getChargingSpeed(context, batteryBroadcast) + == BatteryStatus.CHARGING_FAST; if (!info.mCharging) { updateBatteryInfoDischarging(context, shortString, estimate, info); } else { updateBatteryInfoCharging( - context, batteryBroadcast, batteryUsageStats, info, isCompactStatus); + context, + batteryBroadcast, + batteryUsageStats, + info, + isCompactStatus, + currentTimeMs); } - BatteryUtils.logRuntime(LOG_TAG, "time for getBatteryInfo", startTime); + BatteryUtils.logRuntime(LOG_TAG, "time for getBatteryInfo", currentTimeMs); return info; } + /** Returns a {@code BatteryInfo} with battery and charging relative information. */ + @WorkerThread + public static BatteryInfo getBatteryInfo( + Context context, + Intent batteryBroadcast, + BatteryUsageStats batteryUsageStats, + Estimate estimate, + long elapsedRealtimeUs, + boolean shortString) { + long currentTimeMs = System.currentTimeMillis(); + return getBatteryInfo( + context, + batteryBroadcast, + batteryUsageStats, + estimate, + elapsedRealtimeUs, + shortString, + currentTimeMs); + } + private static void updateBatteryInfoCharging( Context context, Intent batteryBroadcast, BatteryUsageStats stats, BatteryInfo info, - boolean compactStatus) { + boolean compactStatus, + long currentTimeMs) { final Resources resources = context.getResources(); final long chargeTimeMs = stats.getChargeTimeRemainingMs(); if (getSettingsChargeTimeRemaining(context) != chargeTimeMs) { @@ -350,7 +382,7 @@ public class BatteryInfo { || dockDefenderMode == BatteryUtils.DockDefenderMode.ACTIVE) { // Battery defender active, battery charging paused info.remainingLabel = null; - int chargingLimitedResId = com.android.settingslib.R.string.power_charging_limited; + int chargingLimitedResId = R.string.power_charging_limited; info.chargeLabel = context.getString(chargingLimitedResId, info.batteryPercentString); } else if ((chargeTimeMs > 0 && status != BatteryManager.BATTERY_STATUS_FULL @@ -358,30 +390,29 @@ public class BatteryInfo { || dockDefenderMode == BatteryUtils.DockDefenderMode.TEMPORARILY_BYPASSED) { // Battery is charging to full info.remainingTimeUs = PowerUtil.convertMsToUs(chargeTimeMs); - final CharSequence timeString = - StringUtil.formatElapsedTime( - context, - (double) PowerUtil.convertUsToMs(info.remainingTimeUs), - false /* withSeconds */, - true /* collapseTimeUnit */); - int resId = com.android.settingslib.R.string.power_charging_duration; + + int resId = getChargingDurationResId(info.isFastCharging); info.remainingLabel = chargeTimeMs <= 0 ? null - : context.getString( - com.android.settingslib.R.string - .power_remaining_charging_duration_only, - timeString); + : getPowerRemainingChargingLabel( + context, chargeTimeMs, info.isFastCharging, currentTimeMs); + info.chargeLabel = chargeTimeMs <= 0 ? info.batteryPercentString - : context.getString(resId, info.batteryPercentString, timeString); + : getChargeLabelWithTimeToFull( + context, + resId, + info.batteryPercentString, + chargeTimeMs, + info.isFastCharging, + currentTimeMs); } else if (dockDefenderMode == BatteryUtils.DockDefenderMode.FUTURE_BYPASS) { // Dock defender will be triggered in the future, charging will be optimized. info.chargeLabel = context.getString( - com.android.settingslib.R.string.power_charging_future_paused, - info.batteryPercentString); + R.string.power_charging_future_paused, info.batteryPercentString); } else { final String chargeStatusLabel = Utils.getBatteryStatus(context, batteryBroadcast, compactStatus); @@ -390,12 +421,70 @@ public class BatteryInfo { info.batteryLevel == 100 ? info.batteryPercentString : resources.getString( - com.android.settingslib.R.string.power_charging, + R.string.power_charging, info.batteryPercentString, chargeStatusLabel); } } + private static CharSequence getPowerRemainingChargingLabel( + Context context, long remainingTimeMs, boolean isFastCharging, long currentTimeMs) { + if (com.android.settingslib.fuelgauge.BatteryUtils.isChargingStringV2Enabled()) { + int chargeLabelResId = + isFastCharging + ? R.string.power_remaining_fast_charging_duration_only_v2 + : R.string.power_remaining_charging_duration_only_v2; + String timeString = + PowerUtil.getTargetTimeShortString(context, remainingTimeMs, currentTimeMs); + return context.getString(chargeLabelResId, timeString); + } + final CharSequence timeString = + StringUtil.formatElapsedTime( + context, + remainingTimeMs, + /* withSeconds= */ false, + /* collapseTimeUnit= */ true); + return context.getString(R.string.power_remaining_charging_duration_only, timeString); + } + + private static CharSequence getChargeLabelWithTimeToFull( + Context context, + int chargeLabelResId, + String batteryPercentString, + long chargeTimeMs, + boolean isFastCharging, + long currentTimeMs) { + if (com.android.settingslib.fuelgauge.BatteryUtils.isChargingStringV2Enabled()) { + var timeString = + PowerUtil.getTargetTimeShortString(context, chargeTimeMs, currentTimeMs); + + return isFastCharging + ? context.getString( + chargeLabelResId, + batteryPercentString, + context.getString(R.string.battery_info_status_charging_fast_v2), + timeString) + : context.getString(chargeLabelResId, batteryPercentString, timeString); + } else { + var timeString = + StringUtil.formatElapsedTime( + context, + (double) chargeTimeMs, + /* withSeconds= */ false, + /* collapseTimeUnit= */ true); + return context.getString(chargeLabelResId, batteryPercentString, timeString); + } + } + + private static int getChargingDurationResId(boolean isFastCharging) { + if (com.android.settingslib.fuelgauge.BatteryUtils.isChargingStringV2Enabled()) { + return isFastCharging + ? R.string.power_fast_charging_duration_v2 + : R.string.power_charging_duration_v2; + } + return R.string.power_charging_duration; + } + private static void updateBatteryInfoDischarging( Context context, boolean shortString, Estimate estimate, BatteryInfo info) { final long drainTimeUs = PowerUtil.convertMsToUs(estimate.getEstimateMillis()); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java index 9457f99a42d..84f5dbad32e 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java @@ -30,6 +30,7 @@ import android.hardware.usb.UsbPort; import android.hardware.usb.UsbPortStatus; import android.os.BatteryManager; import android.os.PowerManager; +import android.os.SystemProperties; import androidx.preference.PreferenceScreen; @@ -42,6 +43,7 @@ import com.android.settings.testutils.BatteryTestUtils; import com.android.settings.testutils.shadow.ShadowEntityHeaderController; import com.android.settings.testutils.shadow.ShadowUtils; import com.android.settings.widget.EntityHeaderController; +import com.android.settingslib.fuelgauge.BatteryUtils; import com.android.settingslib.widget.UsageProgressBarPreference; import org.junit.After; @@ -105,6 +107,8 @@ public class BatteryHeaderPreferenceControllerTest { mController = spy(new BatteryHeaderPreferenceController(mContext, PREF_KEY)); mController.mBatteryUsageProgressBarPref = mBatteryUsageProgressBarPref; mController.mBatteryStatusFeatureProvider = mBatteryStatusFeatureProvider; + + BatteryUtils.setChargingStringV2Enabled(null); } @After @@ -223,6 +227,67 @@ public class BatteryHeaderPreferenceControllerTest { verify(mBatteryUsageProgressBarPref).setBottomSummary(label); } + @Test + public void updateBatteryStatus_chargingString_statusWithRemainingLabel() { + var batteryInfo = + arrangeUpdateBatteryStatusTestWithRemainingLabel( + /* remainingLabel= */ "1 hr, 40 min left until full", + /* statusLabel= */ "Charging rapidly", + /* isFastCharging= */ true, + /* isChargingStringV2= */ false); + var expectedChargingString = batteryInfo.statusLabel + " • " + batteryInfo.remainingLabel; + + mController.updateBatteryStatus(/* label= */ null, batteryInfo); + + verify(mBatteryUsageProgressBarPref).setBottomSummary(expectedChargingString); + } + + @Test + public void updateBatteryStatus_chargingStringV2FastCharging_statusWithRemainingLabel() { + var batteryInfo = + arrangeUpdateBatteryStatusTestWithRemainingLabel( + /* remainingLabel= */ "Full by 1:30 PM", + /* statusLabel= */ "Fast Charging", + /* isFastCharging= */ true, + /* isChargingStringV2= */ true); + var expectedChargingString = batteryInfo.statusLabel + " • " + batteryInfo.remainingLabel; + + mController.updateBatteryStatus(/* label= */ null, batteryInfo); + + verify(mBatteryUsageProgressBarPref).setBottomSummary(expectedChargingString); + } + + @Test + public void updateBatteryStatus_chargingStringV2NonFastCharging_remainingLabel() { + var batteryInfo = + arrangeUpdateBatteryStatusTestWithRemainingLabel( + /* remainingLabel= */ "Fully charged by 11:10 PM", + /* statusLabel= */ "Charging", + /* isFastCharging= */ false, + /* isChargingStringV2= */ true); + var expectedChargingString = batteryInfo.remainingLabel; + + mController.updateBatteryStatus(/* label= */ null, batteryInfo); + + verify(mBatteryUsageProgressBarPref).setBottomSummary(expectedChargingString); + } + + private BatteryInfo arrangeUpdateBatteryStatusTestWithRemainingLabel( + String remainingLabel, + String statusLabel, + boolean isFastCharging, + boolean isChargingStringV2) { + SystemProperties.set( + BatteryUtils.PROPERTY_CHARGING_STRING_V2_KEY, + String.valueOf(isChargingStringV2)); + mBatteryInfo.isBatteryDefender = false; + mBatteryInfo.remainingLabel = remainingLabel; + mBatteryInfo.statusLabel = statusLabel; + mBatteryInfo.discharging = false; + mBatteryInfo.isFastCharging = isFastCharging; + return mBatteryInfo; + } + @Test public void updateHeaderByBatteryTips_lowBatteryTip_showLowBattery() { setChargingState(/* isDischarging */ true, /* updatedByStatusFeature */ false); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java index e99c4e0a778..e6325968b33 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java @@ -17,6 +17,7 @@ package com.android.settings.fuelgauge; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; @@ -30,12 +31,14 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.AlarmManager; import android.content.Context; import android.content.Intent; import android.os.BatteryManager; import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.SystemClock; +import android.os.SystemProperties; import android.provider.Settings; import android.util.SparseIntArray; @@ -56,6 +59,9 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import java.time.Duration; +import java.time.Instant; +import java.util.Locale; +import java.util.Map; import java.util.concurrent.TimeUnit; @RunWith(RobolectricTestRunner.class) @@ -80,11 +86,23 @@ public class BatteryInfoTest { 1000, /* estimateMillis */ false, /* isBasedOnUsage */ 1000 /* averageDischargeTime */); + private static final Map CHARGING_TYPE_MAP = + Map.of( + ChargingType.WIRED, BatteryManager.BATTERY_PLUGGED_AC, + ChargingType.WIRELESS, BatteryManager.BATTERY_PLUGGED_WIRELESS, + ChargingType.DOCKED, BatteryManager.BATTERY_PLUGGED_DOCK); + private static final Map CHARGING_SPEED_MAP = + Map.of( + ChargingSpeed.FAST, 1501000, + ChargingSpeed.REGULAR, 1500000, + ChargingSpeed.SLOW, 999999); + private static final long UNUSED_TIME_MS = -1L; private Intent mDisChargingBatteryBroadcast; private Intent mChargingBatteryBroadcast; private Context mContext; private FakeFeatureFactory mFeatureFactory; + @Mock private BatteryUsageStats mBatteryUsageStats; @Before @@ -102,10 +120,13 @@ public class BatteryInfoTest { mContext.getContentResolver(), BatteryUtils.SETTINGS_GLOBAL_DOCK_DEFENDER_BYPASS, 0); + + // Reset static cache for testing purpose. + com.android.settingslib.fuelgauge.BatteryUtils.setChargingStringV2Enabled(null); } @Test - public void testGetBatteryInfo_hasStatusLabel() { + public void getBatteryInfo_hasStatusLabel() { doReturn(REMAINING_TIME_NULL).when(mBatteryUsageStats).getBatteryTimeRemainingMs(); BatteryInfo info = BatteryInfo.getBatteryInfoOld( @@ -119,7 +140,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_doNotShowChargingMethod_hasRemainingTime() { + public void getBatteryInfo_doNotShowChargingMethod_hasRemainingTime() { doReturn(REMAINING_TIME).when(mBatteryUsageStats).getChargeTimeRemainingMs(); BatteryInfo info = BatteryInfo.getBatteryInfoOld( @@ -133,7 +154,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_doNotShowChargingMethod_noRemainingTime() { + public void getBatteryInfo_doNotShowChargingMethod_noRemainingTime() { doReturn(REMAINING_TIME_NULL).when(mBatteryUsageStats).getChargeTimeRemainingMs(); BatteryInfo info = BatteryInfo.getBatteryInfoOld( @@ -147,7 +168,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_pluggedInUsingShortString_usesCorrectData() { + public void getBatteryInfo_pluggedInUsingShortString_usesCorrectData() { doReturn(TEST_CHARGE_TIME_REMAINING / 1000) .when(mBatteryUsageStats) .getChargeTimeRemainingMs(); @@ -164,7 +185,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_basedOnUsageTrueMoreThanFifteenMinutes_usesCorrectString() { + public void getBatteryInfo_basedOnUsageTrueMoreThanFifteenMinutes_usesCorrectString() { Estimate estimate = new Estimate( Duration.ofHours(4).toMillis(), @@ -215,7 +236,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_basedOnUsageFalse_usesDefaultString() { + public void getBatteryInfo_basedOnUsageFalse_usesDefaultString() { BatteryInfo info = BatteryInfo.getBatteryInfo( mContext, @@ -238,7 +259,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_charging_usesChargeTime() { + public void getBatteryInfo_charging_usesChargeTime() { doReturn(TEST_CHARGE_TIME_REMAINING / 1000) .when(mBatteryUsageStats) .getChargeTimeRemainingMs(); @@ -258,7 +279,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_pluggedInWithFullBattery_onlyShowBatteryLevel() { + public void getBatteryInfo_pluggedInWithFullBattery_onlyShowBatteryLevel() { mChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_LEVEL, 100); BatteryInfo info = @@ -274,7 +295,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_chargingWithDefender_updateChargeLabel() { + public void getBatteryInfo_chargingWithDefender_updateChargeLabel() { doReturn(TEST_CHARGE_TIME_REMAINING).when(mBatteryUsageStats).getChargeTimeRemainingMs(); mChargingBatteryBroadcast.putExtra( BatteryManager.EXTRA_CHARGING_STATUS, @@ -294,7 +315,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_getChargeTimeRemaining_updateSettingsGlobal() { + public void getBatteryInfo_getChargeTimeRemaining_updateSettingsGlobal() { doReturn(TEST_CHARGE_TIME_REMAINING).when(mBatteryUsageStats).getChargeTimeRemainingMs(); BatteryInfo.getBatteryInfo( @@ -310,7 +331,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_differentChargeTimeRemaining_updateSettingsGlobal() { + public void getBatteryInfo_differentChargeTimeRemaining_updateSettingsGlobal() { doReturn(TEST_CHARGE_TIME_REMAINING).when(mBatteryUsageStats).getChargeTimeRemainingMs(); final long newTimeToFull = 300L; doReturn(newTimeToFull).when(mBatteryUsageStats).getChargeTimeRemainingMs(); @@ -327,16 +348,15 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_dockDefenderActive_updateChargeString() { + public void getBatteryInfo_dockDefenderActive_updateChargeString() { doReturn(TEST_CHARGE_TIME_REMAINING / 1000) .when(mBatteryUsageStats) .getChargeTimeRemainingMs(); doReturn(true).when(mFeatureFactory.powerUsageFeatureProvider).isExtraDefend(); Intent intent = - BatteryTestUtils.getCustomBatteryIntent( + createBatteryIntent( BatteryManager.BATTERY_PLUGGED_DOCK, - 50 /* level */, - 100 /* scale */, + /* level= */ 50, BatteryManager.BATTERY_STATUS_CHARGING) .putExtra( BatteryManager.EXTRA_CHARGING_STATUS, @@ -355,7 +375,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_dockDefenderTemporarilyBypassed_updateChargeLabel() { + public void getBatteryInfo_dockDefenderTemporarilyBypassed_updateChargeLabel() { doReturn(REMAINING_TIME).when(mBatteryUsageStats).getChargeTimeRemainingMs(); mChargingBatteryBroadcast.putExtra( BatteryManager.EXTRA_CHARGING_STATUS, BatteryManager.CHARGING_POLICY_DEFAULT); @@ -367,10 +387,9 @@ public class BatteryInfoTest { BatteryInfo info = BatteryInfo.getBatteryInfo( mContext, - BatteryTestUtils.getCustomBatteryIntent( + createBatteryIntent( BatteryManager.BATTERY_PLUGGED_DOCK, - 50 /* level */, - 100 /* scale */, + /* level= */ 50, BatteryManager.BATTERY_STATUS_CHARGING), mBatteryUsageStats, MOCK_ESTIMATE, @@ -381,7 +400,7 @@ public class BatteryInfoTest { } @Test - public void testGetBatteryInfo_dockDefenderFutureBypass_updateChargeLabel() { + public void getBatteryInfo_dockDefenderFutureBypass_updateChargeLabel() { doReturn(false).when(mFeatureFactory.powerUsageFeatureProvider).isExtraDefend(); mChargingBatteryBroadcast.putExtra( BatteryManager.EXTRA_CHARGING_STATUS, BatteryManager.CHARGING_POLICY_DEFAULT); @@ -389,10 +408,9 @@ public class BatteryInfoTest { BatteryInfo info = BatteryInfo.getBatteryInfo( mContext, - BatteryTestUtils.getCustomBatteryIntent( + createBatteryIntent( BatteryManager.BATTERY_PLUGGED_DOCK, - 50 /* level */, - 100 /* scale */, + /* level= */ 50, BatteryManager.BATTERY_STATUS_CHARGING), mBatteryUsageStats, MOCK_ESTIMATE, @@ -402,6 +420,284 @@ public class BatteryInfoTest { assertThat(info.chargeLabel.toString()).contains(STATUS_CHARGING_FUTURE_BYPASS); } + @Test + public void getBatteryInfo_fastCharging_updateRemainingLabelAndStatusLabel() { + prepareTestGetBatteryInfoEnvironment( + /* remainingTimeMs= */ Duration.ofMinutes(90).toMillis(), + /* chargingStringV2Enabled= */ false); + Intent batteryIntent = + createIntentForGetBatteryInfoTest( + ChargingType.WIRED, ChargingSpeed.FAST, /* batteryLevel= */ 61); + var expectedStatusLabel = "Charging rapidly"; + var expectedRemainingLabel = "1 hr, 30 min left until full"; + var expectedChargeLabel = "61% - " + expectedRemainingLabel; + + assertGetBatteryInfo( + batteryIntent, + /* currentTimeMillis= */ UNUSED_TIME_MS, + expectedStatusLabel, + expectedRemainingLabel, + expectedChargeLabel); + } + + @Test + public void getBatteryInfo_regularCharging_updateRemainingLabelAndStatusLabel() { + prepareTestGetBatteryInfoEnvironment( + /* remainingTimeMs= */ Duration.ofMinutes(80).toMillis(), + /* chargingStringV2Enabled= */ false); + Intent batteryIntent = + createIntentForGetBatteryInfoTest( + ChargingType.WIRED, ChargingSpeed.REGULAR, /* batteryLevel= */ 33); + var expectedStatusLabel = "Charging"; + var expectedRemainingLabel = "1 hr, 20 min left until full"; + var expectedChargeLabel = "33% - " + expectedRemainingLabel; + + assertGetBatteryInfo( + batteryIntent, + /* currentTimeMillis= */ UNUSED_TIME_MS, + expectedStatusLabel, + expectedRemainingLabel, + expectedChargeLabel); + } + + @Test + public void getBatteryInfo_slowCharging_updateRemainingLabelAndStatusLabel() { + prepareTestGetBatteryInfoEnvironment( + /* remainingTimeMs= */ Duration.ofMinutes(100).toMillis(), + /* chargingStringV2Enabled= */ false); + Intent batteryIntent = + createIntentForGetBatteryInfoTest( + ChargingType.WIRED, ChargingSpeed.SLOW, /* batteryLevel= */ 53); + var expectedStatusLabel = "Charging slowly"; + var expectedRemainingLabel = "1 hr, 40 min left until full"; + var expectedChargeLabel = "53% - " + expectedRemainingLabel; + + assertGetBatteryInfo( + batteryIntent, + /* currentTimeMillis= */ UNUSED_TIME_MS, + expectedStatusLabel, + expectedRemainingLabel, + expectedChargeLabel); + } + + @Test + public void getBatteryInfo_wirelessCharging_updateRemainingLabelAndStatusLabel() { + prepareTestGetBatteryInfoEnvironment( + /* remainingTimeMs= */ Duration.ofMinutes(130).toMillis(), + /* chargingStringV2Enabled= */ false); + Intent batteryIntent = + createIntentForGetBatteryInfoTest( + ChargingType.WIRELESS, ChargingSpeed.REGULAR, /* batteryLevel= */ 10); + var expectedStatusLabel = "Charging wirelessly"; + var expectedRemainingLabel = "2 hr, 10 min left until full"; + var expectedChargeLabel = "10% - " + expectedRemainingLabel; + + assertGetBatteryInfo( + batteryIntent, + /* currentTimeMillis= */ UNUSED_TIME_MS, + expectedStatusLabel, + expectedRemainingLabel, + expectedChargeLabel); + } + + @Test + public void getBatteryInfo_dockedCharging_updateRemainingLabelAndStatusLabel() { + prepareTestGetBatteryInfoEnvironment( + /* remainingTimeMs= */ Duration.ofMinutes(30).toMillis(), + /* chargingStringV2Enabled= */ false); + Intent batteryIntent = + createIntentForGetBatteryInfoTest( + ChargingType.DOCKED, ChargingSpeed.REGULAR, /* batteryLevel= */ 51); + var expectedStatusLabel = "Charging"; + var expectedRemainingLabel = "30 min left until full"; + var expectedChargeLabel = "51% - " + expectedRemainingLabel; + + assertGetBatteryInfo( + batteryIntent, + /* currentTimeMillis= */ UNUSED_TIME_MS, + expectedStatusLabel, + expectedRemainingLabel, + expectedChargeLabel); + } + + @Test + public void getBatteryInfo_fastChargingV2_updateRemainingLabelAndStatusLabel() { + prepareTestGetBatteryInfoEnvironment( + /* remainingTimeMs= */ Duration.ofMinutes(30).toMillis(), + /* chargingStringV2Enabled= */ true); + Intent batteryIntent = + createIntentForGetBatteryInfoTest( + ChargingType.WIRED, ChargingSpeed.FAST, /* batteryLevel= */ 56); + var expectedStatusLabel = "Fast charging"; + var expectedRemainingLabel = "Full by 1:30 PM"; + var expectedChargeLabel = "56% - " + expectedStatusLabel + " - " + expectedRemainingLabel; + var currentTimeMillis = Instant.parse("2024-04-01T13:00:00Z").toEpochMilli(); + + assertGetBatteryInfo( + batteryIntent, + currentTimeMillis, + expectedStatusLabel, + expectedRemainingLabel, + expectedChargeLabel); + } + + @Test + public void getBatteryInfo_regularChargingV2_updateRemainingLabelAndStatusLabel() { + prepareTestGetBatteryInfoEnvironment( + /* remainingTimeMs= */ Duration.ofHours(1).toMillis(), + /* chargingStringV2Enabled= */ true); + Intent batteryIntent = + createIntentForGetBatteryInfoTest( + ChargingType.WIRED, ChargingSpeed.REGULAR, /* batteryLevel= */ 12); + var expectedStatusLabel = "Charging"; + var expectedRemainingLabel = "Fully charged by 2:00 PM"; + var expectedChargeLabel = "12% - " + expectedRemainingLabel; + var currentTimeMillis = Instant.parse("2024-04-01T13:00:00Z").toEpochMilli(); + + assertGetBatteryInfo( + batteryIntent, + currentTimeMillis, + expectedStatusLabel, + expectedRemainingLabel, + expectedChargeLabel); + } + + @Test + public void getBatteryInfo_slowChargingV2_updateRemainingLabelAndStatusLabel() { + prepareTestGetBatteryInfoEnvironment( + /* remainingTimeMs= */ Duration.ofHours(2).toMillis(), + /* chargingStringV2Enabled= */ true); + Intent batteryIntent = + createIntentForGetBatteryInfoTest( + ChargingType.WIRED, ChargingSpeed.SLOW, /* batteryLevel= */ 18); + var expectedStatusLabel = "Charging"; + var expectedRemainingLabel = "Fully charged by 3:00 PM"; + var expectedChargeLabel = "18% - " + expectedRemainingLabel; + var currentTimeMillis = Instant.parse("2024-04-01T13:00:00Z").toEpochMilli(); + + assertGetBatteryInfo( + batteryIntent, + currentTimeMillis, + expectedStatusLabel, + expectedRemainingLabel, + expectedChargeLabel); + } + + @Test + public void getBatteryInfo_wirelessChargingV2_updateRemainingLabelAndStatusLabel() { + prepareTestGetBatteryInfoEnvironment( + /* remainingTimeMs= */ Duration.ofHours(1).toMillis(), + /* chargingStringV2Enabled= */ true); + Intent batteryIntent = + createIntentForGetBatteryInfoTest( + ChargingType.WIRELESS, ChargingSpeed.REGULAR, /* batteryLevel= */ 45); + var expectedStatusLabel = "Charging"; + var expectedRemainingLabel = "Fully charged by 4:00 PM"; + var expectedChargeLabel = "45% - " + expectedRemainingLabel; + var currentTimeMillis = Instant.parse("2024-04-01T15:00:00Z").toEpochMilli(); + + assertGetBatteryInfo( + batteryIntent, + currentTimeMillis, + expectedStatusLabel, + expectedRemainingLabel, + expectedChargeLabel); + } + + @Test + public void getBatteryInfo_dockedChargingV2_updateRemainingLabelAndStatusLabel() { + prepareTestGetBatteryInfoEnvironment( + /* remainingTimeMs= */ Duration.ofHours(1).toMillis(), + /* chargingStringV2Enabled= */ true); + Intent batteryIntent = + createIntentForGetBatteryInfoTest( + ChargingType.DOCKED, ChargingSpeed.REGULAR, /* batteryLevel= */ 66); + var expectedStatusLabel = "Charging"; + var expectedRemainingLabel = "Fully charged by 2:00 PM"; + var expectedChargeLabel = "66% - " + expectedRemainingLabel; + var currentTimeMillis = Instant.parse("2021-02-09T13:00:00.00Z").toEpochMilli(); + + assertGetBatteryInfo( + batteryIntent, + currentTimeMillis, + expectedStatusLabel, + expectedRemainingLabel, + expectedChargeLabel); + } + + private enum ChargingSpeed { + FAST, + REGULAR, + SLOW + } + + private enum ChargingType { + WIRED, + WIRELESS, + DOCKED + } + + private Intent createIntentForGetBatteryInfoTest( + ChargingType chargingType, ChargingSpeed chargingSpeed, int batteryLevel) { + return createBatteryIntent( + CHARGING_TYPE_MAP.get(chargingType), + batteryLevel, + BatteryManager.BATTERY_STATUS_CHARGING) + .putExtra( + BatteryManager.EXTRA_MAX_CHARGING_CURRENT, + CHARGING_SPEED_MAP.get(chargingSpeed)) + .putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, 5000000); + } + + private void prepareTestGetBatteryInfoEnvironment( + long remainingTimeMs, boolean chargingStringV2Enabled) { + when(mBatteryUsageStats.getChargeTimeRemainingMs()).thenReturn(remainingTimeMs); + SystemProperties.set( + com.android.settingslib.fuelgauge.BatteryUtils.PROPERTY_CHARGING_STRING_V2_KEY, + String.valueOf(chargingStringV2Enabled)); + Settings.Global.putInt( + mContext.getContentResolver(), + BatteryUtils.SETTINGS_GLOBAL_DOCK_DEFENDER_BYPASS, + 1); + } + + private void assertGetBatteryInfo( + Intent batteryIntent, + long currentTimeMillis, + String expectedStatusLabel, + String expectedRemainingLabel, + String expectedChargeLabel) { + mContext.getResources().getConfiguration().setLocale(Locale.US); + mContext.getSystemService(AlarmManager.class).setTimeZone("UTC"); + var info = + BatteryInfo.getBatteryInfo( + mContext, + batteryIntent, + mBatteryUsageStats, + MOCK_ESTIMATE, + /* elapsedRealtimeUs= */ UNUSED_TIME_MS, + /* shortString= */ false, + /* currentTimeMillis= */ currentTimeMillis); + + assertWithMessage("statusLabel is incorrect") + .that(info.statusLabel) + .isEqualTo(expectedStatusLabel); + assertWithMessage("remainingLabel is incorrect") + .that(info.remainingLabel.toString()) + .isEqualTo(expectedRemainingLabel); + assertWithMessage("chargeLabel is incorrect") + .that(info.chargeLabel.toString()) + .isEqualTo(expectedChargeLabel); + } + + private static Intent createBatteryIntent(int plugged, int level, int status) { + return new Intent() + .putExtra(BatteryManager.EXTRA_PLUGGED, plugged) + .putExtra(BatteryManager.EXTRA_LEVEL, level) + .putExtra(BatteryManager.EXTRA_SCALE, 100) + .putExtra(BatteryManager.EXTRA_STATUS, status); + } + // Make our battery stats return a sequence of battery events. private void mockBatteryStatsHistory() { // Mock out new data every time iterateBatteryStatsHistory is called.