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

Commit c3c5fa5a authored by Kweku Adams's avatar Kweku Adams
Browse files

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
parent 54c1ea89
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));
    }
}