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