1 /*
2  * Copyright (C) 2022 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.server.tare;
18 
19 import static android.app.tare.EconomyManager.arcToCake;
20 import static android.provider.Settings.Global.TARE_JOB_SCHEDULER_CONSTANTS;
21 
22 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
23 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
24 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
25 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
26 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
27 
28 import static org.junit.Assert.assertEquals;
29 import static org.junit.Assert.fail;
30 import static org.mockito.ArgumentMatchers.any;
31 import static org.mockito.ArgumentMatchers.anyInt;
32 import static org.mockito.Mockito.eq;
33 import static org.mockito.Mockito.mock;
34 
35 import android.app.ActivityManager;
36 import android.app.IActivityManager;
37 import android.app.tare.EconomyManager;
38 import android.content.ContentResolver;
39 import android.content.Context;
40 import android.os.BatteryManager;
41 import android.os.Looper;
42 import android.os.PowerManager;
43 import android.os.RemoteException;
44 import android.provider.DeviceConfig;
45 
46 import androidx.annotation.NonNull;
47 import androidx.annotation.Nullable;
48 import androidx.test.runner.AndroidJUnit4;
49 
50 import org.junit.After;
51 import org.junit.Before;
52 import org.junit.Test;
53 import org.junit.runner.RunWith;
54 import org.mockito.ArgumentMatchers;
55 import org.mockito.Mock;
56 import org.mockito.MockitoSession;
57 import org.mockito.quality.Strictness;
58 import org.mockito.stubbing.Answer;
59 
60 @RunWith(AndroidJUnit4.class)
61 public class JobSchedulerEconomicPolicyTest {
62     private JobSchedulerEconomicPolicy mEconomicPolicy;
63     private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
64     private final EconomicPolicy.Injector mInjector = new InjectorForTest();
65 
66     private MockitoSession mMockingSession;
67     @Mock
68     private Context mContext;
69     @Mock
70     private InternalResourceService mIrs;
71 
72     private static class InjectorForTest extends EconomicPolicy.Injector {
73         public String settingsConstant;
74 
75         @Nullable
76         @Override
getSettingsGlobalString(@onNull ContentResolver resolver, @NonNull String name)77         String getSettingsGlobalString(@NonNull ContentResolver resolver, @NonNull String name) {
78             return TARE_JOB_SCHEDULER_CONSTANTS.equals(name) ? settingsConstant : null;
79         }
80     }
81 
82     @Before
setUp()83     public void setUp() {
84         mMockingSession = mockitoSession()
85                 .initMocks(this)
86                 .strictness(Strictness.LENIENT)
87                 .spyStatic(DeviceConfig.class)
88                 .startMocking();
89 
90         when(mIrs.getContext()).thenReturn(mContext);
91         when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
92         when(mContext.getContentResolver()).thenReturn(mock(ContentResolver.class));
93         // Called by Modifiers.
94         when(mContext.getSystemService(BatteryManager.class))
95             .thenReturn(mock(BatteryManager.class));
96         when(mContext.getSystemService(PowerManager.class))
97             .thenReturn(mock(PowerManager.class));
98         IActivityManager activityManager = ActivityManager.getService();
99         spyOn(activityManager);
100         try {
101             doNothing().when(activityManager).registerUidObserver(any(), anyInt(), anyInt(), any());
102         } catch (RemoteException e) {
103             fail("registerUidObserver threw exception: " + e.getMessage());
104         }
105 
106         mDeviceConfigPropertiesBuilder =
107                 new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_TARE);
108         doAnswer(
109                 (Answer<DeviceConfig.Properties>) invocationOnMock
110                         -> mDeviceConfigPropertiesBuilder.build())
111                 .when(() -> DeviceConfig.getProperties(
112                         eq(DeviceConfig.NAMESPACE_TARE), ArgumentMatchers.<String>any()));
113 
114         // Initialize real objects.
115         // Capture the listeners.
116         mEconomicPolicy = new JobSchedulerEconomicPolicy(mIrs, mInjector);
117     }
118 
119     @After
tearDown()120     public void tearDown() {
121         if (mMockingSession != null) {
122             mMockingSession.finishMocking();
123         }
124     }
125 
setDeviceConfigCakes(String key, long valCakes)126     private void setDeviceConfigCakes(String key, long valCakes) {
127         mDeviceConfigPropertiesBuilder.setString(key, valCakes + "c");
128         mEconomicPolicy.setup(mDeviceConfigPropertiesBuilder.build());
129     }
130 
131     @Test
testDefaults()132     public void testDefaults() {
133         assertEquals(EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES,
134                 mEconomicPolicy.getInitialSatiatedConsumptionLimit());
135         assertEquals(EconomyManager.DEFAULT_JS_MIN_CONSUMPTION_LIMIT_CAKES,
136                 mEconomicPolicy.getMinSatiatedConsumptionLimit());
137         assertEquals(EconomyManager.DEFAULT_JS_MAX_CONSUMPTION_LIMIT_CAKES,
138                 mEconomicPolicy.getMaxSatiatedConsumptionLimit());
139 
140         final String pkgRestricted = "com.pkg.restricted";
141         when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
142         assertEquals(0, mEconomicPolicy.getMinSatiatedBalance(0, pkgRestricted));
143         assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
144 
145         final String pkgExempted = "com.pkg.exempted";
146         when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
147         assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
148                 mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
149         assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
150                 mEconomicPolicy.getMaxSatiatedBalance(0, pkgExempted));
151 
152         final String pkgHeadlessSystemApp = "com.pkg.headless_system_app";
153         when(mIrs.isHeadlessSystemApp(anyInt(), eq(pkgHeadlessSystemApp))).thenReturn(true);
154         assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP_CAKES,
155                 mEconomicPolicy.getMinSatiatedBalance(0, pkgHeadlessSystemApp));
156         assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
157                 mEconomicPolicy.getMaxSatiatedBalance(0, pkgHeadlessSystemApp));
158 
159         final String pkgUpdater = "com.pkg.updater";
160         when(mIrs.getAppUpdateResponsibilityCount(anyInt(), eq(pkgUpdater))).thenReturn(5);
161         assertEquals(5 * EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES
162                         + EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES,
163                 mEconomicPolicy.getMinSatiatedBalance(0, pkgUpdater));
164         assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
165                 mEconomicPolicy.getMaxSatiatedBalance(0, pkgUpdater));
166         // Make sure it doesn't suggest a min balance greater than max.
167         final int updateCount = (int) (EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES
168                 / EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES);
169         when(mIrs.getAppUpdateResponsibilityCount(anyInt(), eq(pkgUpdater)))
170                 .thenReturn(updateCount);
171         assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
172                 mEconomicPolicy.getMinSatiatedBalance(0, pkgUpdater));
173 
174         assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
175                 mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
176         assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES,
177                 mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
178     }
179 
180     @Test
testConstantsUpdating_ValidValues()181     public void testConstantsUpdating_ValidValues() {
182         setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(5));
183         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_CONSUMPTION_LIMIT, arcToCake(2));
184         setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_CONSUMPTION_LIMIT, arcToCake(25));
185         setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(10));
186         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(6));
187         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
188                 arcToCake(5));
189         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(4));
190         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER,
191                 arcToCake(1));
192 
193         assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
194         assertEquals(arcToCake(2), mEconomicPolicy.getMinSatiatedConsumptionLimit());
195         assertEquals(arcToCake(25), mEconomicPolicy.getMaxSatiatedConsumptionLimit());
196         final String pkgRestricted = "com.pkg.restricted";
197         when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
198         assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
199         assertEquals(arcToCake(10), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
200         final String pkgExempted = "com.pkg.exempted";
201         when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
202         assertEquals(arcToCake(6), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
203         final String pkgHeadlessSystemApp = "com.pkg.headless_system_app";
204         when(mIrs.isHeadlessSystemApp(anyInt(), eq(pkgHeadlessSystemApp))).thenReturn(true);
205         assertEquals(arcToCake(5), mEconomicPolicy.getMinSatiatedBalance(0, pkgHeadlessSystemApp));
206         assertEquals(arcToCake(4), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
207         final String pkgUpdater = "com.pkg.updater";
208         when(mIrs.getAppUpdateResponsibilityCount(anyInt(), eq(pkgUpdater))).thenReturn(3);
209         assertEquals(arcToCake(4) + 3 * arcToCake(1),
210                 mEconomicPolicy.getMinSatiatedBalance(0, pkgUpdater));
211     }
212 
213     @Test
testConstantsUpdating_InvalidValues()214     public void testConstantsUpdating_InvalidValues() {
215         // Test negatives.
216         setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(-5));
217         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_CONSUMPTION_LIMIT, arcToCake(-5));
218         setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_CONSUMPTION_LIMIT, arcToCake(-5));
219         setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(-1));
220         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(-2));
221         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
222                 arcToCake(-3));
223         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(-3));
224         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER,
225                 arcToCake(-4));
226 
227         assertEquals(arcToCake(1), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
228         assertEquals(arcToCake(1), mEconomicPolicy.getMinSatiatedConsumptionLimit());
229         assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedConsumptionLimit());
230         final String pkgRestricted = "com.pkg.restricted";
231         when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
232         assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
233         assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
234         final String pkgExempted = "com.pkg.exempted";
235         when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
236         assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
237         final String pkgHeadlessSystemApp = "com.pkg.headless_system_app";
238         when(mIrs.isHeadlessSystemApp(anyInt(), eq(pkgHeadlessSystemApp))).thenReturn(true);
239         assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, pkgHeadlessSystemApp));
240         assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
241         final String pkgUpdater = "com.pkg.updater";
242         when(mIrs.getAppUpdateResponsibilityCount(anyInt(), eq(pkgUpdater))).thenReturn(5);
243         assertEquals(arcToCake(0) + 5 * arcToCake(0),
244                 mEconomicPolicy.getMinSatiatedBalance(0, pkgUpdater));
245 
246         // Test min+max reversed.
247         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_CONSUMPTION_LIMIT, arcToCake(5));
248         setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(4));
249         setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_CONSUMPTION_LIMIT, arcToCake(3));
250         setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(10));
251         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(11));
252         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP,
253                 arcToCake(12));
254         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(13));
255 
256         assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
257         assertEquals(arcToCake(5), mEconomicPolicy.getMinSatiatedConsumptionLimit());
258         assertEquals(arcToCake(5), mEconomicPolicy.getMaxSatiatedConsumptionLimit());
259         assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
260         assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
261         assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
262         assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgHeadlessSystemApp));
263         assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
264     }
265 }
266