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

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

Merge "Introduce concept of work types." into sc-dev

parents ab913b2e 1477559f
Loading
Loading
Loading
Loading
+434 −237

File changed.

Preview size limit exceeded, changes collapsed.

+0 −182
Original line number Original line Diff line number Diff line
@@ -380,7 +380,6 @@ public class JobSchedulerService extends com.android.server.SystemService
                        default:
                        default:
                            if (name.startsWith(JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY)
                            if (name.startsWith(JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY)
                                    && !concurrencyUpdated) {
                                    && !concurrencyUpdated) {
                                mConstants.updateConcurrencyConstantsLocked();
                                mConcurrencyManager.updateConfigLocked();
                                mConcurrencyManager.updateConfigLocked();
                                concurrencyUpdated = true;
                                concurrencyUpdated = true;
                            } else {
                            } else {
@@ -408,119 +407,6 @@ public class JobSchedulerService extends com.android.server.SystemService
                mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);
                mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);
    }
    }


    static class MaxJobCounts {
        private final int mTotalDefault;
        private final String mTotalKey;
        private final int mMaxBgDefault;
        private final String mMaxBgKey;
        private final int mMinBgDefault;
        private final String mMinBgKey;
        private int mTotal;
        private int mMaxBg;
        private int mMinBg;

        MaxJobCounts(int totalDefault, String totalKey,
                int maxBgDefault, String maxBgKey, int minBgDefault, String minBgKey) {
            mTotalKey = totalKey;
            mTotal = mTotalDefault = totalDefault;
            mMaxBgKey = maxBgKey;
            mMaxBg = mMaxBgDefault = maxBgDefault;
            mMinBgKey = minBgKey;
            mMinBg = mMinBgDefault = minBgDefault;
        }

        public void update() {
            mTotal = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                    mTotalKey, mTotalDefault);
            mMaxBg = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                    mMaxBgKey, mMaxBgDefault);
            mMinBg = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                    mMinBgKey, mMinBgDefault);

            // Ensure total in the range [1, MAX_JOB_CONTEXTS_COUNT].
            mTotal = Math.min(Math.max(1, mTotal), MAX_JOB_CONTEXTS_COUNT);

            // Ensure maxBg in the range [1, total].
            mMaxBg = Math.min(Math.max(1, mMaxBg), mTotal);

            // Ensure minBg in the range [0, min(maxBg, total - 1)]
            mMinBg = Math.min(Math.max(0, mMinBg), Math.min(mMaxBg, mTotal - 1));
        }

        /** Total number of jobs to run simultaneously. */
        public int getMaxTotal() {
            return mTotal;
        }

        /** Max number of BG (== owned by non-TOP apps) jobs to run simultaneously. */
        public int getMaxBg() {
            return mMaxBg;
        }

        /**
         * We try to run at least this many BG (== owned by non-TOP apps) jobs, when there are any
         * pending, rather than always running the TOTAL number of FG jobs.
         */
        public int getMinBg() {
            return mMinBg;
        }

        public void dump(PrintWriter pw, String prefix) {
            pw.print(prefix);
            pw.print(mTotalKey);
            pw.print("=");
            pw.print(mTotal);
            pw.println();

            pw.print(prefix);
            pw.print(mMaxBgKey);
            pw.print("=");
            pw.print(mMaxBg);
            pw.println();

            pw.print(prefix);
            pw.print(mMinBgKey);
            pw.print("=");
            pw.print(mMinBg);
            pw.println();
        }

        public void dumpProto(ProtoOutputStream proto, long fieldId) {
            final long token = proto.start(fieldId);
            proto.write(MaxJobCountsProto.TOTAL_JOBS, mTotal);
            proto.write(MaxJobCountsProto.MAX_BG, mMaxBg);
            proto.write(MaxJobCountsProto.MIN_BG, mMinBg);
            proto.end(token);
        }
    }

    /** {@link MaxJobCounts} for each memory trim level. */
    static class MaxJobCountsPerMemoryTrimLevel {
        public final MaxJobCounts normal;
        public final MaxJobCounts moderate;
        public final MaxJobCounts low;
        public final MaxJobCounts critical;

        MaxJobCountsPerMemoryTrimLevel(
                MaxJobCounts normal,
                MaxJobCounts moderate, MaxJobCounts low,
                MaxJobCounts critical) {
            this.normal = normal;
            this.moderate = moderate;
            this.low = low;
            this.critical = critical;
        }

        public void dumpProto(ProtoOutputStream proto, long fieldId) {
            final long token = proto.start(fieldId);
            normal.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.NORMAL);
            moderate.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.MODERATE);
            low.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.LOW);
            critical.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.CRITICAL);
            proto.end(token);
        }
    }

    /**
    /**
     * All times are in milliseconds. Any access to this class or its fields should be done while
     * All times are in milliseconds. Any access to this class or its fields should be done while
     * holding the JobSchedulerService.mLock lock.
     * holding the JobSchedulerService.mLock lock.
@@ -580,49 +466,6 @@ public class JobSchedulerService extends com.android.server.SystemService
         */
         */
        float MODERATE_USE_FACTOR = DEFAULT_MODERATE_USE_FACTOR;
        float MODERATE_USE_FACTOR = DEFAULT_MODERATE_USE_FACTOR;


        /** Prefix for all of the max_job constants. */
        private static final String KEY_PREFIX_MAX_JOB =
                JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY + "max_job_";

        // Max job counts for screen on / off, for each memory trim level.
        final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_ON =
                new MaxJobCountsPerMemoryTrimLevel(
                        new MaxJobCounts(
                                8, KEY_PREFIX_MAX_JOB + "total_on_normal",
                                6, KEY_PREFIX_MAX_JOB + "max_bg_on_normal",
                                2, KEY_PREFIX_MAX_JOB + "min_bg_on_normal"),
                        new MaxJobCounts(
                                8, KEY_PREFIX_MAX_JOB + "total_on_moderate",
                                4, KEY_PREFIX_MAX_JOB + "max_bg_on_moderate",
                                2, KEY_PREFIX_MAX_JOB + "min_bg_on_moderate"),
                        new MaxJobCounts(
                                5, KEY_PREFIX_MAX_JOB + "total_on_low",
                                1, KEY_PREFIX_MAX_JOB + "max_bg_on_low",
                                1, KEY_PREFIX_MAX_JOB + "min_bg_on_low"),
                        new MaxJobCounts(
                                5, KEY_PREFIX_MAX_JOB + "total_on_critical",
                                1, KEY_PREFIX_MAX_JOB + "max_bg_on_critical",
                                1, KEY_PREFIX_MAX_JOB + "min_bg_on_critical"));

        final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_OFF =
                new MaxJobCountsPerMemoryTrimLevel(
                        new MaxJobCounts(
                                10, KEY_PREFIX_MAX_JOB + "total_off_normal",
                                6, KEY_PREFIX_MAX_JOB + "max_bg_off_normal",
                                2, KEY_PREFIX_MAX_JOB + "min_bg_off_normal"),
                        new MaxJobCounts(
                                10, KEY_PREFIX_MAX_JOB + "total_off_moderate",
                                4, KEY_PREFIX_MAX_JOB + "max_bg_off_moderate",
                                2, KEY_PREFIX_MAX_JOB + "min_bg_off_moderate"),
                        new MaxJobCounts(
                                5, KEY_PREFIX_MAX_JOB + "total_off_low",
                                1, KEY_PREFIX_MAX_JOB + "max_bg_off_low",
                                1, KEY_PREFIX_MAX_JOB + "min_bg_off_low"),
                        new MaxJobCounts(
                                5, KEY_PREFIX_MAX_JOB + "total_off_critical",
                                1, KEY_PREFIX_MAX_JOB + "max_bg_off_critical",
                                1, KEY_PREFIX_MAX_JOB + "min_bg_off_critical"));

        /**
        /**
         * The minimum backoff time to allow for linear backoff.
         * The minimum backoff time to allow for linear backoff.
         */
         */
@@ -686,18 +529,6 @@ public class JobSchedulerService extends com.android.server.SystemService
                    DEFAULT_MODERATE_USE_FACTOR);
                    DEFAULT_MODERATE_USE_FACTOR);
        }
        }


        void updateConcurrencyConstantsLocked() {
            MAX_JOB_COUNTS_SCREEN_ON.normal.update();
            MAX_JOB_COUNTS_SCREEN_ON.moderate.update();
            MAX_JOB_COUNTS_SCREEN_ON.low.update();
            MAX_JOB_COUNTS_SCREEN_ON.critical.update();

            MAX_JOB_COUNTS_SCREEN_OFF.normal.update();
            MAX_JOB_COUNTS_SCREEN_OFF.moderate.update();
            MAX_JOB_COUNTS_SCREEN_OFF.low.update();
            MAX_JOB_COUNTS_SCREEN_OFF.critical.update();
        }

        private void updateBackoffConstantsLocked() {
        private void updateBackoffConstantsLocked() {
            MIN_LINEAR_BACKOFF_TIME_MS = DeviceConfig.getLong(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
            MIN_LINEAR_BACKOFF_TIME_MS = DeviceConfig.getLong(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                    KEY_MIN_LINEAR_BACKOFF_TIME_MS,
                    KEY_MIN_LINEAR_BACKOFF_TIME_MS,
@@ -747,16 +578,6 @@ public class JobSchedulerService extends com.android.server.SystemService
            pw.print(KEY_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println();
            pw.print(KEY_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println();
            pw.print(KEY_MODERATE_USE_FACTOR, MODERATE_USE_FACTOR).println();
            pw.print(KEY_MODERATE_USE_FACTOR, MODERATE_USE_FACTOR).println();


            MAX_JOB_COUNTS_SCREEN_ON.normal.dump(pw, "");
            MAX_JOB_COUNTS_SCREEN_ON.moderate.dump(pw, "");
            MAX_JOB_COUNTS_SCREEN_ON.low.dump(pw, "");
            MAX_JOB_COUNTS_SCREEN_ON.critical.dump(pw, "");

            MAX_JOB_COUNTS_SCREEN_OFF.normal.dump(pw, "");
            MAX_JOB_COUNTS_SCREEN_OFF.moderate.dump(pw, "");
            MAX_JOB_COUNTS_SCREEN_OFF.low.dump(pw, "");
            MAX_JOB_COUNTS_SCREEN_OFF.critical.dump(pw, "");

            pw.print(KEY_MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS).println();
            pw.print(KEY_MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS).println();
            pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println();
            pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println();
            pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
            pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
@@ -781,9 +602,6 @@ public class JobSchedulerService extends com.android.server.SystemService
            proto.write(ConstantsProto.HEAVY_USE_FACTOR, HEAVY_USE_FACTOR);
            proto.write(ConstantsProto.HEAVY_USE_FACTOR, HEAVY_USE_FACTOR);
            proto.write(ConstantsProto.MODERATE_USE_FACTOR, MODERATE_USE_FACTOR);
            proto.write(ConstantsProto.MODERATE_USE_FACTOR, MODERATE_USE_FACTOR);


            MAX_JOB_COUNTS_SCREEN_ON.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_ON);
            MAX_JOB_COUNTS_SCREEN_OFF.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_OFF);

            proto.write(ConstantsProto.MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS);
            proto.write(ConstantsProto.MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS);
            proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS);
            proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS);
            proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC);
            proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC);
+20 −12
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@


package com.android.server.job;
package com.android.server.job;


import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;


@@ -124,9 +125,12 @@ public final class JobServiceContext implements ServiceConnection {
     *
     *
     * Any reads (dereferences) not done from the handler thread must be synchronized on
     * Any reads (dereferences) not done from the handler thread must be synchronized on
     * {@link #mLock}.
     * {@link #mLock}.
     * Writes can only be done from the handler thread, or {@link #executeRunnableJob(JobStatus)}.
     * Writes can only be done from the handler thread,
     * or {@link #executeRunnableJob(JobStatus, int)}.
     */
     */
    private JobStatus mRunningJob;
    private JobStatus mRunningJob;
    @JobConcurrencyManager.WorkType
    private int mRunningJobWorkType;
    private JobCallback mRunningCallback;
    private JobCallback mRunningCallback;
    /** Used to store next job to run when current job is to be preempted. */
    /** Used to store next job to run when current job is to be preempted. */
    private int mPreferredUid;
    private int mPreferredUid;
@@ -181,30 +185,26 @@ public final class JobServiceContext implements ServiceConnection {


    JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats,
    JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats,
            JobPackageTracker tracker, Looper looper) {
            JobPackageTracker tracker, Looper looper) {
        this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper);
        mContext = service.getContext();
    }
        mLock = service.getLock();

    @VisibleForTesting
    JobServiceContext(Context context, Object lock, IBatteryStats batteryStats,
            JobPackageTracker tracker, JobCompletedListener completedListener, Looper looper) {
        mContext = context;
        mLock = lock;
        mBatteryStats = batteryStats;
        mBatteryStats = batteryStats;
        mJobPackageTracker = tracker;
        mJobPackageTracker = tracker;
        mCallbackHandler = new JobServiceHandler(looper);
        mCallbackHandler = new JobServiceHandler(looper);
        mCompletedListener = completedListener;
        mCompletedListener = service;
        mAvailable = true;
        mAvailable = true;
        mVerb = VERB_FINISHED;
        mVerb = VERB_FINISHED;
        mPreferredUid = NO_PREFERRED_UID;
        mPreferredUid = NO_PREFERRED_UID;
    }
    }


    /**
    /**
     * Give a job to this context for execution. Callers must first check {@link #getRunningJobLocked()}
     * Give a job to this context for execution. Callers must first check {@link
     * #getRunningJobLocked()}
     * and ensure it is null to make sure this is a valid context.
     * and ensure it is null to make sure this is a valid context.
     *
     * @param job The status of the job that we are going to run.
     * @param job The status of the job that we are going to run.
     * @return True if the job is valid and is running. False if the job cannot be executed.
     * @return True if the job is valid and is running. False if the job cannot be executed.
     */
     */
    boolean executeRunnableJob(JobStatus job) {
    boolean executeRunnableJob(JobStatus job, @JobConcurrencyManager.WorkType int workType) {
        synchronized (mLock) {
        synchronized (mLock) {
            if (!mAvailable) {
            if (!mAvailable) {
                Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
                Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
@@ -214,6 +214,7 @@ public final class JobServiceContext implements ServiceConnection {
            mPreferredUid = NO_PREFERRED_UID;
            mPreferredUid = NO_PREFERRED_UID;


            mRunningJob = job;
            mRunningJob = job;
            mRunningJobWorkType = workType;
            mRunningCallback = new JobCallback();
            mRunningCallback = new JobCallback();
            final boolean isDeadlineExpired =
            final boolean isDeadlineExpired =
                    job.hasDeadlineConstraint() &&
                    job.hasDeadlineConstraint() &&
@@ -282,6 +283,7 @@ public final class JobServiceContext implements ServiceConnection {
                    Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
                    Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
                }
                }
                mRunningJob = null;
                mRunningJob = null;
                mRunningJobWorkType = WORK_TYPE_NONE;
                mRunningCallback = null;
                mRunningCallback = null;
                mParams = null;
                mParams = null;
                mExecutionStartTimeElapsed = 0L;
                mExecutionStartTimeElapsed = 0L;
@@ -326,6 +328,11 @@ public final class JobServiceContext implements ServiceConnection {
        return mRunningJob;
        return mRunningJob;
    }
    }


    @JobConcurrencyManager.WorkType
    int getRunningJobWorkType() {
        return mRunningJobWorkType;
    }

    /**
    /**
     * Used only for debugging. Will return <code>"&lt;null&gt;"</code> if there is no job running.
     * Used only for debugging. Will return <code>"&lt;null&gt;"</code> if there is no job running.
     */
     */
@@ -831,6 +838,7 @@ public final class JobServiceContext implements ServiceConnection {
        mContext.unbindService(JobServiceContext.this);
        mContext.unbindService(JobServiceContext.this);
        mWakeLock = null;
        mWakeLock = null;
        mRunningJob = null;
        mRunningJob = null;
        mRunningJobWorkType = WORK_TYPE_NONE;
        mRunningCallback = null;
        mRunningCallback = null;
        mParams = null;
        mParams = null;
        mVerb = VERB_FINISHED;
        mVerb = VERB_FINISHED;
+1 −0
Original line number Original line Diff line number Diff line
@@ -305,6 +305,7 @@ public final class JobStatus {
    public Network network;
    public Network network;
    public ServiceInfo serviceInfo;
    public ServiceInfo serviceInfo;


    /** The evaluated priority of the job when it started running. */
    public int lastEvaluatedPriority;
    public int lastEvaluatedPriority;


    // If non-null, this is work that has been enqueued for the job.
    // If non-null, this is work that has been enqueued for the job.
+44 −22
Original line number Original line Diff line number Diff line
@@ -16,36 +16,42 @@


package com.android.server.job;
package com.android.server.job;


import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertThat;


import android.util.Log;
import android.util.Log;
import android.util.Pair;

import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;


import com.android.server.job.JobConcurrencyManager.JobCountTracker;
import com.android.server.job.JobConcurrencyManager.WorkCountTracker;


import org.junit.Before;
import org.junit.Before;
import org.junit.Test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runner.RunWith;


import java.util.List;
import java.util.Random;
import java.util.Random;


import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;

/**
/**
 * Test for {@link com.android.server.job.JobConcurrencyManager.JobCountTracker}.
 * Test for {@link WorkCountTracker}.
 */
 */
@RunWith(AndroidJUnit4.class)
@RunWith(AndroidJUnit4.class)
@MediumTest
@MediumTest
public class JobCountTrackerTest {
public class WorkCountTrackerTest {
    private static final String TAG = "JobCountTrackerTest";
    private static final String TAG = "WorkerCountTrackerTest";


    private Random mRandom;
    private Random mRandom;
    private JobCountTracker mJobCountTracker;
    private WorkCountTracker mWorkCountTracker;


    @Before
    @Before
    public void setUp() {
    public void setUp() {
        mRandom = new Random(1); // Always use the same series of pseudo random values.
        mRandom = new Random(1); // Always use the same series of pseudo random values.
        mJobCountTracker = new JobCountTracker();
        mWorkCountTracker = new WorkCountTracker();
    }
    }


    /**
    /**
@@ -83,44 +89,57 @@ public class JobCountTrackerTest {




    private void startPendingJobs(Jobs jobs, int totalMax, int maxBg, int minBg) {
    private void startPendingJobs(Jobs jobs, int totalMax, int maxBg, int minBg) {
        mJobCountTracker.reset(totalMax, maxBg, minBg);
        mWorkCountTracker.setConfig(new JobConcurrencyManager.WorkTypeConfig("critical",
                totalMax,
                // defaultMin
                List.of(Pair.create(WORK_TYPE_TOP, totalMax - maxBg),
                        Pair.create(WORK_TYPE_BG, minBg)),
                // defaultMax
                List.of(Pair.create(WORK_TYPE_BG, maxBg))));
        mWorkCountTracker.resetCounts();


        for (int i = 0; i < jobs.runningFg; i++) {
        for (int i = 0; i < jobs.runningFg; i++) {
            mJobCountTracker.incrementRunningJobCount(true);
            mWorkCountTracker.incrementRunningJobCount(WORK_TYPE_TOP);
        }
        }
        for (int i = 0; i < jobs.runningBg; i++) {
        for (int i = 0; i < jobs.runningBg; i++) {
            mJobCountTracker.incrementRunningJobCount(false);
            mWorkCountTracker.incrementRunningJobCount(WORK_TYPE_BG);
        }
        }


        for (int i = 0; i < jobs.pendingFg; i++) {
        for (int i = 0; i < jobs.pendingFg; i++) {
            mJobCountTracker.incrementPendingJobCount(true);
            mWorkCountTracker.incrementPendingJobCount(WORK_TYPE_TOP);
        }
        }
        for (int i = 0; i < jobs.pendingBg; i++) {
        for (int i = 0; i < jobs.pendingBg; i++) {
            mJobCountTracker.incrementPendingJobCount(false);
            mWorkCountTracker.incrementPendingJobCount(WORK_TYPE_BG);
        }
        }


        mJobCountTracker.onCountDone();
        mWorkCountTracker.onCountDone();


        while ((jobs.pendingFg > 0 && mJobCountTracker.canJobStart(true))
        while ((jobs.pendingFg > 0
                || (jobs.pendingBg > 0 && mJobCountTracker.canJobStart(false))) {
                && mWorkCountTracker.canJobStart(WORK_TYPE_TOP) != WORK_TYPE_NONE)
                || (jobs.pendingBg > 0
                && mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE)) {
            final boolean isStartingFg = mRandom.nextBoolean();
            final boolean isStartingFg = mRandom.nextBoolean();


            if (isStartingFg) {
            if (isStartingFg) {
                if (jobs.pendingFg > 0 && mJobCountTracker.canJobStart(true)) {
                if (jobs.pendingFg > 0
                        && mWorkCountTracker.canJobStart(WORK_TYPE_TOP) != WORK_TYPE_NONE) {
                    jobs.pendingFg--;
                    jobs.pendingFg--;
                    jobs.runningFg++;
                    jobs.runningFg++;
                    mJobCountTracker.onStartingNewJob(true);
                    mWorkCountTracker.stageJob(WORK_TYPE_TOP);
                    mWorkCountTracker.onJobStarted(WORK_TYPE_TOP);
                }
                }
            } else {
            } else {
                if (jobs.pendingBg > 0 && mJobCountTracker.canJobStart(false)) {
                if (jobs.pendingBg > 0
                        && mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE) {
                    jobs.pendingBg--;
                    jobs.pendingBg--;
                    jobs.runningBg++;
                    jobs.runningBg++;
                    mJobCountTracker.onStartingNewJob(false);
                    mWorkCountTracker.stageJob(WORK_TYPE_BG);
                    mWorkCountTracker.onJobStarted(WORK_TYPE_BG);
                }
                }
            }
            }
        }
        }


        Log.i(TAG, "" + mJobCountTracker);
        Log.i(TAG, "" + mWorkCountTracker);
    }
    }


    /**
    /**
@@ -277,6 +296,7 @@ public class JobCountTrackerTest {


        startPendingJobs(jobs, totalMax, maxBg, minBg);
        startPendingJobs(jobs, totalMax, maxBg, minBg);


//        fail(mWorkerCountTracker.toString());
        assertThat(jobs.runningFg).isEqualTo(resultRunningFg);
        assertThat(jobs.runningFg).isEqualTo(resultRunningFg);
        assertThat(jobs.runningBg).isEqualTo(resultRunningBg);
        assertThat(jobs.runningBg).isEqualTo(resultRunningBg);


@@ -300,6 +320,8 @@ public class JobCountTrackerTest {
        checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 1, /*res run/pen=*/ 5, 1, 5, 0);
        checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 1, /*res run/pen=*/ 5, 1, 5, 0);
        checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 3, /*res run/pen=*/ 4, 2, 6, 1);
        checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 3, /*res run/pen=*/ 4, 2, 6, 1);


        checkSimple(8, 6, 2, /*run=*/ 0, 0, /*pen=*/ 0, 49, /*res run/pen=*/ 0, 6, 0, 43);

        checkSimple(6, 4, 2, /*run=*/ 6, 0, /*pen=*/ 10, 3, /*res run/pen=*/ 6, 0, 10, 3);
        checkSimple(6, 4, 2, /*run=*/ 6, 0, /*pen=*/ 10, 3, /*res run/pen=*/ 6, 0, 10, 3);
    }
    }
}
}
Loading