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

Commit bbdaf006 authored by Xin Guan's avatar Xin Guan
Browse files

Offer additional quota to system installers.

Give power-exempted apps with the INSTALL_PACKAGES permission
additional quota for regular type of jobs.

Bug: 398264531
Test: atest FrameworksMockingServicesTests:com.android.server.job.controllers.QuotaControllerTest
Test: atest CtsJobSchedulerTestCases
Flag: com.android.server.job.additional_quota_for_system_installer
Change-Id: Iaffd26e74e2ab9dc3a5ef9b22e87912dd221e324
parent 4cd40167
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -116,3 +116,13 @@ flag {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    name: "additional_quota_for_system_installer"
    namespace: "backstage_power"
    description: "Offer additional quota for system installer"
    bug: "398264531"
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}
+59 −8
Original line number Diff line number Diff line
@@ -494,6 +494,9 @@ public final class QuotaController extends StateController {

    private long mEjLimitAdditionSpecialMs = QcConstants.DEFAULT_EJ_LIMIT_ADDITION_SPECIAL_MS;

    private long mAllowedTimePeriodAdditionaInstallerMs =
            QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS;

    /**
     * The period of time used to calculate expedited job sessions. Apps can only have expedited job
     * sessions totalling {@link #mEJLimitsMs}[bucket within this period of time (without factoring
@@ -1095,6 +1098,18 @@ public final class QuotaController extends StateController {
        return baseLimitMs;
    }

    private long getAllowedTimePerPeriodMsLocked(final int userId, @NonNull final String pkgName,
            final int standbyBucket) {
        final long baseLimitMs = mAllowedTimePerPeriodMs[standbyBucket];
        if (Flags.adjustQuotaDefaultConstants()
                && Flags.additionalQuotaForSystemInstaller()
                && standbyBucket == EXEMPTED_INDEX
                && mSystemInstallers.contains(userId, pkgName)) {
            return baseLimitMs + mAllowedTimePeriodAdditionaInstallerMs;
        }
        return baseLimitMs;
    }

    /**
     * Returns the amount of time, in milliseconds, until the package would have reached its
     * duration quota, assuming it has a job counting towards its quota the entire time. This takes
@@ -1112,25 +1127,26 @@ public final class QuotaController extends StateController {

        List<TimedEvent> events = mTimingEvents.get(userId, packageName);
        final ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
        final long allowedTimePerPeriodMs =
                getAllowedTimePerPeriodMsLocked(userId, packageName, standbyBucket);
        if (events == null || events.size() == 0) {
            // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can
            // essentially run until they reach the maximum limit.
            if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) {
            if (stats.windowSizeMs == allowedTimePerPeriodMs) {
                return mMaxExecutionTimeMs;
            }
            return mAllowedTimePerPeriodMs[standbyBucket];
            return allowedTimePerPeriodMs;
        }

        final long startWindowElapsed = nowElapsed - stats.windowSizeMs;
        final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS;
        final long allowedTimePerPeriodMs = mAllowedTimePerPeriodMs[standbyBucket];
        final long allowedTimeRemainingMs = allowedTimePerPeriodMs - stats.executionTimeInWindowMs;
        final long maxExecutionTimeRemainingMs =
                mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs;

        // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can
        // essentially run until they reach the maximum limit.
        if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) {
        if (stats.windowSizeMs == allowedTimePerPeriodMs) {
            return calculateTimeUntilQuotaConsumedLocked(
                    events, startMaxElapsed, maxExecutionTimeRemainingMs);
        }
@@ -1270,7 +1286,8 @@ public final class QuotaController extends StateController {
            appStats[standbyBucket] = stats;
        }
        if (refreshStatsIfOld) {
            final long bucketAllowedTimeMs = mAllowedTimePerPeriodMs[standbyBucket];
            final long bucketAllowedTimeMs =
                    getAllowedTimePerPeriodMsLocked(userId, packageName, standbyBucket);
            final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket];
            final int jobCountLimit = mMaxBucketJobCounts[standbyBucket];
            final int sessionCountLimit = mMaxBucketSessionCounts[standbyBucket];
@@ -1845,9 +1862,10 @@ public final class QuotaController extends StateController {
        final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats);
        final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats);
        final long remainingEJQuota = getRemainingEJExecutionTimeLocked(userId, packageName);

        final long allowedTimePerPeriosMs =
                getAllowedTimePerPeriodMsLocked(userId, packageName, standbyBucket);
        final boolean inRegularQuota =
                stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs[standbyBucket]
                stats.executionTimeInWindowMs < allowedTimePerPeriosMs
                        && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs
                        && isUnderJobCountQuota
                        && isUnderTimingSessionCountQuota;
@@ -3037,6 +3055,9 @@ public final class QuotaController extends StateController {
        static final String KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS =
                QC_CONSTANT_PREFIX + "allowed_time_per_period_restricted_ms";
        @VisibleForTesting
        static final String KEY_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS =
                QC_CONSTANT_PREFIX + "allowed_time_per_period_addition_installer_ms";
        @VisibleForTesting
        static final String KEY_IN_QUOTA_BUFFER_MS =
                QC_CONSTANT_PREFIX + "in_quota_buffer_ms";
        @VisibleForTesting
@@ -3169,6 +3190,8 @@ public final class QuotaController extends StateController {
                10 * 60 * 1000L; // 10 minutes
        private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS =
                10 * 60 * 1000L; // 10 minutes
        private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS =
                10 * 60 * 1000L; // 10 minutes
        private static final long DEFAULT_IN_QUOTA_BUFFER_MS =
                30 * 1000L; // 30 seconds
        // Legacy default window size for EXEMPTED bucket
@@ -3509,6 +3532,9 @@ public final class QuotaController extends StateController {
         */
        public long EJ_LIMIT_ADDITION_INSTALLER_MS = DEFAULT_EJ_LIMIT_ADDITION_INSTALLER_MS;

        public long ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS =
                DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS;

        /**
         * The period of time used to calculate expedited job sessions. Apps can only have expedited
         * job sessions totalling EJ_LIMIT_<bucket>_MS within this period of time (without factoring
@@ -3603,6 +3629,7 @@ public final class QuotaController extends StateController {
                case KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS:
                case KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS:
                case KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS:
                case KEY_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS:
                case KEY_IN_QUOTA_BUFFER_MS:
                case KEY_MAX_EXECUTION_TIME_MS:
                case KEY_WINDOW_SIZE_ACTIVE_MS:
@@ -3847,7 +3874,7 @@ public final class QuotaController extends StateController {
                    KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
                    KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS,
                    KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
                    KEY_IN_QUOTA_BUFFER_MS,
                    KEY_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS, KEY_IN_QUOTA_BUFFER_MS,
                    KEY_MAX_EXECUTION_TIME_MS,
                    KEY_WINDOW_SIZE_EXEMPTED_MS, KEY_WINDOW_SIZE_ACTIVE_MS,
                    KEY_WINDOW_SIZE_WORKING_MS,
@@ -3871,6 +3898,9 @@ public final class QuotaController extends StateController {
            ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS =
                    properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
                            DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS);
            ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS =
                    properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS,
                            DEFAULT_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS);
            IN_QUOTA_BUFFER_MS = properties.getLong(KEY_IN_QUOTA_BUFFER_MS,
                    DEFAULT_IN_QUOTA_BUFFER_MS);
            MAX_EXECUTION_TIME_MS = properties.getLong(KEY_MAX_EXECUTION_TIME_MS,
@@ -3995,6 +4025,18 @@ public final class QuotaController extends StateController {
                mBucketPeriodsMs[RESTRICTED_INDEX] = newRestrictedPeriodMs;
                mShouldReevaluateConstraints = true;
            }

            if (Flags.additionalQuotaForSystemInstaller()) {
                // The additions must be in the range
                // [0 minutes, exempted window size - active limit].
                long newAdditionInstallerMs = Math.max(0,
                        Math.min(mBucketPeriodsMs[EXEMPTED_INDEX] - newAllowedTimeExemptedMs,
                                ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS));
                if (mAllowedTimePeriodAdditionaInstallerMs != newAdditionInstallerMs) {
                    mAllowedTimePeriodAdditionaInstallerMs = newAdditionInstallerMs;
                    mShouldReevaluateConstraints = true;
                }
            }
        }

        private void updateRateLimitingConstantsLocked() {
@@ -4159,6 +4201,8 @@ public final class QuotaController extends StateController {
                    .println();
            pw.print(KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
                    ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS).println();
            pw.print(KEY_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS,
                    ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS).println();
            pw.print(KEY_IN_QUOTA_BUFFER_MS, IN_QUOTA_BUFFER_MS).println();
            pw.print(KEY_WINDOW_SIZE_EXEMPTED_MS, WINDOW_SIZE_EXEMPTED_MS).println();
            pw.print(KEY_WINDOW_SIZE_ACTIVE_MS, WINDOW_SIZE_ACTIVE_MS).println();
@@ -4334,6 +4378,11 @@ public final class QuotaController extends StateController {
        return mEjLimitAdditionInstallerMs;
    }

    @VisibleForTesting
    long getAllowedTimePeriodAdditionInstallerMs() {
        return mAllowedTimePeriodAdditionaInstallerMs;
    }

    @VisibleForTesting
    long getEjLimitAdditionSpecialMs() {
        return mEjLimitAdditionSpecialMs;
@@ -4435,6 +4484,8 @@ public final class QuotaController extends StateController {
                + ": " + Flags.enforceQuotaPolicyToFgsJobs());
        pw.println("    " + Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS
                + ": " + Flags.enforceQuotaPolicyToTopStartedJobs());
        pw.println("    " + Flags.FLAG_ADDITIONAL_QUOTA_FOR_SYSTEM_INSTALLER
                + ": " + Flags.additionalQuotaForSystemInstaller());
        pw.println();

        pw.println("Current elapsed time: " + sElapsedRealtimeClock.millis());
+127 −1
Original line number Diff line number Diff line
@@ -2388,6 +2388,104 @@ public class QuotaControllerTest {
        }
    }

    @Test
    @EnableFlags({Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS,
            Flags.FLAG_ADDITIONAL_QUOTA_FOR_SYSTEM_INSTALLER})
    public void testGetTimeUntilQuotaConsumedLocked_Installer() {
        PackageInfo pi = new PackageInfo();
        pi.packageName = SOURCE_PACKAGE;
        pi.requestedPermissions = new String[]{Manifest.permission.INSTALL_PACKAGES};
        pi.requestedPermissionsFlags = new int[]{PackageInfo.REQUESTED_PERMISSION_GRANTED};
        pi.applicationInfo = new ApplicationInfo();
        pi.applicationInfo.uid = mSourceUid;
        doReturn(List.of(pi)).when(mPackageManager).getInstalledPackagesAsUser(anyInt(), anyInt());
        doReturn(PackageManager.PERMISSION_GRANTED).when(mContext).checkPermission(
                eq(Manifest.permission.INSTALL_PACKAGES), anyInt(), eq(mSourceUid));
        mQuotaController.onSystemServicesReady();

        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
        // Close to RARE boundary.
        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                createTimingSession(
                        now - (mQcConstants.WINDOW_SIZE_RARE_MS - 30 * SECOND_IN_MILLIS),
                        90 * SECOND_IN_MILLIS, 5), false);
        // Far away from FREQUENT boundary.
        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                createTimingSession(
                        now - (mQcConstants.WINDOW_SIZE_FREQUENT_MS -  HOUR_IN_MILLIS),
                        2 * MINUTE_IN_MILLIS, 5), false);
        // Overlap WORKING_SET boundary.
        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                createTimingSession(
                        now - (mQcConstants.WINDOW_SIZE_WORKING_MS + MINUTE_IN_MILLIS),
                        2 * MINUTE_IN_MILLIS, 5), false);
        // Close to ACTIVE boundary.
        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                createTimingSession(
                        now - (mQcConstants.WINDOW_SIZE_ACTIVE_MS -  MINUTE_IN_MILLIS),
                        2 * MINUTE_IN_MILLIS, 5), false);
        // Close to EXEMPTED boundary.
        mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                createTimingSession(
                        now - (mQcConstants.WINDOW_SIZE_EXEMPTED_MS -  MINUTE_IN_MILLIS),
                        2 * MINUTE_IN_MILLIS, 5), false);

        // No additional quota for the system installer when the app is in RARE, FREQUENT,
        // WORKING_SET or ACTIVE bucket.
        setStandbyBucket(RARE_INDEX);
        synchronized (mQuotaController.mLock) {
            assertEquals(30 * SECOND_IN_MILLIS,
                    mQuotaController.getRemainingExecutionTimeLocked(
                            SOURCE_USER_ID, SOURCE_PACKAGE));
            assertEquals(2 * MINUTE_IN_MILLIS,
                    mQuotaController.getTimeUntilQuotaConsumedLocked(
                            SOURCE_USER_ID, SOURCE_PACKAGE));
        }

        setStandbyBucket(FREQUENT_INDEX);
        synchronized (mQuotaController.mLock) {
            assertEquals(2 * MINUTE_IN_MILLIS,
                    mQuotaController.getRemainingExecutionTimeLocked(
                            SOURCE_USER_ID, SOURCE_PACKAGE));
            assertEquals(2 * MINUTE_IN_MILLIS,
                    mQuotaController.getTimeUntilQuotaConsumedLocked(
                            SOURCE_USER_ID, SOURCE_PACKAGE));
        }

        setStandbyBucket(WORKING_INDEX);
        synchronized (mQuotaController.mLock) {
            assertEquals(5 * MINUTE_IN_MILLIS,
                    mQuotaController.getRemainingExecutionTimeLocked(
                            SOURCE_USER_ID, SOURCE_PACKAGE));
            assertEquals(6 * MINUTE_IN_MILLIS,
                    mQuotaController.getTimeUntilQuotaConsumedLocked(
                            SOURCE_USER_ID, SOURCE_PACKAGE));
        }

        // ACTIVE window != allowed time.
        setStandbyBucket(ACTIVE_INDEX);
        synchronized (mQuotaController.mLock) {
            assertEquals(6 * MINUTE_IN_MILLIS,
                    mQuotaController.getRemainingExecutionTimeLocked(
                            SOURCE_USER_ID, SOURCE_PACKAGE));
            assertEquals(8 * MINUTE_IN_MILLIS,
                    mQuotaController.getTimeUntilQuotaConsumedLocked(
                            SOURCE_USER_ID, SOURCE_PACKAGE));
        }

        // Additional quota for the system installer when the app is in EXEMPTED bucket.
        // EXEMPTED window == allowed time.
        setStandbyBucket(EXEMPTED_INDEX);
        synchronized (mQuotaController.mLock) {
            assertEquals(18 * MINUTE_IN_MILLIS,
                    mQuotaController.getRemainingExecutionTimeLocked(
                            SOURCE_USER_ID, SOURCE_PACKAGE));
            assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 8 * MINUTE_IN_MILLIS,
                    mQuotaController.getTimeUntilQuotaConsumedLocked(
                            SOURCE_USER_ID, SOURCE_PACKAGE));
        }
    }

    /**
     * Test getTimeUntilQuotaConsumedLocked when the app is close to the max execution limit.
     */
@@ -4119,6 +4217,12 @@ public class QuotaControllerTest {
        assertEquals(85 * SECOND_IN_MILLIS, mQuotaController.getEJRewardNotificationSeenMs());
        assertEquals(84 * SECOND_IN_MILLIS, mQuotaController.getEJGracePeriodTempAllowlistMs());
        assertEquals(83 * SECOND_IN_MILLIS, mQuotaController.getEJGracePeriodTopAppMs());

        mSetFlagsRule.enableFlags(Flags.FLAG_ADDITIONAL_QUOTA_FOR_SYSTEM_INSTALLER);
        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS,
                6 * MINUTE_IN_MILLIS);
        assertEquals(6 * MINUTE_IN_MILLIS,
                mQuotaController.getAllowedTimePeriodAdditionInstallerMs());
    }

    @Test
@@ -4222,6 +4326,13 @@ public class QuotaControllerTest {
        assertEquals(0, mQuotaController.getEJGracePeriodTempAllowlistMs());
        assertEquals(0, mQuotaController.getEJGracePeriodTopAppMs());

        mSetFlagsRule.enableFlags(Flags.FLAG_ADDITIONAL_QUOTA_FOR_SYSTEM_INSTALLER);
        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS,
                -MINUTE_IN_MILLIS);
        assertEquals(0,
                mQuotaController.getAllowedTimePeriodAdditionInstallerMs());
        mSetFlagsRule.disableFlags(Flags.FLAG_ADDITIONAL_QUOTA_FOR_SYSTEM_INSTALLER);

        // Invalid configurations.
        // In_QUOTA_BUFFER should never be greater than ALLOWED_TIME_PER_PERIOD
        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
@@ -4237,9 +4348,18 @@ public class QuotaControllerTest {
                10 * MINUTE_IN_MILLIS);
        setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 5 * MINUTE_IN_MILLIS);

        assertTrue(mQuotaController.getInQuotaBufferMs()
        assertTrue(mQuotaController.getAllowedTimePeriodAdditionInstallerMs()
                <= mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]);

        mSetFlagsRule.enableFlags(Flags.FLAG_ADDITIONAL_QUOTA_FOR_SYSTEM_INSTALLER);
        // ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER should never be greater than
        // ALLOWED_TIME_PER_PERIOD.
        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS,
                 15 * MINUTE_IN_MILLIS);
        assertTrue(mQuotaController.getInQuotaBufferMs()
                <= mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]);
        mSetFlagsRule.disableFlags(Flags.FLAG_ADDITIONAL_QUOTA_FOR_SYSTEM_INSTALLER);

        // Test larger than a day. Controller should cap at one day.
        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
                25 * HOUR_IN_MILLIS);
@@ -4318,6 +4438,12 @@ public class QuotaControllerTest {
        assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getEJRewardNotificationSeenMs());
        assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJGracePeriodTempAllowlistMs());
        assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJGracePeriodTopAppMs());

        mSetFlagsRule.enableFlags(Flags.FLAG_ADDITIONAL_QUOTA_FOR_SYSTEM_INSTALLER);
        setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ADDITION_INSTALLER_MS,
                25 * HOUR_IN_MILLIS);
        assertEquals(0, mQuotaController.getAllowedTimePeriodAdditionInstallerMs());
        mSetFlagsRule.disableFlags(Flags.FLAG_ADDITIONAL_QUOTA_FOR_SYSTEM_INSTALLER);
    }

    /** Tests that TimingSessions aren't saved when the device is charging. */