Merge "Don't start prefetch jobs for TOP apps."
This commit is contained in:
committed by
Android (Google) Code Review
commit
2f3eca13c2
@@ -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