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

Commit 0c511f99 authored by Kweku Adams's avatar Kweku Adams
Browse files

Grant additional quota to installers.

Give apps with the INSTALL_PACKAGES permission granted an additional 30
minutes of EJ quota.

Bug: 158150494
Bug: 171305774
Test: atest CtsJobSchedulerTestCases
Test: atest FrameworksMockingServicesTests:QuotaControllerTest
Change-Id: I53ac03553e7df8b15ce1770110523da39ee6c867
parent 45abbc30
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -793,6 +793,13 @@ public class JobSchedulerService extends com.android.server.SystemService
                        mDebuggableApps.remove(pkgName);
                    }
                }
            } else if (Intent.ACTION_USER_ADDED.equals(action)) {
                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
                synchronized (mLock) {
                    for (int c = 0; c < mControllers.size(); ++c) {
                        mControllers.get(c).onUserAddedLocked(userId);
                    }
                }
            } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
                if (DEBUG) {
@@ -1454,6 +1461,7 @@ public class JobSchedulerService extends com.android.server.SystemService
            getContext().registerReceiverAsUser(
                    mBroadcastReceiver, UserHandle.ALL, filter, null, null);
            final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
            userFilter.addAction(Intent.ACTION_USER_ADDED);
            getContext().registerReceiverAsUser(
                    mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
            try {
+97 −31
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;

import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -42,7 +43,9 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
import android.os.Handler;
@@ -118,6 +121,10 @@ public final class QuotaController extends StateController {
    private static final String ALARM_TAG_CLEANUP = "*job.cleanup*";
    private static final String ALARM_TAG_QUOTA_CHECK = "*job.quota_check*";

    private static final int SYSTEM_APP_CHECK_FLAGS =
            PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                    | PackageManager.GET_PERMISSIONS | PackageManager.MATCH_KNOWN_PACKAGES;

    /**
     * Standardize the output of userId-packageName combo.
     */
@@ -526,7 +533,9 @@ public final class QuotaController extends StateController {
            QcConstants.DEFAULT_EJ_LIMIT_RESTRICTED_MS
    };

    private long mEjLimitSpecialAdditionMs = QcConstants.DEFAULT_EJ_LIMIT_SPECIAL_ADDITION_MS;
    private long mEjLimitAdditionInstallerMs = QcConstants.DEFAULT_EJ_LIMIT_ADDITION_INSTALLER_MS;

    private long mEjLimitAdditionSpecialMs = QcConstants.DEFAULT_EJ_LIMIT_ADDITION_SPECIAL_MS;

    /**
     * The period of time used to calculate expedited job sessions. Apps can only have expedited job
@@ -560,9 +569,11 @@ public final class QuotaController extends StateController {

    private long mEJGracePeriodTopAppMs = QcConstants.DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS;

    /** The package verifier app. */
    @Nullable
    private String mPackageVerifier;
    /**
     * List of system apps with the {@link android.Manifest.permission#INSTALL_PACKAGES} permission
     * granted for each user.
     */
    private final SparseSetArray<String> mSystemInstallers = new SparseSetArray<>();

    /** An app has reached its quota. The message should contain a {@link Package} object. */
    @VisibleForTesting
@@ -627,11 +638,8 @@ public final class QuotaController extends StateController {

    @Override
    public void onSystemServicesReady() {
        String[] pkgNames = LocalServices.getService(PackageManagerInternal.class)
                .getKnownPackageNames(
                        PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM);
        synchronized (mLock) {
            mPackageVerifier = ArrayUtils.firstOrNull(pkgNames);
            cacheInstallerPackagesLocked(UserHandle.USER_SYSTEM);
        }
    }

@@ -730,6 +738,11 @@ public final class QuotaController extends StateController {
        mTopAppGraceCache.delete(uid);
    }

    @Override
    public void onUserAddedLocked(int userId) {
        cacheInstallerPackagesLocked(userId);
    }

    @Override
    public void onUserRemovedLocked(int userId) {
        mTrackedJobs.delete(userId);
@@ -741,6 +754,7 @@ public final class QuotaController extends StateController {
        mExecutionStatsCache.delete(userId);
        mEJStats.delete(userId);
        mUidToPackageCache.clear();
        mSystemInstallers.remove(userId);
    }

    /** Drop all historical stats and stop tracking any active sessions for the specified app. */
@@ -767,6 +781,22 @@ public final class QuotaController extends StateController {
        mEJStats.delete(userId, packageName);
    }

    private void cacheInstallerPackagesLocked(int userId) {
        final List<PackageInfo> packages = mContext.getPackageManager()
                .getInstalledPackagesAsUser(SYSTEM_APP_CHECK_FLAGS, userId);
        for (int i = packages.size() - 1; i >= 0; --i) {
            final PackageInfo pi = packages.get(i);
            final ApplicationInfo ai = pi.applicationInfo;
            final int idx = ArrayUtils.indexOf(
                    pi.requestedPermissions, Manifest.permission.INSTALL_PACKAGES);

            if (idx >= 0 && ai != null && PackageManager.PERMISSION_GRANTED
                    == mContext.checkPermission(Manifest.permission.INSTALL_PACKAGES, -1, ai.uid)) {
                mSystemInstallers.add(UserHandle.getUserId(ai.uid), pi.packageName);
            }
        }
    }

    private boolean isUidInForeground(int uid) {
        if (UserHandle.isCore(uid)) {
            return true;
@@ -962,7 +992,8 @@ public final class QuotaController extends StateController {
        if (quota.getStandbyBucketLocked() == NEVER_INDEX) {
            return 0;
        }
        final long limitMs = getEJLimitMsLocked(packageName, quota.getStandbyBucketLocked());
        final long limitMs =
                getEJLimitMsLocked(userId, packageName, quota.getStandbyBucketLocked());
        long remainingMs = limitMs - quota.getTallyLocked();

        // Stale sessions may still be factored into tally. Make sure they're removed.
@@ -1000,10 +1031,11 @@ public final class QuotaController extends StateController {
        return remainingMs - timer.getCurrentDuration(sElapsedRealtimeClock.millis());
    }

    private long getEJLimitMsLocked(@NonNull final String packageName, final int standbyBucket) {
    private long getEJLimitMsLocked(final int userId, @NonNull final String packageName,
            final int standbyBucket) {
        final long baseLimitMs = mEJLimitsMs[standbyBucket];
        if (packageName.equals(mPackageVerifier)) {
            return baseLimitMs + mEjLimitSpecialAdditionMs;
        if (mSystemInstallers.contains(userId, packageName)) {
            return baseLimitMs + mEjLimitAdditionInstallerMs;
        }
        return baseLimitMs;
    }
@@ -1116,7 +1148,8 @@ public final class QuotaController extends StateController {

        final long nowElapsed = sElapsedRealtimeClock.millis();
        ShrinkableDebits quota = getEJDebitsLocked(userId, packageName);
        final long limitMs = getEJLimitMsLocked(packageName, quota.getStandbyBucketLocked());
        final long limitMs =
                getEJLimitMsLocked(userId, packageName, quota.getStandbyBucketLocked());
        final long startWindowElapsed = Math.max(0, nowElapsed - mEJLimitWindowSizeMs);
        long remainingDeadSpaceMs = remainingExecutionTimeMs;
        // Total time looked at where a session wouldn't be phasing out.
@@ -1743,7 +1776,8 @@ public final class QuotaController extends StateController {
            inRegularQuotaTimeElapsed = inQuotaTimeElapsed;
        }
        if (remainingEJQuota <= 0) {
            final long limitMs = getEJLimitMsLocked(packageName, standbyBucket) - mQuotaBufferMs;
            final long limitMs =
                    getEJLimitMsLocked(userId, packageName, standbyBucket) - mQuotaBufferMs;
            long sumMs = 0;
            final Timer ejTimer = mEJPkgTimers.get(userId, packageName);
            if (ejTimer != null && ejTimer.isActive()) {
@@ -3029,8 +3063,11 @@ public final class QuotaController extends StateController {
        static final String KEY_EJ_LIMIT_RESTRICTED_MS =
                QC_CONSTANT_PREFIX + "ej_limit_restricted_ms";
        @VisibleForTesting
        static final String KEY_EJ_LIMIT_SPECIAL_ADDITION_MS =
                QC_CONSTANT_PREFIX + "ej_limit_special_addition_ms";
        static final String KEY_EJ_LIMIT_ADDITION_SPECIAL_MS =
                QC_CONSTANT_PREFIX + "ej_limit_addition_special_ms";
        @VisibleForTesting
        static final String KEY_EJ_LIMIT_ADDITION_INSTALLER_MS =
                QC_CONSTANT_PREFIX + "ej_limit_addition_installer_ms";
        @VisibleForTesting
        static final String KEY_EJ_WINDOW_SIZE_MS =
                QC_CONSTANT_PREFIX + "ej_window_size_ms";
@@ -3098,7 +3135,8 @@ public final class QuotaController extends StateController {
        private static final long DEFAULT_EJ_LIMIT_FREQUENT_MS = 10 * MINUTE_IN_MILLIS;
        private static final long DEFAULT_EJ_LIMIT_RARE_MS = DEFAULT_EJ_LIMIT_FREQUENT_MS;
        private static final long DEFAULT_EJ_LIMIT_RESTRICTED_MS = 5 * MINUTE_IN_MILLIS;
        private static final long DEFAULT_EJ_LIMIT_SPECIAL_ADDITION_MS = 30 * MINUTE_IN_MILLIS;
        private static final long DEFAULT_EJ_LIMIT_ADDITION_SPECIAL_MS = 15 * MINUTE_IN_MILLIS;
        private static final long DEFAULT_EJ_LIMIT_ADDITION_INSTALLER_MS = 30 * MINUTE_IN_MILLIS;
        private static final long DEFAULT_EJ_WINDOW_SIZE_MS = 24 * HOUR_IN_MILLIS;
        private static final long DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS = 30 * SECOND_IN_MILLIS;
        private static final long DEFAULT_EJ_REWARD_TOP_APP_MS = 10 * SECOND_IN_MILLIS;
@@ -3303,7 +3341,13 @@ public final class QuotaController extends StateController {
        /**
         * How much additional EJ quota special, critical apps should get.
         */
        public long EJ_LIMIT_SPECIAL_ADDITION_MS = DEFAULT_EJ_LIMIT_SPECIAL_ADDITION_MS;
        public long EJ_LIMIT_ADDITION_SPECIAL_MS = DEFAULT_EJ_LIMIT_ADDITION_SPECIAL_MS;

        /**
         * How much additional EJ quota system installers (with the INSTALL_PACKAGES permission)
         * should get.
         */
        public long EJ_LIMIT_ADDITION_INSTALLER_MS = DEFAULT_EJ_LIMIT_ADDITION_INSTALLER_MS;

        /**
         * The period of time used to calculate expedited job sessions. Apps can only have expedited
@@ -3369,7 +3413,8 @@ public final class QuotaController extends StateController {
                case KEY_EJ_LIMIT_FREQUENT_MS:
                case KEY_EJ_LIMIT_RARE_MS:
                case KEY_EJ_LIMIT_RESTRICTED_MS:
                case KEY_EJ_LIMIT_SPECIAL_ADDITION_MS:
                case KEY_EJ_LIMIT_ADDITION_SPECIAL_MS:
                case KEY_EJ_LIMIT_ADDITION_INSTALLER_MS:
                case KEY_EJ_WINDOW_SIZE_MS:
                    updateEJLimitConstantsLocked();
                    break;
@@ -3704,7 +3749,8 @@ public final class QuotaController extends StateController {
                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                    KEY_EJ_LIMIT_ACTIVE_MS, KEY_EJ_LIMIT_WORKING_MS,
                    KEY_EJ_LIMIT_FREQUENT_MS, KEY_EJ_LIMIT_RARE_MS,
                    KEY_EJ_LIMIT_RESTRICTED_MS, KEY_EJ_LIMIT_SPECIAL_ADDITION_MS,
                    KEY_EJ_LIMIT_RESTRICTED_MS, KEY_EJ_LIMIT_ADDITION_SPECIAL_MS,
                    KEY_EJ_LIMIT_ADDITION_INSTALLER_MS,
                    KEY_EJ_WINDOW_SIZE_MS);
            EJ_LIMIT_ACTIVE_MS = properties.getLong(
                    KEY_EJ_LIMIT_ACTIVE_MS, DEFAULT_EJ_LIMIT_ACTIVE_MS);
@@ -3716,8 +3762,10 @@ public final class QuotaController extends StateController {
                    KEY_EJ_LIMIT_RARE_MS, DEFAULT_EJ_LIMIT_RARE_MS);
            EJ_LIMIT_RESTRICTED_MS = properties.getLong(
                    KEY_EJ_LIMIT_RESTRICTED_MS, DEFAULT_EJ_LIMIT_RESTRICTED_MS);
            EJ_LIMIT_SPECIAL_ADDITION_MS = properties.getLong(
                    KEY_EJ_LIMIT_SPECIAL_ADDITION_MS, DEFAULT_EJ_LIMIT_SPECIAL_ADDITION_MS);
            EJ_LIMIT_ADDITION_INSTALLER_MS = properties.getLong(
                    KEY_EJ_LIMIT_ADDITION_INSTALLER_MS, DEFAULT_EJ_LIMIT_ADDITION_INSTALLER_MS);
            EJ_LIMIT_ADDITION_SPECIAL_MS = properties.getLong(
                    KEY_EJ_LIMIT_ADDITION_SPECIAL_MS, DEFAULT_EJ_LIMIT_ADDITION_SPECIAL_MS);
            EJ_WINDOW_SIZE_MS = properties.getLong(
                    KEY_EJ_WINDOW_SIZE_MS, DEFAULT_EJ_WINDOW_SIZE_MS);

@@ -3763,11 +3811,17 @@ public final class QuotaController extends StateController {
                mEJLimitsMs[RESTRICTED_INDEX] = newRestrictedLimitMs;
                mShouldReevaluateConstraints = true;
            }
            // The addition must be in the range [0 minutes, window size - active limit].
            long newSpecialAdditionMs = Math.max(0,
                    Math.min(newWindowSizeMs - newActiveLimitMs, EJ_LIMIT_SPECIAL_ADDITION_MS));
            if (mEjLimitSpecialAdditionMs != newSpecialAdditionMs) {
                mEjLimitSpecialAdditionMs = newSpecialAdditionMs;
            // The additions must be in the range [0 minutes, window size - active limit].
            long newAdditionInstallerMs = Math.max(0,
                    Math.min(newWindowSizeMs - newActiveLimitMs, EJ_LIMIT_ADDITION_INSTALLER_MS));
            if (mEjLimitAdditionInstallerMs != newAdditionInstallerMs) {
                mEjLimitAdditionInstallerMs = newAdditionInstallerMs;
                mShouldReevaluateConstraints = true;
            }
            long newAdditionSpecialMs = Math.max(0,
                    Math.min(newWindowSizeMs - newActiveLimitMs, EJ_LIMIT_ADDITION_SPECIAL_MS));
            if (mEjLimitAdditionSpecialMs != newAdditionSpecialMs) {
                mEjLimitAdditionSpecialMs = newAdditionSpecialMs;
                mShouldReevaluateConstraints = true;
            }
        }
@@ -3808,7 +3862,8 @@ public final class QuotaController extends StateController {
            pw.print(KEY_EJ_LIMIT_FREQUENT_MS, EJ_LIMIT_FREQUENT_MS).println();
            pw.print(KEY_EJ_LIMIT_RARE_MS, EJ_LIMIT_RARE_MS).println();
            pw.print(KEY_EJ_LIMIT_RESTRICTED_MS, EJ_LIMIT_RESTRICTED_MS).println();
            pw.print(KEY_EJ_LIMIT_SPECIAL_ADDITION_MS, EJ_LIMIT_SPECIAL_ADDITION_MS).println();
            pw.print(KEY_EJ_LIMIT_ADDITION_INSTALLER_MS, EJ_LIMIT_ADDITION_INSTALLER_MS).println();
            pw.print(KEY_EJ_LIMIT_ADDITION_SPECIAL_MS, EJ_LIMIT_ADDITION_SPECIAL_MS).println();
            pw.print(KEY_EJ_WINDOW_SIZE_MS, EJ_WINDOW_SIZE_MS).println();
            pw.print(KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, EJ_TOP_APP_TIME_CHUNK_SIZE_MS).println();
            pw.print(KEY_EJ_REWARD_TOP_APP_MS, EJ_REWARD_TOP_APP_MS).println();
@@ -3945,8 +4000,13 @@ public final class QuotaController extends StateController {
    }

    @VisibleForTesting
    long getEjLimitSpecialAdditionMs() {
        return mEjLimitSpecialAdditionMs;
    long getEjLimitAdditionInstallerMs() {
        return mEjLimitAdditionInstallerMs;
    }

    @VisibleForTesting
    long getEjLimitAdditionSpecialMs() {
        return mEjLimitAdditionSpecialMs;
    }

    @VisibleForTesting
@@ -4067,6 +4127,12 @@ public final class QuotaController extends StateController {
        pw.decreaseIndent();
        pw.println();

        pw.println("Special apps:");
        pw.increaseIndent();
        pw.print("System installers", mSystemInstallers.toString());
        pw.decreaseIndent();

        pw.println();
        mTrackedJobs.forEach((jobs) -> {
            for (int j = 0; j < jobs.size(); j++) {
                final JobStatus js = jobs.valueAt(j);
+4 −0
Original line number Diff line number Diff line
@@ -103,6 +103,10 @@ public abstract class StateController {
    public void onAppRemovedLocked(String packageName, int uid) {
    }

    /** Called when a user is added to the device. */
    public void onUserAddedLocked(int userId) {
    }

    /** Called when a user is removed from the device. */
    public void onUserRemovedLocked(int userId) {
    }
+31 −10
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@ import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import android.Manifest;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
@@ -66,7 +67,10 @@ import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ServiceInfo;
import android.os.BatteryManager;
@@ -142,6 +146,8 @@ public class QuotaControllerTest {
    @Mock
    private JobSchedulerService mJobSchedulerService;
    @Mock
    private PackageManager mPackageManager;
    @Mock
    private PackageManagerInternal mPackageManagerInternal;
    @Mock
    private PowerAllowlistInternal mPowerAllowlistInternal;
@@ -201,6 +207,8 @@ public class QuotaControllerTest {
                        -> mDeviceConfigPropertiesBuilder.build())
                .when(() -> DeviceConfig.getProperties(
                        eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER), ArgumentMatchers.<String>any()));
        // Used in QuotaController.onSystemServicesReady
        when(mContext.getPackageManager()).thenReturn(mPackageManager);

        // Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
        // in the past, and QuotaController sometimes floors values at 0, so if the test time
@@ -2704,7 +2712,8 @@ public class QuotaControllerTest {
        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 1 * HOUR_IN_MILLIS);
        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 30 * MINUTE_IN_MILLIS);
        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 27 * MINUTE_IN_MILLIS);
        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_SPECIAL_ADDITION_MS, 10 * HOUR_IN_MILLIS);
        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ADDITION_INSTALLER_MS, 7 * HOUR_IN_MILLIS);
        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ADDITION_SPECIAL_MS, 10 * HOUR_IN_MILLIS);
        setDeviceConfigLong(QcConstants.KEY_EJ_WINDOW_SIZE_MS, 12 * HOUR_IN_MILLIS);
        setDeviceConfigLong(QcConstants.KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, 10 * MINUTE_IN_MILLIS);
        setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, 87 * SECOND_IN_MILLIS);
@@ -2745,7 +2754,8 @@ public class QuotaControllerTest {
        assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]);
        assertEquals(30 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[RARE_INDEX]);
        assertEquals(27 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[RESTRICTED_INDEX]);
        assertEquals(10 * HOUR_IN_MILLIS, mQuotaController.getEjLimitSpecialAdditionMs());
        assertEquals(7 * HOUR_IN_MILLIS, mQuotaController.getEjLimitAdditionInstallerMs());
        assertEquals(10 * HOUR_IN_MILLIS, mQuotaController.getEjLimitAdditionSpecialMs());
        assertEquals(12 * HOUR_IN_MILLIS, mQuotaController.getEJLimitWindowSizeMs());
        assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getEJTopAppTimeChunkSizeMs());
        assertEquals(87 * SECOND_IN_MILLIS, mQuotaController.getEJRewardTopAppMs());
@@ -2786,7 +2796,8 @@ public class QuotaControllerTest {
        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, -1);
        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, -1);
        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, -1);
        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_SPECIAL_ADDITION_MS, -1);
        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ADDITION_INSTALLER_MS, -1);
        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ADDITION_SPECIAL_MS, -1);
        setDeviceConfigLong(QcConstants.KEY_EJ_WINDOW_SIZE_MS, -1);
        setDeviceConfigLong(QcConstants.KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, -1);
        setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, -1);
@@ -2823,7 +2834,8 @@ public class QuotaControllerTest {
        assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]);
        assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[RARE_INDEX]);
        assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[RESTRICTED_INDEX]);
        assertEquals(0, mQuotaController.getEjLimitSpecialAdditionMs());
        assertEquals(0, mQuotaController.getEjLimitAdditionInstallerMs());
        assertEquals(0, mQuotaController.getEjLimitAdditionSpecialMs());
        assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJLimitWindowSizeMs());
        assertEquals(1, mQuotaController.getEJTopAppTimeChunkSizeMs());
        assertEquals(10 * SECOND_IN_MILLIS, mQuotaController.getEJRewardTopAppMs());
@@ -2858,7 +2870,8 @@ public class QuotaControllerTest {
        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 25 * HOUR_IN_MILLIS);
        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 25 * HOUR_IN_MILLIS);
        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 25 * HOUR_IN_MILLIS);
        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_SPECIAL_ADDITION_MS, 25 * HOUR_IN_MILLIS);
        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ADDITION_INSTALLER_MS, 25 * HOUR_IN_MILLIS);
        setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ADDITION_SPECIAL_MS, 25 * HOUR_IN_MILLIS);
        setDeviceConfigLong(QcConstants.KEY_EJ_WINDOW_SIZE_MS, 25 * HOUR_IN_MILLIS);
        setDeviceConfigLong(QcConstants.KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, 25 * HOUR_IN_MILLIS);
        setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, 25 * HOUR_IN_MILLIS);
@@ -2885,7 +2898,8 @@ public class QuotaControllerTest {
        assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]);
        assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[RARE_INDEX]);
        assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[RESTRICTED_INDEX]);
        assertEquals(0, mQuotaController.getEjLimitSpecialAdditionMs());
        assertEquals(0, mQuotaController.getEjLimitAdditionInstallerMs());
        assertEquals(0, mQuotaController.getEjLimitAdditionSpecialMs());
        assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitWindowSizeMs());
        assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJTopAppTimeChunkSizeMs());
        assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJRewardTopAppMs());
@@ -3957,9 +3971,16 @@ public class QuotaControllerTest {
    }

    @Test
    public void testGetRemainingEJExecutionTimeLocked_SpecialApp() {
        doReturn(new String[]{SOURCE_PACKAGE}).when(mPackageManagerInternal)
                .getKnownPackageNames(eq(PackageManagerInternal.PACKAGE_VERIFIER), anyInt());
    public void testGetRemainingEJExecutionTimeLocked_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();
@@ -3980,7 +4001,7 @@ public class QuotaControllerTest {
            setStandbyBucket(i);
            assertEquals("Got wrong remaining EJ execution time for bucket #" + i,
                    i == NEVER_INDEX ? 0
                            : (limits[i] + mQuotaController.getEjLimitSpecialAdditionMs()
                            : (limits[i] + mQuotaController.getEjLimitAdditionInstallerMs()
                                    - 5 * MINUTE_IN_MILLIS),
                    mQuotaController.getRemainingEJExecutionTimeLocked(
                            SOURCE_USER_ID, SOURCE_PACKAGE));