Don't start prefetch jobs for TOP apps.
If a prefetch job hasn't started by the time an app is opened, then we decide not to run it until the app is closed again. The rationale being that "prefetch" is to do something before the app launch and it wouldn't be useful while the app is on TOP. Any prefetch job that is already running when the app is launched will be allowed to finish. Bug: 194532703 Test: atest FrameworksMockingServicesTests:PrefetchControllerTest Change-Id: I485cd0170a0066a82ab66bf92e36206a9a78bd78
This commit is contained in:
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
@@ -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")
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
@@ -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) {
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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,
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user