OSDN Git Service

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