• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ArgumentMatchers.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 import static org.mockito.Mockito.when;
32 
33 import android.content.Context;
34 import android.content.Intent;
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.testutils.BatteryTestUtils;
41 import com.android.settings.testutils.FakeFeatureFactory;
42 import com.android.settings.widget.UsageView;
43 import com.android.settingslib.R;
44 import com.android.settingslib.fuelgauge.Estimate;
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.RobolectricTestRunner;
56 import org.robolectric.RuntimeEnvironment;
57 
58 import java.time.Duration;
59 import java.util.concurrent.TimeUnit;
60 
61 @RunWith(RobolectricTestRunner.class)
62 public class BatteryInfoTest {
63 
64     private static final String STATUS_CHARGING_NO_TIME = "50% - charging";
65     private static final String STATUS_CHARGING_TIME = "50% - 0 min until fully charged";
66     private static final String STATUS_NOT_CHARGING = "Not charging";
67     private static final long REMAINING_TIME_NULL = -1;
68     private static final long REMAINING_TIME = 2;
69     // Strings are defined in frameworks/base/packages/SettingsLib/res/values/strings.xml
70     private static final String ENHANCED_STRING_SUFFIX = "based on your usage";
71     private static final String EXTEND_PREFIX = "Extend battery life past";
72     private static final long TEST_CHARGE_TIME_REMAINING = TimeUnit.MINUTES.toMicros(1);
73     private static final String TEST_CHARGE_TIME_REMAINING_STRINGIFIED =
74             "1 min left until fully charged";
75     private static final String TEST_BATTERY_LEVEL_10 = "10%";
76     private static final String FIFTEEN_MIN_FORMATTED = "15 min";
77     private static final Estimate DUMMY_ESTIMATE = new Estimate(
78             1000, /* estimateMillis */
79             false, /* isBasedOnUsage */
80             1000 /* averageDischargeTime */);
81 
82     private Intent mDisChargingBatteryBroadcast;
83     private Intent mChargingBatteryBroadcast;
84     private Context mContext;
85     private FakeFeatureFactory mFeatureFactory;
86 
87     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
88     private BatteryStats mBatteryStats;
89 
90     @Before
setUp()91     public void setUp() {
92         MockitoAnnotations.initMocks(this);
93         mContext = spy(RuntimeEnvironment.application);
94         mFeatureFactory = FakeFeatureFactory.setupForTest();
95 
96         mDisChargingBatteryBroadcast = BatteryTestUtils.getDischargingIntent();
97 
98         mChargingBatteryBroadcast = BatteryTestUtils.getChargingIntent();
99     }
100 
101     @Test
testGetBatteryInfo_hasStatusLabel()102     public void testGetBatteryInfo_hasStatusLabel() {
103         doReturn(REMAINING_TIME_NULL).when(mBatteryStats).computeBatteryTimeRemaining(anyLong());
104         BatteryInfo info = BatteryInfo.getBatteryInfoOld(mContext,
105                 mDisChargingBatteryBroadcast, mBatteryStats, SystemClock.elapsedRealtime() * 1000,
106                 true /* shortString */);
107 
108         assertThat(info.statusLabel).isEqualTo(STATUS_NOT_CHARGING);
109     }
110 
111     @Test
testGetBatteryInfo_doNotShowChargingMethod_hasRemainingTime()112     public void testGetBatteryInfo_doNotShowChargingMethod_hasRemainingTime() {
113         doReturn(REMAINING_TIME).when(mBatteryStats).computeChargeTimeRemaining(anyLong());
114         BatteryInfo info = BatteryInfo.getBatteryInfoOld(mContext, mChargingBatteryBroadcast,
115                 mBatteryStats, SystemClock.elapsedRealtime() * 1000, false /* shortString */);
116 
117         assertThat(info.chargeLabel.toString()).isEqualTo(STATUS_CHARGING_TIME);
118     }
119 
120     @Test
testGetBatteryInfo_doNotShowChargingMethod_noRemainingTime()121     public void testGetBatteryInfo_doNotShowChargingMethod_noRemainingTime() {
122         doReturn(REMAINING_TIME_NULL).when(mBatteryStats).computeChargeTimeRemaining(anyLong());
123         BatteryInfo info = BatteryInfo.getBatteryInfoOld(mContext, mChargingBatteryBroadcast,
124                 mBatteryStats, SystemClock.elapsedRealtime() * 1000, false /* shortString */);
125 
126         assertThat(info.chargeLabel.toString()).isEqualTo(STATUS_CHARGING_NO_TIME);
127     }
128 
129     @Test
testGetBatteryInfo_pluggedInUsingShortString_usesCorrectData()130     public void testGetBatteryInfo_pluggedInUsingShortString_usesCorrectData() {
131         doReturn(TEST_CHARGE_TIME_REMAINING).when(mBatteryStats).computeChargeTimeRemaining(
132                 anyLong());
133         BatteryInfo info = BatteryInfo.getBatteryInfoOld(mContext, mChargingBatteryBroadcast,
134                 mBatteryStats, SystemClock.elapsedRealtime() * 1000, true /* shortString */);
135 
136         assertThat(info.discharging).isEqualTo(false);
137         assertThat(info.chargeLabel.toString()).isEqualTo("50% - 1 min until fully charged");
138     }
139 
140     @Test
testGetBatteryInfo_basedOnUsageTrueMoreThanFifteenMinutes_usesCorrectString()141     public void testGetBatteryInfo_basedOnUsageTrueMoreThanFifteenMinutes_usesCorrectString() {
142         Estimate estimate = new Estimate(Duration.ofHours(4).toMillis(),
143                 true /* isBasedOnUsage */,
144                 1000 /* averageDischargeTime */);
145         BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast,
146                 mBatteryStats, estimate, SystemClock.elapsedRealtime() * 1000,
147                 false /* shortString */);
148         BatteryInfo info2 = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast,
149                 mBatteryStats, estimate, SystemClock.elapsedRealtime() * 1000,
150                 true /* shortString */);
151 
152         // We only add special mention for the long string
153         assertThat(info.remainingLabel.toString()).contains(ENHANCED_STRING_SUFFIX);
154         assertThat(info.suggestionLabel).contains(EXTEND_PREFIX);
155         // shortened string should not have extra text
156         assertThat(info2.remainingLabel.toString()).doesNotContain(ENHANCED_STRING_SUFFIX);
157         assertThat(info2.suggestionLabel).contains(EXTEND_PREFIX);
158     }
159 
160     @Test
testGetBatteryInfo_basedOnUsageTrueLessThanSevenMinutes_usesCorrectString()161     public void testGetBatteryInfo_basedOnUsageTrueLessThanSevenMinutes_usesCorrectString() {
162         Estimate estimate = new Estimate(Duration.ofMinutes(7).toMillis(),
163                 true /* isBasedOnUsage */,
164                 1000 /* averageDischargeTime */);
165         BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast,
166                 mBatteryStats, estimate, SystemClock.elapsedRealtime() * 1000,
167                 false /* shortString */);
168         BatteryInfo info2 = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast,
169                 mBatteryStats, estimate, SystemClock.elapsedRealtime() * 1000,
170                 true /* shortString */);
171 
172         // These should be identical in either case
173         assertThat(info.remainingLabel.toString()).isEqualTo(
174                 mContext.getString(R.string.power_remaining_duration_only_shutdown_imminent));
175         assertThat(info2.remainingLabel.toString()).isEqualTo(
176                 mContext.getString(R.string.power_remaining_duration_only_shutdown_imminent));
177         assertThat(info2.suggestionLabel).contains(EXTEND_PREFIX);
178     }
179 
180     @Test
getBatteryInfo_MoreThanOneDay_suggestionLabelIsCorrectString()181     public void getBatteryInfo_MoreThanOneDay_suggestionLabelIsCorrectString() {
182         Estimate estimate = new Estimate(Duration.ofDays(3).toMillis(),
183                 true /* isBasedOnUsage */,
184                 1000 /* averageDischargeTime */);
185         BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast,
186                 mBatteryStats, estimate, SystemClock.elapsedRealtime() * 1000,
187                 false /* shortString */);
188 
189         assertThat(info.suggestionLabel).doesNotContain(EXTEND_PREFIX);
190     }
191 
192     @Test
193     public void
testGetBatteryInfo_basedOnUsageTrueBetweenSevenAndFifteenMinutes_usesCorrectString()194     testGetBatteryInfo_basedOnUsageTrueBetweenSevenAndFifteenMinutes_usesCorrectString() {
195         Estimate estimate = new Estimate(Duration.ofMinutes(10).toMillis(),
196                 true /* isBasedOnUsage */,
197                 1000 /* averageDischargeTime */);
198         BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast,
199                 mBatteryStats, estimate, SystemClock.elapsedRealtime() * 1000,
200                 false /* shortString */);
201 
202         // Check that strings are showing less than 15 minutes remaining regardless of exact time.
203         assertThat(info.chargeLabel.toString()).isEqualTo(
204                 mContext.getString(R.string.power_remaining_less_than_duration,
205                         FIFTEEN_MIN_FORMATTED, TEST_BATTERY_LEVEL_10));
206         assertThat(info.remainingLabel.toString()).isEqualTo(
207                 mContext.getString(R.string.power_remaining_less_than_duration_only,
208                         FIFTEEN_MIN_FORMATTED));
209     }
210 
211     @Test
testGetBatteryInfo_basedOnUsageFalse_usesDefaultString()212     public void testGetBatteryInfo_basedOnUsageFalse_usesDefaultString() {
213         BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast,
214                 mBatteryStats, DUMMY_ESTIMATE, SystemClock.elapsedRealtime() * 1000,
215                 false /* shortString */);
216         BatteryInfo info2 = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast,
217                 mBatteryStats, DUMMY_ESTIMATE, SystemClock.elapsedRealtime() * 1000,
218                 true /* shortString */);
219 
220         assertThat(info.remainingLabel.toString()).doesNotContain(ENHANCED_STRING_SUFFIX);
221         assertThat(info2.remainingLabel.toString()).doesNotContain(ENHANCED_STRING_SUFFIX);
222     }
223 
224     @Test
testGetBatteryInfo_charging_usesChargeTime()225     public void testGetBatteryInfo_charging_usesChargeTime() {
226         doReturn(TEST_CHARGE_TIME_REMAINING)
227                 .when(mBatteryStats)
228                 .computeChargeTimeRemaining(anyLong());
229 
230         BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mChargingBatteryBroadcast,
231                 mBatteryStats, DUMMY_ESTIMATE, SystemClock.elapsedRealtime() * 1000,
232                 false /* shortString */);
233         assertThat(info.remainingTimeUs).isEqualTo(TEST_CHARGE_TIME_REMAINING);
234         assertThat(info.remainingLabel.toString())
235                 .isEqualTo(TEST_CHARGE_TIME_REMAINING_STRINGIFIED);
236     }
237 
238     @Test
testGetBatteryInfo_pluggedInWithFullBattery_onlyShowBatteryLevel()239     public void testGetBatteryInfo_pluggedInWithFullBattery_onlyShowBatteryLevel() {
240         mChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_LEVEL, 100);
241 
242         BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mChargingBatteryBroadcast,
243                 mBatteryStats, DUMMY_ESTIMATE, SystemClock.elapsedRealtime() * 1000,
244                 false /* shortString */);
245 
246         assertThat(info.chargeLabel).isEqualTo("100%");
247     }
248 
249     // Make our battery stats return a sequence of battery events.
mockBatteryStatsHistory()250     private void mockBatteryStatsHistory() {
251         // Mock out new data every time start...Locked is called.
252         doAnswer(invocation -> {
253             doAnswer(new Answer() {
254                 private int count = 0;
255                 private long[] times = {1000, 1500, 2000};
256                 private byte[] levels = {99, 98, 97};
257 
258                 @Override
259                 public Object answer(InvocationOnMock invocation) throws Throwable {
260                     if (count == times.length) {
261                         return false;
262                     }
263                     BatteryStats.HistoryItem record = invocation.getArgument(0);
264                     record.cmd = BatteryStats.HistoryItem.CMD_UPDATE;
265                     record.time = times[count];
266                     record.batteryLevel = levels[count];
267                     count++;
268                     return true;
269                 }
270             }).when(mBatteryStats).getNextHistoryLocked(any(BatteryStats.HistoryItem.class));
271             return true;
272         }).when(mBatteryStats).startIteratingHistoryLocked();
273     }
274 
assertOnlyHistory(BatteryInfo info)275     private void assertOnlyHistory(BatteryInfo info) {
276         mockBatteryStatsHistory();
277         UsageView view = mock(UsageView.class);
278         when(view.getContext()).thenReturn(mContext);
279 
280         info.bindHistory(view);
281         verify(view, times(1)).configureGraph(anyInt(), anyInt());
282         verify(view, times(1)).addPath(any(SparseIntArray.class));
283         verify(view, never()).addProjectedPath(any(SparseIntArray.class));
284     }
285 
assertHistoryAndLinearProjection(BatteryInfo info)286     private void assertHistoryAndLinearProjection(BatteryInfo info) {
287         mockBatteryStatsHistory();
288         UsageView view = mock(UsageView.class);
289         when(view.getContext()).thenReturn(mContext);
290 
291         info.bindHistory(view);
292         verify(view, times(2)).configureGraph(anyInt(), anyInt());
293         verify(view, times(1)).addPath(any(SparseIntArray.class));
294         ArgumentCaptor<SparseIntArray> pointsActual = ArgumentCaptor.forClass(SparseIntArray.class);
295         verify(view, times(1)).addProjectedPath(pointsActual.capture());
296 
297         // Check that we have two points and the first is correct.
298         assertThat(pointsActual.getValue().size()).isEqualTo(2);
299         assertThat(pointsActual.getValue().keyAt(0)).isEqualTo(2000);
300         assertThat(pointsActual.getValue().valueAt(0)).isEqualTo(97);
301     }
302 
assertHistoryAndEnhancedProjection(BatteryInfo info)303     private void assertHistoryAndEnhancedProjection(BatteryInfo info) {
304         mockBatteryStatsHistory();
305         UsageView view = mock(UsageView.class);
306         when(view.getContext()).thenReturn(mContext);
307         SparseIntArray pointsExpected = new SparseIntArray();
308         pointsExpected.append(2000, 96);
309         pointsExpected.append(2500, 95);
310         pointsExpected.append(3000, 94);
311         doReturn(pointsExpected).when(mFeatureFactory.powerUsageFeatureProvider)
312                 .getEnhancedBatteryPredictionCurve(any(Context.class), anyLong());
313 
314         info.bindHistory(view);
315         verify(view, times(2)).configureGraph(anyInt(), anyInt());
316         verify(view, times(1)).addPath(any(SparseIntArray.class));
317         ArgumentCaptor<SparseIntArray> pointsActual = ArgumentCaptor.forClass(SparseIntArray.class);
318         verify(view, times(1)).addProjectedPath(pointsActual.capture());
319         assertThat(pointsActual.getValue()).isEqualTo(pointsExpected);
320     }
321 
getBatteryInfo(boolean charging, boolean enhanced, boolean estimate)322     private BatteryInfo getBatteryInfo(boolean charging, boolean enhanced, boolean estimate) {
323         if (charging && estimate) {
324             doReturn(1000L).when(mBatteryStats).computeChargeTimeRemaining(anyLong());
325         } else {
326             doReturn(0L).when(mBatteryStats).computeChargeTimeRemaining(anyLong());
327         }
328         Estimate batteryEstimate = new Estimate(
329                 estimate ? 1000 : 0,
330                 false /* isBasedOnUsage */,
331                 1000 /* averageDischargeTime */);
332         BatteryInfo info = BatteryInfo.getBatteryInfo(mContext,
333                 charging ? mChargingBatteryBroadcast : mDisChargingBatteryBroadcast,
334                 mBatteryStats, batteryEstimate, SystemClock.elapsedRealtime() * 1000, false);
335         doReturn(enhanced).when(mFeatureFactory.powerUsageFeatureProvider)
336                 .isEnhancedBatteryPredictionEnabled(mContext);
337         return info;
338     }
339 
340     @Test
testBindHistory()341     public void testBindHistory() {
342         BatteryInfo info;
343 
344         info = getBatteryInfo(false /* charging */, false /* enhanced */, false /* estimate */);
345         assertOnlyHistory(info);
346 
347         info = getBatteryInfo(false /* charging */, false /* enhanced */, true /* estimate */);
348         assertHistoryAndLinearProjection(info);
349 
350         info = getBatteryInfo(false /* charging */, true /* enhanced */, false /* estimate */);
351         assertOnlyHistory(info);
352 
353         info = getBatteryInfo(false /* charging */, true /* enhanced */, true /* estimate */);
354         assertHistoryAndEnhancedProjection(info);
355 
356         info = getBatteryInfo(true /* charging */, false /* enhanced */, false /* estimate */);
357         assertOnlyHistory(info);
358 
359         info = getBatteryInfo(true /* charging */, false /* enhanced */, true /* estimate */);
360         assertHistoryAndLinearProjection(info);
361 
362         info = getBatteryInfo(true /* charging */, true /* enhanced */, false /* estimate */);
363         assertOnlyHistory(info);
364 
365         // Linear projection for charging even in enhanced mode.
366         info = getBatteryInfo(true /* charging */, true /* enhanced */, true /* estimate */);
367         assertHistoryAndLinearProjection(info);
368     }
369 }
370