1 /*
2  * Copyright (C) 2021 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 package com.android.settings.fuelgauge;
17 
18 import static com.google.common.truth.Truth.assertThat;
19 
20 import static org.mockito.Mockito.spy;
21 import static org.mockito.Mockito.when;
22 
23 import android.content.ContentValues;
24 import android.content.Context;
25 import android.os.BatteryManager;
26 import android.os.BatteryUsageStats;
27 import android.os.LocaleList;
28 import android.os.UserHandle;
29 
30 import com.android.settings.testutils.FakeFeatureFactory;
31 
32 import org.junit.Before;
33 import org.junit.Test;
34 import org.junit.runner.RunWith;
35 import org.mockito.Mock;
36 import org.mockito.MockitoAnnotations;
37 import org.robolectric.RobolectricTestRunner;
38 import org.robolectric.RuntimeEnvironment;
39 
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.HashMap;
43 import java.util.HashSet;
44 import java.util.List;
45 import java.util.Locale;
46 import java.util.Map;
47 import java.util.TimeZone;
48 
49 @RunWith(RobolectricTestRunner.class)
50 public final class ConvertUtilsTest {
51 
52     private Context mContext;
53 
54     @Mock
55     private BatteryUsageStats mBatteryUsageStats;
56     @Mock
57     private BatteryEntry mockBatteryEntry;
58 
59     private FakeFeatureFactory mFeatureFactory;
60     private PowerUsageFeatureProvider mPowerUsageFeatureProvider;
61 
62     @Before
setUp()63     public void setUp() {
64         MockitoAnnotations.initMocks(this);
65         mContext = spy(RuntimeEnvironment.application);
66         mFeatureFactory = FakeFeatureFactory.setupForTest();
67         mPowerUsageFeatureProvider = mFeatureFactory.powerUsageFeatureProvider;
68     }
69 
70     @Test
convert_returnsExpectedContentValues()71     public void convert_returnsExpectedContentValues() {
72         final int expectedType = 3;
73         when(mockBatteryEntry.getUid()).thenReturn(1001);
74         when(mockBatteryEntry.getLabel()).thenReturn("Settings");
75         when(mockBatteryEntry.getDefaultPackageName())
76             .thenReturn("com.google.android.settings.battery");
77         when(mockBatteryEntry.isHidden()).thenReturn(true);
78         when(mBatteryUsageStats.getConsumedPower()).thenReturn(5.1);
79         when(mockBatteryEntry.getConsumedPower()).thenReturn(1.1);
80         mockBatteryEntry.percent = 0.3;
81         when(mockBatteryEntry.getTimeInForegroundMs()).thenReturn(1234L);
82         when(mockBatteryEntry.getTimeInBackgroundMs()).thenReturn(5689L);
83         when(mockBatteryEntry.getPowerComponentId()).thenReturn(expectedType);
84         when(mockBatteryEntry.getConsumerType())
85             .thenReturn(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY);
86 
87         final ContentValues values =
88             ConvertUtils.convert(
89                 mockBatteryEntry,
90                 mBatteryUsageStats,
91                 /*batteryLevel=*/ 12,
92                 /*batteryStatus=*/ BatteryManager.BATTERY_STATUS_FULL,
93                 /*batteryHealth=*/ BatteryManager.BATTERY_HEALTH_COLD,
94                 /*bootTimestamp=*/ 101L,
95                 /*timestamp=*/ 10001L);
96 
97         assertThat(values.getAsLong(BatteryHistEntry.KEY_UID)).isEqualTo(1001L);
98         assertThat(values.getAsLong(BatteryHistEntry.KEY_USER_ID))
99             .isEqualTo(UserHandle.getUserId(1001));
100         assertThat(values.getAsString(BatteryHistEntry.KEY_APP_LABEL))
101             .isEqualTo("Settings");
102         assertThat(values.getAsString(BatteryHistEntry.KEY_PACKAGE_NAME))
103             .isEqualTo("com.google.android.settings.battery");
104         assertThat(values.getAsBoolean(BatteryHistEntry.KEY_IS_HIDDEN)).isTrue();
105         assertThat(values.getAsLong(BatteryHistEntry.KEY_BOOT_TIMESTAMP))
106             .isEqualTo(101L);
107         assertThat(values.getAsLong(BatteryHistEntry.KEY_TIMESTAMP)).isEqualTo(10001L);
108         assertThat(values.getAsString(BatteryHistEntry.KEY_ZONE_ID))
109             .isEqualTo(TimeZone.getDefault().getID());
110         assertThat(values.getAsDouble(BatteryHistEntry.KEY_TOTAL_POWER)).isEqualTo(5.1);
111         assertThat(values.getAsDouble(BatteryHistEntry.KEY_CONSUME_POWER)).isEqualTo(1.1);
112         assertThat(values.getAsDouble(BatteryHistEntry.KEY_PERCENT_OF_TOTAL)).isEqualTo(0.3);
113         assertThat(values.getAsLong(BatteryHistEntry.KEY_FOREGROUND_USAGE_TIME))
114             .isEqualTo(1234L);
115         assertThat(values.getAsLong(BatteryHistEntry.KEY_BACKGROUND_USAGE_TIME))
116             .isEqualTo(5689L);
117         assertThat(values.getAsInteger(BatteryHistEntry.KEY_DRAIN_TYPE)).isEqualTo(expectedType);
118         assertThat(values.getAsInteger(BatteryHistEntry.KEY_CONSUMER_TYPE))
119             .isEqualTo(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY);
120         assertThat(values.getAsInteger(BatteryHistEntry.KEY_BATTERY_LEVEL)).isEqualTo(12);
121         assertThat(values.getAsInteger(BatteryHistEntry.KEY_BATTERY_STATUS))
122             .isEqualTo(BatteryManager.BATTERY_STATUS_FULL);
123         assertThat(values.getAsInteger(BatteryHistEntry.KEY_BATTERY_HEALTH))
124             .isEqualTo(BatteryManager.BATTERY_HEALTH_COLD);
125     }
126 
127     @Test
convert_nullBatteryEntry_returnsExpectedContentValues()128     public void convert_nullBatteryEntry_returnsExpectedContentValues() {
129         final ContentValues values =
130             ConvertUtils.convert(
131                 /*entry=*/ null,
132                 /*batteryUsageStats=*/ null,
133                 /*batteryLevel=*/ 12,
134                 /*batteryStatus=*/ BatteryManager.BATTERY_STATUS_FULL,
135                 /*batteryHealth=*/ BatteryManager.BATTERY_HEALTH_COLD,
136                 /*bootTimestamp=*/ 101L,
137                 /*timestamp=*/ 10001L);
138 
139         assertThat(values.getAsLong(BatteryHistEntry.KEY_BOOT_TIMESTAMP))
140             .isEqualTo(101L);
141         assertThat(values.getAsLong(BatteryHistEntry.KEY_TIMESTAMP))
142             .isEqualTo(10001L);
143         assertThat(values.getAsString(BatteryHistEntry.KEY_ZONE_ID))
144             .isEqualTo(TimeZone.getDefault().getID());
145         assertThat(values.getAsInteger(BatteryHistEntry.KEY_BATTERY_LEVEL)).isEqualTo(12);
146         assertThat(values.getAsInteger(BatteryHistEntry.KEY_BATTERY_STATUS))
147             .isEqualTo(BatteryManager.BATTERY_STATUS_FULL);
148         assertThat(values.getAsInteger(BatteryHistEntry.KEY_BATTERY_HEALTH))
149             .isEqualTo(BatteryManager.BATTERY_HEALTH_COLD);
150         assertThat(values.getAsString(BatteryHistEntry.KEY_PACKAGE_NAME))
151             .isEqualTo(ConvertUtils.FAKE_PACKAGE_NAME);
152     }
153 
154     @Test
getIndexedUsageMap_nullOrEmptyHistoryMap_returnEmptyCollection()155     public void getIndexedUsageMap_nullOrEmptyHistoryMap_returnEmptyCollection() {
156         final int timeSlotSize = 2;
157         final long[] batteryHistoryKeys = new long[] {101L, 102L, 103L, 104L, 105L};
158 
159         assertThat(ConvertUtils.getIndexedUsageMap(
160                 mContext, timeSlotSize, batteryHistoryKeys,
161                 /*batteryHistoryMap=*/ null, /*purgeLowPercentageAndFakeData=*/ true))
162             .isEmpty();
163         assertThat(ConvertUtils.getIndexedUsageMap(
164                 mContext, timeSlotSize, batteryHistoryKeys,
165                 new HashMap<Long, Map<String, BatteryHistEntry>>(),
166                 /*purgeLowPercentageAndFakeData=*/ true))
167             .isEmpty();
168     }
169     @Test
getIndexedUsageMap_returnsExpectedResult()170     public void getIndexedUsageMap_returnsExpectedResult() {
171         // Creates the fake testing data.
172         final int timeSlotSize = 2;
173         final long[] batteryHistoryKeys = new long[] {101L, 102L, 103L, 104L, 105L};
174         final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
175             new HashMap<>();
176         final BatteryHistEntry fakeEntry = createBatteryHistEntry(
177             ConvertUtils.FAKE_PACKAGE_NAME, "fake_label", 0, 0L, 0L, 0L);
178         // Adds the index = 0 data.
179         Map<String, BatteryHistEntry> entryMap = new HashMap<>();
180         BatteryHistEntry entry = createBatteryHistEntry(
181             "package1", "label1", 5.0, 1L, 10L, 20L);
182         entryMap.put(entry.getKey(), entry);
183         entryMap.put(fakeEntry.getKey(), fakeEntry);
184         batteryHistoryMap.put(Long.valueOf(batteryHistoryKeys[0]), entryMap);
185         // Adds the index = 1 data.
186         entryMap = new HashMap<>();
187         entryMap.put(fakeEntry.getKey(), fakeEntry);
188         batteryHistoryMap.put(Long.valueOf(batteryHistoryKeys[1]), entryMap);
189         // Adds the index = 2 data.
190         entryMap = new HashMap<>();
191         entry = createBatteryHistEntry(
192             "package2", "label2", 10.0, 2L, 15L, 25L);
193         entryMap.put(entry.getKey(), entry);
194         entryMap.put(fakeEntry.getKey(), fakeEntry);
195         batteryHistoryMap.put(Long.valueOf(batteryHistoryKeys[2]), entryMap);
196         // Adds the index = 3 data.
197         entryMap = new HashMap<>();
198         entry = createBatteryHistEntry(
199             "package2", "label2", 15.0, 2L, 25L, 35L);
200         entryMap.put(entry.getKey(), entry);
201         entry = createBatteryHistEntry(
202             "package3", "label3", 5.0, 3L, 5L, 5L);
203         entryMap.put(entry.getKey(), entry);
204         entryMap.put(fakeEntry.getKey(), fakeEntry);
205         batteryHistoryMap.put(Long.valueOf(batteryHistoryKeys[3]), entryMap);
206         // Adds the index = 4 data.
207         entryMap = new HashMap<>();
208         entry = createBatteryHistEntry(
209             "package2", "label2", 30.0, 2L, 30L, 40L);
210         entryMap.put(entry.getKey(), entry);
211         entry = createBatteryHistEntry(
212             "package2", "label2", 75.0, 4L, 40L, 50L);
213         entryMap.put(entry.getKey(), entry);
214         entry = createBatteryHistEntry(
215             "package3", "label3", 5.0, 3L, 5L, 5L);
216         entryMap.put(entry.getKey(), entry);
217         entryMap.put(fakeEntry.getKey(), fakeEntry);
218         batteryHistoryMap.put(Long.valueOf(batteryHistoryKeys[4]), entryMap);
219 
220         final Map<Integer, List<BatteryDiffEntry>> resultMap =
221             ConvertUtils.getIndexedUsageMap(
222                 mContext, timeSlotSize, batteryHistoryKeys, batteryHistoryMap,
223                 /*purgeLowPercentageAndFakeData=*/ false);
224 
225         assertThat(resultMap).hasSize(3);
226         // Verifies the first timestamp result.
227         List<BatteryDiffEntry> entryList = resultMap.get(Integer.valueOf(0));
228         assertThat(entryList).hasSize(1);
229         assertBatteryDiffEntry(entryList.get(0), 100, 15L, 25L);
230         // Verifies the second timestamp result.
231         entryList = resultMap.get(Integer.valueOf(1));
232         assertThat(entryList).hasSize(3);
233         assertBatteryDiffEntry(entryList.get(1), 5, 5L, 5L);
234         assertBatteryDiffEntry(entryList.get(2), 75, 40L, 50L);
235         assertBatteryDiffEntry(entryList.get(0), 20, 15L, 15L);
236         // Verifies the last 24 hours aggregate result.
237         entryList = resultMap.get(Integer.valueOf(-1));
238         assertThat(entryList).hasSize(3);
239         assertBatteryDiffEntry(entryList.get(1), 4, 5L, 5L);
240         assertBatteryDiffEntry(entryList.get(2), 68, 40L, 50L);
241         assertBatteryDiffEntry(entryList.get(0), 27, 30L, 40L);
242 
243         // Test getIndexedUsageMap() with purged data.
244         ConvertUtils.PERCENTAGE_OF_TOTAL_THRESHOLD = 50;
245         final Map<Integer, List<BatteryDiffEntry>> purgedResultMap =
246             ConvertUtils.getIndexedUsageMap(
247                 mContext, timeSlotSize, batteryHistoryKeys, batteryHistoryMap,
248                  /*purgeLowPercentageAndFakeData=*/ true);
249 
250         assertThat(purgedResultMap).hasSize(3);
251         // Verifies the first timestamp result.
252         entryList = purgedResultMap.get(Integer.valueOf(0));
253         assertThat(entryList).hasSize(1);
254         // Verifies the second timestamp result.
255         entryList = purgedResultMap.get(Integer.valueOf(1));
256         assertThat(entryList).hasSize(1);
257         assertBatteryDiffEntry(entryList.get(0), 75, 40L, 50L);
258         // Verifies the last 24 hours aggregate result.
259         entryList = purgedResultMap.get(Integer.valueOf(-1));
260         assertThat(entryList).hasSize(1);
261         // Verifies the fake data is cleared out.
262         assertThat(entryList.get(0).getPackageName())
263             .isNotEqualTo(ConvertUtils.FAKE_PACKAGE_NAME);
264 
265         // Adds lacked data into the battery history map.
266         final int remainingSize = 25 - batteryHistoryKeys.length;
267         for (int index = 0; index < remainingSize; index++) {
268             batteryHistoryMap.put(105L + index + 1, new HashMap<>());
269         }
270         when(mPowerUsageFeatureProvider.getBatteryHistory(mContext))
271             .thenReturn(batteryHistoryMap);
272 
273         final List<BatteryDiffEntry> batteryDiffEntryList =
274             BatteryChartPreferenceController.getBatteryLast24HrUsageData(mContext);
275 
276         assertThat(batteryDiffEntryList).isNotEmpty();
277         final BatteryDiffEntry resultEntry = batteryDiffEntryList.get(0);
278         assertThat(resultEntry.getPackageName()).isEqualTo("package2");
279     }
280 
281     @Test
getIndexedUsageMap_usageTimeExceed_returnsExpectedResult()282     public void getIndexedUsageMap_usageTimeExceed_returnsExpectedResult() {
283         final int timeSlotSize = 1;
284         final long[] batteryHistoryKeys = new long[] {101L, 102L, 103L};
285         final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
286             new HashMap<>();
287         final BatteryHistEntry fakeEntry = createBatteryHistEntry(
288             ConvertUtils.FAKE_PACKAGE_NAME, "fake_label", 0, 0L, 0L, 0L);
289         // Adds the index = 0 data.
290         Map<String, BatteryHistEntry> entryMap = new HashMap<>();
291         entryMap.put(fakeEntry.getKey(), fakeEntry);
292         batteryHistoryMap.put(Long.valueOf(batteryHistoryKeys[0]), entryMap);
293         // Adds the index = 1 data.
294         entryMap = new HashMap<>();
295         entryMap.put(fakeEntry.getKey(), fakeEntry);
296         batteryHistoryMap.put(Long.valueOf(batteryHistoryKeys[1]), entryMap);
297         // Adds the index = 2 data.
298         entryMap = new HashMap<>();
299         final BatteryHistEntry entry = createBatteryHistEntry(
300             "package3", "label3", 500, 5L, 3600000L, 7200000L);
301         entryMap.put(entry.getKey(), entry);
302         batteryHistoryMap.put(Long.valueOf(batteryHistoryKeys[2]), entryMap);
303 
304         final Map<Integer, List<BatteryDiffEntry>> purgedResultMap =
305             ConvertUtils.getIndexedUsageMap(
306                 mContext, timeSlotSize, batteryHistoryKeys, batteryHistoryMap,
307                 /*purgeLowPercentageAndFakeData=*/ true);
308 
309         assertThat(purgedResultMap).hasSize(2);
310         final List<BatteryDiffEntry> entryList = purgedResultMap.get(0);
311         assertThat(entryList).hasSize(1);
312         // Verifies the clipped usage time.
313         final float ratio = (float) (7200) / (float) (3600 + 7200);
314         final BatteryDiffEntry resultEntry = entryList.get(0);
315         assertThat(resultEntry.mForegroundUsageTimeInMs)
316             .isEqualTo(Math.round(entry.mForegroundUsageTimeInMs * ratio));
317         assertThat(resultEntry.mBackgroundUsageTimeInMs)
318             .isEqualTo(Math.round(entry.mBackgroundUsageTimeInMs * ratio));
319         assertThat(resultEntry.mConsumePower)
320             .isEqualTo(entry.mConsumePower * ratio);
321     }
322 
323     @Test
getIndexedUsageMap_hideBackgroundUsageTime_returnsExpectedResult()324     public void getIndexedUsageMap_hideBackgroundUsageTime_returnsExpectedResult() {
325         final long[] batteryHistoryKeys = new long[] {101L, 102L, 103L};
326         final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
327         final BatteryHistEntry fakeEntry = createBatteryHistEntry(
328             ConvertUtils.FAKE_PACKAGE_NAME, "fake_label", 0, 0L, 0L, 0L);
329         // Adds the index = 0 data.
330         Map<String, BatteryHistEntry> entryMap = new HashMap<>();
331         entryMap.put(fakeEntry.getKey(), fakeEntry);
332         batteryHistoryMap.put(Long.valueOf(batteryHistoryKeys[0]), entryMap);
333         // Adds the index = 1 data.
334         entryMap = new HashMap<>();
335         entryMap.put(fakeEntry.getKey(), fakeEntry);
336         batteryHistoryMap.put(Long.valueOf(batteryHistoryKeys[1]), entryMap);
337         // Adds the index = 2 data.
338         entryMap = new HashMap<>();
339         final BatteryHistEntry entry = createBatteryHistEntry(
340             "package3", "label3", 500, 5L, 3600000L, 7200000L);
341         entryMap.put(entry.getKey(), entry);
342         batteryHistoryMap.put(Long.valueOf(batteryHistoryKeys[2]), entryMap);
343         when(mPowerUsageFeatureProvider.getHideBackgroundUsageTimeSet(mContext))
344             .thenReturn(new HashSet(Arrays.asList((CharSequence) "package3")));
345 
346         final Map<Integer, List<BatteryDiffEntry>> purgedResultMap =
347             ConvertUtils.getIndexedUsageMap(
348                 mContext, /*timeSlotSize=*/ 1, batteryHistoryKeys, batteryHistoryMap,
349                 /*purgeLowPercentageAndFakeData=*/ true);
350 
351         final BatteryDiffEntry resultEntry = purgedResultMap.get(0).get(0);
352         assertThat(resultEntry.mBackgroundUsageTimeInMs).isEqualTo(0);
353     }
354 
355     @Test
getLocale_nullContext_returnDefaultLocale()356     public void getLocale_nullContext_returnDefaultLocale() {
357         assertThat(ConvertUtils.getLocale(/*context=*/ null))
358             .isEqualTo(Locale.getDefault());
359     }
360 
361     @Test
getLocale_nullLocaleList_returnDefaultLocale()362     public void getLocale_nullLocaleList_returnDefaultLocale() {
363         mContext.getResources().getConfiguration().setLocales(null);
364         assertThat(ConvertUtils.getLocale(mContext)).isEqualTo(Locale.getDefault());
365     }
366 
367     @Test
getLocale_emptyLocaleList_returnDefaultLocale()368     public void getLocale_emptyLocaleList_returnDefaultLocale() {
369         mContext.getResources().getConfiguration().setLocales(new LocaleList());
370         assertThat(ConvertUtils.getLocale(mContext)).isEqualTo(Locale.getDefault());
371     }
372 
createBatteryHistEntry( String packageName, String appLabel, double consumePower, long uid, long foregroundUsageTimeInMs, long backgroundUsageTimeInMs)373     private static BatteryHistEntry createBatteryHistEntry(
374             String packageName, String appLabel, double consumePower,
375             long uid, long foregroundUsageTimeInMs, long backgroundUsageTimeInMs) {
376         // Only insert required fields.
377         final ContentValues values = new ContentValues();
378         values.put(BatteryHistEntry.KEY_PACKAGE_NAME, packageName);
379         values.put(BatteryHistEntry.KEY_APP_LABEL, appLabel);
380         values.put(BatteryHistEntry.KEY_UID, Long.valueOf(uid));
381         values.put(BatteryHistEntry.KEY_CONSUMER_TYPE,
382             Integer.valueOf(ConvertUtils.CONSUMER_TYPE_UID_BATTERY));
383         values.put(BatteryHistEntry.KEY_CONSUME_POWER, consumePower);
384         values.put(BatteryHistEntry.KEY_FOREGROUND_USAGE_TIME,
385             Long.valueOf(foregroundUsageTimeInMs));
386         values.put(BatteryHistEntry.KEY_BACKGROUND_USAGE_TIME,
387             Long.valueOf(backgroundUsageTimeInMs));
388         return new BatteryHistEntry(values);
389     }
390 
assertBatteryDiffEntry( BatteryDiffEntry entry, int percentOfTotal, long foregroundUsageTimeInMs, long backgroundUsageTimeInMs)391     private static void assertBatteryDiffEntry(
392             BatteryDiffEntry entry, int percentOfTotal,
393             long foregroundUsageTimeInMs, long backgroundUsageTimeInMs) {
394         assertThat((int) entry.getPercentOfTotal()).isEqualTo(percentOfTotal);
395         assertThat(entry.mForegroundUsageTimeInMs).isEqualTo(foregroundUsageTimeInMs);
396         assertThat(entry.mBackgroundUsageTimeInMs).isEqualTo(backgroundUsageTimeInMs);
397     }
398 }
399