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

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

Merge "Apply flex policy to jobs with short deadlines." into main

parents 774b44c9 0f72d995
Loading
Loading
Loading
Loading
+58 −23
Original line number Diff line number Diff line
@@ -241,6 +241,8 @@ public final class FlexibilityController extends StateController {
        private static final long MAX_TIME_WINDOW_MS = 24 * HOUR_IN_MILLIS;
        private final JobScoreBucket[] mScoreBuckets = new JobScoreBucket[NUM_SCORE_BUCKETS];
        private int mScoreBucketIndex = 0;
        private long mCachedScoreExpirationTimeElapsed;
        private int mCachedScore;

        public void addScore(int add, long nowElapsed) {
            JobScoreBucket bucket = mScoreBuckets[mScoreBucketIndex];
@@ -248,10 +250,17 @@ public final class FlexibilityController extends StateController {
                bucket = new JobScoreBucket();
                bucket.startTimeElapsed = nowElapsed;
                mScoreBuckets[mScoreBucketIndex] = bucket;
                // Brand new bucket, there's nothing to remove from the score,
                // so just update the expiration time if needed.
                mCachedScoreExpirationTimeElapsed = Math.min(mCachedScoreExpirationTimeElapsed,
                        nowElapsed + MAX_TIME_WINDOW_MS);
            } else if (bucket.startTimeElapsed < nowElapsed - MAX_TIME_WINDOW_MS) {
                // The bucket is too old.
                bucket.reset();
                bucket.startTimeElapsed = nowElapsed;
                // Force a recalculation of the cached score instead of just updating the cached
                // value and time in case there are multiple stale buckets.
                mCachedScoreExpirationTimeElapsed = nowElapsed;
            } else if (bucket.startTimeElapsed
                    < nowElapsed - MAX_TIME_WINDOW_MS / NUM_SCORE_BUCKETS) {
                // The current bucket's duration has completed. Move on to the next bucket.
@@ -261,16 +270,26 @@ public final class FlexibilityController extends StateController {
            }

            bucket.score += add;
            mCachedScore += add;
        }

        public int getScore(long nowElapsed) {
            if (nowElapsed < mCachedScoreExpirationTimeElapsed) {
                return mCachedScore;
            }
            int score = 0;
            final long earliestElapsed = nowElapsed - MAX_TIME_WINDOW_MS;
            long earliestValidBucketTimeElapsed = Long.MAX_VALUE;
            for (JobScoreBucket bucket : mScoreBuckets) {
                if (bucket != null && bucket.startTimeElapsed >= earliestElapsed) {
                    score += bucket.score;
                    if (earliestValidBucketTimeElapsed > bucket.startTimeElapsed) {
                        earliestValidBucketTimeElapsed = bucket.startTimeElapsed;
                    }
                }
            }
            mCachedScore = score;
            mCachedScoreExpirationTimeElapsed = earliestValidBucketTimeElapsed + MAX_TIME_WINDOW_MS;
            return score;
        }

@@ -378,10 +397,16 @@ public final class FlexibilityController extends StateController {

    @Override
    public void prepareForExecutionLocked(JobStatus jobStatus) {
        if (jobStatus.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
            // Don't include jobs for the TOP app in the score calculation.
            return;
        }
        // Use the job's requested priority to determine its score since that is what the developer
        // selected and it will be stable across job runs.
        final int score = mFallbackFlexibilityDeadlineScores
                .get(jobStatus.getJob().getPriority(), jobStatus.getJob().getPriority() / 100);
        final int priority = jobStatus.getJob().getPriority();
        final int score = mFallbackFlexibilityDeadlineScores.get(priority,
                FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES
                        .get(priority, priority / 100));
        JobScoreTracker jobScoreTracker =
                mJobScoreTrackers.get(jobStatus.getSourceUid(), jobStatus.getSourcePackageName());
        if (jobScoreTracker == null) {
@@ -394,6 +419,10 @@ public final class FlexibilityController extends StateController {

    @Override
    public void unprepareFromExecutionLocked(JobStatus jobStatus) {
        if (jobStatus.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
            // Jobs for the TOP app are excluded from the score calculation.
            return;
        }
        // The job didn't actually start. Undo the score increase.
        JobScoreTracker jobScoreTracker =
                mJobScoreTrackers.get(jobStatus.getSourceUid(), jobStatus.getSourcePackageName());
@@ -401,8 +430,10 @@ public final class FlexibilityController extends StateController {
            Slog.e(TAG, "Unprepared a job that didn't result in a score change");
            return;
        }
        final int score = mFallbackFlexibilityDeadlineScores
                .get(jobStatus.getJob().getPriority(), jobStatus.getJob().getPriority() / 100);
        final int priority = jobStatus.getJob().getPriority();
        final int score = mFallbackFlexibilityDeadlineScores.get(priority,
                FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES
                        .get(priority, priority / 100));
        jobScoreTracker.addScore(-score, sElapsedRealtimeClock.millis());
    }

@@ -649,7 +680,7 @@ public final class FlexibilityController extends StateController {
                    (long) Math.scalb(mRescheduledJobDeadline, js.getNumPreviousAttempts() - 2),
                    mMaxRescheduledDeadline);
        }
        if (js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME) {

        // Intentionally use the effective priority here. If a job's priority was effectively
        // lowered, it will be less likely to run quickly given other policies in JobScheduler.
        // Thus, there's no need to further delay the job based on flex policy.
@@ -657,13 +688,16 @@ public final class FlexibilityController extends StateController {
        final int jobScore =
                getScoreLocked(js.getSourceUid(), js.getSourcePackageName(), nowElapsed);
        // Set an upper limit on the fallback deadline so that the delay doesn't become extreme.
            final long fallbackDeadlineMs = Math.min(3 * mFallbackFlexibilityDeadlineMs,
        final long fallbackDurationMs = Math.min(3 * mFallbackFlexibilityDeadlineMs,
                mFallbackFlexibilityDeadlines.get(jobPriority, mFallbackFlexibilityDeadlineMs)
                        + mFallbackFlexibilityAdditionalScoreTimeFactors
                                .get(jobPriority, MINUTE_IN_MILLIS) * jobScore);
            return earliest + fallbackDeadlineMs;
        final long fallbackDeadlineMs = earliest + fallbackDurationMs;

        if (js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME) {
            return fallbackDeadlineMs;
        }
        return js.getLatestRunTimeElapsed();
        return Math.max(fallbackDeadlineMs, js.getLatestRunTimeElapsed());
    }

    @VisibleForTesting
@@ -976,7 +1010,8 @@ public final class FlexibilityController extends StateController {
                    // Something has gone horribly wrong. This has only occurred on incorrectly
                    // configured tests, but add a check here for safety.
                    Slog.wtf(TAG, "Got invalid latest when scheduling alarm."
                            + " Prefetch=" + js.getJob().isPrefetch());
                            + " prefetch=" + js.getJob().isPrefetch()
                            + " periodic=" + js.getJob().isPeriodic());
                    // Since things have gone wrong, the safest and most reliable thing to do is
                    // stop applying flex policy to the job.
                    mFlexibilityTracker.setNumDroppedFlexibleConstraints(js,
@@ -991,7 +1026,7 @@ public final class FlexibilityController extends StateController {

                if (DEBUG) {
                    Slog.d(TAG, "scheduleDropNumConstraintsAlarm: "
                            + js.getSourcePackageName() + " " + js.getSourceUserId()
                            + js.toShortString()
                            + " numApplied: " + js.getNumAppliedFlexibleConstraints()
                            + " numRequired: " + js.getNumRequiredFlexibleConstraints()
                            + " numSatisfied: " + Integer.bitCount(
@@ -1199,11 +1234,11 @@ public final class FlexibilityController extends StateController {
            DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
                    .put(PRIORITY_MAX, 0);
            DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
                    .put(PRIORITY_HIGH, 4 * MINUTE_IN_MILLIS);
                    .put(PRIORITY_HIGH, 3 * MINUTE_IN_MILLIS);
            DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
                    .put(PRIORITY_DEFAULT, 3 * MINUTE_IN_MILLIS);
                    .put(PRIORITY_DEFAULT, 2 * MINUTE_IN_MILLIS);
            DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
                    .put(PRIORITY_LOW, 2 * MINUTE_IN_MILLIS);
                    .put(PRIORITY_LOW, 1 * MINUTE_IN_MILLIS);
            DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
                    .put(PRIORITY_MIN, 1 * MINUTE_IN_MILLIS);
            DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
@@ -1220,7 +1255,7 @@ public final class FlexibilityController extends StateController {

        private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS;
        private static final long DEFAULT_RESCHEDULED_JOB_DEADLINE_MS = HOUR_IN_MILLIS;
        private static final long DEFAULT_MAX_RESCHEDULED_DEADLINE_MS = 5 * DAY_IN_MILLIS;
        private static final long DEFAULT_MAX_RESCHEDULED_DEADLINE_MS = DAY_IN_MILLIS;
        @VisibleForTesting
        static final long DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = 3 * DAY_IN_MILLIS;

+0 −9
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@

package com.android.server.job.controllers;

import static android.text.format.DateUtils.HOUR_IN_MILLIS;

import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
@@ -430,9 +428,6 @@ public final class JobStatus {
     */
    public static final int INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ = 1 << 2;

    /** Minimum difference between start and end time to have flexible constraint */
    @VisibleForTesting
    static final long MIN_WINDOW_FOR_FLEXIBILITY_MS = HOUR_IN_MILLIS;
    /**
     * Versatile, persistable flags for a job that's updated within the system server,
     * as opposed to {@link JobInfo#flags} that's set by callers.
@@ -708,14 +703,10 @@ public final class JobStatus {
        final boolean lacksSomeFlexibleConstraints =
                ((~requiredConstraints) & SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS) != 0
                        || mCanApplyTransportAffinities;
        final boolean satisfiesMinWindowException =
                (latestRunTimeElapsedMillis - earliestRunTimeElapsedMillis)
                >= MIN_WINDOW_FOR_FLEXIBILITY_MS;

        // The first time a job is rescheduled it will not be subject to flexible constraints.
        // Otherwise, every consecutive reschedule increases a jobs' flexibility deadline.
        if (!isRequestedExpeditedJob() && !job.isUserInitiated()
                && satisfiesMinWindowException
                && (numFailures + numSystemStops) != 1
                && lacksSomeFlexibleConstraints) {
            requiredConstraints |= CONSTRAINT_FLEXIBLE;
+61 −48
Original line number Diff line number Diff line
@@ -49,7 +49,6 @@ import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVI
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONTENT_TRIGGER;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;
import static com.android.server.job.controllers.JobStatus.MIN_WINDOW_FOR_FLEXIBILITY_MS;
import static com.android.server.job.controllers.JobStatus.NO_LATEST_RUNTIME;

import static org.junit.Assert.assertArrayEquals;
@@ -410,10 +409,12 @@ public class FlexibilityControllerTest {

    @Test
    public void testOnConstantsUpdated_PercentsToDropConstraints() {
        final long fallbackDuration = 12 * HOUR_IN_MILLIS;
        JobInfo.Builder jb = createJob(0)
                .setOverrideDeadline(MIN_WINDOW_FOR_FLEXIBILITY_MS);
                .setOverrideDeadline(HOUR_IN_MILLIS);
        JobStatus js = createJobStatus("testPercentsToDropConstraintsConfig", jb);
        assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
        // Even though the override deadline is 1 hour, the fallback duration is still used.
        assertEquals(FROZEN_TIME + fallbackDuration / 10 * 5,
                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
        setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
                "500=1|2|3|4"
@@ -441,13 +442,13 @@ public class FlexibilityControllerTest {
                mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
                        .get(JobInfo.PRIORITY_MIN),
                new int[]{54, 55, 56, 57});
        assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10,
        assertEquals(FROZEN_TIME + fallbackDuration / 10,
                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
        js.setNumDroppedFlexibleConstraints(1);
        assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 2,
        assertEquals(FROZEN_TIME + fallbackDuration / 10 * 2,
                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
        js.setNumDroppedFlexibleConstraints(2);
        assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 3,
        assertEquals(FROZEN_TIME + fallbackDuration / 10 * 3,
                mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
    }

@@ -504,37 +505,38 @@ public class FlexibilityControllerTest {

    @Test
    public void testGetNextConstraintDropTimeElapsedLocked() {
        final long fallbackDuration = 50 * HOUR_IN_MILLIS;
        setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 200 * HOUR_IN_MILLIS);
        setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
                "500=" + HOUR_IN_MILLIS
                        + ",400=" + 25 * HOUR_IN_MILLIS
                        + ",300=" + 50 * HOUR_IN_MILLIS
                        + ",300=" + fallbackDuration
                        + ",200=" + 100 * HOUR_IN_MILLIS
                        + ",100=" + 200 * HOUR_IN_MILLIS);

        long nextTimeToDropNumConstraints;

        // no delay, deadline
        JobInfo.Builder jb = createJob(0).setOverrideDeadline(MIN_WINDOW_FOR_FLEXIBILITY_MS);
        JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
        JobStatus js = createJobStatus("time", jb);

        assertEquals(JobStatus.NO_EARLIEST_RUNTIME, js.getEarliestRunTime());
        assertEquals(MIN_WINDOW_FOR_FLEXIBILITY_MS + FROZEN_TIME, js.getLatestRunTimeElapsed());
        assertEquals(HOUR_IN_MILLIS + FROZEN_TIME, js.getLatestRunTimeElapsed());
        assertEquals(FROZEN_TIME, js.enqueueTime);

        nextTimeToDropNumConstraints = mFlexibilityController
                .getNextConstraintDropTimeElapsedLocked(js);
        assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
        assertEquals(FROZEN_TIME + fallbackDuration / 10 * 5,
                nextTimeToDropNumConstraints);
        js.setNumDroppedFlexibleConstraints(1);
        nextTimeToDropNumConstraints = mFlexibilityController
                .getNextConstraintDropTimeElapsedLocked(js);
        assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 6,
        assertEquals(FROZEN_TIME + fallbackDuration / 10 * 6,
                nextTimeToDropNumConstraints);
        js.setNumDroppedFlexibleConstraints(2);
        nextTimeToDropNumConstraints = mFlexibilityController
                .getNextConstraintDropTimeElapsedLocked(js);
        assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 7,
        assertEquals(FROZEN_TIME + fallbackDuration / 10 * 7,
                nextTimeToDropNumConstraints);

        // delay, no deadline
@@ -574,81 +576,83 @@ public class FlexibilityControllerTest {

        // delay, deadline
        jb = createJob(0)
                .setOverrideDeadline(2 * MIN_WINDOW_FOR_FLEXIBILITY_MS)
                .setMinimumLatency(MIN_WINDOW_FOR_FLEXIBILITY_MS);
                .setOverrideDeadline(2 * HOUR_IN_MILLIS)
                .setMinimumLatency(HOUR_IN_MILLIS);
        js = createJobStatus("time", jb);

        final long windowStart = FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS;
        final long windowStart = FROZEN_TIME + HOUR_IN_MILLIS;
        nextTimeToDropNumConstraints = mFlexibilityController
                .getNextConstraintDropTimeElapsedLocked(js);
        assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
        assertEquals(windowStart + fallbackDuration / 10 * 5,
                nextTimeToDropNumConstraints);
        js.setNumDroppedFlexibleConstraints(1);
        nextTimeToDropNumConstraints = mFlexibilityController
                .getNextConstraintDropTimeElapsedLocked(js);
        assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 6,
        assertEquals(windowStart + fallbackDuration / 10 * 6,
                nextTimeToDropNumConstraints);
        js.setNumDroppedFlexibleConstraints(2);
        nextTimeToDropNumConstraints = mFlexibilityController
                .getNextConstraintDropTimeElapsedLocked(js);
        assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 7,
        assertEquals(windowStart + fallbackDuration / 10 * 7,
                nextTimeToDropNumConstraints);
    }

    @Test
    public void testCurPercent() {
        final long fallbackDuration = 10 * HOUR_IN_MILLIS;
        setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES, "300=" + fallbackDuration);
        long deadline = 100 * MINUTE_IN_MILLIS;
        long nowElapsed = FROZEN_TIME;
        JobInfo.Builder jb = createJob(0).setOverrideDeadline(deadline);
        JobStatus js = createJobStatus("time", jb);

        assertEquals(FROZEN_TIME, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
        assertEquals(deadline + FROZEN_TIME,
        assertEquals(FROZEN_TIME + fallbackDuration,
                mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, FROZEN_TIME));
        nowElapsed = FROZEN_TIME + 60 * MINUTE_IN_MILLIS;
        nowElapsed = FROZEN_TIME + 6 * HOUR_IN_MILLIS;
        JobSchedulerService.sElapsedRealtimeClock =
                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
        assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));

        nowElapsed = FROZEN_TIME + 130 * MINUTE_IN_MILLIS;
        nowElapsed = FROZEN_TIME + 13 * HOUR_IN_MILLIS;
        JobSchedulerService.sElapsedRealtimeClock =
                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
        assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));

        nowElapsed = FROZEN_TIME + 95 * MINUTE_IN_MILLIS;
        nowElapsed = FROZEN_TIME + 9 * HOUR_IN_MILLIS;
        JobSchedulerService.sElapsedRealtimeClock =
                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
        assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
        assertEquals(90, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));

        nowElapsed = FROZEN_TIME;
        JobSchedulerService.sElapsedRealtimeClock =
                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
        long delay = MINUTE_IN_MILLIS;
        deadline = 101 * MINUTE_IN_MILLIS;
        long delay = HOUR_IN_MILLIS;
        deadline = HOUR_IN_MILLIS + 100 * MINUTE_IN_MILLIS;
        jb = createJob(0).setOverrideDeadline(deadline).setMinimumLatency(delay);
        js = createJobStatus("time", jb);

        assertEquals(FROZEN_TIME + delay,
                mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
        assertEquals(deadline + FROZEN_TIME,
        assertEquals(FROZEN_TIME + delay + fallbackDuration,
                mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed,
                        FROZEN_TIME + delay));

        nowElapsed = FROZEN_TIME + delay + 60 * MINUTE_IN_MILLIS;
        nowElapsed = FROZEN_TIME + delay + 6 * HOUR_IN_MILLIS;
        JobSchedulerService.sElapsedRealtimeClock =
                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);

        assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));

        nowElapsed = FROZEN_TIME + 130 * MINUTE_IN_MILLIS;
        nowElapsed = FROZEN_TIME + 13 * HOUR_IN_MILLIS;
        JobSchedulerService.sElapsedRealtimeClock =
                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
        assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));

        nowElapsed = FROZEN_TIME + delay + 95 * MINUTE_IN_MILLIS;
        nowElapsed = FROZEN_TIME + delay + 9 * HOUR_IN_MILLIS;
        JobSchedulerService.sElapsedRealtimeClock =
                Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
        assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
        assertEquals(90, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
    }

    @Test
@@ -786,26 +790,27 @@ public class FlexibilityControllerTest {
        // deadline
        JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
        JobStatus js = createJobStatus("time", jb);
        assertEquals(HOUR_IN_MILLIS + FROZEN_TIME,
                mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
        assertEquals(3 * HOUR_IN_MILLIS + js.enqueueTime,
                mFlexibilityController
                        .getLifeCycleEndElapsedLocked(js, nowElapsed, js.enqueueTime));

        // no deadline
        assertEquals(FROZEN_TIME + 2 * HOUR_IN_MILLIS,
        assertEquals(js.enqueueTime + 2 * HOUR_IN_MILLIS,
                mFlexibilityController.getLifeCycleEndElapsedLocked(
                        createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_HIGH)),
                        nowElapsed, 100L));
        assertEquals(FROZEN_TIME + 3 * HOUR_IN_MILLIS,
                        nowElapsed, js.enqueueTime));
        assertEquals(js.enqueueTime + 3 * HOUR_IN_MILLIS,
                mFlexibilityController.getLifeCycleEndElapsedLocked(
                        createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT)),
                        nowElapsed, 100L));
        assertEquals(FROZEN_TIME + 4 * HOUR_IN_MILLIS,
                        nowElapsed, js.enqueueTime));
        assertEquals(js.enqueueTime + 4 * HOUR_IN_MILLIS,
                mFlexibilityController.getLifeCycleEndElapsedLocked(
                        createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_LOW)),
                        nowElapsed, 100L));
        assertEquals(FROZEN_TIME + 5 * HOUR_IN_MILLIS,
                        nowElapsed, js.enqueueTime));
        assertEquals(js.enqueueTime + 5 * HOUR_IN_MILLIS,
                mFlexibilityController.getLifeCycleEndElapsedLocked(
                        createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_MIN)),
                        nowElapsed, 100L));
                        nowElapsed, js.enqueueTime));
    }

    @Test
@@ -871,14 +876,16 @@ public class FlexibilityControllerTest {
        mFlexibilityController.prepareForExecutionLocked(jsLow);
        mFlexibilityController.prepareForExecutionLocked(jsMin);

        // deadline
        JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
        JobStatus js = createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition", jb);
        assertEquals(HOUR_IN_MILLIS + FROZEN_TIME,
                mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
        final long longDeadlineMs = 14 * 24 * HOUR_IN_MILLIS;
        JobInfo.Builder jbWithLongDeadline = createJob(0).setOverrideDeadline(longDeadlineMs);
        JobStatus jsWithLongDeadline = createJobStatus(
                "testGetLifeCycleEndElapsedLocked_ScoreAddition", jbWithLongDeadline);
        JobInfo.Builder jbWithShortDeadline =
                createJob(0).setOverrideDeadline(15 * MINUTE_IN_MILLIS);
        JobStatus jsWithShortDeadline = createJobStatus(
                "testGetLifeCycleEndElapsedLocked_ScoreAddition", jbWithShortDeadline);

        final long earliestMs = 123L;
        // no deadline
        assertEquals(earliestMs + HOUR_IN_MILLIS + 5 * 15 * MINUTE_IN_MILLIS,
                mFlexibilityController.getLifeCycleEndElapsedLocked(
                        createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
@@ -894,6 +901,9 @@ public class FlexibilityControllerTest {
                        createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
                                createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT)),
                        nowElapsed, earliestMs));
        assertEquals(earliestMs + HOUR_IN_MILLIS + 3 * 15 * MINUTE_IN_MILLIS,
                mFlexibilityController.getLifeCycleEndElapsedLocked(
                        jsWithShortDeadline, nowElapsed, earliestMs));
        assertEquals(earliestMs + HOUR_IN_MILLIS + 2 * 15 * MINUTE_IN_MILLIS,
                mFlexibilityController.getLifeCycleEndElapsedLocked(
                        createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
@@ -904,6 +914,9 @@ public class FlexibilityControllerTest {
                        createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
                                createJob(0).setPriority(JobInfo.PRIORITY_MIN)),
                        nowElapsed, earliestMs));
        assertEquals(jsWithLongDeadline.enqueueTime + longDeadlineMs,
                mFlexibilityController.getLifeCycleEndElapsedLocked(
                        jsWithLongDeadline, nowElapsed, earliestMs));
    }

    @Test
@@ -1033,8 +1046,8 @@ public class FlexibilityControllerTest {
        JobInfo.Builder jb = createJob(0);
        jb.setMinimumLatency(1);
        jb.setOverrideDeadline(2);
        JobStatus js = createJobStatus("Disable Flexible When Job Has Short Window", jb);
        assertFalse(js.hasFlexibilityConstraint());
        JobStatus js = createJobStatus("testExceptions_ShortWindow", jb);
        assertTrue(js.hasFlexibilityConstraint());
    }

    @Test