1 /*
2  * Copyright (C) 2018 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.text.format.DateUtils.HOUR_IN_MILLIS;
20 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
21 
22 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
23 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
24 import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
25 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
26 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
27 import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
28 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
29 import static com.android.server.job.JobSchedulerService.sSystemClock;
30 
31 import static org.junit.Assert.assertEquals;
32 import static org.junit.Assert.assertFalse;
33 import static org.junit.Assert.assertTrue;
34 import static org.mockito.ArgumentMatchers.any;
35 import static org.mockito.ArgumentMatchers.anyInt;
36 import static org.mockito.ArgumentMatchers.anyLong;
37 import static org.mockito.ArgumentMatchers.anyString;
38 import static org.mockito.Mockito.eq;
39 import static org.mockito.Mockito.mock;
40 import static org.mockito.Mockito.timeout;
41 import static org.mockito.Mockito.verify;
42 
43 import android.app.AlarmManager;
44 import android.app.job.JobInfo;
45 import android.app.usage.UsageStatsManagerInternal;
46 import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener;
47 import android.appwidget.AppWidgetManager;
48 import android.content.ComponentName;
49 import android.content.Context;
50 import android.os.Handler;
51 import android.os.Looper;
52 import android.os.Process;
53 import android.os.SystemClock;
54 import android.provider.DeviceConfig;
55 import android.util.ArraySet;
56 import android.util.SparseArray;
57 
58 import androidx.test.runner.AndroidJUnit4;
59 
60 import com.android.server.LocalServices;
61 import com.android.server.job.JobSchedulerInternal;
62 import com.android.server.job.JobSchedulerService;
63 import com.android.server.job.controllers.PrefetchController.PcConstants;
64 
65 import org.junit.After;
66 import org.junit.Before;
67 import org.junit.Test;
68 import org.junit.runner.RunWith;
69 import org.mockito.ArgumentCaptor;
70 import org.mockito.ArgumentMatchers;
71 import org.mockito.InOrder;
72 import org.mockito.Mock;
73 import org.mockito.MockitoSession;
74 import org.mockito.quality.Strictness;
75 import org.mockito.stubbing.Answer;
76 
77 import java.time.Clock;
78 import java.time.Duration;
79 import java.time.ZoneOffset;
80 import java.util.concurrent.Executor;
81 
82 @RunWith(AndroidJUnit4.class)
83 public class PrefetchControllerTest {
84     private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
85     private static final int SOURCE_USER_ID = 0;
86     private static final int CALLING_UID = 1000;
87     private static final long DEFAULT_WAIT_MS = 3000;
88     private static final String TAG_PREFETCH = "*job.prefetch*";
89 
90     private PrefetchController mPrefetchController;
91     private PcConstants mPcConstants;
92     private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
93     private EstimatedLaunchTimeChangedListener mEstimatedLaunchTimeChangedListener;
94     private SparseArray<ArraySet<String>> mPackagesForUid = new SparseArray<>();
95 
96     private MockitoSession mMockingSession;
97     @Mock
98     private AlarmManager mAlarmManager;
99     @Mock
100     private Context mContext;
101     @Mock
102     private JobSchedulerService mJobSchedulerService;
103     @Mock
104     private UsageStatsManagerInternal mUsageStatsManagerInternal;
105 
106     @Before
setUp()107     public void setUp() {
108         mMockingSession = mockitoSession()
109                 .initMocks(this)
110                 .strictness(Strictness.LENIENT)
111                 .spyStatic(DeviceConfig.class)
112                 .mockStatic(LocalServices.class)
113                 .startMocking();
114 
115         // Called in StateController constructor.
116         when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
117         when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
118         // Called in PrefetchController constructor.
119         doReturn(mUsageStatsManagerInternal)
120                 .when(() -> LocalServices.getService(UsageStatsManagerInternal.class));
121         when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
122         when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
123         // Used in PrefetchController.PcConstants
124         doAnswer((Answer<Void>) invocationOnMock -> null)
125                 .when(() -> DeviceConfig.addOnPropertiesChangedListener(
126                         anyString(), any(Executor.class),
127                         any(DeviceConfig.OnPropertiesChangedListener.class)));
128         mDeviceConfigPropertiesBuilder =
129                 new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
130         doAnswer(
131                 (Answer<DeviceConfig.Properties>) invocationOnMock
132                         -> mDeviceConfigPropertiesBuilder.build())
133                 .when(() -> DeviceConfig.getProperties(
134                         eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER), ArgumentMatchers.<String>any()));
135         // Used in PrefetchController.maybeUpdateConstraintForUid
136         when(mJobSchedulerService.getPackagesForUidLocked(anyInt()))
137                 .thenAnswer(invocationOnMock
138                         -> mPackagesForUid.get(invocationOnMock.getArgument(0)));
139         // Used in JobStatus.
140         doReturn(mock(JobSchedulerInternal.class))
141                 .when(() -> LocalServices.getService(JobSchedulerInternal.class));
142 
143         // Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
144         // in the past, and PrefetchController sometimes floors values at 0, so if the test time
145         // causes sessions with negative timestamps, they will fail.
146         sSystemClock =
147                 getShiftedClock(Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC),
148                         24 * HOUR_IN_MILLIS);
149         JobSchedulerService.sUptimeMillisClock = getShiftedClock(
150                 Clock.fixed(SystemClock.uptimeClock().instant(), ZoneOffset.UTC),
151                 24 * HOUR_IN_MILLIS);
152         JobSchedulerService.sElapsedRealtimeClock = getShiftedClock(
153                 Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC),
154                 24 * HOUR_IN_MILLIS);
155 
156         // Initialize real objects.
157         // Capture the listeners.
158         ArgumentCaptor<EstimatedLaunchTimeChangedListener> eltListenerCaptor =
159                 ArgumentCaptor.forClass(EstimatedLaunchTimeChangedListener.class);
160         mPrefetchController = new PrefetchController(mJobSchedulerService);
161         mPcConstants = mPrefetchController.getPcConstants();
162 
163         setUidBias(Process.myUid(), JobInfo.BIAS_DEFAULT);
164 
165         verify(mUsageStatsManagerInternal)
166                 .registerLaunchTimeChangedListener(eltListenerCaptor.capture());
167         mEstimatedLaunchTimeChangedListener = eltListenerCaptor.getValue();
168     }
169 
170     @After
tearDown()171     public void tearDown() {
172         if (mMockingSession != null) {
173             mMockingSession.finishMocking();
174         }
175     }
176 
createJobInfo(int jobId)177     private JobInfo createJobInfo(int jobId) {
178         return new JobInfo.Builder(jobId,
179                 new ComponentName(mContext, "TestPrefetchJobService"))
180                 .setPrefetch(true)
181                 .build();
182     }
183 
createJobStatus(String testTag, int jobId)184     private JobStatus createJobStatus(String testTag, int jobId) {
185         return createJobStatus(testTag, SOURCE_PACKAGE, CALLING_UID, createJobInfo(jobId));
186     }
187 
createJobStatus(String testTag, String packageName, int callingUid, JobInfo jobInfo)188     private static JobStatus createJobStatus(String testTag, String packageName, int callingUid,
189             JobInfo jobInfo) {
190         JobStatus js = JobStatus.createFromJobInfo(
191                 jobInfo, callingUid, packageName, SOURCE_USER_ID, "PCTest", testTag);
192         js.serviceProcessName = "testProcess";
193         js.setStandbyBucket(FREQUENT_INDEX);
194         // Make sure Doze and background-not-restricted don't affect tests.
195         js.setDeviceNotDozingConstraintSatisfied(/* nowElapsed */ sElapsedRealtimeClock.millis(),
196                 /* state */ true, /* allowlisted */false);
197         js.setBackgroundNotRestrictedConstraintSatisfied(
198                 sElapsedRealtimeClock.millis(), true, false);
199         js.setQuotaConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
200         js.setTareWealthConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
201         js.setExpeditedJobQuotaApproved(sElapsedRealtimeClock.millis(), true);
202         js.setExpeditedJobTareApproved(sElapsedRealtimeClock.millis(), true);
203         js.setFlexibilityConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
204         return js;
205     }
206 
getShiftedClock(Clock clock, long incrementMs)207     private Clock getShiftedClock(Clock clock, long incrementMs) {
208         return Clock.offset(clock, Duration.ofMillis(incrementMs));
209     }
210 
setUidBias(int uid, int bias)211     private void setUidBias(int uid, int bias) {
212         int prevBias = mJobSchedulerService.getUidBias(uid);
213         doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
214         synchronized (mPrefetchController.mLock) {
215             mPrefetchController.onUidBiasChangedLocked(uid, prevBias, bias);
216         }
217     }
218 
setDeviceConfigLong(String key, long val)219     private void setDeviceConfigLong(String key, long val) {
220         mDeviceConfigPropertiesBuilder.setLong(key, val);
221         synchronized (mPrefetchController.mLock) {
222             mPrefetchController.prepareForUpdatedConstantsLocked();
223             mPcConstants.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
224             mPrefetchController.onConstantsUpdatedLocked();
225         }
226     }
227 
trackJobs(JobStatus... jobs)228     private void trackJobs(JobStatus... jobs) {
229         for (JobStatus job : jobs) {
230             ArraySet<String> pkgs = mPackagesForUid.get(job.getSourceUid());
231             if (pkgs == null) {
232                 pkgs = new ArraySet<>();
233                 mPackagesForUid.put(job.getSourceUid(), pkgs);
234             }
235             pkgs.add(job.getSourcePackageName());
236             synchronized (mPrefetchController.mLock) {
237                 mPrefetchController.maybeStartTrackingJobLocked(job, null);
238             }
239         }
240     }
241 
242     @Test
testConstantsUpdating_ValidValues()243     public void testConstantsUpdating_ValidValues() {
244         setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 5 * HOUR_IN_MILLIS);
245         setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_ALLOWANCE_MS, 5 * MINUTE_IN_MILLIS);
246 
247         assertEquals(5 * HOUR_IN_MILLIS, mPrefetchController.getLaunchTimeThresholdMs());
248         assertEquals(5 * MINUTE_IN_MILLIS, mPrefetchController.getLaunchTimeAllowanceMs());
249     }
250 
251     @Test
testConstantsUpdating_InvalidValues()252     public void testConstantsUpdating_InvalidValues() {
253         // Test negatives/too low.
254         setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 4 * MINUTE_IN_MILLIS);
255         setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_ALLOWANCE_MS, -MINUTE_IN_MILLIS);
256 
257         assertEquals(HOUR_IN_MILLIS, mPrefetchController.getLaunchTimeThresholdMs());
258         assertEquals(0, mPrefetchController.getLaunchTimeAllowanceMs());
259 
260         // Test larger than a day. Controller should cap at one day.
261         setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 25 * HOUR_IN_MILLIS);
262         setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_ALLOWANCE_MS, 5 * HOUR_IN_MILLIS);
263 
264         assertEquals(24 * HOUR_IN_MILLIS, mPrefetchController.getLaunchTimeThresholdMs());
265         assertEquals(2 * HOUR_IN_MILLIS, mPrefetchController.getLaunchTimeAllowanceMs());
266     }
267 
268     @Test
testConstantsUpdating_ThresholdChangesAlarms()269     public void testConstantsUpdating_ThresholdChangesAlarms() {
270         final long launchDelayMs = 11 * HOUR_IN_MILLIS;
271         setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS);
272         setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_ALLOWANCE_MS, 0);
273         when(mUsageStatsManagerInternal
274                 .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
275                 .thenReturn(sSystemClock.millis() + launchDelayMs);
276         JobStatus jobStatus = createJobStatus("testConstantsUpdating_ThresholdChangesAlarms", 1);
277         trackJobs(jobStatus);
278 
279         InOrder inOrder = inOrder(mAlarmManager);
280 
281         inOrder.verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1))
282                 .setWindow(
283                         anyInt(), eq(sElapsedRealtimeClock.millis() + 4 * HOUR_IN_MILLIS),
284                         anyLong(), eq(TAG_PREFETCH), any(), any(Handler.class));
285 
286         setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 3 * HOUR_IN_MILLIS);
287         inOrder.verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1))
288                 .setWindow(
289                         anyInt(), eq(sElapsedRealtimeClock.millis() + 8 * HOUR_IN_MILLIS),
290                         anyLong(), eq(TAG_PREFETCH), any(), any(Handler.class));
291     }
292 
293     @Test
testConstraintNotSatisfiedWhenLaunchLate()294     public void testConstraintNotSatisfiedWhenLaunchLate() {
295         setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_ALLOWANCE_MS, 0);
296         setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS);
297 
298         final JobStatus job = createJobStatus("testConstraintNotSatisfiedWhenLaunchLate", 1);
299         when(mUsageStatsManagerInternal
300                 .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
301                 .thenReturn(sSystemClock.millis() + 10 * HOUR_IN_MILLIS);
302         trackJobs(job);
303         verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
304                 .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
305         assertFalse(job.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
306         assertFalse(job.isReady());
307     }
308 
309     @Test
testConstraintSatisfiedWhenLaunchSoon()310     public void testConstraintSatisfiedWhenLaunchSoon() {
311         setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_ALLOWANCE_MS, 0);
312 
313         final JobStatus job = createJobStatus("testConstraintSatisfiedWhenLaunchSoon", 2);
314         when(mUsageStatsManagerInternal
315                 .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
316                 .thenReturn(sSystemClock.millis() + MINUTE_IN_MILLIS);
317         trackJobs(job);
318         verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
319                 .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
320         verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS)).onControllerStateChanged(any());
321         assertTrue(job.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
322         assertTrue(job.isReady());
323     }
324 
325     @Test
testConstraintSatisfiedWhenTop()326     public void testConstraintSatisfiedWhenTop() {
327         final JobStatus jobPending = createJobStatus("testConstraintSatisfiedWhenTop", 1);
328         final JobStatus jobRunning = createJobStatus("testConstraintSatisfiedWhenTop", 2);
329         final int uid = jobPending.getSourceUid();
330 
331         when(mJobSchedulerService.isCurrentlyRunningLocked(jobPending)).thenReturn(false);
332         when(mJobSchedulerService.isCurrentlyRunningLocked(jobRunning)).thenReturn(true);
333 
334         InOrder inOrder = inOrder(mJobSchedulerService);
335 
336         when(mUsageStatsManagerInternal
337                 .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
338                 .thenReturn(sSystemClock.millis() + 10 * MINUTE_IN_MILLIS);
339         trackJobs(jobPending, jobRunning);
340         verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
341                 .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
342         inOrder.verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS))
343                 .onControllerStateChanged(any());
344         assertTrue(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
345         assertTrue(jobPending.isReady());
346         assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
347         assertTrue(jobRunning.isReady());
348         setUidBias(uid, JobInfo.BIAS_TOP_APP);
349         // Processing happens on the handler, so wait until we're sure the change has been processed
350         inOrder.verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS))
351                 .onControllerStateChanged(any());
352         // Already running job should continue but pending job must wait.
353         assertFalse(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
354         assertFalse(jobPending.isReady());
355         assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
356         assertTrue(jobRunning.isReady());
357         setUidBias(uid, JobInfo.BIAS_DEFAULT);
358         inOrder.verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS))
359                 .onControllerStateChanged(any());
360         assertTrue(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
361         assertTrue(jobPending.isReady());
362         assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
363         assertTrue(jobRunning.isReady());
364     }
365 
366     @Test
testConstraintSatisfiedWhenWidget()367     public void testConstraintSatisfiedWhenWidget() {
368         setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_ALLOWANCE_MS, 0);
369 
370         final JobStatus jobNonWidget = createJobStatus("testConstraintSatisfiedWhenWidget", 1);
371         final JobStatus jobWidget = createJobStatus("testConstraintSatisfiedWhenWidget", 2);
372 
373         when(mUsageStatsManagerInternal
374                 .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
375                 .thenReturn(sSystemClock.millis() + 100 * HOUR_IN_MILLIS);
376 
377         final AppWidgetManager appWidgetManager = mock(AppWidgetManager.class);
378         when(mContext.getSystemService(AppWidgetManager.class)).thenReturn(appWidgetManager);
379         mPrefetchController.onSystemServicesReady();
380 
381         when(appWidgetManager.isBoundWidgetPackage(SOURCE_PACKAGE, SOURCE_USER_ID))
382                 .thenReturn(false);
383         trackJobs(jobNonWidget);
384         verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
385                 .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
386         assertFalse(jobNonWidget.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
387         assertFalse(jobNonWidget.isReady());
388 
389         when(appWidgetManager.isBoundWidgetPackage(SOURCE_PACKAGE, SOURCE_USER_ID))
390                 .thenReturn(true);
391         trackJobs(jobWidget);
392         assertTrue(jobWidget.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
393         assertTrue(jobWidget.isReady());
394     }
395 
396     @Test
testEstimatedLaunchTimeChangedToLate()397     public void testEstimatedLaunchTimeChangedToLate() {
398         setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS);
399         setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_ALLOWANCE_MS, 0);
400         when(mUsageStatsManagerInternal
401                 .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
402                 .thenReturn(sSystemClock.millis() + HOUR_IN_MILLIS);
403 
404         InOrder inOrder = inOrder(mUsageStatsManagerInternal);
405 
406         JobStatus jobStatus = createJobStatus("testEstimatedLaunchTimeChangedToLate", 1);
407         trackJobs(jobStatus);
408         inOrder.verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
409                 .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
410         verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS)).onControllerStateChanged(any());
411         assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
412         assertTrue(jobStatus.isReady());
413 
414         mEstimatedLaunchTimeChangedListener.onEstimatedLaunchTimeChanged(SOURCE_USER_ID,
415                 SOURCE_PACKAGE, sSystemClock.millis() + 10 * HOUR_IN_MILLIS);
416 
417         inOrder.verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS).times(0))
418                 .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
419         verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1))
420                 .setWindow(
421                         anyInt(), eq(sElapsedRealtimeClock.millis() + 3 * HOUR_IN_MILLIS),
422                         anyLong(), eq(TAG_PREFETCH), any(), any(Handler.class));
423         assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
424         assertFalse(jobStatus.isReady());
425     }
426 
427     @Test
testEstimatedLaunchTimeChangedToSoon()428     public void testEstimatedLaunchTimeChangedToSoon() {
429         setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS);
430         setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_ALLOWANCE_MS, 0);
431         when(mUsageStatsManagerInternal
432                 .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
433                 .thenReturn(sSystemClock.millis() + 10 * HOUR_IN_MILLIS);
434 
435         InOrder inOrder = inOrder(mUsageStatsManagerInternal);
436 
437         JobStatus jobStatus = createJobStatus("testEstimatedLaunchTimeChangedToSoon", 1);
438         trackJobs(jobStatus);
439         inOrder.verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
440                 .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
441         assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
442         assertFalse(jobStatus.isReady());
443 
444         mEstimatedLaunchTimeChangedListener.onEstimatedLaunchTimeChanged(SOURCE_USER_ID,
445                 SOURCE_PACKAGE, sSystemClock.millis() + MINUTE_IN_MILLIS);
446 
447         inOrder.verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS).times(0))
448                 .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
449         verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS)).onControllerStateChanged(any());
450         assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
451         assertTrue(jobStatus.isReady());
452     }
453 
454     @Test
testEstimatedLaunchTimeAllowance()455     public void testEstimatedLaunchTimeAllowance() {
456         setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS);
457         setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_ALLOWANCE_MS, 15 * MINUTE_IN_MILLIS);
458         when(mUsageStatsManagerInternal
459                 .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
460                 .thenReturn(sSystemClock.millis() + 10 * HOUR_IN_MILLIS);
461 
462         InOrder inOrder = inOrder(mUsageStatsManagerInternal);
463 
464         JobStatus jobStatus = createJobStatus("testEstimatedLaunchTimeAllowance", 1);
465         trackJobs(jobStatus);
466         inOrder.verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS))
467                 .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
468         // The allowance shouldn't shift the alarm
469         verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1))
470                 .setWindow(
471                         anyInt(), eq(sElapsedRealtimeClock.millis() + 3 * HOUR_IN_MILLIS),
472                         anyLong(), eq(TAG_PREFETCH), any(), any(Handler.class));
473         assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
474         assertFalse(jobStatus.isReady());
475 
476         mEstimatedLaunchTimeChangedListener.onEstimatedLaunchTimeChanged(SOURCE_USER_ID,
477                 SOURCE_PACKAGE, sSystemClock.millis() + HOUR_IN_MILLIS);
478 
479         inOrder.verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS).times(0))
480                 .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID);
481         verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS)).onControllerStateChanged(any());
482         assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
483         assertTrue(jobStatus.isReady());
484 
485         sSystemClock = getShiftedClock(sSystemClock, HOUR_IN_MILLIS + MINUTE_IN_MILLIS);
486     }
487 
488     @Test
testRegisterOnPrefetchChangedListener()489     public void testRegisterOnPrefetchChangedListener() {
490         when(mUsageStatsManagerInternal
491                 .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID))
492                 .thenReturn(sSystemClock.millis() + 10 * HOUR_IN_MILLIS);
493         // Needs to get wrapped in an array to get accessed by an inner class.
494         final boolean[] onPrefetchCacheChangedCalled = new boolean[1];
495         final PrefetchController.PrefetchChangedListener prefetchChangedListener =
496                 new PrefetchController.PrefetchChangedListener() {
497                     @Override
498                     public void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs,
499                             int userId, String pkgName, long prevEstimatedLaunchTime,
500                             long newEstimatedLaunchTime, long nowElapsed) {
501                         onPrefetchCacheChangedCalled[0] = true;
502                     }
503                 };
504         mPrefetchController.registerPrefetchChangedListener(prefetchChangedListener);
505 
506         JobStatus jobStatus = createJobStatus("testRegisterOnPrefetchChangedListener", 1);
507         trackJobs(jobStatus);
508 
509         mEstimatedLaunchTimeChangedListener.onEstimatedLaunchTimeChanged(SOURCE_USER_ID,
510                 SOURCE_PACKAGE, sSystemClock.millis() + HOUR_IN_MILLIS);
511         verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS)).onControllerStateChanged(any());
512 
513         assertTrue(onPrefetchCacheChangedCalled[0]);
514     }
515 }
516