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

Commit 9e97d821 authored by Xin Guan's avatar Xin Guan Committed by Android (Google) Code Review
Browse files

Merge "JobScheduler: Enforce quota to jobs running in FGS." into main

parents 3e806614 37cbf384
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -75,3 +75,10 @@ flag {
       purpose: PURPOSE_BUGFIX
   }
}

flag {
   name: "enforce_quota_policy_to_fgs_jobs"
   namespace: "backstage_power"
   description: "Applies the normal quota policy to FGS jobs"
   bug: "341201311"
}
+18 −7
Original line number Diff line number Diff line
@@ -99,10 +99,10 @@ import java.util.function.Predicate;
 * the number of jobs or sessions that can run within the window. Regardless of bucket, apps will
 * not be allowed to run more than 20 jobs within the past 10 minutes.
 *
 * Jobs are throttled while an app is not in a foreground state. All jobs are allowed to run
 * freely when an app enters the foreground state and are restricted when the app leaves the
 * foreground state. However, jobs that are started while the app is in the TOP state do not count
 * towards any quota and are not restricted regardless of the app's state change.
 * Jobs are throttled while an app is not in a TOP or BOUND_TOP state. All jobs are allowed to run
 * freely when an app enters the TOP or BOUND_TOP state and are restricted when the app leaves those
 * states. However, jobs that are started while the app is in the TOP state do not count towards any
 * quota and are not restricted regardless of the app's state change.
 *
 * Jobs will not be throttled when the device is charging. The device is considered to be charging
 * once the {@link BatteryManager#ACTION_CHARGING} intent has been broadcast.
@@ -567,6 +567,11 @@ public final class QuotaController extends StateController {
            ActivityManager.getService().registerUidObserver(new QcUidObserver(),
                    ActivityManager.UID_OBSERVER_PROCSTATE,
                    ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, null);
            if (Flags.enforceQuotaPolicyToFgsJobs()) {
                ActivityManager.getService().registerUidObserver(new QcUidObserver(),
                        ActivityManager.UID_OBSERVER_PROCSTATE,
                        ActivityManager.PROCESS_STATE_BOUND_TOP, null);
            }
            ActivityManager.getService().registerUidObserver(new QcUidObserver(),
                    ActivityManager.UID_OBSERVER_PROCSTATE,
                    ActivityManager.PROCESS_STATE_TOP, null);
@@ -2706,6 +2711,12 @@ public final class QuotaController extends StateController {
        }
    }

    @VisibleForTesting
    int getProcessStateQuotaFreeThreshold() {
        return Flags.enforceQuotaPolicyToFgsJobs() ? ActivityManager.PROCESS_STATE_BOUND_TOP :
                ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
    }

    private class QcHandler extends Handler {

        QcHandler(Looper looper) {
@@ -2832,15 +2843,15 @@ public final class QuotaController extends StateController {
                                mTopAppCache.put(uid, true);
                                mTopAppGraceCache.delete(uid);
                                if (mForegroundUids.get(uid)) {
                                    // Went from FGS to TOP. We don't need to reprocess timers or
                                    // jobs.
                                    // Went from a process state with quota free to TOP. We don't
                                    // need to reprocess timers or jobs.
                                    break;
                                }
                                mForegroundUids.put(uid, true);
                                isQuotaFree = true;
                            } else {
                                final boolean reprocess;
                                if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
                                if (procState <= getProcessStateQuotaFreeThreshold()) {
                                    reprocess = !mForegroundUids.get(uid);
                                    mForegroundUids.put(uid, true);
                                    isQuotaFree = true;
+51 −14
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.job.Flags.FLAG_COUNT_QUOTA_FIX;
import static com.android.server.job.Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS;
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
@@ -303,6 +304,12 @@ public class QuotaControllerTest {
        }
    }

    private int getProcessStateQuotaFreeThreshold() {
        synchronized (mQuotaController.mLock) {
            return mQuotaController.getProcessStateQuotaFreeThreshold();
        }
    }

    private void setProcessState(int procState) {
        setProcessState(procState, mSourceUid);
    }
@@ -315,7 +322,7 @@ public class QuotaControllerTest {
            final boolean contained = foregroundUids.get(uid);
            mUidObserver.onUidStateChanged(uid, procState, 0,
                    ActivityManager.PROCESS_CAPABILITY_NONE);
            if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
            if (procState <= getProcessStateQuotaFreeThreshold()) {
                if (!contained) {
                    verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1))
                            .put(eq(uid), eq(true));
@@ -1371,7 +1378,7 @@ public class QuotaControllerTest {
        }

        setDischarging();
        setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
        setProcessState(getProcessStateQuotaFreeThreshold());
        synchronized (mQuotaController.mLock) {
            assertEquals(timeUntilQuotaConsumedMs,
                    mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
@@ -1473,7 +1480,7 @@ public class QuotaControllerTest {
        }

        setDischarging();
        setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
        setProcessState(getProcessStateQuotaFreeThreshold());
        synchronized (mQuotaController.mLock) {
            assertEquals(mQcConstants.EJ_LIMIT_WORKING_MS / 2,
                    mQuotaController.getMaxJobExecutionTimeMsLocked(job));
@@ -1505,7 +1512,7 @@ public class QuotaControllerTest {
                createTimingSession(sElapsedRealtimeClock.millis() - mQcConstants.EJ_WINDOW_SIZE_MS,
                        timeUsedMs, 5), true);

        setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
        setProcessState(getProcessStateQuotaFreeThreshold());
        synchronized (mQuotaController.mLock) {
            assertEquals(mQcConstants.EJ_LIMIT_WORKING_MS / 2,
                    mQuotaController.getMaxJobExecutionTimeMsLocked(job));
@@ -4126,7 +4133,7 @@ public class QuotaControllerTest {
        }
        advanceElapsedClock(5 * SECOND_IN_MILLIS);
        // Change to a state that should still be considered foreground.
        setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
        setProcessState(getProcessStateQuotaFreeThreshold());
        advanceElapsedClock(5 * SECOND_IN_MILLIS);
        synchronized (mQuotaController.mLock) {
            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
@@ -4134,6 +4141,36 @@ public class QuotaControllerTest {
        assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
    }

    /** Tests that Timers count FOREGROUND_SERVICE jobs. */
    @Test
    @RequiresFlagsEnabled(FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS)
    public void testTimerTracking_Fgs() {
        setDischarging();

        JobStatus jobStatus = createJobStatus("testTimerTracking_Fgs", 1);
        setProcessState(ActivityManager.PROCESS_STATE_BOUND_TOP);
        synchronized (mQuotaController.mLock) {
            mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
        }

        assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));

        synchronized (mQuotaController.mLock) {
            mQuotaController.prepareForExecutionLocked(jobStatus);
        }
        advanceElapsedClock(5 * SECOND_IN_MILLIS);
        // Change to FOREGROUND_SERVICE state that should count.
        setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
        long start = JobSchedulerService.sElapsedRealtimeClock.millis();
        advanceElapsedClock(5 * SECOND_IN_MILLIS);
        synchronized (mQuotaController.mLock) {
            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
        }
        List<TimingSession> expected = new ArrayList<>();
        expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
        assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
    }

    /**
     * Tests that Timers properly track sessions when switching between foreground and background
     * states.
@@ -4180,7 +4217,7 @@ public class QuotaControllerTest {
        }
        advanceElapsedClock(10 * SECOND_IN_MILLIS);
        expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
        setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
        setProcessState(getProcessStateQuotaFreeThreshold());
        synchronized (mQuotaController.mLock) {
            mQuotaController.prepareForExecutionLocked(jobFg3);
        }
@@ -4213,7 +4250,7 @@ public class QuotaControllerTest {
        }
        advanceElapsedClock(10 * SECOND_IN_MILLIS);
        expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
        setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
        setProcessState(getProcessStateQuotaFreeThreshold());
        synchronized (mQuotaController.mLock) {
            mQuotaController.prepareForExecutionLocked(jobFg3);
        }
@@ -4262,7 +4299,7 @@ public class QuotaControllerTest {
        }
        assertEquals(0, stats.jobCountInRateLimitingWindow);

        setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
        setProcessState(getProcessStateQuotaFreeThreshold());
        synchronized (mQuotaController.mLock) {
            mQuotaController.prepareForExecutionLocked(jobFg1);
        }
@@ -4412,7 +4449,7 @@ public class QuotaControllerTest {
            mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
        }
        advanceElapsedClock(5 * SECOND_IN_MILLIS);
        setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
        setProcessState(getProcessStateQuotaFreeThreshold());
        synchronized (mQuotaController.mLock) {
            mQuotaController.prepareForExecutionLocked(jobFg1);
        }
@@ -4625,7 +4662,7 @@ public class QuotaControllerTest {

        // App still in foreground so everything should be in quota.
        advanceElapsedClock(20 * SECOND_IN_MILLIS);
        setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
        setProcessState(getProcessStateQuotaFreeThreshold());
        assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
        assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
        assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
@@ -5901,7 +5938,7 @@ public class QuotaControllerTest {
        }
        advanceElapsedClock(10 * SECOND_IN_MILLIS);
        expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
        setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
        setProcessState(getProcessStateQuotaFreeThreshold());
        synchronized (mQuotaController.mLock) {
            mQuotaController.prepareForExecutionLocked(jobFg3);
        }
@@ -5935,7 +5972,7 @@ public class QuotaControllerTest {
        }
        advanceElapsedClock(10 * SECOND_IN_MILLIS);
        expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
        setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
        setProcessState(getProcessStateQuotaFreeThreshold());
        synchronized (mQuotaController.mLock) {
            mQuotaController.prepareForExecutionLocked(jobFg3);
        }
@@ -6056,7 +6093,7 @@ public class QuotaControllerTest {
            mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
        }
        advanceElapsedClock(5 * SECOND_IN_MILLIS);
        setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
        setProcessState(getProcessStateQuotaFreeThreshold());
        synchronized (mQuotaController.mLock) {
            mQuotaController.prepareForExecutionLocked(jobFg1);
        }
@@ -6534,7 +6571,7 @@ public class QuotaControllerTest {

        // App still in foreground so everything should be in quota.
        advanceElapsedClock(20 * SECOND_IN_MILLIS);
        setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
        setProcessState(getProcessStateQuotaFreeThreshold());
        assertTrue(jobTop2.isExpeditedQuotaApproved());
        assertTrue(jobFg.isExpeditedQuotaApproved());
        assertTrue(jobBg.isExpeditedQuotaApproved());