blob: 9c833c0e0d1fae029444ad9783304bd83244a629 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.job.controllers;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.JobSchedulerService.sSystemClock;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import android.app.AlarmManager;
import android.app.job.JobInfo;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ServiceInfo;
import android.os.Looper;
import android.os.Process;
import android.os.SystemClock;
import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.SparseArray;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.controllers.PrefetchController.PcConstants;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;
import java.time.Clock;
import java.time.Duration;
import java.time.ZoneOffset;
import java.util.concurrent.Executor;
@RunWith(AndroidJUnit4.class)
public class PrefetchControllerTest {
private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
private static final int SOURCE_USER_ID = 0;
private static final int CALLING_UID = 1000;
private static final long DEFAULT_WAIT_MS = 3000;
private static final String TAG_PREFETCH = "*job.prefetch*";
private PrefetchController mPrefetchController;
private PcConstants mPcConstants;
private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
private EstimatedLaunchTimeChangedListener mEstimatedLaunchTimeChangedListener;
private SparseArray<ArraySet<String>> mPackagesForUid = new SparseArray<>();
private MockitoSession mMockingSession;
@Mock
private AlarmManager mAlarmManager;
@Mock
private Context mContext;
@Mock
private JobSchedulerService mJobSchedulerService;
@Mock
private UsageStatsManagerInternal mUsageStatsManagerInternal;
@Before
public void setUp() {
mMockingSession = mockitoSession()
.initMocks(this)
.strictness(Strictness.LENIENT)
.spyStatic(DeviceConfig.class)
.mockStatic(LocalServices.class)
.startMocking();
// Called in StateController constructor.
when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
// Called in PrefetchController constructor.
doReturn(mUsageStatsManagerInternal)
.when(() -> LocalServices.getService(UsageStatsManagerInternal.class));
when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
// Used in PrefetchController.PcConstants
doAnswer((Answer<Void>) invocationOnMock -> null)
.when(() -> DeviceConfig.addOnPropertiesChangedListener(
anyString(), any(Executor.class),
any(DeviceConfig.OnPropertiesChangedListener.class)));
mDeviceConfigPropertiesBuilder =
new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
doAnswer(
(Answer<DeviceConfig.Properties>) invocationOnMock
-> mDeviceConfigPropertiesBuilder.build())
.when(() -> DeviceConfig.getProperties(
eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER), ArgumentMatchers.<String>any()));
// Used in PrefetchController.maybeUpdateConstraintForUid
when(mJobSchedulerService.getPackagesForUidLocked(anyInt()))
.thenAnswer(invocationOnMock
-> mPackagesForUid.get(invocationOnMock.getArgument(0)));
// Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
// in the past, and PrefetchController sometimes floors values at 0, so if the test time
// causes sessions with negative timestamps, they will fail.
sSystemClock =
getShiftedClock(Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC),
24 * HOUR_IN_MILLIS);
JobSchedulerService.sUptimeMillisClock = getShiftedClock(
Clock.fixed(SystemClock.uptimeClock().instant(), ZoneOffset.UTC),
24 * HOUR_IN_MILLIS);
JobSchedulerService.sElapsedRealtimeClock = getShiftedClock(
Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC),
24 * HOUR_IN_MILLIS);
// Initialize real objects.
// Capture the listeners.
ArgumentCaptor<EstimatedLaunchTimeChangedListener> eltListenerCaptor =
ArgumentCaptor.forClass(EstimatedLaunchTimeChangedListener.class);
mPrefetchController = new PrefetchController(mJobSchedulerService);
mPcConstants = mPrefetchController.getPcConstants();
setUidBias(Process.myUid(), JobInfo.BIAS_DEFAULT);
verify(mUsageStatsManagerInternal)
.registerLaunchTimeChangedListener(eltListenerCaptor.capture());
mEstimatedLaunchTimeChangedListener = eltListenerCaptor.getValue();
}
@After
public void tearDown() {
if (mMockingSession != null) {
mMockingSession.finishMocking();
}
}
private JobInfo createJobInfo(int jobId) {
return new JobInfo.Builder(jobId,
new ComponentName(mContext, "TestPrefetchJobService"))
.setPrefetch(true)
.build();
}
private JobStatus createJobStatus(String testTag, int jobId) {
return createJobStatus(testTag, SOURCE_PACKAGE, CALLING_UID, createJobInfo(jobId));
}
private static JobStatus createJobStatus(String testTag, String packageName, int callingUid,
JobInfo jobInfo) {
JobStatus js = JobStatus.createFromJobInfo(
jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
js.serviceInfo = mock(ServiceInfo.class);
js.setStandbyBucket(FREQUENT_INDEX);
// Make sure Doze and background-not-restricted don't affect tests.
js.setDeviceNotDozingConstraintSatisfied(/* nowElapsed */ sElapsedRealtimeClock.millis(),
/* state */ true, /* allowlisted */false);
js.setBackgroundNotRestrictedConstraintSatisfied(
sElapsedRealtimeClock.millis(), true, false);
js.setQuotaConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
js.setTareWealthConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
js.setExpeditedJobQuotaApproved(sElapsedRealtimeClock.millis(), true);
js.setExpeditedJobTareApproved(sElapsedRealtimeClock.millis(), true);
return js;
}
private Clock getShiftedClock(Clock clock, long incrementMs) {
return Clock.offset(clock, Duration.ofMillis(incrementMs));
}
private void setUidBias(int uid, int bias) {
int prevBias = mJobSchedulerService.getUidBias(uid);
doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
synchronized (mPrefetchController.mLock) {
mPrefetchController.onUidBiasChangedLocked(uid, prevBias, bias);
}
}
private void setDeviceConfigLong(String key, long val) {
mDeviceConfigPropertiesBuilder.setLong(key, val);
synchronized (mPrefetchController.mLock) {
mPrefetchController.prepareForUpdatedConstantsLocked();
mPcConstants.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
mPrefetchController.onConstantsUpdatedLocked();
}
}
private void trackJobs(JobStatus... jobs) {
for (JobStatus job : jobs) {
ArraySet<String> pkgs = mPackagesForUid.get(job.getSourceUid());
if (pkgs == null) {
pkgs = new ArraySet<>();
mPackagesForUid.put(job.getSourceUid(), pkgs);
}
pkgs.add(job.getSourcePackageName());
synchronized (mPrefetchController.mLock) {
mPrefetchController.maybeStartTrackingJobLocked(job, null);
}
}
}
@Test
public void testConstantsUpdating_ValidValues() {
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 5 * HOUR_IN_MILLIS);
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_ALLOWANCE_MS, 5 * MINUTE_IN_MILLIS);
assertEquals(5 * HOUR_IN_MILLIS, mPrefetchController.getLaunchTimeThresholdMs());
assertEquals(5 * MINUTE_IN_MILLIS, mPrefetchController.getLaunchTimeAllowanceMs());
}
@Test
public void testConstantsUpdating_InvalidValues() {
// Test negatives/too low.
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 4 * MINUTE_IN_MILLIS);
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_ALLOWANCE_MS, -MINUTE_IN_MILLIS);
assertEquals(HOUR_IN_MILLIS, mPrefetchController.getLaunchTimeThresholdMs());
assertEquals(0, mPrefetchController.getLaunchTimeAllowanceMs());
// Test larger than a day. Controller should cap at one day.
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 25 * HOUR_IN_MILLIS);
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_ALLOWANCE_MS, 5 * HOUR_IN_MILLIS);
assertEquals(24 * HOUR_IN_MILLIS, mPrefetchController.getLaunchTimeThresholdMs());
assertEquals(2 * HOUR_IN_MILLIS, mPrefetchController.getLaunchTimeAllowanceMs());
}
@Test
public void testConstantsUpdating_ThresholdChangesAlarms() {
final long launchDelayMs = 11 * HOUR_IN_MILLIS;
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS);
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_ALLOWANCE_MS, 0);
when(mUsageStatsManagerInternal
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
.thenReturn(sSystemClock.millis() + launchDelayMs);
JobStatus jobStatus = createJobStatus("testConstantsUpdating_ThresholdChangesAlarms", 1);
trackJobs(jobStatus);
InOrder inOrder = inOrder(mAlarmManager);
inOrder.verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1))
.setWindow(
anyInt(), eq(sElapsedRealtimeClock.millis() + 4 * HOUR_IN_MILLIS),
anyLong(), eq(TAG_PREFETCH), any(), any());
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 3 * HOUR_IN_MILLIS);
inOrder.verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1))
.setWindow(
anyInt(), eq(sElapsedRealtimeClock.millis() + 8 * HOUR_IN_MILLIS),
anyLong(), eq(TAG_PREFETCH), any(), any());
}
@Test
public void testConstraintNotSatisfiedWhenLaunchLate() {
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_ALLOWANCE_MS, 0);
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS);
final JobStatus job = createJobStatus("testConstraintNotSatisfiedWhenLaunchLate", 1);
when(mUsageStatsManagerInternal
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
.thenReturn(sSystemClock.millis() + 10 * HOUR_IN_MILLIS);
trackJobs(job);
verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
assertFalse(job.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
assertFalse(job.isReady());
}
@Test
public void testConstraintSatisfiedWhenLaunchSoon() {
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_ALLOWANCE_MS, 0);
final JobStatus job = createJobStatus("testConstraintSatisfiedWhenLaunchSoon", 2);
when(mUsageStatsManagerInternal
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
.thenReturn(sSystemClock.millis() + MINUTE_IN_MILLIS);
trackJobs(job);
verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS)).onControllerStateChanged(any());
assertTrue(job.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
assertTrue(job.isReady());
}
@Test
public void testConstraintSatisfiedWhenTop() {
final JobStatus jobPending = createJobStatus("testConstraintSatisfiedWhenTop", 1);
final JobStatus jobRunning = createJobStatus("testConstraintSatisfiedWhenTop", 2);
final int uid = jobPending.getSourceUid();
when(mJobSchedulerService.isCurrentlyRunningLocked(jobPending)).thenReturn(false);
when(mJobSchedulerService.isCurrentlyRunningLocked(jobRunning)).thenReturn(true);
InOrder inOrder = inOrder(mJobSchedulerService);
when(mUsageStatsManagerInternal
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
.thenReturn(sSystemClock.millis() + 10 * MINUTE_IN_MILLIS);
trackJobs(jobPending, jobRunning);
verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
inOrder.verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS))
.onControllerStateChanged(any());
assertTrue(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
assertTrue(jobPending.isReady());
assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
assertTrue(jobRunning.isReady());
setUidBias(uid, JobInfo.BIAS_TOP_APP);
// Processing happens on the handler, so wait until we're sure the change has been processed
inOrder.verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS))
.onControllerStateChanged(any());
// Already running job should continue but pending job must wait.
assertFalse(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
assertFalse(jobPending.isReady());
assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
assertTrue(jobRunning.isReady());
setUidBias(uid, JobInfo.BIAS_DEFAULT);
inOrder.verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS))
.onControllerStateChanged(any());
assertTrue(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
assertTrue(jobPending.isReady());
assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
assertTrue(jobRunning.isReady());
}
@Test
public void testConstraintSatisfiedWhenWidget() {
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_ALLOWANCE_MS, 0);
final JobStatus jobNonWidget = createJobStatus("testConstraintSatisfiedWhenWidget", 1);
final JobStatus jobWidget = createJobStatus("testConstraintSatisfiedWhenWidget", 2);
when(mUsageStatsManagerInternal
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
.thenReturn(sSystemClock.millis() + 100 * HOUR_IN_MILLIS);
final AppWidgetManager appWidgetManager = mock(AppWidgetManager.class);
when(mContext.getSystemService(AppWidgetManager.class)).thenReturn(appWidgetManager);
mPrefetchController.onSystemServicesReady();
when(appWidgetManager.isBoundWidgetPackage(SOURCE_PACKAGE, SOURCE_USER_ID))
.thenReturn(false);
trackJobs(jobNonWidget);
verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
assertFalse(jobNonWidget.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
assertFalse(jobNonWidget.isReady());
when(appWidgetManager.isBoundWidgetPackage(SOURCE_PACKAGE, SOURCE_USER_ID))
.thenReturn(true);
trackJobs(jobWidget);
assertTrue(jobWidget.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
assertTrue(jobWidget.isReady());
}
@Test
public void testEstimatedLaunchTimeChangedToLate() {
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS);
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_ALLOWANCE_MS, 0);
when(mUsageStatsManagerInternal
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
.thenReturn(sSystemClock.millis() + HOUR_IN_MILLIS);
InOrder inOrder = inOrder(mUsageStatsManagerInternal);
JobStatus jobStatus = createJobStatus("testEstimatedLaunchTimeChangedToLate", 1);
trackJobs(jobStatus);
inOrder.verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS)).onControllerStateChanged(any());
assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
assertTrue(jobStatus.isReady());
mEstimatedLaunchTimeChangedListener.onEstimatedLaunchTimeChanged(SOURCE_USER_ID,
SOURCE_PACKAGE, sSystemClock.millis() + 10 * HOUR_IN_MILLIS);
inOrder.verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS).times(0))
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1))
.setWindow(
anyInt(), eq(sElapsedRealtimeClock.millis() + 3 * HOUR_IN_MILLIS),
anyLong(), eq(TAG_PREFETCH), any(), any());
assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
assertFalse(jobStatus.isReady());
}
@Test
public void testEstimatedLaunchTimeChangedToSoon() {
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS);
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_ALLOWANCE_MS, 0);
when(mUsageStatsManagerInternal
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
.thenReturn(sSystemClock.millis() + 10 * HOUR_IN_MILLIS);
InOrder inOrder = inOrder(mUsageStatsManagerInternal);
JobStatus jobStatus = createJobStatus("testEstimatedLaunchTimeChangedToSoon", 1);
trackJobs(jobStatus);
inOrder.verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
assertFalse(jobStatus.isReady());
mEstimatedLaunchTimeChangedListener.onEstimatedLaunchTimeChanged(SOURCE_USER_ID,
SOURCE_PACKAGE, sSystemClock.millis() + MINUTE_IN_MILLIS);
inOrder.verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS).times(0))
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS)).onControllerStateChanged(any());
assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
assertTrue(jobStatus.isReady());
}
@Test
public void testEstimatedLaunchTimeAllowance() {
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS);
setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_ALLOWANCE_MS, 15 * MINUTE_IN_MILLIS);
when(mUsageStatsManagerInternal
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
.thenReturn(sSystemClock.millis() + 10 * HOUR_IN_MILLIS);
InOrder inOrder = inOrder(mUsageStatsManagerInternal);
JobStatus jobStatus = createJobStatus("testEstimatedLaunchTimeAllowance", 1);
trackJobs(jobStatus);
inOrder.verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
// The allowance shouldn't shift the alarm
verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1))
.setWindow(
anyInt(), eq(sElapsedRealtimeClock.millis() + 3 * HOUR_IN_MILLIS),
anyLong(), eq(TAG_PREFETCH), any(), any());
assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
assertFalse(jobStatus.isReady());
mEstimatedLaunchTimeChangedListener.onEstimatedLaunchTimeChanged(SOURCE_USER_ID,
SOURCE_PACKAGE, sSystemClock.millis() + HOUR_IN_MILLIS);
inOrder.verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS).times(0))
.getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS)).onControllerStateChanged(any());
assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
assertTrue(jobStatus.isReady());
sSystemClock = getShiftedClock(sSystemClock, HOUR_IN_MILLIS + MINUTE_IN_MILLIS);
}
}