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

Commit 8c4cfc57 authored by Kweku Adams's avatar Kweku Adams Committed by Automerger Merge Worker
Browse files

Merge "Don't give RESTRICTED jobs temp allowlist exemption." into tm-dev am:...

Merge "Don't give RESTRICTED jobs temp allowlist exemption." into tm-dev am: 58dce496 am: 843f6468

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17571815



Change-Id: I6d105b120795120fd4f089cfcca128d010ea797c
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 22373406 843f6468
Loading
Loading
Loading
Loading
+17 −9
Original line number Diff line number Diff line
@@ -307,7 +307,7 @@ public final class QuotaController extends StateController {
    private final SparseBooleanArray mTempAllowlistCache = new SparseBooleanArray();

    /**
     * Mapping of UIDs to the when their temp allowlist grace period ends (in the elapsed
     * Mapping of UIDs to when their temp allowlist grace period ends (in the elapsed
     * realtime timebase).
     */
    private final SparseLongArray mTempAllowlistGraceCache = new SparseLongArray();
@@ -815,6 +815,19 @@ public final class QuotaController extends StateController {
                jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
    }

    private boolean hasTempAllowlistExemptionLocked(int sourceUid, int standbyBucket,
            long nowElapsed) {
        if (standbyBucket == RESTRICTED_INDEX || standbyBucket == NEVER_INDEX) {
            // Don't let RESTRICTED apps get free quota from the temp allowlist.
            // TODO: consider granting the exemption to RESTRICTED apps if the temp allowlist allows
            // them to start FGS
            return false;
        }
        final long tempAllowlistGracePeriodEndElapsed = mTempAllowlistGraceCache.get(sourceUid);
        return mTempAllowlistCache.get(sourceUid)
                || nowElapsed < tempAllowlistGracePeriodEndElapsed;
    }

    /** @return true if the job is within expedited job quota. */
    @GuardedBy("mLock")
    public boolean isWithinEJQuotaLocked(@NonNull final JobStatus jobStatus) {
@@ -833,11 +846,8 @@ public final class QuotaController extends StateController {
        }

        final long nowElapsed = sElapsedRealtimeClock.millis();
        final long tempAllowlistGracePeriodEndElapsed =
                mTempAllowlistGraceCache.get(jobStatus.getSourceUid());
        final boolean hasTempAllowlistExemption = mTempAllowlistCache.get(jobStatus.getSourceUid())
                || nowElapsed < tempAllowlistGracePeriodEndElapsed;
        if (hasTempAllowlistExemption) {
        if (hasTempAllowlistExemptionLocked(jobStatus.getSourceUid(),
                jobStatus.getEffectiveStandbyBucket(), nowElapsed)) {
            return true;
        }

@@ -2127,10 +2137,8 @@ public final class QuotaController extends StateController {
            final long nowElapsed = sElapsedRealtimeClock.millis();
            final int standbyBucket = JobSchedulerService.standbyBucketForPackage(mPkg.packageName,
                    mPkg.userId, nowElapsed);
            final long tempAllowlistGracePeriodEndElapsed = mTempAllowlistGraceCache.get(mUid);
            final boolean hasTempAllowlistExemption = !mRegularJobTimer
                    && (mTempAllowlistCache.get(mUid)
                    || nowElapsed < tempAllowlistGracePeriodEndElapsed);
                    && hasTempAllowlistExemptionLocked(mUid, standbyBucket, nowElapsed);
            final long topAppGracePeriodEndElapsed = mTopAppGraceCache.get(mUid);
            final boolean hasTopAppExemption = !mRegularJobTimer
                    && (mTopAppCache.get(mUid) || nowElapsed < topAppGracePeriodEndElapsed);
+148 −0
Original line number Diff line number Diff line
@@ -2164,6 +2164,49 @@ public class QuotaControllerTest {
        }
    }

    @Test
    public void testIsWithinEJQuotaLocked_TempAllowlisting_Restricted() {
        setDischarging();
        JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_TempAllowlisting_Restricted", 1);
        setStandbyBucket(RESTRICTED_INDEX, js);
        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 10 * MINUTE_IN_MILLIS);
        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), true);
        synchronized (mQuotaController.mLock) {
            assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
        }

        setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
        final long gracePeriodMs = 15 * SECOND_IN_MILLIS;
        setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs);
        Handler handler = mQuotaController.getHandler();
        spyOn(handler);

        // The temp allowlist should not enable RESTRICTED apps' to schedule & start EJs if they're
        // out of quota.
        mTempAllowlistListener.onAppAdded(mSourceUid);
        synchronized (mQuotaController.mLock) {
            assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
        }
        advanceElapsedClock(10 * SECOND_IN_MILLIS);
        mTempAllowlistListener.onAppRemoved(mSourceUid);
        advanceElapsedClock(10 * SECOND_IN_MILLIS);
        // Still in grace period
        synchronized (mQuotaController.mLock) {
            assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
        }
        advanceElapsedClock(6 * SECOND_IN_MILLIS);
        // Out of grace period.
        synchronized (mQuotaController.mLock) {
            assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
        }
    }

    /**
     * Tests that Timers properly track sessions when an app becomes top and is closed.
     */
@@ -5559,6 +5602,111 @@ public class QuotaControllerTest {
                mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
    }

    @Test
    public void testEJTimerTracking_TempAllowlisting_Restricted() {
        setDischarging();
        setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
        final long gracePeriodMs = 15 * SECOND_IN_MILLIS;
        setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs);
        Handler handler = mQuotaController.getHandler();
        spyOn(handler);

        JobStatus job = createExpeditedJobStatus("testEJTimerTracking_TempAllowlisting_Restricted", 1);
        setStandbyBucket(RESTRICTED_INDEX, job);
        synchronized (mQuotaController.mLock) {
            mQuotaController.maybeStartTrackingJobLocked(job, null);
        }
        assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
        List<TimingSession> expected = new ArrayList<>();

        long start = JobSchedulerService.sElapsedRealtimeClock.millis();
        synchronized (mQuotaController.mLock) {
            mQuotaController.prepareForExecutionLocked(job);
        }
        advanceElapsedClock(10 * SECOND_IN_MILLIS);
        synchronized (mQuotaController.mLock) {
            mQuotaController.maybeStopTrackingJobLocked(job, job, true);
        }
        expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
        assertEquals(expected,
                mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));

        advanceElapsedClock(SECOND_IN_MILLIS);

        // Job starts after app is added to temp allowlist and stops before removal.
        start = JobSchedulerService.sElapsedRealtimeClock.millis();
        mTempAllowlistListener.onAppAdded(mSourceUid);
        synchronized (mQuotaController.mLock) {
            mQuotaController.maybeStartTrackingJobLocked(job, null);
            mQuotaController.prepareForExecutionLocked(job);
        }
        advanceElapsedClock(10 * SECOND_IN_MILLIS);
        synchronized (mQuotaController.mLock) {
            mQuotaController.maybeStopTrackingJobLocked(job, null, false);
        }
        expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
        assertEquals(expected,
                mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));

        // Job starts after app is added to temp allowlist and stops after removal,
        // before grace period ends.
        start = JobSchedulerService.sElapsedRealtimeClock.millis();
        mTempAllowlistListener.onAppAdded(mSourceUid);
        synchronized (mQuotaController.mLock) {
            mQuotaController.maybeStartTrackingJobLocked(job, null);
            mQuotaController.prepareForExecutionLocked(job);
        }
        advanceElapsedClock(10 * SECOND_IN_MILLIS);
        mTempAllowlistListener.onAppRemoved(mSourceUid);
        long elapsedGracePeriodMs = 2 * SECOND_IN_MILLIS;
        advanceElapsedClock(elapsedGracePeriodMs);
        synchronized (mQuotaController.mLock) {
            mQuotaController.maybeStopTrackingJobLocked(job, null, false);
        }
        expected.add(createTimingSession(start, 12 * SECOND_IN_MILLIS, 1));
        assertEquals(expected,
                mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));

        advanceElapsedClock(SECOND_IN_MILLIS);
        elapsedGracePeriodMs += SECOND_IN_MILLIS;

        // Job starts during grace period and ends after grace period ends
        start = JobSchedulerService.sElapsedRealtimeClock.millis();
        synchronized (mQuotaController.mLock) {
            mQuotaController.maybeStartTrackingJobLocked(job, null);
            mQuotaController.prepareForExecutionLocked(job);
        }
        final long remainingGracePeriod = gracePeriodMs - elapsedGracePeriodMs;
        advanceElapsedClock(remainingGracePeriod);
        // Wait for handler to update Timer
        // Can't directly evaluate the message because for some reason, the captured message returns
        // the wrong 'what' even though the correct message goes to the handler and the correct
        // path executes.
        verify(handler, timeout(gracePeriodMs + 5 * SECOND_IN_MILLIS)).handleMessage(any());
        advanceElapsedClock(10 * SECOND_IN_MILLIS);
        expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS + remainingGracePeriod, 1));
        synchronized (mQuotaController.mLock) {
            mQuotaController.maybeStopTrackingJobLocked(job, job, true);
        }
        assertEquals(expected,
                mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));

        // Job starts and runs completely after temp allowlist grace period.
        advanceElapsedClock(10 * SECOND_IN_MILLIS);
        start = JobSchedulerService.sElapsedRealtimeClock.millis();
        synchronized (mQuotaController.mLock) {
            mQuotaController.maybeStartTrackingJobLocked(job, null);
            mQuotaController.prepareForExecutionLocked(job);
        }
        advanceElapsedClock(10 * SECOND_IN_MILLIS);
        synchronized (mQuotaController.mLock) {
            mQuotaController.maybeStopTrackingJobLocked(job, job, true);
        }
        expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
        assertEquals(expected,
                mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
    }

    /**
     * Tests that Timers properly track sessions when TOP state and temp allowlisting overlaps.
     */