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.job.controllers;
18 
19 import static android.app.job.JobInfo.BIAS_FOREGROUND_SERVICE;
20 import static android.app.job.JobInfo.BIAS_TOP_APP;
21 import static android.app.job.JobInfo.NETWORK_TYPE_ANY;
22 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
23 
24 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
25 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
26 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
27 import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
28 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_DEADLINE_PROXIMITY_LIMIT;
29 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE;
30 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FLEXIBILITY_ENABLED;
31 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS;
32 import static com.android.server.job.controllers.FlexibilityController.NUM_FLEXIBLE_CONSTRAINTS;
33 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
34 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
35 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE;
36 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;
37 import static com.android.server.job.controllers.JobStatus.MIN_WINDOW_FOR_FLEXIBILITY_MS;
38 import static com.android.server.job.controllers.JobStatus.NO_LATEST_RUNTIME;
39 
40 import static org.junit.Assert.assertArrayEquals;
41 import static org.junit.Assert.assertEquals;
42 import static org.junit.Assert.assertFalse;
43 import static org.junit.Assert.assertTrue;
44 import static org.mockito.ArgumentMatchers.any;
45 import static org.mockito.ArgumentMatchers.anyString;
46 import static org.mockito.ArgumentMatchers.eq;
47 import static org.mockito.Mockito.mock;
48 import static org.mockito.Mockito.when;
49 
50 import android.app.AlarmManager;
51 import android.app.AppGlobals;
52 import android.app.job.JobInfo;
53 import android.content.ComponentName;
54 import android.content.Context;
55 import android.content.pm.PackageManager;
56 import android.content.pm.PackageManagerInternal;
57 import android.os.Looper;
58 import android.provider.DeviceConfig;
59 import android.util.ArraySet;
60 
61 import com.android.server.LocalServices;
62 import com.android.server.job.JobSchedulerService;
63 import com.android.server.job.JobStore;
64 
65 import org.junit.After;
66 import org.junit.Before;
67 import org.junit.Test;
68 import org.mockito.ArgumentMatchers;
69 import org.mockito.Mock;
70 import org.mockito.MockitoSession;
71 import org.mockito.quality.Strictness;
72 import org.mockito.stubbing.Answer;
73 
74 import java.time.Clock;
75 import java.time.Instant;
76 import java.time.ZoneOffset;
77 import java.util.ArrayList;
78 import java.util.concurrent.Executor;
79 
80 public class FlexibilityControllerTest {
81     private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
82     private static final int SOURCE_USER_ID = 0;
83     private static final long FROZEN_TIME = 100L;
84 
85     private MockitoSession mMockingSession;
86     private FlexibilityController mFlexibilityController;
87     private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
88     private JobStore mJobStore;
89     private FlexibilityController.FcConfig mFcConfig;
90     private int mSourceUid;
91 
92     @Mock
93     private AlarmManager mAlarmManager;
94     @Mock
95     private Context mContext;
96     @Mock
97     private JobSchedulerService mJobSchedulerService;
98     @Mock
99     private PrefetchController mPrefetchController;
100     @Mock
101     private PackageManager mPackageManager;
102 
103     @Before
setup()104     public void setup() throws Exception {
105         mMockingSession = mockitoSession()
106                 .initMocks(this)
107                 .strictness(Strictness.LENIENT)
108                 .spyStatic(DeviceConfig.class)
109                 .mockStatic(LocalServices.class)
110                 .startMocking();
111         // Called in StateController constructor.
112         when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
113         when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
114         when(mJobSchedulerService.getConstants()).thenReturn(
115                 mock(JobSchedulerService.Constants.class));
116         // Called in FlexibilityController constructor.
117         when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
118         when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
119         when(mContext.getPackageManager()).thenReturn(mPackageManager);
120         when(mPackageManager.hasSystemFeature(
121                 PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(false);
122         // Used in FlexibilityController.FcConstants.
123         doAnswer((Answer<Void>) invocationOnMock -> null)
124                 .when(() -> DeviceConfig.addOnPropertiesChangedListener(
125                         anyString(), any(Executor.class),
126                         any(DeviceConfig.OnPropertiesChangedListener.class)));
127         mDeviceConfigPropertiesBuilder =
128                 new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
129         doAnswer(
130                 (Answer<DeviceConfig.Properties>) invocationOnMock
131                         -> mDeviceConfigPropertiesBuilder.build())
132                 .when(() -> DeviceConfig.getProperties(
133                         eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER), ArgumentMatchers.<String>any()));
134         //used to get jobs by UID
135         mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir());
136         when(mJobSchedulerService.getJobStore()).thenReturn(mJobStore);
137         // Used in JobStatus.
138         doReturn(mock(PackageManagerInternal.class))
139                 .when(() -> LocalServices.getService(PackageManagerInternal.class));
140         // Freeze the clocks at a moment in time
141         JobSchedulerService.sSystemClock =
142                 Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
143         JobSchedulerService.sElapsedRealtimeClock =
144                 Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
145         // Initialize real objects.
146         mFlexibilityController = new FlexibilityController(mJobSchedulerService,
147                 mPrefetchController);
148         mFcConfig = mFlexibilityController.getFcConfig();
149 
150         mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0);
151 
152         setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "50,60,70,80");
153         setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, 0L);
154         setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, true);
155     }
156 
157     @After
teardown()158     public void teardown() {
159         if (mMockingSession != null) {
160             mMockingSession.finishMocking();
161         }
162     }
163 
setDeviceConfigBoolean(String key, boolean val)164     private void setDeviceConfigBoolean(String key, boolean val) {
165         mDeviceConfigPropertiesBuilder.setBoolean(key, val);
166         synchronized (mFlexibilityController.mLock) {
167             mFlexibilityController.prepareForUpdatedConstantsLocked();
168             mFcConfig.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
169             mFlexibilityController.onConstantsUpdatedLocked();
170         }
171     }
172 
setDeviceConfigLong(String key, Long val)173     private void setDeviceConfigLong(String key, Long val) {
174         mDeviceConfigPropertiesBuilder.setLong(key, val);
175         synchronized (mFlexibilityController.mLock) {
176             mFlexibilityController.prepareForUpdatedConstantsLocked();
177             mFcConfig.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
178             mFlexibilityController.onConstantsUpdatedLocked();
179         }
180     }
181 
setDeviceConfigString(String key, String val)182     private void setDeviceConfigString(String key, String val) {
183         mDeviceConfigPropertiesBuilder.setString(key, val);
184         synchronized (mFlexibilityController.mLock) {
185             mFlexibilityController.prepareForUpdatedConstantsLocked();
186             mFcConfig.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
187             mFlexibilityController.onConstantsUpdatedLocked();
188         }
189     }
190 
createJob(int id)191     private static JobInfo.Builder createJob(int id) {
192         return new JobInfo.Builder(id, new ComponentName("foo", "bar"))
193                 .setPrefersBatteryNotLow(true)
194                 .setPrefersCharging(true)
195                 .setPrefersDeviceIdle(true);
196     }
197 
createJobStatus(String testTag, JobInfo.Builder job)198     private JobStatus createJobStatus(String testTag, JobInfo.Builder job) {
199         JobInfo jobInfo = job.build();
200         JobStatus js = JobStatus.createFromJobInfo(
201                 jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, "FCTest", testTag);
202         js.enqueueTime = FROZEN_TIME;
203         return js;
204     }
205 
206     /**
207      * Tests that the there are equally many percents to drop constraints as there are constraints
208      */
209     @Test
testDefaultVariableValues()210     public void testDefaultVariableValues() {
211         assertEquals(NUM_FLEXIBLE_CONSTRAINTS,
212                 mFlexibilityController.mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS.length
213         );
214     }
215 
216     @Test
testOnConstantsUpdated_DefaultFlexibility()217     public void testOnConstantsUpdated_DefaultFlexibility() {
218         JobStatus js = createJobStatus("testDefaultFlexibilityConfig", createJob(0));
219         assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
220         setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, false);
221         assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
222         setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, true);
223         assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
224     }
225 
226     @Test
testOnConstantsUpdated_DeadlineProximity()227     public void testOnConstantsUpdated_DeadlineProximity() {
228         JobStatus js = createJobStatus("testDeadlineProximityConfig", createJob(0));
229         setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, Long.MAX_VALUE);
230         mFlexibilityController.mFlexibilityAlarmQueue
231                 .scheduleDropNumConstraintsAlarm(js, FROZEN_TIME);
232         assertEquals(0, js.getNumRequiredFlexibleConstraints());
233     }
234 
235     @Test
testOnConstantsUpdated_FallbackDeadline()236     public void testOnConstantsUpdated_FallbackDeadline() {
237         JobStatus js = createJobStatus("testFallbackDeadlineConfig", createJob(0));
238         assertEquals(DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS,
239                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0L));
240         setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 100L);
241         assertEquals(100L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0L));
242     }
243 
244     @Test
testOnConstantsUpdated_PercentsToDropConstraints()245     public void testOnConstantsUpdated_PercentsToDropConstraints() {
246         JobInfo.Builder jb = createJob(0)
247                 .setOverrideDeadline(MIN_WINDOW_FOR_FLEXIBILITY_MS);
248         JobStatus js = createJobStatus("testPercentsToDropConstraintsConfig", jb);
249         assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
250                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
251         setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,20,30,40");
252         assertArrayEquals(
253                 mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS,
254                 new int[] {10, 20, 30, 40});
255         assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10,
256                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
257         js.adjustNumRequiredFlexibleConstraints(-1);
258         assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 2,
259                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
260         js.adjustNumRequiredFlexibleConstraints(-1);
261         assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 3,
262                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
263     }
264 
265     @Test
testOnConstantsUpdated_PercentsToDropConstraintsInvalidValues()266     public void testOnConstantsUpdated_PercentsToDropConstraintsInvalidValues() {
267         JobInfo.Builder jb = createJob(0).setOverrideDeadline(100L);
268         JobStatus js = createJobStatus("testPercentsToDropConstraintsConfig", jb);
269         js.enqueueTime = 100L;
270         assertEquals(150L,
271                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
272         setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,20a,030,40");
273         assertEquals(150L,
274                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
275         setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,40");
276         assertEquals(150L,
277                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
278         setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "50,40,10,40");
279         assertEquals(150L,
280                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
281     }
282 
283     @Test
testGetNextConstraintDropTimeElapsedLocked()284     public void testGetNextConstraintDropTimeElapsedLocked() {
285         long nextTimeToDropNumConstraints;
286 
287         // no delay, deadline
288         JobInfo.Builder jb = createJob(0).setOverrideDeadline(MIN_WINDOW_FOR_FLEXIBILITY_MS);
289         JobStatus js = createJobStatus("time", jb);
290 
291         assertEquals(JobStatus.NO_EARLIEST_RUNTIME, js.getEarliestRunTime());
292         assertEquals(MIN_WINDOW_FOR_FLEXIBILITY_MS + FROZEN_TIME, js.getLatestRunTimeElapsed());
293         assertEquals(FROZEN_TIME, js.enqueueTime);
294 
295         nextTimeToDropNumConstraints = mFlexibilityController
296                 .getNextConstraintDropTimeElapsedLocked(js);
297         assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
298                 nextTimeToDropNumConstraints);
299         js.adjustNumRequiredFlexibleConstraints(-1);
300         nextTimeToDropNumConstraints = mFlexibilityController
301                 .getNextConstraintDropTimeElapsedLocked(js);
302         assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 6,
303                 nextTimeToDropNumConstraints);
304         js.adjustNumRequiredFlexibleConstraints(-1);
305         nextTimeToDropNumConstraints = mFlexibilityController
306                 .getNextConstraintDropTimeElapsedLocked(js);
307         assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 7,
308                 nextTimeToDropNumConstraints);
309 
310         // delay, no deadline
311         jb = createJob(0).setMinimumLatency(800000L);
312         js = createJobStatus("time", jb);
313 
314         nextTimeToDropNumConstraints = mFlexibilityController
315                 .getNextConstraintDropTimeElapsedLocked(js);
316         assertEquals(130400100, nextTimeToDropNumConstraints);
317         js.adjustNumRequiredFlexibleConstraints(-1);
318         nextTimeToDropNumConstraints = mFlexibilityController
319                 .getNextConstraintDropTimeElapsedLocked(js);
320         assertEquals(156320100L, nextTimeToDropNumConstraints);
321         js.adjustNumRequiredFlexibleConstraints(-1);
322         nextTimeToDropNumConstraints = mFlexibilityController
323                 .getNextConstraintDropTimeElapsedLocked(js);
324         assertEquals(182240100L, nextTimeToDropNumConstraints);
325 
326         // no delay, no deadline
327         jb = createJob(0);
328         js = createJobStatus("time", jb);
329 
330         nextTimeToDropNumConstraints = mFlexibilityController
331                 .getNextConstraintDropTimeElapsedLocked(js);
332         assertEquals(129600100, nextTimeToDropNumConstraints);
333         js.adjustNumRequiredFlexibleConstraints(-1);
334         nextTimeToDropNumConstraints = mFlexibilityController
335                 .getNextConstraintDropTimeElapsedLocked(js);
336         assertEquals(155520100L, nextTimeToDropNumConstraints);
337         js.adjustNumRequiredFlexibleConstraints(-1);
338         nextTimeToDropNumConstraints = mFlexibilityController
339                 .getNextConstraintDropTimeElapsedLocked(js);
340         assertEquals(181440100L, nextTimeToDropNumConstraints);
341 
342         // delay, deadline
343         jb = createJob(0)
344                 .setOverrideDeadline(2 * MIN_WINDOW_FOR_FLEXIBILITY_MS)
345                 .setMinimumLatency(MIN_WINDOW_FOR_FLEXIBILITY_MS);
346         js = createJobStatus("time", jb);
347 
348         final long windowStart = FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS;
349         nextTimeToDropNumConstraints = mFlexibilityController
350                 .getNextConstraintDropTimeElapsedLocked(js);
351         assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
352                 nextTimeToDropNumConstraints);
353         js.adjustNumRequiredFlexibleConstraints(-1);
354         nextTimeToDropNumConstraints = mFlexibilityController
355                 .getNextConstraintDropTimeElapsedLocked(js);
356         assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 6,
357                 nextTimeToDropNumConstraints);
358         js.adjustNumRequiredFlexibleConstraints(-1);
359         nextTimeToDropNumConstraints = mFlexibilityController
360                 .getNextConstraintDropTimeElapsedLocked(js);
361         assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 7,
362                 nextTimeToDropNumConstraints);
363     }
364 
365     @Test
testCurPercent()366     public void testCurPercent() {
367         long deadline = 1000;
368         long nowElapsed;
369         JobInfo.Builder jb = createJob(0).setOverrideDeadline(deadline);
370         JobStatus js = createJobStatus("time", jb);
371 
372         assertEquals(FROZEN_TIME, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
373         assertEquals(deadline + FROZEN_TIME,
374                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, FROZEN_TIME));
375         nowElapsed = 600 + FROZEN_TIME;
376         JobSchedulerService.sElapsedRealtimeClock =
377                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
378         assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
379 
380         nowElapsed = 1400;
381         JobSchedulerService.sElapsedRealtimeClock =
382                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
383         assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
384 
385         nowElapsed = 950 + FROZEN_TIME;
386         JobSchedulerService.sElapsedRealtimeClock =
387                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
388         assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
389 
390         nowElapsed = FROZEN_TIME;
391         JobSchedulerService.sElapsedRealtimeClock =
392                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
393         long delay = 100;
394         deadline = 1100;
395         jb = createJob(0).setOverrideDeadline(deadline).setMinimumLatency(delay);
396         js = createJobStatus("time", jb);
397 
398         assertEquals(FROZEN_TIME + delay,
399                 mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
400         assertEquals(deadline + FROZEN_TIME,
401                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, FROZEN_TIME + delay));
402 
403         nowElapsed = 600 + FROZEN_TIME + delay;
404         JobSchedulerService.sElapsedRealtimeClock =
405                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
406 
407         assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
408 
409         nowElapsed = 1400;
410         JobSchedulerService.sElapsedRealtimeClock =
411                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
412         assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
413 
414         nowElapsed = 950 + FROZEN_TIME + delay;
415         JobSchedulerService.sElapsedRealtimeClock =
416                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
417         assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
418     }
419 
420     @Test
testGetLifeCycleBeginningElapsedLocked_Prefetch()421     public void testGetLifeCycleBeginningElapsedLocked_Prefetch() {
422         // prefetch with lifecycle
423         when(mPrefetchController.getLaunchTimeThresholdMs()).thenReturn(700L);
424         JobInfo.Builder jb = createJob(0).setPrefetch(true);
425         JobStatus js = createJobStatus("time", jb);
426         when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(900L);
427         assertEquals(900L - 700L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
428         // prefetch with enqueue
429         jb = createJob(0).setPrefetch(true);
430         js = createJobStatus("time", jb);
431         assertEquals(FROZEN_TIME, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
432         // prefetch with delay
433         jb = createJob(0).setPrefetch(true).setMinimumLatency(200);
434         js = createJobStatus("time", jb);
435         assertEquals(200 + FROZEN_TIME, js.getEarliestRunTime());
436         assertEquals(js.getEarliestRunTime(),
437                 mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
438         // prefetch without estimate
439         mFlexibilityController.mPrefetchLifeCycleStart
440                 .add(js.getUserId(), js.getSourcePackageName(), 500L);
441         when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(Long.MAX_VALUE);
442         jb = createJob(0).setPrefetch(true);
443         js = createJobStatus("time", jb);
444         assertEquals(500L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
445     }
446 
447     @Test
testGetLifeCycleBeginningElapsedLocked_NonPrefetch()448     public void testGetLifeCycleBeginningElapsedLocked_NonPrefetch() {
449         // delay
450         long delay = 100;
451         JobInfo.Builder jb = createJob(0).setMinimumLatency(delay);
452         JobStatus js = createJobStatus("time", jb);
453         assertEquals(delay + FROZEN_TIME,
454                 mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
455         // no delay
456         jb = createJob(0);
457         js = createJobStatus("time", jb);
458         assertEquals(FROZEN_TIME,
459                 mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
460     }
461 
462     @Test
testGetLifeCycleEndElapsedLocked_Prefetch()463     public void testGetLifeCycleEndElapsedLocked_Prefetch() {
464         // prefetch no estimate
465         JobInfo.Builder jb = createJob(0).setPrefetch(true);
466         JobStatus js = createJobStatus("time", jb);
467         when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(Long.MAX_VALUE);
468         assertEquals(Long.MAX_VALUE, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
469         // prefetch with estimate
470         jb = createJob(0).setPrefetch(true);
471         js = createJobStatus("time", jb);
472         when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(1000L);
473         assertEquals(1000L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
474     }
475 
476     @Test
testGetLifeCycleEndElapsedLocked_NonPrefetch()477     public void testGetLifeCycleEndElapsedLocked_NonPrefetch() {
478         // deadline
479         JobInfo.Builder jb = createJob(0).setOverrideDeadline(1000L);
480         JobStatus js = createJobStatus("time", jb);
481         assertEquals(1000L + FROZEN_TIME,
482                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
483         // no deadline
484         jb = createJob(0);
485         js = createJobStatus("time", jb);
486         assertEquals(100L + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS,
487                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, 100L));
488     }
489 
490     @Test
testGetLifeCycleEndElapsedLocked_Rescheduled()491     public void testGetLifeCycleEndElapsedLocked_Rescheduled() {
492         JobInfo.Builder jb = createJob(0).setOverrideDeadline(1000L);
493         JobStatus js = createJobStatus("time", jb);
494         js = new JobStatus(
495                 js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, /* numSystemStops */ 0,
496                 0, FROZEN_TIME, FROZEN_TIME);
497 
498         assertEquals(mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
499                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
500 
501         js = new JobStatus(
502                 js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, /* numSystemStops */ 1,
503                 0, FROZEN_TIME, FROZEN_TIME);
504 
505         assertEquals(2 * mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
506                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
507 
508         js = new JobStatus(
509                 js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0, /* numSystemStops */ 10,
510                 0, FROZEN_TIME, FROZEN_TIME);
511         assertEquals(mFcConfig.MAX_RESCHEDULED_DEADLINE_MS,
512                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
513     }
514 
515     @Test
testWontStopJobFromRunning()516     public void testWontStopJobFromRunning() {
517         JobStatus js = createJobStatus("testWontStopJobFromRunning", createJob(101));
518         // Stop satisfied constraints from causing a false positive.
519         js.adjustNumRequiredFlexibleConstraints(100);
520         synchronized (mFlexibilityController.mLock) {
521             when(mJobSchedulerService.isCurrentlyRunningLocked(js)).thenReturn(true);
522             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
523         }
524     }
525 
526     @Test
testFlexibilityTracker()527     public void testFlexibilityTracker() {
528         FlexibilityController.FlexibilityTracker flexTracker =
529                 mFlexibilityController.new
530                         FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS);
531         // Plus one for jobs with 0 required constraint.
532         assertEquals(NUM_FLEXIBLE_CONSTRAINTS + 1, flexTracker.size());
533         JobStatus[] jobs = new JobStatus[4];
534         JobInfo.Builder jb;
535         for (int i = 0; i < jobs.length; i++) {
536             jb = createJob(i);
537             if (i > 0) {
538                 jb.setRequiresDeviceIdle(true);
539                 jb.setPrefersDeviceIdle(false);
540             }
541             if (i > 1) {
542                 jb.setRequiresBatteryNotLow(true);
543                 jb.setPrefersBatteryNotLow(false);
544             }
545             if (i > 2) {
546                 jb.setRequiresCharging(true);
547                 jb.setPrefersCharging(false);
548             }
549             jobs[i] = createJobStatus("", jb);
550             flexTracker.add(jobs[i]);
551         }
552 
553         synchronized (mFlexibilityController.mLock) {
554             ArrayList<ArraySet<JobStatus>> trackedJobs = flexTracker.getArrayList();
555             assertEquals(1, trackedJobs.get(0).size());
556             assertEquals(1, trackedJobs.get(1).size());
557             assertEquals(1, trackedJobs.get(2).size());
558             assertEquals(1, trackedJobs.get(3).size());
559             assertEquals(0, trackedJobs.get(4).size());
560 
561             flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME);
562             assertEquals(1, trackedJobs.get(0).size());
563             assertEquals(1, trackedJobs.get(1).size());
564             assertEquals(2, trackedJobs.get(2).size());
565             assertEquals(0, trackedJobs.get(3).size());
566             assertEquals(0, trackedJobs.get(4).size());
567 
568             flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME);
569             assertEquals(1, trackedJobs.get(0).size());
570             assertEquals(2, trackedJobs.get(1).size());
571             assertEquals(1, trackedJobs.get(2).size());
572             assertEquals(0, trackedJobs.get(3).size());
573             assertEquals(0, trackedJobs.get(4).size());
574 
575             flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME);
576             assertEquals(2, trackedJobs.get(0).size());
577             assertEquals(1, trackedJobs.get(1).size());
578             assertEquals(1, trackedJobs.get(2).size());
579             assertEquals(0, trackedJobs.get(3).size());
580             assertEquals(0, trackedJobs.get(4).size());
581 
582             flexTracker.remove(jobs[1]);
583             assertEquals(2, trackedJobs.get(0).size());
584             assertEquals(1, trackedJobs.get(1).size());
585             assertEquals(0, trackedJobs.get(2).size());
586             assertEquals(0, trackedJobs.get(3).size());
587             assertEquals(0, trackedJobs.get(4).size());
588 
589             flexTracker.resetJobNumDroppedConstraints(jobs[0], FROZEN_TIME);
590             assertEquals(1, trackedJobs.get(0).size());
591             assertEquals(1, trackedJobs.get(1).size());
592             assertEquals(0, trackedJobs.get(2).size());
593             assertEquals(1, trackedJobs.get(3).size());
594             assertEquals(0, trackedJobs.get(4).size());
595 
596             flexTracker.adjustJobsRequiredConstraints(jobs[0], -2, FROZEN_TIME);
597             assertEquals(1, trackedJobs.get(0).size());
598             assertEquals(2, trackedJobs.get(1).size());
599             assertEquals(0, trackedJobs.get(2).size());
600             assertEquals(0, trackedJobs.get(3).size());
601             assertEquals(0, trackedJobs.get(4).size());
602 
603             // Over halfway through the flex window. The job that prefers all flex constraints
604             // should have its first flex constraint dropped.
605             final long nowElapsed = ((DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 2)
606                     + HOUR_IN_MILLIS);
607             JobSchedulerService.sElapsedRealtimeClock =
608                     Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
609 
610             flexTracker.resetJobNumDroppedConstraints(jobs[0], nowElapsed);
611             assertEquals(1, trackedJobs.get(0).size());
612             assertEquals(1, trackedJobs.get(1).size());
613             assertEquals(1, trackedJobs.get(2).size());
614             assertEquals(0, trackedJobs.get(3).size());
615             assertEquals(0, trackedJobs.get(4).size());
616         }
617     }
618 
619     @Test
testExceptions_Expedited()620     public void testExceptions_Expedited() {
621         JobInfo.Builder jb = createJob(0);
622         jb.setExpedited(true);
623         JobStatus js = createJobStatus("testExceptions_Expedited", jb);
624         assertFalse(js.hasFlexibilityConstraint());
625     }
626 
627     @Test
testExceptions_UserInitiated()628     public void testExceptions_UserInitiated() {
629         JobInfo.Builder jb = createJob(0)
630                 .setUserInitiated(true)
631                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
632                 // Attempt to add flex constraints to the job. For now, we will ignore them.
633                 .setPrefersBatteryNotLow(true)
634                 .setPrefersCharging(true)
635                 .setPrefersDeviceIdle(false);
636         JobStatus js = createJobStatus("testExceptions_UserInitiated", jb);
637         assertFalse(js.hasFlexibilityConstraint());
638     }
639 
640     @Test
testExceptions_ShortWindow()641     public void testExceptions_ShortWindow() {
642         JobInfo.Builder jb = createJob(0);
643         jb.setMinimumLatency(1);
644         jb.setOverrideDeadline(2);
645         JobStatus js = createJobStatus("Disable Flexible When Job Has Short Window", jb);
646         assertFalse(js.hasFlexibilityConstraint());
647     }
648 
649     @Test
testExceptions_NoFlexibleConstraints()650     public void testExceptions_NoFlexibleConstraints() {
651         JobInfo.Builder jb = createJob(0)
652                 .setPrefersBatteryNotLow(false)
653                 .setPrefersCharging(false)
654                 .setPrefersDeviceIdle(false);
655         JobStatus js = createJobStatus("testExceptions_NoFlexibleConstraints", jb);
656         assertFalse(js.hasFlexibilityConstraint());
657     }
658 
659     @Test
testExceptions_RescheduledOnce()660     public void testExceptions_RescheduledOnce() {
661         JobInfo.Builder jb = createJob(0);
662         JobStatus js = createJobStatus("time", jb);
663         js = new JobStatus(
664                 js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 1, /* numSystemStops */ 0,
665                 0, FROZEN_TIME, FROZEN_TIME);
666         assertFalse(js.hasFlexibilityConstraint());
667         js = new JobStatus(
668                 js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0, /* numSystemStops */ 1,
669                 0, FROZEN_TIME, FROZEN_TIME);
670         assertFalse(js.hasFlexibilityConstraint());
671     }
672 
673     @Test
testExceptions_None()674     public void testExceptions_None() {
675         JobInfo.Builder jb = createJob(0);
676         JobStatus js = createJobStatus("testExceptions_None", jb);
677         assertTrue(js.hasFlexibilityConstraint());
678         assertEquals(3, js.getNumRequiredFlexibleConstraints());
679     }
680 
681     @Test
testTopAppBypass()682     public void testTopAppBypass() {
683         JobInfo.Builder jb = createJob(0);
684         JobStatus js = createJobStatus("testTopAppBypass", jb);
685         mJobStore.add(js);
686 
687         // Needed because if before and after Uid bias is the same, nothing happens.
688         when(mJobSchedulerService.getUidBias(mSourceUid))
689                 .thenReturn(JobInfo.BIAS_FOREGROUND_SERVICE);
690 
691         synchronized (mFlexibilityController.mLock) {
692             mFlexibilityController.maybeStartTrackingJobLocked(js, null);
693             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
694 
695             setUidBias(mSourceUid, JobInfo.BIAS_TOP_APP);
696 
697             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
698             assertTrue(js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE));
699 
700             setUidBias(mSourceUid, JobInfo.BIAS_FOREGROUND_SERVICE);
701 
702             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
703             assertFalse(js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE));
704         }
705     }
706 
707     @Test
testConnectionToUnMeteredNetwork()708     public void testConnectionToUnMeteredNetwork() {
709         JobInfo.Builder jb = createJob(0).setRequiredNetworkType(NETWORK_TYPE_ANY);
710         JobStatus js = createJobStatus("testTopAppBypass", jb);
711         synchronized (mFlexibilityController.mLock) {
712             js.setHasAccessToUnmetered(false);
713             assertEquals(0, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js));
714             js.setHasAccessToUnmetered(true);
715             assertEquals(1, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js));
716             js.setHasAccessToUnmetered(false);
717             assertEquals(0, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js));
718         }
719     }
720 
721     @Test
testGetNumSatisfiedFlexibleConstraints()722     public void testGetNumSatisfiedFlexibleConstraints() {
723         long nowElapsed = FROZEN_TIME;
724         mFlexibilityController.setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW, true, nowElapsed);
725         mFlexibilityController.setConstraintSatisfied(CONSTRAINT_CHARGING, true, nowElapsed);
726         mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, true, nowElapsed);
727         JobInfo.Builder jb = createJob(0)
728                 .setPrefersBatteryNotLow(false)
729                 .setPrefersCharging(false)
730                 .setPrefersDeviceIdle(false);
731         JobStatus js = createJobStatus("testGetNumSatisfiedFlexibleConstraints", jb);
732         assertEquals(0, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js));
733 
734         jb = createJob(0)
735                 .setPrefersBatteryNotLow(true)
736                 .setPrefersCharging(false)
737                 .setPrefersDeviceIdle(false);
738         js = createJobStatus("testGetNumSatisfiedFlexibleConstraints", jb);
739         assertEquals(1, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js));
740 
741         jb = createJob(0)
742                 .setPrefersBatteryNotLow(true)
743                 .setPrefersCharging(false)
744                 .setPrefersDeviceIdle(true);
745         js = createJobStatus("testGetNumSatisfiedFlexibleConstraints", jb);
746         assertEquals(2, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js));
747 
748         jb = createJob(0)
749                 .setPrefersBatteryNotLow(true)
750                 .setPrefersCharging(true)
751                 .setPrefersDeviceIdle(true);
752         js = createJobStatus("testGetNumSatisfiedFlexibleConstraints", jb);
753         assertEquals(3, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js));
754     }
755 
756     @Test
testSetConstraintSatisfied_Constraints()757     public void testSetConstraintSatisfied_Constraints() {
758         mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false, FROZEN_TIME);
759         assertFalse(mFlexibilityController.isConstraintSatisfied(CONSTRAINT_IDLE));
760 
761         mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, true, FROZEN_TIME);
762         assertTrue(mFlexibilityController.isConstraintSatisfied(CONSTRAINT_IDLE));
763 
764         mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false, FROZEN_TIME);
765         assertFalse(mFlexibilityController.isConstraintSatisfied(CONSTRAINT_IDLE));
766     }
767 
768     @Test
testSetConstraintSatisfied_Jobs()769     public void testSetConstraintSatisfied_Jobs() {
770         JobInfo.Builder jb;
771         int[] constraintCombinations = {
772                 CONSTRAINT_IDLE & CONSTRAINT_CHARGING & CONSTRAINT_BATTERY_NOT_LOW,
773                 CONSTRAINT_IDLE & CONSTRAINT_BATTERY_NOT_LOW,
774                 CONSTRAINT_IDLE & CONSTRAINT_CHARGING,
775                 CONSTRAINT_CHARGING & CONSTRAINT_BATTERY_NOT_LOW,
776                 CONSTRAINT_IDLE,
777                 CONSTRAINT_CHARGING,
778                 CONSTRAINT_BATTERY_NOT_LOW,
779                 0
780         };
781 
782         int constraints;
783         for (int i = 0; i < constraintCombinations.length; i++) {
784             jb = createJob(i);
785             constraints = constraintCombinations[i];
786             jb.setRequiresDeviceIdle((constraints & CONSTRAINT_IDLE) != 0);
787             jb.setPrefersDeviceIdle((constraints & CONSTRAINT_IDLE) == 0);
788             jb.setRequiresBatteryNotLow((constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0);
789             jb.setPrefersBatteryNotLow((constraints & CONSTRAINT_BATTERY_NOT_LOW) == 0);
790             jb.setRequiresCharging((constraints & CONSTRAINT_CHARGING) != 0);
791             jb.setPrefersCharging((constraints & CONSTRAINT_CHARGING) == 0);
792             synchronized (mFlexibilityController.mLock) {
793                 mFlexibilityController.maybeStartTrackingJobLocked(
794                         createJobStatus(String.valueOf(i), jb), null);
795             }
796         }
797         mFlexibilityController.setConstraintSatisfied(CONSTRAINT_CHARGING, false, FROZEN_TIME);
798         mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false, FROZEN_TIME);
799         mFlexibilityController.setConstraintSatisfied(
800                 CONSTRAINT_BATTERY_NOT_LOW, false, FROZEN_TIME);
801 
802         assertEquals(0, mFlexibilityController.mSatisfiedFlexibleConstraints);
803 
804         for (int i = 0; i < constraintCombinations.length; i++) {
805             constraints = constraintCombinations[i];
806             mFlexibilityController.setConstraintSatisfied(CONSTRAINT_CHARGING,
807                     (constraints & CONSTRAINT_CHARGING) != 0, FROZEN_TIME);
808             mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE,
809                     (constraints & CONSTRAINT_IDLE) != 0, FROZEN_TIME);
810             mFlexibilityController.setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW,
811                     (constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0, FROZEN_TIME);
812 
813             assertEquals(constraints, mFlexibilityController.mSatisfiedFlexibleConstraints);
814             synchronized (mFlexibilityController.mLock) {
815                 assertSatisfiedJobsMatchSatisfiedConstraints(
816                         mFlexibilityController.mFlexibilityTracker.getArrayList(), constraints);
817             }
818         }
819     }
820 
821     @Test
testResetJobNumDroppedConstraints()822     public void testResetJobNumDroppedConstraints() {
823         JobInfo.Builder jb = createJob(22);
824         JobStatus js = createJobStatus("testResetJobNumDroppedConstraints", jb);
825         long nowElapsed = FROZEN_TIME;
826 
827         mFlexibilityController.mFlexibilityTracker.add(js);
828 
829         assertEquals(3, js.getNumRequiredFlexibleConstraints());
830         assertEquals(0, js.getNumDroppedFlexibleConstraints());
831         assertEquals(1, mFlexibilityController
832                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
833 
834         nowElapsed += DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 10 * 5;
835         JobSchedulerService.sElapsedRealtimeClock =
836                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
837 
838         mFlexibilityController.mFlexibilityTracker
839                 .adjustJobsRequiredConstraints(js, -1, nowElapsed);
840 
841         assertEquals(2, js.getNumRequiredFlexibleConstraints());
842         assertEquals(1, js.getNumDroppedFlexibleConstraints());
843         assertEquals(1, mFlexibilityController
844                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
845 
846         mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
847 
848         assertEquals(2, js.getNumRequiredFlexibleConstraints());
849         assertEquals(1, js.getNumDroppedFlexibleConstraints());
850         assertEquals(1, mFlexibilityController
851                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
852 
853         nowElapsed = FROZEN_TIME;
854         JobSchedulerService.sElapsedRealtimeClock =
855                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
856 
857         mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
858 
859         assertEquals(3, js.getNumRequiredFlexibleConstraints());
860         assertEquals(0, js.getNumDroppedFlexibleConstraints());
861         assertEquals(1, mFlexibilityController
862                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
863 
864         nowElapsed += DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 10 * 9;
865         JobSchedulerService.sElapsedRealtimeClock =
866                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
867 
868         mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
869 
870         assertEquals(0, js.getNumRequiredFlexibleConstraints());
871         assertEquals(3, js.getNumDroppedFlexibleConstraints());
872 
873         nowElapsed = FROZEN_TIME + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 10 * 6;
874         JobSchedulerService.sElapsedRealtimeClock =
875                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
876 
877         mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed);
878 
879         assertEquals(1, js.getNumRequiredFlexibleConstraints());
880         assertEquals(2, js.getNumDroppedFlexibleConstraints());
881         assertEquals(1, mFlexibilityController
882                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(1).size());
883     }
884 
885     @Test
testOnPrefetchCacheUpdated()886     public void testOnPrefetchCacheUpdated() {
887         ArraySet<JobStatus> jobs = new ArraySet<JobStatus>();
888         JobInfo.Builder jb = createJob(22).setPrefetch(true);
889         JobStatus js = createJobStatus("onPrefetchCacheUpdated", jb);
890         jobs.add(js);
891         when(mPrefetchController.getLaunchTimeThresholdMs()).thenReturn(7 * HOUR_IN_MILLIS);
892         when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(
893                 1150L + mFlexibilityController.mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS);
894 
895         mFlexibilityController.maybeStartTrackingJobLocked(js, null);
896 
897         final long nowElapsed = 150L;
898         JobSchedulerService.sElapsedRealtimeClock =
899                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
900 
901         mFlexibilityController.mPrefetchChangedListener.onPrefetchCacheUpdated(
902                 jobs, js.getUserId(), js.getSourcePackageName(), Long.MAX_VALUE,
903                 1150L + mFlexibilityController.mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS,
904                 nowElapsed);
905 
906         assertEquals(150L,
907                 (long) mFlexibilityController.mPrefetchLifeCycleStart
908                         .get(js.getSourceUserId(), js.getSourcePackageName()));
909         assertEquals(150L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
910         assertEquals(1150L,
911                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, 150L));
912         assertEquals(0, mFlexibilityController.getCurPercentOfLifecycleLocked(js, FROZEN_TIME));
913         assertEquals(650L, mFlexibilityController
914                 .getNextConstraintDropTimeElapsedLocked(js));
915         assertEquals(3, js.getNumRequiredFlexibleConstraints());
916         assertEquals(1, mFlexibilityController
917                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
918     }
919 
920     /**
921      * The beginning of a lifecycle for prefetch jobs includes the cached maximum of the last time
922      * the estimated launch time was updated and the last time the app was opened.
923      * When the UID bias updates it means the app might have been opened.
924      * This tests that the cached value is updated properly.
925      */
926     @Test
testUidUpdatesLifeCycle()927     public void testUidUpdatesLifeCycle() {
928         JobInfo.Builder jb = createJob(0).setPrefetch(true);
929         JobStatus js = createJobStatus("uidTest", jb);
930         mFlexibilityController.maybeStartTrackingJobLocked(js, null);
931         mJobStore.add(js);
932 
933         final ArraySet<String> pkgs = new ArraySet<>();
934         pkgs.add(js.getSourcePackageName());
935         when(mJobSchedulerService.getPackagesForUidLocked(mSourceUid)).thenReturn(pkgs);
936 
937         setUidBias(mSourceUid, BIAS_TOP_APP);
938         setUidBias(mSourceUid, BIAS_FOREGROUND_SERVICE);
939         assertEquals(100L, (long) mFlexibilityController.mPrefetchLifeCycleStart
940                 .getOrDefault(js.getSourceUserId(), js.getSourcePackageName(), 0L));
941 
942         JobSchedulerService.sElapsedRealtimeClock =
943                 Clock.fixed(Instant.ofEpochMilli(50L), ZoneOffset.UTC);
944 
945         setUidBias(mSourceUid, BIAS_TOP_APP);
946         setUidBias(mSourceUid, BIAS_FOREGROUND_SERVICE);
947         assertEquals(100L, (long) mFlexibilityController
948                 .mPrefetchLifeCycleStart.get(js.getSourceUserId(), js.getSourcePackageName()));
949 
950     }
951 
952     @Test
testDeviceDisabledFlexibility_Auto()953     public void testDeviceDisabledFlexibility_Auto() {
954         when(mPackageManager.hasSystemFeature(
955                 PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(true);
956         mFlexibilityController =
957                 new FlexibilityController(mJobSchedulerService, mPrefetchController);
958         assertFalse(mFlexibilityController.mFlexibilityEnabled);
959 
960         JobStatus js = createJobStatus("testIsAuto", createJob(0));
961 
962         mFlexibilityController.maybeStartTrackingJobLocked(js, null);
963         assertTrue(js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE));
964 
965         setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, true);
966         assertFalse(mFlexibilityController.mFlexibilityEnabled);
967 
968         ArrayList<ArraySet<JobStatus>> jobs =
969                 mFlexibilityController.mFlexibilityTracker.getArrayList();
970         for (int i = 0; i < jobs.size(); i++) {
971             assertEquals(0, jobs.get(i).size());
972         }
973     }
974 
setUidBias(int uid, int bias)975     private void setUidBias(int uid, int bias) {
976         int prevBias = mJobSchedulerService.getUidBias(uid);
977         doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
978         synchronized (mFlexibilityController.mLock) {
979             mFlexibilityController.onUidBiasChangedLocked(uid, prevBias, bias);
980         }
981     }
982 
assertSatisfiedJobsMatchSatisfiedConstraints( ArrayList<ArraySet<JobStatus>> trackedJobs, int satisfiedConstraints)983     private void assertSatisfiedJobsMatchSatisfiedConstraints(
984             ArrayList<ArraySet<JobStatus>> trackedJobs, int satisfiedConstraints) {
985         int numSatisfiedConstraints;
986         numSatisfiedConstraints = Integer.bitCount(satisfiedConstraints);
987         for (int i = 0; i < trackedJobs.size(); i++) {
988             ArraySet<JobStatus> jobs = trackedJobs.get(i);
989             for (int j = 0; j < jobs.size(); j++) {
990                 JobStatus js = jobs.valueAt(j);
991                 final int isUnMetered = js.getPreferUnmetered()
992                         && js.getHasAccessToUnmetered() ? 1 : 0;
993                 assertEquals(js.getNumRequiredFlexibleConstraints()
994                                 <= numSatisfiedConstraints + isUnMetered,
995                         js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE));
996             }
997         }
998     }
999 }
1000