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

Commit ed5f09c7 authored by Kweku Adams's avatar Kweku Adams Committed by Android (Google) Code Review
Browse files

Merge "Handle NEVER-but-elevated-bucket case."

parents 518eef8a aba1f226
Loading
Loading
Loading
Loading
+29 −15
Original line number Original line Diff line number Diff line
@@ -1563,11 +1563,11 @@ public final class QuotaController extends StateController {
                standbyBucket);
                standbyBucket);
        final long remainingEJQuota = getRemainingEJExecutionTimeLocked(userId, packageName);
        final long remainingEJQuota = getRemainingEJExecutionTimeLocked(userId, packageName);


        if (stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs
        final boolean inRegularQuota = stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs
                && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs
                && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs
                && isUnderJobCountQuota
                && isUnderJobCountQuota
                && isUnderTimingSessionCountQuota
                && isUnderTimingSessionCountQuota;
                && remainingEJQuota > 0) {
        if (inRegularQuota && remainingEJQuota > 0) {
            // Already in quota. Why was this method called?
            // Already in quota. Why was this method called?
            if (DEBUG) {
            if (DEBUG) {
                Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
                Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
@@ -1582,10 +1582,7 @@ public final class QuotaController extends StateController {


        long inRegularQuotaTimeElapsed = Long.MAX_VALUE;
        long inRegularQuotaTimeElapsed = Long.MAX_VALUE;
        long inEJQuotaTimeElapsed = Long.MAX_VALUE;
        long inEJQuotaTimeElapsed = Long.MAX_VALUE;
        if (!(stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs
        if (!inRegularQuota) {
                && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs
                && isUnderJobCountQuota
                && isUnderTimingSessionCountQuota)) {
            // The time this app will have quota again.
            // The time this app will have quota again.
            long inQuotaTimeElapsed = stats.inQuotaTimeElapsed;
            long inQuotaTimeElapsed = stats.inQuotaTimeElapsed;
            if (!isUnderJobCountQuota && stats.bgJobCountInWindow < stats.jobCountLimit) {
            if (!isUnderJobCountQuota && stats.bgJobCountInWindow < stats.jobCountLimit) {
@@ -1603,8 +1600,17 @@ public final class QuotaController extends StateController {
        }
        }
        if (remainingEJQuota <= 0) {
        if (remainingEJQuota <= 0) {
            final long limitMs = mEJLimitsMs[standbyBucket] - mQuotaBufferMs;
            final long limitMs = mEJLimitsMs[standbyBucket] - mQuotaBufferMs;
            List<TimingSession> timingSessions = mEJTimingSessions.get(userId, packageName);
            long sumMs = 0;
            long sumMs = 0;
            final Timer ejTimer = mEJPkgTimers.get(userId, packageName);
            if (ejTimer != null && ejTimer.isActive()) {
                final long nowElapsed = sElapsedRealtimeClock.millis();
                sumMs += ejTimer.getCurrentDuration(nowElapsed);
                if (sumMs >= limitMs) {
                    inEJQuotaTimeElapsed = (nowElapsed - limitMs) + mEJLimitWindowSizeMs;
                }
            }
            List<TimingSession> timingSessions = mEJTimingSessions.get(userId, packageName);
            if (timingSessions != null) {
                for (int i = timingSessions.size() - 1; i >= 0; --i) {
                for (int i = timingSessions.size() - 1; i >= 0; --i) {
                    TimingSession ts = timingSessions.get(i);
                    TimingSession ts = timingSessions.get(i);
                    final long durationMs = ts.endTimeElapsed - ts.startTimeElapsed;
                    final long durationMs = ts.endTimeElapsed - ts.startTimeElapsed;
@@ -1615,6 +1621,14 @@ public final class QuotaController extends StateController {
                        break;
                        break;
                    }
                    }
                }
                }
            } else if ((ejTimer == null || !ejTimer.isActive()) && inRegularQuota) {
                // In some strange cases, an app may end be in the NEVER bucket but could have run
                // some regular jobs. This results in no EJ timing sessions and QC having a bad
                // time.
                Slog.wtf(TAG,
                        string(userId, packageName) + " has 0 EJ quota without running anything");
                return;
            }
        }
        }
        long inQuotaTimeElapsed = Math.min(inRegularQuotaTimeElapsed, inEJQuotaTimeElapsed);
        long inQuotaTimeElapsed = Math.min(inRegularQuotaTimeElapsed, inEJQuotaTimeElapsed);


+73 −0
Original line number Original line Diff line number Diff line
@@ -1960,6 +1960,79 @@ public class QuotaControllerTest {
                .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
                .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
    }
    }


    /**
     * Test that QC handles invalid cases where an app is in the NEVER bucket but has still run
     * jobs.
     */
    @Test
    public void testMaybeScheduleStartAlarmLocked_Never_EffectiveNotNever() {
        // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
        // because it schedules an alarm too. Prevent it from doing so.
        spyOn(mQuotaController);
        doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();

        // The app is really in the NEVER bucket but is elevated somehow (eg via uidActive).
        setStandbyBucket(NEVER_INDEX);
        final int effectiveStandbyBucket = FREQUENT_INDEX;

        // No sessions saved yet.
        synchronized (mQuotaController.mLock) {
            mQuotaController.maybeScheduleStartAlarmLocked(
                    SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
        }
        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());

        // Test with timing sessions out of window.
        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false);
        synchronized (mQuotaController.mLock) {
            mQuotaController.maybeScheduleStartAlarmLocked(
                    SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
        }
        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());

        // Test with timing sessions in window but still in quota.
        final long start = now - (6 * HOUR_IN_MILLIS);
        final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS;
        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1), false);
        synchronized (mQuotaController.mLock) {
            mQuotaController.maybeScheduleStartAlarmLocked(
                    SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
        }
        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());

        // Add some more sessions, but still in quota.
        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false);
        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1), false);
        synchronized (mQuotaController.mLock) {
            mQuotaController.maybeScheduleStartAlarmLocked(
                    SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
        }
        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());

        // Test when out of quota.
        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false);
        synchronized (mQuotaController.mLock) {
            mQuotaController.maybeScheduleStartAlarmLocked(
                    SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
        }
        verify(mAlarmManager, times(1))
                .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());

        // Alarm already scheduled, so make sure it's not scheduled again.
        synchronized (mQuotaController.mLock) {
            mQuotaController.maybeScheduleStartAlarmLocked(
                    SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
        }
        verify(mAlarmManager, times(1))
                .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
    }

    @Test
    @Test
    public void testMaybeScheduleStartAlarmLocked_Rare() {
    public void testMaybeScheduleStartAlarmLocked_Rare() {
        // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
        // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests