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 
17 package com.android.settings.fuelgauge;
18 
19 import static com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_OPTIMIZED;
20 import static com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_RESTRICTED;
21 import static com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_UNRESTRICTED;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 
25 import static org.junit.Assert.assertFalse;
26 import static org.junit.Assert.assertNull;
27 import static org.junit.Assert.assertTrue;
28 import static org.mockito.Mockito.anyInt;
29 import static org.mockito.Mockito.anyLong;
30 import static org.mockito.Mockito.anyString;
31 import static org.mockito.Mockito.doReturn;
32 import static org.mockito.Mockito.doThrow;
33 import static org.mockito.Mockito.inOrder;
34 import static org.mockito.Mockito.never;
35 import static org.mockito.Mockito.spy;
36 import static org.mockito.Mockito.verify;
37 import static org.mockito.Mockito.verifyNoInteractions;
38 import static org.mockito.Mockito.verifyNoMoreInteractions;
39 import static org.mockito.Mockito.when;
40 
41 import android.app.AppOpsManager;
42 import android.content.Context;
43 import android.content.pm.ApplicationInfo;
44 import android.content.pm.IPackageManager;
45 import android.content.pm.PackageManager;
46 import android.content.pm.ParceledListSlice;
47 import android.content.pm.UserInfo;
48 import android.os.UserManager;
49 import android.util.ArraySet;
50 
51 import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action;
52 import com.android.settingslib.datastore.DataChangeReason;
53 import com.android.settingslib.datastore.Observer;
54 import com.android.settingslib.fuelgauge.PowerAllowlistBackend;
55 
56 import com.google.common.util.concurrent.MoreExecutors;
57 
58 import org.junit.Before;
59 import org.junit.Test;
60 import org.junit.runner.RunWith;
61 import org.mockito.InOrder;
62 import org.mockito.Mock;
63 import org.mockito.MockitoAnnotations;
64 import org.robolectric.RobolectricTestRunner;
65 import org.robolectric.RuntimeEnvironment;
66 
67 import java.util.Arrays;
68 import java.util.concurrent.TimeUnit;
69 
70 @RunWith(RobolectricTestRunner.class)
71 public class BatteryOptimizeUtilsTest {
72 
73     private static final int UID = 12345;
74     private static final String PACKAGE_NAME = "com.android.app";
75 
76     @Mock private BatteryUtils mMockBatteryUtils;
77     @Mock private AppOpsManager mMockAppOpsManager;
78     @Mock private PowerAllowlistBackend mMockBackend;
79     @Mock private IPackageManager mMockIPackageManager;
80     @Mock private UserManager mMockUserManager;
81     @Mock private Observer mObserver;
82 
83     private Context mContext;
84     private BatteryOptimizeUtils mBatteryOptimizeUtils;
85     private BatterySettingsStorage mBatterySettingsStorage;
86 
87     @Before
setUp()88     public void setUp() {
89         MockitoAnnotations.initMocks(this);
90         mContext = spy(RuntimeEnvironment.application);
91         mBatterySettingsStorage = BatterySettingsStorage.get(mContext);
92         mBatterySettingsStorage.addObserver(mObserver, MoreExecutors.directExecutor());
93         mBatteryOptimizeUtils = spy(new BatteryOptimizeUtils(mContext, UID, PACKAGE_NAME));
94         mBatteryOptimizeUtils.mAppOpsManager = mMockAppOpsManager;
95         mBatteryOptimizeUtils.mBatteryUtils = mMockBatteryUtils;
96         mBatteryOptimizeUtils.mPowerAllowListBackend = mMockBackend;
97         // Sets the default mode as MODE_RESTRICTED.
98         mBatteryOptimizeUtils.mMode = AppOpsManager.MODE_IGNORED;
99         mBatteryOptimizeUtils.mAllowListed = false;
100         doReturn(mMockUserManager).when(mContext).getSystemService(UserManager.class);
101     }
102 
103     @Test
testGetAppOptimizationMode_returnRestricted()104     public void testGetAppOptimizationMode_returnRestricted() {
105         when(mMockBackend.isAllowlisted(anyString(), anyInt())).thenReturn(false);
106         when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), anyString()))
107                 .thenReturn(AppOpsManager.MODE_IGNORED);
108 
109         assertThat(mBatteryOptimizeUtils.getAppOptimizationMode()).isEqualTo(MODE_RESTRICTED);
110     }
111 
112     @Test
testGetAppOptimizationMode_returnUnrestricted()113     public void testGetAppOptimizationMode_returnUnrestricted() {
114         when(mMockBackend.isAllowlisted(anyString(), anyInt())).thenReturn(true);
115         when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), anyString()))
116                 .thenReturn(AppOpsManager.MODE_ALLOWED);
117 
118         assertThat(mBatteryOptimizeUtils.getAppOptimizationMode()).isEqualTo(MODE_UNRESTRICTED);
119     }
120 
121     @Test
testGetAppOptimizationMode_returnOptimized()122     public void testGetAppOptimizationMode_returnOptimized() {
123         when(mMockBackend.isAllowlisted(anyString(), anyInt())).thenReturn(false);
124         when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), anyString()))
125                 .thenReturn(AppOpsManager.MODE_ALLOWED);
126 
127         assertThat(mBatteryOptimizeUtils.getAppOptimizationMode()).isEqualTo(MODE_OPTIMIZED);
128     }
129 
130     @Test
testIsSystemOrDefaultApp_isSystemOrDefaultApp_returnTrue()131     public void testIsSystemOrDefaultApp_isSystemOrDefaultApp_returnTrue() {
132         when(mMockBackend.isAllowlisted(anyString(), anyInt())).thenReturn(true);
133         when(mMockBackend.isDefaultActiveApp(anyString(), anyInt())).thenReturn(true);
134 
135         assertThat(mBatteryOptimizeUtils.isSystemOrDefaultApp()).isTrue();
136     }
137 
138     @Test
testIsSystemOrDefaultApp_notSystemOrDefaultApp_returnFalse()139     public void testIsSystemOrDefaultApp_notSystemOrDefaultApp_returnFalse() {
140         assertThat(mBatteryOptimizeUtils.isSystemOrDefaultApp()).isFalse();
141     }
142 
143     @Test
isDisabledForOptimizeModeOnly_invalidPackageName_returnTrue()144     public void isDisabledForOptimizeModeOnly_invalidPackageName_returnTrue() {
145         final BatteryOptimizeUtils testBatteryOptimizeUtils =
146                 new BatteryOptimizeUtils(mContext, UID, null);
147 
148         assertThat(testBatteryOptimizeUtils.isDisabledForOptimizeModeOnly()).isTrue();
149     }
150 
151     @Test
isDisabledForOptimizeModeOnly_validPackageName_returnFalse()152     public void isDisabledForOptimizeModeOnly_validPackageName_returnFalse() {
153         assertThat(mBatteryOptimizeUtils.isDisabledForOptimizeModeOnly()).isFalse();
154     }
155 
156     @Test
testSetAppUsageState_Restricted_verifyAction()157     public void testSetAppUsageState_Restricted_verifyAction() throws Exception {
158         // Sets the current mode as MODE_UNRESTRICTED.
159         when(mMockBackend.isAllowlisted(anyString(), anyInt())).thenReturn(true);
160         when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), anyString()))
161                 .thenReturn(AppOpsManager.MODE_ALLOWED);
162 
163         mBatteryOptimizeUtils.setAppUsageState(MODE_RESTRICTED, Action.UNKNOWN);
164         TimeUnit.SECONDS.sleep(1);
165 
166         verifySetAppOptimizationMode(AppOpsManager.MODE_IGNORED, /* allowListed */ false);
167         verify(mObserver).onChanged(DataChangeReason.UPDATE);
168     }
169 
170     @Test
testSetAppUsageState_Unrestricted_verifyAction()171     public void testSetAppUsageState_Unrestricted_verifyAction() throws Exception {
172         // Sets the current mode as MODE_RESTRICTED.
173         when(mMockBackend.isAllowlisted(anyString(), anyInt())).thenReturn(false);
174         when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), anyString()))
175                 .thenReturn(AppOpsManager.MODE_IGNORED);
176 
177         mBatteryOptimizeUtils.setAppUsageState(MODE_UNRESTRICTED, Action.UNKNOWN);
178         TimeUnit.SECONDS.sleep(1);
179 
180         verifySetAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ true);
181         verify(mObserver).onChanged(DataChangeReason.UPDATE);
182     }
183 
184     @Test
testSetAppUsageState_Optimized_verifyAction()185     public void testSetAppUsageState_Optimized_verifyAction() throws Exception {
186         // Sets the current mode as MODE_UNRESTRICTED.
187         when(mMockBackend.isAllowlisted(anyString(), anyInt())).thenReturn(true);
188         when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), anyString()))
189                 .thenReturn(AppOpsManager.MODE_ALLOWED);
190 
191         mBatteryOptimizeUtils.setAppUsageState(MODE_OPTIMIZED, Action.UNKNOWN);
192         TimeUnit.SECONDS.sleep(1);
193 
194         verifySetAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ false);
195         verify(mObserver).onChanged(DataChangeReason.UPDATE);
196     }
197 
198     @Test
testSetAppUsageState_sameUnrestrictedMode_verifyNoAction()199     public void testSetAppUsageState_sameUnrestrictedMode_verifyNoAction() throws Exception {
200         // Sets the current mode as MODE_UNRESTRICTED.
201         when(mMockBackend.isAllowlisted(anyString(), anyInt())).thenReturn(true);
202         when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), anyString()))
203                 .thenReturn(AppOpsManager.MODE_ALLOWED);
204 
205         mBatteryOptimizeUtils.setAppUsageState(MODE_UNRESTRICTED, Action.UNKNOWN);
206         TimeUnit.SECONDS.sleep(1);
207 
208         verify(mMockBatteryUtils, never()).setForceAppStandby(anyInt(), anyString(), anyInt());
209         verify(mMockBackend, never()).addApp(anyString(), anyInt());
210         verify(mMockBackend, never()).removeApp(anyString(), anyInt());
211         verifyNoInteractions(mObserver);
212     }
213 
214     @Test
testGetInstalledApplications_returnEmptyArray()215     public void testGetInstalledApplications_returnEmptyArray() {
216         assertTrue(
217                 BatteryOptimizeUtils.getInstalledApplications(mContext, mMockIPackageManager)
218                         .isEmpty());
219     }
220 
221     @Test
testGetInstalledApplications_returnNull()222     public void testGetInstalledApplications_returnNull() throws Exception {
223         final UserInfo userInfo =
224                 new UserInfo(/* userId= */ 0, /* userName= */ "google", /* flag= */ 0);
225         doReturn(Arrays.asList(userInfo)).when(mMockUserManager).getProfiles(anyInt());
226         doThrow(new RuntimeException())
227                 .when(mMockIPackageManager)
228                 .getInstalledApplications(anyLong(), anyInt());
229 
230         assertNull(BatteryOptimizeUtils.getInstalledApplications(mContext, mMockIPackageManager));
231     }
232 
233     @Test
testGetInstalledApplications_returnInstalledApps()234     public void testGetInstalledApplications_returnInstalledApps() throws Exception {
235         final UserInfo userInfo =
236                 new UserInfo(/* userId= */ 0, /* userName= */ "google", /* flag= */ 0);
237         doReturn(Arrays.asList(userInfo)).when(mMockUserManager).getProfiles(anyInt());
238 
239         final ApplicationInfo applicationInfo1 = new ApplicationInfo();
240         applicationInfo1.enabled = true;
241         applicationInfo1.uid = 1;
242         final ApplicationInfo applicationInfo2 = new ApplicationInfo();
243         applicationInfo2.enabled = false;
244         applicationInfo2.uid = 2;
245         applicationInfo2.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
246         final ApplicationInfo applicationInfo3 = new ApplicationInfo();
247         applicationInfo3.enabled = false;
248         applicationInfo3.uid = 3;
249         applicationInfo3.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
250         final ApplicationInfo applicationInfo4 = new ApplicationInfo();
251         applicationInfo4.enabled = true;
252         applicationInfo4.uid = 4;
253         applicationInfo4.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
254         doReturn(
255                         new ParceledListSlice<ApplicationInfo>(
256                                 Arrays.asList(
257                                         applicationInfo1,
258                                         applicationInfo2,
259                                         applicationInfo3,
260                                         applicationInfo4)))
261                 .when(mMockIPackageManager)
262                 .getInstalledApplications(anyLong(), anyInt());
263 
264         final ArraySet<ApplicationInfo> applications =
265                 BatteryOptimizeUtils.getInstalledApplications(mContext, mMockIPackageManager);
266         assertThat(applications.size()).isEqualTo(3);
267         // applicationInfo3 should be filtered.
268         assertTrue(applications.contains(applicationInfo1));
269         assertTrue(applications.contains(applicationInfo2));
270         assertFalse(applications.contains(applicationInfo3));
271         assertTrue(applications.contains(applicationInfo4));
272     }
273 
274     @Test
testResetAppOptimizationModeInternal_Optimized_verifyAction()275     public void testResetAppOptimizationModeInternal_Optimized_verifyAction() throws Exception {
276         runTestForResetWithMode(
277                 AppOpsManager.MODE_ALLOWED, /* allowListed */
278                 false,
279                 /* isSystemOrDefaultApp */ false);
280 
281         verifyNoInteractions(mMockBatteryUtils);
282 
283         final InOrder inOrder = inOrder(mMockBackend);
284         inOrder.verify(mMockBackend).refreshList();
285         inOrder.verify(mMockBackend).isAllowlisted(PACKAGE_NAME, UID);
286         verifyNoMoreInteractions(mMockBackend);
287     }
288 
289     @Test
testResetAppOptimizationModeInternal_SystemOrDefault_verifyAction()290     public void testResetAppOptimizationModeInternal_SystemOrDefault_verifyAction()
291             throws Exception {
292         runTestForResetWithMode(
293                 AppOpsManager.MODE_ALLOWED, /* allowListed */
294                 true,
295                 /* isSystemOrDefaultApp */ true);
296 
297         verifyNoInteractions(mMockBatteryUtils);
298 
299         final InOrder inOrder = inOrder(mMockBackend);
300         inOrder.verify(mMockBackend).refreshList();
301         inOrder.verify(mMockBackend).isAllowlisted(PACKAGE_NAME, UID);
302         inOrder.verify(mMockBackend).isSysAllowlisted(PACKAGE_NAME);
303         verifyNoMoreInteractions(mMockBackend);
304         verify(mObserver).onChanged(DataChangeReason.DELETE);
305     }
306 
307     @Test
testResetAppOptimizationModeInternal_Restricted_verifyAction()308     public void testResetAppOptimizationModeInternal_Restricted_verifyAction() throws Exception {
309         runTestForResetWithMode(
310                 AppOpsManager.MODE_IGNORED, /* allowListed */
311                 false,
312                 /* isSystemOrDefaultApp */ false);
313 
314         verifySetAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ false);
315         verify(mObserver).onChanged(DataChangeReason.DELETE);
316     }
317 
318     @Test
testResetAppOptimizationModeInternal_Unrestricted_verifyAction()319     public void testResetAppOptimizationModeInternal_Unrestricted_verifyAction() throws Exception {
320         runTestForResetWithMode(
321                 AppOpsManager.MODE_ALLOWED, /* allowListed */
322                 true,
323                 /* isSystemOrDefaultApp */ false);
324 
325         verifySetAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ false);
326         verify(mObserver).onChanged(DataChangeReason.DELETE);
327     }
328 
runTestForResetWithMode( int appStandbyMode, boolean allowListed, boolean isSystemOrDefaultApp)329     private void runTestForResetWithMode(
330             int appStandbyMode, boolean allowListed, boolean isSystemOrDefaultApp)
331             throws Exception {
332         final UserInfo userInfo =
333                 new UserInfo(/* userId= */ 0, /* userName= */ "google", /* flag= */ 0);
334         doReturn(Arrays.asList(userInfo)).when(mMockUserManager).getProfiles(anyInt());
335         final ApplicationInfo applicationInfo = new ApplicationInfo();
336         applicationInfo.uid = UID;
337         applicationInfo.packageName = PACKAGE_NAME;
338         applicationInfo.enabled = true;
339         doReturn(new ParceledListSlice<ApplicationInfo>(Arrays.asList(applicationInfo)))
340                 .when(mMockIPackageManager)
341                 .getInstalledApplications(anyLong(), anyInt());
342 
343         doReturn(appStandbyMode)
344                 .when(mMockAppOpsManager)
345                 .checkOpNoThrow(anyInt(), anyInt(), anyString());
346         doReturn(allowListed).when(mMockBackend).isAllowlisted(anyString(), anyInt());
347         doReturn(isSystemOrDefaultApp).when(mMockBackend).isSysAllowlisted(anyString());
348         doReturn(isSystemOrDefaultApp).when(mMockBackend).isDefaultActiveApp(anyString(), anyInt());
349 
350         BatteryOptimizeUtils.resetAppOptimizationModeInternal(
351                 mContext,
352                 mMockIPackageManager,
353                 mMockAppOpsManager,
354                 mMockBackend,
355                 mMockBatteryUtils);
356         TimeUnit.SECONDS.sleep(1);
357     }
358 
verifySetAppOptimizationMode(int appStandbyMode, boolean allowListed)359     private void verifySetAppOptimizationMode(int appStandbyMode, boolean allowListed) {
360         verify(mMockBatteryUtils).setForceAppStandby(UID, PACKAGE_NAME, appStandbyMode);
361         if (allowListed) {
362             verify(mMockBackend).addApp(PACKAGE_NAME, UID);
363         } else {
364             verify(mMockBackend).removeApp(PACKAGE_NAME, UID);
365         }
366     }
367 }
368