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.ArgumentMatchers.any;
21 import static org.mockito.ArgumentMatchers.eq;
22 import static org.mockito.Matchers.anyInt;
23 import static org.mockito.Mockito.doReturn;
24 import static org.mockito.Mockito.spy;
25 
26 import android.content.ContentValues;
27 import android.content.Context;
28 import android.content.pm.ApplicationInfo;
29 import android.content.pm.PackageInfo;
30 import android.content.pm.PackageManager;
31 import android.graphics.drawable.Drawable;
32 import android.graphics.drawable.Drawable.ConstantState;
33 import android.os.BatteryConsumer;
34 import android.os.UserHandle;
35 import android.os.UserManager;
36 
37 import com.android.settings.R;
38 
39 import org.junit.Before;
40 import org.junit.Test;
41 import org.junit.runner.RunWith;
42 import org.mockito.Mock;
43 import org.mockito.MockitoAnnotations;
44 import org.robolectric.RobolectricTestRunner;
45 import org.robolectric.RuntimeEnvironment;
46 import org.robolectric.annotation.Config;
47 import org.robolectric.annotation.Implementation;
48 import org.robolectric.annotation.Implements;
49 import org.robolectric.annotation.Resetter;
50 
51 import java.util.ArrayList;
52 import java.util.Collections;
53 import java.util.List;
54 import java.util.Locale;
55 
56 @RunWith(RobolectricTestRunner.class)
57 @Config(shadows = {BatteryDiffEntryTest.ShadowUserHandle.class})
58 public final class BatteryDiffEntryTest {
59 
60     private Context mContext;
61 
62     @Mock private ApplicationInfo mockAppInfo;
63     @Mock private PackageManager mockPackageManager;
64     @Mock private UserManager mockUserManager;
65     @Mock private Drawable mockDrawable;
66     @Mock private Drawable mockDrawable2;
67     @Mock private Drawable mockBadgedDrawable;
68     @Mock private BatteryHistEntry mBatteryHistEntry;
69     @Mock private PackageInfo mockPackageInfo;
70     @Mock private ConstantState mockConstantState;
71 
72     @Before
setUp()73     public void setUp() {
74         MockitoAnnotations.initMocks(this);
75         ShadowUserHandle.reset();
76         mContext = spy(RuntimeEnvironment.application);
77         doReturn(mContext).when(mContext).getApplicationContext();
78         doReturn(mockUserManager).when(mContext).getSystemService(UserManager.class);
79         doReturn(mockPackageManager).when(mContext).getPackageManager();
80         BatteryDiffEntry.clearCache();
81     }
82 
83     @Test
testSetTotalConsumePower_returnExpectedResult()84     public void testSetTotalConsumePower_returnExpectedResult() {
85         final BatteryDiffEntry entry =
86             new BatteryDiffEntry(
87                 mContext,
88                 /*foregroundUsageTimeInMs=*/ 10001L,
89                 /*backgroundUsageTimeInMs=*/ 20002L,
90                 /*consumePower=*/ 22.0,
91                 /*batteryHistEntry=*/ null);
92         entry.setTotalConsumePower(100.0);
93 
94         assertThat(entry.getPercentOfTotal()).isEqualTo(22.0);
95     }
96 
97     @Test
testSetTotalConsumePower_setZeroValue_returnsZeroValue()98     public void testSetTotalConsumePower_setZeroValue_returnsZeroValue() {
99         final BatteryDiffEntry entry =
100             new BatteryDiffEntry(
101                 mContext,
102                 /*foregroundUsageTimeInMs=*/ 10001L,
103                 /*backgroundUsageTimeInMs=*/ 20002L,
104                 /*consumePower=*/ 22.0,
105                 /*batteryHistEntry=*/ null);
106         entry.setTotalConsumePower(0);
107 
108         assertThat(entry.getPercentOfTotal()).isEqualTo(0);
109     }
110 
111     @Test
testComparator_sortCollectionsInDescOrder()112     public void testComparator_sortCollectionsInDescOrder() {
113         final List<BatteryDiffEntry> entryList = new ArrayList<>();
114         // Generates fake testing data.
115         entryList.add(createBatteryDiffEntry(30, mBatteryHistEntry));
116         entryList.add(createBatteryDiffEntry(20, mBatteryHistEntry));
117         entryList.add(createBatteryDiffEntry(10, mBatteryHistEntry));
118         Collections.sort(entryList, BatteryDiffEntry.COMPARATOR);
119 
120         assertThat(entryList.get(0).getPercentOfTotal()).isEqualTo(30);
121         assertThat(entryList.get(1).getPercentOfTotal()).isEqualTo(20);
122         assertThat(entryList.get(2).getPercentOfTotal()).isEqualTo(10);
123     }
124 
125     @Test
testLoadLabelAndIcon_forSystemBattery_returnExpectedResult()126     public void testLoadLabelAndIcon_forSystemBattery_returnExpectedResult() {
127         final String expectedName = "Ambient display";
128         // Generates fake testing data.
129         final ContentValues values = getContentValuesWithType(
130             ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY);
131         values.put("drainType",
132             Integer.valueOf(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY));
133         final BatteryHistEntry batteryHistEntry = new BatteryHistEntry(values);
134 
135         final BatteryDiffEntry entry = createBatteryDiffEntry(10, batteryHistEntry);
136 
137         assertThat(entry.getAppLabel()).isEqualTo(expectedName);
138         assertThat(entry.getAppIconId()).isEqualTo(R.drawable.ic_settings_aod);
139         assertThat(BatteryDiffEntry.sResourceCache).hasSize(1);
140         // Verifies the app label in the cache.
141         final BatteryEntry.NameAndIcon nameAndIcon =
142             BatteryDiffEntry.sResourceCache.get(entry.getKey());
143         assertThat(nameAndIcon.name).isEqualTo(expectedName);
144         assertThat(nameAndIcon.iconId).isEqualTo(R.drawable.ic_settings_aod);
145         // Verifies the restrictable flag in the cache.
146         assertThat(entry.mValidForRestriction).isTrue();
147         assertThat(BatteryDiffEntry.sValidForRestriction.get(entry.getKey())).isTrue();
148     }
149 
150     @Test
testLoadLabelAndIcon_forUserBattery_returnExpectedResult()151     public void testLoadLabelAndIcon_forUserBattery_returnExpectedResult() {
152         final String expectedName = "Removed user";
153         doReturn(null).when(mockUserManager).getUserInfo(1001);
154         // Generates fake testing data.
155         final ContentValues values = getContentValuesWithType(
156             ConvertUtils.CONSUMER_TYPE_USER_BATTERY);
157         values.put("userId", Integer.valueOf(1001));
158         final BatteryHistEntry batteryHistEntry = new BatteryHistEntry(values);
159 
160         final BatteryDiffEntry entry = createBatteryDiffEntry(10, batteryHistEntry);
161 
162         assertThat(entry.getAppLabel()).isEqualTo(expectedName);
163         assertThat(entry.getAppIcon()).isNull();
164         assertThat(entry.getAppIconId()).isEqualTo(0);
165         assertThat(BatteryDiffEntry.sResourceCache).hasSize(1);
166         // Verifies the app label in the cache.
167         final BatteryEntry.NameAndIcon nameAndIcon =
168             BatteryDiffEntry.sResourceCache.get(entry.getKey());
169         assertThat(nameAndIcon.name).isEqualTo(expectedName);
170         assertThat(nameAndIcon.iconId).isEqualTo(0);
171         // Verifies the restrictable flag in the cache.
172         assertThat(entry.mValidForRestriction).isTrue();
173         assertThat(BatteryDiffEntry.sValidForRestriction.get(entry.getKey())).isTrue();
174     }
175 
176     @Test
testGetAppLabel_loadDataFromApplicationInfo()177     public void testGetAppLabel_loadDataFromApplicationInfo() throws Exception {
178         final String expectedAppLabel = "fake app label";
179         final String fakePackageName = "com.fake.google.com";
180         final ContentValues values = getContentValuesWithType(
181             ConvertUtils.CONSUMER_TYPE_UID_BATTERY);
182         values.put("uid", /*invalid uid*/ 10001);
183         values.put("packageName", fakePackageName);
184         doReturn(mockAppInfo).when(mockPackageManager)
185             .getApplicationInfo(fakePackageName, 0);
186         doReturn(expectedAppLabel).when(mockPackageManager)
187             .getApplicationLabel(mockAppInfo);
188         final BatteryHistEntry batteryHistEntry = new BatteryHistEntry(values);
189 
190         final BatteryDiffEntry entry = createBatteryDiffEntry(10, batteryHistEntry);
191 
192         assertThat(entry.getAppLabel()).isEqualTo(expectedAppLabel);
193         assertThat(entry.getAppIconId()).isEqualTo(0);
194         assertThat(BatteryDiffEntry.sResourceCache).hasSize(1);
195         // Verifies the app label in the cache.
196         final BatteryEntry.NameAndIcon nameAndIcon =
197             BatteryDiffEntry.sResourceCache.get(entry.getKey());
198         assertThat(nameAndIcon.name).isEqualTo(expectedAppLabel);
199         // Verifies the restrictable flag in the cache.
200         assertThat(entry.mValidForRestriction).isFalse();
201         assertThat(BatteryDiffEntry.sValidForRestriction.get(entry.getKey())).isFalse();
202     }
203 
204     @Test
testGetAppLabel_loadDataFromPreDefinedNameAndUid()205     public void testGetAppLabel_loadDataFromPreDefinedNameAndUid() {
206         final String expectedAppLabel = "Android OS";
207         final ContentValues values = getContentValuesWithType(
208             ConvertUtils.CONSUMER_TYPE_UID_BATTERY);
209         final BatteryHistEntry batteryHistEntry = new BatteryHistEntry(values);
210 
211         final BatteryDiffEntry entry = createBatteryDiffEntry(10, batteryHistEntry);
212 
213         assertThat(entry.getAppLabel()).isEqualTo(expectedAppLabel);
214         assertThat(BatteryDiffEntry.sResourceCache).hasSize(1);
215         // Verifies the app label in the cache.
216         final BatteryEntry.NameAndIcon nameAndIcon =
217             BatteryDiffEntry.sResourceCache.get(entry.getKey());
218         assertThat(nameAndIcon.name).isEqualTo(expectedAppLabel);
219     }
220 
221     @Test
testGetAppLabel_nullAppLabel_returnAppLabelInBatteryHistEntry()222     public void testGetAppLabel_nullAppLabel_returnAppLabelInBatteryHistEntry() {
223         final String expectedAppLabel = "fake app label";
224         final ContentValues values = getContentValuesWithType(
225             ConvertUtils.CONSUMER_TYPE_UID_BATTERY);
226         values.put("appLabel", expectedAppLabel);
227         final BatteryHistEntry batteryHistEntry = new BatteryHistEntry(values);
228 
229         final BatteryDiffEntry entry = createBatteryDiffEntry(10, batteryHistEntry);
230 
231         entry.mIsLoaded = true;
232         assertThat(entry.getAppLabel()).isEqualTo(expectedAppLabel);
233         assertThat(BatteryDiffEntry.sResourceCache).isEmpty();
234     }
235 
236     @Test
testGetAppIcon_nonUidConsumer_returnAppIconInBatteryDiffEntry()237     public void testGetAppIcon_nonUidConsumer_returnAppIconInBatteryDiffEntry() {
238         final ContentValues values = getContentValuesWithType(
239             ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY);
240         final BatteryHistEntry batteryHistEntry = new BatteryHistEntry(values);
241         mockConstantState(mockDrawable);
242 
243         final BatteryDiffEntry entry = createBatteryDiffEntry(10, batteryHistEntry);
244 
245         entry.mIsLoaded = true;
246         entry.mAppIcon = mockDrawable;
247         assertThat(entry.getAppIcon()).isEqualTo(mockDrawable);
248         assertThat(BatteryDiffEntry.sResourceCache).isEmpty();
249     }
250 
251     @Test
testGetAppIcon_uidConsumerForNonOwner_returnDefaultActivityIconWithBadge()252     public void testGetAppIcon_uidConsumerForNonOwner_returnDefaultActivityIconWithBadge()
253             throws Exception {
254         ShadowUserHandle.setUid(10);
255         final BatteryDiffEntry entry = createBatteryDiffEntry(mockDrawable);
256         mockConstantState(mockDrawable);
257         mockConstantState(mockBadgedDrawable);
258         doReturn(mockBadgedDrawable).when(mockUserManager)
259             .getBadgedIconForUser(eq(mockDrawable), any());
260 
261         entry.mAppIcon = null;
262         assertThat(entry.getAppIcon()).isEqualTo(mockBadgedDrawable);
263     }
264 
265     @Test
testGetAppIcon_uidConsumerWithNullIcon_returnDefaultActivityIcon()266     public void testGetAppIcon_uidConsumerWithNullIcon_returnDefaultActivityIcon()
267             throws Exception {
268         final BatteryDiffEntry entry = createBatteryDiffEntry(mockDrawable);
269         mockConstantState(mockDrawable);
270 
271         entry.mAppIcon = null;
272         assertThat(entry.getAppIcon()).isEqualTo(mockDrawable);
273         assertThat(BatteryDiffEntry.sResourceCache).hasSize(1);
274         // Verifies the app label in the cache.
275         final BatteryEntry.NameAndIcon nameAndIcon =
276             BatteryDiffEntry.sResourceCache.get(entry.getKey());
277         assertThat(nameAndIcon.icon).isEqualTo(mockDrawable);
278     }
279 
280     @Test
testClearCache_clearDataForResourcesAndFlags()281     public void testClearCache_clearDataForResourcesAndFlags() {
282         BatteryDiffEntry.sResourceCache.put(
283             "fake application key",
284             new BatteryEntry.NameAndIcon("app label", null, /*iconId=*/ 0));
285         BatteryDiffEntry.sValidForRestriction.put(
286             "fake application key", Boolean.valueOf(false));
287 
288         BatteryDiffEntry.clearCache();
289 
290         assertThat(BatteryDiffEntry.sResourceCache).isEmpty();
291         assertThat(BatteryDiffEntry.sValidForRestriction).isEmpty();
292     }
293 
294     @Test
testClearCache_switchLocale_clearCacheIconAndLabel()295     public void testClearCache_switchLocale_clearCacheIconAndLabel() throws Exception {
296         final int userId = UserHandle.getUserId(1001);
297         Locale.setDefault(new Locale("en_US"));
298         final BatteryDiffEntry entry1 = createBatteryDiffEntry(mockDrawable);
299         mockConstantState(mockDrawable);
300         assertThat(entry1.getAppIcon()).isEqualTo(mockDrawable);
301         // Switch the locale into another one.
302         Locale.setDefault(new Locale("zh_TW"));
303 
304         final BatteryDiffEntry entry2 = createBatteryDiffEntry(mockDrawable2);
305 
306         // We should get new drawable without caching.
307         mockConstantState(mockDrawable2);
308         assertThat(entry2.getAppIcon()).isEqualTo(mockDrawable2);
309         // Verifies the cache is updated into the new drawable.
310         final BatteryEntry.NameAndIcon nameAndIcon =
311             BatteryDiffEntry.sResourceCache.get(entry2.getKey());
312         assertThat(nameAndIcon.icon).isEqualTo(mockDrawable2);
313     }
314 
315     @Test
testIsSystemEntry_userBattery_returnTrue()316     public void testIsSystemEntry_userBattery_returnTrue() {
317         final BatteryDiffEntry entry =
318             createBatteryDiffEntry(
319                 ConvertUtils.CONSUMER_TYPE_USER_BATTERY,
320                 /*uid=*/ 0, /*isHidden=*/ false);
321         assertThat(entry.isSystemEntry()).isTrue();
322     }
323 
324     @Test
testIsSystemEntry_systemBattery_returnTrue()325     public void testIsSystemEntry_systemBattery_returnTrue() {
326         final BatteryDiffEntry entry =
327             createBatteryDiffEntry(
328                 ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY,
329                 /*uid=*/ 0, /*isHidden=*/ false);
330         assertThat(entry.isSystemEntry()).isTrue();
331     }
332 
333     @Test
testIsSystemEntry_uidBattery_returnFalse()334     public void testIsSystemEntry_uidBattery_returnFalse() {
335         final BatteryDiffEntry entry =
336             createBatteryDiffEntry(
337                 ConvertUtils.CONSUMER_TYPE_UID_BATTERY,
338                 /*uid=*/ 123, /*isHidden=*/ false);
339         assertThat(entry.isSystemEntry()).isFalse();
340     }
341 
342     @Test
testIsSystemEntry_uidBatteryWithHiddenState_returnTrue()343     public void testIsSystemEntry_uidBatteryWithHiddenState_returnTrue() {
344         final BatteryDiffEntry entry =
345             createBatteryDiffEntry(
346                 ConvertUtils.CONSUMER_TYPE_UID_BATTERY,
347                 /*uid=*/ 123, /*isHidden=*/ true);
348         assertThat(entry.isSystemEntry()).isTrue();
349     }
350 
351     @Test
testIsSystemEntry_uidBatteryWithSystemProcess_returnTrue()352     public void testIsSystemEntry_uidBatteryWithSystemProcess_returnTrue() {
353         final BatteryDiffEntry entry =
354             createBatteryDiffEntry(
355                 ConvertUtils.CONSUMER_TYPE_UID_BATTERY,
356                 /*uid=*/ 1230, /*isHidden=*/ false);
357         assertThat(entry.isSystemEntry()).isTrue();
358     }
359 
360     @Test
testUpdateRestrictionFlagState_updateFlagAsExpected()361     public void testUpdateRestrictionFlagState_updateFlagAsExpected() throws Exception {
362         final String expectedAppLabel = "fake app label";
363         final String fakePackageName = "com.fake.google.com";
364         final ContentValues values = getContentValuesWithType(
365             ConvertUtils.CONSUMER_TYPE_UID_BATTERY);
366         values.put("uid", /*invalid uid*/ 10001);
367         values.put("packageName", fakePackageName);
368         final BatteryDiffEntry entry =
369             createBatteryDiffEntry(10, new BatteryHistEntry(values));
370 
371         entry.updateRestrictionFlagState();
372         // Sets false if the app entry cannot be found.
373         assertThat(entry.mValidForRestriction).isFalse();
374 
375         doReturn(BatteryUtils.UID_NULL).when(mockPackageManager).getPackageUid(
376             entry.getPackageName(), PackageManager.GET_META_DATA);
377         entry.updateRestrictionFlagState();
378         // Sets false if the app is invalid package name.
379         assertThat(entry.mValidForRestriction).isFalse();
380 
381         doReturn(1000).when(mockPackageManager).getPackageUid(
382             entry.getPackageName(), PackageManager.GET_META_DATA);
383         entry.updateRestrictionFlagState();
384         // Sets false if the app PackageInfo cannot be found.
385         assertThat(entry.mValidForRestriction).isFalse();
386 
387         doReturn(mockPackageInfo).when(mockPackageManager).getPackageInfo(
388             eq(entry.getPackageName()), anyInt());
389         entry.updateRestrictionFlagState();
390         // Sets true if package is valid and PackageInfo can be found.
391         assertThat(entry.mValidForRestriction).isTrue();
392     }
393 
394     @Test
testGetPackageName_returnExpectedResult()395     public void testGetPackageName_returnExpectedResult() {
396         final String expectedPackageName = "com.fake.google.com";
397         final ContentValues values = getContentValuesWithType(
398             ConvertUtils.CONSUMER_TYPE_UID_BATTERY);
399         values.put("packageName", expectedPackageName);
400         final BatteryDiffEntry entry =
401             createBatteryDiffEntry(10, new BatteryHistEntry(values));
402 
403         assertThat(entry.getPackageName()).isEqualTo(expectedPackageName);
404     }
405 
406     @Test
testGetPackageName_withProcessName_returnExpectedResult()407     public void testGetPackageName_withProcessName_returnExpectedResult() {
408         final String expectedPackageName = "com.fake.google.com";
409         final ContentValues values = getContentValuesWithType(
410             ConvertUtils.CONSUMER_TYPE_UID_BATTERY);
411         values.put(
412             "packageName",
413             expectedPackageName + ":privileged_process0");
414         final BatteryDiffEntry entry =
415             createBatteryDiffEntry(10, new BatteryHistEntry(values));
416 
417         assertThat(entry.getPackageName()).isEqualTo(expectedPackageName);
418     }
419 
createBatteryDiffEntry( int consumerType, long uid, boolean isHidden)420     private BatteryDiffEntry createBatteryDiffEntry(
421             int consumerType, long uid, boolean isHidden) {
422         final ContentValues values = getContentValuesWithType(consumerType);
423         values.put("isHidden", isHidden);
424         values.put("uid", uid);
425         return new BatteryDiffEntry(
426             mContext,
427             /*foregroundUsageTimeInMs=*/ 0,
428             /*backgroundUsageTimeInMs=*/ 0,
429             /*consumePower=*/ 0,
430             new BatteryHistEntry(values));
431     }
432 
createBatteryDiffEntry( double consumePower, BatteryHistEntry batteryHistEntry)433     private BatteryDiffEntry createBatteryDiffEntry(
434         double consumePower, BatteryHistEntry batteryHistEntry) {
435         final BatteryDiffEntry entry = new BatteryDiffEntry(
436             mContext,
437             /*foregroundUsageTimeInMs=*/ 0,
438             /*backgroundUsageTimeInMs=*/ 0,
439             consumePower,
440             batteryHistEntry);
441         entry.setTotalConsumePower(100.0);
442         return entry;
443     }
444 
getContentValuesWithType(int consumerType)445     private static ContentValues getContentValuesWithType(int consumerType) {
446         final ContentValues values = new ContentValues();
447         values.put("consumerType", Integer.valueOf(consumerType));
448         return values;
449     }
450 
createBatteryDiffEntry(Drawable drawable)451     private BatteryDiffEntry createBatteryDiffEntry(Drawable drawable) throws Exception {
452         final ContentValues values = getContentValuesWithType(
453             ConvertUtils.CONSUMER_TYPE_UID_BATTERY);
454         values.put("uid", 1001);
455         values.put("packageName", "com.a.b.c");
456         final BatteryHistEntry batteryHistEntry = new BatteryHistEntry(values);
457         doReturn(drawable).when(mockPackageManager).getDefaultActivityIcon();
458         doReturn(null).when(mockPackageManager).getApplicationInfo("com.a.b.c", 0);
459         doReturn(new String[] {"com.a.b.c"}).when(mockPackageManager)
460             .getPackagesForUid(1001);
461         return createBatteryDiffEntry(10, batteryHistEntry);
462     }
463 
mockConstantState(Drawable drawable)464     private void mockConstantState(Drawable drawable) {
465         doReturn(mockConstantState).when(drawable).getConstantState();
466         doReturn(drawable).when(mockConstantState).newDrawable();
467     }
468 
469     @Implements(UserHandle.class)
470     public static class ShadowUserHandle {
471         // Sets the default as thte OWNER role.
472         private static int sUid = 0;
473 
setUid(int uid)474         public static void setUid(int uid) {
475             sUid = uid;
476         }
477 
478         @Implementation
myUserId()479         public static int myUserId() {
480             return sUid;
481         }
482 
483         @Implementation
getUserId(int userId)484         public static int getUserId(int userId) {
485             return sUid;
486         }
487 
488         @Resetter
reset()489         public static void reset() {
490             sUid = 0;
491         }
492     }
493 }
494