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.app.job.JobInfo.NETWORK_TYPE_CELLULAR;
23 import static android.app.job.JobInfo.NETWORK_TYPE_NONE;
24 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
25 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
26 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
27 
28 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
29 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
30 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
31 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
32 import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
33 import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
34 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
35 import static com.android.server.job.controllers.FlexibilityController.FLEXIBLE_CONSTRAINTS;
36 import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES;
37 import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS;
38 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_APPLIED_CONSTRAINTS;
39 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_DEADLINE_PROXIMITY_LIMIT;
40 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE;
41 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINES;
42 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS;
43 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES;
44 import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
45 import static com.android.server.job.controllers.FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
46 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
47 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
48 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVITY;
49 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONTENT_TRIGGER;
50 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE;
51 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;
52 import static com.android.server.job.controllers.JobStatus.NO_LATEST_RUNTIME;
53 
54 import static org.junit.Assert.assertArrayEquals;
55 import static org.junit.Assert.assertEquals;
56 import static org.junit.Assert.assertFalse;
57 import static org.junit.Assert.assertTrue;
58 import static org.mockito.ArgumentMatchers.any;
59 import static org.mockito.ArgumentMatchers.anyInt;
60 import static org.mockito.ArgumentMatchers.anyLong;
61 import static org.mockito.ArgumentMatchers.anyString;
62 import static org.mockito.ArgumentMatchers.eq;
63 import static org.mockito.Mockito.atLeast;
64 import static org.mockito.Mockito.clearInvocations;
65 import static org.mockito.Mockito.mock;
66 import static org.mockito.Mockito.verify;
67 import static org.mockito.Mockito.when;
68 
69 import android.annotation.Nullable;
70 import android.app.AlarmManager;
71 import android.app.AppGlobals;
72 import android.app.job.JobInfo;
73 import android.content.BroadcastReceiver;
74 import android.content.ComponentName;
75 import android.content.Context;
76 import android.content.Intent;
77 import android.content.pm.IPackageManager;
78 import android.content.pm.PackageManager;
79 import android.net.NetworkRequest;
80 import android.os.Looper;
81 import android.os.PowerManager;
82 import android.os.UserHandle;
83 import android.provider.DeviceConfig;
84 import android.telephony.TelephonyManager;
85 import android.telephony.UiccSlotMapping;
86 import android.util.ArraySet;
87 import android.util.EmptyArray;
88 import android.util.SparseArray;
89 
90 import com.android.server.AppSchedulingModuleThread;
91 import com.android.server.DeviceIdleInternal;
92 import com.android.server.LocalServices;
93 import com.android.server.job.JobSchedulerService;
94 import com.android.server.job.JobStore;
95 
96 import libcore.junit.util.compat.CoreCompatChangeRule;
97 
98 import org.junit.After;
99 import org.junit.Before;
100 import org.junit.Test;
101 import org.mockito.ArgumentCaptor;
102 import org.mockito.ArgumentMatchers;
103 import org.mockito.Mock;
104 import org.mockito.MockitoSession;
105 import org.mockito.quality.Strictness;
106 import org.mockito.stubbing.Answer;
107 
108 import java.time.Clock;
109 import java.time.Duration;
110 import java.time.Instant;
111 import java.time.ZoneOffset;
112 import java.util.ArrayList;
113 import java.util.Collection;
114 import java.util.Collections;
115 import java.util.List;
116 import java.util.concurrent.Executor;
117 
118 public class FlexibilityControllerTest {
119     private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
120     private static final int SOURCE_USER_ID = 0;
121     private static final long FROZEN_TIME = 100L;
122 
123     private MockitoSession mMockingSession;
124     private BroadcastReceiver mBroadcastReceiver;
125     private final SparseArray<ArraySet<String>> mCarrierPrivilegedApps = new SparseArray<>();
126     private final SparseArray<TelephonyManager.CarrierPrivilegesCallback>
127             mCarrierPrivilegedCallbacks = new SparseArray<>();
128     private FlexibilityController mFlexibilityController;
129     private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
130     private JobStore mJobStore;
131     private FlexibilityController.FcConfig mFcConfig;
132     private int mSourceUid;
133 
134     @Mock
135     private AlarmManager mAlarmManager;
136     @Mock
137     private Context mContext;
138     @Mock
139     private DeviceIdleInternal mDeviceIdleInternal;
140     @Mock
141     private JobSchedulerService mJobSchedulerService;
142     @Mock
143     private PrefetchController mPrefetchController;
144     @Mock
145     private TelephonyManager mTelephonyManager;
146     @Mock
147     private IPackageManager mIPackageManager;
148     @Mock
149     private PackageManager mPackageManager;
150 
151     @Before
setup()152     public void setup() throws Exception {
153         mMockingSession = mockitoSession()
154                 .initMocks(this)
155                 .strictness(Strictness.LENIENT)
156                 .spyStatic(DeviceConfig.class)
157                 .mockStatic(AppGlobals.class)
158                 .mockStatic(LocalServices.class)
159                 .startMocking();
160         // Called in StateController constructor.
161         when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
162         when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
163         when(mJobSchedulerService.getConstants()).thenReturn(
164                 mock(JobSchedulerService.Constants.class));
165         // Called in FlexibilityController constructor.
166         when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
167         when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
168         doNothing().when(mAlarmManager).setExact(anyInt(), anyLong(), anyString(), any(), any());
169         when(mContext.getPackageManager()).thenReturn(mPackageManager);
170         when(mPackageManager.hasSystemFeature(
171                 PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(false);
172         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_EMBEDDED)).thenReturn(false);
173         doReturn(mDeviceIdleInternal)
174                 .when(() -> LocalServices.getService(DeviceIdleInternal.class));
175         // Used in FlexibilityController.FcConstants.
176         doAnswer((Answer<Void>) invocationOnMock -> null)
177                 .when(() -> DeviceConfig.addOnPropertiesChangedListener(
178                         anyString(), any(Executor.class),
179                         any(DeviceConfig.OnPropertiesChangedListener.class)));
180         mDeviceConfigPropertiesBuilder =
181                 new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
182         doAnswer(
183                 (Answer<DeviceConfig.Properties>) invocationOnMock
184                         -> mDeviceConfigPropertiesBuilder.build())
185                 .when(() -> DeviceConfig.getProperties(
186                         eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER), ArgumentMatchers.<String>any()));
187         // Used in FlexibilityController.SpecialAppTracker.
188         when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
189         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION))
190                 .thenReturn(true);
191         //used to get jobs by UID
192         mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir());
193         doReturn(mJobStore).when(mJobSchedulerService).getJobStore();
194         // Used in JobStatus.
195         doReturn(mIPackageManager).when(AppGlobals::getPackageManager);
196         // Freeze the clocks at a moment in time
197         JobSchedulerService.sSystemClock =
198                 Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
199         JobSchedulerService.sElapsedRealtimeClock =
200                 Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
201         // Set empty set of privileged apps.
202         setSimSlotMappings(null);
203         setPowerWhitelistExceptIdle();
204         // Initialize real objects.
205         doReturn(Long.MAX_VALUE).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(any());
206         ArgumentCaptor<BroadcastReceiver> receiverCaptor =
207                 ArgumentCaptor.forClass(BroadcastReceiver.class);
208         mFlexibilityController = new FlexibilityController(mJobSchedulerService,
209                 mPrefetchController);
210         mFcConfig = mFlexibilityController.getFcConfig();
211 
212         mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0);
213 
214         setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
215                 "500=50|60|70|80"
216                         + ",400=50|60|70|80"
217                         + ",300=50|60|70|80"
218                         + ",200=50|60|70|80"
219                         + ",100=50|60|70|80");
220         setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, 0L);
221         setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, FLEXIBLE_CONSTRAINTS);
222         waitForQuietModuleThread();
223 
224         verify(mContext).registerReceiver(receiverCaptor.capture(),
225                 ArgumentMatchers.argThat(filter ->
226                         filter.hasAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED)));
227         mBroadcastReceiver = receiverCaptor.getValue();
228     }
229 
230     @After
teardown()231     public void teardown() {
232         if (mMockingSession != null) {
233             mMockingSession.finishMocking();
234         }
235     }
236 
advanceElapsedClock(long incrementMs)237     private void advanceElapsedClock(long incrementMs) {
238         JobSchedulerService.sElapsedRealtimeClock = Clock.offset(
239                 sElapsedRealtimeClock, Duration.ofMillis(incrementMs));
240     }
241 
setDeviceConfigInt(String key, int val)242     private void setDeviceConfigInt(String key, int val) {
243         mDeviceConfigPropertiesBuilder.setInt(key, val);
244         updateDeviceConfig(key);
245     }
246 
setDeviceConfigLong(String key, Long val)247     private void setDeviceConfigLong(String key, Long val) {
248         mDeviceConfigPropertiesBuilder.setLong(key, val);
249         updateDeviceConfig(key);
250     }
251 
setDeviceConfigString(String key, String val)252     private void setDeviceConfigString(String key, String val) {
253         mDeviceConfigPropertiesBuilder.setString(key, val);
254         updateDeviceConfig(key);
255     }
256 
updateDeviceConfig(String key)257     private void updateDeviceConfig(String key) {
258         synchronized (mFlexibilityController.mLock) {
259             mFlexibilityController.prepareForUpdatedConstantsLocked();
260             mFcConfig.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
261             mFlexibilityController.onConstantsUpdatedLocked();
262         }
263     }
264 
waitForQuietModuleThread()265     private void waitForQuietModuleThread() {
266         assertTrue("Failed to wait for quiet module thread",
267                 AppSchedulingModuleThread.getHandler().runWithScissors(() -> {}, 10_000L));
268     }
269 
createJob(int id)270     private static JobInfo.Builder createJob(int id) {
271         return new JobInfo.Builder(id, new ComponentName("foo", "bar"));
272     }
273 
createJobStatus(String testTag, JobInfo.Builder job)274     private JobStatus createJobStatus(String testTag, JobInfo.Builder job) {
275         return createJobStatus(testTag, job, SOURCE_PACKAGE);
276     }
277 
createJobStatus(String testTag, JobInfo.Builder job, String sourcePackage)278     private JobStatus createJobStatus(String testTag, JobInfo.Builder job, String sourcePackage) {
279         JobInfo jobInfo = job.build();
280         JobStatus js = JobStatus.createFromJobInfo(
281                 jobInfo, 1000, sourcePackage, SOURCE_USER_ID, "FCTest", testTag);
282         js.enqueueTime = FROZEN_TIME;
283         js.setStandbyBucket(ACTIVE_INDEX);
284         if (js.hasFlexibilityConstraint()) {
285             js.setNumAppliedFlexibleConstraints(Integer.bitCount(
286                     mFlexibilityController.getRelevantAppliedConstraintsLocked(js)));
287         }
288         return js;
289     }
290 
291     /**
292      * Tests that the there are equally many percents to drop constraints as there are constraints
293      */
294     @Test
testDefaultVariableValues()295     public void testDefaultVariableValues() {
296         SparseArray<int[]> defaultPercentsToDrop =
297                 FlexibilityController.FcConfig.DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
298         for (int i = 0; i < defaultPercentsToDrop.size(); ++i) {
299             assertEquals(Integer.bitCount(FLEXIBLE_CONSTRAINTS),
300                     defaultPercentsToDrop.valueAt(i).length);
301         }
302     }
303 
304     @Test
testAppliedConstraints()305     public void testAppliedConstraints() {
306         setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, FLEXIBLE_CONSTRAINTS);
307 
308         // Add connectivity to require 4 constraints
309         JobStatus connJs = createJobStatus("testAppliedConstraints",
310                 createJob(0).setRequiredNetworkType(NETWORK_TYPE_ANY));
311         JobStatus nonConnJs = createJobStatus("testAppliedConstraints",
312                 createJob(1).setRequiredNetworkType(NETWORK_TYPE_NONE));
313 
314         mFlexibilityController.maybeStartTrackingJobLocked(connJs, null);
315         mFlexibilityController.maybeStartTrackingJobLocked(nonConnJs, null);
316 
317         assertEquals(4, connJs.getNumAppliedFlexibleConstraints());
318         assertEquals(3, nonConnJs.getNumAppliedFlexibleConstraints());
319 
320         mFlexibilityController.setConstraintSatisfied(
321                 CONSTRAINT_BATTERY_NOT_LOW, true,
322                 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS);
323         mFlexibilityController.setConstraintSatisfied(
324                 CONSTRAINT_CHARGING, false,
325                 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS);
326         mFlexibilityController.setConstraintSatisfied(
327                 CONSTRAINT_IDLE, false,
328                 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS);
329         mFlexibilityController.setConstraintSatisfied(
330                 CONSTRAINT_CONNECTIVITY, true,
331                 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS);
332         connJs.setTransportAffinitiesSatisfied(true);
333 
334         synchronized (mFlexibilityController.mLock) {
335             assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
336             assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
337         }
338 
339         setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS,
340                 CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_CONNECTIVITY);
341         waitForQuietModuleThread();
342 
343         // Only battery-not-low (which is satisfied) applies to the non-connectivity job, so it
344         // should be able to run.
345         assertEquals(2, connJs.getNumAppliedFlexibleConstraints());
346         assertEquals(1, nonConnJs.getNumAppliedFlexibleConstraints());
347         synchronized (mFlexibilityController.mLock) {
348             assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
349             assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
350         }
351 
352         setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, CONSTRAINT_BATTERY_NOT_LOW);
353         waitForQuietModuleThread();
354 
355         assertEquals(1, connJs.getNumAppliedFlexibleConstraints());
356         assertEquals(1, nonConnJs.getNumAppliedFlexibleConstraints());
357         synchronized (mFlexibilityController.mLock) {
358             assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
359             assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
360         }
361 
362         setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, CONSTRAINT_CONNECTIVITY);
363         waitForQuietModuleThread();
364 
365         // No constraints apply to the non-connectivity job, so it should be able to run.
366         assertEquals(1, connJs.getNumAppliedFlexibleConstraints());
367         assertEquals(0, nonConnJs.getNumAppliedFlexibleConstraints());
368         synchronized (mFlexibilityController.mLock) {
369             assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
370             assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
371         }
372 
373         setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, CONSTRAINT_CHARGING);
374         waitForQuietModuleThread();
375 
376         assertEquals(1, connJs.getNumAppliedFlexibleConstraints());
377         assertEquals(1, nonConnJs.getNumAppliedFlexibleConstraints());
378         synchronized (mFlexibilityController.mLock) {
379             assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
380             assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
381         }
382 
383         setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, 0);
384         waitForQuietModuleThread();
385 
386         // No constraints apply, so they should be able to run.
387         assertEquals(0, connJs.getNumAppliedFlexibleConstraints());
388         assertEquals(0, nonConnJs.getNumAppliedFlexibleConstraints());
389         synchronized (mFlexibilityController.mLock) {
390             assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
391             assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
392         }
393 
394         // Invalid constraint to apply.
395         setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, CONSTRAINT_CONTENT_TRIGGER);
396         waitForQuietModuleThread();
397 
398         // No constraints apply, so they should be able to run.
399         assertEquals(0, connJs.getNumAppliedFlexibleConstraints());
400         assertEquals(0, nonConnJs.getNumAppliedFlexibleConstraints());
401         synchronized (mFlexibilityController.mLock) {
402             assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
403             assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
404         }
405     }
406 
407     @Test
testOnConstantsUpdated_AppliedConstraints()408     public void testOnConstantsUpdated_AppliedConstraints() {
409         JobStatus js = createJobStatus("testDefaultFlexibilityConfig", createJob(0));
410         setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, 0);
411         assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
412         setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, FLEXIBLE_CONSTRAINTS);
413         assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
414     }
415 
416     @Test
testOnConstantsUpdated_DeadlineProximity()417     public void testOnConstantsUpdated_DeadlineProximity() {
418         JobStatus js = createJobStatus("testDeadlineProximityConfig", createJob(0));
419         setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, Long.MAX_VALUE);
420         mFlexibilityController.mFlexibilityAlarmQueue
421                 .scheduleDropNumConstraintsAlarm(js, FROZEN_TIME);
422         assertEquals(0, js.getNumRequiredFlexibleConstraints());
423     }
424 
425     @Test
testOnConstantsUpdated_FallbackDeadline()426     public void testOnConstantsUpdated_FallbackDeadline() {
427         JobStatus js = createJobStatus("testFallbackDeadlineConfig", createJob(0));
428         final long nowElapsed = sElapsedRealtimeClock.millis();
429         assertEquals(DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.get(JobInfo.PRIORITY_DEFAULT),
430                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0L));
431         setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 123L);
432         setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
433                 "500=500,400=400,300=300,200=200,100=100");
434         assertEquals(300L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0L));
435     }
436 
437     @Test
testOnConstantsUpdated_PercentsToDropConstraints()438     public void testOnConstantsUpdated_PercentsToDropConstraints() {
439         final long fallbackDuration = 12 * HOUR_IN_MILLIS;
440         JobInfo.Builder jb = createJob(0)
441                 .setOverrideDeadline(HOUR_IN_MILLIS);
442         JobStatus js = createJobStatus("testPercentsToDropConstraintsConfig", jb);
443         // Even though the override deadline is 1 hour, the fallback duration is still used.
444         assertEquals(FROZEN_TIME + fallbackDuration / 10 * 5,
445                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
446         setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
447                 "500=1|2|3|4"
448                         + ",400=5|6|7|8"
449                         + ",300=10|20|30|40"
450                         + ",200=50|51|52|53"
451                         + ",100=54|55|56|57");
452         assertArrayEquals(
453                 mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
454                         .get(JobInfo.PRIORITY_MAX),
455                 new int[]{1, 2, 3, 4});
456         assertArrayEquals(
457                 mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
458                         .get(JobInfo.PRIORITY_HIGH),
459                 new int[]{5, 6, 7, 8});
460         assertArrayEquals(
461                 mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
462                         .get(JobInfo.PRIORITY_DEFAULT),
463                 new int[]{10, 20, 30, 40});
464         assertArrayEquals(
465                 mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
466                         .get(JobInfo.PRIORITY_LOW),
467                 new int[]{50, 51, 52, 53});
468         assertArrayEquals(
469                 mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
470                         .get(JobInfo.PRIORITY_MIN),
471                 new int[]{54, 55, 56, 57});
472         assertEquals(FROZEN_TIME + fallbackDuration / 10,
473                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
474         js.setNumDroppedFlexibleConstraints(1);
475         assertEquals(FROZEN_TIME + fallbackDuration / 10 * 2,
476                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
477         js.setNumDroppedFlexibleConstraints(2);
478         assertEquals(FROZEN_TIME + fallbackDuration / 10 * 3,
479                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
480     }
481 
482     @Test
testOnConstantsUpdated_PercentsToDropConstraintsInvalidValues()483     public void testOnConstantsUpdated_PercentsToDropConstraintsInvalidValues() {
484         // No priority mapping
485         setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS, "10,20,30,40");
486         final SparseArray<int[]> defaultPercentsToDrop =
487                 FlexibilityController.FcConfig.DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
488         final SparseArray<int[]> percentsToDrop =
489                 mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
490         for (int i = 0; i < defaultPercentsToDrop.size(); ++i) {
491             assertArrayEquals(defaultPercentsToDrop.valueAt(i), percentsToDrop.valueAt(i));
492         }
493 
494         // Invalid priority-percentList string
495         setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
496                 "500=10,20a,030,40"
497                         + ",400=20|40|60|80"
498                         + ",300=25|50|75|80"
499                         + ",200=40|50|60|80"
500                         + ",100=20|40|60|80");
501         for (int i = 0; i < defaultPercentsToDrop.size(); ++i) {
502             assertArrayEquals(defaultPercentsToDrop.valueAt(i), percentsToDrop.valueAt(i));
503         }
504 
505         // Invalid percentList strings
506         setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
507                 "500=10|20a|030|40" // Letters
508                         + ",400=10|40" // Not enough
509                         + ",300=.|50|_|80" // Characters
510                         + ",200=50|40|10|40" // Out of order
511                         + ",100=30|60|90|101"); // Over 100
512         for (int i = 0; i < defaultPercentsToDrop.size(); ++i) {
513             assertArrayEquals(defaultPercentsToDrop.valueAt(i), percentsToDrop.valueAt(i));
514         }
515 
516         // Only partially invalid
517         setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
518                 "500=10|20a|030|40" // Letters
519                         + ",400=10|40" // Not enough
520                         + ",300=.|50|_|80" // Characters
521                         + ",200=10|20|30|40" // Valid
522                         + ",100=20|40|60|80"); // Valid
523         assertArrayEquals(defaultPercentsToDrop.get(JobInfo.PRIORITY_MAX),
524                 percentsToDrop.get(JobInfo.PRIORITY_MAX));
525         assertArrayEquals(defaultPercentsToDrop.get(JobInfo.PRIORITY_HIGH),
526                 percentsToDrop.get(JobInfo.PRIORITY_HIGH));
527         assertArrayEquals(defaultPercentsToDrop.get(JobInfo.PRIORITY_DEFAULT),
528                 percentsToDrop.get(JobInfo.PRIORITY_DEFAULT));
529         assertArrayEquals(new int[]{10, 20, 30, 40}, percentsToDrop.get(JobInfo.PRIORITY_LOW));
530         assertArrayEquals(new int[]{20, 40, 60, 80}, percentsToDrop.get(JobInfo.PRIORITY_MIN));
531     }
532 
533     @Test
testGetNextConstraintDropTimeElapsedLocked()534     public void testGetNextConstraintDropTimeElapsedLocked() {
535         final long fallbackDuration = 50 * HOUR_IN_MILLIS;
536         setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 200 * HOUR_IN_MILLIS);
537         setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
538                 "500=" + HOUR_IN_MILLIS
539                         + ",400=" + 25 * HOUR_IN_MILLIS
540                         + ",300=" + fallbackDuration
541                         + ",200=" + 100 * HOUR_IN_MILLIS
542                         + ",100=" + 200 * HOUR_IN_MILLIS);
543 
544         long nextTimeToDropNumConstraints;
545 
546         // no delay, deadline
547         JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
548         JobStatus js = createJobStatus("time", jb);
549 
550         assertEquals(JobStatus.NO_EARLIEST_RUNTIME, js.getEarliestRunTime());
551         assertEquals(HOUR_IN_MILLIS + FROZEN_TIME, js.getLatestRunTimeElapsed());
552         assertEquals(FROZEN_TIME, js.enqueueTime);
553 
554         nextTimeToDropNumConstraints = mFlexibilityController
555                 .getNextConstraintDropTimeElapsedLocked(js);
556         assertEquals(FROZEN_TIME + fallbackDuration / 10 * 5,
557                 nextTimeToDropNumConstraints);
558         js.setNumDroppedFlexibleConstraints(1);
559         nextTimeToDropNumConstraints = mFlexibilityController
560                 .getNextConstraintDropTimeElapsedLocked(js);
561         assertEquals(FROZEN_TIME + fallbackDuration / 10 * 6,
562                 nextTimeToDropNumConstraints);
563         js.setNumDroppedFlexibleConstraints(2);
564         nextTimeToDropNumConstraints = mFlexibilityController
565                 .getNextConstraintDropTimeElapsedLocked(js);
566         assertEquals(FROZEN_TIME + fallbackDuration / 10 * 7,
567                 nextTimeToDropNumConstraints);
568 
569         // delay, no deadline
570         jb = createJob(0).setMinimumLatency(800000L);
571         js = createJobStatus("time", jb);
572 
573         nextTimeToDropNumConstraints = mFlexibilityController
574                 .getNextConstraintDropTimeElapsedLocked(js);
575         assertEquals(FROZEN_TIME + 800000L + (50 * HOUR_IN_MILLIS) / 2,
576                 nextTimeToDropNumConstraints);
577         js.setNumDroppedFlexibleConstraints(1);
578         nextTimeToDropNumConstraints = mFlexibilityController
579                 .getNextConstraintDropTimeElapsedLocked(js);
580         assertEquals(FROZEN_TIME + 800000L + (50 * HOUR_IN_MILLIS) * 6 / 10,
581                 nextTimeToDropNumConstraints);
582         js.setNumDroppedFlexibleConstraints(2);
583         nextTimeToDropNumConstraints = mFlexibilityController
584                 .getNextConstraintDropTimeElapsedLocked(js);
585         assertEquals(FROZEN_TIME + 800000L + (50 * HOUR_IN_MILLIS) * 7 / 10,
586                 nextTimeToDropNumConstraints);
587 
588         // no delay, no deadline
589         jb = createJob(0).setPriority(JobInfo.PRIORITY_LOW);
590         js = createJobStatus("time", jb);
591 
592         nextTimeToDropNumConstraints = mFlexibilityController
593                 .getNextConstraintDropTimeElapsedLocked(js);
594         assertEquals(FROZEN_TIME + (100 * HOUR_IN_MILLIS) / 2, nextTimeToDropNumConstraints);
595         js.setNumDroppedFlexibleConstraints(1);
596         nextTimeToDropNumConstraints = mFlexibilityController
597                 .getNextConstraintDropTimeElapsedLocked(js);
598         assertEquals(FROZEN_TIME + (100 * HOUR_IN_MILLIS) * 6 / 10, nextTimeToDropNumConstraints);
599         js.setNumDroppedFlexibleConstraints(2);
600         nextTimeToDropNumConstraints = mFlexibilityController
601                 .getNextConstraintDropTimeElapsedLocked(js);
602         assertEquals(FROZEN_TIME + (100 * HOUR_IN_MILLIS) * 7 / 10, nextTimeToDropNumConstraints);
603 
604         // delay, deadline
605         jb = createJob(0)
606                 .setOverrideDeadline(2 * HOUR_IN_MILLIS)
607                 .setMinimumLatency(HOUR_IN_MILLIS);
608         js = createJobStatus("time", jb);
609 
610         final long windowStart = FROZEN_TIME + HOUR_IN_MILLIS;
611         nextTimeToDropNumConstraints = mFlexibilityController
612                 .getNextConstraintDropTimeElapsedLocked(js);
613         assertEquals(windowStart + fallbackDuration / 10 * 5,
614                 nextTimeToDropNumConstraints);
615         js.setNumDroppedFlexibleConstraints(1);
616         nextTimeToDropNumConstraints = mFlexibilityController
617                 .getNextConstraintDropTimeElapsedLocked(js);
618         assertEquals(windowStart + fallbackDuration / 10 * 6,
619                 nextTimeToDropNumConstraints);
620         js.setNumDroppedFlexibleConstraints(2);
621         nextTimeToDropNumConstraints = mFlexibilityController
622                 .getNextConstraintDropTimeElapsedLocked(js);
623         assertEquals(windowStart + fallbackDuration / 10 * 7,
624                 nextTimeToDropNumConstraints);
625     }
626 
627     @Test
testCurPercent()628     public void testCurPercent() {
629         final long fallbackDuration = 10 * HOUR_IN_MILLIS;
630         setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES, "300=" + fallbackDuration);
631         long deadline = 100 * MINUTE_IN_MILLIS;
632         long nowElapsed = FROZEN_TIME;
633         JobInfo.Builder jb = createJob(0).setOverrideDeadline(deadline);
634         JobStatus js = createJobStatus("time", jb);
635 
636         assertEquals(FROZEN_TIME, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
637         assertEquals(FROZEN_TIME + fallbackDuration,
638                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, FROZEN_TIME));
639         nowElapsed = FROZEN_TIME + 6 * HOUR_IN_MILLIS;
640         JobSchedulerService.sElapsedRealtimeClock =
641                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
642         assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
643 
644         nowElapsed = FROZEN_TIME + 13 * HOUR_IN_MILLIS;
645         JobSchedulerService.sElapsedRealtimeClock =
646                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
647         assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
648 
649         nowElapsed = FROZEN_TIME + 9 * HOUR_IN_MILLIS;
650         JobSchedulerService.sElapsedRealtimeClock =
651                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
652         assertEquals(90, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
653 
654         nowElapsed = FROZEN_TIME;
655         JobSchedulerService.sElapsedRealtimeClock =
656                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
657         long delay = HOUR_IN_MILLIS;
658         deadline = HOUR_IN_MILLIS + 100 * MINUTE_IN_MILLIS;
659         jb = createJob(0).setOverrideDeadline(deadline).setMinimumLatency(delay);
660         js = createJobStatus("time", jb);
661 
662         assertEquals(FROZEN_TIME + delay,
663                 mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
664         assertEquals(FROZEN_TIME + delay + fallbackDuration,
665                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed,
666                         FROZEN_TIME + delay));
667 
668         nowElapsed = FROZEN_TIME + delay + 6 * HOUR_IN_MILLIS;
669         JobSchedulerService.sElapsedRealtimeClock =
670                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
671 
672         assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
673 
674         nowElapsed = FROZEN_TIME + 13 * HOUR_IN_MILLIS;
675         JobSchedulerService.sElapsedRealtimeClock =
676                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
677         assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
678 
679         nowElapsed = FROZEN_TIME + delay + 9 * HOUR_IN_MILLIS;
680         JobSchedulerService.sElapsedRealtimeClock =
681                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
682         assertEquals(90, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
683     }
684 
685     @Test
testGetLifeCycleBeginningElapsedLocked_Periodic()686     public void testGetLifeCycleBeginningElapsedLocked_Periodic() {
687         // Periodic with lifecycle
688         JobInfo.Builder jbBasic = createJob(0).setPeriodic(HOUR_IN_MILLIS);
689         JobInfo.Builder jbFlex = createJob(0)
690                 .setPeriodic(HOUR_IN_MILLIS, 20 * MINUTE_IN_MILLIS);
691         JobStatus jsBasic =
692                 createJobStatus("testGetLifeCycleBeginningElapsedLocked_Periodic", jbBasic);
693         JobStatus jsFlex =
694                 createJobStatus("testGetLifeCycleBeginningElapsedLocked_Periodic", jbFlex);
695 
696         final long nowElapsed = JobSchedulerService.sElapsedRealtimeClock.millis();
697         // Base case, no start adjustment
698         assertEquals(nowElapsed,
699                 mFlexibilityController.getLifeCycleBeginningElapsedLocked(jsBasic));
700         assertEquals(nowElapsed + 40 * MINUTE_IN_MILLIS,
701                 mFlexibilityController.getLifeCycleBeginningElapsedLocked(jsFlex));
702 
703         // Rescheduled with start adjustment
704         final long adjustmentMs = 4 * MINUTE_IN_MILLIS;
705         jsBasic = new JobStatus(jsBasic,
706                 // "True" start is nowElapsed + HOUR_IN_MILLIS
707                 nowElapsed + HOUR_IN_MILLIS + adjustmentMs,
708                 nowElapsed + 2 * HOUR_IN_MILLIS,
709                 0 /* numFailures */, 0 /* numAbandonedFailures */, 0 /* numSystemStops */,
710                 JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
711                 0, 0);
712         jsFlex = new JobStatus(jsFlex,
713                 // "True" start is nowElapsed + 2 * HOUR_IN_MILLIS - 20 * MINUTE_IN_MILLIS
714                 nowElapsed + 2 * HOUR_IN_MILLIS - 20 * MINUTE_IN_MILLIS + adjustmentMs,
715                 nowElapsed + 2 * HOUR_IN_MILLIS,
716                 0 /* numFailures */, 0 /* numAbandonedFailures */, 0 /* numSystemStops */,
717                 JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
718                 0, 0);
719 
720         assertEquals(nowElapsed + HOUR_IN_MILLIS + adjustmentMs / 2,
721                 mFlexibilityController.getLifeCycleBeginningElapsedLocked(jsBasic));
722         assertEquals(nowElapsed + 2 * HOUR_IN_MILLIS - 20 * MINUTE_IN_MILLIS + adjustmentMs / 2,
723                 mFlexibilityController.getLifeCycleBeginningElapsedLocked(jsFlex));
724 
725         // Rescheduled for failure
726         jsBasic = new JobStatus(jsBasic,
727                 nowElapsed + 30 * MINUTE_IN_MILLIS,
728                 NO_LATEST_RUNTIME,
729                 1 /* numFailures */, 0 /* numAbandonedFailures */, 1 /* numSystemStops */,
730                 JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
731                 0, 0);
732         jsFlex = new JobStatus(jsFlex,
733                 nowElapsed + 30 * MINUTE_IN_MILLIS,
734                 NO_LATEST_RUNTIME,
735                 1 /* numFailures */, 0 /* numAbandonedFailures */, 1 /* numSystemStops */,
736                 JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
737                 0, 0);
738 
739         assertEquals(nowElapsed + 30 * MINUTE_IN_MILLIS,
740                 mFlexibilityController.getLifeCycleBeginningElapsedLocked(jsBasic));
741         assertEquals(nowElapsed + 30 * MINUTE_IN_MILLIS,
742                 mFlexibilityController.getLifeCycleBeginningElapsedLocked(jsFlex));
743     }
744 
745     @Test
testGetLifeCycleBeginningElapsedLocked_Prefetch()746     public void testGetLifeCycleBeginningElapsedLocked_Prefetch() {
747         // prefetch with lifecycle
748         doReturn(700L).when(mPrefetchController).getLaunchTimeThresholdMs();
749         JobInfo.Builder jb = createJob(0).setPrefetch(true);
750         JobStatus js = createJobStatus("time", jb);
751         doReturn(900L).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js);
752         assertEquals(900L - 700L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
753         // prefetch with enqueue
754         jb = createJob(0).setPrefetch(true);
755         js = createJobStatus("time", jb);
756         assertEquals(FROZEN_TIME, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
757         // prefetch with delay
758         jb = createJob(0).setPrefetch(true).setMinimumLatency(200);
759         js = createJobStatus("time", jb);
760         assertEquals(200 + FROZEN_TIME, js.getEarliestRunTime());
761         assertEquals(js.getEarliestRunTime(),
762                 mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
763         // prefetch without estimate
764         mFlexibilityController.mPrefetchLifeCycleStart
765                 .add(js.getUserId(), js.getSourcePackageName(), 500L);
766         doReturn(Long.MAX_VALUE).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js);
767         jb = createJob(0).setPrefetch(true);
768         js = createJobStatus("time", jb);
769         assertEquals(500L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
770     }
771 
772     @Test
testGetLifeCycleBeginningElapsedLocked_NonPrefetch()773     public void testGetLifeCycleBeginningElapsedLocked_NonPrefetch() {
774         // delay
775         long delay = 100;
776         JobInfo.Builder jb = createJob(0).setMinimumLatency(delay);
777         JobStatus js = createJobStatus("time", jb);
778         assertEquals(delay + FROZEN_TIME,
779                 mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
780         // no delay
781         jb = createJob(0);
782         js = createJobStatus("time", jb);
783         assertEquals(FROZEN_TIME,
784                 mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
785     }
786 
787     @Test
testGetLifeCycleEndElapsedLocked_Prefetch()788     public void testGetLifeCycleEndElapsedLocked_Prefetch() {
789         final long nowElapsed = sElapsedRealtimeClock.millis();
790 
791         // prefetch no estimate
792         JobInfo.Builder jb = createJob(0).setPrefetch(true);
793         JobStatus js = createJobStatus("time", jb);
794         doReturn(Long.MAX_VALUE).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js);
795         assertEquals(Long.MAX_VALUE,
796                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
797 
798         // prefetch with estimate
799         jb = createJob(0).setPrefetch(true);
800         js = createJobStatus("time", jb);
801         doReturn(1000L).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js);
802         assertEquals(1000L,
803                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
804     }
805 
806     @Test
testGetLifeCycleEndElapsedLocked_NonPrefetch()807     public void testGetLifeCycleEndElapsedLocked_NonPrefetch() {
808         setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
809                 "500=" + HOUR_IN_MILLIS
810                         + ",400=" + 2 * HOUR_IN_MILLIS
811                         + ",300=" + 3 * HOUR_IN_MILLIS
812                         + ",200=" + 4 * HOUR_IN_MILLIS
813                         + ",100=" + 5 * HOUR_IN_MILLIS);
814 
815         final long nowElapsed = sElapsedRealtimeClock.millis();
816 
817         // deadline
818         JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
819         JobStatus js = createJobStatus("time", jb);
820         assertEquals(3 * HOUR_IN_MILLIS + js.enqueueTime,
821                 mFlexibilityController
822                         .getLifeCycleEndElapsedLocked(js, nowElapsed, js.enqueueTime));
823 
824         // no deadline
825         assertEquals(js.enqueueTime + 2 * HOUR_IN_MILLIS,
826                 mFlexibilityController.getLifeCycleEndElapsedLocked(
827                         createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_HIGH)),
828                         nowElapsed, js.enqueueTime));
829         assertEquals(js.enqueueTime + 3 * HOUR_IN_MILLIS,
830                 mFlexibilityController.getLifeCycleEndElapsedLocked(
831                         createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT)),
832                         nowElapsed, js.enqueueTime));
833         assertEquals(js.enqueueTime + 4 * HOUR_IN_MILLIS,
834                 mFlexibilityController.getLifeCycleEndElapsedLocked(
835                         createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_LOW)),
836                         nowElapsed, js.enqueueTime));
837         assertEquals(js.enqueueTime + 5 * HOUR_IN_MILLIS,
838                 mFlexibilityController.getLifeCycleEndElapsedLocked(
839                         createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_MIN)),
840                         nowElapsed, js.enqueueTime));
841     }
842 
843     @Test
testGetLifeCycleEndElapsedLocked_Rescheduled()844     public void testGetLifeCycleEndElapsedLocked_Rescheduled() {
845         final long nowElapsed = sElapsedRealtimeClock.millis();
846 
847         JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
848         JobStatus js = createJobStatus("time", jb);
849         js = new JobStatus(
850                 js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2,
851                 0 /* numAbandonedFailures */, /* numSystemStops */ 0,
852                 0, FROZEN_TIME, FROZEN_TIME);
853 
854         assertEquals(mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
855                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
856 
857         js = new JobStatus(
858                 js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2,
859                 0 /* numAbandonedFailures */, /* numSystemStops */ 1,
860                 0, FROZEN_TIME, FROZEN_TIME);
861 
862         assertEquals(2 * mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
863                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
864 
865         js = new JobStatus(
866                 js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0,
867                 0 /* numAbandonedFailures */, /* numSystemStops */ 10,
868                 0, FROZEN_TIME, FROZEN_TIME);
869         assertEquals(mFcConfig.MAX_RESCHEDULED_DEADLINE_MS,
870                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
871     }
872 
873     @Test
testGetLifeCycleEndElapsedLocked_ScoreAddition()874     public void testGetLifeCycleEndElapsedLocked_ScoreAddition() {
875         setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
876                 "500=" + HOUR_IN_MILLIS
877                         + ",400=" + HOUR_IN_MILLIS
878                         + ",300=" + HOUR_IN_MILLIS
879                         + ",200=" + HOUR_IN_MILLIS
880                         + ",100=" + HOUR_IN_MILLIS);
881         setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES,
882                 "500=5,400=4,300=3,200=2,100=1");
883         setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS,
884                 "500=" + 5 * MINUTE_IN_MILLIS
885                         + ",400=" + 4 * MINUTE_IN_MILLIS
886                         + ",300=" + 3 * MINUTE_IN_MILLIS
887                         + ",200=" + 2 * MINUTE_IN_MILLIS
888                         + ",100=" + 1 * MINUTE_IN_MILLIS);
889 
890         final long nowElapsed = sElapsedRealtimeClock.millis();
891 
892         JobStatus jsMax = createJobStatus("testScoreCalculation",
893                 createJob(0).setExpedited(true).setPriority(JobInfo.PRIORITY_MAX));
894         JobStatus jsHigh = createJobStatus("testScoreCalculation",
895                 createJob(0).setPriority(JobInfo.PRIORITY_HIGH));
896         JobStatus jsDefault = createJobStatus("testScoreCalculation",
897                 createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT));
898         JobStatus jsLow = createJobStatus("testScoreCalculation",
899                 createJob(0).setPriority(JobInfo.PRIORITY_LOW));
900         JobStatus jsMin = createJobStatus("testScoreCalculation",
901                 createJob(0).setPriority(JobInfo.PRIORITY_MIN));
902         // Make score = 15
903         mFlexibilityController.prepareForExecutionLocked(jsMax);
904         mFlexibilityController.prepareForExecutionLocked(jsHigh);
905         mFlexibilityController.prepareForExecutionLocked(jsDefault);
906         mFlexibilityController.prepareForExecutionLocked(jsLow);
907         mFlexibilityController.prepareForExecutionLocked(jsMin);
908 
909         final long longDeadlineMs = 14 * 24 * HOUR_IN_MILLIS;
910         JobInfo.Builder jbWithLongDeadline = createJob(0).setOverrideDeadline(longDeadlineMs);
911         JobStatus jsWithLongDeadline = createJobStatus(
912                 "testGetLifeCycleEndElapsedLocked_ScoreAddition", jbWithLongDeadline);
913         JobInfo.Builder jbWithShortDeadline =
914                 createJob(0).setOverrideDeadline(15 * MINUTE_IN_MILLIS);
915         JobStatus jsWithShortDeadline = createJobStatus(
916                 "testGetLifeCycleEndElapsedLocked_ScoreAddition", jbWithShortDeadline);
917 
918         final long earliestMs = 123L;
919         assertEquals(earliestMs + HOUR_IN_MILLIS + 5 * 15 * MINUTE_IN_MILLIS,
920                 mFlexibilityController.getLifeCycleEndElapsedLocked(
921                         createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
922                                 createJob(0).setExpedited(true).setPriority(JobInfo.PRIORITY_MAX)),
923                         nowElapsed, earliestMs));
924         assertEquals(earliestMs + HOUR_IN_MILLIS + 4 * 15 * MINUTE_IN_MILLIS,
925                 mFlexibilityController.getLifeCycleEndElapsedLocked(
926                         createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
927                                 createJob(0).setPriority(JobInfo.PRIORITY_HIGH)),
928                         nowElapsed, earliestMs));
929         assertEquals(earliestMs + HOUR_IN_MILLIS + 3 * 15 * MINUTE_IN_MILLIS,
930                 mFlexibilityController.getLifeCycleEndElapsedLocked(
931                         createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
932                                 createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT)),
933                         nowElapsed, earliestMs));
934         assertEquals(earliestMs + HOUR_IN_MILLIS + 3 * 15 * MINUTE_IN_MILLIS,
935                 mFlexibilityController.getLifeCycleEndElapsedLocked(
936                         jsWithShortDeadline, nowElapsed, earliestMs));
937         assertEquals(earliestMs + HOUR_IN_MILLIS + 2 * 15 * MINUTE_IN_MILLIS,
938                 mFlexibilityController.getLifeCycleEndElapsedLocked(
939                         createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
940                                 createJob(0).setPriority(JobInfo.PRIORITY_LOW)),
941                         nowElapsed, earliestMs));
942         assertEquals(earliestMs + HOUR_IN_MILLIS + 1 * 15 * MINUTE_IN_MILLIS,
943                 mFlexibilityController.getLifeCycleEndElapsedLocked(
944                         createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
945                                 createJob(0).setPriority(JobInfo.PRIORITY_MIN)),
946                         nowElapsed, earliestMs));
947         assertEquals(jsWithLongDeadline.enqueueTime + longDeadlineMs,
948                 mFlexibilityController.getLifeCycleEndElapsedLocked(
949                         jsWithLongDeadline, nowElapsed, earliestMs));
950     }
951 
952     @Test
testWontStopAlreadyRunningJob()953     public void testWontStopAlreadyRunningJob() {
954         JobStatus js = createJobStatus("testWontStopAlreadyRunningJob", createJob(101));
955         // Stop satisfied constraints from causing a false positive.
956         js.setNumAppliedFlexibleConstraints(100);
957         synchronized (mFlexibilityController.mLock) {
958             doReturn(true).when(mJobSchedulerService).isCurrentlyRunningLocked(js);
959             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
960         }
961     }
962 
963     @Test
testFlexibilityTracker()964     public void testFlexibilityTracker() {
965         setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 100 * HOUR_IN_MILLIS);
966         setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
967                 "500=" + 100 * HOUR_IN_MILLIS
968                         + ",400=" + 100 * HOUR_IN_MILLIS
969                         + ",300=" + 100 * HOUR_IN_MILLIS
970                         + ",200=" + 100 * HOUR_IN_MILLIS
971                         + ",100=" + 100 * HOUR_IN_MILLIS);
972 
973         FlexibilityController.FlexibilityTracker flexTracker =
974                 mFlexibilityController.new FlexibilityTracker(4);
975         // Plus one for jobs with 0 required constraint.
976         assertEquals(4 + 1, flexTracker.size());
977         JobStatus[] jobs = new JobStatus[4];
978         JobInfo.Builder jb;
979         for (int i = 0; i < jobs.length; i++) {
980             jb = createJob(i);
981             if (i > 0) {
982                 jb.setRequiresDeviceIdle(true);
983             }
984             if (i > 1) {
985                 jb.setRequiresBatteryNotLow(true);
986             }
987             if (i > 2) {
988                 jb.setRequiresCharging(true);
989             }
990             jobs[i] = createJobStatus("", jb);
991             flexTracker.add(jobs[i]);
992         }
993 
994         synchronized (mFlexibilityController.mLock) {
995             ArrayList<ArraySet<JobStatus>> trackedJobs = flexTracker.getArrayList();
996             assertEquals(1, trackedJobs.get(0).size());
997             assertEquals(0, trackedJobs.get(1).size());
998             assertEquals(0, trackedJobs.get(2).size());
999             assertEquals(3, trackedJobs.get(3).size());
1000             assertEquals(0, trackedJobs.get(4).size());
1001 
1002             flexTracker.setNumDroppedFlexibleConstraints(jobs[0], 1);
1003             assertEquals(1, trackedJobs.get(0).size());
1004             assertEquals(0, trackedJobs.get(1).size());
1005             assertEquals(1, trackedJobs.get(2).size());
1006             assertEquals(2, trackedJobs.get(3).size());
1007             assertEquals(0, trackedJobs.get(4).size());
1008 
1009             flexTracker.setNumDroppedFlexibleConstraints(jobs[0], 2);
1010             assertEquals(1, trackedJobs.get(0).size());
1011             assertEquals(1, trackedJobs.get(1).size());
1012             assertEquals(0, trackedJobs.get(2).size());
1013             assertEquals(2, trackedJobs.get(3).size());
1014             assertEquals(0, trackedJobs.get(4).size());
1015 
1016             flexTracker.setNumDroppedFlexibleConstraints(jobs[0], 3);
1017             assertEquals(2, trackedJobs.get(0).size());
1018             assertEquals(0, trackedJobs.get(1).size());
1019             assertEquals(0, trackedJobs.get(2).size());
1020             assertEquals(2, trackedJobs.get(3).size());
1021             assertEquals(0, trackedJobs.get(4).size());
1022 
1023             flexTracker.remove(jobs[1]);
1024             assertEquals(2, trackedJobs.get(0).size());
1025             assertEquals(0, trackedJobs.get(1).size());
1026             assertEquals(0, trackedJobs.get(2).size());
1027             assertEquals(1, trackedJobs.get(3).size());
1028             assertEquals(0, trackedJobs.get(4).size());
1029 
1030             flexTracker.calculateNumDroppedConstraints(jobs[0], FROZEN_TIME);
1031             assertEquals(1, trackedJobs.get(0).size());
1032             assertEquals(0, trackedJobs.get(1).size());
1033             assertEquals(0, trackedJobs.get(2).size());
1034             assertEquals(2, trackedJobs.get(3).size());
1035             assertEquals(0, trackedJobs.get(4).size());
1036 
1037             flexTracker.setNumDroppedFlexibleConstraints(jobs[0], 2);
1038             assertEquals(1, trackedJobs.get(0).size());
1039             assertEquals(1, trackedJobs.get(1).size());
1040             assertEquals(0, trackedJobs.get(2).size());
1041             assertEquals(1, trackedJobs.get(3).size());
1042             assertEquals(0, trackedJobs.get(4).size());
1043 
1044             final long nowElapsed = 51 * HOUR_IN_MILLIS;
1045             JobSchedulerService.sElapsedRealtimeClock =
1046                     Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
1047 
1048             flexTracker.calculateNumDroppedConstraints(jobs[0], nowElapsed);
1049             assertEquals(1, trackedJobs.get(0).size());
1050             assertEquals(0, trackedJobs.get(1).size());
1051             assertEquals(1, trackedJobs.get(2).size());
1052             assertEquals(1, trackedJobs.get(3).size());
1053             assertEquals(0, trackedJobs.get(4).size());
1054         }
1055     }
1056 
1057     @Test
testExceptions_Expedited()1058     public void testExceptions_Expedited() {
1059         JobInfo.Builder jb = createJob(0);
1060         jb.setExpedited(true);
1061         JobStatus js = createJobStatus("testExceptions_Expedited", jb);
1062         assertFalse(js.hasFlexibilityConstraint());
1063     }
1064 
1065     @Test
testExceptions_UserInitiated()1066     public void testExceptions_UserInitiated() {
1067         JobInfo.Builder jb = createJob(0);
1068         jb.setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
1069         JobStatus js = createJobStatus("testExceptions_UserInitiated", jb);
1070         assertFalse(js.hasFlexibilityConstraint());
1071     }
1072 
1073     @Test
1074     @CoreCompatChangeRule.DisableCompatChanges({JobInfo.ENFORCE_MINIMUM_TIME_WINDOWS})
testExceptions_ShortWindow()1075     public void testExceptions_ShortWindow() {
1076         JobInfo.Builder jb = createJob(0);
1077         jb.setMinimumLatency(1);
1078         jb.setOverrideDeadline(2);
1079         JobStatus js = createJobStatus("testExceptions_ShortWindow", jb);
1080         assertTrue(js.hasFlexibilityConstraint());
1081     }
1082 
1083     @Test
testExceptions_NoFlexibleConstraints()1084     public void testExceptions_NoFlexibleConstraints() {
1085         JobInfo.Builder jb = createJob(0);
1086         jb.setRequiresDeviceIdle(true);
1087         jb.setRequiresCharging(true);
1088         jb.setRequiresBatteryNotLow(true);
1089         JobStatus js = createJobStatus("testExceptions_NoFlexibleConstraints", jb);
1090         assertFalse(js.hasFlexibilityConstraint());
1091     }
1092 
1093     @Test
testExceptions_RescheduledOnce()1094     public void testExceptions_RescheduledOnce() {
1095         JobInfo.Builder jb = createJob(0);
1096         JobStatus js = createJobStatus("time", jb);
1097         js = new JobStatus(
1098                 js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 1,
1099                 /* numAbandonedFailures */ 0, /* numSystemStops */ 0,
1100                 0, FROZEN_TIME, FROZEN_TIME);
1101         assertFalse(js.hasFlexibilityConstraint());
1102         js = new JobStatus(
1103                 js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0,
1104                 /* numAbandonedFailures */ 0, /* numSystemStops */ 1,
1105                 0, FROZEN_TIME, FROZEN_TIME);
1106         assertFalse(js.hasFlexibilityConstraint());
1107     }
1108 
1109     @Test
testExceptions_None()1110     public void testExceptions_None() {
1111         JobInfo.Builder jb = createJob(0);
1112         JobStatus js = createJobStatus("testExceptions_None", jb);
1113         assertTrue(js.hasFlexibilityConstraint());
1114         assertEquals(3, js.getNumRequiredFlexibleConstraints());
1115     }
1116 
1117     @Test
testAllowlistedAppBypass()1118     public void testAllowlistedAppBypass() {
1119         mFlexibilityController.onSystemServicesReady();
1120 
1121         JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass",
1122                 createJob(0).setPriority(JobInfo.PRIORITY_HIGH));
1123         JobStatus jsDefault = createJobStatus("testAllowlistedAppBypass",
1124                 createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT));
1125         JobStatus jsLow = createJobStatus("testAllowlistedAppBypass",
1126                 createJob(0).setPriority(JobInfo.PRIORITY_LOW));
1127         JobStatus jsMin = createJobStatus("testAllowlistedAppBypass",
1128                 createJob(0).setPriority(JobInfo.PRIORITY_MIN));
1129         jsHigh.setStandbyBucket(EXEMPTED_INDEX);
1130         jsDefault.setStandbyBucket(EXEMPTED_INDEX);
1131         jsLow.setStandbyBucket(EXEMPTED_INDEX);
1132         jsMin.setStandbyBucket(EXEMPTED_INDEX);
1133 
1134         setPowerWhitelistExceptIdle();
1135         synchronized (mFlexibilityController.mLock) {
1136             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh));
1137             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault));
1138             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow));
1139             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin));
1140         }
1141 
1142         setPowerWhitelistExceptIdle(SOURCE_PACKAGE);
1143         synchronized (mFlexibilityController.mLock) {
1144             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh));
1145             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault));
1146             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow));
1147             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin));
1148         }
1149     }
1150 
1151     @Test
testCarrierPrivilegedAppBypass()1152     public void testCarrierPrivilegedAppBypass() throws Exception {
1153         mFlexibilityController.onSystemServicesReady();
1154 
1155         final String carrier1Pkg1 = "com.test.carrier.1.pkg.1";
1156         final String carrier1Pkg2 = "com.test.carrier.1.pkg.2";
1157         final String carrier2Pkg = "com.test.carrier.2.pkg";
1158         final String nonCarrierPkg = "com.test.normal.pkg";
1159 
1160         setPackageUid(carrier1Pkg1, 1);
1161         setPackageUid(carrier1Pkg2, 11);
1162         setPackageUid(carrier2Pkg, 2);
1163         setPackageUid(nonCarrierPkg, 3);
1164 
1165         // Set the second carrier's privileged list before SIM configuration is sent to test
1166         // initialization.
1167         setCarrierPrivilegedAppList(2, carrier2Pkg);
1168 
1169         UiccSlotMapping sim1 = mock(UiccSlotMapping.class);
1170         UiccSlotMapping sim2 = mock(UiccSlotMapping.class);
1171         doReturn(1).when(sim1).getLogicalSlotIndex();
1172         doReturn(2).when(sim2).getLogicalSlotIndex();
1173         setSimSlotMappings(List.of(sim1, sim2));
1174 
1175         JobStatus jsHighC1P1 = createJobStatus("testCarrierPrivilegedAppBypass",
1176                 createJob(0).setPriority(JobInfo.PRIORITY_HIGH), carrier1Pkg1);
1177         JobStatus jsDefaultC1P1 = createJobStatus("testCarrierPrivilegedAppBypass",
1178                 createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), carrier1Pkg1);
1179         JobStatus jsLowC1P1 = createJobStatus("testCarrierPrivilegedAppBypass",
1180                 createJob(0).setPriority(JobInfo.PRIORITY_LOW), carrier1Pkg1);
1181         JobStatus jsMinC1P1 = createJobStatus("testCarrierPrivilegedAppBypass",
1182                 createJob(0).setPriority(JobInfo.PRIORITY_MIN), carrier1Pkg1);
1183         JobStatus jsHighC1P2 = createJobStatus("testCarrierPrivilegedAppBypass",
1184                 createJob(0).setPriority(JobInfo.PRIORITY_HIGH), carrier1Pkg2);
1185         JobStatus jsDefaultC1P2 = createJobStatus("testCarrierPrivilegedAppBypass",
1186                 createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), carrier1Pkg2);
1187         JobStatus jsLowC1P2 = createJobStatus("testCarrierPrivilegedAppBypass",
1188                 createJob(0).setPriority(JobInfo.PRIORITY_LOW), carrier1Pkg2);
1189         JobStatus jsMinC1P2 = createJobStatus("testCarrierPrivilegedAppBypass",
1190                 createJob(0).setPriority(JobInfo.PRIORITY_MIN), carrier1Pkg2);
1191         JobStatus jsHighC2P = createJobStatus("testCarrierPrivilegedAppBypass",
1192                 createJob(0).setPriority(JobInfo.PRIORITY_HIGH), carrier2Pkg);
1193         JobStatus jsDefaultC2P = createJobStatus("testCarrierPrivilegedAppBypass",
1194                 createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), carrier2Pkg);
1195         JobStatus jsLowC2P = createJobStatus("testCarrierPrivilegedAppBypass",
1196                 createJob(0).setPriority(JobInfo.PRIORITY_LOW), carrier2Pkg);
1197         JobStatus jsMinC2P = createJobStatus("testCarrierPrivilegedAppBypass",
1198                 createJob(0).setPriority(JobInfo.PRIORITY_MIN), carrier2Pkg);
1199         JobStatus jsHighNCP = createJobStatus("testCarrierPrivilegedAppBypass",
1200                 createJob(0).setPriority(JobInfo.PRIORITY_HIGH), nonCarrierPkg);
1201         JobStatus jsDefaultNCP = createJobStatus("testCarrierPrivilegedAppBypass",
1202                 createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), nonCarrierPkg);
1203         JobStatus jsLowNCP = createJobStatus("testCarrierPrivilegedAppBypass",
1204                 createJob(0).setPriority(JobInfo.PRIORITY_LOW), nonCarrierPkg);
1205         JobStatus jsMinNCP = createJobStatus("testCarrierPrivilegedAppBypass",
1206                 createJob(0).setPriority(JobInfo.PRIORITY_MIN), nonCarrierPkg);
1207 
1208         setCarrierPrivilegedAppList(1);
1209         synchronized (mFlexibilityController.mLock) {
1210             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1));
1211             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1));
1212             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1));
1213             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1));
1214             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2));
1215             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2));
1216             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2));
1217             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2));
1218             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P));
1219             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P));
1220             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P));
1221             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P));
1222             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP));
1223             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP));
1224             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP));
1225             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP));
1226         }
1227 
1228         // Only mark the first package of carrier 1 as privileged. Only that app's jobs should
1229         // be exempted.
1230         setCarrierPrivilegedAppList(1, carrier1Pkg1);
1231         synchronized (mFlexibilityController.mLock) {
1232             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1));
1233             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1));
1234             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1));
1235             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1));
1236             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2));
1237             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2));
1238             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2));
1239             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2));
1240             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P));
1241             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P));
1242             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P));
1243             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P));
1244             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP));
1245             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP));
1246             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP));
1247             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP));
1248         }
1249 
1250         // Add the second package of carrier 1. Both apps' jobs should be exempted.
1251         setCarrierPrivilegedAppList(1, carrier1Pkg1, carrier1Pkg2);
1252         synchronized (mFlexibilityController.mLock) {
1253             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1));
1254             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1));
1255             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1));
1256             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1));
1257             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2));
1258             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2));
1259             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2));
1260             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2));
1261             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P));
1262             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P));
1263             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P));
1264             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P));
1265             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP));
1266             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP));
1267             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP));
1268             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP));
1269         }
1270 
1271         // Remove a SIM slot. The relevant app's should no longer have exempted jobs.
1272         setSimSlotMappings(List.of(sim1));
1273         synchronized (mFlexibilityController.mLock) {
1274             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1));
1275             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1));
1276             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1));
1277             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1));
1278             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2));
1279             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2));
1280             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2));
1281             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2));
1282             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P));
1283             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P));
1284             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P));
1285             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P));
1286             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP));
1287             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP));
1288             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP));
1289             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP));
1290         }
1291     }
1292 
1293     @Test
testForegroundAppBypass()1294     public void testForegroundAppBypass() {
1295         JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass",
1296                 createJob(0).setPriority(JobInfo.PRIORITY_HIGH));
1297         JobStatus jsDefault = createJobStatus("testAllowlistedAppBypass",
1298                 createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT));
1299         JobStatus jsLow = createJobStatus("testAllowlistedAppBypass",
1300                 createJob(0).setPriority(JobInfo.PRIORITY_LOW));
1301         JobStatus jsMin = createJobStatus("testAllowlistedAppBypass",
1302                 createJob(0).setPriority(JobInfo.PRIORITY_MIN));
1303 
1304         doReturn(JobInfo.BIAS_DEFAULT).when(mJobSchedulerService).getUidBias(mSourceUid);
1305         synchronized (mFlexibilityController.mLock) {
1306             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh));
1307             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault));
1308             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow));
1309             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin));
1310         }
1311 
1312         setUidBias(mSourceUid, JobInfo.BIAS_BOUND_FOREGROUND_SERVICE);
1313         synchronized (mFlexibilityController.mLock) {
1314             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh));
1315             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault));
1316             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow));
1317             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin));
1318         }
1319 
1320         setUidBias(mSourceUid, JobInfo.BIAS_FOREGROUND_SERVICE);
1321         synchronized (mFlexibilityController.mLock) {
1322             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHigh));
1323             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefault));
1324             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLow));
1325             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMin));
1326         }
1327     }
1328 
1329     @Test
testTopAppBypass()1330     public void testTopAppBypass() {
1331         JobInfo.Builder jb = createJob(0).setPriority(JobInfo.PRIORITY_MIN);
1332         JobStatus js = createJobStatus("testTopAppBypass", jb);
1333         mJobStore.add(js);
1334 
1335         // Needed because if before and after Uid bias is the same, nothing happens.
1336         doReturn(JobInfo.BIAS_DEFAULT).when(mJobSchedulerService).getUidBias(mSourceUid);
1337 
1338         synchronized (mFlexibilityController.mLock) {
1339             mFlexibilityController.maybeStartTrackingJobLocked(js, null);
1340             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
1341 
1342             setUidBias(mSourceUid, JobInfo.BIAS_TOP_APP);
1343 
1344             assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
1345             assertTrue(js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE));
1346 
1347             setUidBias(mSourceUid, JobInfo.BIAS_SYNC_INITIALIZATION);
1348 
1349             assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
1350             assertFalse(js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE));
1351         }
1352     }
1353 
1354     @Test
testTransportAffinity()1355     public void testTransportAffinity() {
1356         JobStatus jsAny = createJobStatus("testTransportAffinity",
1357                 createJob(0).setRequiredNetworkType(NETWORK_TYPE_ANY));
1358         JobStatus jsCell = createJobStatus("testTransportAffinity",
1359                 createJob(0).setRequiredNetworkType(NETWORK_TYPE_CELLULAR));
1360         JobStatus jsWifi = createJobStatus("testTransportAffinity",
1361                 createJob(0).setRequiredNetwork(
1362                         new NetworkRequest.Builder()
1363                                 .addTransportType(TRANSPORT_WIFI)
1364                                 .build()));
1365         // Disable the unseen constraint logic.
1366         mFlexibilityController.setConstraintSatisfied(
1367                 SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS, true, FROZEN_TIME);
1368         mFlexibilityController.setConstraintSatisfied(
1369                 SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS, false, FROZEN_TIME);
1370         // Require only a single constraint
1371         jsAny.setNumAppliedFlexibleConstraints(1);
1372         jsCell.setNumAppliedFlexibleConstraints(1);
1373         jsWifi.setNumAppliedFlexibleConstraints(1);
1374         synchronized (mFlexibilityController.mLock) {
1375             jsAny.setTransportAffinitiesSatisfied(false);
1376             jsCell.setTransportAffinitiesSatisfied(false);
1377             jsWifi.setTransportAffinitiesSatisfied(false);
1378             mFlexibilityController.setConstraintSatisfied(
1379                     CONSTRAINT_CONNECTIVITY, false, FROZEN_TIME);
1380             assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsAny));
1381             assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsCell));
1382             assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsWifi));
1383 
1384             // A good network exists, but the network hasn't been assigned to any of the jobs
1385             jsAny.setTransportAffinitiesSatisfied(false);
1386             jsCell.setTransportAffinitiesSatisfied(false);
1387             jsWifi.setTransportAffinitiesSatisfied(false);
1388             mFlexibilityController.setConstraintSatisfied(
1389                     CONSTRAINT_CONNECTIVITY, true, FROZEN_TIME);
1390             assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsAny));
1391             assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsCell));
1392             assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsWifi));
1393 
1394             // The good network has been assigned to the relevant jobs
1395             jsAny.setTransportAffinitiesSatisfied(true);
1396             jsCell.setTransportAffinitiesSatisfied(false);
1397             jsWifi.setTransportAffinitiesSatisfied(true);
1398             assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsAny));
1399             assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsCell));
1400             assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsWifi));
1401 
1402             // One job loses access to the network.
1403             jsAny.setTransportAffinitiesSatisfied(true);
1404             jsCell.setTransportAffinitiesSatisfied(false);
1405             jsWifi.setTransportAffinitiesSatisfied(false);
1406             assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsAny));
1407             assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsCell));
1408             assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(jsWifi));
1409         }
1410     }
1411 
1412     @Test
testSetConstraintSatisfied_Constraints()1413     public void testSetConstraintSatisfied_Constraints() {
1414         mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false, FROZEN_TIME);
1415         assertFalse(mFlexibilityController.isConstraintSatisfied(CONSTRAINT_IDLE));
1416 
1417         mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, true, FROZEN_TIME);
1418         assertTrue(mFlexibilityController.isConstraintSatisfied(CONSTRAINT_IDLE));
1419 
1420         mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false, FROZEN_TIME);
1421         assertFalse(mFlexibilityController.isConstraintSatisfied(CONSTRAINT_IDLE));
1422     }
1423 
1424     @Test
testSetConstraintSatisfied_Jobs()1425     public void testSetConstraintSatisfied_Jobs() {
1426         JobInfo.Builder jb;
1427         int[] constraintCombinations = {
1428                 CONSTRAINT_IDLE & CONSTRAINT_CHARGING & CONSTRAINT_BATTERY_NOT_LOW,
1429                 CONSTRAINT_IDLE & CONSTRAINT_BATTERY_NOT_LOW,
1430                 CONSTRAINT_IDLE & CONSTRAINT_CHARGING,
1431                 CONSTRAINT_CHARGING & CONSTRAINT_BATTERY_NOT_LOW,
1432                 CONSTRAINT_IDLE,
1433                 CONSTRAINT_CHARGING,
1434                 CONSTRAINT_BATTERY_NOT_LOW,
1435                 0
1436         };
1437 
1438         int constraints;
1439         for (int i = 0; i < constraintCombinations.length; i++) {
1440             jb = createJob(i);
1441             constraints = constraintCombinations[i];
1442             jb.setRequiresDeviceIdle((constraints & CONSTRAINT_IDLE) != 0);
1443             jb.setRequiresBatteryNotLow((constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0);
1444             jb.setRequiresCharging((constraints & CONSTRAINT_CHARGING) != 0);
1445             synchronized (mFlexibilityController.mLock) {
1446                 mFlexibilityController.maybeStartTrackingJobLocked(
1447                         createJobStatus(String.valueOf(i), jb), null);
1448             }
1449         }
1450         mFlexibilityController.setConstraintSatisfied(CONSTRAINT_CHARGING, false, FROZEN_TIME);
1451         mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false, FROZEN_TIME);
1452         mFlexibilityController.setConstraintSatisfied(
1453                 CONSTRAINT_BATTERY_NOT_LOW, false, FROZEN_TIME);
1454 
1455         assertEquals(0, mFlexibilityController.mSatisfiedFlexibleConstraints);
1456 
1457         for (int i = 0; i < constraintCombinations.length; i++) {
1458             constraints = constraintCombinations[i];
1459             mFlexibilityController.setConstraintSatisfied(CONSTRAINT_CHARGING,
1460                     (constraints & CONSTRAINT_CHARGING) != 0, FROZEN_TIME);
1461             mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE,
1462                     (constraints & CONSTRAINT_IDLE) != 0, FROZEN_TIME);
1463             mFlexibilityController.setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW,
1464                     (constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0, FROZEN_TIME);
1465 
1466             assertEquals(constraints, mFlexibilityController.mSatisfiedFlexibleConstraints);
1467             synchronized (mFlexibilityController.mLock) {
1468                 assertSatisfiedJobsMatchSatisfiedConstraints(
1469                         mFlexibilityController.mFlexibilityTracker.getArrayList(), constraints);
1470             }
1471         }
1472     }
1473 
1474     @Test
testHasEnoughSatisfiedConstraints_unseenConstraints_soonAfterBoot()1475     public void testHasEnoughSatisfiedConstraints_unseenConstraints_soonAfterBoot() {
1476         // Add connectivity to require 4 constraints
1477         JobStatus js = createJobStatus("testHasEnoughSatisfiedConstraints",
1478                 createJob(0).setRequiredNetworkType(NETWORK_TYPE_ANY));
1479 
1480         // Too soon after boot
1481         JobSchedulerService.sElapsedRealtimeClock =
1482                 Clock.fixed(Instant.ofEpochMilli(100 - 1), ZoneOffset.UTC);
1483         synchronized (mFlexibilityController.mLock) {
1484             assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(js));
1485         }
1486         JobSchedulerService.sElapsedRealtimeClock =
1487                 Clock.fixed(Instant.ofEpochMilli(DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS - 1),
1488                         ZoneOffset.UTC);
1489         synchronized (mFlexibilityController.mLock) {
1490             assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(js));
1491         }
1492 
1493         // Long after boot
1494 
1495         // No constraints ever seen. Don't bother waiting
1496         JobSchedulerService.sElapsedRealtimeClock =
1497                 Clock.fixed(Instant.ofEpochMilli(DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS),
1498                         ZoneOffset.UTC);
1499         synchronized (mFlexibilityController.mLock) {
1500             assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(js));
1501         }
1502     }
1503 
1504     @Test
testHasEnoughSatisfiedConstraints_unseenConstraints_longAfterBoot()1505     public void testHasEnoughSatisfiedConstraints_unseenConstraints_longAfterBoot() {
1506         // Add connectivity to require 4 constraints
1507         JobStatus connJs = createJobStatus("testHasEnoughSatisfiedConstraints",
1508                 createJob(0).setRequiredNetworkType(NETWORK_TYPE_ANY));
1509         JobStatus nonConnJs = createJobStatus("testHasEnoughSatisfiedConstraints",
1510                 createJob(0).setRequiredNetworkType(NETWORK_TYPE_NONE));
1511 
1512         mFlexibilityController.setConstraintSatisfied(
1513                 CONSTRAINT_BATTERY_NOT_LOW, true,
1514                 2 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10);
1515         mFlexibilityController.setConstraintSatisfied(
1516                 CONSTRAINT_CHARGING, true,
1517                 3 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10);
1518         mFlexibilityController.setConstraintSatisfied(
1519                 CONSTRAINT_IDLE, true,
1520                 4 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10);
1521         mFlexibilityController.setConstraintSatisfied(
1522                 CONSTRAINT_CONNECTIVITY, true,
1523                 5 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10);
1524 
1525         // Long after boot
1526         // All constraints satisfied right now
1527         JobSchedulerService.sElapsedRealtimeClock =
1528                 Clock.fixed(Instant.ofEpochMilli(DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS),
1529                         ZoneOffset.UTC);
1530         synchronized (mFlexibilityController.mLock) {
1531             assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
1532             assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
1533         }
1534 
1535         // Go down to 2 satisfied
1536         mFlexibilityController.setConstraintSatisfied(
1537                 CONSTRAINT_CONNECTIVITY, false,
1538                 6 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10);
1539         mFlexibilityController.setConstraintSatisfied(
1540                 CONSTRAINT_IDLE, false,
1541                 7 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10);
1542         // 3 & 4 constraints were seen recently enough, so the job should wait
1543         synchronized (mFlexibilityController.mLock) {
1544             assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
1545             assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
1546         }
1547 
1548         // 4 constraints still in the grace period. Wait.
1549         JobSchedulerService.sElapsedRealtimeClock =
1550                 Clock.fixed(
1551                         Instant.ofEpochMilli(16 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10),
1552                         ZoneOffset.UTC);
1553         synchronized (mFlexibilityController.mLock) {
1554             assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
1555             assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
1556         }
1557 
1558         // 3 constraints still in the grace period. Wait.
1559         JobSchedulerService.sElapsedRealtimeClock =
1560                 Clock.fixed(
1561                         Instant.ofEpochMilli(17 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10),
1562                         ZoneOffset.UTC);
1563         synchronized (mFlexibilityController.mLock) {
1564             assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
1565             assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
1566         }
1567 
1568         // 3 constraints haven't been seen recently. Don't wait.
1569         JobSchedulerService.sElapsedRealtimeClock =
1570                 Clock.fixed(
1571                         Instant.ofEpochMilli(
1572                                 17 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10 + 1),
1573                         ZoneOffset.UTC);
1574         synchronized (mFlexibilityController.mLock) {
1575             assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
1576             assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
1577         }
1578 
1579         // Add then remove connectivity. Resets expectation of 3 constraints for connectivity jobs.
1580         // Connectivity job should wait while the non-connectivity job can run.
1581         // of getting back to 4 constraints.
1582         mFlexibilityController.setConstraintSatisfied(
1583                 CONSTRAINT_CONNECTIVITY, true,
1584                 18 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10);
1585         mFlexibilityController.setConstraintSatisfied(
1586                 CONSTRAINT_CONNECTIVITY, false,
1587                 19 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10);
1588         JobSchedulerService.sElapsedRealtimeClock =
1589                 Clock.fixed(
1590                         Instant.ofEpochMilli(
1591                                 19 * DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS / 10 + 1),
1592                         ZoneOffset.UTC);
1593         synchronized (mFlexibilityController.mLock) {
1594             assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs));
1595             assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs));
1596         }
1597     }
1598 
1599     @Test
testCalculateNumDroppedConstraints()1600     public void testCalculateNumDroppedConstraints() {
1601         setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
1602                 "500=" + HOUR_IN_MILLIS
1603                         + ",400=" + 5 * HOUR_IN_MILLIS
1604                         + ",300=" + 8 * HOUR_IN_MILLIS
1605                         + ",200=" + 10 * HOUR_IN_MILLIS
1606                         + ",100=" + 20 * HOUR_IN_MILLIS);
1607         setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
1608                 "500=20|40|60|80"
1609                         + ",400=20|40|60|80"
1610                         + ",300=25|50|75|80"
1611                         + ",200=40|50|60|80"
1612                         + ",100=20|40|60|80");
1613 
1614         JobStatus jsHigh = createJobStatus("testCalculateNumDroppedConstraints",
1615                 createJob(24).setPriority(JobInfo.PRIORITY_HIGH));
1616         JobStatus jsDefault = createJobStatus("testCalculateNumDroppedConstraints",
1617                 createJob(23).setPriority(JobInfo.PRIORITY_DEFAULT));
1618         JobStatus jsLow = createJobStatus("testCalculateNumDroppedConstraints",
1619                 createJob(22).setPriority(JobInfo.PRIORITY_LOW));
1620         JobStatus jsMin = createJobStatus("testCalculateNumDroppedConstraints",
1621                 createJob(21).setPriority(JobInfo.PRIORITY_MIN));
1622         final long startElapsed = FROZEN_TIME;
1623         long nowElapsed = startElapsed;
1624 
1625         mFlexibilityController.mFlexibilityTracker.add(jsHigh);
1626         mFlexibilityController.mFlexibilityTracker.add(jsDefault);
1627         mFlexibilityController.mFlexibilityTracker.add(jsLow);
1628         mFlexibilityController.mFlexibilityTracker.add(jsMin);
1629 
1630         assertEquals(3, jsHigh.getNumRequiredFlexibleConstraints());
1631         assertEquals(0, jsHigh.getNumDroppedFlexibleConstraints());
1632         assertEquals(3, jsDefault.getNumRequiredFlexibleConstraints());
1633         assertEquals(0, jsDefault.getNumDroppedFlexibleConstraints());
1634         assertEquals(3, jsLow.getNumRequiredFlexibleConstraints());
1635         assertEquals(0, jsLow.getNumDroppedFlexibleConstraints());
1636         assertEquals(3, jsMin.getNumRequiredFlexibleConstraints());
1637         assertEquals(0, jsMin.getNumDroppedFlexibleConstraints());
1638         assertEquals(4, mFlexibilityController
1639                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
1640 
1641         nowElapsed = startElapsed + HOUR_IN_MILLIS;
1642         JobSchedulerService.sElapsedRealtimeClock =
1643                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
1644 
1645         mFlexibilityController.mFlexibilityTracker
1646                 .calculateNumDroppedConstraints(jsHigh, nowElapsed);
1647         mFlexibilityController.mFlexibilityTracker
1648                 .calculateNumDroppedConstraints(jsDefault, nowElapsed);
1649         mFlexibilityController.mFlexibilityTracker
1650                 .calculateNumDroppedConstraints(jsLow, nowElapsed);
1651         mFlexibilityController.mFlexibilityTracker
1652                 .calculateNumDroppedConstraints(jsMin, nowElapsed);
1653 
1654         assertEquals(2, jsHigh.getNumRequiredFlexibleConstraints());
1655         assertEquals(1, jsHigh.getNumDroppedFlexibleConstraints());
1656         assertEquals(3, jsDefault.getNumRequiredFlexibleConstraints());
1657         assertEquals(0, jsDefault.getNumDroppedFlexibleConstraints());
1658         assertEquals(3, jsLow.getNumRequiredFlexibleConstraints());
1659         assertEquals(0, jsLow.getNumDroppedFlexibleConstraints());
1660         assertEquals(3, jsMin.getNumRequiredFlexibleConstraints());
1661         assertEquals(0, jsMin.getNumDroppedFlexibleConstraints());
1662         assertEquals(3, mFlexibilityController
1663                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
1664         assertEquals(1, mFlexibilityController
1665                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
1666 
1667         nowElapsed = startElapsed;
1668         JobSchedulerService.sElapsedRealtimeClock =
1669                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
1670 
1671         mFlexibilityController.mFlexibilityTracker
1672                 .calculateNumDroppedConstraints(jsHigh, nowElapsed);
1673         mFlexibilityController.mFlexibilityTracker
1674                 .calculateNumDroppedConstraints(jsDefault, nowElapsed);
1675         mFlexibilityController.mFlexibilityTracker
1676                 .calculateNumDroppedConstraints(jsLow, nowElapsed);
1677         mFlexibilityController.mFlexibilityTracker
1678                 .calculateNumDroppedConstraints(jsMin, nowElapsed);
1679 
1680         assertEquals(3, jsHigh.getNumRequiredFlexibleConstraints());
1681         assertEquals(0, jsHigh.getNumDroppedFlexibleConstraints());
1682         assertEquals(3, jsDefault.getNumRequiredFlexibleConstraints());
1683         assertEquals(0, jsDefault.getNumDroppedFlexibleConstraints());
1684         assertEquals(3, jsLow.getNumRequiredFlexibleConstraints());
1685         assertEquals(0, jsLow.getNumDroppedFlexibleConstraints());
1686         assertEquals(3, jsMin.getNumRequiredFlexibleConstraints());
1687         assertEquals(0, jsMin.getNumDroppedFlexibleConstraints());
1688         assertEquals(4, mFlexibilityController
1689                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
1690         assertEquals(0, mFlexibilityController
1691                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
1692         assertEquals(0, mFlexibilityController
1693                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(1).size());
1694         assertEquals(0, mFlexibilityController
1695                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(0).size());
1696 
1697         nowElapsed = startElapsed + 3 * HOUR_IN_MILLIS;
1698         JobSchedulerService.sElapsedRealtimeClock =
1699                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
1700 
1701         mFlexibilityController.mFlexibilityTracker
1702                 .calculateNumDroppedConstraints(jsHigh, nowElapsed);
1703         mFlexibilityController.mFlexibilityTracker
1704                 .calculateNumDroppedConstraints(jsDefault, nowElapsed);
1705         mFlexibilityController.mFlexibilityTracker
1706                 .calculateNumDroppedConstraints(jsLow, nowElapsed);
1707         mFlexibilityController.mFlexibilityTracker
1708                 .calculateNumDroppedConstraints(jsMin, nowElapsed);
1709 
1710         assertEquals(0, jsHigh.getNumRequiredFlexibleConstraints());
1711         assertEquals(3, jsHigh.getNumDroppedFlexibleConstraints());
1712         assertEquals(2, jsDefault.getNumRequiredFlexibleConstraints());
1713         assertEquals(1, jsDefault.getNumDroppedFlexibleConstraints());
1714         assertEquals(3, jsLow.getNumRequiredFlexibleConstraints());
1715         assertEquals(0, jsLow.getNumDroppedFlexibleConstraints());
1716         assertEquals(3, jsMin.getNumRequiredFlexibleConstraints());
1717         assertEquals(0, jsMin.getNumDroppedFlexibleConstraints());
1718         assertEquals(2, mFlexibilityController
1719                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
1720         assertEquals(1, mFlexibilityController
1721                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
1722         assertEquals(0, mFlexibilityController
1723                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(1).size());
1724         assertEquals(1, mFlexibilityController
1725                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(0).size());
1726 
1727         nowElapsed = startElapsed + 4 * HOUR_IN_MILLIS;
1728         JobSchedulerService.sElapsedRealtimeClock =
1729                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
1730 
1731         mFlexibilityController.mFlexibilityTracker
1732                 .calculateNumDroppedConstraints(jsHigh, nowElapsed);
1733         mFlexibilityController.mFlexibilityTracker
1734                 .calculateNumDroppedConstraints(jsDefault, nowElapsed);
1735         mFlexibilityController.mFlexibilityTracker
1736                 .calculateNumDroppedConstraints(jsLow, nowElapsed);
1737         mFlexibilityController.mFlexibilityTracker
1738                 .calculateNumDroppedConstraints(jsMin, nowElapsed);
1739 
1740         assertEquals(0, jsHigh.getNumRequiredFlexibleConstraints());
1741         assertEquals(3, jsHigh.getNumDroppedFlexibleConstraints());
1742         assertEquals(1, jsDefault.getNumRequiredFlexibleConstraints());
1743         assertEquals(2, jsDefault.getNumDroppedFlexibleConstraints());
1744         assertEquals(2, jsLow.getNumRequiredFlexibleConstraints());
1745         assertEquals(1, jsLow.getNumDroppedFlexibleConstraints());
1746         assertEquals(2, jsMin.getNumRequiredFlexibleConstraints());
1747         assertEquals(1, jsMin.getNumDroppedFlexibleConstraints());
1748         assertEquals(0, mFlexibilityController
1749                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
1750         assertEquals(2, mFlexibilityController
1751                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
1752         assertEquals(1, mFlexibilityController
1753                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(1).size());
1754         assertEquals(1, mFlexibilityController
1755                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(0).size());
1756     }
1757 
1758     @Test
testOnPrefetchCacheUpdated()1759     public void testOnPrefetchCacheUpdated() {
1760         ArraySet<JobStatus> jobs = new ArraySet<JobStatus>();
1761         JobInfo.Builder jb = createJob(22).setPrefetch(true);
1762         JobStatus js = createJobStatus("onPrefetchCacheUpdated", jb);
1763         jobs.add(js);
1764         doReturn(7 * HOUR_IN_MILLIS).when(mPrefetchController).getLaunchTimeThresholdMs();
1765         doReturn(1150L + mFlexibilityController.mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS)
1766                 .when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js);
1767 
1768         mFlexibilityController.maybeStartTrackingJobLocked(js, null);
1769 
1770         final long nowElapsed = 150L;
1771         JobSchedulerService.sElapsedRealtimeClock =
1772                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
1773 
1774         mFlexibilityController.mPrefetchChangedListener.onPrefetchCacheUpdated(
1775                 jobs, js.getUserId(), js.getSourcePackageName(), Long.MAX_VALUE,
1776                 1150L + mFlexibilityController.mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS,
1777                 nowElapsed);
1778 
1779         assertEquals(150L,
1780                 (long) mFlexibilityController.mPrefetchLifeCycleStart
1781                         .get(js.getSourceUserId(), js.getSourcePackageName()));
1782         assertEquals(150L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
1783         assertEquals(1150L,
1784                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 150L));
1785         assertEquals(0, mFlexibilityController.getCurPercentOfLifecycleLocked(js, FROZEN_TIME));
1786         assertEquals(650L, mFlexibilityController
1787                 .getNextConstraintDropTimeElapsedLocked(js));
1788         assertEquals(3, js.getNumRequiredFlexibleConstraints());
1789         assertEquals(1, mFlexibilityController
1790                 .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
1791     }
1792 
1793     @Test
testScoreCalculation()1794     public void testScoreCalculation() {
1795         JobStatus jsMax = createJobStatus("testScoreCalculation",
1796                 createJob(0).setExpedited(true).setPriority(JobInfo.PRIORITY_MAX));
1797         JobStatus jsHigh = createJobStatus("testScoreCalculation",
1798                 createJob(0).setPriority(JobInfo.PRIORITY_HIGH));
1799         JobStatus jsDefault = createJobStatus("testScoreCalculation",
1800                 createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT));
1801         JobStatus jsLow = createJobStatus("testScoreCalculation",
1802                 createJob(0).setPriority(JobInfo.PRIORITY_LOW));
1803         JobStatus jsMin = createJobStatus("testScoreCalculation",
1804                 createJob(0).setPriority(JobInfo.PRIORITY_MIN));
1805 
1806         long nowElapsed = sElapsedRealtimeClock.millis();
1807         assertEquals(0,
1808                 mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
1809 
1810         mFlexibilityController.prepareForExecutionLocked(jsMax);
1811         assertEquals(5,
1812                 mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
1813 
1814         advanceElapsedClock(30 * MINUTE_IN_MILLIS);
1815         nowElapsed += 30 * MINUTE_IN_MILLIS;
1816         mFlexibilityController.prepareForExecutionLocked(jsMax);
1817         assertEquals(10,
1818                 mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
1819 
1820         advanceElapsedClock(31 * MINUTE_IN_MILLIS);
1821         nowElapsed += 31 * MINUTE_IN_MILLIS;
1822         mFlexibilityController.prepareForExecutionLocked(jsHigh);
1823         assertEquals(14,
1824                 mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
1825 
1826         advanceElapsedClock(2 * HOUR_IN_MILLIS);
1827         nowElapsed += 2 * HOUR_IN_MILLIS;
1828         mFlexibilityController.prepareForExecutionLocked(jsDefault);
1829         assertEquals(17,
1830                 mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
1831 
1832         advanceElapsedClock(3 * HOUR_IN_MILLIS);
1833         nowElapsed += 3 * HOUR_IN_MILLIS;
1834         mFlexibilityController.prepareForExecutionLocked(jsLow);
1835         assertEquals(19,
1836                 mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
1837 
1838         advanceElapsedClock(3 * HOUR_IN_MILLIS);
1839         nowElapsed += 3 * HOUR_IN_MILLIS;
1840         mFlexibilityController.prepareForExecutionLocked(jsMin);
1841         assertEquals(20,
1842                 mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
1843 
1844         advanceElapsedClock(3 * HOUR_IN_MILLIS);
1845         nowElapsed += 3 * HOUR_IN_MILLIS;
1846         mFlexibilityController.prepareForExecutionLocked(jsMax);
1847         assertEquals(25,
1848                 mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
1849         mFlexibilityController.unprepareFromExecutionLocked(jsMax);
1850         assertEquals(20,
1851                 mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
1852 
1853         // 24 hours haven't passed yet. The jobs in the first hour bucket should still be included.
1854         advanceElapsedClock(12 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS - 1);
1855         nowElapsed += 12 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS - 1;
1856         assertEquals(20,
1857                 mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
1858 
1859         // Passed the 24 hour mark. The jobs in the first hour bucket should no longer be included.
1860         advanceElapsedClock(2);
1861         nowElapsed += 2;
1862         assertEquals(10,
1863                 mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
1864 
1865     }
1866 
1867     /**
1868      * The beginning of a lifecycle for prefetch jobs includes the cached maximum of the last time
1869      * the estimated launch time was updated and the last time the app was opened.
1870      * When the UID bias updates it means the app might have been opened.
1871      * This tests that the cached value is updated properly.
1872      */
1873     @Test
testUidUpdatesLifeCycle()1874     public void testUidUpdatesLifeCycle() {
1875         JobInfo.Builder jb = createJob(0).setPrefetch(true);
1876         JobStatus js = createJobStatus("uidTest", jb);
1877         mFlexibilityController.maybeStartTrackingJobLocked(js, null);
1878         mJobStore.add(js);
1879 
1880         final ArraySet<String> pkgs = new ArraySet<>();
1881         pkgs.add(js.getSourcePackageName());
1882         doReturn(pkgs).when(mJobSchedulerService).getPackagesForUidLocked(mSourceUid);
1883 
1884         setUidBias(mSourceUid, BIAS_TOP_APP);
1885         setUidBias(mSourceUid, BIAS_FOREGROUND_SERVICE);
1886         assertEquals(100L, (long) mFlexibilityController.mPrefetchLifeCycleStart
1887                 .getOrDefault(js.getSourceUserId(), js.getSourcePackageName(), 0L));
1888 
1889         JobSchedulerService.sElapsedRealtimeClock =
1890                 Clock.fixed(Instant.ofEpochMilli(50L), ZoneOffset.UTC);
1891 
1892         setUidBias(mSourceUid, BIAS_TOP_APP);
1893         setUidBias(mSourceUid, BIAS_FOREGROUND_SERVICE);
1894         assertEquals(100L, (long) mFlexibilityController
1895                 .mPrefetchLifeCycleStart.get(js.getSourceUserId(), js.getSourcePackageName()));
1896     }
1897 
1898     @Test
testUnsupportedDevice_Auto()1899     public void testUnsupportedDevice_Auto() {
1900         runTestUnsupportedDevice(PackageManager.FEATURE_AUTOMOTIVE);
1901     }
1902 
1903     @Test
testUnsupportedDevice_Embedded()1904     public void testUnsupportedDevice_Embedded() {
1905         runTestUnsupportedDevice(PackageManager.FEATURE_EMBEDDED);
1906     }
1907 
runTestUnsupportedDevice(String feature)1908     private void runTestUnsupportedDevice(String feature) {
1909         doReturn(true).when(mPackageManager).hasSystemFeature(feature);
1910         mFlexibilityController =
1911                 new FlexibilityController(mJobSchedulerService, mPrefetchController);
1912         assertFalse(mFlexibilityController.isEnabled());
1913 
1914         JobStatus js = createJobStatus("testUnsupportedDevice", createJob(0));
1915 
1916         mFlexibilityController.maybeStartTrackingJobLocked(js, null);
1917         assertTrue(js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE));
1918 
1919         setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, FLEXIBLE_CONSTRAINTS);
1920         assertFalse(mFlexibilityController.isEnabled());
1921 
1922         ArrayList<ArraySet<JobStatus>> jobs =
1923                 mFlexibilityController.mFlexibilityTracker.getArrayList();
1924         for (int i = 0; i < jobs.size(); i++) {
1925             assertEquals(0, jobs.get(i).size());
1926         }
1927     }
1928 
setCarrierPrivilegedAppList(int logicalIndex, String... packages)1929     private void setCarrierPrivilegedAppList(int logicalIndex, String... packages) {
1930         final ArraySet<String> packageSet = packages == null
1931                 ? new ArraySet<>() : new ArraySet<>(packages);
1932         mCarrierPrivilegedApps.put(logicalIndex, packageSet);
1933 
1934         TelephonyManager.CarrierPrivilegesCallback callback =
1935                 mCarrierPrivilegedCallbacks.get(logicalIndex);
1936         if (callback != null) {
1937             callback.onCarrierPrivilegesChanged(packageSet, Collections.emptySet());
1938             waitForQuietModuleThread();
1939         }
1940     }
1941 
setPackageUid(final String pkgName, final int uid)1942     private void setPackageUid(final String pkgName, final int uid) throws Exception {
1943         doReturn(uid).when(mIPackageManager)
1944                 .getPackageUid(eq(pkgName), anyLong(), eq(UserHandle.getUserId(uid)));
1945     }
1946 
setPowerWhitelistExceptIdle(String... packages)1947     private void setPowerWhitelistExceptIdle(String... packages) {
1948         doReturn(packages == null ? EmptyArray.STRING : packages)
1949                 .when(mDeviceIdleInternal).getFullPowerWhitelistExceptIdle();
1950         if (mBroadcastReceiver != null) {
1951             mBroadcastReceiver.onReceive(mContext,
1952                     new Intent(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED));
1953             waitForQuietModuleThread();
1954         }
1955     }
1956 
setSimSlotMappings(@ullable Collection<UiccSlotMapping> simSlotMapping)1957     private void setSimSlotMappings(@Nullable Collection<UiccSlotMapping> simSlotMapping) {
1958         clearInvocations(mTelephonyManager);
1959         final Collection<UiccSlotMapping> returnedMapping = simSlotMapping == null
1960                 ? Collections.emptyList() : simSlotMapping;
1961         doReturn(returnedMapping).when(mTelephonyManager).getSimSlotMapping();
1962         if (mBroadcastReceiver != null) {
1963             final Intent intent = new Intent(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
1964             mBroadcastReceiver.onReceive(mContext, intent);
1965             waitForQuietModuleThread();
1966         }
1967         if (returnedMapping.size() > 0) {
1968             ArgumentCaptor<TelephonyManager.CarrierPrivilegesCallback> callbackCaptor =
1969                     ArgumentCaptor.forClass(TelephonyManager.CarrierPrivilegesCallback.class);
1970             ArgumentCaptor<Integer> logicalIndexCaptor = ArgumentCaptor.forClass(Integer.class);
1971 
1972             final int minExpectedNewRegistrations = Math.max(0,
1973                     returnedMapping.size() - mCarrierPrivilegedCallbacks.size());
1974             verify(mTelephonyManager, atLeast(minExpectedNewRegistrations))
1975                     .registerCarrierPrivilegesCallback(
1976                             logicalIndexCaptor.capture(), any(), callbackCaptor.capture());
1977 
1978             final List<Integer> registeredIndices = logicalIndexCaptor.getAllValues();
1979             final List<TelephonyManager.CarrierPrivilegesCallback> registeredCallbacks =
1980                     callbackCaptor.getAllValues();
1981             for (int i = 0; i < registeredIndices.size(); ++i) {
1982                 final int logicalIndex = registeredIndices.get(i);
1983                 final TelephonyManager.CarrierPrivilegesCallback callback =
1984                         registeredCallbacks.get(i);
1985 
1986                 mCarrierPrivilegedCallbacks.put(logicalIndex, callback);
1987 
1988                 // The API contract promises a callback upon registration with the current list.
1989                 final ArraySet<String> cpApps = mCarrierPrivilegedApps.get(logicalIndex);
1990                 callback.onCarrierPrivilegesChanged(
1991                         cpApps == null ? Collections.emptySet() : cpApps,
1992                         Collections.emptySet());
1993             }
1994             waitForQuietModuleThread();
1995         }
1996     }
1997 
setUidBias(int uid, int bias)1998     private void setUidBias(int uid, int bias) {
1999         int prevBias = mJobSchedulerService.getUidBias(uid);
2000         doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
2001         synchronized (mFlexibilityController.mLock) {
2002             mFlexibilityController.onUidBiasChangedLocked(uid, prevBias, bias);
2003         }
2004     }
2005 
assertSatisfiedJobsMatchSatisfiedConstraints( ArrayList<ArraySet<JobStatus>> trackedJobs, int satisfiedConstraints)2006     private void assertSatisfiedJobsMatchSatisfiedConstraints(
2007             ArrayList<ArraySet<JobStatus>> trackedJobs, int satisfiedConstraints) {
2008         int numSatisfiedConstraints;
2009         numSatisfiedConstraints = Integer.bitCount(satisfiedConstraints);
2010         for (int i = 0; i < trackedJobs.size(); i++) {
2011             ArraySet<JobStatus> jobs = trackedJobs.get(i);
2012             for (int j = 0; j < jobs.size(); j++) {
2013                 JobStatus js = jobs.valueAt(j);
2014                 final int transportAffinitySatisfied = js.canApplyTransportAffinities()
2015                         && js.areTransportAffinitiesSatisfied() ? 1 : 0;
2016                 assertEquals(js.getNumRequiredFlexibleConstraints()
2017                                 <= numSatisfiedConstraints + transportAffinitySatisfied,
2018                         js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE));
2019             }
2020         }
2021     }
2022 }
2023