Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2f3eca13 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

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

parents 6f6e6534 c3c5fa5a
Loading
Loading
Loading
Loading
+68 −3
Original line number Diff line number Diff line
@@ -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;
            }
        }
    }
+60 −0
Original line number Diff line number Diff line
@@ -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));
    }
}