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