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 21 import static org.mockito.ArgumentMatchers.anyInt; 22 import static org.mockito.Matchers.anyLong; 23 import static org.mockito.Mockito.any; 24 import static org.mockito.Mockito.doAnswer; 25 import static org.mockito.Mockito.doReturn; 26 import static org.mockito.Mockito.mock; 27 import static org.mockito.Mockito.never; 28 import static org.mockito.Mockito.spy; 29 import static org.mockito.Mockito.times; 30 import static org.mockito.Mockito.verify; 31 32 import android.content.Context; 33 import android.content.Intent; 34 import android.content.res.Resources; 35 import android.os.BatteryManager; 36 import android.os.BatteryStats; 37 import android.os.SystemClock; 38 import android.util.SparseIntArray; 39 40 import com.android.settings.TestConfig; 41 import com.android.settings.graph.UsageView; 42 import com.android.settings.testutils.BatteryTestUtils; 43 import com.android.settings.testutils.FakeFeatureFactory; 44 import com.android.settings.testutils.SettingsRobolectricTestRunner; 45 46 import org.junit.Before; 47 import org.junit.Test; 48 import org.junit.runner.RunWith; 49 import org.mockito.Answers; 50 import org.mockito.ArgumentCaptor; 51 import org.mockito.Mock; 52 import org.mockito.MockitoAnnotations; 53 import org.mockito.invocation.InvocationOnMock; 54 import org.mockito.stubbing.Answer; 55 import org.robolectric.RuntimeEnvironment; 56 import org.robolectric.annotation.Config; 57 58 import java.util.concurrent.TimeUnit; 59 60 @RunWith(SettingsRobolectricTestRunner.class) 61 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) 62 public class BatteryInfoTest { 63 64 private static final String STATUS_FULL = "Full"; 65 private static final String STATUS_CHARGING_NO_TIME = "50% - charging"; 66 private static final String STATUS_CHARGING_TIME = "50% - 0m until fully charged"; 67 private static final String STATUS_NOT_CHARGING = "Not charging"; 68 private static final int PLUGGED_IN = 1; 69 private static final long REMAINING_TIME_NULL = -1; 70 private static final long REMAINING_TIME = 2; 71 public static final String ENHANCED_STRING_SUFFIX = "left based on your usage"; 72 public static final long TEST_CHARGE_TIME_REMAINING = TimeUnit.MINUTES.toMicros(1); 73 public static final String TEST_CHARGE_TIME_REMAINING_STRINGIFIED = 74 "1m left until fully charged"; 75 private Intent mDisChargingBatteryBroadcast; 76 private Intent mChargingBatteryBroadcast; 77 private Context mContext; 78 private FakeFeatureFactory mFeatureFactory; 79 80 @Mock(answer = Answers.RETURNS_DEEP_STUBS) 81 private BatteryStats mBatteryStats; 82 @Mock(answer = Answers.RETURNS_DEEP_STUBS) 83 private Resources mResources; 84 85 86 @Before setUp()87 public void setUp() { 88 MockitoAnnotations.initMocks(this); 89 mContext = spy(RuntimeEnvironment.application); 90 mFeatureFactory = FakeFeatureFactory.setupForTest(mContext); 91 92 mDisChargingBatteryBroadcast = BatteryTestUtils.getDischargingIntent(); 93 94 mChargingBatteryBroadcast = BatteryTestUtils.getChargingIntent(); 95 } 96 97 @Test testGetBatteryInfo_hasStatusLabel()98 public void testGetBatteryInfo_hasStatusLabel() { 99 doReturn(REMAINING_TIME_NULL).when(mBatteryStats).computeBatteryTimeRemaining(anyLong()); 100 BatteryInfo info = BatteryInfo.getBatteryInfoOld(mContext, 101 mDisChargingBatteryBroadcast, mBatteryStats, SystemClock.elapsedRealtime() * 1000, 102 true /* shortString */); 103 104 assertThat(info.statusLabel).isEqualTo(STATUS_NOT_CHARGING); 105 } 106 107 @Test testGetBatteryInfo_doNotShowChargingMethod_hasRemainingTime()108 public void testGetBatteryInfo_doNotShowChargingMethod_hasRemainingTime() { 109 doReturn(REMAINING_TIME).when(mBatteryStats).computeChargeTimeRemaining(anyLong()); 110 BatteryInfo info = BatteryInfo.getBatteryInfoOld(mContext, mChargingBatteryBroadcast, 111 mBatteryStats, SystemClock.elapsedRealtime() * 1000, false /* shortString */); 112 113 assertThat(info.chargeLabel.toString()).isEqualTo(STATUS_CHARGING_TIME); 114 } 115 116 @Test testGetBatteryInfo_doNotShowChargingMethod_noRemainingTime()117 public void testGetBatteryInfo_doNotShowChargingMethod_noRemainingTime() { 118 doReturn(REMAINING_TIME_NULL).when(mBatteryStats).computeChargeTimeRemaining(anyLong()); 119 BatteryInfo info = BatteryInfo.getBatteryInfoOld(mContext, mChargingBatteryBroadcast, 120 mBatteryStats, SystemClock.elapsedRealtime() * 1000, false /* shortString */); 121 122 assertThat(info.chargeLabel.toString()).isEqualTo(STATUS_CHARGING_NO_TIME); 123 } 124 125 @Test testGetBatteryInfo_pluggedInUsingShortString_usesCorrectData()126 public void testGetBatteryInfo_pluggedInUsingShortString_usesCorrectData() { 127 doReturn(TEST_CHARGE_TIME_REMAINING).when(mBatteryStats).computeChargeTimeRemaining( 128 anyLong()); 129 BatteryInfo info = BatteryInfo.getBatteryInfoOld(mContext, mChargingBatteryBroadcast, 130 mBatteryStats, SystemClock.elapsedRealtime() * 1000, true /* shortString */); 131 132 assertThat(info.discharging).isEqualTo(false); 133 assertThat(info.chargeLabel.toString()).isEqualTo("50% - 1m until fully charged"); 134 } 135 136 @Test testGetBatteryInfo_basedOnUsageTrue_usesCorrectString()137 public void testGetBatteryInfo_basedOnUsageTrue_usesCorrectString() { 138 BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast, 139 mBatteryStats, SystemClock.elapsedRealtime() * 1000, false /* shortString */, 140 1000, true /* basedOnUsage */); 141 BatteryInfo info2 = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast, 142 mBatteryStats, SystemClock.elapsedRealtime() * 1000, true /* shortString */, 143 1000, true /* basedOnUsage */); 144 145 // We only add special mention for the long string 146 assertThat(info.remainingLabel.toString()).contains(ENHANCED_STRING_SUFFIX); 147 // shortened string should not have extra text 148 assertThat(info2.remainingLabel.toString()).doesNotContain(ENHANCED_STRING_SUFFIX); 149 } 150 151 @Test testGetBatteryInfo_basedOnUsageFalse_usesDefaultString()152 public void testGetBatteryInfo_basedOnUsageFalse_usesDefaultString() { 153 BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast, 154 mBatteryStats, SystemClock.elapsedRealtime() * 1000, false /* shortString */, 155 1000, false /* basedOnUsage */); 156 BatteryInfo info2 = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast, 157 mBatteryStats, SystemClock.elapsedRealtime() * 1000, true /* shortString */, 158 1000, false /* basedOnUsage */); 159 160 assertThat(info.remainingLabel.toString()).doesNotContain(ENHANCED_STRING_SUFFIX); 161 assertThat(info2.remainingLabel.toString()).doesNotContain(ENHANCED_STRING_SUFFIX); 162 } 163 164 @Test testGetBatteryInfo_charging_usesChargeTime()165 public void testGetBatteryInfo_charging_usesChargeTime() { 166 doReturn(TEST_CHARGE_TIME_REMAINING) 167 .when(mBatteryStats) 168 .computeChargeTimeRemaining(anyLong()); 169 BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mChargingBatteryBroadcast, 170 mBatteryStats, SystemClock.elapsedRealtime() * 1000, false, 1000, false); 171 assertThat(info.remainingTimeUs).isEqualTo(TEST_CHARGE_TIME_REMAINING); 172 assertThat(info.remainingLabel.toString()) 173 .isEqualTo(TEST_CHARGE_TIME_REMAINING_STRINGIFIED); 174 } 175 176 @Test testGetBatteryInfo_pluggedInWithFullBattery_onlyShowBatteryLevel()177 public void testGetBatteryInfo_pluggedInWithFullBattery_onlyShowBatteryLevel() { 178 mChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_LEVEL, 100); 179 180 BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mChargingBatteryBroadcast, 181 mBatteryStats, SystemClock.elapsedRealtime() * 1000, false /* shortString */, 182 1000, false /* basedOnUsage */); 183 184 assertThat(info.chargeLabel).isEqualTo("100%"); 185 } 186 187 // Make our battery stats return a sequence of battery events. mockBatteryStatsHistory()188 private void mockBatteryStatsHistory() { 189 // Mock out new data every time start...Locked is called. 190 doAnswer(invocation -> { 191 doAnswer(new Answer() { 192 private int count = 0; 193 private long[] times = {1000, 1500, 2000}; 194 private byte[] levels = {99, 98, 97}; 195 196 @Override 197 public Object answer(InvocationOnMock invocation) throws Throwable { 198 if (count == times.length) { 199 return false; 200 } 201 BatteryStats.HistoryItem record = invocation.getArgument(0); 202 record.cmd = BatteryStats.HistoryItem.CMD_UPDATE; 203 record.time = times[count]; 204 record.batteryLevel = levels[count]; 205 count++; 206 return true; 207 } 208 }).when(mBatteryStats).getNextHistoryLocked(any(BatteryStats.HistoryItem.class)); 209 return true; 210 }).when(mBatteryStats).startIteratingHistoryLocked(); 211 } 212 assertOnlyHistory(BatteryInfo info)213 private void assertOnlyHistory(BatteryInfo info) { 214 mockBatteryStatsHistory(); 215 UsageView view = mock(UsageView.class); 216 doReturn(mContext).when(view).getContext(); 217 218 info.bindHistory(view); 219 verify(view, times(1)).configureGraph(anyInt(), anyInt()); 220 verify(view, times(1)).addPath(any(SparseIntArray.class)); 221 verify(view, never()).addProjectedPath(any(SparseIntArray.class)); 222 } 223 assertHistoryAndLinearProjection(BatteryInfo info)224 private void assertHistoryAndLinearProjection(BatteryInfo info) { 225 mockBatteryStatsHistory(); 226 UsageView view = mock(UsageView.class); 227 doReturn(mContext).when(view).getContext(); 228 229 info.bindHistory(view); 230 verify(view, times(2)).configureGraph(anyInt(), anyInt()); 231 verify(view, times(1)).addPath(any(SparseIntArray.class)); 232 ArgumentCaptor<SparseIntArray> pointsActual = ArgumentCaptor.forClass(SparseIntArray.class); 233 verify(view, times(1)).addProjectedPath(pointsActual.capture()); 234 235 // Check that we have two points and the first is correct. 236 assertThat(pointsActual.getValue().size()).isEqualTo(2); 237 assertThat(pointsActual.getValue().keyAt(0)).isEqualTo(2000); 238 assertThat(pointsActual.getValue().valueAt(0)).isEqualTo(97); 239 } 240 assertHistoryAndEnhancedProjection(BatteryInfo info)241 private void assertHistoryAndEnhancedProjection(BatteryInfo info) { 242 mockBatteryStatsHistory(); 243 UsageView view = mock(UsageView.class); 244 doReturn(mContext).when(view).getContext(); 245 SparseIntArray pointsExpected = new SparseIntArray(); 246 pointsExpected.append(2000, 96); 247 pointsExpected.append(2500, 95); 248 pointsExpected.append(3000, 94); 249 doReturn(pointsExpected).when(mFeatureFactory.powerUsageFeatureProvider) 250 .getEnhancedBatteryPredictionCurve(any(Context.class), anyLong()); 251 252 info.bindHistory(view); 253 verify(view, times(2)).configureGraph(anyInt(), anyInt()); 254 verify(view, times(1)).addPath(any(SparseIntArray.class)); 255 ArgumentCaptor<SparseIntArray> pointsActual = ArgumentCaptor.forClass(SparseIntArray.class); 256 verify(view, times(1)).addProjectedPath(pointsActual.capture()); 257 assertThat(pointsActual.getValue()).isEqualTo(pointsExpected); 258 } 259 getBatteryInfo(boolean charging, boolean enhanced, boolean estimate)260 private BatteryInfo getBatteryInfo(boolean charging, boolean enhanced, boolean estimate) { 261 if (charging && estimate) { 262 doReturn(1000L).when(mBatteryStats).computeChargeTimeRemaining(anyLong()); 263 } else { 264 doReturn(0L).when(mBatteryStats).computeChargeTimeRemaining(anyLong()); 265 } 266 BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, 267 charging ? mChargingBatteryBroadcast : mDisChargingBatteryBroadcast, 268 mBatteryStats, SystemClock.elapsedRealtime() * 1000, false, 269 estimate ? 1000 : 0 /* drainTimeUs */, false); 270 doReturn(enhanced).when(mFeatureFactory.powerUsageFeatureProvider) 271 .isEnhancedBatteryPredictionEnabled(mContext); 272 return info; 273 } 274 275 @Test testBindHistory()276 public void testBindHistory() { 277 BatteryInfo info; 278 279 info = getBatteryInfo(false /* charging */, false /* enhanced */, false /* estimate */); 280 assertOnlyHistory(info); 281 282 info = getBatteryInfo(false /* charging */, false /* enhanced */, true /* estimate */); 283 assertHistoryAndLinearProjection(info); 284 285 info = getBatteryInfo(false /* charging */, true /* enhanced */, false /* estimate */); 286 assertOnlyHistory(info); 287 288 info = getBatteryInfo(false /* charging */, true /* enhanced */, true /* estimate */); 289 assertHistoryAndEnhancedProjection(info); 290 291 info = getBatteryInfo(true /* charging */, false /* enhanced */, false /* estimate */); 292 assertOnlyHistory(info); 293 294 info = getBatteryInfo(true /* charging */, false /* enhanced */, true /* estimate */); 295 assertHistoryAndLinearProjection(info); 296 297 info = getBatteryInfo(true /* charging */, true /* enhanced */, false /* estimate */); 298 assertOnlyHistory(info); 299 300 // Linear projection for charging even in enhanced mode. 301 info = getBatteryInfo(true /* charging */, true /* enhanced */, true /* estimate */); 302 assertHistoryAndLinearProjection(info); 303 } 304 } 305