Merge "Don't start prefetch jobs for TOP apps."

This commit is contained in:
TreeHugger Robot
2021-11-16 03:26:30 +00:00
committed by Android (Google) Code Review
2 changed files with 128 additions and 3 deletions

View File

@@ -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;
}
}
}

View File

@@ -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));
}
}