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

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

Merge "Make job execution limits flexible." into sc-dev am: f5415261

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

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: I83a7c561898a628b552fff7a9dfc4d8a2af12627
parents 03d75ce1 f5415261
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