Loading apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java +68 −3 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import static com.android.server.job.controllers.Package.packageToString; import android.annotation.CurrentTimeMillisLong; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.app.job.JobInfo; import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener; import android.content.Context; Loading @@ -38,6 +39,7 @@ import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Slog; import android.util.SparseArrayMap; import android.util.SparseBooleanArray; import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; Loading Loading @@ -71,6 +73,9 @@ public class PrefetchController extends StateController { */ @GuardedBy("mLock") private final SparseArrayMap<String, Long> mEstimatedLaunchTimes = new SparseArrayMap<>(); /** Cached list of UIDs in the TOP state. */ @GuardedBy("mLock") private final SparseBooleanArray mTopUids = new SparseBooleanArray(); private final ThresholdAlarmListener mThresholdAlarmListener; /** Loading Loading @@ -98,6 +103,7 @@ public class PrefetchController extends StateController { private static final int MSG_RETRIEVE_ESTIMATED_LAUNCH_TIME = 0; private static final int MSG_PROCESS_UPDATED_ESTIMATED_LAUNCH_TIME = 1; private static final int MSG_PROCESS_TOP_STATE_CHANGE = 2; public PrefetchController(JobSchedulerService service) { super(service); Loading Loading @@ -165,6 +171,22 @@ public class PrefetchController extends StateController { mThresholdAlarmListener.removeAlarmsForUserId(userId); } @GuardedBy("mLock") @Override public void onUidBiasChangedLocked(int uid, int newBias) { final boolean isNowTop = newBias == JobInfo.BIAS_TOP_APP; final boolean wasTop = mTopUids.get(uid); if (isNowTop) { mTopUids.put(uid, true); } else { // Delete entries of non-top apps so the set doesn't get too large. mTopUids.delete(uid); } if (isNowTop != wasTop) { mHandler.obtainMessage(MSG_PROCESS_TOP_STATE_CHANGE, uid, 0).sendToTarget(); } } /** Return the app's next estimated launch time. */ @GuardedBy("mLock") @CurrentTimeMillisLong Loading Loading @@ -205,6 +227,35 @@ public class PrefetchController extends StateController { return changed; } private void maybeUpdateConstraintForUid(int uid) { synchronized (mLock) { final ArraySet<String> pkgs = mService.getPackagesForUidLocked(uid); if (pkgs == null) { return; } final int userId = UserHandle.getUserId(uid); final ArraySet<JobStatus> changedJobs = new ArraySet<>(); final long now = sSystemClock.millis(); final long nowElapsed = sElapsedRealtimeClock.millis(); for (int p = pkgs.size() - 1; p >= 0; --p) { final String pkgName = pkgs.valueAt(p); final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName); if (jobs == null) { continue; } for (int i = 0; i < jobs.size(); i++) { final JobStatus js = jobs.valueAt(i); if (updateConstraintLocked(js, now, nowElapsed)) { changedJobs.add(js); } } } if (changedJobs.size() > 0) { mStateChangedListener.onControllerStateChanged(changedJobs); } } } private void processUpdatedEstimatedLaunchTime(int userId, @NonNull String pkgName, @CurrentTimeMillisLong long newEstimatedLaunchTime) { if (DEBUG) { Loading Loading @@ -244,9 +295,18 @@ public class PrefetchController extends StateController { @GuardedBy("mLock") private boolean updateConstraintLocked(@NonNull JobStatus jobStatus, @CurrentTimeMillisLong long now, @ElapsedRealtimeLong long nowElapsed) { return jobStatus.setPrefetchConstraintSatisfied(nowElapsed, willBeLaunchedSoonLocked( jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), now)); // Mark a prefetch constraint as satisfied in the following scenarios: // 1. The app is not open but it will be launched soon // 2. The app is open and the job is already running (so we let it finish) final boolean appIsOpen = mTopUids.get(jobStatus.getSourceUid()); final boolean satisfied; if (!appIsOpen) { satisfied = willBeLaunchedSoonLocked( jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), now); } else { satisfied = mService.isCurrentlyRunningLocked(jobStatus); } return jobStatus.setPrefetchConstraintSatisfied(nowElapsed, satisfied); } @GuardedBy("mLock") Loading Loading @@ -399,6 +459,11 @@ public class PrefetchController extends StateController { processUpdatedEstimatedLaunchTime(args.argi1, (String) args.arg1, args.argl1); args.recycle(); break; case MSG_PROCESS_TOP_STATE_CHANGE: final int uid = msg.arg1; maybeUpdateConstraintForUid(uid); break; } } } Loading services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java +60 −0 Original line number Diff line number Diff line Loading @@ -47,8 +47,11 @@ 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; Loading Loading @@ -85,6 +88,7 @@ public class PrefetchControllerTest { private PcConstants mPcConstants; private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder; private EstimatedLaunchTimeChangedListener mEstimatedLaunchTimeChangedListener; private SparseArray<ArraySet<String>> mPackagesForUid = new SparseArray<>(); private MockitoSession mMockingSession; @Mock Loading Loading @@ -125,6 +129,10 @@ public class PrefetchControllerTest { -> 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 Loading @@ -146,6 +154,8 @@ public class PrefetchControllerTest { mPrefetchController = new PrefetchController(mJobSchedulerService); mPcConstants = mPrefetchController.getPcConstants(); setUidBias(Process.myUid(), JobInfo.BIAS_DEFAULT); verify(mUsageStatsManagerInternal) .registerLaunchTimeChangedListener(eltListenerCaptor.capture()); mEstimatedLaunchTimeChangedListener = eltListenerCaptor.getValue(); Loading Loading @@ -185,6 +195,12 @@ public class PrefetchControllerTest { return Clock.offset(clock, Duration.ofMillis(incrementMs)); } private void setUidBias(int uid, int bias) { synchronized (mPrefetchController.mLock) { mPrefetchController.onUidBiasChangedLocked(uid, bias); } } private void setDeviceConfigLong(String key, long val) { mDeviceConfigPropertiesBuilder.setLong(key, val); synchronized (mPrefetchController.mLock) { Loading @@ -196,6 +212,12 @@ public class PrefetchControllerTest { 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); } Loading Loading @@ -269,9 +291,45 @@ public class PrefetchControllerTest { 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)); } @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(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH)); 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)); assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH)); setUidBias(uid, JobInfo.BIAS_DEFAULT); inOrder.verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS)) .onControllerStateChanged(any()); assertTrue(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH)); assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH)); } @Test public void testEstimatedLaunchTimeChangedToLate() { setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS); Loading @@ -285,6 +343,7 @@ public class PrefetchControllerTest { 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)); mEstimatedLaunchTimeChangedListener.onEstimatedLaunchTimeChanged(SOURCE_USER_ID, Loading Loading @@ -315,6 +374,7 @@ public class PrefetchControllerTest { 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)); } } Loading
apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java +68 −3 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import static com.android.server.job.controllers.Package.packageToString; import android.annotation.CurrentTimeMillisLong; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.app.job.JobInfo; import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener; import android.content.Context; Loading @@ -38,6 +39,7 @@ import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Slog; import android.util.SparseArrayMap; import android.util.SparseBooleanArray; import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; Loading Loading @@ -71,6 +73,9 @@ public class PrefetchController extends StateController { */ @GuardedBy("mLock") private final SparseArrayMap<String, Long> mEstimatedLaunchTimes = new SparseArrayMap<>(); /** Cached list of UIDs in the TOP state. */ @GuardedBy("mLock") private final SparseBooleanArray mTopUids = new SparseBooleanArray(); private final ThresholdAlarmListener mThresholdAlarmListener; /** Loading Loading @@ -98,6 +103,7 @@ public class PrefetchController extends StateController { private static final int MSG_RETRIEVE_ESTIMATED_LAUNCH_TIME = 0; private static final int MSG_PROCESS_UPDATED_ESTIMATED_LAUNCH_TIME = 1; private static final int MSG_PROCESS_TOP_STATE_CHANGE = 2; public PrefetchController(JobSchedulerService service) { super(service); Loading Loading @@ -165,6 +171,22 @@ public class PrefetchController extends StateController { mThresholdAlarmListener.removeAlarmsForUserId(userId); } @GuardedBy("mLock") @Override public void onUidBiasChangedLocked(int uid, int newBias) { final boolean isNowTop = newBias == JobInfo.BIAS_TOP_APP; final boolean wasTop = mTopUids.get(uid); if (isNowTop) { mTopUids.put(uid, true); } else { // Delete entries of non-top apps so the set doesn't get too large. mTopUids.delete(uid); } if (isNowTop != wasTop) { mHandler.obtainMessage(MSG_PROCESS_TOP_STATE_CHANGE, uid, 0).sendToTarget(); } } /** Return the app's next estimated launch time. */ @GuardedBy("mLock") @CurrentTimeMillisLong Loading Loading @@ -205,6 +227,35 @@ public class PrefetchController extends StateController { return changed; } private void maybeUpdateConstraintForUid(int uid) { synchronized (mLock) { final ArraySet<String> pkgs = mService.getPackagesForUidLocked(uid); if (pkgs == null) { return; } final int userId = UserHandle.getUserId(uid); final ArraySet<JobStatus> changedJobs = new ArraySet<>(); final long now = sSystemClock.millis(); final long nowElapsed = sElapsedRealtimeClock.millis(); for (int p = pkgs.size() - 1; p >= 0; --p) { final String pkgName = pkgs.valueAt(p); final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName); if (jobs == null) { continue; } for (int i = 0; i < jobs.size(); i++) { final JobStatus js = jobs.valueAt(i); if (updateConstraintLocked(js, now, nowElapsed)) { changedJobs.add(js); } } } if (changedJobs.size() > 0) { mStateChangedListener.onControllerStateChanged(changedJobs); } } } private void processUpdatedEstimatedLaunchTime(int userId, @NonNull String pkgName, @CurrentTimeMillisLong long newEstimatedLaunchTime) { if (DEBUG) { Loading Loading @@ -244,9 +295,18 @@ public class PrefetchController extends StateController { @GuardedBy("mLock") private boolean updateConstraintLocked(@NonNull JobStatus jobStatus, @CurrentTimeMillisLong long now, @ElapsedRealtimeLong long nowElapsed) { return jobStatus.setPrefetchConstraintSatisfied(nowElapsed, willBeLaunchedSoonLocked( jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), now)); // Mark a prefetch constraint as satisfied in the following scenarios: // 1. The app is not open but it will be launched soon // 2. The app is open and the job is already running (so we let it finish) final boolean appIsOpen = mTopUids.get(jobStatus.getSourceUid()); final boolean satisfied; if (!appIsOpen) { satisfied = willBeLaunchedSoonLocked( jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), now); } else { satisfied = mService.isCurrentlyRunningLocked(jobStatus); } return jobStatus.setPrefetchConstraintSatisfied(nowElapsed, satisfied); } @GuardedBy("mLock") Loading Loading @@ -399,6 +459,11 @@ public class PrefetchController extends StateController { processUpdatedEstimatedLaunchTime(args.argi1, (String) args.arg1, args.argl1); args.recycle(); break; case MSG_PROCESS_TOP_STATE_CHANGE: final int uid = msg.arg1; maybeUpdateConstraintForUid(uid); break; } } } Loading
services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java +60 −0 Original line number Diff line number Diff line Loading @@ -47,8 +47,11 @@ 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; Loading Loading @@ -85,6 +88,7 @@ public class PrefetchControllerTest { private PcConstants mPcConstants; private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder; private EstimatedLaunchTimeChangedListener mEstimatedLaunchTimeChangedListener; private SparseArray<ArraySet<String>> mPackagesForUid = new SparseArray<>(); private MockitoSession mMockingSession; @Mock Loading Loading @@ -125,6 +129,10 @@ public class PrefetchControllerTest { -> 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 Loading @@ -146,6 +154,8 @@ public class PrefetchControllerTest { mPrefetchController = new PrefetchController(mJobSchedulerService); mPcConstants = mPrefetchController.getPcConstants(); setUidBias(Process.myUid(), JobInfo.BIAS_DEFAULT); verify(mUsageStatsManagerInternal) .registerLaunchTimeChangedListener(eltListenerCaptor.capture()); mEstimatedLaunchTimeChangedListener = eltListenerCaptor.getValue(); Loading Loading @@ -185,6 +195,12 @@ public class PrefetchControllerTest { return Clock.offset(clock, Duration.ofMillis(incrementMs)); } private void setUidBias(int uid, int bias) { synchronized (mPrefetchController.mLock) { mPrefetchController.onUidBiasChangedLocked(uid, bias); } } private void setDeviceConfigLong(String key, long val) { mDeviceConfigPropertiesBuilder.setLong(key, val); synchronized (mPrefetchController.mLock) { Loading @@ -196,6 +212,12 @@ public class PrefetchControllerTest { 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); } Loading Loading @@ -269,9 +291,45 @@ public class PrefetchControllerTest { 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)); } @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(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH)); 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)); assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH)); setUidBias(uid, JobInfo.BIAS_DEFAULT); inOrder.verify(mJobSchedulerService, timeout(DEFAULT_WAIT_MS)) .onControllerStateChanged(any()); assertTrue(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH)); assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH)); } @Test public void testEstimatedLaunchTimeChangedToLate() { setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS); Loading @@ -285,6 +343,7 @@ public class PrefetchControllerTest { 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)); mEstimatedLaunchTimeChangedListener.onEstimatedLaunchTimeChanged(SOURCE_USER_ID, Loading Loading @@ -315,6 +374,7 @@ public class PrefetchControllerTest { 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)); } }