• 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.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