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

Commit a4cb64d9 authored by Kweku Adams's avatar Kweku Adams
Browse files

Make job execution limits flexible.

1. Change the execution timeout to allow all jobs to run for longer than
   10 minutes if the app has sufficient quota and JobScheduler is quiet.
   If jobs end up running longer than 10 minutes, their execution is
   capped at 30 minutes or their remaining quota, whichever is less.
   Overrunning jobs will also be stopped if there are pending jobs
   waiting for an execution slot.
2. Expedited jobs will have a minimum execution timeout of 3 minutes.
   The above behavior with flexible limits also apply to EJs. However,
   EJs will not be allowed to overrun if the device is in Doze or
   battery saver. The limit is flexible, but EJs for apps in the
   RESTRICTED bucket cannot have a minimum timeout greater than 5
   minutes (ie. if we change the minimum timeout for EJs to be greater
   than 5 minutes, RESTRICTED EJs will have their minimum set to 5
   minutes).
   Since TOP-started jobs and jobs running while the app is FGS don't
   count towards quota, their calculations are handled separately. Those
   jobs currently have a minimum guarantee of 15 minutes (50% of
   ACTIVE/WORKING limits = 15 minutes). The gist of the allowance is:
      min runtime = 5 minutes
      TOP: max(min runtime, 50% ACTIVE limit, remaining quota)
      FGS: max(min runtime, 50% WORKING limit, remaining quota)
      Else: max(min runtime, remaining quota)
3. Update documentation to clarify execution limit differences across
   versions.

Bug: 19536175
Bug: 171305774
Test: atest CtsJobSchedulerTestCases
Test: atest FrameworksMockingServicesTests:ConnectivityControllerTest
Test: atest FrameworksMockingServicesTests:JobSchedulerServiceTest
Test: atest FrameworksMockingServicesTests:QuotaControllerTest
Test: atest FrameworksServicesTests:PrioritySchedulingTest
Test: atest FrameworksServicesTests:WorkCountTrackerTest
Test: atest FrameworksServicesTests:WorkTypeConfigTest
Change-Id: I21674fdcddfa849581198a1a9133c431fb7cfa46
parent f3efab13
Loading
Loading
Loading
Loading
+19 −2
Original line number Diff line number Diff line
@@ -59,6 +59,12 @@ import java.util.Objects;
 * constraint on the JobInfo object that you are creating. Otherwise, the builder would throw an
 * exception when building. From Android version {@link Build.VERSION_CODES#Q} and onwards, it is
 * valid to schedule jobs with no constraints.
 * <p> In Android version {@link Build.VERSION_CODES#LOLLIPOP}, jobs had a maximum execution time
 * of one minute. Starting with Android version {@link Build.VERSION_CODES#M} and ending with
 * Android version {@link Build.VERSION_CODES#R}, jobs had a maximum execution time of 10 minutes.
 * Starting from Android version {@link Build.VERSION_CODES#S}, jobs will still be stopped after
 * 10 minutes if the system is busy or needs the resources, but if not, jobs may continue running
 * longer than 10 minutes.
 */
public class JobInfo implements Parcelable {
    private static String TAG = "JobInfo";
@@ -1461,11 +1467,13 @@ public class JobInfo implements Parcelable {
         * possible with stronger guarantees than regular jobs. These "expedited" jobs will:
         * <ol>
         *     <li>Run as soon as possible</li>
         *     <li>Be exempted from Doze and battery saver restrictions</li>
         *     <li>Be less restricted during Doze and battery saver</li>
         *     <li>Have network access</li>
         *     <li>Less likely to be killed than regular jobs</li>
         *     <li>Be less likely to be killed than regular jobs</li>
         *     <li>Be subject to background location throttling</li>
         * </ol>
         *
         * <p>
         * Since these jobs have stronger guarantees than regular jobs, they will be subject to
         * stricter quotas. As long as an app has available expedited quota, jobs scheduled with
         * this set to true will run with these guarantees. If an app has run out of available
@@ -1475,9 +1483,18 @@ public class JobInfo implements Parcelable {
         * will immediately return {@link JobScheduler#RESULT_FAILURE} if the app does not have
         * available quota (and the job will not be successfully scheduled).
         *
         * <p>
         * Expedited jobs may only set network, storage-not-low, and persistence constraints.
         * No other constraints are allowed.
         *
         * <p>
         * Assuming all constraints remain satisfied (including ideal system load conditions),
         * expedited jobs are guaranteed to have a minimum allowed runtime of 1 minute. If your
         * app has remaining expedited job quota, then the expedited job <i>may</i> potentially run
         * longer until remaining quota is used up. Just like with regular jobs, quota is not
         * consumed while the app is on top and visible to the user.
         *
         * <p>
         * Note: Even though expedited jobs are meant to run as soon as possible, they may be
         * deferred if the system is under heavy load or requested constraints are not satisfied.
         *
+171 −13
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.UserSwitchObserver;
@@ -80,6 +81,8 @@ class JobConcurrencyManager {
    static final int WORK_TYPE_BGUSER = 1 << 3;
    @VisibleForTesting
    static final int NUM_WORK_TYPES = 4;
    private static final int ALL_WORK_TYPES =
            WORK_TYPE_TOP | WORK_TYPE_EJ | WORK_TYPE_BG | WORK_TYPE_BGUSER;

    @IntDef(prefix = {"WORK_TYPE_"}, flag = true, value = {
            WORK_TYPE_NONE,
@@ -92,6 +95,23 @@ class JobConcurrencyManager {
    public @interface WorkType {
    }

    private static String workTypeToString(@WorkType int workType) {
        switch (workType) {
            case WORK_TYPE_NONE:
                return "NONE";
            case WORK_TYPE_TOP:
                return "TOP";
            case WORK_TYPE_EJ:
                return "EJ";
            case WORK_TYPE_BG:
                return "BG";
            case WORK_TYPE_BGUSER:
                return "BGUSER";
            default:
                return "WORK(" + workType + ")";
        }
    }

    private final Object mLock;
    private final JobSchedulerService mService;
    private final Context mContext;
@@ -182,10 +202,16 @@ class JobConcurrencyManager {

    int[] mRecycledWorkTypeForContext = new int[MAX_JOB_CONTEXTS_COUNT];

    String[] mRecycledPreemptReasonForContext = new String[MAX_JOB_CONTEXTS_COUNT];

    String[] mRecycledShouldStopJobReason = new String[MAX_JOB_CONTEXTS_COUNT];

    private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>();

    private final WorkCountTracker mWorkCountTracker = new WorkCountTracker();

    private WorkTypeConfig mWorkTypeConfig = CONFIG_LIMITS_SCREEN_OFF.normal;

    /** Wait for this long after screen off before adjusting the job concurrency. */
    private long mScreenOffAdjustmentDelayMs = DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS;

@@ -353,23 +379,22 @@ class JobConcurrencyManager {
        final WorkConfigLimitsPerMemoryTrimLevel workConfigs = mEffectiveInteractiveState
                ? CONFIG_LIMITS_SCREEN_ON : CONFIG_LIMITS_SCREEN_OFF;

        WorkTypeConfig workTypeConfig;
        switch (mLastMemoryTrimLevel) {
            case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
                workTypeConfig = workConfigs.moderate;
                mWorkTypeConfig = workConfigs.moderate;
                break;
            case ProcessStats.ADJ_MEM_FACTOR_LOW:
                workTypeConfig = workConfigs.low;
                mWorkTypeConfig = workConfigs.low;
                break;
            case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
                workTypeConfig = workConfigs.critical;
                mWorkTypeConfig = workConfigs.critical;
                break;
            default:
                workTypeConfig = workConfigs.normal;
                mWorkTypeConfig = workConfigs.normal;
                break;
        }

        mWorkCountTracker.setConfig(workTypeConfig);
        mWorkCountTracker.setConfig(mWorkTypeConfig);
    }

    /**
@@ -401,13 +426,20 @@ class JobConcurrencyManager {
        boolean[] slotChanged = mRecycledSlotChanged;
        int[] preferredUidForContext = mRecycledPreferredUidForContext;
        int[] workTypeForContext = mRecycledWorkTypeForContext;
        String[] preemptReasonForContext = mRecycledPreemptReasonForContext;
        String[] shouldStopJobReason = mRecycledShouldStopJobReason;

        updateCounterConfigLocked();
        // Reset everything since we'll re-evaluate the current state.
        mWorkCountTracker.resetCounts();

        // Update the priorities of jobs that aren't running, and also count the pending work types.
        // Do this before the following loop to hopefully reduce the cost of
        // shouldStopRunningJobLocked().
        updateNonRunningPriorities(pendingJobs, true);

        for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
            final JobServiceContext js = mService.mActiveServices.get(i);
            final JobServiceContext js = activeServices.get(i);
            final JobStatus status = js.getRunningJobLocked();

            if ((contextIdToJobMap[i] = status) != null) {
@@ -417,14 +449,13 @@ class JobConcurrencyManager {

            slotChanged[i] = false;
            preferredUidForContext[i] = js.getPreferredUid();
            preemptReasonForContext[i] = null;
            shouldStopJobReason[i] = shouldStopRunningJobLocked(js);
        }
        if (DEBUG) {
            Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
        }

        // Next, update the job priorities, and also count the pending FG / BG jobs.
        updateNonRunningPriorities(pendingJobs, true);

        mWorkCountTracker.onCountDone();

        for (int i = 0; i < pendingJobs.size(); i++) {
@@ -434,8 +465,6 @@ class JobConcurrencyManager {
                continue;
            }

            // TODO(171305774): make sure HPJs aren't pre-empted and add dedicated contexts for them

            // Find an available slot for nextPending. The context should be available OR
            // it should have lowest priority among all running jobs
            // (sharing the same Uid as nextPending)
@@ -444,6 +473,9 @@ class JobConcurrencyManager {
            int allWorkTypes = getJobWorkTypes(nextPending);
            int workType = mWorkCountTracker.canJobStart(allWorkTypes);
            boolean startingJob = false;
            String preemptReason = null;
            // TODO(141645789): rewrite this to look at empty contexts first so we don't
            // unnecessarily preempt
            for (int j = 0; j < MAX_JOB_CONTEXTS_COUNT; j++) {
                JobStatus job = contextIdToJobMap[j];
                int preferredUid = preferredUidForContext[j];
@@ -464,6 +496,15 @@ class JobConcurrencyManager {
                    continue;
                }
                if (job.getUid() != nextPending.getUid()) {
                    // Maybe stop the job if it has had its day in the sun.
                    final String reason = shouldStopJobReason[j];
                    if (reason != null && mWorkCountTracker.canJobStart(allWorkTypes,
                            activeServices.get(j).getRunningJobWorkType()) != WORK_TYPE_NONE) {
                        // Right now, the way the code is set up, we don't need to explicitly
                        // assign the new job to this context since we'll reassign when the
                        // preempted job finally stops.
                        preemptReason = reason;
                    }
                    continue;
                }

@@ -477,6 +518,7 @@ class JobConcurrencyManager {
                    // the lowest-priority running job
                    minPriorityForPreemption = jobPriority;
                    selectedContextId = j;
                    preemptReason = "higher priority job found";
                    // In this case, we're just going to preempt a low priority job, we're not
                    // actually starting a job, so don't set startingJob.
                }
@@ -484,6 +526,7 @@ class JobConcurrencyManager {
            if (selectedContextId != -1) {
                contextIdToJobMap[selectedContextId] = nextPending;
                slotChanged[selectedContextId] = true;
                preemptReasonForContext[selectedContextId] = preemptReason;
            }
            if (startingJob) {
                // Increase the counters when we're going to start a job.
@@ -509,7 +552,7 @@ class JobConcurrencyManager {
                                + activeServices.get(i).getRunningJobLocked());
                    }
                    // preferredUid will be set to uid of currently running job.
                    activeServices.get(i).preemptExecutingJobLocked();
                    activeServices.get(i).preemptExecutingJobLocked(preemptReasonForContext[i]);
                    preservePreferredUid = true;
                } else {
                    final JobStatus pendingJob = contextIdToJobMap[i];
@@ -692,6 +735,91 @@ class JobConcurrencyManager {
        noteConcurrency();
    }

    /**
     * Returns {@code null} if the job can continue running and a non-null String if the job should
     * be stopped. The non-null String details the reason for stopping the job. A job will generally
     * be stopped if there similar job types waiting to be run and stopping this job would allow
     * another job to run, or if system state suggests the job should stop.
     */
    @Nullable
    String shouldStopRunningJobLocked(@NonNull JobServiceContext context) {
        final JobStatus js = context.getRunningJobLocked();
        if (js == null) {
            // This can happen when we try to assign newly found pending jobs to contexts.
            return null;
        }

        if (context.isWithinExecutionGuaranteeTime()) {
            return null;
        }

        // Update config in case memory usage has changed significantly.
        updateCounterConfigLocked();

        @WorkType final int workType = context.getRunningJobWorkType();

        // We're over the minimum guaranteed runtime. Stop the job if we're over config limits or
        // there are pending jobs that could replace this one.
        if (mRunningJobs.size() > mWorkTypeConfig.getMaxTotal()
                || mWorkCountTracker.isOverTypeLimit(workType)) {
            return "too many jobs running";
        }

        final List<JobStatus> pendingJobs = mService.mPendingJobs;
        final int numPending = pendingJobs.size();
        if (numPending == 0) {
            // All quiet. We can let this job run to completion.
            return null;
        }

        // Only expedited jobs can replace expedited jobs.
        if (js.shouldTreatAsExpeditedJob()) {
            // Keep fg/bg user distinction.
            if (workType == WORK_TYPE_BGUSER) {
                // For now, let any bg user job replace a bg user expedited job.
                // TODO: limit to ej once we have dedicated bg user ej slots.
                if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_BGUSER) > 0) {
                    return "blocking " + workTypeToString(workType) + " queue";
                }
            } else {
                if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0) {
                    return "blocking " + workTypeToString(workType) + " queue";
                }
            }

            if (mPowerManager.isPowerSaveMode()) {
                return "battery saver";
            }
            if (mPowerManager.isDeviceIdleMode()) {
                return "deep doze";
            }
        }

        // Easy check. If there are pending jobs of the same work type, then we know that
        // something will replace this.
        if (mWorkCountTracker.getPendingJobCount(workType) > 0) {
            return "blocking " + workTypeToString(workType) + " queue";
        }

        // Harder check. We need to see if a different work type can replace this job.
        int remainingWorkTypes = ALL_WORK_TYPES;
        for (int i = 0; i < numPending; ++i) {
            final JobStatus pending = pendingJobs.get(i);
            final int workTypes = getJobWorkTypes(pending);
            if ((workTypes & remainingWorkTypes) > 0
                    && mWorkCountTracker.canJobStart(workTypes, workType) != WORK_TYPE_NONE) {
                return "blocking other pending jobs";
            }

            remainingWorkTypes = remainingWorkTypes & ~workTypes;
            if (remainingWorkTypes == 0) {
                break;
            }
        }

        return null;
    }

    @GuardedBy("mLock")
    private String printPendingQueueLocked() {
        StringBuilder s = new StringBuilder("Pending queue: ");
@@ -1362,10 +1490,40 @@ class JobConcurrencyManager {
            return WORK_TYPE_NONE;
        }

        int canJobStart(int workTypes, @WorkType int replacingWorkType) {
            final boolean changedNums;
            int oldNumRunning = mNumRunningJobs.get(replacingWorkType);
            if (replacingWorkType != WORK_TYPE_NONE && oldNumRunning > 0) {
                mNumRunningJobs.put(replacingWorkType, oldNumRunning - 1);
                // Lazy implementation to avoid lots of processing. Best way would be to go
                // through the whole process of adjusting reservations, but the processing cost
                // is likely not worth it.
                mNumUnspecializedRemaining++;
                changedNums = true;
            } else {
                changedNums = false;
            }

            final int ret = canJobStart(workTypes);
            if (changedNums) {
                mNumRunningJobs.put(replacingWorkType, oldNumRunning);
                mNumUnspecializedRemaining--;
            }
            return ret;
        }

        int getPendingJobCount(@WorkType final int workType) {
            return mNumPendingJobs.get(workType, 0);
        }

        int getRunningJobCount(@WorkType final int workType) {
            return mNumRunningJobs.get(workType, 0);
        }

        boolean isOverTypeLimit(@WorkType final int workType) {
            return getRunningJobCount(workType) > mConfigAbsoluteMaxSlots.get(workType);
        }

        public String toString() {
            StringBuilder sb = new StringBuilder();

+73 −3
Original line number Diff line number Diff line
@@ -339,6 +339,7 @@ public class JobSchedulerService extends com.android.server.SystemService
        public void onPropertiesChanged(DeviceConfig.Properties properties) {
            boolean apiQuotaScheduleUpdated = false;
            boolean concurrencyUpdated = false;
            boolean runtimeUpdated = false;
            for (int controller = 0; controller < mControllers.size(); controller++) {
                final StateController sc = mControllers.get(controller);
                sc.prepareForUpdatedConstantsLocked();
@@ -377,6 +378,14 @@ public class JobSchedulerService extends com.android.server.SystemService
                        case Constants.KEY_CONN_PREFETCH_RELAX_FRAC:
                            mConstants.updateConnectivityConstantsLocked();
                            break;
                        case Constants.KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS:
                        case Constants.KEY_RUNTIME_MIN_GUARANTEE_MS:
                        case Constants.KEY_RUNTIME_MIN_EJ_GUARANTEE_MS:
                            if (!runtimeUpdated) {
                                mConstants.updateRuntimeConstantsLocked();
                                runtimeUpdated = true;
                            }
                            break;
                        default:
                            if (name.startsWith(JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY)
                                    && !concurrencyUpdated) {
@@ -432,6 +441,11 @@ public class JobSchedulerService extends com.android.server.SystemService
        private static final String KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT =
                "aq_schedule_return_failure";

        private static final String KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS =
                "runtime_free_quota_max_limit_ms";
        private static final String KEY_RUNTIME_MIN_GUARANTEE_MS = "runtime_min_guarantee_ms";
        private static final String KEY_RUNTIME_MIN_EJ_GUARANTEE_MS = "runtime_min_ej_guarantee_ms";

        private static final int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT = 5;
        private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS;
        private static final float DEFAULT_HEAVY_USE_FACTOR = .9f;
@@ -445,6 +459,12 @@ public class JobSchedulerService extends com.android.server.SystemService
        private static final long DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS = MINUTE_IN_MILLIS;
        private static final boolean DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION = true;
        private static final boolean DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
        @VisibleForTesting
        public static final long DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS = 30 * MINUTE_IN_MILLIS;
        @VisibleForTesting
        public static final long DEFAULT_RUNTIME_MIN_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS;
        @VisibleForTesting
        public static final long DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS = 3 * MINUTE_IN_MILLIS;

        /**
         * Minimum # of non-ACTIVE jobs for which the JMS will be happy running some work early.
@@ -509,6 +529,19 @@ public class JobSchedulerService extends com.android.server.SystemService
        public boolean API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT =
                DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT;

        /** The maximum amount of time we will let a job run for when quota is "free". */
        public long RUNTIME_FREE_QUOTA_MAX_LIMIT_MS = DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;

        /**
         * The minimum amount of time we try to guarantee regular jobs will run for.
         */
        public long RUNTIME_MIN_GUARANTEE_MS = DEFAULT_RUNTIME_MIN_GUARANTEE_MS;

        /**
         * The minimum amount of time we try to guarantee EJs will run for.
         */
        public long RUNTIME_MIN_EJ_GUARANTEE_MS = DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS;

        private void updateBatchingConstantsLocked() {
            MIN_READY_NON_ACTIVE_JOBS_COUNT = DeviceConfig.getInt(
                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
@@ -568,6 +601,25 @@ public class JobSchedulerService extends com.android.server.SystemService
                    DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT);
        }

        private void updateRuntimeConstantsLocked() {
            DeviceConfig.Properties properties = DeviceConfig.getProperties(
                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                    KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
                    KEY_RUNTIME_MIN_GUARANTEE_MS, KEY_RUNTIME_MIN_EJ_GUARANTEE_MS);

            // Make sure min runtime for regular jobs is at least 10 minutes.
            RUNTIME_MIN_GUARANTEE_MS = Math.max(10 * MINUTE_IN_MILLIS,
                    properties.getLong(
                            KEY_RUNTIME_MIN_GUARANTEE_MS, DEFAULT_RUNTIME_MIN_GUARANTEE_MS));
            // Make sure min runtime for expedited jobs is at least one minute.
            RUNTIME_MIN_EJ_GUARANTEE_MS = Math.max(MINUTE_IN_MILLIS,
                    properties.getLong(
                            KEY_RUNTIME_MIN_EJ_GUARANTEE_MS, DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS));
            RUNTIME_FREE_QUOTA_MAX_LIMIT_MS = Math.max(RUNTIME_MIN_GUARANTEE_MS,
                    properties.getLong(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
                            DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS));
        }

        void dump(IndentingPrintWriter pw) {
            pw.println("Settings:");
            pw.increaseIndent();
@@ -591,6 +643,11 @@ public class JobSchedulerService extends com.android.server.SystemService
            pw.print(KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT,
                    API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT).println();

            pw.print(KEY_RUNTIME_MIN_GUARANTEE_MS, RUNTIME_MIN_GUARANTEE_MS).println();
            pw.print(KEY_RUNTIME_MIN_EJ_GUARANTEE_MS, RUNTIME_MIN_EJ_GUARANTEE_MS).println();
            pw.print(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
                    .println();

            pw.decreaseIndent();
        }

@@ -1602,7 +1659,7 @@ public class JobSchedulerService extends com.android.server.SystemService
     * time of the job to be the time of completion (i.e. the time at which this function is
     * called).
     * <p>This could be inaccurate b/c the job can run for as long as
     * {@link com.android.server.job.JobServiceContext#DEFAULT_EXECUTING_TIMESLICE_MILLIS}, but
     * {@link Constants#DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS}, but
     * will lead to underscheduling at least, rather than if we had taken the last execution time
     * to be the start of the execution.
     *
@@ -2213,11 +2270,24 @@ public class JobSchedulerService extends com.android.server.SystemService
        return isComponentUsable(job);
    }

    /** Returns the minimum amount of time we should let this job run before timing out. */
    public long getMinJobExecutionGuaranteeMs(JobStatus job) {
        synchronized (mLock) {
            if (job.shouldTreatAsExpeditedJob()) {
                // Don't guarantee RESTRICTED jobs more than 5 minutes.
                return job.getEffectiveStandbyBucket() != RESTRICTED_INDEX
                        ? mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS
                        : Math.min(mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS, 5 * MINUTE_IN_MILLIS);
            } else {
                return mConstants.RUNTIME_MIN_GUARANTEE_MS;
            }
        }
    }

    /** Returns the maximum amount of time this job could run for. */
    public long getMaxJobExecutionTimeMs(JobStatus job) {
        synchronized (mLock) {
            return Math.min(mQuotaController.getMaxJobExecutionTimeMsLocked(job),
                    JobServiceContext.DEFAULT_EXECUTING_TIMESLICE_MILLIS);
            return mQuotaController.getMaxJobExecutionTimeMsLocked(job);
        }
    }

+64 −23

File changed.

Preview size limit exceeded, changes collapsed.

+2 −0
Original line number Diff line number Diff line
@@ -325,6 +325,8 @@ public final class ConnectivityController extends RestrictingController implemen
     */
    private boolean isInsane(JobStatus jobStatus, Network network,
            NetworkCapabilities capabilities, Constants constants) {
        // Use the maximum possible time since it gives us an upper bound, even though the job
        // could end up stopping earlier.
        final long maxJobExecutionTimeMs = mService.getMaxJobExecutionTimeMs(jobStatus);

        final long downloadBytes = jobStatus.getEstimatedNetworkDownloadBytes();
Loading