Move the job scheduler service code to its own jar file.

- Also remove the dependency from SystemServiceRegistry to JobScheduler

See apex/jobscheduler/README_js-mainline.md for the details.

Bug: 137763703
Test: build and boot
Test: atest CtsJobSchedulerTestCases
Change-Id: I2386c78b7a6085d6e543a63f22cb620c4cabd06a
diff --git a/services/core/java/com/android/server/job/GrantedUriPermissions.java b/services/core/java/com/android/server/job/GrantedUriPermissions.java
deleted file mode 100644
index 005b189..0000000
--- a/services/core/java/com/android/server/job/GrantedUriPermissions.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job;
-
-import android.app.IActivityManager;
-import android.app.UriGrantsManager;
-import android.content.ClipData;
-import android.content.ContentProvider;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-import com.android.server.LocalServices;
-import com.android.server.uri.UriGrantsManagerInternal;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-public final class GrantedUriPermissions {
-    private final int mGrantFlags;
-    private final int mSourceUserId;
-    private final String mTag;
-    private final IBinder mPermissionOwner;
-    private final ArrayList<Uri> mUris = new ArrayList<>();
-
-    private GrantedUriPermissions(IActivityManager am, int grantFlags, int uid, String tag)
-            throws RemoteException {
-        mGrantFlags = grantFlags;
-        mSourceUserId = UserHandle.getUserId(uid);
-        mTag = tag;
-        mPermissionOwner = LocalServices
-                .getService(UriGrantsManagerInternal.class).newUriPermissionOwner("job: " + tag);
-    }
-
-    public void revoke(IActivityManager am) {
-        for (int i = mUris.size()-1; i >= 0; i--) {
-            LocalServices.getService(UriGrantsManagerInternal.class).revokeUriPermissionFromOwner(
-                    mPermissionOwner, mUris.get(i), mGrantFlags, mSourceUserId);
-        }
-        mUris.clear();
-    }
-
-    public static boolean checkGrantFlags(int grantFlags) {
-        return (grantFlags & (Intent.FLAG_GRANT_WRITE_URI_PERMISSION
-                |Intent.FLAG_GRANT_READ_URI_PERMISSION)) != 0;
-    }
-
-    public static GrantedUriPermissions createFromIntent(IActivityManager am, Intent intent,
-            int sourceUid, String targetPackage, int targetUserId, String tag) {
-        int grantFlags = intent.getFlags();
-        if (!checkGrantFlags(grantFlags)) {
-            return null;
-        }
-
-        GrantedUriPermissions perms = null;
-
-        Uri data = intent.getData();
-        if (data != null) {
-            perms = grantUri(am, data, sourceUid, targetPackage, targetUserId, grantFlags, tag,
-                    perms);
-        }
-
-        ClipData clip = intent.getClipData();
-        if (clip != null) {
-            perms = grantClip(am, clip, sourceUid, targetPackage, targetUserId, grantFlags, tag,
-                    perms);
-        }
-
-        return perms;
-    }
-
-    public static GrantedUriPermissions createFromClip(IActivityManager am, ClipData clip,
-            int sourceUid, String targetPackage, int targetUserId, int grantFlags, String tag) {
-        if (!checkGrantFlags(grantFlags)) {
-            return null;
-        }
-        GrantedUriPermissions perms = null;
-        if (clip != null) {
-            perms = grantClip(am, clip, sourceUid, targetPackage, targetUserId, grantFlags,
-                    tag, perms);
-        }
-        return perms;
-    }
-
-    private static GrantedUriPermissions grantClip(IActivityManager am, ClipData clip,
-            int sourceUid, String targetPackage, int targetUserId, int grantFlags, String tag,
-            GrantedUriPermissions curPerms) {
-        final int N = clip.getItemCount();
-        for (int i = 0; i < N; i++) {
-            curPerms = grantItem(am, clip.getItemAt(i), sourceUid, targetPackage, targetUserId,
-                    grantFlags, tag, curPerms);
-        }
-        return curPerms;
-    }
-
-    private static GrantedUriPermissions grantUri(IActivityManager am, Uri uri,
-            int sourceUid, String targetPackage, int targetUserId, int grantFlags, String tag,
-            GrantedUriPermissions curPerms) {
-        try {
-            int sourceUserId = ContentProvider.getUserIdFromUri(uri,
-                    UserHandle.getUserId(sourceUid));
-            uri = ContentProvider.getUriWithoutUserId(uri);
-            if (curPerms == null) {
-                curPerms = new GrantedUriPermissions(am, grantFlags, sourceUid, tag);
-            }
-            UriGrantsManager.getService().grantUriPermissionFromOwner(curPerms.mPermissionOwner,
-                    sourceUid, targetPackage, uri, grantFlags, sourceUserId, targetUserId);
-            curPerms.mUris.add(uri);
-        } catch (RemoteException e) {
-            Slog.e("JobScheduler", "AM dead");
-        }
-        return curPerms;
-    }
-
-    private static GrantedUriPermissions grantItem(IActivityManager am, ClipData.Item item,
-            int sourceUid, String targetPackage, int targetUserId, int grantFlags, String tag,
-            GrantedUriPermissions curPerms) {
-        if (item.getUri() != null) {
-            curPerms = grantUri(am, item.getUri(), sourceUid, targetPackage, targetUserId,
-                    grantFlags, tag, curPerms);
-        }
-        Intent intent = item.getIntent();
-        if (intent != null && intent.getData() != null) {
-            curPerms = grantUri(am, intent.getData(), sourceUid, targetPackage, targetUserId,
-                    grantFlags, tag, curPerms);
-        }
-        return curPerms;
-    }
-
-    // Dumpsys infrastructure
-    public void dump(PrintWriter pw, String prefix) {
-        pw.print(prefix); pw.print("mGrantFlags=0x"); pw.print(Integer.toHexString(mGrantFlags));
-        pw.print(" mSourceUserId="); pw.println(mSourceUserId);
-        pw.print(prefix); pw.print("mTag="); pw.println(mTag);
-        pw.print(prefix); pw.print("mPermissionOwner="); pw.println(mPermissionOwner);
-        for (int i = 0; i < mUris.size(); i++) {
-            pw.print(prefix); pw.print("#"); pw.print(i); pw.print(": ");
-            pw.println(mUris.get(i));
-        }
-    }
-
-    public void dump(ProtoOutputStream proto, long fieldId) {
-        final long token = proto.start(fieldId);
-
-        proto.write(GrantedUriPermissionsDumpProto.FLAGS, mGrantFlags);
-        proto.write(GrantedUriPermissionsDumpProto.SOURCE_USER_ID, mSourceUserId);
-        proto.write(GrantedUriPermissionsDumpProto.TAG, mTag);
-        proto.write(GrantedUriPermissionsDumpProto.PERMISSION_OWNER, mPermissionOwner.toString());
-        for (int i = 0; i < mUris.size(); i++) {
-            Uri u = mUris.get(i);
-            if (u != null) {
-                proto.write(GrantedUriPermissionsDumpProto.URIS, u.toString());
-            }
-        }
-
-        proto.end(token);
-    }
-}
diff --git a/services/core/java/com/android/server/job/JobCompletedListener.java b/services/core/java/com/android/server/job/JobCompletedListener.java
deleted file mode 100644
index 34ba753b3..0000000
--- a/services/core/java/com/android/server/job/JobCompletedListener.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job;
-
-import com.android.server.job.controllers.JobStatus;
-
-/**
- * Used for communication between {@link com.android.server.job.JobServiceContext} and the
- * {@link com.android.server.job.JobSchedulerService}.
- */
-public interface JobCompletedListener {
-    /**
-     * Callback for when a job is completed.
-     * @param needsReschedule Whether the implementing class should reschedule this job.
-     */
-    void onJobCompletedLocked(JobStatus jobStatus, boolean needsReschedule);
-}
diff --git a/services/core/java/com/android/server/job/JobConcurrencyManager.java b/services/core/java/com/android/server/job/JobConcurrencyManager.java
deleted file mode 100644
index bec1947..0000000
--- a/services/core/java/com/android/server/job/JobConcurrencyManager.java
+++ /dev/null
@@ -1,722 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.job;
-
-import android.app.ActivityManager;
-import android.app.job.JobInfo;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.os.RemoteException;
-import android.util.Slog;
-import android.util.TimeUtils;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.procstats.ProcessStats;
-import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.StatLogger;
-import com.android.server.job.JobSchedulerService.Constants;
-import com.android.server.job.JobSchedulerService.MaxJobCountsPerMemoryTrimLevel;
-import com.android.server.job.controllers.JobStatus;
-import com.android.server.job.controllers.StateController;
-
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * This class decides, given the various configuration and the system status, how many more jobs
- * can start.
- */
-class JobConcurrencyManager {
-    private static final String TAG = JobSchedulerService.TAG;
-    private static final boolean DEBUG = JobSchedulerService.DEBUG;
-
-    private final Object mLock;
-    private final JobSchedulerService mService;
-    private final JobSchedulerService.Constants mConstants;
-    private final Context mContext;
-    private final Handler mHandler;
-
-    private PowerManager mPowerManager;
-
-    private boolean mCurrentInteractiveState;
-    private boolean mEffectiveInteractiveState;
-
-    private long mLastScreenOnRealtime;
-    private long mLastScreenOffRealtime;
-
-    private static final int MAX_JOB_CONTEXTS_COUNT = JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
-
-    /**
-     * This array essentially stores the state of mActiveServices array.
-     * The ith index stores the job present on the ith JobServiceContext.
-     * We manipulate this array until we arrive at what jobs should be running on
-     * what JobServiceContext.
-     */
-    JobStatus[] mRecycledAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT];
-
-    boolean[] mRecycledSlotChanged = new boolean[MAX_JOB_CONTEXTS_COUNT];
-
-    int[] mRecycledPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
-
-    /** Max job counts according to the current system state. */
-    private JobSchedulerService.MaxJobCounts mMaxJobCounts;
-
-    private final JobCountTracker mJobCountTracker = new JobCountTracker();
-
-    /** Current memory trim level. */
-    private int mLastMemoryTrimLevel;
-
-    /** Used to throttle heavy API calls. */
-    private long mNextSystemStateRefreshTime;
-    private static final int SYSTEM_STATE_REFRESH_MIN_INTERVAL = 1000;
-
-    private final StatLogger mStatLogger = new StatLogger(new String[]{
-            "assignJobsToContexts",
-            "refreshSystemState",
-    });
-
-    interface Stats {
-        int ASSIGN_JOBS_TO_CONTEXTS = 0;
-        int REFRESH_SYSTEM_STATE = 1;
-
-        int COUNT = REFRESH_SYSTEM_STATE + 1;
-    }
-
-    JobConcurrencyManager(JobSchedulerService service) {
-        mService = service;
-        mLock = mService.mLock;
-        mConstants = service.mConstants;
-        mContext = service.getContext();
-
-        mHandler = BackgroundThread.getHandler();
-    }
-
-    public void onSystemReady() {
-        mPowerManager = mContext.getSystemService(PowerManager.class);
-
-        final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
-        filter.addAction(Intent.ACTION_SCREEN_OFF);
-        mContext.registerReceiver(mReceiver, filter);
-
-        onInteractiveStateChanged(mPowerManager.isInteractive());
-    }
-
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            switch (intent.getAction()) {
-                case Intent.ACTION_SCREEN_ON:
-                    onInteractiveStateChanged(true);
-                    break;
-                case Intent.ACTION_SCREEN_OFF:
-                    onInteractiveStateChanged(false);
-                    break;
-            }
-        }
-    };
-
-    /**
-     * Called when the screen turns on / off.
-     */
-    private void onInteractiveStateChanged(boolean interactive) {
-        synchronized (mLock) {
-            if (mCurrentInteractiveState == interactive) {
-                return;
-            }
-            mCurrentInteractiveState = interactive;
-            if (DEBUG) {
-                Slog.d(TAG, "Interactive: " + interactive);
-            }
-
-            final long nowRealtime = JobSchedulerService.sElapsedRealtimeClock.millis();
-            if (interactive) {
-                mLastScreenOnRealtime = nowRealtime;
-                mEffectiveInteractiveState = true;
-
-                mHandler.removeCallbacks(mRampUpForScreenOff);
-            } else {
-                mLastScreenOffRealtime = nowRealtime;
-
-                // Set mEffectiveInteractiveState to false after the delay, when we may increase
-                // the concurrency.
-                // We don't need a wakeup alarm here. When there's a pending job, there should
-                // also be jobs running too, meaning the device should be awake.
-
-                // Note: we can't directly do postDelayed(this::rampUpForScreenOn), because
-                // we need the exact same instance for removeCallbacks().
-                mHandler.postDelayed(mRampUpForScreenOff,
-                        mConstants.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.getValue());
-            }
-        }
-    }
-
-    private final Runnable mRampUpForScreenOff = this::rampUpForScreenOff;
-
-    /**
-     * Called in {@link Constants#SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS} after
-     * the screen turns off, in order to increase concurrency.
-     */
-    private void rampUpForScreenOff() {
-        synchronized (mLock) {
-            // Make sure the screen has really been off for the configured duration.
-            // (There could be a race.)
-            if (!mEffectiveInteractiveState) {
-                return;
-            }
-            if (mLastScreenOnRealtime > mLastScreenOffRealtime) {
-                return;
-            }
-            final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
-            if ((mLastScreenOffRealtime
-                    + mConstants.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.getValue())
-                    > now) {
-                return;
-            }
-
-            mEffectiveInteractiveState = false;
-
-            if (DEBUG) {
-                Slog.d(TAG, "Ramping up concurrency");
-            }
-
-            mService.maybeRunPendingJobsLocked();
-        }
-    }
-
-    private boolean isFgJob(JobStatus job) {
-        return job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP;
-    }
-
-    @GuardedBy("mLock")
-    private void refreshSystemStateLocked() {
-        final long nowUptime = JobSchedulerService.sUptimeMillisClock.millis();
-
-        // Only refresh the information every so often.
-        if (nowUptime < mNextSystemStateRefreshTime) {
-            return;
-        }
-
-        final long start = mStatLogger.getTime();
-        mNextSystemStateRefreshTime = nowUptime + SYSTEM_STATE_REFRESH_MIN_INTERVAL;
-
-        mLastMemoryTrimLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
-        try {
-            mLastMemoryTrimLevel = ActivityManager.getService().getMemoryTrimLevel();
-        } catch (RemoteException e) {
-        }
-
-        mStatLogger.logDurationStat(Stats.REFRESH_SYSTEM_STATE, start);
-    }
-
-    @GuardedBy("mLock")
-    private void updateMaxCountsLocked() {
-        refreshSystemStateLocked();
-
-        final MaxJobCountsPerMemoryTrimLevel jobCounts = mEffectiveInteractiveState
-                ? mConstants.MAX_JOB_COUNTS_SCREEN_ON
-                : mConstants.MAX_JOB_COUNTS_SCREEN_OFF;
-
-
-        switch (mLastMemoryTrimLevel) {
-            case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
-                mMaxJobCounts = jobCounts.moderate;
-                break;
-            case ProcessStats.ADJ_MEM_FACTOR_LOW:
-                mMaxJobCounts = jobCounts.low;
-                break;
-            case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
-                mMaxJobCounts = jobCounts.critical;
-                break;
-            default:
-                mMaxJobCounts = jobCounts.normal;
-                break;
-        }
-    }
-
-    /**
-     * Takes jobs from pending queue and runs them on available contexts.
-     * If no contexts are available, preempts lower priority jobs to
-     * run higher priority ones.
-     * Lock on mJobs before calling this function.
-     */
-    @GuardedBy("mLock")
-    void assignJobsToContextsLocked() {
-        final long start = mStatLogger.getTime();
-
-        assignJobsToContextsInternalLocked();
-
-        mStatLogger.logDurationStat(Stats.ASSIGN_JOBS_TO_CONTEXTS, start);
-    }
-
-    @GuardedBy("mLock")
-    private void assignJobsToContextsInternalLocked() {
-        if (DEBUG) {
-            Slog.d(TAG, printPendingQueueLocked());
-        }
-
-        final JobPackageTracker tracker = mService.mJobPackageTracker;
-        final List<JobStatus> pendingJobs = mService.mPendingJobs;
-        final List<JobServiceContext> activeServices = mService.mActiveServices;
-        final List<StateController> controllers = mService.mControllers;
-
-        updateMaxCountsLocked();
-
-        // To avoid GC churn, we recycle the arrays.
-        JobStatus[] contextIdToJobMap = mRecycledAssignContextIdToJobMap;
-        boolean[] slotChanged = mRecycledSlotChanged;
-        int[] preferredUidForContext = mRecycledPreferredUidForContext;
-
-
-        // Initialize the work variables and also count running jobs.
-        mJobCountTracker.reset(
-                mMaxJobCounts.getMaxTotal(),
-                mMaxJobCounts.getMaxBg(),
-                mMaxJobCounts.getMinBg());
-
-        for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
-            final JobServiceContext js = mService.mActiveServices.get(i);
-            final JobStatus status = js.getRunningJobLocked();
-
-            if ((contextIdToJobMap[i] = status) != null) {
-                mJobCountTracker.incrementRunningJobCount(isFgJob(status));
-            }
-
-            slotChanged[i] = false;
-            preferredUidForContext[i] = js.getPreferredUid();
-        }
-        if (DEBUG) {
-            Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
-        }
-
-        // Next, update the job priorities, and also count the pending FG / BG jobs.
-        for (int i = 0; i < pendingJobs.size(); i++) {
-            final JobStatus pending = pendingJobs.get(i);
-
-            // If job is already running, go to next job.
-            int jobRunningContext = findJobContextIdFromMap(pending, contextIdToJobMap);
-            if (jobRunningContext != -1) {
-                continue;
-            }
-
-            final int priority = mService.evaluateJobPriorityLocked(pending);
-            pending.lastEvaluatedPriority = priority;
-
-            mJobCountTracker.incrementPendingJobCount(isFgJob(pending));
-        }
-
-        mJobCountTracker.onCountDone();
-
-        for (int i = 0; i < pendingJobs.size(); i++) {
-            final JobStatus nextPending = pendingJobs.get(i);
-
-            // Unfortunately we need to repeat this relatively expensive check.
-            int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
-            if (jobRunningContext != -1) {
-                continue;
-            }
-
-            final boolean isPendingFg = isFgJob(nextPending);
-
-            // 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)
-            int minPriorityForPreemption = Integer.MAX_VALUE;
-            int selectedContextId = -1;
-            boolean startingJob = false;
-            for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
-                JobStatus job = contextIdToJobMap[j];
-                int preferredUid = preferredUidForContext[j];
-                if (job == null) {
-                    final boolean preferredUidOkay = (preferredUid == nextPending.getUid())
-                            || (preferredUid == JobServiceContext.NO_PREFERRED_UID);
-
-                    if (preferredUidOkay && mJobCountTracker.canJobStart(isPendingFg)) {
-                        // This slot is free, and we haven't yet hit the limit on
-                        // concurrent jobs...  we can just throw the job in to here.
-                        selectedContextId = j;
-                        startingJob = true;
-                        break;
-                    }
-                    // No job on this context, but nextPending can't run here because
-                    // the context has a preferred Uid or we have reached the limit on
-                    // concurrent jobs.
-                    continue;
-                }
-                if (job.getUid() != nextPending.getUid()) {
-                    continue;
-                }
-
-                final int jobPriority = mService.evaluateJobPriorityLocked(job);
-                if (jobPriority >= nextPending.lastEvaluatedPriority) {
-                    continue;
-                }
-
-                // TODO lastEvaluatedPriority should be evaluateJobPriorityLocked. (double check it)
-                if (minPriorityForPreemption > nextPending.lastEvaluatedPriority) {
-                    minPriorityForPreemption = nextPending.lastEvaluatedPriority;
-                    selectedContextId = j;
-                    // 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.
-                }
-            }
-            if (selectedContextId != -1) {
-                contextIdToJobMap[selectedContextId] = nextPending;
-                slotChanged[selectedContextId] = true;
-            }
-            if (startingJob) {
-                // Increase the counters when we're going to start a job.
-                mJobCountTracker.onStartingNewJob(isPendingFg);
-            }
-        }
-        if (DEBUG) {
-            Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
-        }
-
-        mJobCountTracker.logStatus();
-
-        tracker.noteConcurrency(mJobCountTracker.getTotalRunningJobCountToNote(),
-                mJobCountTracker.getFgRunningJobCountToNote());
-
-        for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
-            boolean preservePreferredUid = false;
-            if (slotChanged[i]) {
-                JobStatus js = activeServices.get(i).getRunningJobLocked();
-                if (js != null) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "preempting job: "
-                                + activeServices.get(i).getRunningJobLocked());
-                    }
-                    // preferredUid will be set to uid of currently running job.
-                    activeServices.get(i).preemptExecutingJobLocked();
-                    preservePreferredUid = true;
-                } else {
-                    final JobStatus pendingJob = contextIdToJobMap[i];
-                    if (DEBUG) {
-                        Slog.d(TAG, "About to run job on context "
-                                + i + ", job: " + pendingJob);
-                    }
-                    for (int ic=0; ic<controllers.size(); ic++) {
-                        controllers.get(ic).prepareForExecutionLocked(pendingJob);
-                    }
-                    if (!activeServices.get(i).executeRunnableJob(pendingJob)) {
-                        Slog.d(TAG, "Error executing " + pendingJob);
-                    }
-                    if (pendingJobs.remove(pendingJob)) {
-                        tracker.noteNonpending(pendingJob);
-                    }
-                }
-            }
-            if (!preservePreferredUid) {
-                activeServices.get(i).clearPreferredUid();
-            }
-        }
-    }
-
-    private static int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) {
-        for (int i=0; i<map.length; i++) {
-            if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
-    @GuardedBy("mLock")
-    private String printPendingQueueLocked() {
-        StringBuilder s = new StringBuilder("Pending queue: ");
-        Iterator<JobStatus> it = mService.mPendingJobs.iterator();
-        while (it.hasNext()) {
-            JobStatus js = it.next();
-            s.append("(")
-                    .append(js.getJob().getId())
-                    .append(", ")
-                    .append(js.getUid())
-                    .append(") ");
-        }
-        return s.toString();
-    }
-
-    private static String printContextIdToJobMap(JobStatus[] map, String initial) {
-        StringBuilder s = new StringBuilder(initial + ": ");
-        for (int i=0; i<map.length; i++) {
-            s.append("(")
-                    .append(map[i] == null? -1: map[i].getJobId())
-                    .append(map[i] == null? -1: map[i].getUid())
-                    .append(")" );
-        }
-        return s.toString();
-    }
-
-
-    public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) {
-        pw.println("Concurrency:");
-
-        pw.increaseIndent();
-        try {
-            pw.print("Screen state: current ");
-            pw.print(mCurrentInteractiveState ? "ON" : "OFF");
-            pw.print("  effective ");
-            pw.print(mEffectiveInteractiveState ? "ON" : "OFF");
-            pw.println();
-
-            pw.print("Last screen ON : ");
-            TimeUtils.dumpTimeWithDelta(pw, now - nowRealtime + mLastScreenOnRealtime, now);
-            pw.println();
-
-            pw.print("Last screen OFF: ");
-            TimeUtils.dumpTimeWithDelta(pw, now - nowRealtime + mLastScreenOffRealtime, now);
-            pw.println();
-
-            pw.println();
-
-            pw.println("Current max jobs:");
-            pw.println("  ");
-            pw.println(mJobCountTracker);
-
-            pw.println();
-
-            pw.print("mLastMemoryTrimLevel: ");
-            pw.print(mLastMemoryTrimLevel);
-            pw.println();
-
-            mStatLogger.dump(pw);
-        } finally {
-            pw.decreaseIndent();
-        }
-    }
-
-    public void dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime) {
-        final long token = proto.start(tag);
-
-        proto.write(JobConcurrencyManagerProto.CURRENT_INTERACTIVE,
-                mCurrentInteractiveState);
-        proto.write(JobConcurrencyManagerProto.EFFECTIVE_INTERACTIVE,
-                mEffectiveInteractiveState);
-
-        proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_ON_MS,
-                nowRealtime - mLastScreenOnRealtime);
-        proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_OFF_MS,
-                nowRealtime - mLastScreenOffRealtime);
-
-        mJobCountTracker.dumpProto(proto, JobConcurrencyManagerProto.JOB_COUNT_TRACKER);
-
-        proto.write(JobConcurrencyManagerProto.MEMORY_TRIM_LEVEL,
-                mLastMemoryTrimLevel);
-
-        proto.end(token);
-    }
-
-    /**
-     * This class decides, taking into account {@link #mMaxJobCounts} and how mny jos are running /
-     * pending, how many more job can start.
-     *
-     * Extracted for testing and logging.
-     */
-    @VisibleForTesting
-    static class JobCountTracker {
-        private int mConfigNumMaxTotalJobs;
-        private int mConfigNumMaxBgJobs;
-        private int mConfigNumMinBgJobs;
-
-        private int mNumRunningFgJobs;
-        private int mNumRunningBgJobs;
-
-        private int mNumPendingFgJobs;
-        private int mNumPendingBgJobs;
-
-        private int mNumStartingFgJobs;
-        private int mNumStartingBgJobs;
-
-        private int mNumReservedForBg;
-        private int mNumActualMaxFgJobs;
-        private int mNumActualMaxBgJobs;
-
-        void reset(int numTotalMaxJobs, int numMaxBgJobs, int numMinBgJobs) {
-            mConfigNumMaxTotalJobs = numTotalMaxJobs;
-            mConfigNumMaxBgJobs = numMaxBgJobs;
-            mConfigNumMinBgJobs = numMinBgJobs;
-
-            mNumRunningFgJobs = 0;
-            mNumRunningBgJobs = 0;
-
-            mNumPendingFgJobs = 0;
-            mNumPendingBgJobs = 0;
-
-            mNumStartingFgJobs = 0;
-            mNumStartingBgJobs = 0;
-
-            mNumReservedForBg = 0;
-            mNumActualMaxFgJobs = 0;
-            mNumActualMaxBgJobs = 0;
-        }
-
-        void incrementRunningJobCount(boolean isFg) {
-            if (isFg) {
-                mNumRunningFgJobs++;
-            } else {
-                mNumRunningBgJobs++;
-            }
-        }
-
-        void incrementPendingJobCount(boolean isFg) {
-            if (isFg) {
-                mNumPendingFgJobs++;
-            } else {
-                mNumPendingBgJobs++;
-            }
-        }
-
-        void onStartingNewJob(boolean isFg) {
-            if (isFg) {
-                mNumStartingFgJobs++;
-            } else {
-                mNumStartingBgJobs++;
-            }
-        }
-
-        void onCountDone() {
-            // Note some variables are used only here but are made class members in order to have
-            // them on logcat / dumpsys.
-
-            // How many slots should we allocate to BG jobs at least?
-            // That's basically "getMinBg()", but if there are less jobs, decrease it.
-            // (e.g. even if min-bg is 2, if there's only 1 running+pending job, this has to be 1.)
-            final int reservedForBg = Math.min(
-                    mConfigNumMinBgJobs,
-                    mNumRunningBgJobs + mNumPendingBgJobs);
-
-            // However, if there are FG jobs already running, we have to adjust it.
-            mNumReservedForBg = Math.min(reservedForBg,
-                    mConfigNumMaxTotalJobs - mNumRunningFgJobs);
-
-            // Max FG is [total - [number needed for BG jobs]]
-            // [number needed for BG jobs] is the bigger one of [running BG] or [reserved BG]
-            final int maxFg =
-                    mConfigNumMaxTotalJobs - Math.max(mNumRunningBgJobs, mNumReservedForBg);
-
-            // The above maxFg is the theoretical max. If there are less FG jobs, the actual
-            // max FG will be lower accordingly.
-            mNumActualMaxFgJobs = Math.min(
-                    maxFg,
-                    mNumRunningFgJobs + mNumPendingFgJobs);
-
-            // Max BG is [total - actual max FG], but cap at [config max BG].
-            final int maxBg = Math.min(
-                    mConfigNumMaxBgJobs,
-                    mConfigNumMaxTotalJobs - mNumActualMaxFgJobs);
-
-            // If there are less BG jobs than maxBg, then reduce the actual max BG accordingly.
-            // This isn't needed for the logic to work, but this will give consistent output
-            // on logcat and dumpsys.
-            mNumActualMaxBgJobs = Math.min(
-                    maxBg,
-                    mNumRunningBgJobs + mNumPendingBgJobs);
-        }
-
-        boolean canJobStart(boolean isFg) {
-            if (isFg) {
-                return mNumRunningFgJobs + mNumStartingFgJobs < mNumActualMaxFgJobs;
-            } else {
-                return mNumRunningBgJobs + mNumStartingBgJobs < mNumActualMaxBgJobs;
-            }
-        }
-
-        public int getNumStartingFgJobs() {
-            return mNumStartingFgJobs;
-        }
-
-        public int getNumStartingBgJobs() {
-            return mNumStartingBgJobs;
-        }
-
-        int getTotalRunningJobCountToNote() {
-            return mNumRunningFgJobs + mNumRunningBgJobs
-                    + mNumStartingFgJobs + mNumStartingBgJobs;
-        }
-
-        int getFgRunningJobCountToNote() {
-            return mNumRunningFgJobs + mNumStartingFgJobs;
-        }
-
-        void logStatus() {
-            if (DEBUG) {
-                Slog.d(TAG, "assignJobsToContexts: " + this);
-            }
-        }
-
-        public String toString() {
-            final int totalFg = mNumRunningFgJobs + mNumStartingFgJobs;
-            final int totalBg = mNumRunningBgJobs + mNumStartingBgJobs;
-            return String.format(
-                    "Config={tot=%d bg min/max=%d/%d}"
-                            + " Running[FG/BG (total)]: %d / %d (%d)"
-                            + " Pending: %d / %d (%d)"
-                            + " Actual max: %d%s / %d%s (%d%s)"
-                            + " Res BG: %d"
-                            + " Starting: %d / %d (%d)"
-                            + " Total: %d%s / %d%s (%d%s)",
-                    mConfigNumMaxTotalJobs,
-                    mConfigNumMinBgJobs,
-                    mConfigNumMaxBgJobs,
-
-                    mNumRunningFgJobs, mNumRunningBgJobs,
-                    mNumRunningFgJobs + mNumRunningBgJobs,
-
-                    mNumPendingFgJobs, mNumPendingBgJobs,
-                    mNumPendingFgJobs + mNumPendingBgJobs,
-
-                    mNumActualMaxFgJobs, (totalFg <= mConfigNumMaxTotalJobs) ? "" : "*",
-                    mNumActualMaxBgJobs, (totalBg <= mConfigNumMaxBgJobs) ? "" : "*",
-
-                    mNumActualMaxFgJobs + mNumActualMaxBgJobs,
-                    (mNumActualMaxFgJobs + mNumActualMaxBgJobs <= mConfigNumMaxTotalJobs)
-                            ? "" : "*",
-
-                    mNumReservedForBg,
-
-                    mNumStartingFgJobs, mNumStartingBgJobs, mNumStartingFgJobs + mNumStartingBgJobs,
-
-                    totalFg, (totalFg <= mNumActualMaxFgJobs) ? "" : "*",
-                    totalBg, (totalBg <= mNumActualMaxBgJobs) ? "" : "*",
-                    totalFg + totalBg, (totalFg + totalBg <= mConfigNumMaxTotalJobs) ? "" : "*"
-            );
-        }
-
-        public void dumpProto(ProtoOutputStream proto, long fieldId) {
-            final long token = proto.start(fieldId);
-
-            proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_TOTAL_JOBS, mConfigNumMaxTotalJobs);
-            proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_BG_JOBS, mConfigNumMaxBgJobs);
-            proto.write(JobCountTrackerProto.CONFIG_NUM_MIN_BG_JOBS, mConfigNumMinBgJobs);
-
-            proto.write(JobCountTrackerProto.NUM_RUNNING_FG_JOBS, mNumRunningFgJobs);
-            proto.write(JobCountTrackerProto.NUM_RUNNING_BG_JOBS, mNumRunningBgJobs);
-
-            proto.write(JobCountTrackerProto.NUM_PENDING_FG_JOBS, mNumPendingFgJobs);
-            proto.write(JobCountTrackerProto.NUM_PENDING_BG_JOBS, mNumPendingBgJobs);
-
-            proto.end(token);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/job/JobPackageTracker.java b/services/core/java/com/android/server/job/JobPackageTracker.java
deleted file mode 100644
index e28e5bd..0000000
--- a/services/core/java/com/android/server/job/JobPackageTracker.java
+++ /dev/null
@@ -1,653 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job;
-
-import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
-import static com.android.server.job.JobSchedulerService.sSystemClock;
-import static com.android.server.job.JobSchedulerService.sUptimeMillisClock;
-
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
-import android.os.UserHandle;
-import android.text.format.DateFormat;
-import android.util.ArrayMap;
-import android.util.SparseArray;
-import android.util.SparseIntArray;
-import android.util.TimeUtils;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.util.RingBufferIndices;
-import com.android.server.job.controllers.JobStatus;
-
-import java.io.PrintWriter;
-
-public final class JobPackageTracker {
-    // We batch every 30 minutes.
-    static final long BATCHING_TIME = 30*60*1000;
-    // Number of historical data sets we keep.
-    static final int NUM_HISTORY = 5;
-
-    private static final int EVENT_BUFFER_SIZE = 100;
-
-    public static final int EVENT_CMD_MASK = 0xff;
-    public static final int EVENT_STOP_REASON_SHIFT = 8;
-    public static final int EVENT_STOP_REASON_MASK = 0xff << EVENT_STOP_REASON_SHIFT;
-    public static final int EVENT_NULL = 0;
-    public static final int EVENT_START_JOB = 1;
-    public static final int EVENT_STOP_JOB = 2;
-    public static final int EVENT_START_PERIODIC_JOB = 3;
-    public static final int EVENT_STOP_PERIODIC_JOB = 4;
-
-    private final RingBufferIndices mEventIndices = new RingBufferIndices(EVENT_BUFFER_SIZE);
-    private final int[] mEventCmds = new int[EVENT_BUFFER_SIZE];
-    private final long[] mEventTimes = new long[EVENT_BUFFER_SIZE];
-    private final int[] mEventUids = new int[EVENT_BUFFER_SIZE];
-    private final String[] mEventTags = new String[EVENT_BUFFER_SIZE];
-    private final int[] mEventJobIds = new int[EVENT_BUFFER_SIZE];
-    private final String[] mEventReasons = new String[EVENT_BUFFER_SIZE];
-
-    public void addEvent(int cmd, int uid, String tag, int jobId, int stopReason,
-            String debugReason) {
-        int index = mEventIndices.add();
-        mEventCmds[index] = cmd | ((stopReason<<EVENT_STOP_REASON_SHIFT) & EVENT_STOP_REASON_MASK);
-        mEventTimes[index] = sElapsedRealtimeClock.millis();
-        mEventUids[index] = uid;
-        mEventTags[index] = tag;
-        mEventJobIds[index] = jobId;
-        mEventReasons[index] = debugReason;
-    }
-
-    DataSet mCurDataSet = new DataSet();
-    DataSet[] mLastDataSets = new DataSet[NUM_HISTORY];
-
-    final static class PackageEntry {
-        long pastActiveTime;
-        long activeStartTime;
-        int activeNesting;
-        int activeCount;
-        boolean hadActive;
-        long pastActiveTopTime;
-        long activeTopStartTime;
-        int activeTopNesting;
-        int activeTopCount;
-        boolean hadActiveTop;
-        long pastPendingTime;
-        long pendingStartTime;
-        int pendingNesting;
-        int pendingCount;
-        boolean hadPending;
-        final SparseIntArray stopReasons = new SparseIntArray();
-
-        public long getActiveTime(long now) {
-            long time = pastActiveTime;
-            if (activeNesting > 0) {
-                time += now - activeStartTime;
-            }
-            return time;
-        }
-
-        public long getActiveTopTime(long now) {
-            long time = pastActiveTopTime;
-            if (activeTopNesting > 0) {
-                time += now - activeTopStartTime;
-            }
-            return time;
-        }
-
-        public long getPendingTime(long now) {
-            long time = pastPendingTime;
-            if (pendingNesting > 0) {
-                time += now - pendingStartTime;
-            }
-            return time;
-        }
-    }
-
-    final static class DataSet {
-        final SparseArray<ArrayMap<String, PackageEntry>> mEntries = new SparseArray<>();
-        final long mStartUptimeTime;
-        final long mStartElapsedTime;
-        final long mStartClockTime;
-        long mSummedTime;
-        int mMaxTotalActive;
-        int mMaxFgActive;
-
-        public DataSet(DataSet otherTimes) {
-            mStartUptimeTime = otherTimes.mStartUptimeTime;
-            mStartElapsedTime = otherTimes.mStartElapsedTime;
-            mStartClockTime = otherTimes.mStartClockTime;
-        }
-
-        public DataSet() {
-            mStartUptimeTime = sUptimeMillisClock.millis();
-            mStartElapsedTime = sElapsedRealtimeClock.millis();
-            mStartClockTime = sSystemClock.millis();
-        }
-
-        private PackageEntry getOrCreateEntry(int uid, String pkg) {
-            ArrayMap<String, PackageEntry> uidMap = mEntries.get(uid);
-            if (uidMap == null) {
-                uidMap = new ArrayMap<>();
-                mEntries.put(uid, uidMap);
-            }
-            PackageEntry entry = uidMap.get(pkg);
-            if (entry == null) {
-                entry = new PackageEntry();
-                uidMap.put(pkg, entry);
-            }
-            return entry;
-        }
-
-        public PackageEntry getEntry(int uid, String pkg) {
-            ArrayMap<String, PackageEntry> uidMap = mEntries.get(uid);
-            if (uidMap == null) {
-                return null;
-            }
-            return uidMap.get(pkg);
-        }
-
-        long getTotalTime(long now) {
-            if (mSummedTime > 0) {
-                return mSummedTime;
-            }
-            return now - mStartUptimeTime;
-        }
-
-        void incPending(int uid, String pkg, long now) {
-            PackageEntry pe = getOrCreateEntry(uid, pkg);
-            if (pe.pendingNesting == 0) {
-                pe.pendingStartTime = now;
-                pe.pendingCount++;
-            }
-            pe.pendingNesting++;
-        }
-
-        void decPending(int uid, String pkg, long now) {
-            PackageEntry pe = getOrCreateEntry(uid, pkg);
-            if (pe.pendingNesting == 1) {
-                pe.pastPendingTime += now - pe.pendingStartTime;
-            }
-            pe.pendingNesting--;
-        }
-
-        void incActive(int uid, String pkg, long now) {
-            PackageEntry pe = getOrCreateEntry(uid, pkg);
-            if (pe.activeNesting == 0) {
-                pe.activeStartTime = now;
-                pe.activeCount++;
-            }
-            pe.activeNesting++;
-        }
-
-        void decActive(int uid, String pkg, long now, int stopReason) {
-            PackageEntry pe = getOrCreateEntry(uid, pkg);
-            if (pe.activeNesting == 1) {
-                pe.pastActiveTime += now - pe.activeStartTime;
-            }
-            pe.activeNesting--;
-            int count = pe.stopReasons.get(stopReason, 0);
-            pe.stopReasons.put(stopReason, count+1);
-        }
-
-        void incActiveTop(int uid, String pkg, long now) {
-            PackageEntry pe = getOrCreateEntry(uid, pkg);
-            if (pe.activeTopNesting == 0) {
-                pe.activeTopStartTime = now;
-                pe.activeTopCount++;
-            }
-            pe.activeTopNesting++;
-        }
-
-        void decActiveTop(int uid, String pkg, long now, int stopReason) {
-            PackageEntry pe = getOrCreateEntry(uid, pkg);
-            if (pe.activeTopNesting == 1) {
-                pe.pastActiveTopTime += now - pe.activeTopStartTime;
-            }
-            pe.activeTopNesting--;
-            int count = pe.stopReasons.get(stopReason, 0);
-            pe.stopReasons.put(stopReason, count+1);
-        }
-
-        void finish(DataSet next, long now) {
-            for (int i = mEntries.size() - 1; i >= 0; i--) {
-                ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i);
-                for (int j = uidMap.size() - 1; j >= 0; j--) {
-                    PackageEntry pe = uidMap.valueAt(j);
-                    if (pe.activeNesting > 0 || pe.activeTopNesting > 0 || pe.pendingNesting > 0) {
-                        // Propagate existing activity in to next data set.
-                        PackageEntry nextPe = next.getOrCreateEntry(mEntries.keyAt(i), uidMap.keyAt(j));
-                        nextPe.activeStartTime = now;
-                        nextPe.activeNesting = pe.activeNesting;
-                        nextPe.activeTopStartTime = now;
-                        nextPe.activeTopNesting = pe.activeTopNesting;
-                        nextPe.pendingStartTime = now;
-                        nextPe.pendingNesting = pe.pendingNesting;
-                        // Finish it off.
-                        if (pe.activeNesting > 0) {
-                            pe.pastActiveTime += now - pe.activeStartTime;
-                            pe.activeNesting = 0;
-                        }
-                        if (pe.activeTopNesting > 0) {
-                            pe.pastActiveTopTime += now - pe.activeTopStartTime;
-                            pe.activeTopNesting = 0;
-                        }
-                        if (pe.pendingNesting > 0) {
-                            pe.pastPendingTime += now - pe.pendingStartTime;
-                            pe.pendingNesting = 0;
-                        }
-                    }
-                }
-            }
-        }
-
-        void addTo(DataSet out, long now) {
-            out.mSummedTime += getTotalTime(now);
-            for (int i = mEntries.size() - 1; i >= 0; i--) {
-                ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i);
-                for (int j = uidMap.size() - 1; j >= 0; j--) {
-                    PackageEntry pe = uidMap.valueAt(j);
-                    PackageEntry outPe = out.getOrCreateEntry(mEntries.keyAt(i), uidMap.keyAt(j));
-                    outPe.pastActiveTime += pe.pastActiveTime;
-                    outPe.activeCount += pe.activeCount;
-                    outPe.pastActiveTopTime += pe.pastActiveTopTime;
-                    outPe.activeTopCount += pe.activeTopCount;
-                    outPe.pastPendingTime += pe.pastPendingTime;
-                    outPe.pendingCount += pe.pendingCount;
-                    if (pe.activeNesting > 0) {
-                        outPe.pastActiveTime += now - pe.activeStartTime;
-                        outPe.hadActive = true;
-                    }
-                    if (pe.activeTopNesting > 0) {
-                        outPe.pastActiveTopTime += now - pe.activeTopStartTime;
-                        outPe.hadActiveTop = true;
-                    }
-                    if (pe.pendingNesting > 0) {
-                        outPe.pastPendingTime += now - pe.pendingStartTime;
-                        outPe.hadPending = true;
-                    }
-                    for (int k = pe.stopReasons.size()-1; k >= 0; k--) {
-                        int type = pe.stopReasons.keyAt(k);
-                        outPe.stopReasons.put(type, outPe.stopReasons.get(type, 0)
-                                + pe.stopReasons.valueAt(k));
-                    }
-                }
-            }
-            if (mMaxTotalActive > out.mMaxTotalActive) {
-                out.mMaxTotalActive = mMaxTotalActive;
-            }
-            if (mMaxFgActive > out.mMaxFgActive) {
-                out.mMaxFgActive = mMaxFgActive;
-            }
-        }
-
-        void printDuration(PrintWriter pw, long period, long duration, int count, String suffix) {
-            float fraction = duration / (float) period;
-            int percent = (int) ((fraction * 100) + .5f);
-            if (percent > 0) {
-                pw.print(" ");
-                pw.print(percent);
-                pw.print("% ");
-                pw.print(count);
-                pw.print("x ");
-                pw.print(suffix);
-            } else if (count > 0) {
-                pw.print(" ");
-                pw.print(count);
-                pw.print("x ");
-                pw.print(suffix);
-            }
-        }
-
-        void dump(PrintWriter pw, String header, String prefix, long now, long nowElapsed,
-                int filterUid) {
-            final long period = getTotalTime(now);
-            pw.print(prefix); pw.print(header); pw.print(" at ");
-            pw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", mStartClockTime).toString());
-            pw.print(" (");
-            TimeUtils.formatDuration(mStartElapsedTime, nowElapsed, pw);
-            pw.print(") over ");
-            TimeUtils.formatDuration(period, pw);
-            pw.println(":");
-            final int NE = mEntries.size();
-            for (int i = 0; i < NE; i++) {
-                int uid = mEntries.keyAt(i);
-                if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) {
-                    continue;
-                }
-                ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i);
-                final int NP = uidMap.size();
-                for (int j = 0; j < NP; j++) {
-                    PackageEntry pe = uidMap.valueAt(j);
-                    pw.print(prefix); pw.print("  ");
-                    UserHandle.formatUid(pw, uid);
-                    pw.print(" / "); pw.print(uidMap.keyAt(j));
-                    pw.println(":");
-                    pw.print(prefix); pw.print("   ");
-                    printDuration(pw, period, pe.getPendingTime(now), pe.pendingCount, "pending");
-                    printDuration(pw, period, pe.getActiveTime(now), pe.activeCount, "active");
-                    printDuration(pw, period, pe.getActiveTopTime(now), pe.activeTopCount,
-                            "active-top");
-                    if (pe.pendingNesting > 0 || pe.hadPending) {
-                        pw.print(" (pending)");
-                    }
-                    if (pe.activeNesting > 0 || pe.hadActive) {
-                        pw.print(" (active)");
-                    }
-                    if (pe.activeTopNesting > 0 || pe.hadActiveTop) {
-                        pw.print(" (active-top)");
-                    }
-                    pw.println();
-                    if (pe.stopReasons.size() > 0) {
-                        pw.print(prefix); pw.print("    ");
-                        for (int k = 0; k < pe.stopReasons.size(); k++) {
-                            if (k > 0) {
-                                pw.print(", ");
-                            }
-                            pw.print(pe.stopReasons.valueAt(k));
-                            pw.print("x ");
-                            pw.print(JobParameters.getReasonName(pe.stopReasons.keyAt(k)));
-                        }
-                        pw.println();
-                    }
-                }
-            }
-            pw.print(prefix); pw.print("  Max concurrency: ");
-            pw.print(mMaxTotalActive); pw.print(" total, ");
-            pw.print(mMaxFgActive); pw.println(" foreground");
-        }
-
-        private void printPackageEntryState(ProtoOutputStream proto, long fieldId,
-                long duration, int count) {
-            final long token = proto.start(fieldId);
-            proto.write(DataSetProto.PackageEntryProto.State.DURATION_MS, duration);
-            proto.write(DataSetProto.PackageEntryProto.State.COUNT, count);
-            proto.end(token);
-        }
-
-        void dump(ProtoOutputStream proto, long fieldId, long now, long nowElapsed, int filterUid) {
-            final long token = proto.start(fieldId);
-            final long period = getTotalTime(now);
-
-            proto.write(DataSetProto.START_CLOCK_TIME_MS, mStartClockTime);
-            proto.write(DataSetProto.ELAPSED_TIME_MS, nowElapsed - mStartElapsedTime);
-            proto.write(DataSetProto.PERIOD_MS, period);
-
-            final int NE = mEntries.size();
-            for (int i = 0; i < NE; i++) {
-                int uid = mEntries.keyAt(i);
-                if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) {
-                    continue;
-                }
-                ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i);
-                final int NP = uidMap.size();
-                for (int j = 0; j < NP; j++) {
-                    final long peToken = proto.start(DataSetProto.PACKAGE_ENTRIES);
-                    PackageEntry pe = uidMap.valueAt(j);
-
-                    proto.write(DataSetProto.PackageEntryProto.UID, uid);
-                    proto.write(DataSetProto.PackageEntryProto.PACKAGE_NAME, uidMap.keyAt(j));
-
-                    printPackageEntryState(proto, DataSetProto.PackageEntryProto.PENDING_STATE,
-                            pe.getPendingTime(now), pe.pendingCount);
-                    printPackageEntryState(proto, DataSetProto.PackageEntryProto.ACTIVE_STATE,
-                            pe.getActiveTime(now), pe.activeCount);
-                    printPackageEntryState(proto, DataSetProto.PackageEntryProto.ACTIVE_TOP_STATE,
-                            pe.getActiveTopTime(now), pe.activeTopCount);
-
-                    proto.write(DataSetProto.PackageEntryProto.PENDING,
-                          pe.pendingNesting > 0 || pe.hadPending);
-                    proto.write(DataSetProto.PackageEntryProto.ACTIVE,
-                          pe.activeNesting > 0 || pe.hadActive);
-                    proto.write(DataSetProto.PackageEntryProto.ACTIVE_TOP,
-                          pe.activeTopNesting > 0 || pe.hadActiveTop);
-
-                    for (int k = 0; k < pe.stopReasons.size(); k++) {
-                        final long srcToken =
-                                proto.start(DataSetProto.PackageEntryProto.STOP_REASONS);
-
-                        proto.write(DataSetProto.PackageEntryProto.StopReasonCount.REASON,
-                                pe.stopReasons.keyAt(k));
-                        proto.write(DataSetProto.PackageEntryProto.StopReasonCount.COUNT,
-                                pe.stopReasons.valueAt(k));
-
-                        proto.end(srcToken);
-                    }
-
-                    proto.end(peToken);
-                }
-            }
-
-            proto.write(DataSetProto.MAX_CONCURRENCY, mMaxTotalActive);
-            proto.write(DataSetProto.MAX_FOREGROUND_CONCURRENCY, mMaxFgActive);
-
-            proto.end(token);
-        }
-    }
-
-    void rebatchIfNeeded(long now) {
-        long totalTime = mCurDataSet.getTotalTime(now);
-        if (totalTime > BATCHING_TIME) {
-            DataSet last = mCurDataSet;
-            last.mSummedTime = totalTime;
-            mCurDataSet = new DataSet();
-            last.finish(mCurDataSet, now);
-            System.arraycopy(mLastDataSets, 0, mLastDataSets, 1, mLastDataSets.length-1);
-            mLastDataSets[0] = last;
-        }
-    }
-
-    public void notePending(JobStatus job) {
-        final long now = sUptimeMillisClock.millis();
-        job.madePending = now;
-        rebatchIfNeeded(now);
-        mCurDataSet.incPending(job.getSourceUid(), job.getSourcePackageName(), now);
-    }
-
-    public void noteNonpending(JobStatus job) {
-        final long now = sUptimeMillisClock.millis();
-        mCurDataSet.decPending(job.getSourceUid(), job.getSourcePackageName(), now);
-        rebatchIfNeeded(now);
-    }
-
-    public void noteActive(JobStatus job) {
-        final long now = sUptimeMillisClock.millis();
-        job.madeActive = now;
-        rebatchIfNeeded(now);
-        if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
-            mCurDataSet.incActiveTop(job.getSourceUid(), job.getSourcePackageName(), now);
-        } else {
-            mCurDataSet.incActive(job.getSourceUid(), job.getSourcePackageName(), now);
-        }
-        addEvent(job.getJob().isPeriodic() ? EVENT_START_PERIODIC_JOB :  EVENT_START_JOB,
-                job.getSourceUid(), job.getBatteryName(), job.getJobId(), 0, null);
-    }
-
-    public void noteInactive(JobStatus job, int stopReason, String debugReason) {
-        final long now = sUptimeMillisClock.millis();
-        if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
-            mCurDataSet.decActiveTop(job.getSourceUid(), job.getSourcePackageName(), now,
-                    stopReason);
-        } else {
-            mCurDataSet.decActive(job.getSourceUid(), job.getSourcePackageName(), now, stopReason);
-        }
-        rebatchIfNeeded(now);
-        addEvent(job.getJob().isPeriodic() ? EVENT_STOP_JOB :  EVENT_STOP_PERIODIC_JOB,
-                job.getSourceUid(), job.getBatteryName(), job.getJobId(), stopReason, debugReason);
-    }
-
-    public void noteConcurrency(int totalActive, int fgActive) {
-        if (totalActive > mCurDataSet.mMaxTotalActive) {
-            mCurDataSet.mMaxTotalActive = totalActive;
-        }
-        if (fgActive > mCurDataSet.mMaxFgActive) {
-            mCurDataSet.mMaxFgActive = fgActive;
-        }
-    }
-
-    public float getLoadFactor(JobStatus job) {
-        final int uid = job.getSourceUid();
-        final String pkg = job.getSourcePackageName();
-        PackageEntry cur = mCurDataSet.getEntry(uid, pkg);
-        PackageEntry last = mLastDataSets[0] != null ? mLastDataSets[0].getEntry(uid, pkg) : null;
-        if (cur == null && last == null) {
-            return 0;
-        }
-        final long now = sUptimeMillisClock.millis();
-        long time = 0;
-        if (cur != null) {
-            time += cur.getActiveTime(now) + cur.getPendingTime(now);
-        }
-        long period = mCurDataSet.getTotalTime(now);
-        if (last != null) {
-            time += last.getActiveTime(now) + last.getPendingTime(now);
-            period += mLastDataSets[0].getTotalTime(now);
-        }
-        return time / (float)period;
-    }
-
-    public void dump(PrintWriter pw, String prefix, int filterUid) {
-        final long now = sUptimeMillisClock.millis();
-        final long nowElapsed = sElapsedRealtimeClock.millis();
-        final DataSet total;
-        if (mLastDataSets[0] != null) {
-            total = new DataSet(mLastDataSets[0]);
-            mLastDataSets[0].addTo(total, now);
-        } else {
-            total = new DataSet(mCurDataSet);
-        }
-        mCurDataSet.addTo(total, now);
-        for (int i = 1; i < mLastDataSets.length; i++) {
-            if (mLastDataSets[i] != null) {
-                mLastDataSets[i].dump(pw, "Historical stats", prefix, now, nowElapsed, filterUid);
-                pw.println();
-            }
-        }
-        total.dump(pw, "Current stats", prefix, now, nowElapsed, filterUid);
-    }
-
-    public void dump(ProtoOutputStream proto, long fieldId, int filterUid) {
-        final long token = proto.start(fieldId);
-        final long now = sUptimeMillisClock.millis();
-        final long nowElapsed = sElapsedRealtimeClock.millis();
-
-        final DataSet total;
-        if (mLastDataSets[0] != null) {
-            total = new DataSet(mLastDataSets[0]);
-            mLastDataSets[0].addTo(total, now);
-        } else {
-            total = new DataSet(mCurDataSet);
-        }
-        mCurDataSet.addTo(total, now);
-
-        for (int i = 1; i < mLastDataSets.length; i++) {
-            if (mLastDataSets[i] != null) {
-                mLastDataSets[i].dump(proto, JobPackageTrackerDumpProto.HISTORICAL_STATS,
-                        now, nowElapsed, filterUid);
-            }
-        }
-        total.dump(proto, JobPackageTrackerDumpProto.CURRENT_STATS,
-                now, nowElapsed, filterUid);
-
-        proto.end(token);
-    }
-
-    public boolean dumpHistory(PrintWriter pw, String prefix, int filterUid) {
-        final int size = mEventIndices.size();
-        if (size <= 0) {
-            return false;
-        }
-        pw.println("  Job history:");
-        final long now = sElapsedRealtimeClock.millis();
-        for (int i=0; i<size; i++) {
-            final int index = mEventIndices.indexOf(i);
-            final int uid = mEventUids[index];
-            if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) {
-                continue;
-            }
-            final int cmd = mEventCmds[index] & EVENT_CMD_MASK;
-            if (cmd == EVENT_NULL) {
-                continue;
-            }
-            final String label;
-            switch (cmd) {
-                case EVENT_START_JOB:           label = "  START"; break;
-                case EVENT_STOP_JOB:            label = "   STOP"; break;
-                case EVENT_START_PERIODIC_JOB:  label = "START-P"; break;
-                case EVENT_STOP_PERIODIC_JOB:   label = " STOP-P"; break;
-                default:                        label = "     ??"; break;
-            }
-            pw.print(prefix);
-            TimeUtils.formatDuration(mEventTimes[index]-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN);
-            pw.print(" ");
-            pw.print(label);
-            pw.print(": #");
-            UserHandle.formatUid(pw, uid);
-            pw.print("/");
-            pw.print(mEventJobIds[index]);
-            pw.print(" ");
-            pw.print(mEventTags[index]);
-            if (cmd == EVENT_STOP_JOB || cmd == EVENT_STOP_PERIODIC_JOB) {
-                pw.print(" ");
-                final String reason = mEventReasons[index];
-                if (reason != null) {
-                    pw.print(mEventReasons[index]);
-                } else {
-                    pw.print(JobParameters.getReasonName((mEventCmds[index] & EVENT_STOP_REASON_MASK)
-                            >> EVENT_STOP_REASON_SHIFT));
-                }
-            }
-            pw.println();
-        }
-        return true;
-    }
-
-    public void dumpHistory(ProtoOutputStream proto, long fieldId, int filterUid) {
-        final int size = mEventIndices.size();
-        if (size == 0) {
-            return;
-        }
-        final long token = proto.start(fieldId);
-
-        final long now = sElapsedRealtimeClock.millis();
-        for (int i = 0; i < size; i++) {
-            final int index = mEventIndices.indexOf(i);
-            final int uid = mEventUids[index];
-            if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) {
-                continue;
-            }
-            final int cmd = mEventCmds[index] & EVENT_CMD_MASK;
-            if (cmd == EVENT_NULL) {
-                continue;
-            }
-            final long heToken = proto.start(JobPackageHistoryProto.HISTORY_EVENT);
-
-            proto.write(JobPackageHistoryProto.HistoryEvent.EVENT, cmd);
-            proto.write(JobPackageHistoryProto.HistoryEvent.TIME_SINCE_EVENT_MS, now - mEventTimes[index]);
-            proto.write(JobPackageHistoryProto.HistoryEvent.UID, uid);
-            proto.write(JobPackageHistoryProto.HistoryEvent.JOB_ID, mEventJobIds[index]);
-            proto.write(JobPackageHistoryProto.HistoryEvent.TAG, mEventTags[index]);
-            if (cmd == EVENT_STOP_JOB || cmd == EVENT_STOP_PERIODIC_JOB) {
-                proto.write(JobPackageHistoryProto.HistoryEvent.STOP_REASON,
-                    (mEventCmds[index] & EVENT_STOP_REASON_MASK) >> EVENT_STOP_REASON_SHIFT);
-            }
-
-            proto.end(heToken);
-        }
-
-        proto.end(token);
-    }
-}
diff --git a/services/core/java/com/android/server/job/JobSchedulerInternal.java b/services/core/java/com/android/server/job/JobSchedulerInternal.java
deleted file mode 100644
index 425ec47..0000000
--- a/services/core/java/com/android/server/job/JobSchedulerInternal.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job;
-
-import android.annotation.UserIdInt;
-import android.app.job.JobInfo;
-
-import java.util.List;
-
-/**
- * JobScheduler local system service interface.
- * {@hide} Only for use within the system server.
- */
-public interface JobSchedulerInternal {
-
-    // Bookkeeping about app standby bucket scheduling
-
-    /**
-     * The current bucket heartbeat ordinal
-     */
-    long currentHeartbeat();
-
-    /**
-     * Heartbeat ordinal at which the given standby bucket's jobs next become runnable
-     */
-    long nextHeartbeatForBucket(int bucket);
-
-    /**
-     * Heartbeat ordinal for the given app.  This is typically the heartbeat at which
-     * the app last ran jobs, so that a newly-scheduled job in an app that hasn't run
-     * jobs in a long time is immediately runnable even if the app is bucketed into
-     * an infrequent time allocation.
-     */
-    public long baseHeartbeatForApp(String packageName, @UserIdInt int userId, int appBucket);
-
-    /**
-     * Tell the scheduler when a JobServiceContext starts running a job in an app
-     */
-    void noteJobStart(String packageName, int userId);
-
-    /**
-     * Returns a list of pending jobs scheduled by the system service.
-     */
-    List<JobInfo> getSystemScheduledPendingJobs();
-
-    /**
-     * Cancel the jobs for a given uid (e.g. when app data is cleared)
-     */
-    void cancelJobsForUid(int uid, String reason);
-
-    /**
-     * These are for activity manager to communicate to use what is currently performing backups.
-     */
-    void addBackingUpUid(int uid);
-    void removeBackingUpUid(int uid);
-    void clearAllBackingUpUids();
-
-    /**
-     * The user has started interacting with the app.  Take any appropriate action.
-     */
-    void reportAppUsage(String packageName, int userId);
-
-    /**
-     * Report a snapshot of sync-related jobs back to the sync manager
-     */
-    JobStorePersistStats getPersistStats();
-
-    /**
-     * Stats about the first load after boot and the most recent save.
-     */
-    public class JobStorePersistStats {
-        public int countAllJobsLoaded = -1;
-        public int countSystemServerJobsLoaded = -1;
-        public int countSystemSyncManagerJobsLoaded = -1;
-
-        public int countAllJobsSaved = -1;
-        public int countSystemServerJobsSaved = -1;
-        public int countSystemSyncManagerJobsSaved = -1;
-
-        public JobStorePersistStats() {
-        }
-
-        public JobStorePersistStats(JobStorePersistStats source) {
-            countAllJobsLoaded = source.countAllJobsLoaded;
-            countSystemServerJobsLoaded = source.countSystemServerJobsLoaded;
-            countSystemSyncManagerJobsLoaded = source.countSystemSyncManagerJobsLoaded;
-
-            countAllJobsSaved = source.countAllJobsSaved;
-            countSystemServerJobsSaved = source.countSystemServerJobsSaved;
-            countSystemSyncManagerJobsSaved = source.countSystemSyncManagerJobsSaved;
-        }
-
-        @Override
-        public String toString() {
-            return "FirstLoad: "
-                    + countAllJobsLoaded + "/"
-                    + countSystemServerJobsLoaded + "/"
-                    + countSystemSyncManagerJobsLoaded
-                    + " LastSave: "
-                    + countAllJobsSaved + "/"
-                    + countSystemServerJobsSaved + "/"
-                    + countSystemSyncManagerJobsSaved;
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
deleted file mode 100644
index e44e902..0000000
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ /dev/null
@@ -1,3657 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job;
-
-import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
-import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
-import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
-
-import android.annotation.NonNull;
-import android.annotation.UserIdInt;
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.ActivityManagerInternal;
-import android.app.AlarmManager;
-import android.app.AppGlobals;
-import android.app.IUidObserver;
-import android.app.job.IJobScheduler;
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
-import android.app.job.JobProtoEnums;
-import android.app.job.JobScheduler;
-import android.app.job.JobService;
-import android.app.job.JobSnapshot;
-import android.app.job.JobWorkItem;
-import android.app.usage.UsageStatsManager;
-import android.app.usage.UsageStatsManagerInternal;
-import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PackageManagerInternal;
-import android.content.pm.ParceledListSlice;
-import android.content.pm.ServiceInfo;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.BatteryStats;
-import android.os.BatteryStatsInternal;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IThermalService;
-import android.os.IThermalStatusListener;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.ServiceManager;
-import android.os.ShellCallback;
-import android.os.SystemClock;
-import android.os.Temperature;
-import android.os.UserHandle;
-import android.os.UserManagerInternal;
-import android.os.WorkSource;
-import android.provider.Settings;
-import android.text.format.DateUtils;
-import android.util.KeyValueListParser;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseIntArray;
-import android.util.StatsLog;
-import android.util.TimeUtils;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.DumpUtils;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Preconditions;
-import com.android.server.AppStateTracker;
-import com.android.server.DeviceIdleController;
-import com.android.server.FgThread;
-import com.android.server.LocalServices;
-import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
-import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
-import com.android.server.job.JobSchedulerServiceDumpProto.RegisteredJob;
-import com.android.server.job.controllers.BackgroundJobsController;
-import com.android.server.job.controllers.BatteryController;
-import com.android.server.job.controllers.ConnectivityController;
-import com.android.server.job.controllers.ContentObserverController;
-import com.android.server.job.controllers.DeviceIdleJobsController;
-import com.android.server.job.controllers.IdleController;
-import com.android.server.job.controllers.JobStatus;
-import com.android.server.job.controllers.QuotaController;
-import com.android.server.job.controllers.StateController;
-import com.android.server.job.controllers.StorageController;
-import com.android.server.job.controllers.TimeController;
-
-import libcore.util.EmptyArray;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.time.Clock;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-/**
- * Responsible for taking jobs representing work to be performed by a client app, and determining
- * based on the criteria specified when that job should be run against the client application's
- * endpoint.
- * Implements logic for scheduling, and rescheduling jobs. The JobSchedulerService knows nothing
- * about constraints, or the state of active jobs. It receives callbacks from the various
- * controllers and completed jobs and operates accordingly.
- *
- * Note on locking: Any operations that manipulate {@link #mJobs} need to lock on that object.
- * Any function with the suffix 'Locked' also needs to lock on {@link #mJobs}.
- * @hide
- */
-public class JobSchedulerService extends com.android.server.SystemService
-        implements StateChangedListener, JobCompletedListener {
-    public static final String TAG = "JobScheduler";
-    public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    public static final boolean DEBUG_STANDBY = DEBUG || false;
-
-    /** The maximum number of concurrent jobs we run at one time. */
-    static final int MAX_JOB_CONTEXTS_COUNT = 16;
-    /** Enforce a per-app limit on scheduled jobs? */
-    private static final boolean ENFORCE_MAX_JOBS = true;
-    /** The maximum number of jobs that we allow an unprivileged app to schedule */
-    private static final int MAX_JOBS_PER_APP = 100;
-
-    @VisibleForTesting
-    public static Clock sSystemClock = Clock.systemUTC();
-    @VisibleForTesting
-    public static Clock sUptimeMillisClock = SystemClock.uptimeClock();
-    @VisibleForTesting
-    public static Clock sElapsedRealtimeClock = SystemClock.elapsedRealtimeClock();
-
-    /** Global local for all job scheduler state. */
-    final Object mLock = new Object();
-    /** Master list of jobs. */
-    final JobStore mJobs;
-    /** Tracking the standby bucket state of each app */
-    final StandbyTracker mStandbyTracker;
-    /** Tracking amount of time each package runs for. */
-    final JobPackageTracker mJobPackageTracker = new JobPackageTracker();
-    final JobConcurrencyManager mConcurrencyManager;
-
-    static final int MSG_JOB_EXPIRED = 0;
-    static final int MSG_CHECK_JOB = 1;
-    static final int MSG_STOP_JOB = 2;
-    static final int MSG_CHECK_JOB_GREEDY = 3;
-    static final int MSG_UID_STATE_CHANGED = 4;
-    static final int MSG_UID_GONE = 5;
-    static final int MSG_UID_ACTIVE = 6;
-    static final int MSG_UID_IDLE = 7;
-
-    /**
-     * Track Services that have currently active or pending jobs. The index is provided by
-     * {@link JobStatus#getServiceToken()}
-     */
-    final List<JobServiceContext> mActiveServices = new ArrayList<>();
-
-    /** List of controllers that will notify this service of updates to jobs. */
-    final List<StateController> mControllers;
-    /** Need direct access to this for testing. */
-    private final BatteryController mBatteryController;
-    /** Need direct access to this for testing. */
-    private final StorageController mStorageController;
-    /** Need directly for sending uid state changes */
-    private final DeviceIdleJobsController mDeviceIdleJobsController;
-    /** Needed to get remaining quota time. */
-    private final QuotaController mQuotaController;
-
-    /** Need directly for receiving thermal events */
-    private IThermalService mThermalService;
-    /** Thermal constraint. */
-    @GuardedBy("mLock")
-    private boolean mThermalConstraint = false;
-
-    /**
-     * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
-     * when ready to execute them.
-     */
-    final ArrayList<JobStatus> mPendingJobs = new ArrayList<>();
-
-    int[] mStartedUsers = EmptyArray.INT;
-
-    final JobHandler mHandler;
-    final JobSchedulerStub mJobSchedulerStub;
-
-    PackageManagerInternal mLocalPM;
-    ActivityManagerInternal mActivityManagerInternal;
-    IBatteryStats mBatteryStats;
-    DeviceIdleController.LocalService mLocalDeviceIdleController;
-    AppStateTracker mAppStateTracker;
-    final UsageStatsManagerInternal mUsageStats;
-
-    /**
-     * Set to true once we are allowed to run third party apps.
-     */
-    boolean mReadyToRock;
-
-    /**
-     * What we last reported to DeviceIdleController about whether we are active.
-     */
-    boolean mReportedActive;
-
-    /**
-     * Are we currently in device-wide standby parole?
-     */
-    volatile boolean mInParole;
-
-    /**
-     * A mapping of which uids are currently in the foreground to their effective priority.
-     */
-    final SparseIntArray mUidPriorityOverride = new SparseIntArray();
-
-    /**
-     * Which uids are currently performing backups, so we shouldn't allow their jobs to run.
-     */
-    final SparseIntArray mBackingUpUids = new SparseIntArray();
-
-    /**
-     * Count standby heartbeats, and keep track of which beat each bucket's jobs will
-     * next become runnable.  Index into this array is by normalized bucket:
-     * { ACTIVE, WORKING, FREQUENT, RARE, NEVER }.  The ACTIVE and NEVER bucket
-     * milestones are not updated: ACTIVE apps get jobs whenever they ask for them,
-     * and NEVER apps don't get them at all.
-     */
-    final long[] mNextBucketHeartbeat = { 0, 0, 0, 0, Long.MAX_VALUE };
-    long mHeartbeat = 0;
-    long mLastHeartbeatTime = sElapsedRealtimeClock.millis();
-
-    /**
-     * Named indices into the STANDBY_BEATS array, for clarity in referring to
-     * specific buckets' bookkeeping.
-     */
-    public static final int ACTIVE_INDEX = 0;
-    public static final int WORKING_INDEX = 1;
-    public static final int FREQUENT_INDEX = 2;
-    public static final int RARE_INDEX = 3;
-    public static final int NEVER_INDEX = 4;
-
-    /**
-     * Bookkeeping about when jobs last run.  We keep our own record in heartbeat time,
-     * rather than rely on Usage Stats' timestamps, because heartbeat time can be
-     * manipulated for testing purposes and we need job runnability to track that rather
-     * than real time.
-     *
-     * Outer SparseArray slices by user handle; inner map of package name to heartbeat
-     * is a HashMap<> rather than ArrayMap<> because we expect O(hundreds) of keys
-     * and it will be accessed in a known-hot code path.
-     */
-    final SparseArray<HashMap<String, Long>> mLastJobHeartbeats = new SparseArray<>();
-
-    static final String HEARTBEAT_TAG = "*job.heartbeat*";
-    final HeartbeatAlarmListener mHeartbeatAlarm = new HeartbeatAlarmListener();
-
-    // -- Pre-allocated temporaries only for use in assignJobsToContextsLocked --
-
-    private class ConstantsObserver extends ContentObserver {
-        private ContentResolver mResolver;
-
-        public ConstantsObserver(Handler handler) {
-            super(handler);
-        }
-
-        public void start(ContentResolver resolver) {
-            mResolver = resolver;
-            mResolver.registerContentObserver(Settings.Global.getUriFor(
-                    Settings.Global.JOB_SCHEDULER_CONSTANTS), false, this);
-            updateConstants();
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            updateConstants();
-        }
-
-        private void updateConstants() {
-            synchronized (mLock) {
-                try {
-                    mConstants.updateConstantsLocked(Settings.Global.getString(mResolver,
-                            Settings.Global.JOB_SCHEDULER_CONSTANTS));
-                    for (int controller = 0; controller < mControllers.size(); controller++) {
-                        final StateController sc = mControllers.get(controller);
-                        sc.onConstantsUpdatedLocked();
-                    }
-                } catch (IllegalArgumentException e) {
-                    // Failed to parse the settings string, log this and move on
-                    // with defaults.
-                    Slog.e(TAG, "Bad jobscheduler settings", e);
-                }
-            }
-
-            if (mConstants.USE_HEARTBEATS) {
-                // Reset the heartbeat alarm based on the new heartbeat duration
-                setNextHeartbeatAlarm();
-            }
-        }
-    }
-
-    /**
-     *  Thermal event received from Thermal Service
-     */
-    private final class ThermalStatusListener extends IThermalStatusListener.Stub {
-        @Override public void onStatusChange(int status) {
-            // Throttle for Temperature.THROTTLING_SEVERE and above
-            synchronized (mLock) {
-                mThermalConstraint = status >= Temperature.THROTTLING_SEVERE;
-            }
-            onControllerStateChanged();
-        }
-    }
-
-    static class MaxJobCounts {
-        private final KeyValueListParser.IntValue mTotal;
-        private final KeyValueListParser.IntValue mMaxBg;
-        private final KeyValueListParser.IntValue mMinBg;
-
-        MaxJobCounts(int totalDefault, String totalKey,
-                int maxBgDefault, String maxBgKey, int minBgDefault, String minBgKey) {
-            mTotal = new KeyValueListParser.IntValue(totalKey, totalDefault);
-            mMaxBg = new KeyValueListParser.IntValue(maxBgKey, maxBgDefault);
-            mMinBg = new KeyValueListParser.IntValue(minBgKey, minBgDefault);
-        }
-
-        public void parse(KeyValueListParser parser) {
-            mTotal.parse(parser);
-            mMaxBg.parse(parser);
-            mMinBg.parse(parser);
-
-            if (mTotal.getValue() < 1) {
-                mTotal.setValue(1);
-            } else if (mTotal.getValue() > MAX_JOB_CONTEXTS_COUNT) {
-                mTotal.setValue(MAX_JOB_CONTEXTS_COUNT);
-            }
-
-            if (mMaxBg.getValue() < 1) {
-                mMaxBg.setValue(1);
-            } else if (mMaxBg.getValue() > mTotal.getValue()) {
-                mMaxBg.setValue(mTotal.getValue());
-            }
-            if (mMinBg.getValue() < 0) {
-                mMinBg.setValue(0);
-            } else {
-                if (mMinBg.getValue() > mMaxBg.getValue()) {
-                    mMinBg.setValue(mMaxBg.getValue());
-                }
-                if (mMinBg.getValue() >= mTotal.getValue()) {
-                    mMinBg.setValue(mTotal.getValue() - 1);
-                }
-            }
-        }
-
-        /** Total number of jobs to run simultaneously. */
-        public int getMaxTotal() {
-            return mTotal.getValue();
-        }
-
-        /** Max number of BG (== owned by non-TOP apps) jobs to run simultaneously. */
-        public int getMaxBg() {
-            return mMaxBg.getValue();
-        }
-
-        /**
-         * 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.getValue();
-        }
-
-        public void dump(PrintWriter pw, String prefix) {
-            mTotal.dump(pw, prefix);
-            mMaxBg.dump(pw, prefix);
-            mMinBg.dump(pw, prefix);
-        }
-
-        public void dumpProto(ProtoOutputStream proto, long fieldId) {
-            final long token = proto.start(fieldId);
-            mTotal.dumpProto(proto, MaxJobCountsProto.TOTAL_JOBS);
-            mMaxBg.dumpProto(proto, MaxJobCountsProto.MAX_BG);
-            mMinBg.dumpProto(proto, MaxJobCountsProto.MIN_BG);
-            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. These constants are kept synchronized with the system
-     * global Settings. Any access to this class or its fields should be done while
-     * holding the JobSchedulerService.mLock lock.
-     */
-    public static class Constants {
-        // Key names stored in the settings value.
-        private static final String KEY_MIN_IDLE_COUNT = "min_idle_count";
-        private static final String KEY_MIN_CHARGING_COUNT = "min_charging_count";
-        private static final String KEY_MIN_BATTERY_NOT_LOW_COUNT = "min_battery_not_low_count";
-        private static final String KEY_MIN_STORAGE_NOT_LOW_COUNT = "min_storage_not_low_count";
-        private static final String KEY_MIN_CONNECTIVITY_COUNT = "min_connectivity_count";
-        private static final String KEY_MIN_CONTENT_COUNT = "min_content_count";
-        private static final String KEY_MIN_READY_JOBS_COUNT = "min_ready_jobs_count";
-        private static final String KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT =
-                "min_ready_non_active_jobs_count";
-        private static final String KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS =
-                "max_non_active_job_batch_delay_ms";
-        private static final String KEY_HEAVY_USE_FACTOR = "heavy_use_factor";
-        private static final String KEY_MODERATE_USE_FACTOR = "moderate_use_factor";
-
-        // The following values used to be used on P and below. Do not reuse them.
-        private static final String DEPRECATED_KEY_FG_JOB_COUNT = "fg_job_count";
-        private static final String DEPRECATED_KEY_BG_NORMAL_JOB_COUNT = "bg_normal_job_count";
-        private static final String DEPRECATED_KEY_BG_MODERATE_JOB_COUNT = "bg_moderate_job_count";
-        private static final String DEPRECATED_KEY_BG_LOW_JOB_COUNT = "bg_low_job_count";
-        private static final String DEPRECATED_KEY_BG_CRITICAL_JOB_COUNT = "bg_critical_job_count";
-
-        private static final String KEY_MAX_STANDARD_RESCHEDULE_COUNT
-                = "max_standard_reschedule_count";
-        private static final String KEY_MAX_WORK_RESCHEDULE_COUNT = "max_work_reschedule_count";
-        private static final String KEY_MIN_LINEAR_BACKOFF_TIME = "min_linear_backoff_time";
-        private static final String KEY_MIN_EXP_BACKOFF_TIME = "min_exp_backoff_time";
-        private static final String KEY_STANDBY_HEARTBEAT_TIME = "standby_heartbeat_time";
-        private static final String KEY_STANDBY_WORKING_BEATS = "standby_working_beats";
-        private static final String KEY_STANDBY_FREQUENT_BEATS = "standby_frequent_beats";
-        private static final String KEY_STANDBY_RARE_BEATS = "standby_rare_beats";
-        private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac";
-        private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac";
-        private static final String KEY_USE_HEARTBEATS = "use_heartbeats";
-
-        private static final int DEFAULT_MIN_IDLE_COUNT = 1;
-        private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
-        private static final int DEFAULT_MIN_BATTERY_NOT_LOW_COUNT = 1;
-        private static final int DEFAULT_MIN_STORAGE_NOT_LOW_COUNT = 1;
-        private static final int DEFAULT_MIN_CONNECTIVITY_COUNT = 1;
-        private static final int DEFAULT_MIN_CONTENT_COUNT = 1;
-        private static final int DEFAULT_MIN_READY_JOBS_COUNT = 1;
-        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;
-        private static final float DEFAULT_MODERATE_USE_FACTOR = .5f;
-        private static final int DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT = Integer.MAX_VALUE;
-        private static final int DEFAULT_MAX_WORK_RESCHEDULE_COUNT = Integer.MAX_VALUE;
-        private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS;
-        private static final long DEFAULT_MIN_EXP_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS;
-        private static final long DEFAULT_STANDBY_HEARTBEAT_TIME = 11 * 60 * 1000L;
-        private static final int DEFAULT_STANDBY_WORKING_BEATS = 11;  // ~ 2 hours, with 11min beats
-        private static final int DEFAULT_STANDBY_FREQUENT_BEATS = 43; // ~ 8 hours
-        private static final int DEFAULT_STANDBY_RARE_BEATS = 130; // ~ 24 hours
-        private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
-        private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
-        private static final boolean DEFAULT_USE_HEARTBEATS = false;
-
-        /**
-         * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
-         * early.
-         */
-        int MIN_IDLE_COUNT = DEFAULT_MIN_IDLE_COUNT;
-        /**
-         * Minimum # of charging jobs that must be ready in order to force the JMS to schedule
-         * things early.
-         */
-        int MIN_CHARGING_COUNT = DEFAULT_MIN_CHARGING_COUNT;
-        /**
-         * Minimum # of "battery not low" jobs that must be ready in order to force the JMS to
-         * schedule things early.
-         */
-        int MIN_BATTERY_NOT_LOW_COUNT = DEFAULT_MIN_BATTERY_NOT_LOW_COUNT;
-        /**
-         * Minimum # of "storage not low" jobs that must be ready in order to force the JMS to
-         * schedule things early.
-         */
-        int MIN_STORAGE_NOT_LOW_COUNT = DEFAULT_MIN_STORAGE_NOT_LOW_COUNT;
-        /**
-         * Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule
-         * things early.  1 == Run connectivity jobs as soon as ready.
-         */
-        int MIN_CONNECTIVITY_COUNT = DEFAULT_MIN_CONNECTIVITY_COUNT;
-        /**
-         * Minimum # of content trigger jobs that must be ready in order to force the JMS to
-         * schedule things early.
-         */
-        int MIN_CONTENT_COUNT = DEFAULT_MIN_CONTENT_COUNT;
-        /**
-         * Minimum # of jobs (with no particular constraints) for which the JMS will be happy
-         * running some work early.  This (and thus the other min counts) is now set to 1, to
-         * prevent any batching at this level.  Since we now do batching through doze, that is
-         * a much better mechanism.
-         */
-        int MIN_READY_JOBS_COUNT = DEFAULT_MIN_READY_JOBS_COUNT;
-
-        /**
-         * Minimum # of non-ACTIVE jobs for which the JMS will be happy running some work early.
-         */
-        int MIN_READY_NON_ACTIVE_JOBS_COUNT = DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT;
-
-        /**
-         * Don't batch a non-ACTIVE job if it's been delayed due to force batching attempts for
-         * at least this amount of time.
-         */
-        long MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS;
-
-        /**
-         * This is the job execution factor that is considered to be heavy use of the system.
-         */
-        float HEAVY_USE_FACTOR = DEFAULT_HEAVY_USE_FACTOR;
-        /**
-         * This is the job execution factor that is considered to be moderate use of the system.
-         */
-        float MODERATE_USE_FACTOR = DEFAULT_MODERATE_USE_FACTOR;
-
-        // Max job counts for screen on / off, for each memory trim level.
-        final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_ON =
-                new MaxJobCountsPerMemoryTrimLevel(
-                        new MaxJobCounts(
-                                8, "max_job_total_on_normal",
-                                6, "max_job_max_bg_on_normal",
-                                2, "max_job_min_bg_on_normal"),
-                        new MaxJobCounts(
-                                8, "max_job_total_on_moderate",
-                                4, "max_job_max_bg_on_moderate",
-                                2, "max_job_min_bg_on_moderate"),
-                        new MaxJobCounts(
-                                5, "max_job_total_on_low",
-                                1, "max_job_max_bg_on_low",
-                                1, "max_job_min_bg_on_low"),
-                        new MaxJobCounts(
-                                5, "max_job_total_on_critical",
-                                1, "max_job_max_bg_on_critical",
-                                1, "max_job_min_bg_on_critical"));
-
-        final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_OFF =
-                new MaxJobCountsPerMemoryTrimLevel(
-                        new MaxJobCounts(
-                                10, "max_job_total_off_normal",
-                                6, "max_job_max_bg_off_normal",
-                                2, "max_job_min_bg_off_normal"),
-                        new MaxJobCounts(
-                                10, "max_job_total_off_moderate",
-                                4, "max_job_max_bg_off_moderate",
-                                2, "max_job_min_bg_off_moderate"),
-                        new MaxJobCounts(
-                                5, "max_job_total_off_low",
-                                1, "max_job_max_bg_off_low",
-                                1, "max_job_min_bg_off_low"),
-                        new MaxJobCounts(
-                                5, "max_job_total_off_critical",
-                                1, "max_job_max_bg_off_critical",
-                                1, "max_job_min_bg_off_critical"));
-
-
-        /** Wait for this long after screen off before increasing the job concurrency. */
-        final KeyValueListParser.IntValue SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS =
-                new KeyValueListParser.IntValue(
-                        "screen_off_job_concurrency_increase_delay_ms", 30_000);
-
-        /**
-         * The maximum number of times we allow a job to have itself rescheduled before
-         * giving up on it, for standard jobs.
-         */
-        int MAX_STANDARD_RESCHEDULE_COUNT = DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT;
-        /**
-         * The maximum number of times we allow a job to have itself rescheduled before
-         * giving up on it, for jobs that are executing work.
-         */
-        int MAX_WORK_RESCHEDULE_COUNT = DEFAULT_MAX_WORK_RESCHEDULE_COUNT;
-        /**
-         * The minimum backoff time to allow for linear backoff.
-         */
-        long MIN_LINEAR_BACKOFF_TIME = DEFAULT_MIN_LINEAR_BACKOFF_TIME;
-        /**
-         * The minimum backoff time to allow for exponential backoff.
-         */
-        long MIN_EXP_BACKOFF_TIME = DEFAULT_MIN_EXP_BACKOFF_TIME;
-        /**
-         * How often we recalculate runnability based on apps' standby bucket assignment.
-         * This should be prime relative to common time interval lengths such as a quarter-
-         * hour or day, so that the heartbeat drifts relative to wall-clock milestones.
-         */
-        long STANDBY_HEARTBEAT_TIME = DEFAULT_STANDBY_HEARTBEAT_TIME;
-        /**
-         * Mapping: standby bucket -> number of heartbeats between each sweep of that
-         * bucket's jobs.
-         *
-         * Bucket assignments as recorded in the JobStatus objects are normalized to be
-         * indices into this array, rather than the raw constants used
-         * by AppIdleHistory.
-         */
-        final int[] STANDBY_BEATS = {
-                0,
-                DEFAULT_STANDBY_WORKING_BEATS,
-                DEFAULT_STANDBY_FREQUENT_BEATS,
-                DEFAULT_STANDBY_RARE_BEATS
-        };
-        /**
-         * The fraction of a job's running window that must pass before we
-         * consider running it when the network is congested.
-         */
-        public float CONN_CONGESTION_DELAY_FRAC = DEFAULT_CONN_CONGESTION_DELAY_FRAC;
-        /**
-         * The fraction of a prefetch job's running window that must pass before
-         * we consider matching it against a metered network.
-         */
-        public float CONN_PREFETCH_RELAX_FRAC = DEFAULT_CONN_PREFETCH_RELAX_FRAC;
-        /**
-         * Whether to use heartbeats or rolling window for quota management. True will use
-         * heartbeats, false will use a rolling window.
-         */
-        public boolean USE_HEARTBEATS = DEFAULT_USE_HEARTBEATS;
-
-        private final KeyValueListParser mParser = new KeyValueListParser(',');
-
-        void updateConstantsLocked(String value) {
-            try {
-                mParser.setString(value);
-            } catch (Exception e) {
-                // Failed to parse the settings string, log this and move on
-                // with defaults.
-                Slog.e(TAG, "Bad jobscheduler settings", e);
-            }
-
-            MIN_IDLE_COUNT = mParser.getInt(KEY_MIN_IDLE_COUNT,
-                    DEFAULT_MIN_IDLE_COUNT);
-            MIN_CHARGING_COUNT = mParser.getInt(KEY_MIN_CHARGING_COUNT,
-                    DEFAULT_MIN_CHARGING_COUNT);
-            MIN_BATTERY_NOT_LOW_COUNT = mParser.getInt(KEY_MIN_BATTERY_NOT_LOW_COUNT,
-                    DEFAULT_MIN_BATTERY_NOT_LOW_COUNT);
-            MIN_STORAGE_NOT_LOW_COUNT = mParser.getInt(KEY_MIN_STORAGE_NOT_LOW_COUNT,
-                    DEFAULT_MIN_STORAGE_NOT_LOW_COUNT);
-            MIN_CONNECTIVITY_COUNT = mParser.getInt(KEY_MIN_CONNECTIVITY_COUNT,
-                    DEFAULT_MIN_CONNECTIVITY_COUNT);
-            MIN_CONTENT_COUNT = mParser.getInt(KEY_MIN_CONTENT_COUNT,
-                    DEFAULT_MIN_CONTENT_COUNT);
-            MIN_READY_JOBS_COUNT = mParser.getInt(KEY_MIN_READY_JOBS_COUNT,
-                    DEFAULT_MIN_READY_JOBS_COUNT);
-            MIN_READY_NON_ACTIVE_JOBS_COUNT = mParser.getInt(
-                    KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT,
-                    DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT);
-            MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = mParser.getLong(
-                    KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS,
-                    DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS);
-            HEAVY_USE_FACTOR = mParser.getFloat(KEY_HEAVY_USE_FACTOR,
-                    DEFAULT_HEAVY_USE_FACTOR);
-            MODERATE_USE_FACTOR = mParser.getFloat(KEY_MODERATE_USE_FACTOR,
-                    DEFAULT_MODERATE_USE_FACTOR);
-
-            MAX_JOB_COUNTS_SCREEN_ON.normal.parse(mParser);
-            MAX_JOB_COUNTS_SCREEN_ON.moderate.parse(mParser);
-            MAX_JOB_COUNTS_SCREEN_ON.low.parse(mParser);
-            MAX_JOB_COUNTS_SCREEN_ON.critical.parse(mParser);
-
-            MAX_JOB_COUNTS_SCREEN_OFF.normal.parse(mParser);
-            MAX_JOB_COUNTS_SCREEN_OFF.moderate.parse(mParser);
-            MAX_JOB_COUNTS_SCREEN_OFF.low.parse(mParser);
-            MAX_JOB_COUNTS_SCREEN_OFF.critical.parse(mParser);
-
-            SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.parse(mParser);
-
-            MAX_STANDARD_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_STANDARD_RESCHEDULE_COUNT,
-                    DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT);
-            MAX_WORK_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_WORK_RESCHEDULE_COUNT,
-                    DEFAULT_MAX_WORK_RESCHEDULE_COUNT);
-            MIN_LINEAR_BACKOFF_TIME = mParser.getDurationMillis(KEY_MIN_LINEAR_BACKOFF_TIME,
-                    DEFAULT_MIN_LINEAR_BACKOFF_TIME);
-            MIN_EXP_BACKOFF_TIME = mParser.getDurationMillis(KEY_MIN_EXP_BACKOFF_TIME,
-                    DEFAULT_MIN_EXP_BACKOFF_TIME);
-            STANDBY_HEARTBEAT_TIME = mParser.getDurationMillis(KEY_STANDBY_HEARTBEAT_TIME,
-                    DEFAULT_STANDBY_HEARTBEAT_TIME);
-            STANDBY_BEATS[WORKING_INDEX] = mParser.getInt(KEY_STANDBY_WORKING_BEATS,
-                    DEFAULT_STANDBY_WORKING_BEATS);
-            STANDBY_BEATS[FREQUENT_INDEX] = mParser.getInt(KEY_STANDBY_FREQUENT_BEATS,
-                    DEFAULT_STANDBY_FREQUENT_BEATS);
-            STANDBY_BEATS[RARE_INDEX] = mParser.getInt(KEY_STANDBY_RARE_BEATS,
-                    DEFAULT_STANDBY_RARE_BEATS);
-            CONN_CONGESTION_DELAY_FRAC = mParser.getFloat(KEY_CONN_CONGESTION_DELAY_FRAC,
-                    DEFAULT_CONN_CONGESTION_DELAY_FRAC);
-            CONN_PREFETCH_RELAX_FRAC = mParser.getFloat(KEY_CONN_PREFETCH_RELAX_FRAC,
-                    DEFAULT_CONN_PREFETCH_RELAX_FRAC);
-            USE_HEARTBEATS = mParser.getBoolean(KEY_USE_HEARTBEATS, DEFAULT_USE_HEARTBEATS);
-        }
-
-        void dump(IndentingPrintWriter pw) {
-            pw.println("Settings:");
-            pw.increaseIndent();
-            pw.printPair(KEY_MIN_IDLE_COUNT, MIN_IDLE_COUNT).println();
-            pw.printPair(KEY_MIN_CHARGING_COUNT, MIN_CHARGING_COUNT).println();
-            pw.printPair(KEY_MIN_BATTERY_NOT_LOW_COUNT, MIN_BATTERY_NOT_LOW_COUNT).println();
-            pw.printPair(KEY_MIN_STORAGE_NOT_LOW_COUNT, MIN_STORAGE_NOT_LOW_COUNT).println();
-            pw.printPair(KEY_MIN_CONNECTIVITY_COUNT, MIN_CONNECTIVITY_COUNT).println();
-            pw.printPair(KEY_MIN_CONTENT_COUNT, MIN_CONTENT_COUNT).println();
-            pw.printPair(KEY_MIN_READY_JOBS_COUNT, MIN_READY_JOBS_COUNT).println();
-            pw.printPair(KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT,
-                    MIN_READY_NON_ACTIVE_JOBS_COUNT).println();
-            pw.printPair(KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS,
-                    MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS).println();
-            pw.printPair(KEY_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println();
-            pw.printPair(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, "");
-
-            SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dump(pw, "");
-
-            pw.printPair(KEY_MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT).println();
-            pw.printPair(KEY_MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT).println();
-            pw.printPair(KEY_MIN_LINEAR_BACKOFF_TIME, MIN_LINEAR_BACKOFF_TIME).println();
-            pw.printPair(KEY_MIN_EXP_BACKOFF_TIME, MIN_EXP_BACKOFF_TIME).println();
-            pw.printPair(KEY_STANDBY_HEARTBEAT_TIME, STANDBY_HEARTBEAT_TIME).println();
-            pw.print("standby_beats={");
-            pw.print(STANDBY_BEATS[0]);
-            for (int i = 1; i < STANDBY_BEATS.length; i++) {
-                pw.print(", ");
-                pw.print(STANDBY_BEATS[i]);
-            }
-            pw.println('}');
-            pw.printPair(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
-            pw.printPair(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
-            pw.printPair(KEY_USE_HEARTBEATS, USE_HEARTBEATS).println();
-
-            pw.decreaseIndent();
-        }
-
-        void dump(ProtoOutputStream proto) {
-            proto.write(ConstantsProto.MIN_IDLE_COUNT, MIN_IDLE_COUNT);
-            proto.write(ConstantsProto.MIN_CHARGING_COUNT, MIN_CHARGING_COUNT);
-            proto.write(ConstantsProto.MIN_BATTERY_NOT_LOW_COUNT, MIN_BATTERY_NOT_LOW_COUNT);
-            proto.write(ConstantsProto.MIN_STORAGE_NOT_LOW_COUNT, MIN_STORAGE_NOT_LOW_COUNT);
-            proto.write(ConstantsProto.MIN_CONNECTIVITY_COUNT, MIN_CONNECTIVITY_COUNT);
-            proto.write(ConstantsProto.MIN_CONTENT_COUNT, MIN_CONTENT_COUNT);
-            proto.write(ConstantsProto.MIN_READY_JOBS_COUNT, MIN_READY_JOBS_COUNT);
-            proto.write(ConstantsProto.MIN_READY_NON_ACTIVE_JOBS_COUNT,
-                    MIN_READY_NON_ACTIVE_JOBS_COUNT);
-            proto.write(ConstantsProto.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS,
-                    MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS);
-            proto.write(ConstantsProto.HEAVY_USE_FACTOR, HEAVY_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);
-
-            SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dumpProto(proto,
-                    ConstantsProto.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS);
-
-            proto.write(ConstantsProto.MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT);
-            proto.write(ConstantsProto.MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT);
-            proto.write(ConstantsProto.MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME);
-            proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME);
-            proto.write(ConstantsProto.STANDBY_HEARTBEAT_TIME_MS, STANDBY_HEARTBEAT_TIME);
-            for (int period : STANDBY_BEATS) {
-                proto.write(ConstantsProto.STANDBY_BEATS, period);
-            }
-            proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC);
-            proto.write(ConstantsProto.CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC);
-            proto.write(ConstantsProto.USE_HEARTBEATS, USE_HEARTBEATS);
-        }
-    }
-
-    final Constants mConstants;
-    final ConstantsObserver mConstantsObserver;
-
-    static final Comparator<JobStatus> mEnqueueTimeComparator = (o1, o2) -> {
-        if (o1.enqueueTime < o2.enqueueTime) {
-            return -1;
-        }
-        return o1.enqueueTime > o2.enqueueTime ? 1 : 0;
-    };
-
-    static <T> void addOrderedItem(ArrayList<T> array, T newItem, Comparator<T> comparator) {
-        int where = Collections.binarySearch(array, newItem, comparator);
-        if (where < 0) {
-            where = ~where;
-        }
-        array.add(where, newItem);
-    }
-
-    /**
-     * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
-     * still clean up. On reinstall the package will have a new uid.
-     */
-    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (DEBUG) {
-                Slog.d(TAG, "Receieved: " + action);
-            }
-            final String pkgName = getPackageName(intent);
-            final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
-
-            if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
-                // Purge the app's jobs if the whole package was just disabled.  When this is
-                // the case the component name will be a bare package name.
-                if (pkgName != null && pkgUid != -1) {
-                    final String[] changedComponents = intent.getStringArrayExtra(
-                            Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
-                    if (changedComponents != null) {
-                        for (String component : changedComponents) {
-                            if (component.equals(pkgName)) {
-                                if (DEBUG) {
-                                    Slog.d(TAG, "Package state change: " + pkgName);
-                                }
-                                try {
-                                    final int userId = UserHandle.getUserId(pkgUid);
-                                    IPackageManager pm = AppGlobals.getPackageManager();
-                                    final int state = pm.getApplicationEnabledSetting(pkgName, userId);
-                                    if (state == COMPONENT_ENABLED_STATE_DISABLED
-                                            || state ==  COMPONENT_ENABLED_STATE_DISABLED_USER) {
-                                        if (DEBUG) {
-                                            Slog.d(TAG, "Removing jobs for package " + pkgName
-                                                    + " in user " + userId);
-                                        }
-                                        cancelJobsForPackageAndUid(pkgName, pkgUid,
-                                                "app disabled");
-                                    }
-                                } catch (RemoteException|IllegalArgumentException e) {
-                                    /*
-                                     * IllegalArgumentException means that the package doesn't exist.
-                                     * This arises when PACKAGE_CHANGED broadcast delivery has lagged
-                                     * behind outright uninstall, so by the time we try to act it's gone.
-                                     * We don't need to act on this PACKAGE_CHANGED when this happens;
-                                     * we'll get a PACKAGE_REMOVED later and clean up then.
-                                     *
-                                     * RemoteException can't actually happen; the package manager is
-                                     * running in this same process.
-                                     */
-                                }
-                                break;
-                            }
-                        }
-                        if (DEBUG) {
-                            Slog.d(TAG, "Something in " + pkgName
-                                    + " changed. Reevaluating controller states.");
-                        }
-                        synchronized (mLock) {
-                            for (int c = mControllers.size() - 1; c >= 0; --c) {
-                                mControllers.get(c).reevaluateStateLocked(pkgUid);
-                            }
-                        }
-                    }
-                } else {
-                    Slog.w(TAG, "PACKAGE_CHANGED for " + pkgName + " / uid " + pkgUid);
-                }
-            } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
-                // If this is an outright uninstall rather than the first half of an
-                // app update sequence, cancel the jobs associated with the app.
-                if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
-                    int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1);
-                    if (DEBUG) {
-                        Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
-                    }
-                    cancelJobsForPackageAndUid(pkgName, uidRemoved, "app uninstalled");
-                    synchronized (mLock) {
-                        for (int c = 0; c < mControllers.size(); ++c) {
-                            mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid);
-                        }
-                    }
-                }
-            } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
-                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
-                if (DEBUG) {
-                    Slog.d(TAG, "Removing jobs for user: " + userId);
-                }
-                cancelJobsForUser(userId);
-                synchronized (mLock) {
-                    for (int c = 0; c < mControllers.size(); ++c) {
-                        mControllers.get(c).onUserRemovedLocked(userId);
-                    }
-                }
-            } else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
-                // Has this package scheduled any jobs, such that we will take action
-                // if it were to be force-stopped?
-                if (pkgUid != -1) {
-                    List<JobStatus> jobsForUid;
-                    synchronized (mLock) {
-                        jobsForUid = mJobs.getJobsByUid(pkgUid);
-                    }
-                    for (int i = jobsForUid.size() - 1; i >= 0; i--) {
-                        if (jobsForUid.get(i).getSourcePackageName().equals(pkgName)) {
-                            if (DEBUG) {
-                                Slog.d(TAG, "Restart query: package " + pkgName + " at uid "
-                                        + pkgUid + " has jobs");
-                            }
-                            setResultCode(Activity.RESULT_OK);
-                            break;
-                        }
-                    }
-                }
-            } else if (Intent.ACTION_PACKAGE_RESTARTED.equals(action)) {
-                // possible force-stop
-                if (pkgUid != -1) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Removing jobs for pkg " + pkgName + " at uid " + pkgUid);
-                    }
-                    cancelJobsForPackageAndUid(pkgName, pkgUid, "app force stopped");
-                }
-            }
-        }
-    };
-
-    private String getPackageName(Intent intent) {
-        Uri uri = intent.getData();
-        String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
-        return pkg;
-    }
-
-    final private IUidObserver mUidObserver = new IUidObserver.Stub() {
-        @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
-            mHandler.obtainMessage(MSG_UID_STATE_CHANGED, uid, procState).sendToTarget();
-        }
-
-        @Override public void onUidGone(int uid, boolean disabled) {
-            mHandler.obtainMessage(MSG_UID_GONE, uid, disabled ? 1 : 0).sendToTarget();
-        }
-
-        @Override public void onUidActive(int uid) throws RemoteException {
-            mHandler.obtainMessage(MSG_UID_ACTIVE, uid, 0).sendToTarget();
-        }
-
-        @Override public void onUidIdle(int uid, boolean disabled) {
-            mHandler.obtainMessage(MSG_UID_IDLE, uid, disabled ? 1 : 0).sendToTarget();
-        }
-
-        @Override public void onUidCachedChanged(int uid, boolean cached) {
-        }
-    };
-
-    public Context getTestableContext() {
-        return getContext();
-    }
-
-    public Object getLock() {
-        return mLock;
-    }
-
-    public JobStore getJobStore() {
-        return mJobs;
-    }
-
-    public Constants getConstants() {
-        return mConstants;
-    }
-
-    public boolean isChainedAttributionEnabled() {
-        return WorkSource.isChainedBatteryAttributionEnabled(getContext());
-    }
-
-    @Override
-    public void onStartUser(int userHandle) {
-        synchronized (mLock) {
-            mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle);
-        }
-        // Let's kick any outstanding jobs for this user.
-        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
-    }
-
-    @Override
-    public void onUnlockUser(int userHandle) {
-        // Let's kick any outstanding jobs for this user.
-        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
-    }
-
-    @Override
-    public void onStopUser(int userHandle) {
-        synchronized (mLock) {
-            mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
-        }
-    }
-
-    /**
-     * Return whether an UID is active or idle.
-     */
-    private boolean isUidActive(int uid) {
-        return mAppStateTracker.isUidActiveSynced(uid);
-    }
-
-    private final Predicate<Integer> mIsUidActivePredicate = this::isUidActive;
-
-    public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
-            int userId, String tag) {
-        try {
-            if (ActivityManager.getService().isAppStartModeDisabled(uId,
-                    job.getService().getPackageName())) {
-                Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
-                        + " -- package not allowed to start");
-                return JobScheduler.RESULT_FAILURE;
-            }
-        } catch (RemoteException e) {
-        }
-
-        synchronized (mLock) {
-            final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
-
-            if (work != null && toCancel != null) {
-                // Fast path: we are adding work to an existing job, and the JobInfo is not
-                // changing.  We can just directly enqueue this work in to the job.
-                if (toCancel.getJob().equals(job)) {
-
-                    toCancel.enqueueWorkLocked(ActivityManager.getService(), work);
-
-                    // If any of work item is enqueued when the source is in the foreground,
-                    // exempt the entire job.
-                    toCancel.maybeAddForegroundExemption(mIsUidActivePredicate);
-
-                    return JobScheduler.RESULT_SUCCESS;
-                }
-            }
-
-            JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
-
-            // Give exemption if the source is in the foreground just now.
-            // Note if it's a sync job, this method is called on the handler so it's not exactly
-            // the state when requestSync() was called, but that should be fine because of the
-            // 1 minute foreground grace period.
-            jobStatus.maybeAddForegroundExemption(mIsUidActivePredicate);
-
-            if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
-            // Jobs on behalf of others don't apply to the per-app job cap
-            if (ENFORCE_MAX_JOBS && packageName == null) {
-                if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
-                    Slog.w(TAG, "Too many jobs for uid " + uId);
-                    throw new IllegalStateException("Apps may not schedule more than "
-                                + MAX_JOBS_PER_APP + " distinct jobs");
-                }
-            }
-
-            // This may throw a SecurityException.
-            jobStatus.prepareLocked(ActivityManager.getService());
-
-            if (work != null) {
-                // If work has been supplied, enqueue it into the new job.
-                jobStatus.enqueueWorkLocked(ActivityManager.getService(), work);
-            }
-
-            if (toCancel != null) {
-                // Implicitly replaces the existing job record with the new instance
-                cancelJobImplLocked(toCancel, jobStatus, "job rescheduled by app");
-            } else {
-                startTrackingJobLocked(jobStatus, null);
-            }
-            StatsLog.write_non_chained(StatsLog.SCHEDULED_JOB_STATE_CHANGED,
-                    uId, null, jobStatus.getBatteryName(),
-                    StatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__SCHEDULED,
-                    JobProtoEnums.STOP_REASON_CANCELLED, jobStatus.getStandbyBucket(),
-                    jobStatus.getJobId());
-
-            // If the job is immediately ready to run, then we can just immediately
-            // put it in the pending list and try to schedule it.  This is especially
-            // important for jobs with a 0 deadline constraint, since they will happen a fair
-            // amount, we want to handle them as quickly as possible, and semantically we want to
-            // make sure we have started holding the wake lock for the job before returning to
-            // the caller.
-            // If the job is not yet ready to run, there is nothing more to do -- we are
-            // now just waiting for one of its controllers to change state and schedule
-            // the job appropriately.
-            if (isReadyToBeExecutedLocked(jobStatus)) {
-                // This is a new job, we can just immediately put it on the pending
-                // list and try to run it.
-                mJobPackageTracker.notePending(jobStatus);
-                addOrderedItem(mPendingJobs, jobStatus, mEnqueueTimeComparator);
-                maybeRunPendingJobsLocked();
-            } else {
-                evaluateControllerStatesLocked(jobStatus);
-            }
-        }
-        return JobScheduler.RESULT_SUCCESS;
-    }
-
-    public List<JobInfo> getPendingJobs(int uid) {
-        synchronized (mLock) {
-            List<JobStatus> jobs = mJobs.getJobsByUid(uid);
-            ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
-            for (int i = jobs.size() - 1; i >= 0; i--) {
-                JobStatus job = jobs.get(i);
-                outList.add(job.getJob());
-            }
-            return outList;
-        }
-    }
-
-    public JobInfo getPendingJob(int uid, int jobId) {
-        synchronized (mLock) {
-            List<JobStatus> jobs = mJobs.getJobsByUid(uid);
-            for (int i = jobs.size() - 1; i >= 0; i--) {
-                JobStatus job = jobs.get(i);
-                if (job.getJobId() == jobId) {
-                    return job.getJob();
-                }
-            }
-            return null;
-        }
-    }
-
-    void cancelJobsForUser(int userHandle) {
-        synchronized (mLock) {
-            final List<JobStatus> jobsForUser = mJobs.getJobsByUser(userHandle);
-            for (int i=0; i<jobsForUser.size(); i++) {
-                JobStatus toRemove = jobsForUser.get(i);
-                cancelJobImplLocked(toRemove, null, "user removed");
-            }
-        }
-    }
-
-    private void cancelJobsForNonExistentUsers() {
-        UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
-        synchronized (mLock) {
-            mJobs.removeJobsOfNonUsers(umi.getUserIds());
-        }
-    }
-
-    void cancelJobsForPackageAndUid(String pkgName, int uid, String reason) {
-        if ("android".equals(pkgName)) {
-            Slog.wtfStack(TAG, "Can't cancel all jobs for system package");
-            return;
-        }
-        synchronized (mLock) {
-            final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
-            for (int i = jobsForUid.size() - 1; i >= 0; i--) {
-                final JobStatus job = jobsForUid.get(i);
-                if (job.getSourcePackageName().equals(pkgName)) {
-                    cancelJobImplLocked(job, null, reason);
-                }
-            }
-        }
-    }
-
-    /**
-     * Entry point from client to cancel all jobs originating from their uid.
-     * This will remove the job from the master list, and cancel the job if it was staged for
-     * execution or being executed.
-     * @param uid Uid to check against for removal of a job.
-     *
-     */
-    public boolean cancelJobsForUid(int uid, String reason) {
-        if (uid == Process.SYSTEM_UID) {
-            Slog.wtfStack(TAG, "Can't cancel all jobs for system uid");
-            return false;
-        }
-
-        boolean jobsCanceled = false;
-        synchronized (mLock) {
-            final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
-            for (int i=0; i<jobsForUid.size(); i++) {
-                JobStatus toRemove = jobsForUid.get(i);
-                cancelJobImplLocked(toRemove, null, reason);
-                jobsCanceled = true;
-            }
-        }
-        return jobsCanceled;
-    }
-
-    /**
-     * Entry point from client to cancel the job corresponding to the jobId provided.
-     * This will remove the job from the master list, and cancel the job if it was staged for
-     * execution or being executed.
-     * @param uid Uid of the calling client.
-     * @param jobId Id of the job, provided at schedule-time.
-     */
-    public boolean cancelJob(int uid, int jobId, int callingUid) {
-        JobStatus toCancel;
-        synchronized (mLock) {
-            toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
-            if (toCancel != null) {
-                cancelJobImplLocked(toCancel, null,
-                        "cancel() called by app, callingUid=" + callingUid
-                        + " uid=" + uid + " jobId=" + jobId);
-            }
-            return (toCancel != null);
-        }
-    }
-
-    /**
-     * Cancel the given job, stopping it if it's currently executing.  If {@code incomingJob}
-     * is null, the cancelled job is removed outright from the system.  If
-     * {@code incomingJob} is non-null, it replaces {@code cancelled} in the store of
-     * currently scheduled jobs.
-     */
-    private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob, String reason) {
-        if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
-        cancelled.unprepareLocked(ActivityManager.getService());
-        stopTrackingJobLocked(cancelled, incomingJob, true /* writeBack */);
-        // Remove from pending queue.
-        if (mPendingJobs.remove(cancelled)) {
-            mJobPackageTracker.noteNonpending(cancelled);
-        }
-        // Cancel if running.
-        stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED, reason);
-        // If this is a replacement, bring in the new version of the job
-        if (incomingJob != null) {
-            if (DEBUG) Slog.i(TAG, "Tracking replacement job " + incomingJob.toShortString());
-            startTrackingJobLocked(incomingJob, cancelled);
-        }
-        reportActiveLocked();
-    }
-
-    void updateUidState(int uid, int procState) {
-        synchronized (mLock) {
-            if (procState == ActivityManager.PROCESS_STATE_TOP) {
-                // Only use this if we are exactly the top app.  All others can live
-                // with just the foreground priority.  This means that persistent processes
-                // can never be the top app priority...  that is fine.
-                mUidPriorityOverride.put(uid, JobInfo.PRIORITY_TOP_APP);
-            } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
-                mUidPriorityOverride.put(uid, JobInfo.PRIORITY_FOREGROUND_SERVICE);
-            } else if (procState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
-                mUidPriorityOverride.put(uid, JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE);
-            } else {
-                mUidPriorityOverride.delete(uid);
-            }
-        }
-    }
-
-    @Override
-    public void onDeviceIdleStateChanged(boolean deviceIdle) {
-        synchronized (mLock) {
-            if (DEBUG) {
-                Slog.d(TAG, "Doze state changed: " + deviceIdle);
-            }
-            if (deviceIdle) {
-                // When becoming idle, make sure no jobs are actively running,
-                // except those using the idle exemption flag.
-                for (int i=0; i<mActiveServices.size(); i++) {
-                    JobServiceContext jsc = mActiveServices.get(i);
-                    final JobStatus executing = jsc.getRunningJobLocked();
-                    if (executing != null
-                            && (executing.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) == 0) {
-                        jsc.cancelExecutingJobLocked(JobParameters.REASON_DEVICE_IDLE,
-                                "cancelled due to doze");
-                    }
-                }
-            } else {
-                // When coming out of idle, allow thing to start back up.
-                if (mReadyToRock) {
-                    if (mLocalDeviceIdleController != null) {
-                        if (!mReportedActive) {
-                            mReportedActive = true;
-                            mLocalDeviceIdleController.setJobsActive(true);
-                        }
-                    }
-                    mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
-                }
-            }
-        }
-    }
-
-    void reportActiveLocked() {
-        // active is true if pending queue contains jobs OR some job is running.
-        boolean active = mPendingJobs.size() > 0;
-        if (mPendingJobs.size() <= 0) {
-            for (int i=0; i<mActiveServices.size(); i++) {
-                final JobServiceContext jsc = mActiveServices.get(i);
-                final JobStatus job = jsc.getRunningJobLocked();
-                if (job != null
-                        && (job.getJob().getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) == 0
-                        && !job.dozeWhitelisted
-                        && !job.uidActive) {
-                    // We will report active if we have a job running and it is not an exception
-                    // due to being in the foreground or whitelisted.
-                    active = true;
-                    break;
-                }
-            }
-        }
-
-        if (mReportedActive != active) {
-            mReportedActive = active;
-            if (mLocalDeviceIdleController != null) {
-                mLocalDeviceIdleController.setJobsActive(active);
-            }
-        }
-    }
-
-    void reportAppUsage(String packageName, int userId) {
-        // This app just transitioned into interactive use or near equivalent, so we should
-        // take a look at its job state for feedback purposes.
-    }
-
-    /**
-     * Initializes the system service.
-     * <p>
-     * Subclasses must define a single argument constructor that accepts the context
-     * and passes it to super.
-     * </p>
-     *
-     * @param context The system server context.
-     */
-    public JobSchedulerService(Context context) {
-        super(context);
-
-        mLocalPM = LocalServices.getService(PackageManagerInternal.class);
-        mActivityManagerInternal = Preconditions.checkNotNull(
-                LocalServices.getService(ActivityManagerInternal.class));
-
-        mHandler = new JobHandler(context.getMainLooper());
-        mConstants = new Constants();
-        mConstantsObserver = new ConstantsObserver(mHandler);
-        mJobSchedulerStub = new JobSchedulerStub();
-
-        mConcurrencyManager = new JobConcurrencyManager(this);
-
-        // Set up the app standby bucketing tracker
-        mStandbyTracker = new StandbyTracker();
-        mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
-        mUsageStats.addAppIdleStateChangeListener(mStandbyTracker);
-
-        // The job store needs to call back
-        publishLocalService(JobSchedulerInternal.class, new LocalService());
-
-        // Initialize the job store and set up any persisted jobs
-        mJobs = JobStore.initAndGet(this);
-
-        // Create the controllers.
-        mControllers = new ArrayList<StateController>();
-        mControllers.add(new ConnectivityController(this));
-        mControllers.add(new TimeController(this));
-        mControllers.add(new IdleController(this));
-        mBatteryController = new BatteryController(this);
-        mControllers.add(mBatteryController);
-        mStorageController = new StorageController(this);
-        mControllers.add(mStorageController);
-        mControllers.add(new BackgroundJobsController(this));
-        mControllers.add(new ContentObserverController(this));
-        mDeviceIdleJobsController = new DeviceIdleJobsController(this);
-        mControllers.add(mDeviceIdleJobsController);
-        mQuotaController = new QuotaController(this);
-        mControllers.add(mQuotaController);
-
-        // If the job store determined that it can't yet reschedule persisted jobs,
-        // we need to start watching the clock.
-        if (!mJobs.jobTimesInflatedValid()) {
-            Slog.w(TAG, "!!! RTC not yet good; tracking time updates for job scheduling");
-            context.registerReceiver(mTimeSetReceiver, new IntentFilter(Intent.ACTION_TIME_CHANGED));
-        }
-    }
-
-    private final BroadcastReceiver mTimeSetReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) {
-                // When we reach clock sanity, recalculate the temporal windows
-                // of all affected jobs.
-                if (mJobs.clockNowValidToInflate(sSystemClock.millis())) {
-                    Slog.i(TAG, "RTC now valid; recalculating persisted job windows");
-
-                    // We've done our job now, so stop watching the time.
-                    context.unregisterReceiver(this);
-
-                    // And kick off the work to update the affected jobs, using a secondary
-                    // thread instead of chugging away here on the main looper thread.
-                    FgThread.getHandler().post(mJobTimeUpdater);
-                }
-            }
-        }
-    };
-
-    private final Runnable mJobTimeUpdater = () -> {
-        final ArrayList<JobStatus> toRemove = new ArrayList<>();
-        final ArrayList<JobStatus> toAdd = new ArrayList<>();
-        synchronized (mLock) {
-            // Note: we intentionally both look up the existing affected jobs and replace them
-            // with recalculated ones inside the same lock lifetime.
-            getJobStore().getRtcCorrectedJobsLocked(toAdd, toRemove);
-
-            // Now, at each position [i], we have both the existing JobStatus
-            // and the one that replaces it.
-            final int N = toAdd.size();
-            for (int i = 0; i < N; i++) {
-                final JobStatus oldJob = toRemove.get(i);
-                final JobStatus newJob = toAdd.get(i);
-                if (DEBUG) {
-                    Slog.v(TAG, "  replacing " + oldJob + " with " + newJob);
-                }
-                cancelJobImplLocked(oldJob, newJob, "deferred rtc calculation");
-            }
-        }
-    };
-
-    @Override
-    public void onStart() {
-        publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
-    }
-
-    @Override
-    public void onBootPhase(int phase) {
-        if (PHASE_SYSTEM_SERVICES_READY == phase) {
-            mConstantsObserver.start(getContext().getContentResolver());
-            for (StateController controller : mControllers) {
-                controller.onSystemServicesReady();
-            }
-
-            mAppStateTracker = Preconditions.checkNotNull(
-                    LocalServices.getService(AppStateTracker.class));
-            if (mConstants.USE_HEARTBEATS) {
-                setNextHeartbeatAlarm();
-            }
-
-            // Register br for package removals and user removals.
-            final IntentFilter filter = new IntentFilter();
-            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-            filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
-            filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
-            filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
-            filter.addDataScheme("package");
-            getContext().registerReceiverAsUser(
-                    mBroadcastReceiver, UserHandle.ALL, filter, null, null);
-            final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
-            getContext().registerReceiverAsUser(
-                    mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
-            try {
-                ActivityManager.getService().registerUidObserver(mUidObserver,
-                        ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE
-                        | ActivityManager.UID_OBSERVER_IDLE | ActivityManager.UID_OBSERVER_ACTIVE,
-                        ActivityManager.PROCESS_STATE_UNKNOWN, null);
-            } catch (RemoteException e) {
-                // ignored; both services live in system_server
-            }
-
-            mConcurrencyManager.onSystemReady();
-
-            // Remove any jobs that are not associated with any of the current users.
-            cancelJobsForNonExistentUsers();
-            // Register thermal callback
-            mThermalService = IThermalService.Stub.asInterface(
-                    ServiceManager.getService(Context.THERMAL_SERVICE));
-            if (mThermalService != null) {
-                try {
-                    mThermalService.registerThermalStatusListener(new ThermalStatusListener());
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Failed to register thermal callback.", e);
-                }
-            }
-        } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
-            synchronized (mLock) {
-                // Let's go!
-                mReadyToRock = true;
-                mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
-                        BatteryStats.SERVICE_NAME));
-                mLocalDeviceIdleController
-                        = LocalServices.getService(DeviceIdleController.LocalService.class);
-                // Create the "runners".
-                for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
-                    mActiveServices.add(
-                            new JobServiceContext(this, mBatteryStats, mJobPackageTracker,
-                                    getContext().getMainLooper()));
-                }
-                // Attach jobs to their controllers.
-                mJobs.forEachJob((job) -> {
-                    for (int controller = 0; controller < mControllers.size(); controller++) {
-                        final StateController sc = mControllers.get(controller);
-                        sc.maybeStartTrackingJobLocked(job, null);
-                    }
-                });
-                // GO GO GO!
-                mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
-            }
-        }
-    }
-
-    /**
-     * Called when we have a job status object that we need to insert in our
-     * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know
-     * about.
-     */
-    private void startTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
-        if (!jobStatus.isPreparedLocked()) {
-            Slog.wtf(TAG, "Not yet prepared when started tracking: " + jobStatus);
-        }
-        jobStatus.enqueueTime = sElapsedRealtimeClock.millis();
-        final boolean update = mJobs.add(jobStatus);
-        if (mReadyToRock) {
-            for (int i = 0; i < mControllers.size(); i++) {
-                StateController controller = mControllers.get(i);
-                if (update) {
-                    controller.maybeStopTrackingJobLocked(jobStatus, null, true);
-                }
-                controller.maybeStartTrackingJobLocked(jobStatus, lastJob);
-            }
-        }
-    }
-
-    /**
-     * Called when we want to remove a JobStatus object that we've finished executing.
-     * @return true if the job was removed.
-     */
-    private boolean stopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
-            boolean removeFromPersisted) {
-        // Deal with any remaining work items in the old job.
-        jobStatus.stopTrackingJobLocked(ActivityManager.getService(), incomingJob);
-
-        // Remove from store as well as controllers.
-        final boolean removed = mJobs.remove(jobStatus, removeFromPersisted);
-        if (removed && mReadyToRock) {
-            for (int i=0; i<mControllers.size(); i++) {
-                StateController controller = mControllers.get(i);
-                controller.maybeStopTrackingJobLocked(jobStatus, incomingJob, false);
-            }
-        }
-        return removed;
-    }
-
-    private boolean stopJobOnServiceContextLocked(JobStatus job, int reason, String debugReason) {
-        for (int i=0; i<mActiveServices.size(); i++) {
-            JobServiceContext jsc = mActiveServices.get(i);
-            final JobStatus executing = jsc.getRunningJobLocked();
-            if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
-                jsc.cancelExecutingJobLocked(reason, debugReason);
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * @param job JobStatus we are querying against.
-     * @return Whether or not the job represented by the status object is currently being run or
-     * is pending.
-     */
-    private boolean isCurrentlyActiveLocked(JobStatus job) {
-        for (int i=0; i<mActiveServices.size(); i++) {
-            JobServiceContext serviceContext = mActiveServices.get(i);
-            final JobStatus running = serviceContext.getRunningJobLocked();
-            if (running != null && running.matches(job.getUid(), job.getJobId())) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    void noteJobsPending(List<JobStatus> jobs) {
-        for (int i = jobs.size() - 1; i >= 0; i--) {
-            JobStatus job = jobs.get(i);
-            mJobPackageTracker.notePending(job);
-        }
-    }
-
-    void noteJobsNonpending(List<JobStatus> jobs) {
-        for (int i = jobs.size() - 1; i >= 0; i--) {
-            JobStatus job = jobs.get(i);
-            mJobPackageTracker.noteNonpending(job);
-        }
-    }
-
-    /**
-     * Reschedules the given job based on the job's backoff policy. It doesn't make sense to
-     * specify an override deadline on a failed job (the failed job will run even though it's not
-     * ready), so we reschedule it with {@link JobStatus#NO_LATEST_RUNTIME}, but specify that any
-     * ready job with {@link JobStatus#getNumFailures()} > 0 will be executed.
-     *
-     * @param failureToReschedule Provided job status that we will reschedule.
-     * @return A newly instantiated JobStatus with the same constraints as the last job except
-     * with adjusted timing constraints.
-     *
-     * @see #maybeQueueReadyJobsForExecutionLocked
-     */
-    @VisibleForTesting
-    JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule) {
-        final long elapsedNowMillis = sElapsedRealtimeClock.millis();
-        final JobInfo job = failureToReschedule.getJob();
-
-        final long initialBackoffMillis = job.getInitialBackoffMillis();
-        final int backoffAttempts = failureToReschedule.getNumFailures() + 1;
-        long delayMillis;
-
-        if (failureToReschedule.hasWorkLocked()) {
-            if (backoffAttempts > mConstants.MAX_WORK_RESCHEDULE_COUNT) {
-                Slog.w(TAG, "Not rescheduling " + failureToReschedule + ": attempt #"
-                        + backoffAttempts + " > work limit "
-                        + mConstants.MAX_STANDARD_RESCHEDULE_COUNT);
-                return null;
-            }
-        } else if (backoffAttempts > mConstants.MAX_STANDARD_RESCHEDULE_COUNT) {
-            Slog.w(TAG, "Not rescheduling " + failureToReschedule + ": attempt #"
-                    + backoffAttempts + " > std limit " + mConstants.MAX_STANDARD_RESCHEDULE_COUNT);
-            return null;
-        }
-
-        switch (job.getBackoffPolicy()) {
-            case JobInfo.BACKOFF_POLICY_LINEAR: {
-                long backoff = initialBackoffMillis;
-                if (backoff < mConstants.MIN_LINEAR_BACKOFF_TIME) {
-                    backoff = mConstants.MIN_LINEAR_BACKOFF_TIME;
-                }
-                delayMillis = backoff * backoffAttempts;
-            } break;
-            default:
-                if (DEBUG) {
-                    Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
-                }
-            case JobInfo.BACKOFF_POLICY_EXPONENTIAL: {
-                long backoff = initialBackoffMillis;
-                if (backoff < mConstants.MIN_EXP_BACKOFF_TIME) {
-                    backoff = mConstants.MIN_EXP_BACKOFF_TIME;
-                }
-                delayMillis = (long) Math.scalb(backoff, backoffAttempts - 1);
-            } break;
-        }
-        delayMillis =
-                Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
-        JobStatus newJob = new JobStatus(failureToReschedule, getCurrentHeartbeat(),
-                elapsedNowMillis + delayMillis,
-                JobStatus.NO_LATEST_RUNTIME, backoffAttempts,
-                failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis());
-        if (job.isPeriodic()) {
-            newJob.setOriginalLatestRunTimeElapsed(
-                    failureToReschedule.getOriginalLatestRunTimeElapsed());
-        }
-        for (int ic=0; ic<mControllers.size(); ic++) {
-            StateController controller = mControllers.get(ic);
-            controller.rescheduleForFailureLocked(newJob, failureToReschedule);
-        }
-        return newJob;
-    }
-
-    /**
-     * Maximum time buffer in which JobScheduler will try to optimize periodic job scheduling. This
-     * does not cause a job's period to be larger than requested (eg: if the requested period is
-     * shorter than this buffer). This is used to put a limit on when JobScheduler will intervene
-     * and try to optimize scheduling if the current job finished less than this amount of time to
-     * the start of the next period
-     */
-    private static final long PERIODIC_JOB_WINDOW_BUFFER = 30 * MINUTE_IN_MILLIS;
-
-    /** The maximum period a periodic job can have. Anything higher will be clamped down to this. */
-    public static final long MAX_ALLOWED_PERIOD_MS = 365 * 24 * 60 * 60 * 1000L;
-
-    /**
-     * Called after a periodic has executed so we can reschedule it. We take the last execution
-     * 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#EXECUTING_TIMESLICE_MILLIS}, but will lead
-     * to underscheduling at least, rather than if we had taken the last execution time to be the
-     * start of the execution.
-     * <p>Unlike a reschedule prior to execution, in this case we advance the next-heartbeat
-     * tracking as though the job were newly-scheduled.
-     * @return A new job representing the execution criteria for this instantiation of the
-     * recurring job.
-     */
-    @VisibleForTesting
-    JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
-        final long elapsedNow = sElapsedRealtimeClock.millis();
-        final long newLatestRuntimeElapsed;
-        // Make sure period is in the interval [min_possible_period, max_possible_period].
-        final long period = Math.max(JobInfo.getMinPeriodMillis(),
-                Math.min(MAX_ALLOWED_PERIOD_MS, periodicToReschedule.getJob().getIntervalMillis()));
-        // Make sure flex is in the interval [min_possible_flex, period].
-        final long flex = Math.max(JobInfo.getMinFlexMillis(),
-                Math.min(period, periodicToReschedule.getJob().getFlexMillis()));
-        long rescheduleBuffer = 0;
-
-        long olrte = periodicToReschedule.getOriginalLatestRunTimeElapsed();
-        if (olrte < 0 || olrte == JobStatus.NO_LATEST_RUNTIME) {
-            Slog.wtf(TAG, "Invalid periodic job original latest run time: " + olrte);
-            olrte = elapsedNow;
-        }
-        final long latestRunTimeElapsed = olrte;
-
-        final long diffMs = Math.abs(elapsedNow - latestRunTimeElapsed);
-        if (elapsedNow > latestRunTimeElapsed) {
-            // The job ran past its expected run window. Have it count towards the current window
-            // and schedule a new job for the next window.
-            if (DEBUG) {
-                Slog.i(TAG, "Periodic job ran after its intended window.");
-            }
-            long numSkippedWindows = (diffMs / period) + 1; // +1 to include original window
-            if (period != flex && diffMs > Math.min(PERIODIC_JOB_WINDOW_BUFFER,
-                    (period - flex) / 2)) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Custom flex job ran too close to next window.");
-                }
-                // For custom flex periods, if the job was run too close to the next window,
-                // skip the next window and schedule for the following one.
-                numSkippedWindows += 1;
-            }
-            newLatestRuntimeElapsed = latestRunTimeElapsed + (period * numSkippedWindows);
-        } else {
-            newLatestRuntimeElapsed = latestRunTimeElapsed + period;
-            if (diffMs < PERIODIC_JOB_WINDOW_BUFFER && diffMs < period / 6) {
-                // Add a little buffer to the start of the next window so the job doesn't run
-                // too soon after this completed one.
-                rescheduleBuffer = Math.min(PERIODIC_JOB_WINDOW_BUFFER, period / 6 - diffMs);
-            }
-        }
-
-        if (newLatestRuntimeElapsed < elapsedNow) {
-            Slog.wtf(TAG, "Rescheduling calculated latest runtime in the past: "
-                    + newLatestRuntimeElapsed);
-            return new JobStatus(periodicToReschedule, getCurrentHeartbeat(),
-                    elapsedNow + period - flex, elapsedNow + period,
-                    0 /* backoffAttempt */,
-                    sSystemClock.millis() /* lastSuccessfulRunTime */,
-                    periodicToReschedule.getLastFailedRunTime());
-        }
-
-        final long newEarliestRunTimeElapsed = newLatestRuntimeElapsed
-                - Math.min(flex, period - rescheduleBuffer);
-
-        if (DEBUG) {
-            Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
-                    newEarliestRunTimeElapsed / 1000 + ", " + newLatestRuntimeElapsed / 1000
-                    + "]s");
-        }
-        return new JobStatus(periodicToReschedule, getCurrentHeartbeat(),
-                newEarliestRunTimeElapsed, newLatestRuntimeElapsed,
-                0 /* backoffAttempt */,
-                sSystemClock.millis() /* lastSuccessfulRunTime */,
-                periodicToReschedule.getLastFailedRunTime());
-    }
-
-    /*
-     * We default to "long enough ago that every bucket's jobs are immediately runnable" to
-     * avoid starvation of apps in uncommon-use buckets that might arise from repeated
-     * reboot behavior.
-     */
-    long heartbeatWhenJobsLastRun(String packageName, final @UserIdInt int userId) {
-        // The furthest back in pre-boot time that we need to bother with
-        long heartbeat = -mConstants.STANDBY_BEATS[RARE_INDEX];
-        boolean cacheHit = false;
-        synchronized (mLock) {
-            HashMap<String, Long> jobPackages = mLastJobHeartbeats.get(userId);
-            if (jobPackages != null) {
-                long cachedValue = jobPackages.getOrDefault(packageName, Long.MAX_VALUE);
-                if (cachedValue < Long.MAX_VALUE) {
-                    cacheHit = true;
-                    heartbeat = cachedValue;
-                }
-            }
-            if (!cacheHit) {
-                // We haven't seen it yet; ask usage stats about it
-                final long timeSinceJob = mUsageStats.getTimeSinceLastJobRun(packageName, userId);
-                if (timeSinceJob < Long.MAX_VALUE) {
-                    // Usage stats knows about it from before, so calculate back from that
-                    // and go from there.
-                    heartbeat = mHeartbeat - (timeSinceJob / mConstants.STANDBY_HEARTBEAT_TIME);
-                }
-                // If usage stats returned its "not found" MAX_VALUE, we still have the
-                // negative default 'heartbeat' value we established above
-                setLastJobHeartbeatLocked(packageName, userId, heartbeat);
-            }
-        }
-        if (DEBUG_STANDBY) {
-            Slog.v(TAG, "Last job heartbeat " + heartbeat + " for "
-                    + packageName + "/" + userId);
-        }
-        return heartbeat;
-    }
-
-    long heartbeatWhenJobsLastRun(JobStatus job) {
-        return heartbeatWhenJobsLastRun(job.getSourcePackageName(), job.getSourceUserId());
-    }
-
-    void setLastJobHeartbeatLocked(String packageName, int userId, long heartbeat) {
-        HashMap<String, Long> jobPackages = mLastJobHeartbeats.get(userId);
-        if (jobPackages == null) {
-            jobPackages = new HashMap<>();
-            mLastJobHeartbeats.put(userId, jobPackages);
-        }
-        jobPackages.put(packageName, heartbeat);
-    }
-
-    // JobCompletedListener implementations.
-
-    /**
-     * A job just finished executing. We fetch the
-     * {@link com.android.server.job.controllers.JobStatus} from the store and depending on
-     * whether we want to reschedule we re-add it to the controllers.
-     * @param jobStatus Completed job.
-     * @param needsReschedule Whether the implementing class should reschedule this job.
-     */
-    @Override
-    public void onJobCompletedLocked(JobStatus jobStatus, boolean needsReschedule) {
-        if (DEBUG) {
-            Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
-        }
-
-        // If the job wants to be rescheduled, we first need to make the next upcoming
-        // job so we can transfer any appropriate state over from the previous job when
-        // we stop it.
-        final JobStatus rescheduledJob = needsReschedule
-                ? getRescheduleJobForFailureLocked(jobStatus) : null;
-
-        // Do not write back immediately if this is a periodic job. The job may get lost if system
-        // shuts down before it is added back.
-        if (!stopTrackingJobLocked(jobStatus, rescheduledJob, !jobStatus.getJob().isPeriodic())) {
-            if (DEBUG) {
-                Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
-            }
-            // We still want to check for jobs to execute, because this job may have
-            // scheduled a new job under the same job id, and now we can run it.
-            mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
-            return;
-        }
-
-        if (rescheduledJob != null) {
-            try {
-                rescheduledJob.prepareLocked(ActivityManager.getService());
-            } catch (SecurityException e) {
-                Slog.w(TAG, "Unable to regrant job permissions for " + rescheduledJob);
-            }
-            startTrackingJobLocked(rescheduledJob, jobStatus);
-        } else if (jobStatus.getJob().isPeriodic()) {
-            JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
-            try {
-                rescheduledPeriodic.prepareLocked(ActivityManager.getService());
-            } catch (SecurityException e) {
-                Slog.w(TAG, "Unable to regrant job permissions for " + rescheduledPeriodic);
-            }
-            startTrackingJobLocked(rescheduledPeriodic, jobStatus);
-        }
-        jobStatus.unprepareLocked(ActivityManager.getService());
-        reportActiveLocked();
-        mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
-    }
-
-    // StateChangedListener implementations.
-
-    /**
-     * Posts a message to the {@link com.android.server.job.JobSchedulerService.JobHandler} that
-     * some controller's state has changed, so as to run through the list of jobs and start/stop
-     * any that are eligible.
-     */
-    @Override
-    public void onControllerStateChanged() {
-        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
-    }
-
-    @Override
-    public void onRunJobNow(JobStatus jobStatus) {
-        mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
-    }
-
-    final private class JobHandler extends Handler {
-
-        public JobHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message message) {
-            synchronized (mLock) {
-                if (!mReadyToRock) {
-                    return;
-                }
-                switch (message.what) {
-                    case MSG_JOB_EXPIRED: {
-                        JobStatus runNow = (JobStatus) message.obj;
-                        // runNow can be null, which is a controller's way of indicating that its
-                        // state is such that all ready jobs should be run immediately.
-                        if (runNow != null && isReadyToBeExecutedLocked(runNow)) {
-                            mJobPackageTracker.notePending(runNow);
-                            addOrderedItem(mPendingJobs, runNow, mEnqueueTimeComparator);
-                        } else {
-                            queueReadyJobsForExecutionLocked();
-                        }
-                    } break;
-                    case MSG_CHECK_JOB:
-                        if (DEBUG) {
-                            Slog.d(TAG, "MSG_CHECK_JOB");
-                        }
-                        removeMessages(MSG_CHECK_JOB);
-                        if (mReportedActive) {
-                            // if jobs are currently being run, queue all ready jobs for execution.
-                            queueReadyJobsForExecutionLocked();
-                        } else {
-                            // Check the list of jobs and run some of them if we feel inclined.
-                            maybeQueueReadyJobsForExecutionLocked();
-                        }
-                        break;
-                    case MSG_CHECK_JOB_GREEDY:
-                        if (DEBUG) {
-                            Slog.d(TAG, "MSG_CHECK_JOB_GREEDY");
-                        }
-                        queueReadyJobsForExecutionLocked();
-                        break;
-                    case MSG_STOP_JOB:
-                        cancelJobImplLocked((JobStatus) message.obj, null,
-                                "app no longer allowed to run");
-                        break;
-
-                    case MSG_UID_STATE_CHANGED: {
-                        final int uid = message.arg1;
-                        final int procState = message.arg2;
-                        updateUidState(uid, procState);
-                        break;
-                    }
-                    case MSG_UID_GONE: {
-                        final int uid = message.arg1;
-                        final boolean disabled = message.arg2 != 0;
-                        updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
-                        if (disabled) {
-                            cancelJobsForUid(uid, "uid gone");
-                        }
-                        synchronized (mLock) {
-                            mDeviceIdleJobsController.setUidActiveLocked(uid, false);
-                        }
-                        break;
-                    }
-                    case MSG_UID_ACTIVE: {
-                        final int uid = message.arg1;
-                        synchronized (mLock) {
-                            mDeviceIdleJobsController.setUidActiveLocked(uid, true);
-                        }
-                        break;
-                    }
-                    case MSG_UID_IDLE: {
-                        final int uid = message.arg1;
-                        final boolean disabled = message.arg2 != 0;
-                        if (disabled) {
-                            cancelJobsForUid(uid, "app uid idle");
-                        }
-                        synchronized (mLock) {
-                            mDeviceIdleJobsController.setUidActiveLocked(uid, false);
-                        }
-                        break;
-                    }
-
-                }
-                maybeRunPendingJobsLocked();
-                // Don't remove JOB_EXPIRED in case one came along while processing the queue.
-            }
-        }
-    }
-
-    private boolean isJobThermalConstrainedLocked(JobStatus job) {
-        return mThermalConstraint && job.hasConnectivityConstraint()
-                && (evaluateJobPriorityLocked(job) < JobInfo.PRIORITY_FOREGROUND_APP);
-    }
-
-    private void stopNonReadyActiveJobsLocked() {
-        for (int i=0; i<mActiveServices.size(); i++) {
-            JobServiceContext serviceContext = mActiveServices.get(i);
-            final JobStatus running = serviceContext.getRunningJobLocked();
-            if (running == null) {
-                continue;
-            }
-            if (!running.isReady()) {
-                serviceContext.cancelExecutingJobLocked(
-                        JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED,
-                        "cancelled due to unsatisfied constraints");
-            } else if (isJobThermalConstrainedLocked(running)) {
-                serviceContext.cancelExecutingJobLocked(
-                        JobParameters.REASON_DEVICE_THERMAL,
-                        "cancelled due to thermal condition");
-            }
-        }
-    }
-
-    /**
-     * Run through list of jobs and execute all possible - at least one is expired so we do
-     * as many as we can.
-     */
-    private void queueReadyJobsForExecutionLocked() {
-        if (DEBUG) {
-            Slog.d(TAG, "queuing all ready jobs for execution:");
-        }
-        noteJobsNonpending(mPendingJobs);
-        mPendingJobs.clear();
-        stopNonReadyActiveJobsLocked();
-        mJobs.forEachJob(mReadyQueueFunctor);
-        mReadyQueueFunctor.postProcess();
-
-        if (DEBUG) {
-            final int queuedJobs = mPendingJobs.size();
-            if (queuedJobs == 0) {
-                Slog.d(TAG, "No jobs pending.");
-            } else {
-                Slog.d(TAG, queuedJobs + " jobs queued.");
-            }
-        }
-    }
-
-    final class ReadyJobQueueFunctor implements Consumer<JobStatus> {
-        final ArrayList<JobStatus> newReadyJobs = new ArrayList<>();
-
-        @Override
-        public void accept(JobStatus job) {
-            if (isReadyToBeExecutedLocked(job)) {
-                if (DEBUG) {
-                    Slog.d(TAG, "    queued " + job.toShortString());
-                }
-                newReadyJobs.add(job);
-            } else {
-                evaluateControllerStatesLocked(job);
-            }
-        }
-
-        public void postProcess() {
-            noteJobsPending(newReadyJobs);
-            mPendingJobs.addAll(newReadyJobs);
-            if (mPendingJobs.size() > 1) {
-                mPendingJobs.sort(mEnqueueTimeComparator);
-            }
-
-            newReadyJobs.clear();
-        }
-    }
-    private final ReadyJobQueueFunctor mReadyQueueFunctor = new ReadyJobQueueFunctor();
-
-    /**
-     * The state of at least one job has changed. Here is where we could enforce various
-     * policies on when we want to execute jobs.
-     */
-    final class MaybeReadyJobQueueFunctor implements Consumer<JobStatus> {
-        int chargingCount;
-        int batteryNotLowCount;
-        int storageNotLowCount;
-        int idleCount;
-        int backoffCount;
-        int connectivityCount;
-        int contentCount;
-        int forceBatchedCount;
-        int unbatchedCount;
-        final List<JobStatus> runnableJobs = new ArrayList<>();
-
-        public MaybeReadyJobQueueFunctor() {
-            reset();
-        }
-
-        // Functor method invoked for each job via JobStore.forEachJob()
-        @Override
-        public void accept(JobStatus job) {
-            if (isReadyToBeExecutedLocked(job)) {
-                try {
-                    if (ActivityManager.getService().isAppStartModeDisabled(job.getUid(),
-                            job.getJob().getService().getPackageName())) {
-                        Slog.w(TAG, "Aborting job " + job.getUid() + ":"
-                                + job.getJob().toString() + " -- package not allowed to start");
-                        mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
-                        return;
-                    }
-                } catch (RemoteException e) {
-                }
-                if (mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1
-                        && job.getStandbyBucket() != ACTIVE_INDEX
-                        && (job.getFirstForceBatchedTimeElapsed() == 0
-                        || sElapsedRealtimeClock.millis() - job.getFirstForceBatchedTimeElapsed()
-                                < mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS)) {
-                    // Force batching non-ACTIVE jobs. Don't include them in the other counts.
-                    forceBatchedCount++;
-                    if (job.getFirstForceBatchedTimeElapsed() == 0) {
-                        job.setFirstForceBatchedTimeElapsed(sElapsedRealtimeClock.millis());
-                    }
-                } else {
-                    unbatchedCount++;
-                    if (job.getNumFailures() > 0) {
-                        backoffCount++;
-                    }
-                    if (job.hasIdleConstraint()) {
-                        idleCount++;
-                    }
-                    if (job.hasConnectivityConstraint()) {
-                        connectivityCount++;
-                    }
-                    if (job.hasChargingConstraint()) {
-                        chargingCount++;
-                    }
-                    if (job.hasBatteryNotLowConstraint()) {
-                        batteryNotLowCount++;
-                    }
-                    if (job.hasStorageNotLowConstraint()) {
-                        storageNotLowCount++;
-                    }
-                    if (job.hasContentTriggerConstraint()) {
-                        contentCount++;
-                    }
-                }
-                runnableJobs.add(job);
-            } else {
-                evaluateControllerStatesLocked(job);
-            }
-        }
-
-        public void postProcess() {
-            if (backoffCount > 0 ||
-                    idleCount >= mConstants.MIN_IDLE_COUNT ||
-                    connectivityCount >= mConstants.MIN_CONNECTIVITY_COUNT ||
-                    chargingCount >= mConstants.MIN_CHARGING_COUNT ||
-                    batteryNotLowCount >= mConstants.MIN_BATTERY_NOT_LOW_COUNT ||
-                    storageNotLowCount >= mConstants.MIN_STORAGE_NOT_LOW_COUNT ||
-                    contentCount >= mConstants.MIN_CONTENT_COUNT ||
-                    forceBatchedCount >= mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT ||
-                    (unbatchedCount > 0 && (unbatchedCount + forceBatchedCount)
-                            >= mConstants.MIN_READY_JOBS_COUNT)) {
-                if (DEBUG) {
-                    Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Running jobs.");
-                }
-                noteJobsPending(runnableJobs);
-                mPendingJobs.addAll(runnableJobs);
-                if (mPendingJobs.size() > 1) {
-                    mPendingJobs.sort(mEnqueueTimeComparator);
-                }
-            } else {
-                if (DEBUG) {
-                    Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Not running anything.");
-                }
-            }
-
-            // Be ready for next time
-            reset();
-        }
-
-        @VisibleForTesting
-        void reset() {
-            chargingCount = 0;
-            idleCount =  0;
-            backoffCount = 0;
-            connectivityCount = 0;
-            batteryNotLowCount = 0;
-            storageNotLowCount = 0;
-            contentCount = 0;
-            forceBatchedCount = 0;
-            unbatchedCount = 0;
-            runnableJobs.clear();
-        }
-    }
-    private final MaybeReadyJobQueueFunctor mMaybeQueueFunctor = new MaybeReadyJobQueueFunctor();
-
-    private void maybeQueueReadyJobsForExecutionLocked() {
-        if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
-
-        noteJobsNonpending(mPendingJobs);
-        mPendingJobs.clear();
-        stopNonReadyActiveJobsLocked();
-        mJobs.forEachJob(mMaybeQueueFunctor);
-        mMaybeQueueFunctor.postProcess();
-    }
-
-    /**
-     * Heartbeat tracking.  The heartbeat alarm is intentionally non-wakeup.
-     */
-    class HeartbeatAlarmListener implements AlarmManager.OnAlarmListener {
-
-        @Override
-        public void onAlarm() {
-            synchronized (mLock) {
-                final long sinceLast = sElapsedRealtimeClock.millis() - mLastHeartbeatTime;
-                final long beatsElapsed = sinceLast / mConstants.STANDBY_HEARTBEAT_TIME;
-                if (beatsElapsed > 0) {
-                    mLastHeartbeatTime += beatsElapsed * mConstants.STANDBY_HEARTBEAT_TIME;
-                    advanceHeartbeatLocked(beatsElapsed);
-                }
-            }
-            setNextHeartbeatAlarm();
-        }
-    }
-
-    // Intentionally does not touch the alarm timing
-    void advanceHeartbeatLocked(long beatsElapsed) {
-        if (!mConstants.USE_HEARTBEATS) {
-            return;
-        }
-        mHeartbeat += beatsElapsed;
-        if (DEBUG_STANDBY) {
-            Slog.v(TAG, "Advancing standby heartbeat by " + beatsElapsed
-                    + " to " + mHeartbeat);
-        }
-        // Don't update ACTIVE or NEVER bucket milestones.  Note that mHeartbeat
-        // will be equal to mNextBucketHeartbeat[bucket] for one beat, during which
-        // new jobs scheduled by apps in that bucket will be permitted to run
-        // immediately.
-        boolean didAdvanceBucket = false;
-        for (int i = 1; i < mNextBucketHeartbeat.length - 1; i++) {
-            // Did we reach or cross a bucket boundary?
-            if (mHeartbeat >= mNextBucketHeartbeat[i]) {
-                didAdvanceBucket = true;
-            }
-            while (mHeartbeat > mNextBucketHeartbeat[i]) {
-                mNextBucketHeartbeat[i] += mConstants.STANDBY_BEATS[i];
-            }
-            if (DEBUG_STANDBY) {
-                Slog.v(TAG, "   Bucket " + i + " next heartbeat "
-                        + mNextBucketHeartbeat[i]);
-            }
-        }
-
-        if (didAdvanceBucket) {
-            if (DEBUG_STANDBY) {
-                Slog.v(TAG, "Hit bucket boundary; reevaluating job runnability");
-            }
-            mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
-        }
-    }
-
-    void setNextHeartbeatAlarm() {
-        final long heartbeatLength;
-        synchronized (mLock) {
-            if (!mConstants.USE_HEARTBEATS) {
-                return;
-            }
-            heartbeatLength = mConstants.STANDBY_HEARTBEAT_TIME;
-        }
-        final long now = sElapsedRealtimeClock.millis();
-        final long nextBeatOrdinal = (now + heartbeatLength) / heartbeatLength;
-        final long nextHeartbeat = nextBeatOrdinal * heartbeatLength;
-        if (DEBUG_STANDBY) {
-            Slog.i(TAG, "Setting heartbeat alarm for " + nextHeartbeat
-                    + " = " + TimeUtils.formatDuration(nextHeartbeat - now));
-        }
-        AlarmManager am = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
-        am.setExact(AlarmManager.ELAPSED_REALTIME, nextHeartbeat,
-                HEARTBEAT_TAG, mHeartbeatAlarm, mHandler);
-    }
-
-    /** Returns true if both the calling and source users for the job are started. */
-    private boolean areUsersStartedLocked(final JobStatus job) {
-        boolean sourceStarted = ArrayUtils.contains(mStartedUsers, job.getSourceUserId());
-        if (job.getUserId() == job.getSourceUserId()) {
-            return sourceStarted;
-        }
-        return sourceStarted && ArrayUtils.contains(mStartedUsers, job.getUserId());
-    }
-
-    /**
-     * Criteria for moving a job into the pending queue:
-     *      - It's ready.
-     *      - It's not pending.
-     *      - It's not already running on a JSC.
-     *      - The user that requested the job is running.
-     *      - The job's standby bucket has come due to be runnable.
-     *      - The component is enabled and runnable.
-     */
-    @VisibleForTesting
-    boolean isReadyToBeExecutedLocked(JobStatus job) {
-        final boolean jobReady = job.isReady();
-
-        if (DEBUG) {
-            Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
-                    + " ready=" + jobReady);
-        }
-
-        // This is a condition that is very likely to be false (most jobs that are
-        // scheduled are sitting there, not ready yet) and very cheap to check (just
-        // a few conditions on data in JobStatus).
-        if (!jobReady) {
-            if (job.getSourcePackageName().equals("android.jobscheduler.cts.jobtestapp")) {
-                Slog.v(TAG, "    NOT READY: " + job);
-            }
-            return false;
-        }
-
-        final boolean jobExists = mJobs.containsJob(job);
-
-        final boolean userStarted = areUsersStartedLocked(job);
-
-        if (DEBUG) {
-            Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
-                    + " exists=" + jobExists + " userStarted=" + userStarted);
-        }
-
-        // These are also fairly cheap to check, though they typically will not
-        // be conditions we fail.
-        if (!jobExists || !userStarted) {
-            return false;
-        }
-
-        if (isJobThermalConstrainedLocked(job)) {
-            return false;
-        }
-
-        final boolean jobPending = mPendingJobs.contains(job);
-        final boolean jobActive = isCurrentlyActiveLocked(job);
-
-        if (DEBUG) {
-            Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
-                    + " pending=" + jobPending + " active=" + jobActive);
-        }
-
-        // These can be a little more expensive (especially jobActive, since we need to
-        // go through the array of all potentially active jobs), so we are doing them
-        // later...  but still before checking with the package manager!
-        if (jobPending || jobActive) {
-            return false;
-        }
-
-        if (mConstants.USE_HEARTBEATS) {
-            // If the app is in a non-active standby bucket, make sure we've waited
-            // an appropriate amount of time since the last invocation.  During device-
-            // wide parole, standby bucketing is ignored.
-            //
-            // Jobs in 'active' apps are not subject to standby, nor are jobs that are
-            // specifically marked as exempt.
-            if (DEBUG_STANDBY) {
-                Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
-                        + " parole=" + mInParole + " active=" + job.uidActive
-                        + " exempt=" + job.getJob().isExemptedFromAppStandby());
-            }
-            if (!mInParole
-                    && !job.uidActive
-                    && !job.getJob().isExemptedFromAppStandby()) {
-                final int bucket = job.getStandbyBucket();
-                if (DEBUG_STANDBY) {
-                    Slog.v(TAG, "  bucket=" + bucket + " heartbeat=" + mHeartbeat
-                            + " next=" + mNextBucketHeartbeat[bucket]);
-                }
-                if (mHeartbeat < mNextBucketHeartbeat[bucket]) {
-                    // Only skip this job if the app is still waiting for the end of its nominal
-                    // bucket interval.  Once it's waited that long, we let it go ahead and clear.
-                    // The final (NEVER) bucket is special; we never age those apps' jobs into
-                    // runnability.
-                    final long appLastRan = heartbeatWhenJobsLastRun(job);
-                    if (bucket >= mConstants.STANDBY_BEATS.length
-                            || (mHeartbeat > appLastRan
-                            && mHeartbeat < appLastRan + mConstants.STANDBY_BEATS[bucket])) {
-                        if (job.getWhenStandbyDeferred() == 0) {
-                            if (DEBUG_STANDBY) {
-                                Slog.v(TAG, "Bucket deferral: " + mHeartbeat + " < "
-                                        + (appLastRan + mConstants.STANDBY_BEATS[bucket])
-                                        + " for " + job);
-                            }
-                            job.setWhenStandbyDeferred(sElapsedRealtimeClock.millis());
-                        }
-                        return false;
-                    } else {
-                        if (DEBUG_STANDBY) {
-                            Slog.v(TAG, "Bucket deferred job aged into runnability at "
-                                    + mHeartbeat + " : " + job);
-                        }
-                    }
-                }
-            }
-        }
-
-        // The expensive check: validate that the defined package+service is
-        // still present & viable.
-        return isComponentUsable(job);
-    }
-
-    private boolean isComponentUsable(@NonNull JobStatus job) {
-        final ServiceInfo service;
-        try {
-            // TODO: cache result until we're notified that something in the package changed.
-            service = AppGlobals.getPackageManager().getServiceInfo(
-                    job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
-                    job.getUserId());
-        } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
-        }
-
-        if (service == null) {
-            if (DEBUG) {
-                Slog.v(TAG, "isComponentUsable: " + job.toShortString()
-                        + " component not present");
-            }
-            return false;
-        }
-
-        // Everything else checked out so far, so this is the final yes/no check
-        final boolean appIsBad = mActivityManagerInternal.isAppBad(service.applicationInfo);
-        if (DEBUG && appIsBad) {
-            Slog.i(TAG, "App is bad for " + job.toShortString() + " so not runnable");
-        }
-        return !appIsBad;
-    }
-
-    @VisibleForTesting
-    void evaluateControllerStatesLocked(final JobStatus job) {
-        for (int c = mControllers.size() - 1; c >= 0; --c) {
-            final StateController sc = mControllers.get(c);
-            sc.evaluateStateLocked(job);
-        }
-    }
-
-    /**
-     * Returns true if non-job constraint components are in place -- if job.isReady() returns true
-     * and this method returns true, then the job is ready to be executed.
-     */
-    public boolean areComponentsInPlaceLocked(JobStatus job) {
-        // This code is very similar to the code in isReadyToBeExecutedLocked --- it uses the same
-        // conditions.
-
-        final boolean jobExists = mJobs.containsJob(job);
-        final boolean userStarted = areUsersStartedLocked(job);
-
-        if (DEBUG) {
-            Slog.v(TAG, "areComponentsInPlaceLocked: " + job.toShortString()
-                    + " exists=" + jobExists + " userStarted=" + userStarted);
-        }
-
-        // These are also fairly cheap to check, though they typically will not
-        // be conditions we fail.
-        if (!jobExists || !userStarted) {
-            return false;
-        }
-
-        if (isJobThermalConstrainedLocked(job)) {
-            return false;
-        }
-
-        // Job pending/active doesn't affect the readiness of a job.
-
-        // Skipping the heartbeat check as this will only come into play when using the rolling
-        // window quota management system.
-
-        // The expensive check: validate that the defined package+service is
-        // still present & viable.
-        return isComponentUsable(job);
-    }
-
-    /** Returns the maximum amount of time this job could run for. */
-    public long getMaxJobExecutionTimeMs(JobStatus job) {
-        synchronized (mLock) {
-            if (mConstants.USE_HEARTBEATS) {
-                return JobServiceContext.EXECUTING_TIMESLICE_MILLIS;
-            }
-            return Math.min(mQuotaController.getMaxJobExecutionTimeMsLocked(job),
-                    JobServiceContext.EXECUTING_TIMESLICE_MILLIS);
-        }
-    }
-
-    /**
-     * Reconcile jobs in the pending queue against available execution contexts.
-     * A controller can force a job into the pending queue even if it's already running, but
-     * here is where we decide whether to actually execute it.
-     */
-    void maybeRunPendingJobsLocked() {
-        if (DEBUG) {
-            Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
-        }
-        mConcurrencyManager.assignJobsToContextsLocked();
-        reportActiveLocked();
-    }
-
-    private int adjustJobPriority(int curPriority, JobStatus job) {
-        if (curPriority < JobInfo.PRIORITY_TOP_APP) {
-            float factor = mJobPackageTracker.getLoadFactor(job);
-            if (factor >= mConstants.HEAVY_USE_FACTOR) {
-                curPriority += JobInfo.PRIORITY_ADJ_ALWAYS_RUNNING;
-            } else if (factor >= mConstants.MODERATE_USE_FACTOR) {
-                curPriority += JobInfo.PRIORITY_ADJ_OFTEN_RUNNING;
-            }
-        }
-        return curPriority;
-    }
-
-    int evaluateJobPriorityLocked(JobStatus job) {
-        int priority = job.getPriority();
-        if (priority >= JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE) {
-            return adjustJobPriority(priority, job);
-        }
-        int override = mUidPriorityOverride.get(job.getSourceUid(), 0);
-        if (override != 0) {
-            return adjustJobPriority(override, job);
-        }
-        return adjustJobPriority(priority, job);
-    }
-
-    final class LocalService implements JobSchedulerInternal {
-
-        /**
-         * The current bucket heartbeat ordinal
-         */
-        public long currentHeartbeat() {
-            return getCurrentHeartbeat();
-        }
-
-        /**
-         * Heartbeat ordinal at which the given standby bucket's jobs next become runnable
-         */
-        public long nextHeartbeatForBucket(int bucket) {
-            synchronized (mLock) {
-                return mNextBucketHeartbeat[bucket];
-            }
-        }
-
-        /**
-         * Heartbeat ordinal for the given app.  This is typically the heartbeat at which
-         * the app last ran jobs, so that a newly-scheduled job in an app that hasn't run
-         * jobs in a long time is immediately runnable even if the app is bucketed into
-         * an infrequent time allocation.
-         */
-        public long baseHeartbeatForApp(String packageName, @UserIdInt int userId,
-                final int appStandbyBucket) {
-            if (appStandbyBucket == 0 ||
-                    appStandbyBucket >= mConstants.STANDBY_BEATS.length) {
-                // ACTIVE => everything can be run right away
-                // NEVER => we won't run them anyway, so let them go in the future
-                // as soon as the app enters normal use
-                if (DEBUG_STANDBY) {
-                    Slog.v(TAG, "Base heartbeat forced ZERO for new job in "
-                            + packageName + "/" + userId);
-                }
-                return 0;
-            }
-
-            final long baseHeartbeat = heartbeatWhenJobsLastRun(packageName, userId);
-            if (DEBUG_STANDBY) {
-                Slog.v(TAG, "Base heartbeat " + baseHeartbeat + " for new job in "
-                        + packageName + "/" + userId);
-            }
-            return baseHeartbeat;
-        }
-
-        public void noteJobStart(String packageName, int userId) {
-            synchronized (mLock) {
-                setLastJobHeartbeatLocked(packageName, userId, mHeartbeat);
-            }
-        }
-
-        /**
-         * Returns a list of all pending jobs. A running job is not considered pending. Periodic
-         * jobs are always considered pending.
-         */
-        @Override
-        public List<JobInfo> getSystemScheduledPendingJobs() {
-            synchronized (mLock) {
-                final List<JobInfo> pendingJobs = new ArrayList<JobInfo>();
-                mJobs.forEachJob(Process.SYSTEM_UID, (job) -> {
-                    if (job.getJob().isPeriodic() || !isCurrentlyActiveLocked(job)) {
-                        pendingJobs.add(job.getJob());
-                    }
-                });
-                return pendingJobs;
-            }
-        }
-
-        @Override
-        public void cancelJobsForUid(int uid, String reason) {
-            JobSchedulerService.this.cancelJobsForUid(uid, reason);
-        }
-
-        @Override
-        public void addBackingUpUid(int uid) {
-            synchronized (mLock) {
-                // No need to actually do anything here, since for a full backup the
-                // activity manager will kill the process which will kill the job (and
-                // cause it to restart, but now it can't run).
-                mBackingUpUids.put(uid, uid);
-            }
-        }
-
-        @Override
-        public void removeBackingUpUid(int uid) {
-            synchronized (mLock) {
-                mBackingUpUids.delete(uid);
-                // If there are any jobs for this uid, we need to rebuild the pending list
-                // in case they are now ready to run.
-                if (mJobs.countJobsForUid(uid) > 0) {
-                    mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
-                }
-            }
-        }
-
-        @Override
-        public void clearAllBackingUpUids() {
-            synchronized (mLock) {
-                if (mBackingUpUids.size() > 0) {
-                    mBackingUpUids.clear();
-                    mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
-                }
-            }
-        }
-
-        @Override
-        public void reportAppUsage(String packageName, int userId) {
-            JobSchedulerService.this.reportAppUsage(packageName, userId);
-        }
-
-        @Override
-        public JobStorePersistStats getPersistStats() {
-            synchronized (mLock) {
-                return new JobStorePersistStats(mJobs.getPersistStats());
-            }
-        }
-    }
-
-    /**
-     * Tracking of app assignments to standby buckets
-     */
-    final class StandbyTracker extends AppIdleStateChangeListener {
-
-        // AppIdleStateChangeListener interface for live updates
-
-        @Override
-        public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId,
-                boolean idle, int bucket, int reason) {
-            // QuotaController handles this now.
-        }
-
-        @Override
-        public void onParoleStateChanged(boolean isParoleOn) {
-            if (DEBUG_STANDBY) {
-                Slog.i(TAG, "Global parole state now " + (isParoleOn ? "ON" : "OFF"));
-            }
-            mInParole = isParoleOn;
-        }
-
-        @Override
-        public void onUserInteractionStarted(String packageName, int userId) {
-            final int uid = mLocalPM.getPackageUid(packageName,
-                    PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
-            if (uid < 0) {
-                // Quietly ignore; the case is already logged elsewhere
-                return;
-            }
-
-            long sinceLast = mUsageStats.getTimeSinceLastJobRun(packageName, userId);
-            if (sinceLast > 2 * DateUtils.DAY_IN_MILLIS) {
-                // Too long ago, not worth logging
-                sinceLast = 0L;
-            }
-            final DeferredJobCounter counter = new DeferredJobCounter();
-            synchronized (mLock) {
-                mJobs.forEachJobForSourceUid(uid, counter);
-            }
-            if (counter.numDeferred() > 0 || sinceLast > 0) {
-                BatteryStatsInternal mBatteryStatsInternal = LocalServices.getService
-                        (BatteryStatsInternal.class);
-                mBatteryStatsInternal.noteJobsDeferred(uid, counter.numDeferred(), sinceLast);
-                StatsLog.write_non_chained(StatsLog.DEFERRED_JOB_STATS_REPORTED, uid, null,
-                        counter.numDeferred(), sinceLast);
-            }
-        }
-    }
-
-    static class DeferredJobCounter implements Consumer<JobStatus> {
-        private int mDeferred = 0;
-
-        public int numDeferred() {
-            return mDeferred;
-        }
-
-        @Override
-        public void accept(JobStatus job) {
-            if (job.getWhenStandbyDeferred() > 0) {
-                mDeferred++;
-            }
-        }
-    }
-
-    public static int standbyBucketToBucketIndex(int bucket) {
-        // Normalize AppStandby constants to indices into our bookkeeping
-        if (bucket == UsageStatsManager.STANDBY_BUCKET_NEVER) return NEVER_INDEX;
-        else if (bucket > UsageStatsManager.STANDBY_BUCKET_FREQUENT) return RARE_INDEX;
-        else if (bucket > UsageStatsManager.STANDBY_BUCKET_WORKING_SET) return FREQUENT_INDEX;
-        else if (bucket > UsageStatsManager.STANDBY_BUCKET_ACTIVE) return WORKING_INDEX;
-        else return ACTIVE_INDEX;
-    }
-
-    // Static to support external callers
-    public static int standbyBucketForPackage(String packageName, int userId, long elapsedNow) {
-        UsageStatsManagerInternal usageStats = LocalServices.getService(
-                UsageStatsManagerInternal.class);
-        int bucket = usageStats != null
-                ? usageStats.getAppStandbyBucket(packageName, userId, elapsedNow)
-                : 0;
-
-        bucket = standbyBucketToBucketIndex(bucket);
-
-        if (DEBUG_STANDBY) {
-            Slog.v(TAG, packageName + "/" + userId + " standby bucket index: " + bucket);
-        }
-        return bucket;
-    }
-
-    /**
-     * Binder stub trampoline implementation
-     */
-    final class JobSchedulerStub extends IJobScheduler.Stub {
-        /** Cache determination of whether a given app can persist jobs
-         * key is uid of the calling app; value is undetermined/true/false
-         */
-        private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
-
-        // Enforce that only the app itself (or shared uid participant) can schedule a
-        // job that runs one of the app's services, as well as verifying that the
-        // named service properly requires the BIND_JOB_SERVICE permission
-        private void enforceValidJobRequest(int uid, JobInfo job) {
-            final IPackageManager pm = AppGlobals.getPackageManager();
-            final ComponentName service = job.getService();
-            try {
-                ServiceInfo si = pm.getServiceInfo(service,
-                        PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
-                        UserHandle.getUserId(uid));
-                if (si == null) {
-                    throw new IllegalArgumentException("No such service " + service);
-                }
-                if (si.applicationInfo.uid != uid) {
-                    throw new IllegalArgumentException("uid " + uid +
-                            " cannot schedule job in " + service.getPackageName());
-                }
-                if (!JobService.PERMISSION_BIND.equals(si.permission)) {
-                    throw new IllegalArgumentException("Scheduled service " + service
-                            + " does not require android.permission.BIND_JOB_SERVICE permission");
-                }
-            } catch (RemoteException e) {
-                // Can't happen; the Package Manager is in this same process
-            }
-        }
-
-        private boolean canPersistJobs(int pid, int uid) {
-            // If we get this far we're good to go; all we need to do now is check
-            // whether the app is allowed to persist its scheduled work.
-            final boolean canPersist;
-            synchronized (mPersistCache) {
-                Boolean cached = mPersistCache.get(uid);
-                if (cached != null) {
-                    canPersist = cached.booleanValue();
-                } else {
-                    // Persisting jobs is tantamount to running at boot, so we permit
-                    // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
-                    // permission
-                    int result = getContext().checkPermission(
-                            android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
-                    canPersist = (result == PackageManager.PERMISSION_GRANTED);
-                    mPersistCache.put(uid, canPersist);
-                }
-            }
-            return canPersist;
-        }
-
-        private void validateJobFlags(JobInfo job, int callingUid) {
-            if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
-                getContext().enforceCallingOrSelfPermission(
-                        android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG);
-            }
-            if ((job.getFlags() & JobInfo.FLAG_EXEMPT_FROM_APP_STANDBY) != 0) {
-                if (callingUid != Process.SYSTEM_UID) {
-                    throw new SecurityException("Job has invalid flags");
-                }
-                if (job.isPeriodic()) {
-                    Slog.wtf(TAG, "Periodic jobs mustn't have"
-                            + " FLAG_EXEMPT_FROM_APP_STANDBY. Job=" + job);
-                }
-            }
-        }
-
-        // IJobScheduler implementation
-        @Override
-        public int schedule(JobInfo job) throws RemoteException {
-            if (DEBUG) {
-                Slog.d(TAG, "Scheduling job: " + job.toString());
-            }
-            final int pid = Binder.getCallingPid();
-            final int uid = Binder.getCallingUid();
-            final int userId = UserHandle.getUserId(uid);
-
-            enforceValidJobRequest(uid, job);
-            if (job.isPersisted()) {
-                if (!canPersistJobs(pid, uid)) {
-                    throw new IllegalArgumentException("Error: requested job be persisted without"
-                            + " holding RECEIVE_BOOT_COMPLETED permission.");
-                }
-            }
-
-            validateJobFlags(job, uid);
-
-            long ident = Binder.clearCallingIdentity();
-            try {
-                return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, userId,
-                        null);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-
-        // IJobScheduler implementation
-        @Override
-        public int enqueue(JobInfo job, JobWorkItem work) throws RemoteException {
-            if (DEBUG) {
-                Slog.d(TAG, "Enqueueing job: " + job.toString() + " work: " + work);
-            }
-            final int uid = Binder.getCallingUid();
-            final int userId = UserHandle.getUserId(uid);
-
-            enforceValidJobRequest(uid, job);
-            if (job.isPersisted()) {
-                throw new IllegalArgumentException("Can't enqueue work for persisted jobs");
-            }
-            if (work == null) {
-                throw new NullPointerException("work is null");
-            }
-
-            validateJobFlags(job, uid);
-
-            long ident = Binder.clearCallingIdentity();
-            try {
-                return JobSchedulerService.this.scheduleAsPackage(job, work, uid, null, userId,
-                        null);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-
-        @Override
-        public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag)
-                throws RemoteException {
-            final int callerUid = Binder.getCallingUid();
-            if (DEBUG) {
-                Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString()
-                        + " on behalf of " + packageName + "/");
-            }
-
-            if (packageName == null) {
-                throw new NullPointerException("Must specify a package for scheduleAsPackage()");
-            }
-
-            int mayScheduleForOthers = getContext().checkCallingOrSelfPermission(
-                    android.Manifest.permission.UPDATE_DEVICE_STATS);
-            if (mayScheduleForOthers != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException("Caller uid " + callerUid
-                        + " not permitted to schedule jobs for other apps");
-            }
-
-            validateJobFlags(job, callerUid);
-
-            long ident = Binder.clearCallingIdentity();
-            try {
-                return JobSchedulerService.this.scheduleAsPackage(job, null, callerUid,
-                        packageName, userId, tag);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-
-        @Override
-        public ParceledListSlice<JobInfo> getAllPendingJobs() throws RemoteException {
-            final int uid = Binder.getCallingUid();
-
-            long ident = Binder.clearCallingIdentity();
-            try {
-                return new ParceledListSlice<>(JobSchedulerService.this.getPendingJobs(uid));
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-
-        @Override
-        public JobInfo getPendingJob(int jobId) throws RemoteException {
-            final int uid = Binder.getCallingUid();
-
-            long ident = Binder.clearCallingIdentity();
-            try {
-                return JobSchedulerService.this.getPendingJob(uid, jobId);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-
-        @Override
-        public void cancelAll() throws RemoteException {
-            final int uid = Binder.getCallingUid();
-            long ident = Binder.clearCallingIdentity();
-            try {
-                JobSchedulerService.this.cancelJobsForUid(uid,
-                        "cancelAll() called by app, callingUid=" + uid);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-
-        @Override
-        public void cancel(int jobId) throws RemoteException {
-            final int uid = Binder.getCallingUid();
-
-            long ident = Binder.clearCallingIdentity();
-            try {
-                JobSchedulerService.this.cancelJob(uid, jobId, uid);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-
-        /**
-         * "dumpsys" infrastructure
-         */
-        @Override
-        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, pw)) return;
-
-            int filterUid = -1;
-            boolean proto = false;
-            if (!ArrayUtils.isEmpty(args)) {
-                int opti = 0;
-                while (opti < args.length) {
-                    String arg = args[opti];
-                    if ("-h".equals(arg)) {
-                        dumpHelp(pw);
-                        return;
-                    } else if ("-a".equals(arg)) {
-                        // Ignore, we always dump all.
-                    } else if ("--proto".equals(arg)) {
-                        proto = true;
-                    } else if (arg.length() > 0 && arg.charAt(0) == '-') {
-                        pw.println("Unknown option: " + arg);
-                        return;
-                    } else {
-                        break;
-                    }
-                    opti++;
-                }
-                if (opti < args.length) {
-                    String pkg = args[opti];
-                    try {
-                        filterUid = getContext().getPackageManager().getPackageUid(pkg,
-                                PackageManager.MATCH_ANY_USER);
-                    } catch (NameNotFoundException ignored) {
-                        pw.println("Invalid package: " + pkg);
-                        return;
-                    }
-                }
-            }
-
-            final long identityToken = Binder.clearCallingIdentity();
-            try {
-                if (proto) {
-                    JobSchedulerService.this.dumpInternalProto(fd, filterUid);
-                } else {
-                    JobSchedulerService.this.dumpInternal(new IndentingPrintWriter(pw, "  "),
-                            filterUid);
-                }
-            } finally {
-                Binder.restoreCallingIdentity(identityToken);
-            }
-        }
-
-        @Override
-        public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
-                String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
-                (new JobSchedulerShellCommand(JobSchedulerService.this)).exec(
-                        this, in, out, err, args, callback, resultReceiver);
-        }
-
-        /**
-         * <b>For internal system user only!</b>
-         * Returns a list of all currently-executing jobs.
-         */
-        @Override
-        public List<JobInfo> getStartedJobs() {
-            final int uid = Binder.getCallingUid();
-            if (uid != Process.SYSTEM_UID) {
-                throw new SecurityException(
-                    "getStartedJobs() is system internal use only.");
-            }
-
-            final ArrayList<JobInfo> runningJobs;
-
-            synchronized (mLock) {
-                runningJobs = new ArrayList<>(mActiveServices.size());
-                for (JobServiceContext jsc : mActiveServices) {
-                    final JobStatus job = jsc.getRunningJobLocked();
-                    if (job != null) {
-                        runningJobs.add(job.getJob());
-                    }
-                }
-            }
-
-            return runningJobs;
-        }
-
-        /**
-         * <b>For internal system user only!</b>
-         * Returns a snapshot of the state of all jobs known to the system.
-         *
-         * <p class="note">This is a slow operation, so it should be called sparingly.
-         */
-        @Override
-        public ParceledListSlice<JobSnapshot> getAllJobSnapshots() {
-            final int uid = Binder.getCallingUid();
-            if (uid != Process.SYSTEM_UID) {
-                throw new SecurityException(
-                    "getAllJobSnapshots() is system internal use only.");
-            }
-            synchronized (mLock) {
-                final ArrayList<JobSnapshot> snapshots = new ArrayList<>(mJobs.size());
-                mJobs.forEachJob((job) -> snapshots.add(
-                        new JobSnapshot(job.getJob(), job.getSatisfiedConstraintFlags(),
-                                isReadyToBeExecutedLocked(job))));
-                return new ParceledListSlice<>(snapshots);
-            }
-        }
-    };
-
-    // Shell command infrastructure: run the given job immediately
-    int executeRunCommand(String pkgName, int userId, int jobId, boolean force) {
-        if (DEBUG) {
-            Slog.v(TAG, "executeRunCommand(): " + pkgName + "/" + userId
-                    + " " + jobId + " f=" + force);
-        }
-
-        try {
-            final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
-                    userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
-            if (uid < 0) {
-                return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
-            }
-
-            synchronized (mLock) {
-                final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
-                if (js == null) {
-                    return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
-                }
-
-                js.overrideState = (force) ? JobStatus.OVERRIDE_FULL : JobStatus.OVERRIDE_SOFT;
-                if (!js.isConstraintsSatisfied()) {
-                    js.overrideState = 0;
-                    return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
-                }
-
-                queueReadyJobsForExecutionLocked();
-                maybeRunPendingJobsLocked();
-            }
-        } catch (RemoteException e) {
-            // can't happen
-        }
-        return 0;
-    }
-
-    // Shell command infrastructure: immediately timeout currently executing jobs
-    int executeTimeoutCommand(PrintWriter pw, String pkgName, int userId,
-            boolean hasJobId, int jobId) {
-        if (DEBUG) {
-            Slog.v(TAG, "executeTimeoutCommand(): " + pkgName + "/" + userId + " " + jobId);
-        }
-
-        synchronized (mLock) {
-            boolean foundSome = false;
-            for (int i=0; i<mActiveServices.size(); i++) {
-                final JobServiceContext jc = mActiveServices.get(i);
-                final JobStatus js = jc.getRunningJobLocked();
-                if (jc.timeoutIfExecutingLocked(pkgName, userId, hasJobId, jobId, "shell")) {
-                    foundSome = true;
-                    pw.print("Timing out: ");
-                    js.printUniqueId(pw);
-                    pw.print(" ");
-                    pw.println(js.getServiceComponent().flattenToShortString());
-                }
-            }
-            if (!foundSome) {
-                pw.println("No matching executing jobs found.");
-            }
-        }
-        return 0;
-    }
-
-    // Shell command infrastructure: cancel a scheduled job
-    int executeCancelCommand(PrintWriter pw, String pkgName, int userId,
-            boolean hasJobId, int jobId) {
-        if (DEBUG) {
-            Slog.v(TAG, "executeCancelCommand(): " + pkgName + "/" + userId + " " + jobId);
-        }
-
-        int pkgUid = -1;
-        try {
-            IPackageManager pm = AppGlobals.getPackageManager();
-            pkgUid = pm.getPackageUid(pkgName, 0, userId);
-        } catch (RemoteException e) { /* can't happen */ }
-
-        if (pkgUid < 0) {
-            pw.println("Package " + pkgName + " not found.");
-            return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
-        }
-
-        if (!hasJobId) {
-            pw.println("Canceling all jobs for " + pkgName + " in user " + userId);
-            if (!cancelJobsForUid(pkgUid, "cancel shell command for package")) {
-                pw.println("No matching jobs found.");
-            }
-        } else {
-            pw.println("Canceling job " + pkgName + "/#" + jobId + " in user " + userId);
-            if (!cancelJob(pkgUid, jobId, Process.SHELL_UID)) {
-                pw.println("No matching job found.");
-            }
-        }
-
-        return 0;
-    }
-
-    void setMonitorBattery(boolean enabled) {
-        synchronized (mLock) {
-            if (mBatteryController != null) {
-                mBatteryController.getTracker().setMonitorBatteryLocked(enabled);
-            }
-        }
-    }
-
-    int getBatterySeq() {
-        synchronized (mLock) {
-            return mBatteryController != null ? mBatteryController.getTracker().getSeq() : -1;
-        }
-    }
-
-    boolean getBatteryCharging() {
-        synchronized (mLock) {
-            return mBatteryController != null
-                    ? mBatteryController.getTracker().isOnStablePower() : false;
-        }
-    }
-
-    boolean getBatteryNotLow() {
-        synchronized (mLock) {
-            return mBatteryController != null
-                    ? mBatteryController.getTracker().isBatteryNotLow() : false;
-        }
-    }
-
-    int getStorageSeq() {
-        synchronized (mLock) {
-            return mStorageController != null ? mStorageController.getTracker().getSeq() : -1;
-        }
-    }
-
-    boolean getStorageNotLow() {
-        synchronized (mLock) {
-            return mStorageController != null
-                    ? mStorageController.getTracker().isStorageNotLow() : false;
-        }
-    }
-
-    long getCurrentHeartbeat() {
-        synchronized (mLock) {
-            return mHeartbeat;
-        }
-    }
-
-    // Shell command infrastructure
-    int getJobState(PrintWriter pw, String pkgName, int userId, int jobId) {
-        try {
-            final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
-                    userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
-            if (uid < 0) {
-                pw.print("unknown("); pw.print(pkgName); pw.println(")");
-                return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
-            }
-
-            synchronized (mLock) {
-                final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
-                if (DEBUG) Slog.d(TAG, "get-job-state " + uid + "/" + jobId + ": " + js);
-                if (js == null) {
-                    pw.print("unknown("); UserHandle.formatUid(pw, uid);
-                    pw.print("/jid"); pw.print(jobId); pw.println(")");
-                    return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
-                }
-
-                boolean printed = false;
-                if (mPendingJobs.contains(js)) {
-                    pw.print("pending");
-                    printed = true;
-                }
-                if (isCurrentlyActiveLocked(js)) {
-                    if (printed) {
-                        pw.print(" ");
-                    }
-                    printed = true;
-                    pw.println("active");
-                }
-                if (!ArrayUtils.contains(mStartedUsers, js.getUserId())) {
-                    if (printed) {
-                        pw.print(" ");
-                    }
-                    printed = true;
-                    pw.println("user-stopped");
-                }
-                if (!ArrayUtils.contains(mStartedUsers, js.getSourceUserId())) {
-                    if (printed) {
-                        pw.print(" ");
-                    }
-                    printed = true;
-                    pw.println("source-user-stopped");
-                }
-                if (mBackingUpUids.indexOfKey(js.getSourceUid()) >= 0) {
-                    if (printed) {
-                        pw.print(" ");
-                    }
-                    printed = true;
-                    pw.println("backing-up");
-                }
-                boolean componentPresent = false;
-                try {
-                    componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
-                            js.getServiceComponent(),
-                            PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
-                            js.getUserId()) != null);
-                } catch (RemoteException e) {
-                }
-                if (!componentPresent) {
-                    if (printed) {
-                        pw.print(" ");
-                    }
-                    printed = true;
-                    pw.println("no-component");
-                }
-                if (js.isReady()) {
-                    if (printed) {
-                        pw.print(" ");
-                    }
-                    printed = true;
-                    pw.println("ready");
-                }
-                if (!printed) {
-                    pw.print("waiting");
-                }
-                pw.println();
-            }
-        } catch (RemoteException e) {
-            // can't happen
-        }
-        return 0;
-    }
-
-    // Shell command infrastructure
-    int executeHeartbeatCommand(PrintWriter pw, int numBeats) {
-        if (numBeats < 1) {
-            pw.println(getCurrentHeartbeat());
-            return 0;
-        }
-
-        pw.print("Advancing standby heartbeat by ");
-        pw.println(numBeats);
-        synchronized (mLock) {
-            advanceHeartbeatLocked(numBeats);
-        }
-        return 0;
-    }
-
-    void triggerDockState(boolean idleState) {
-        final Intent dockIntent;
-        if (idleState) {
-            dockIntent = new Intent(Intent.ACTION_DOCK_IDLE);
-        } else {
-            dockIntent = new Intent(Intent.ACTION_DOCK_ACTIVE);
-        }
-        dockIntent.setPackage("android");
-        dockIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
-        getContext().sendBroadcastAsUser(dockIntent, UserHandle.ALL);
-    }
-
-    static void dumpHelp(PrintWriter pw) {
-        pw.println("Job Scheduler (jobscheduler) dump options:");
-        pw.println("  [-h] [package] ...");
-        pw.println("    -h: print this help");
-        pw.println("  [package] is an optional package name to limit the output to.");
-    }
-
-    /** Sort jobs by caller UID, then by Job ID. */
-    private static void sortJobs(List<JobStatus> jobs) {
-        Collections.sort(jobs, new Comparator<JobStatus>() {
-            @Override
-            public int compare(JobStatus o1, JobStatus o2) {
-                int uid1 = o1.getUid();
-                int uid2 = o2.getUid();
-                int id1 = o1.getJobId();
-                int id2 = o2.getJobId();
-                if (uid1 != uid2) {
-                    return uid1 < uid2 ? -1 : 1;
-                }
-                return id1 < id2 ? -1 : (id1 > id2 ? 1 : 0);
-            }
-        });
-    }
-
-    void dumpInternal(final IndentingPrintWriter pw, int filterUid) {
-        final int filterUidFinal = UserHandle.getAppId(filterUid);
-        final long now = sSystemClock.millis();
-        final long nowElapsed = sElapsedRealtimeClock.millis();
-        final long nowUptime = sUptimeMillisClock.millis();
-
-        final Predicate<JobStatus> predicate = (js) -> {
-            return filterUidFinal == -1 || UserHandle.getAppId(js.getUid()) == filterUidFinal
-                    || UserHandle.getAppId(js.getSourceUid()) == filterUidFinal;
-        };
-        synchronized (mLock) {
-            mConstants.dump(pw);
-            for (StateController controller : mControllers) {
-                pw.increaseIndent();
-                controller.dumpConstants(pw);
-                pw.decreaseIndent();
-            }
-            pw.println();
-
-            pw.println("  Heartbeat:");
-            pw.print("    Current:    "); pw.println(mHeartbeat);
-            pw.println("    Next");
-            pw.print("      ACTIVE:   "); pw.println(mNextBucketHeartbeat[0]);
-            pw.print("      WORKING:  "); pw.println(mNextBucketHeartbeat[1]);
-            pw.print("      FREQUENT: "); pw.println(mNextBucketHeartbeat[2]);
-            pw.print("      RARE:     "); pw.println(mNextBucketHeartbeat[3]);
-            pw.print("    Last heartbeat: ");
-            TimeUtils.formatDuration(mLastHeartbeatTime, nowElapsed, pw);
-            pw.println();
-            pw.print("    Next heartbeat: ");
-            TimeUtils.formatDuration(mLastHeartbeatTime + mConstants.STANDBY_HEARTBEAT_TIME,
-                    nowElapsed, pw);
-            pw.println();
-            pw.print("    In parole?: ");
-            pw.print(mInParole);
-            pw.println();
-            pw.print("    In thermal throttling?: ");
-            pw.print(mThermalConstraint);
-            pw.println();
-            pw.println();
-
-            pw.println("Started users: " + Arrays.toString(mStartedUsers));
-            pw.print("Registered ");
-            pw.print(mJobs.size());
-            pw.println(" jobs:");
-            if (mJobs.size() > 0) {
-                final List<JobStatus> jobs = mJobs.mJobSet.getAllJobs();
-                sortJobs(jobs);
-                for (JobStatus job : jobs) {
-                    pw.print("  JOB #"); job.printUniqueId(pw); pw.print(": ");
-                    pw.println(job.toShortStringExceptUniqueId());
-
-                    // Skip printing details if the caller requested a filter
-                    if (!predicate.test(job)) {
-                        continue;
-                    }
-
-                    job.dump(pw, "    ", true, nowElapsed);
-                    pw.print("    Last run heartbeat: ");
-                    pw.print(heartbeatWhenJobsLastRun(job));
-                    pw.println();
-
-                    pw.print("    Ready: ");
-                    pw.print(isReadyToBeExecutedLocked(job));
-                    pw.print(" (job=");
-                    pw.print(job.isReady());
-                    pw.print(" user=");
-                    pw.print(areUsersStartedLocked(job));
-                    pw.print(" !pending=");
-                    pw.print(!mPendingJobs.contains(job));
-                    pw.print(" !active=");
-                    pw.print(!isCurrentlyActiveLocked(job));
-                    pw.print(" !backingup=");
-                    pw.print(!(mBackingUpUids.indexOfKey(job.getSourceUid()) >= 0));
-                    pw.print(" comp=");
-                    boolean componentPresent = false;
-                    try {
-                        componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
-                                job.getServiceComponent(),
-                                PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
-                                job.getUserId()) != null);
-                    } catch (RemoteException e) {
-                    }
-                    pw.print(componentPresent);
-                    pw.println(")");
-                }
-            } else {
-                pw.println("  None.");
-            }
-            for (int i=0; i<mControllers.size(); i++) {
-                pw.println();
-                pw.println(mControllers.get(i).getClass().getSimpleName() + ":");
-                pw.increaseIndent();
-                mControllers.get(i).dumpControllerStateLocked(pw, predicate);
-                pw.decreaseIndent();
-            }
-            pw.println();
-            pw.println("Uid priority overrides:");
-            for (int i=0; i< mUidPriorityOverride.size(); i++) {
-                int uid = mUidPriorityOverride.keyAt(i);
-                if (filterUidFinal == -1 || filterUidFinal == UserHandle.getAppId(uid)) {
-                    pw.print("  "); pw.print(UserHandle.formatUid(uid));
-                    pw.print(": "); pw.println(mUidPriorityOverride.valueAt(i));
-                }
-            }
-            if (mBackingUpUids.size() > 0) {
-                pw.println();
-                pw.println("Backing up uids:");
-                boolean first = true;
-                for (int i = 0; i < mBackingUpUids.size(); i++) {
-                    int uid = mBackingUpUids.keyAt(i);
-                    if (filterUidFinal == -1 || filterUidFinal == UserHandle.getAppId(uid)) {
-                        if (first) {
-                            pw.print("  ");
-                            first = false;
-                        } else {
-                            pw.print(", ");
-                        }
-                        pw.print(UserHandle.formatUid(uid));
-                    }
-                }
-                pw.println();
-            }
-            pw.println();
-            mJobPackageTracker.dump(pw, "", filterUidFinal);
-            pw.println();
-            if (mJobPackageTracker.dumpHistory(pw, "", filterUidFinal)) {
-                pw.println();
-            }
-            pw.println("Pending queue:");
-            for (int i=0; i<mPendingJobs.size(); i++) {
-                JobStatus job = mPendingJobs.get(i);
-                pw.print("  Pending #"); pw.print(i); pw.print(": ");
-                pw.println(job.toShortString());
-                job.dump(pw, "    ", false, nowElapsed);
-                int priority = evaluateJobPriorityLocked(job);
-                pw.print("    Evaluated priority: ");
-                pw.println(JobInfo.getPriorityString(priority));
-
-                pw.print("    Tag: "); pw.println(job.getTag());
-                pw.print("    Enq: ");
-                TimeUtils.formatDuration(job.madePending - nowUptime, pw);
-                pw.println();
-            }
-            pw.println();
-            pw.println("Active jobs:");
-            for (int i=0; i<mActiveServices.size(); i++) {
-                JobServiceContext jsc = mActiveServices.get(i);
-                pw.print("  Slot #"); pw.print(i); pw.print(": ");
-                final JobStatus job = jsc.getRunningJobLocked();
-                if (job == null) {
-                    if (jsc.mStoppedReason != null) {
-                        pw.print("inactive since ");
-                        TimeUtils.formatDuration(jsc.mStoppedTime, nowElapsed, pw);
-                        pw.print(", stopped because: ");
-                        pw.println(jsc.mStoppedReason);
-                    } else {
-                        pw.println("inactive");
-                    }
-                    continue;
-                } else {
-                    pw.println(job.toShortString());
-                    pw.print("    Running for: ");
-                    TimeUtils.formatDuration(nowElapsed - jsc.getExecutionStartTimeElapsed(), pw);
-                    pw.print(", timeout at: ");
-                    TimeUtils.formatDuration(jsc.getTimeoutElapsed() - nowElapsed, pw);
-                    pw.println();
-                    job.dump(pw, "    ", false, nowElapsed);
-                    int priority = evaluateJobPriorityLocked(jsc.getRunningJobLocked());
-                    pw.print("    Evaluated priority: ");
-                    pw.println(JobInfo.getPriorityString(priority));
-
-                    pw.print("    Active at ");
-                    TimeUtils.formatDuration(job.madeActive - nowUptime, pw);
-                    pw.print(", pending for ");
-                    TimeUtils.formatDuration(job.madeActive - job.madePending, pw);
-                    pw.println();
-                }
-            }
-            if (filterUid == -1) {
-                pw.println();
-                pw.print("mReadyToRock="); pw.println(mReadyToRock);
-                pw.print("mReportedActive="); pw.println(mReportedActive);
-            }
-            pw.println();
-
-            mConcurrencyManager.dumpLocked(pw, now, nowElapsed);
-
-            pw.println();
-            pw.print("PersistStats: ");
-            pw.println(mJobs.getPersistStats());
-        }
-        pw.println();
-    }
-
-    void dumpInternalProto(final FileDescriptor fd, int filterUid) {
-        ProtoOutputStream proto = new ProtoOutputStream(fd);
-        final int filterUidFinal = UserHandle.getAppId(filterUid);
-        final long now = sSystemClock.millis();
-        final long nowElapsed = sElapsedRealtimeClock.millis();
-        final long nowUptime = sUptimeMillisClock.millis();
-        final Predicate<JobStatus> predicate = (js) -> {
-            return filterUidFinal == -1 || UserHandle.getAppId(js.getUid()) == filterUidFinal
-                    || UserHandle.getAppId(js.getSourceUid()) == filterUidFinal;
-        };
-
-        synchronized (mLock) {
-            final long settingsToken = proto.start(JobSchedulerServiceDumpProto.SETTINGS);
-            mConstants.dump(proto);
-            for (StateController controller : mControllers) {
-                controller.dumpConstants(proto);
-            }
-            proto.end(settingsToken);
-
-            proto.write(JobSchedulerServiceDumpProto.CURRENT_HEARTBEAT, mHeartbeat);
-            proto.write(JobSchedulerServiceDumpProto.NEXT_HEARTBEAT, mNextBucketHeartbeat[0]);
-            proto.write(JobSchedulerServiceDumpProto.NEXT_HEARTBEAT, mNextBucketHeartbeat[1]);
-            proto.write(JobSchedulerServiceDumpProto.NEXT_HEARTBEAT, mNextBucketHeartbeat[2]);
-            proto.write(JobSchedulerServiceDumpProto.NEXT_HEARTBEAT, mNextBucketHeartbeat[3]);
-            proto.write(JobSchedulerServiceDumpProto.LAST_HEARTBEAT_TIME_MILLIS,
-                    mLastHeartbeatTime - nowUptime);
-            proto.write(JobSchedulerServiceDumpProto.NEXT_HEARTBEAT_TIME_MILLIS,
-                    mLastHeartbeatTime + mConstants.STANDBY_HEARTBEAT_TIME - nowUptime);
-            proto.write(JobSchedulerServiceDumpProto.IN_PAROLE, mInParole);
-            proto.write(JobSchedulerServiceDumpProto.IN_THERMAL, mThermalConstraint);
-
-            for (int u : mStartedUsers) {
-                proto.write(JobSchedulerServiceDumpProto.STARTED_USERS, u);
-            }
-            if (mJobs.size() > 0) {
-                final List<JobStatus> jobs = mJobs.mJobSet.getAllJobs();
-                sortJobs(jobs);
-                for (JobStatus job : jobs) {
-                    final long rjToken = proto.start(JobSchedulerServiceDumpProto.REGISTERED_JOBS);
-                    job.writeToShortProto(proto, JobSchedulerServiceDumpProto.RegisteredJob.INFO);
-
-                    // Skip printing details if the caller requested a filter
-                    if (!predicate.test(job)) {
-                        continue;
-                    }
-
-                    job.dump(proto, JobSchedulerServiceDumpProto.RegisteredJob.DUMP, true, nowElapsed);
-
-                    // isReadyToBeExecuted
-                    proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_READY,
-                            job.isReady());
-                    proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_USER_STARTED,
-                            areUsersStartedLocked(job));
-                    proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_PENDING,
-                            mPendingJobs.contains(job));
-                    proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_CURRENTLY_ACTIVE,
-                            isCurrentlyActiveLocked(job));
-                    proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_UID_BACKING_UP,
-                            mBackingUpUids.indexOfKey(job.getSourceUid()) >= 0);
-                    boolean componentPresent = false;
-                    try {
-                        componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
-                                job.getServiceComponent(),
-                                PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
-                                job.getUserId()) != null);
-                    } catch (RemoteException e) {
-                    }
-                    proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_COMPONENT_PRESENT,
-                            componentPresent);
-                    proto.write(RegisteredJob.LAST_RUN_HEARTBEAT, heartbeatWhenJobsLastRun(job));
-
-                    proto.end(rjToken);
-                }
-            }
-            for (StateController controller : mControllers) {
-                controller.dumpControllerStateLocked(
-                        proto, JobSchedulerServiceDumpProto.CONTROLLERS, predicate);
-            }
-            for (int i=0; i< mUidPriorityOverride.size(); i++) {
-                int uid = mUidPriorityOverride.keyAt(i);
-                if (filterUidFinal == -1 || filterUidFinal == UserHandle.getAppId(uid)) {
-                    long pToken = proto.start(JobSchedulerServiceDumpProto.PRIORITY_OVERRIDES);
-                    proto.write(JobSchedulerServiceDumpProto.PriorityOverride.UID, uid);
-                    proto.write(JobSchedulerServiceDumpProto.PriorityOverride.OVERRIDE_VALUE,
-                            mUidPriorityOverride.valueAt(i));
-                    proto.end(pToken);
-                }
-            }
-            for (int i = 0; i < mBackingUpUids.size(); i++) {
-                int uid = mBackingUpUids.keyAt(i);
-                if (filterUidFinal == -1 || filterUidFinal == UserHandle.getAppId(uid)) {
-                    proto.write(JobSchedulerServiceDumpProto.BACKING_UP_UIDS, uid);
-                }
-            }
-
-            mJobPackageTracker.dump(proto, JobSchedulerServiceDumpProto.PACKAGE_TRACKER,
-                    filterUidFinal);
-            mJobPackageTracker.dumpHistory(proto, JobSchedulerServiceDumpProto.HISTORY,
-                    filterUidFinal);
-
-            for (JobStatus job : mPendingJobs) {
-                final long pjToken = proto.start(JobSchedulerServiceDumpProto.PENDING_JOBS);
-
-                job.writeToShortProto(proto, PendingJob.INFO);
-                job.dump(proto, PendingJob.DUMP, false, nowElapsed);
-                proto.write(PendingJob.EVALUATED_PRIORITY, evaluateJobPriorityLocked(job));
-                proto.write(PendingJob.ENQUEUED_DURATION_MS, nowUptime - job.madePending);
-
-                proto.end(pjToken);
-            }
-            for (JobServiceContext jsc : mActiveServices) {
-                final long ajToken = proto.start(JobSchedulerServiceDumpProto.ACTIVE_JOBS);
-                final JobStatus job = jsc.getRunningJobLocked();
-
-                if (job == null) {
-                    final long ijToken = proto.start(ActiveJob.INACTIVE);
-
-                        proto.write(ActiveJob.InactiveJob.TIME_SINCE_STOPPED_MS,
-                                nowElapsed - jsc.mStoppedTime);
-                    if (jsc.mStoppedReason != null) {
-                        proto.write(ActiveJob.InactiveJob.STOPPED_REASON,
-                                jsc.mStoppedReason);
-                    }
-
-                    proto.end(ijToken);
-                } else {
-                    final long rjToken = proto.start(ActiveJob.RUNNING);
-
-                    job.writeToShortProto(proto, ActiveJob.RunningJob.INFO);
-
-                    proto.write(ActiveJob.RunningJob.RUNNING_DURATION_MS,
-                            nowElapsed - jsc.getExecutionStartTimeElapsed());
-                    proto.write(ActiveJob.RunningJob.TIME_UNTIL_TIMEOUT_MS,
-                            jsc.getTimeoutElapsed() - nowElapsed);
-
-                    job.dump(proto, ActiveJob.RunningJob.DUMP, false, nowElapsed);
-
-                    proto.write(ActiveJob.RunningJob.EVALUATED_PRIORITY,
-                            evaluateJobPriorityLocked(jsc.getRunningJobLocked()));
-
-                    proto.write(ActiveJob.RunningJob.TIME_SINCE_MADE_ACTIVE_MS,
-                            nowUptime - job.madeActive);
-                    proto.write(ActiveJob.RunningJob.PENDING_DURATION_MS,
-                            job.madeActive - job.madePending);
-
-                    proto.end(rjToken);
-                }
-                proto.end(ajToken);
-            }
-            if (filterUid == -1) {
-                proto.write(JobSchedulerServiceDumpProto.IS_READY_TO_ROCK, mReadyToRock);
-                proto.write(JobSchedulerServiceDumpProto.REPORTED_ACTIVE, mReportedActive);
-            }
-            mConcurrencyManager.dumpProtoLocked(proto,
-                    JobSchedulerServiceDumpProto.CONCURRENCY_MANAGER, now, nowElapsed);
-        }
-
-        proto.flush();
-    }
-}
diff --git a/services/core/java/com/android/server/job/JobSchedulerShellCommand.java b/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
deleted file mode 100644
index e361441..0000000
--- a/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
+++ /dev/null
@@ -1,436 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job;
-
-import android.app.ActivityManager;
-import android.app.AppGlobals;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
-import android.os.Binder;
-import android.os.ShellCommand;
-import android.os.UserHandle;
-
-import java.io.PrintWriter;
-
-public final class JobSchedulerShellCommand extends ShellCommand {
-    public static final int CMD_ERR_NO_PACKAGE = -1000;
-    public static final int CMD_ERR_NO_JOB = -1001;
-    public static final int CMD_ERR_CONSTRAINTS = -1002;
-
-    JobSchedulerService mInternal;
-    IPackageManager mPM;
-
-    JobSchedulerShellCommand(JobSchedulerService service) {
-        mInternal = service;
-        mPM = AppGlobals.getPackageManager();
-    }
-
-    @Override
-    public int onCommand(String cmd) {
-        final PrintWriter pw = getOutPrintWriter();
-        try {
-            switch (cmd != null ? cmd : "") {
-                case "run":
-                    return runJob(pw);
-                case "timeout":
-                    return timeout(pw);
-                case "cancel":
-                    return cancelJob(pw);
-                case "monitor-battery":
-                    return monitorBattery(pw);
-                case "get-battery-seq":
-                    return getBatterySeq(pw);
-                case "get-battery-charging":
-                    return getBatteryCharging(pw);
-                case "get-battery-not-low":
-                    return getBatteryNotLow(pw);
-                case "get-storage-seq":
-                    return getStorageSeq(pw);
-                case "get-storage-not-low":
-                    return getStorageNotLow(pw);
-                case "get-job-state":
-                    return getJobState(pw);
-                case "heartbeat":
-                    return doHeartbeat(pw);
-                case "trigger-dock-state":
-                    return triggerDockState(pw);
-                default:
-                    return handleDefaultCommands(cmd);
-            }
-        } catch (Exception e) {
-            pw.println("Exception: " + e);
-        }
-        return -1;
-    }
-
-    private void checkPermission(String operation) throws Exception {
-        final int uid = Binder.getCallingUid();
-        if (uid == 0) {
-            // Root can do anything.
-            return;
-        }
-        final int perm = mPM.checkUidPermission(
-                "android.permission.CHANGE_APP_IDLE_STATE", uid);
-        if (perm != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Uid " + uid
-                    + " not permitted to " + operation);
-        }
-    }
-
-    private boolean printError(int errCode, String pkgName, int userId, int jobId) {
-        PrintWriter pw;
-        switch (errCode) {
-            case CMD_ERR_NO_PACKAGE:
-                pw = getErrPrintWriter();
-                pw.print("Package not found: ");
-                pw.print(pkgName);
-                pw.print(" / user ");
-                pw.println(userId);
-                return true;
-
-            case CMD_ERR_NO_JOB:
-                pw = getErrPrintWriter();
-                pw.print("Could not find job ");
-                pw.print(jobId);
-                pw.print(" in package ");
-                pw.print(pkgName);
-                pw.print(" / user ");
-                pw.println(userId);
-                return true;
-
-            case CMD_ERR_CONSTRAINTS:
-                pw = getErrPrintWriter();
-                pw.print("Job ");
-                pw.print(jobId);
-                pw.print(" in package ");
-                pw.print(pkgName);
-                pw.print(" / user ");
-                pw.print(userId);
-                pw.println(" has functional constraints but --force not specified");
-                return true;
-
-            default:
-                return false;
-        }
-    }
-
-    private int runJob(PrintWriter pw) throws Exception {
-        checkPermission("force scheduled jobs");
-
-        boolean force = false;
-        int userId = UserHandle.USER_SYSTEM;
-
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            switch (opt) {
-                case "-f":
-                case "--force":
-                    force = true;
-                    break;
-
-                case "-u":
-                case "--user":
-                    userId = Integer.parseInt(getNextArgRequired());
-                    break;
-
-                default:
-                    pw.println("Error: unknown option '" + opt + "'");
-                    return -1;
-            }
-        }
-
-        final String pkgName = getNextArgRequired();
-        final int jobId = Integer.parseInt(getNextArgRequired());
-
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            int ret = mInternal.executeRunCommand(pkgName, userId, jobId, force);
-            if (printError(ret, pkgName, userId, jobId)) {
-                return ret;
-            }
-
-            // success!
-            pw.print("Running job");
-            if (force) {
-                pw.print(" [FORCED]");
-            }
-            pw.println();
-
-            return ret;
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    private int timeout(PrintWriter pw) throws Exception {
-        checkPermission("force timeout jobs");
-
-        int userId = UserHandle.USER_ALL;
-
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            switch (opt) {
-                case "-u":
-                case "--user":
-                    userId = UserHandle.parseUserArg(getNextArgRequired());
-                    break;
-
-                default:
-                    pw.println("Error: unknown option '" + opt + "'");
-                    return -1;
-            }
-        }
-
-        if (userId == UserHandle.USER_CURRENT) {
-            userId = ActivityManager.getCurrentUser();
-        }
-
-        final String pkgName = getNextArg();
-        final String jobIdStr = getNextArg();
-        final int jobId = jobIdStr != null ? Integer.parseInt(jobIdStr) : -1;
-
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            return mInternal.executeTimeoutCommand(pw, pkgName, userId, jobIdStr != null, jobId);
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    private int cancelJob(PrintWriter pw) throws Exception {
-        checkPermission("cancel jobs");
-
-        int userId = UserHandle.USER_SYSTEM;
-
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            switch (opt) {
-                case "-u":
-                case "--user":
-                    userId = UserHandle.parseUserArg(getNextArgRequired());
-                    break;
-
-                default:
-                    pw.println("Error: unknown option '" + opt + "'");
-                    return -1;
-            }
-        }
-
-        if (userId < 0) {
-            pw.println("Error: must specify a concrete user ID");
-            return -1;
-        }
-
-        final String pkgName = getNextArg();
-        final String jobIdStr = getNextArg();
-        final int jobId = jobIdStr != null ? Integer.parseInt(jobIdStr) : -1;
-
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            return mInternal.executeCancelCommand(pw, pkgName, userId, jobIdStr != null, jobId);
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    private int monitorBattery(PrintWriter pw) throws Exception {
-        checkPermission("change battery monitoring");
-        String opt = getNextArgRequired();
-        boolean enabled;
-        if ("on".equals(opt)) {
-            enabled = true;
-        } else if ("off".equals(opt)) {
-            enabled = false;
-        } else {
-            getErrPrintWriter().println("Error: unknown option " + opt);
-            return 1;
-        }
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            mInternal.setMonitorBattery(enabled);
-            if (enabled) pw.println("Battery monitoring enabled");
-            else pw.println("Battery monitoring disabled");
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-        return 0;
-    }
-
-    private int getBatterySeq(PrintWriter pw) {
-        int seq = mInternal.getBatterySeq();
-        pw.println(seq);
-        return 0;
-    }
-
-    private int getBatteryCharging(PrintWriter pw) {
-        boolean val = mInternal.getBatteryCharging();
-        pw.println(val);
-        return 0;
-    }
-
-    private int getBatteryNotLow(PrintWriter pw) {
-        boolean val = mInternal.getBatteryNotLow();
-        pw.println(val);
-        return 0;
-    }
-
-    private int getStorageSeq(PrintWriter pw) {
-        int seq = mInternal.getStorageSeq();
-        pw.println(seq);
-        return 0;
-    }
-
-    private int getStorageNotLow(PrintWriter pw) {
-        boolean val = mInternal.getStorageNotLow();
-        pw.println(val);
-        return 0;
-    }
-
-    private int getJobState(PrintWriter pw) throws Exception {
-        checkPermission("force timeout jobs");
-
-        int userId = UserHandle.USER_SYSTEM;
-
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            switch (opt) {
-                case "-u":
-                case "--user":
-                    userId = UserHandle.parseUserArg(getNextArgRequired());
-                    break;
-
-                default:
-                    pw.println("Error: unknown option '" + opt + "'");
-                    return -1;
-            }
-        }
-
-        if (userId == UserHandle.USER_CURRENT) {
-            userId = ActivityManager.getCurrentUser();
-        }
-
-        final String pkgName = getNextArgRequired();
-        final String jobIdStr = getNextArgRequired();
-        final int jobId = Integer.parseInt(jobIdStr);
-
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            int ret = mInternal.getJobState(pw, pkgName, userId, jobId);
-            printError(ret, pkgName, userId, jobId);
-            return ret;
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    private int doHeartbeat(PrintWriter pw) throws Exception {
-        checkPermission("manipulate scheduler heartbeat");
-
-        final String arg = getNextArg();
-        final int numBeats = (arg != null) ? Integer.parseInt(arg) : 0;
-
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            return mInternal.executeHeartbeatCommand(pw, numBeats);
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    private int triggerDockState(PrintWriter pw) throws Exception {
-        checkPermission("trigger wireless charging dock state");
-
-        final String opt = getNextArgRequired();
-        boolean idleState;
-        if ("idle".equals(opt)) {
-            idleState = true;
-        } else if ("active".equals(opt)) {
-            idleState = false;
-        } else {
-            getErrPrintWriter().println("Error: unknown option " + opt);
-            return 1;
-        }
-
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            mInternal.triggerDockState(idleState);
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-        return 0;
-    }
-
-    @Override
-    public void onHelp() {
-        final PrintWriter pw = getOutPrintWriter();
-
-        pw.println("Job scheduler (jobscheduler) commands:");
-        pw.println("  help");
-        pw.println("    Print this help text.");
-        pw.println("  run [-f | --force] [-u | --user USER_ID] PACKAGE JOB_ID");
-        pw.println("    Trigger immediate execution of a specific scheduled job.");
-        pw.println("    Options:");
-        pw.println("      -f or --force: run the job even if technical constraints such as");
-        pw.println("         connectivity are not currently met");
-        pw.println("      -u or --user: specify which user's job is to be run; the default is");
-        pw.println("         the primary or system user");
-        pw.println("  timeout [-u | --user USER_ID] [PACKAGE] [JOB_ID]");
-        pw.println("    Trigger immediate timeout of currently executing jobs, as if their.");
-        pw.println("    execution timeout had expired.");
-        pw.println("    Options:");
-        pw.println("      -u or --user: specify which user's job is to be run; the default is");
-        pw.println("         all users");
-        pw.println("  cancel [-u | --user USER_ID] PACKAGE [JOB_ID]");
-        pw.println("    Cancel a scheduled job.  If a job ID is not supplied, all jobs scheduled");
-        pw.println("    by that package will be canceled.  USE WITH CAUTION.");
-        pw.println("    Options:");
-        pw.println("      -u or --user: specify which user's job is to be run; the default is");
-        pw.println("         the primary or system user");
-        pw.println("  heartbeat [num]");
-        pw.println("    With no argument, prints the current standby heartbeat.  With a positive");
-        pw.println("    argument, advances the standby heartbeat by that number.");
-        pw.println("  monitor-battery [on|off]");
-        pw.println("    Control monitoring of all battery changes.  Off by default.  Turning");
-        pw.println("    on makes get-battery-seq useful.");
-        pw.println("  get-battery-seq");
-        pw.println("    Return the last battery update sequence number that was received.");
-        pw.println("  get-battery-charging");
-        pw.println("    Return whether the battery is currently considered to be charging.");
-        pw.println("  get-battery-not-low");
-        pw.println("    Return whether the battery is currently considered to not be low.");
-        pw.println("  get-storage-seq");
-        pw.println("    Return the last storage update sequence number that was received.");
-        pw.println("  get-storage-not-low");
-        pw.println("    Return whether storage is currently considered to not be low.");
-        pw.println("  get-job-state [-u | --user USER_ID] PACKAGE JOB_ID");
-        pw.println("    Return the current state of a job, may be any combination of:");
-        pw.println("      pending: currently on the pending list, waiting to be active");
-        pw.println("      active: job is actively running");
-        pw.println("      user-stopped: job can't run because its user is stopped");
-        pw.println("      backing-up: job can't run because app is currently backing up its data");
-        pw.println("      no-component: job can't run because its component is not available");
-        pw.println("      ready: job is ready to run (all constraints satisfied or bypassed)");
-        pw.println("      waiting: if nothing else above is printed, job not ready to run");
-        pw.println("    Options:");
-        pw.println("      -u or --user: specify which user's job is to be run; the default is");
-        pw.println("         the primary or system user");
-        pw.println("  trigger-dock-state [idle|active]");
-        pw.println("    Trigger wireless charging dock state.  Active by default.");
-        pw.println();
-    }
-
-}
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
deleted file mode 100644
index 7da128f..0000000
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ /dev/null
@@ -1,856 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job;
-
-import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
-
-import android.app.ActivityManager;
-import android.app.job.IJobCallback;
-import android.app.job.IJobService;
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
-import android.app.job.JobWorkItem;
-import android.app.usage.UsageStatsManagerInternal;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.WorkSource;
-import android.util.EventLog;
-import android.util.Slog;
-import android.util.TimeUtils;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.IBatteryStats;
-import com.android.server.EventLogTags;
-import com.android.server.LocalServices;
-import com.android.server.job.controllers.JobStatus;
-
-/**
- * Handles client binding and lifecycle of a job. Jobs execute one at a time on an instance of this
- * class.
- *
- * There are two important interactions into this class from the
- * {@link com.android.server.job.JobSchedulerService}. To execute a job and to cancel a job.
- * - Execution of a new job is handled by the {@link #mAvailable}. This bit is flipped once when a
- * job lands, and again when it is complete.
- * - Cancelling is trickier, because there are also interactions from the client. It's possible
- * the {@link com.android.server.job.JobServiceContext.JobServiceHandler} tries to process a
- * {@link #doCancelLocked} after the client has already finished. This is handled by having
- * {@link com.android.server.job.JobServiceContext.JobServiceHandler#handleCancelLocked} check whether
- * the context is still valid.
- * To mitigate this, we avoid sending duplicate onStopJob()
- * calls to the client after they've specified jobFinished().
- */
-public final class JobServiceContext implements ServiceConnection {
-    private static final boolean DEBUG = JobSchedulerService.DEBUG;
-    private static final boolean DEBUG_STANDBY = JobSchedulerService.DEBUG_STANDBY;
-
-    private static final String TAG = "JobServiceContext";
-    /** Amount of time a job is allowed to execute for before being considered timed-out. */
-    public static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000;  // 10mins.
-    /** Amount of time the JobScheduler waits for the initial service launch+bind. */
-    private static final long OP_BIND_TIMEOUT_MILLIS = 18 * 1000;
-    /** Amount of time the JobScheduler will wait for a response from an app for a message. */
-    private static final long OP_TIMEOUT_MILLIS = 8 * 1000;
-
-    private static final String[] VERB_STRINGS = {
-            "VERB_BINDING", "VERB_STARTING", "VERB_EXECUTING", "VERB_STOPPING", "VERB_FINISHED"
-    };
-
-    // States that a job occupies while interacting with the client.
-    static final int VERB_BINDING = 0;
-    static final int VERB_STARTING = 1;
-    static final int VERB_EXECUTING = 2;
-    static final int VERB_STOPPING = 3;
-    static final int VERB_FINISHED = 4;
-
-    // Messages that result from interactions with the client service.
-    /** System timed out waiting for a response. */
-    private static final int MSG_TIMEOUT = 0;
-
-    public static final int NO_PREFERRED_UID = -1;
-
-    private final Handler mCallbackHandler;
-    /** Make callbacks to {@link JobSchedulerService} to inform on job completion status. */
-    private final JobCompletedListener mCompletedListener;
-    /** Used for service binding, etc. */
-    private final Context mContext;
-    private final Object mLock;
-    private final IBatteryStats mBatteryStats;
-    private final JobPackageTracker mJobPackageTracker;
-    private PowerManager.WakeLock mWakeLock;
-
-    // Execution state.
-    private JobParameters mParams;
-    @VisibleForTesting
-    int mVerb;
-    private boolean mCancelled;
-
-    /**
-     * All the information maintained about the job currently being executed.
-     *
-     * Any reads (dereferences) not done from the handler thread must be synchronized on
-     * {@link #mLock}.
-     * Writes can only be done from the handler thread, or {@link #executeRunnableJob(JobStatus)}.
-     */
-    private JobStatus mRunningJob;
-    private JobCallback mRunningCallback;
-    /** Used to store next job to run when current job is to be preempted. */
-    private int mPreferredUid;
-    IJobService service;
-
-    /**
-     * Whether this context is free. This is set to false at the start of execution, and reset to
-     * true when execution is complete.
-     */
-    @GuardedBy("mLock")
-    private boolean mAvailable;
-    /** Track start time. */
-    private long mExecutionStartTimeElapsed;
-    /** Track when job will timeout. */
-    private long mTimeoutElapsed;
-
-    // Debugging: reason this job was last stopped.
-    public String mStoppedReason;
-
-    // Debugging: time this job was last stopped.
-    public long mStoppedTime;
-
-    final class JobCallback extends IJobCallback.Stub {
-        public String mStoppedReason;
-        public long mStoppedTime;
-
-        @Override
-        public void acknowledgeStartMessage(int jobId, boolean ongoing) {
-            doAcknowledgeStartMessage(this, jobId, ongoing);
-        }
-
-        @Override
-        public void acknowledgeStopMessage(int jobId, boolean reschedule) {
-            doAcknowledgeStopMessage(this, jobId, reschedule);
-        }
-
-        @Override
-        public JobWorkItem dequeueWork(int jobId) {
-            return doDequeueWork(this, jobId);
-        }
-
-        @Override
-        public boolean completeWork(int jobId, int workId) {
-            return doCompleteWork(this, jobId, workId);
-        }
-
-        @Override
-        public void jobFinished(int jobId, boolean reschedule) {
-            doJobFinished(this, jobId, reschedule);
-        }
-    }
-
-    JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats,
-            JobPackageTracker tracker, Looper looper) {
-        this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper);
-    }
-
-    @VisibleForTesting
-    JobServiceContext(Context context, Object lock, IBatteryStats batteryStats,
-            JobPackageTracker tracker, JobCompletedListener completedListener, Looper looper) {
-        mContext = context;
-        mLock = lock;
-        mBatteryStats = batteryStats;
-        mJobPackageTracker = tracker;
-        mCallbackHandler = new JobServiceHandler(looper);
-        mCompletedListener = completedListener;
-        mAvailable = true;
-        mVerb = VERB_FINISHED;
-        mPreferredUid = NO_PREFERRED_UID;
-    }
-
-    /**
-     * 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.
-     * @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.
-     */
-    boolean executeRunnableJob(JobStatus job) {
-        synchronized (mLock) {
-            if (!mAvailable) {
-                Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
-                return false;
-            }
-
-            mPreferredUid = NO_PREFERRED_UID;
-
-            mRunningJob = job;
-            mRunningCallback = new JobCallback();
-            final boolean isDeadlineExpired =
-                    job.hasDeadlineConstraint() &&
-                            (job.getLatestRunTimeElapsed() < sElapsedRealtimeClock.millis());
-            Uri[] triggeredUris = null;
-            if (job.changedUris != null) {
-                triggeredUris = new Uri[job.changedUris.size()];
-                job.changedUris.toArray(triggeredUris);
-            }
-            String[] triggeredAuthorities = null;
-            if (job.changedAuthorities != null) {
-                triggeredAuthorities = new String[job.changedAuthorities.size()];
-                job.changedAuthorities.toArray(triggeredAuthorities);
-            }
-            final JobInfo ji = job.getJob();
-            mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(),
-                    ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
-                    isDeadlineExpired, triggeredUris, triggeredAuthorities, job.network);
-            mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis();
-
-            final long whenDeferred = job.getWhenStandbyDeferred();
-            if (whenDeferred > 0) {
-                final long deferral = mExecutionStartTimeElapsed - whenDeferred;
-                EventLog.writeEvent(EventLogTags.JOB_DEFERRED_EXECUTION, deferral);
-                if (DEBUG_STANDBY) {
-                    StringBuilder sb = new StringBuilder(128);
-                    sb.append("Starting job deferred for standby by ");
-                    TimeUtils.formatDuration(deferral, sb);
-                    sb.append(" ms : ");
-                    sb.append(job.toShortString());
-                    Slog.v(TAG, sb.toString());
-                }
-            }
-
-            // Once we'e begun executing a job, we by definition no longer care whether
-            // it was inflated from disk with not-yet-coherent delay/deadline bounds.
-            job.clearPersistedUtcTimes();
-
-            mVerb = VERB_BINDING;
-            scheduleOpTimeOutLocked();
-            final Intent intent = new Intent().setComponent(job.getServiceComponent());
-            boolean binding = false;
-            try {
-                binding = mContext.bindServiceAsUser(intent, this,
-                        Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
-                        | Context.BIND_NOT_PERCEPTIBLE,
-                        new UserHandle(job.getUserId()));
-            } catch (SecurityException e) {
-                // Some permission policy, for example INTERACT_ACROSS_USERS and
-                // android:singleUser, can result in a SecurityException being thrown from
-                // bindServiceAsUser().  If this happens, catch it and fail gracefully.
-                Slog.w(TAG, "Job service " + job.getServiceComponent().getShortClassName()
-                        + " cannot be executed: " + e.getMessage());
-                binding = false;
-            }
-            if (!binding) {
-                if (DEBUG) {
-                    Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
-                }
-                mRunningJob = null;
-                mRunningCallback = null;
-                mParams = null;
-                mExecutionStartTimeElapsed = 0L;
-                mVerb = VERB_FINISHED;
-                removeOpTimeOutLocked();
-                return false;
-            }
-            mJobPackageTracker.noteActive(job);
-            try {
-                mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid(),
-                        job.getStandbyBucket(), job.getJobId());
-            } catch (RemoteException e) {
-                // Whatever.
-            }
-            final String jobPackage = job.getSourcePackageName();
-            final int jobUserId = job.getSourceUserId();
-            UsageStatsManagerInternal usageStats =
-                    LocalServices.getService(UsageStatsManagerInternal.class);
-            usageStats.setLastJobRunTime(jobPackage, jobUserId, mExecutionStartTimeElapsed);
-            JobSchedulerInternal jobScheduler =
-                    LocalServices.getService(JobSchedulerInternal.class);
-            jobScheduler.noteJobStart(jobPackage, jobUserId);
-            mAvailable = false;
-            mStoppedReason = null;
-            mStoppedTime = 0;
-            return true;
-        }
-    }
-
-    /**
-     * Used externally to query the running job. Will return null if there is no job running.
-     */
-    JobStatus getRunningJobLocked() {
-        return mRunningJob;
-    }
-
-    /**
-     * Used only for debugging. Will return <code>"&lt;null&gt;"</code> if there is no job running.
-     */
-    private String getRunningJobNameLocked() {
-        return mRunningJob != null ? mRunningJob.toShortString() : "<null>";
-    }
-
-    /** Called externally when a job that was scheduled for execution should be cancelled. */
-    @GuardedBy("mLock")
-    void cancelExecutingJobLocked(int reason, String debugReason) {
-        doCancelLocked(reason, debugReason);
-    }
-
-    @GuardedBy("mLock")
-    void preemptExecutingJobLocked() {
-        doCancelLocked(JobParameters.REASON_PREEMPT, "cancelled due to preemption");
-    }
-
-    int getPreferredUid() {
-        return mPreferredUid;
-    }
-
-    void clearPreferredUid() {
-        mPreferredUid = NO_PREFERRED_UID;
-    }
-
-    long getExecutionStartTimeElapsed() {
-        return mExecutionStartTimeElapsed;
-    }
-
-    long getTimeoutElapsed() {
-        return mTimeoutElapsed;
-    }
-
-    @GuardedBy("mLock")
-    boolean timeoutIfExecutingLocked(String pkgName, int userId, boolean matchJobId, int jobId,
-            String reason) {
-        final JobStatus executing = getRunningJobLocked();
-        if (executing != null && (userId == UserHandle.USER_ALL || userId == executing.getUserId())
-                && (pkgName == null || pkgName.equals(executing.getSourcePackageName()))
-                && (!matchJobId || jobId == executing.getJobId())) {
-            if (mVerb == VERB_EXECUTING) {
-                mParams.setStopReason(JobParameters.REASON_TIMEOUT, reason);
-                sendStopMessageLocked("force timeout from shell");
-                return true;
-            }
-        }
-        return false;
-    }
-
-    void doJobFinished(JobCallback cb, int jobId, boolean reschedule) {
-        doCallback(cb, reschedule, "app called jobFinished");
-    }
-
-    void doAcknowledgeStopMessage(JobCallback cb, int jobId, boolean reschedule) {
-        doCallback(cb, reschedule, null);
-    }
-
-    void doAcknowledgeStartMessage(JobCallback cb, int jobId, boolean ongoing) {
-        doCallback(cb, ongoing, "finished start");
-    }
-
-    JobWorkItem doDequeueWork(JobCallback cb, int jobId) {
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            synchronized (mLock) {
-                assertCallerLocked(cb);
-                if (mVerb == VERB_STOPPING || mVerb == VERB_FINISHED) {
-                    // This job is either all done, or on its way out.  Either way, it
-                    // should not dispatch any more work.  We will pick up any remaining
-                    // work the next time we start the job again.
-                    return null;
-                }
-                final JobWorkItem work = mRunningJob.dequeueWorkLocked();
-                if (work == null && !mRunningJob.hasExecutingWorkLocked()) {
-                    // This will finish the job.
-                    doCallbackLocked(false, "last work dequeued");
-                }
-                return work;
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    boolean doCompleteWork(JobCallback cb, int jobId, int workId) {
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            synchronized (mLock) {
-                assertCallerLocked(cb);
-                return mRunningJob.completeWorkLocked(ActivityManager.getService(), workId);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    /**
-     * We acquire/release a wakelock on onServiceConnected/unbindService. This mirrors the work
-     * we intend to send to the client - we stop sending work when the service is unbound so until
-     * then we keep the wakelock.
-     * @param name The concrete component name of the service that has been connected.
-     * @param service The IBinder of the Service's communication channel,
-     */
-    @Override
-    public void onServiceConnected(ComponentName name, IBinder service) {
-        JobStatus runningJob;
-        synchronized (mLock) {
-            // This isn't strictly necessary b/c the JobServiceHandler is running on the main
-            // looper and at this point we can't get any binder callbacks from the client. Better
-            // safe than sorry.
-            runningJob = mRunningJob;
-
-            if (runningJob == null || !name.equals(runningJob.getServiceComponent())) {
-                closeAndCleanupJobLocked(true /* needsReschedule */,
-                        "connected for different component");
-                return;
-            }
-            this.service = IJobService.Stub.asInterface(service);
-            final PowerManager pm =
-                    (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-            PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
-                    runningJob.getTag());
-            wl.setWorkSource(deriveWorkSource(runningJob));
-            wl.setReferenceCounted(false);
-            wl.acquire();
-
-            // We use a new wakelock instance per job.  In rare cases there is a race between
-            // teardown following job completion/cancellation and new job service spin-up
-            // such that if we simply assign mWakeLock to be the new instance, we orphan
-            // the currently-live lock instead of cleanly replacing it.  Watch for this and
-            // explicitly fast-forward the release if we're in that situation.
-            if (mWakeLock != null) {
-                Slog.w(TAG, "Bound new job " + runningJob + " but live wakelock " + mWakeLock
-                        + " tag=" + mWakeLock.getTag());
-                mWakeLock.release();
-            }
-            mWakeLock = wl;
-            doServiceBoundLocked();
-        }
-    }
-
-    private WorkSource deriveWorkSource(JobStatus runningJob) {
-        final int jobUid = runningJob.getSourceUid();
-        if (WorkSource.isChainedBatteryAttributionEnabled(mContext)) {
-            WorkSource workSource = new WorkSource();
-            workSource.createWorkChain()
-                    .addNode(jobUid, null)
-                    .addNode(android.os.Process.SYSTEM_UID, "JobScheduler");
-            return workSource;
-        } else {
-            return new WorkSource(jobUid);
-        }
-    }
-
-    /** If the client service crashes we reschedule this job and clean up. */
-    @Override
-    public void onServiceDisconnected(ComponentName name) {
-        synchronized (mLock) {
-            closeAndCleanupJobLocked(true /* needsReschedule */, "unexpectedly disconnected");
-        }
-    }
-
-    /**
-     * This class is reused across different clients, and passes itself in as a callback. Check
-     * whether the client exercising the callback is the client we expect.
-     * @return True if the binder calling is coming from the client we expect.
-     */
-    private boolean verifyCallerLocked(JobCallback cb) {
-        if (mRunningCallback != cb) {
-            if (DEBUG) {
-                Slog.d(TAG, "Stale callback received, ignoring.");
-            }
-            return false;
-        }
-        return true;
-    }
-
-    private void assertCallerLocked(JobCallback cb) {
-        if (!verifyCallerLocked(cb)) {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append("Caller no longer running");
-            if (cb.mStoppedReason != null) {
-                sb.append(", last stopped ");
-                TimeUtils.formatDuration(sElapsedRealtimeClock.millis() - cb.mStoppedTime, sb);
-                sb.append(" because: ");
-                sb.append(cb.mStoppedReason);
-            }
-            throw new SecurityException(sb.toString());
-        }
-    }
-
-    /**
-     * Scheduling of async messages (basically timeouts at this point).
-     */
-    private class JobServiceHandler extends Handler {
-        JobServiceHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message message) {
-            switch (message.what) {
-                case MSG_TIMEOUT:
-                    synchronized (mLock) {
-                        if (message.obj == mRunningCallback) {
-                            handleOpTimeoutLocked();
-                        } else {
-                            JobCallback jc = (JobCallback)message.obj;
-                            StringBuilder sb = new StringBuilder(128);
-                            sb.append("Ignoring timeout of no longer active job");
-                            if (jc.mStoppedReason != null) {
-                                sb.append(", stopped ");
-                                TimeUtils.formatDuration(sElapsedRealtimeClock.millis()
-                                        - jc.mStoppedTime, sb);
-                                sb.append(" because: ");
-                                sb.append(jc.mStoppedReason);
-                            }
-                            Slog.w(TAG, sb.toString());
-                        }
-                    }
-                    break;
-                default:
-                    Slog.e(TAG, "Unrecognised message: " + message);
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    void doServiceBoundLocked() {
-        removeOpTimeOutLocked();
-        handleServiceBoundLocked();
-    }
-
-    void doCallback(JobCallback cb, boolean reschedule, String reason) {
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            synchronized (mLock) {
-                if (!verifyCallerLocked(cb)) {
-                    return;
-                }
-                doCallbackLocked(reschedule, reason);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    @GuardedBy("mLock")
-    void doCallbackLocked(boolean reschedule, String reason) {
-        if (DEBUG) {
-            Slog.d(TAG, "doCallback of : " + mRunningJob
-                    + " v:" + VERB_STRINGS[mVerb]);
-        }
-        removeOpTimeOutLocked();
-
-        if (mVerb == VERB_STARTING) {
-            handleStartedLocked(reschedule);
-        } else if (mVerb == VERB_EXECUTING ||
-                mVerb == VERB_STOPPING) {
-            handleFinishedLocked(reschedule, reason);
-        } else {
-            if (DEBUG) {
-                Slog.d(TAG, "Unrecognised callback: " + mRunningJob);
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    void doCancelLocked(int arg1, String debugReason) {
-        if (mVerb == VERB_FINISHED) {
-            if (DEBUG) {
-                Slog.d(TAG,
-                        "Trying to process cancel for torn-down context, ignoring.");
-            }
-            return;
-        }
-        mParams.setStopReason(arg1, debugReason);
-        if (arg1 == JobParameters.REASON_PREEMPT) {
-            mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
-                    NO_PREFERRED_UID;
-        }
-        handleCancelLocked(debugReason);
-    }
-
-    /** Start the job on the service. */
-    @GuardedBy("mLock")
-    private void handleServiceBoundLocked() {
-        if (DEBUG) {
-            Slog.d(TAG, "handleServiceBound for " + getRunningJobNameLocked());
-        }
-        if (mVerb != VERB_BINDING) {
-            Slog.e(TAG, "Sending onStartJob for a job that isn't pending. "
-                    + VERB_STRINGS[mVerb]);
-            closeAndCleanupJobLocked(false /* reschedule */, "started job not pending");
-            return;
-        }
-        if (mCancelled) {
-            if (DEBUG) {
-                Slog.d(TAG, "Job cancelled while waiting for bind to complete. "
-                        + mRunningJob);
-            }
-            closeAndCleanupJobLocked(true /* reschedule */, "cancelled while waiting for bind");
-            return;
-        }
-        try {
-            mVerb = VERB_STARTING;
-            scheduleOpTimeOutLocked();
-            service.startJob(mParams);
-        } catch (Exception e) {
-            // We catch 'Exception' because client-app malice or bugs might induce a wide
-            // range of possible exception-throw outcomes from startJob() and its handling
-            // of the client's ParcelableBundle extras.
-            Slog.e(TAG, "Error sending onStart message to '" +
-                    mRunningJob.getServiceComponent().getShortClassName() + "' ", e);
-        }
-    }
-
-    /**
-     * State behaviours.
-     * VERB_STARTING   -> Successful start, change job to VERB_EXECUTING and post timeout.
-     *     _PENDING    -> Error
-     *     _EXECUTING  -> Error
-     *     _STOPPING   -> Error
-     */
-    @GuardedBy("mLock")
-    private void handleStartedLocked(boolean workOngoing) {
-        switch (mVerb) {
-            case VERB_STARTING:
-                mVerb = VERB_EXECUTING;
-                if (!workOngoing) {
-                    // Job is finished already so fast-forward to handleFinished.
-                    handleFinishedLocked(false, "onStartJob returned false");
-                    return;
-                }
-                if (mCancelled) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Job cancelled while waiting for onStartJob to complete.");
-                    }
-                    // Cancelled *while* waiting for acknowledgeStartMessage from client.
-                    handleCancelLocked(null);
-                    return;
-                }
-                scheduleOpTimeOutLocked();
-                break;
-            default:
-                Slog.e(TAG, "Handling started job but job wasn't starting! Was "
-                        + VERB_STRINGS[mVerb] + ".");
-                return;
-        }
-    }
-
-    /**
-     * VERB_EXECUTING  -> Client called jobFinished(), clean up and notify done.
-     *     _STOPPING   -> Successful finish, clean up and notify done.
-     *     _STARTING   -> Error
-     *     _PENDING    -> Error
-     */
-    @GuardedBy("mLock")
-    private void handleFinishedLocked(boolean reschedule, String reason) {
-        switch (mVerb) {
-            case VERB_EXECUTING:
-            case VERB_STOPPING:
-                closeAndCleanupJobLocked(reschedule, reason);
-                break;
-            default:
-                Slog.e(TAG, "Got an execution complete message for a job that wasn't being" +
-                        "executed. Was " + VERB_STRINGS[mVerb] + ".");
-        }
-    }
-
-    /**
-     * A job can be in various states when a cancel request comes in:
-     * VERB_BINDING    -> Cancelled before bind completed. Mark as cancelled and wait for
-     *                    {@link #onServiceConnected(android.content.ComponentName, android.os.IBinder)}
-     *     _STARTING   -> Mark as cancelled and wait for
-     *                    {@link JobServiceContext#doAcknowledgeStartMessage}
-     *     _EXECUTING  -> call {@link #sendStopMessageLocked}}, but only if there are no callbacks
-     *                      in the message queue.
-     *     _ENDING     -> No point in doing anything here, so we ignore.
-     */
-    @GuardedBy("mLock")
-    private void handleCancelLocked(String reason) {
-        if (JobSchedulerService.DEBUG) {
-            Slog.d(TAG, "Handling cancel for: " + mRunningJob.getJobId() + " "
-                    + VERB_STRINGS[mVerb]);
-        }
-        switch (mVerb) {
-            case VERB_BINDING:
-            case VERB_STARTING:
-                mCancelled = true;
-                applyStoppedReasonLocked(reason);
-                break;
-            case VERB_EXECUTING:
-                sendStopMessageLocked(reason);
-                break;
-            case VERB_STOPPING:
-                // Nada.
-                break;
-            default:
-                Slog.e(TAG, "Cancelling a job without a valid verb: " + mVerb);
-                break;
-        }
-    }
-
-    /** Process MSG_TIMEOUT here. */
-    @GuardedBy("mLock")
-    private void handleOpTimeoutLocked() {
-        switch (mVerb) {
-            case VERB_BINDING:
-                Slog.w(TAG, "Time-out while trying to bind " + getRunningJobNameLocked()
-                        + ", dropping.");
-                closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while binding");
-                break;
-            case VERB_STARTING:
-                // Client unresponsive - wedged or failed to respond in time. We don't really
-                // know what happened so let's log it and notify the JobScheduler
-                // FINISHED/NO-RETRY.
-                Slog.w(TAG, "No response from client for onStartJob "
-                        + getRunningJobNameLocked());
-                closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while starting");
-                break;
-            case VERB_STOPPING:
-                // At least we got somewhere, so fail but ask the JobScheduler to reschedule.
-                Slog.w(TAG, "No response from client for onStopJob "
-                        + getRunningJobNameLocked());
-                closeAndCleanupJobLocked(true /* needsReschedule */, "timed out while stopping");
-                break;
-            case VERB_EXECUTING:
-                // Not an error - client ran out of time.
-                Slog.i(TAG, "Client timed out while executing (no jobFinished received), " +
-                        "sending onStop: " + getRunningJobNameLocked());
-                mParams.setStopReason(JobParameters.REASON_TIMEOUT, "client timed out");
-                sendStopMessageLocked("timeout while executing");
-                break;
-            default:
-                Slog.e(TAG, "Handling timeout for an invalid job state: "
-                        + getRunningJobNameLocked() + ", dropping.");
-                closeAndCleanupJobLocked(false /* needsReschedule */, "invalid timeout");
-        }
-    }
-
-    /**
-     * Already running, need to stop. Will switch {@link #mVerb} from VERB_EXECUTING ->
-     * VERB_STOPPING.
-     */
-    @GuardedBy("mLock")
-    private void sendStopMessageLocked(String reason) {
-        removeOpTimeOutLocked();
-        if (mVerb != VERB_EXECUTING) {
-            Slog.e(TAG, "Sending onStopJob for a job that isn't started. " + mRunningJob);
-            closeAndCleanupJobLocked(false /* reschedule */, reason);
-            return;
-        }
-        try {
-            applyStoppedReasonLocked(reason);
-            mVerb = VERB_STOPPING;
-            scheduleOpTimeOutLocked();
-            service.stopJob(mParams);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Error sending onStopJob to client.", e);
-            // The job's host app apparently crashed during the job, so we should reschedule.
-            closeAndCleanupJobLocked(true /* reschedule */, "host crashed when trying to stop");
-        }
-    }
-
-    /**
-     * The provided job has finished, either by calling
-     * {@link android.app.job.JobService#jobFinished(android.app.job.JobParameters, boolean)}
-     * or from acknowledging the stop message we sent. Either way, we're done tracking it and
-     * we want to clean up internally.
-     */
-    @GuardedBy("mLock")
-    private void closeAndCleanupJobLocked(boolean reschedule, String reason) {
-        final JobStatus completedJob;
-        if (mVerb == VERB_FINISHED) {
-            return;
-        }
-        applyStoppedReasonLocked(reason);
-        completedJob = mRunningJob;
-        mJobPackageTracker.noteInactive(completedJob, mParams.getStopReason(), reason);
-        try {
-            mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(),
-                    mRunningJob.getSourceUid(), mParams.getStopReason(),
-                    mRunningJob.getStandbyBucket(), mRunningJob.getJobId());
-        } catch (RemoteException e) {
-            // Whatever.
-        }
-        if (mWakeLock != null) {
-            mWakeLock.release();
-        }
-        mContext.unbindService(JobServiceContext.this);
-        mWakeLock = null;
-        mRunningJob = null;
-        mRunningCallback = null;
-        mParams = null;
-        mVerb = VERB_FINISHED;
-        mCancelled = false;
-        service = null;
-        mAvailable = true;
-        removeOpTimeOutLocked();
-        mCompletedListener.onJobCompletedLocked(completedJob, reschedule);
-    }
-
-    private void applyStoppedReasonLocked(String reason) {
-        if (reason != null && mStoppedReason == null) {
-            mStoppedReason = reason;
-            mStoppedTime = sElapsedRealtimeClock.millis();
-            if (mRunningCallback != null) {
-                mRunningCallback.mStoppedReason = mStoppedReason;
-                mRunningCallback.mStoppedTime = mStoppedTime;
-            }
-        }
-    }
-
-    /**
-     * Called when sending a message to the client, over whose execution we have no control. If
-     * we haven't received a response in a certain amount of time, we want to give up and carry
-     * on with life.
-     */
-    private void scheduleOpTimeOutLocked() {
-        removeOpTimeOutLocked();
-
-        final long timeoutMillis;
-        switch (mVerb) {
-            case VERB_EXECUTING:
-                timeoutMillis = EXECUTING_TIMESLICE_MILLIS;
-                break;
-
-            case VERB_BINDING:
-                timeoutMillis = OP_BIND_TIMEOUT_MILLIS;
-                break;
-
-            default:
-                timeoutMillis = OP_TIMEOUT_MILLIS;
-                break;
-        }
-        if (DEBUG) {
-            Slog.d(TAG, "Scheduling time out for '" +
-                    mRunningJob.getServiceComponent().getShortClassName() + "' jId: " +
-                    mParams.getJobId() + ", in " + (timeoutMillis / 1000) + " s");
-        }
-        Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT, mRunningCallback);
-        mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
-        mTimeoutElapsed = sElapsedRealtimeClock.millis() + timeoutMillis;
-    }
-
-
-    private void removeOpTimeOutLocked() {
-        mCallbackHandler.removeMessages(MSG_TIMEOUT);
-    }
-}
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
deleted file mode 100644
index d69faf3..0000000
--- a/services/core/java/com/android/server/job/JobStore.java
+++ /dev/null
@@ -1,1285 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job;
-
-import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
-import static com.android.server.job.JobSchedulerService.sSystemClock;
-
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.IActivityManager;
-import android.app.job.JobInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.net.NetworkRequest;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.PersistableBundle;
-import android.os.Process;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.text.format.DateUtils;
-import android.util.ArraySet;
-import android.util.AtomicFile;
-import android.util.Pair;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.Xml;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.BitUtils;
-import com.android.internal.util.FastXmlSerializer;
-import com.android.server.IoThread;
-import com.android.server.LocalServices;
-import com.android.server.job.JobSchedulerInternal.JobStorePersistStats;
-import com.android.server.job.controllers.JobStatus;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-/**
- * Maintains the master list of jobs that the job scheduler is tracking. These jobs are compared by
- * reference, so none of the functions in this class should make a copy.
- * Also handles read/write of persisted jobs.
- *
- * Note on locking:
- *      All callers to this class must <strong>lock on the class object they are calling</strong>.
- *      This is important b/c {@link com.android.server.job.JobStore.WriteJobsMapToDiskRunnable}
- *      and {@link com.android.server.job.JobStore.ReadJobMapFromDiskRunnable} lock on that
- *      object.
- *
- * Test:
- * atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
- */
-public final class JobStore {
-    private static final String TAG = "JobStore";
-    private static final boolean DEBUG = JobSchedulerService.DEBUG;
-
-    /** Threshold to adjust how often we want to write to the db. */
-    private static final long JOB_PERSIST_DELAY = 2000L;
-
-    final Object mLock;
-    final Object mWriteScheduleLock;    // used solely for invariants around write scheduling
-    final JobSet mJobSet; // per-caller-uid and per-source-uid tracking
-    final Context mContext;
-
-    // Bookkeeping around incorrect boot-time system clock
-    private final long mXmlTimestamp;
-    private boolean mRtcGood;
-
-    @GuardedBy("mWriteScheduleLock")
-    private boolean mWriteScheduled;
-
-    @GuardedBy("mWriteScheduleLock")
-    private boolean mWriteInProgress;
-
-    private static final Object sSingletonLock = new Object();
-    private final AtomicFile mJobsFile;
-    /** Handler backed by IoThread for writing to disk. */
-    private final Handler mIoHandler = IoThread.getHandler();
-    private static JobStore sSingleton;
-
-    private JobStorePersistStats mPersistInfo = new JobStorePersistStats();
-
-    /** Used by the {@link JobSchedulerService} to instantiate the JobStore. */
-    static JobStore initAndGet(JobSchedulerService jobManagerService) {
-        synchronized (sSingletonLock) {
-            if (sSingleton == null) {
-                sSingleton = new JobStore(jobManagerService.getContext(),
-                        jobManagerService.getLock(), Environment.getDataDirectory());
-            }
-            return sSingleton;
-        }
-    }
-
-    /**
-     * @return A freshly initialized job store object, with no loaded jobs.
-     */
-    @VisibleForTesting
-    public static JobStore initAndGetForTesting(Context context, File dataDir) {
-        JobStore jobStoreUnderTest = new JobStore(context, new Object(), dataDir);
-        jobStoreUnderTest.clear();
-        return jobStoreUnderTest;
-    }
-
-    /**
-     * Construct the instance of the job store. This results in a blocking read from disk.
-     */
-    private JobStore(Context context, Object lock, File dataDir) {
-        mLock = lock;
-        mWriteScheduleLock = new Object();
-        mContext = context;
-
-        File systemDir = new File(dataDir, "system");
-        File jobDir = new File(systemDir, "job");
-        jobDir.mkdirs();
-        mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"), "jobs");
-
-        mJobSet = new JobSet();
-
-        // If the current RTC is earlier than the timestamp on our persisted jobs file,
-        // we suspect that the RTC is uninitialized and so we cannot draw conclusions
-        // about persisted job scheduling.
-        //
-        // Note that if the persisted jobs file does not exist, we proceed with the
-        // assumption that the RTC is good.  This is less work and is safe: if the
-        // clock updates to sanity then we'll be saving the persisted jobs file in that
-        // correct state, which is normal; or we'll wind up writing the jobs file with
-        // an incorrect historical timestamp.  That's fine; at worst we'll reboot with
-        // a *correct* timestamp, see a bunch of overdue jobs, and run them; then
-        // settle into normal operation.
-        mXmlTimestamp = mJobsFile.getLastModifiedTime();
-        mRtcGood = (sSystemClock.millis() > mXmlTimestamp);
-
-        readJobMapFromDisk(mJobSet, mRtcGood);
-    }
-
-    public boolean jobTimesInflatedValid() {
-        return mRtcGood;
-    }
-
-    public boolean clockNowValidToInflate(long now) {
-        return now >= mXmlTimestamp;
-    }
-
-    /**
-     * Find all the jobs that were affected by RTC clock uncertainty at boot time.  Returns
-     * parallel lists of the existing JobStatus objects and of new, equivalent JobStatus instances
-     * with now-corrected time bounds.
-     */
-    public void getRtcCorrectedJobsLocked(final ArrayList<JobStatus> toAdd,
-            final ArrayList<JobStatus> toRemove) {
-        final long elapsedNow = sElapsedRealtimeClock.millis();
-        final IActivityManager am = ActivityManager.getService();
-
-        // Find the jobs that need to be fixed up, collecting them for post-iteration
-        // replacement with their new versions
-        forEachJob(job -> {
-            final Pair<Long, Long> utcTimes = job.getPersistedUtcTimes();
-            if (utcTimes != null) {
-                Pair<Long, Long> elapsedRuntimes =
-                        convertRtcBoundsToElapsed(utcTimes, elapsedNow);
-                JobStatus newJob = new JobStatus(job, job.getBaseHeartbeat(),
-                        elapsedRuntimes.first, elapsedRuntimes.second,
-                        0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime());
-                newJob.prepareLocked(am);
-                toAdd.add(newJob);
-                toRemove.add(job);
-            }
-        });
-    }
-
-    /**
-     * Add a job to the master list, persisting it if necessary. If the JobStatus already exists,
-     * it will be replaced.
-     * @param jobStatus Job to add.
-     * @return Whether or not an equivalent JobStatus was replaced by this operation.
-     */
-    public boolean add(JobStatus jobStatus) {
-        boolean replaced = mJobSet.remove(jobStatus);
-        mJobSet.add(jobStatus);
-        if (jobStatus.isPersisted()) {
-            maybeWriteStatusToDiskAsync();
-        }
-        if (DEBUG) {
-            Slog.d(TAG, "Added job status to store: " + jobStatus);
-        }
-        return replaced;
-    }
-
-    boolean containsJob(JobStatus jobStatus) {
-        return mJobSet.contains(jobStatus);
-    }
-
-    public int size() {
-        return mJobSet.size();
-    }
-
-    public JobStorePersistStats getPersistStats() {
-        return mPersistInfo;
-    }
-
-    public int countJobsForUid(int uid) {
-        return mJobSet.countJobsForUid(uid);
-    }
-
-    /**
-     * Remove the provided job. Will also delete the job if it was persisted.
-     * @param removeFromPersisted If true, the job will be removed from the persisted job list
-     *                            immediately (if it was persisted).
-     * @return Whether or not the job existed to be removed.
-     */
-    public boolean remove(JobStatus jobStatus, boolean removeFromPersisted) {
-        boolean removed = mJobSet.remove(jobStatus);
-        if (!removed) {
-            if (DEBUG) {
-                Slog.d(TAG, "Couldn't remove job: didn't exist: " + jobStatus);
-            }
-            return false;
-        }
-        if (removeFromPersisted && jobStatus.isPersisted()) {
-            maybeWriteStatusToDiskAsync();
-        }
-        return removed;
-    }
-
-    /**
-     * Remove the jobs of users not specified in the whitelist.
-     * @param whitelist Array of User IDs whose jobs are not to be removed.
-     */
-    public void removeJobsOfNonUsers(int[] whitelist) {
-        mJobSet.removeJobsOfNonUsers(whitelist);
-    }
-
-    @VisibleForTesting
-    public void clear() {
-        mJobSet.clear();
-        maybeWriteStatusToDiskAsync();
-    }
-
-    /**
-     * @param userHandle User for whom we are querying the list of jobs.
-     * @return A list of all the jobs scheduled for the provided user. Never null.
-     */
-    public List<JobStatus> getJobsByUser(int userHandle) {
-        return mJobSet.getJobsByUser(userHandle);
-    }
-
-    /**
-     * @param uid Uid of the requesting app.
-     * @return All JobStatus objects for a given uid from the master list. Never null.
-     */
-    public List<JobStatus> getJobsByUid(int uid) {
-        return mJobSet.getJobsByUid(uid);
-    }
-
-    /**
-     * @param uid Uid of the requesting app.
-     * @param jobId Job id, specified at schedule-time.
-     * @return the JobStatus that matches the provided uId and jobId, or null if none found.
-     */
-    public JobStatus getJobByUidAndJobId(int uid, int jobId) {
-        return mJobSet.get(uid, jobId);
-    }
-
-    /**
-     * Iterate over the set of all jobs, invoking the supplied functor on each.  This is for
-     * customers who need to examine each job; we'd much rather not have to generate
-     * transient unified collections for them to iterate over and then discard, or creating
-     * iterators every time a client needs to perform a sweep.
-     */
-    public void forEachJob(Consumer<JobStatus> functor) {
-        mJobSet.forEachJob(null, functor);
-    }
-
-    public void forEachJob(@Nullable Predicate<JobStatus> filterPredicate,
-            Consumer<JobStatus> functor) {
-        mJobSet.forEachJob(filterPredicate, functor);
-    }
-
-    public void forEachJob(int uid, Consumer<JobStatus> functor) {
-        mJobSet.forEachJob(uid, functor);
-    }
-
-    public void forEachJobForSourceUid(int sourceUid, Consumer<JobStatus> functor) {
-        mJobSet.forEachJobForSourceUid(sourceUid, functor);
-    }
-
-    /** Version of the db schema. */
-    private static final int JOBS_FILE_VERSION = 0;
-    /** Tag corresponds to constraints this job needs. */
-    private static final String XML_TAG_PARAMS_CONSTRAINTS = "constraints";
-    /** Tag corresponds to execution parameters. */
-    private static final String XML_TAG_PERIODIC = "periodic";
-    private static final String XML_TAG_ONEOFF = "one-off";
-    private static final String XML_TAG_EXTRAS = "extras";
-
-    /**
-     * Every time the state changes we write all the jobs in one swath, instead of trying to
-     * track incremental changes.
-     */
-    private void maybeWriteStatusToDiskAsync() {
-        synchronized (mWriteScheduleLock) {
-            if (!mWriteScheduled) {
-                if (DEBUG) {
-                    Slog.v(TAG, "Scheduling persist of jobs to disk.");
-                }
-                mIoHandler.postDelayed(mWriteRunnable, JOB_PERSIST_DELAY);
-                mWriteScheduled = mWriteInProgress = true;
-            }
-        }
-    }
-
-    @VisibleForTesting
-    public void readJobMapFromDisk(JobSet jobSet, boolean rtcGood) {
-        new ReadJobMapFromDiskRunnable(jobSet, rtcGood).run();
-    }
-
-    /** Write persisted JobStore state to disk synchronously. Should only be used for testing. */
-    @VisibleForTesting
-    public void writeStatusToDiskForTesting() {
-        synchronized (mWriteScheduleLock) {
-            if (mWriteScheduled) {
-                throw new IllegalStateException("An asynchronous write is already scheduled.");
-            }
-
-            mWriteScheduled = mWriteInProgress = true;
-            mWriteRunnable.run();
-        }
-    }
-
-    /**
-     * Wait for any pending write to the persistent store to clear
-     * @param maxWaitMillis Maximum time from present to wait
-     * @return {@code true} if I/O cleared as expected, {@code false} if the wait
-     *     timed out before the pending write completed.
-     */
-    @VisibleForTesting
-    public boolean waitForWriteToCompleteForTesting(long maxWaitMillis) {
-        final long start = SystemClock.uptimeMillis();
-        final long end = start + maxWaitMillis;
-        synchronized (mWriteScheduleLock) {
-            while (mWriteInProgress) {
-                final long now = SystemClock.uptimeMillis();
-                if (now >= end) {
-                    // still not done and we've hit the end; failure
-                    return false;
-                }
-                try {
-                    mWriteScheduleLock.wait(now - start + maxWaitMillis);
-                } catch (InterruptedException e) {
-                    // Spurious; keep waiting
-                    break;
-                }
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Runnable that writes {@link #mJobSet} out to xml.
-     * NOTE: This Runnable locks on mLock
-     */
-    private final Runnable mWriteRunnable = new Runnable() {
-        @Override
-        public void run() {
-            final long startElapsed = sElapsedRealtimeClock.millis();
-            final List<JobStatus> storeCopy = new ArrayList<JobStatus>();
-            // Intentionally allow new scheduling of a write operation *before* we clone
-            // the job set.  If we reset it to false after cloning, there's a window in
-            // which no new write will be scheduled but mLock is not held, i.e. a new
-            // job might appear and fail to be recognized as needing a persist.  The
-            // potential cost is one redundant write of an identical set of jobs in the
-            // rare case of that specific race, but by doing it this way we avoid quite
-            // a bit of lock contention.
-            synchronized (mWriteScheduleLock) {
-                mWriteScheduled = false;
-            }
-            synchronized (mLock) {
-                // Clone the jobs so we can release the lock before writing.
-                mJobSet.forEachJob(null, (job) -> {
-                    if (job.isPersisted()) {
-                        storeCopy.add(new JobStatus(job));
-                    }
-                });
-            }
-            writeJobsMapImpl(storeCopy);
-            if (DEBUG) {
-                Slog.v(TAG, "Finished writing, took " + (sElapsedRealtimeClock.millis()
-                        - startElapsed) + "ms");
-            }
-            synchronized (mWriteScheduleLock) {
-                mWriteInProgress = false;
-                mWriteScheduleLock.notifyAll();
-            }
-        }
-
-        private void writeJobsMapImpl(List<JobStatus> jobList) {
-            int numJobs = 0;
-            int numSystemJobs = 0;
-            int numSyncJobs = 0;
-            try {
-                final long startTime = SystemClock.uptimeMillis();
-                ByteArrayOutputStream baos = new ByteArrayOutputStream();
-                XmlSerializer out = new FastXmlSerializer();
-                out.setOutput(baos, StandardCharsets.UTF_8.name());
-                out.startDocument(null, true);
-                out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
-
-                out.startTag(null, "job-info");
-                out.attribute(null, "version", Integer.toString(JOBS_FILE_VERSION));
-                for (int i=0; i<jobList.size(); i++) {
-                    JobStatus jobStatus = jobList.get(i);
-                    if (DEBUG) {
-                        Slog.d(TAG, "Saving job " + jobStatus.getJobId());
-                    }
-                    out.startTag(null, "job");
-                    addAttributesToJobTag(out, jobStatus);
-                    writeConstraintsToXml(out, jobStatus);
-                    writeExecutionCriteriaToXml(out, jobStatus);
-                    writeBundleToXml(jobStatus.getJob().getExtras(), out);
-                    out.endTag(null, "job");
-
-                    numJobs++;
-                    if (jobStatus.getUid() == Process.SYSTEM_UID) {
-                        numSystemJobs++;
-                        if (isSyncJob(jobStatus)) {
-                            numSyncJobs++;
-                        }
-                    }
-                }
-                out.endTag(null, "job-info");
-                out.endDocument();
-
-                // Write out to disk in one fell swoop.
-                FileOutputStream fos = mJobsFile.startWrite(startTime);
-                fos.write(baos.toByteArray());
-                mJobsFile.finishWrite(fos);
-            } catch (IOException e) {
-                if (DEBUG) {
-                    Slog.v(TAG, "Error writing out job data.", e);
-                }
-            } catch (XmlPullParserException e) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Error persisting bundle.", e);
-                }
-            } finally {
-                mPersistInfo.countAllJobsSaved = numJobs;
-                mPersistInfo.countSystemServerJobsSaved = numSystemJobs;
-                mPersistInfo.countSystemSyncManagerJobsSaved = numSyncJobs;
-            }
-        }
-
-        /** Write out a tag with data comprising the required fields and priority of this job and
-         * its client.
-         */
-        private void addAttributesToJobTag(XmlSerializer out, JobStatus jobStatus)
-                throws IOException {
-            out.attribute(null, "jobid", Integer.toString(jobStatus.getJobId()));
-            out.attribute(null, "package", jobStatus.getServiceComponent().getPackageName());
-            out.attribute(null, "class", jobStatus.getServiceComponent().getClassName());
-            if (jobStatus.getSourcePackageName() != null) {
-                out.attribute(null, "sourcePackageName", jobStatus.getSourcePackageName());
-            }
-            if (jobStatus.getSourceTag() != null) {
-                out.attribute(null, "sourceTag", jobStatus.getSourceTag());
-            }
-            out.attribute(null, "sourceUserId", String.valueOf(jobStatus.getSourceUserId()));
-            out.attribute(null, "uid", Integer.toString(jobStatus.getUid()));
-            out.attribute(null, "priority", String.valueOf(jobStatus.getPriority()));
-            out.attribute(null, "flags", String.valueOf(jobStatus.getFlags()));
-            if (jobStatus.getInternalFlags() != 0) {
-                out.attribute(null, "internalFlags", String.valueOf(jobStatus.getInternalFlags()));
-            }
-
-            out.attribute(null, "lastSuccessfulRunTime",
-                    String.valueOf(jobStatus.getLastSuccessfulRunTime()));
-            out.attribute(null, "lastFailedRunTime",
-                    String.valueOf(jobStatus.getLastFailedRunTime()));
-        }
-
-        private void writeBundleToXml(PersistableBundle extras, XmlSerializer out)
-                throws IOException, XmlPullParserException {
-            out.startTag(null, XML_TAG_EXTRAS);
-            PersistableBundle extrasCopy = deepCopyBundle(extras, 10);
-            extrasCopy.saveToXml(out);
-            out.endTag(null, XML_TAG_EXTRAS);
-        }
-
-        private PersistableBundle deepCopyBundle(PersistableBundle bundle, int maxDepth) {
-            if (maxDepth <= 0) {
-                return null;
-            }
-            PersistableBundle copy = (PersistableBundle) bundle.clone();
-            Set<String> keySet = bundle.keySet();
-            for (String key: keySet) {
-                Object o = copy.get(key);
-                if (o instanceof PersistableBundle) {
-                    PersistableBundle bCopy = deepCopyBundle((PersistableBundle) o, maxDepth-1);
-                    copy.putPersistableBundle(key, bCopy);
-                }
-            }
-            return copy;
-        }
-
-        /**
-         * Write out a tag with data identifying this job's constraints. If the constraint isn't here
-         * it doesn't apply.
-         */
-        private void writeConstraintsToXml(XmlSerializer out, JobStatus jobStatus) throws IOException {
-            out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS);
-            if (jobStatus.hasConnectivityConstraint()) {
-                final NetworkRequest network = jobStatus.getJob().getRequiredNetwork();
-                out.attribute(null, "net-capabilities", Long.toString(
-                        BitUtils.packBits(network.networkCapabilities.getCapabilities())));
-                out.attribute(null, "net-unwanted-capabilities", Long.toString(
-                        BitUtils.packBits(network.networkCapabilities.getUnwantedCapabilities())));
-
-                out.attribute(null, "net-transport-types", Long.toString(
-                        BitUtils.packBits(network.networkCapabilities.getTransportTypes())));
-            }
-            if (jobStatus.hasIdleConstraint()) {
-                out.attribute(null, "idle", Boolean.toString(true));
-            }
-            if (jobStatus.hasChargingConstraint()) {
-                out.attribute(null, "charging", Boolean.toString(true));
-            }
-            if (jobStatus.hasBatteryNotLowConstraint()) {
-                out.attribute(null, "battery-not-low", Boolean.toString(true));
-            }
-            if (jobStatus.hasStorageNotLowConstraint()) {
-                out.attribute(null, "storage-not-low", Boolean.toString(true));
-            }
-            out.endTag(null, XML_TAG_PARAMS_CONSTRAINTS);
-        }
-
-        private void writeExecutionCriteriaToXml(XmlSerializer out, JobStatus jobStatus)
-                throws IOException {
-            final JobInfo job = jobStatus.getJob();
-            if (jobStatus.getJob().isPeriodic()) {
-                out.startTag(null, XML_TAG_PERIODIC);
-                out.attribute(null, "period", Long.toString(job.getIntervalMillis()));
-                out.attribute(null, "flex", Long.toString(job.getFlexMillis()));
-            } else {
-                out.startTag(null, XML_TAG_ONEOFF);
-            }
-
-            // If we still have the persisted times, we need to record those directly because
-            // we haven't yet been able to calculate the usual elapsed-timebase bounds
-            // correctly due to wall-clock uncertainty.
-            Pair <Long, Long> utcJobTimes = jobStatus.getPersistedUtcTimes();
-            if (DEBUG && utcJobTimes != null) {
-                Slog.i(TAG, "storing original UTC timestamps for " + jobStatus);
-            }
-
-            final long nowRTC = sSystemClock.millis();
-            final long nowElapsed = sElapsedRealtimeClock.millis();
-            if (jobStatus.hasDeadlineConstraint()) {
-                // Wall clock deadline.
-                final long deadlineWallclock = (utcJobTimes == null)
-                        ? nowRTC + (jobStatus.getLatestRunTimeElapsed() - nowElapsed)
-                        : utcJobTimes.second;
-                out.attribute(null, "deadline", Long.toString(deadlineWallclock));
-            }
-            if (jobStatus.hasTimingDelayConstraint()) {
-                final long delayWallclock = (utcJobTimes == null)
-                        ? nowRTC + (jobStatus.getEarliestRunTime() - nowElapsed)
-                        : utcJobTimes.first;
-                out.attribute(null, "delay", Long.toString(delayWallclock));
-            }
-
-            // Only write out back-off policy if it differs from the default.
-            // This also helps the case where the job is idle -> these aren't allowed to specify
-            // back-off.
-            if (jobStatus.getJob().getInitialBackoffMillis() != JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS
-                    || jobStatus.getJob().getBackoffPolicy() != JobInfo.DEFAULT_BACKOFF_POLICY) {
-                out.attribute(null, "backoff-policy", Integer.toString(job.getBackoffPolicy()));
-                out.attribute(null, "initial-backoff", Long.toString(job.getInitialBackoffMillis()));
-            }
-            if (job.isPeriodic()) {
-                out.endTag(null, XML_TAG_PERIODIC);
-            } else {
-                out.endTag(null, XML_TAG_ONEOFF);
-            }
-        }
-    };
-
-    /**
-     * Translate the supplied RTC times to the elapsed timebase, with clamping appropriate
-     * to interpreting them as a job's delay + deadline times for alarm-setting purposes.
-     * @param rtcTimes a Pair<Long, Long> in which {@code first} is the "delay" earliest
-     *     allowable runtime for the job, and {@code second} is the "deadline" time at which
-     *     the job becomes overdue.
-     */
-    private static Pair<Long, Long> convertRtcBoundsToElapsed(Pair<Long, Long> rtcTimes,
-            long nowElapsed) {
-        final long nowWallclock = sSystemClock.millis();
-        final long earliest = (rtcTimes.first > JobStatus.NO_EARLIEST_RUNTIME)
-                ? nowElapsed + Math.max(rtcTimes.first - nowWallclock, 0)
-                : JobStatus.NO_EARLIEST_RUNTIME;
-        final long latest = (rtcTimes.second < JobStatus.NO_LATEST_RUNTIME)
-                ? nowElapsed + Math.max(rtcTimes.second - nowWallclock, 0)
-                : JobStatus.NO_LATEST_RUNTIME;
-        return Pair.create(earliest, latest);
-    }
-
-    private static boolean isSyncJob(JobStatus status) {
-        return com.android.server.content.SyncJobService.class.getName()
-                .equals(status.getServiceComponent().getClassName());
-    }
-
-    /**
-     * Runnable that reads list of persisted job from xml. This is run once at start up, so doesn't
-     * need to go through {@link JobStore#add(com.android.server.job.controllers.JobStatus)}.
-     */
-    private final class ReadJobMapFromDiskRunnable implements Runnable {
-        private final JobSet jobSet;
-        private final boolean rtcGood;
-
-        /**
-         * @param jobSet Reference to the (empty) set of JobStatus objects that back the JobStore,
-         *               so that after disk read we can populate it directly.
-         */
-        ReadJobMapFromDiskRunnable(JobSet jobSet, boolean rtcIsGood) {
-            this.jobSet = jobSet;
-            this.rtcGood = rtcIsGood;
-        }
-
-        @Override
-        public void run() {
-            int numJobs = 0;
-            int numSystemJobs = 0;
-            int numSyncJobs = 0;
-            try {
-                List<JobStatus> jobs;
-                FileInputStream fis = mJobsFile.openRead();
-                synchronized (mLock) {
-                    jobs = readJobMapImpl(fis, rtcGood);
-                    if (jobs != null) {
-                        long now = sElapsedRealtimeClock.millis();
-                        IActivityManager am = ActivityManager.getService();
-                        for (int i=0; i<jobs.size(); i++) {
-                            JobStatus js = jobs.get(i);
-                            js.prepareLocked(am);
-                            js.enqueueTime = now;
-                            this.jobSet.add(js);
-
-                            numJobs++;
-                            if (js.getUid() == Process.SYSTEM_UID) {
-                                numSystemJobs++;
-                                if (isSyncJob(js)) {
-                                    numSyncJobs++;
-                                }
-                            }
-                        }
-                    }
-                }
-                fis.close();
-            } catch (FileNotFoundException e) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Could not find jobs file, probably there was nothing to load.");
-                }
-            } catch (XmlPullParserException | IOException e) {
-                Slog.wtf(TAG, "Error jobstore xml.", e);
-            } finally {
-                if (mPersistInfo.countAllJobsLoaded < 0) { // Only set them once.
-                    mPersistInfo.countAllJobsLoaded = numJobs;
-                    mPersistInfo.countSystemServerJobsLoaded = numSystemJobs;
-                    mPersistInfo.countSystemSyncManagerJobsLoaded = numSyncJobs;
-                }
-            }
-            Slog.i(TAG, "Read " + numJobs + " jobs");
-        }
-
-        private List<JobStatus> readJobMapImpl(FileInputStream fis, boolean rtcIsGood)
-                throws XmlPullParserException, IOException {
-            XmlPullParser parser = Xml.newPullParser();
-            parser.setInput(fis, StandardCharsets.UTF_8.name());
-
-            int eventType = parser.getEventType();
-            while (eventType != XmlPullParser.START_TAG &&
-                    eventType != XmlPullParser.END_DOCUMENT) {
-                eventType = parser.next();
-                Slog.d(TAG, "Start tag: " + parser.getName());
-            }
-            if (eventType == XmlPullParser.END_DOCUMENT) {
-                if (DEBUG) {
-                    Slog.d(TAG, "No persisted jobs.");
-                }
-                return null;
-            }
-
-            String tagName = parser.getName();
-            if ("job-info".equals(tagName)) {
-                final List<JobStatus> jobs = new ArrayList<JobStatus>();
-                // Read in version info.
-                try {
-                    int version = Integer.parseInt(parser.getAttributeValue(null, "version"));
-                    if (version != JOBS_FILE_VERSION) {
-                        Slog.d(TAG, "Invalid version number, aborting jobs file read.");
-                        return null;
-                    }
-                } catch (NumberFormatException e) {
-                    Slog.e(TAG, "Invalid version number, aborting jobs file read.");
-                    return null;
-                }
-                eventType = parser.next();
-                do {
-                    // Read each <job/>
-                    if (eventType == XmlPullParser.START_TAG) {
-                        tagName = parser.getName();
-                        // Start reading job.
-                        if ("job".equals(tagName)) {
-                            JobStatus persistedJob = restoreJobFromXml(rtcIsGood, parser);
-                            if (persistedJob != null) {
-                                if (DEBUG) {
-                                    Slog.d(TAG, "Read out " + persistedJob);
-                                }
-                                jobs.add(persistedJob);
-                            } else {
-                                Slog.d(TAG, "Error reading job from file.");
-                            }
-                        }
-                    }
-                    eventType = parser.next();
-                } while (eventType != XmlPullParser.END_DOCUMENT);
-                return jobs;
-            }
-            return null;
-        }
-
-        /**
-         * @param parser Xml parser at the beginning of a "<job/>" tag. The next "parser.next()" call
-         *               will take the parser into the body of the job tag.
-         * @return Newly instantiated job holding all the information we just read out of the xml tag.
-         */
-        private JobStatus restoreJobFromXml(boolean rtcIsGood, XmlPullParser parser)
-                throws XmlPullParserException, IOException {
-            JobInfo.Builder jobBuilder;
-            int uid, sourceUserId;
-            long lastSuccessfulRunTime;
-            long lastFailedRunTime;
-            int internalFlags = 0;
-
-            // Read out job identifier attributes and priority.
-            try {
-                jobBuilder = buildBuilderFromXml(parser);
-                jobBuilder.setPersisted(true);
-                uid = Integer.parseInt(parser.getAttributeValue(null, "uid"));
-
-                String val = parser.getAttributeValue(null, "priority");
-                if (val != null) {
-                    jobBuilder.setPriority(Integer.parseInt(val));
-                }
-                val = parser.getAttributeValue(null, "flags");
-                if (val != null) {
-                    jobBuilder.setFlags(Integer.parseInt(val));
-                }
-                val = parser.getAttributeValue(null, "internalFlags");
-                if (val != null) {
-                    internalFlags = Integer.parseInt(val);
-                }
-                val = parser.getAttributeValue(null, "sourceUserId");
-                sourceUserId = val == null ? -1 : Integer.parseInt(val);
-
-                val = parser.getAttributeValue(null, "lastSuccessfulRunTime");
-                lastSuccessfulRunTime = val == null ? 0 : Long.parseLong(val);
-
-                val = parser.getAttributeValue(null, "lastFailedRunTime");
-                lastFailedRunTime = val == null ? 0 : Long.parseLong(val);
-            } catch (NumberFormatException e) {
-                Slog.e(TAG, "Error parsing job's required fields, skipping");
-                return null;
-            }
-
-            String sourcePackageName = parser.getAttributeValue(null, "sourcePackageName");
-            final String sourceTag = parser.getAttributeValue(null, "sourceTag");
-
-            int eventType;
-            // Read out constraints tag.
-            do {
-                eventType = parser.next();
-            } while (eventType == XmlPullParser.TEXT);  // Push through to next START_TAG.
-
-            if (!(eventType == XmlPullParser.START_TAG &&
-                    XML_TAG_PARAMS_CONSTRAINTS.equals(parser.getName()))) {
-                // Expecting a <constraints> start tag.
-                return null;
-            }
-            try {
-                buildConstraintsFromXml(jobBuilder, parser);
-            } catch (NumberFormatException e) {
-                Slog.d(TAG, "Error reading constraints, skipping.");
-                return null;
-            }
-            parser.next(); // Consume </constraints>
-
-            // Read out execution parameters tag.
-            do {
-                eventType = parser.next();
-            } while (eventType == XmlPullParser.TEXT);
-            if (eventType != XmlPullParser.START_TAG) {
-                return null;
-            }
-
-            // Tuple of (earliest runtime, latest runtime) in UTC.
-            final Pair<Long, Long> rtcRuntimes;
-            try {
-                rtcRuntimes = buildRtcExecutionTimesFromXml(parser);
-            } catch (NumberFormatException e) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Error parsing execution time parameters, skipping.");
-                }
-                return null;
-            }
-
-            final long elapsedNow = sElapsedRealtimeClock.millis();
-            Pair<Long, Long> elapsedRuntimes = convertRtcBoundsToElapsed(rtcRuntimes, elapsedNow);
-
-            if (XML_TAG_PERIODIC.equals(parser.getName())) {
-                try {
-                    String val = parser.getAttributeValue(null, "period");
-                    final long periodMillis = Long.parseLong(val);
-                    val = parser.getAttributeValue(null, "flex");
-                    final long flexMillis = (val != null) ? Long.valueOf(val) : periodMillis;
-                    jobBuilder.setPeriodic(periodMillis, flexMillis);
-                    // As a sanity check, cap the recreated run time to be no later than flex+period
-                    // from now. This is the latest the periodic could be pushed out. This could
-                    // happen if the periodic ran early (at flex time before period), and then the
-                    // device rebooted.
-                    if (elapsedRuntimes.second > elapsedNow + periodMillis + flexMillis) {
-                        final long clampedLateRuntimeElapsed = elapsedNow + flexMillis
-                                + periodMillis;
-                        final long clampedEarlyRuntimeElapsed = clampedLateRuntimeElapsed
-                                - flexMillis;
-                        Slog.w(TAG,
-                                String.format("Periodic job for uid='%d' persisted run-time is" +
-                                                " too big [%s, %s]. Clamping to [%s,%s]",
-                                        uid,
-                                        DateUtils.formatElapsedTime(elapsedRuntimes.first / 1000),
-                                        DateUtils.formatElapsedTime(elapsedRuntimes.second / 1000),
-                                        DateUtils.formatElapsedTime(
-                                                clampedEarlyRuntimeElapsed / 1000),
-                                        DateUtils.formatElapsedTime(
-                                                clampedLateRuntimeElapsed / 1000))
-                        );
-                        elapsedRuntimes =
-                                Pair.create(clampedEarlyRuntimeElapsed, clampedLateRuntimeElapsed);
-                    }
-                } catch (NumberFormatException e) {
-                    Slog.d(TAG, "Error reading periodic execution criteria, skipping.");
-                    return null;
-                }
-            } else if (XML_TAG_ONEOFF.equals(parser.getName())) {
-                try {
-                    if (elapsedRuntimes.first != JobStatus.NO_EARLIEST_RUNTIME) {
-                        jobBuilder.setMinimumLatency(elapsedRuntimes.first - elapsedNow);
-                    }
-                    if (elapsedRuntimes.second != JobStatus.NO_LATEST_RUNTIME) {
-                        jobBuilder.setOverrideDeadline(
-                                elapsedRuntimes.second - elapsedNow);
-                    }
-                } catch (NumberFormatException e) {
-                    Slog.d(TAG, "Error reading job execution criteria, skipping.");
-                    return null;
-                }
-            } else {
-                if (DEBUG) {
-                    Slog.d(TAG, "Invalid parameter tag, skipping - " + parser.getName());
-                }
-                // Expecting a parameters start tag.
-                return null;
-            }
-            maybeBuildBackoffPolicyFromXml(jobBuilder, parser);
-
-            parser.nextTag(); // Consume parameters end tag.
-
-            // Read out extras Bundle.
-            do {
-                eventType = parser.next();
-            } while (eventType == XmlPullParser.TEXT);
-            if (!(eventType == XmlPullParser.START_TAG
-                    && XML_TAG_EXTRAS.equals(parser.getName()))) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Error reading extras, skipping.");
-                }
-                return null;
-            }
-
-            PersistableBundle extras = PersistableBundle.restoreFromXml(parser);
-            jobBuilder.setExtras(extras);
-            parser.nextTag(); // Consume </extras>
-
-            final JobInfo builtJob;
-            try {
-                builtJob = jobBuilder.build();
-            } catch (Exception e) {
-                Slog.w(TAG, "Unable to build job from XML, ignoring: "
-                        + jobBuilder.summarize());
-                return null;
-            }
-
-            // Migrate sync jobs forward from earlier, incomplete representation
-            if ("android".equals(sourcePackageName)
-                    && extras != null
-                    && extras.getBoolean("SyncManagerJob", false)) {
-                sourcePackageName = extras.getString("owningPackage", sourcePackageName);
-                if (DEBUG) {
-                    Slog.i(TAG, "Fixing up sync job source package name from 'android' to '"
-                            + sourcePackageName + "'");
-                }
-            }
-
-            // And now we're done
-            JobSchedulerInternal service = LocalServices.getService(JobSchedulerInternal.class);
-            final int appBucket = JobSchedulerService.standbyBucketForPackage(sourcePackageName,
-                    sourceUserId, elapsedNow);
-            long currentHeartbeat = service != null ? service.currentHeartbeat() : 0;
-            JobStatus js = new JobStatus(
-                    jobBuilder.build(), uid, sourcePackageName, sourceUserId,
-                    appBucket, currentHeartbeat, sourceTag,
-                    elapsedRuntimes.first, elapsedRuntimes.second,
-                    lastSuccessfulRunTime, lastFailedRunTime,
-                    (rtcIsGood) ? null : rtcRuntimes, internalFlags);
-            return js;
-        }
-
-        private JobInfo.Builder buildBuilderFromXml(XmlPullParser parser) throws NumberFormatException {
-            // Pull out required fields from <job> attributes.
-            int jobId = Integer.parseInt(parser.getAttributeValue(null, "jobid"));
-            String packageName = parser.getAttributeValue(null, "package");
-            String className = parser.getAttributeValue(null, "class");
-            ComponentName cname = new ComponentName(packageName, className);
-
-            return new JobInfo.Builder(jobId, cname);
-        }
-
-        private void buildConstraintsFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser) {
-            String val;
-
-            final String netCapabilities = parser.getAttributeValue(null, "net-capabilities");
-            final String netUnwantedCapabilities = parser.getAttributeValue(
-                    null, "net-unwanted-capabilities");
-            final String netTransportTypes = parser.getAttributeValue(null, "net-transport-types");
-            if (netCapabilities != null && netTransportTypes != null) {
-                final NetworkRequest request = new NetworkRequest.Builder().build();
-                final long unwantedCapabilities = netUnwantedCapabilities != null
-                        ? Long.parseLong(netUnwantedCapabilities)
-                        : BitUtils.packBits(request.networkCapabilities.getUnwantedCapabilities());
-
-                // We're okay throwing NFE here; caught by caller
-                request.networkCapabilities.setCapabilities(
-                        BitUtils.unpackBits(Long.parseLong(netCapabilities)),
-                        BitUtils.unpackBits(unwantedCapabilities));
-                request.networkCapabilities.setTransportTypes(
-                        BitUtils.unpackBits(Long.parseLong(netTransportTypes)));
-                jobBuilder.setRequiredNetwork(request);
-            } else {
-                // Read legacy values
-                val = parser.getAttributeValue(null, "connectivity");
-                if (val != null) {
-                    jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
-                }
-                val = parser.getAttributeValue(null, "metered");
-                if (val != null) {
-                    jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED);
-                }
-                val = parser.getAttributeValue(null, "unmetered");
-                if (val != null) {
-                    jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
-                }
-                val = parser.getAttributeValue(null, "not-roaming");
-                if (val != null) {
-                    jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING);
-                }
-            }
-
-            val = parser.getAttributeValue(null, "idle");
-            if (val != null) {
-                jobBuilder.setRequiresDeviceIdle(true);
-            }
-            val = parser.getAttributeValue(null, "charging");
-            if (val != null) {
-                jobBuilder.setRequiresCharging(true);
-            }
-            val = parser.getAttributeValue(null, "battery-not-low");
-            if (val != null) {
-                jobBuilder.setRequiresBatteryNotLow(true);
-            }
-            val = parser.getAttributeValue(null, "storage-not-low");
-            if (val != null) {
-                jobBuilder.setRequiresStorageNotLow(true);
-            }
-        }
-
-        /**
-         * Builds the back-off policy out of the params tag. These attributes may not exist, depending
-         * on whether the back-off was set when the job was first scheduled.
-         */
-        private void maybeBuildBackoffPolicyFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser) {
-            String val = parser.getAttributeValue(null, "initial-backoff");
-            if (val != null) {
-                long initialBackoff = Long.parseLong(val);
-                val = parser.getAttributeValue(null, "backoff-policy");
-                int backoffPolicy = Integer.parseInt(val);  // Will throw NFE which we catch higher up.
-                jobBuilder.setBackoffCriteria(initialBackoff, backoffPolicy);
-            }
-        }
-
-        /**
-         * Extract a job's earliest/latest run time data from XML.  These are returned in
-         * unadjusted UTC wall clock time, because we do not yet know whether the system
-         * clock is reliable for purposes of calculating deltas from 'now'.
-         *
-         * @param parser
-         * @return A Pair of timestamps in UTC wall-clock time.  The first is the earliest
-         *     time at which the job is to become runnable, and the second is the deadline at
-         *     which it becomes overdue to execute.
-         * @throws NumberFormatException
-         */
-        private Pair<Long, Long> buildRtcExecutionTimesFromXml(XmlPullParser parser)
-                throws NumberFormatException {
-            String val;
-            // Pull out execution time data.
-            val = parser.getAttributeValue(null, "delay");
-            final long earliestRunTimeRtc = (val != null)
-                    ? Long.parseLong(val)
-                    : JobStatus.NO_EARLIEST_RUNTIME;
-            val = parser.getAttributeValue(null, "deadline");
-            final long latestRunTimeRtc = (val != null)
-                    ? Long.parseLong(val)
-                    : JobStatus.NO_LATEST_RUNTIME;
-            return Pair.create(earliestRunTimeRtc, latestRunTimeRtc);
-        }
-    }
-
-    /** Set of all tracked jobs. */
-    @VisibleForTesting
-    public static final class JobSet {
-        @VisibleForTesting // Key is the getUid() originator of the jobs in each sheaf
-        final SparseArray<ArraySet<JobStatus>> mJobs;
-
-        @VisibleForTesting // Same data but with the key as getSourceUid() of the jobs in each sheaf
-        final SparseArray<ArraySet<JobStatus>> mJobsPerSourceUid;
-
-        public JobSet() {
-            mJobs = new SparseArray<ArraySet<JobStatus>>();
-            mJobsPerSourceUid = new SparseArray<>();
-        }
-
-        public List<JobStatus> getJobsByUid(int uid) {
-            ArrayList<JobStatus> matchingJobs = new ArrayList<JobStatus>();
-            ArraySet<JobStatus> jobs = mJobs.get(uid);
-            if (jobs != null) {
-                matchingJobs.addAll(jobs);
-            }
-            return matchingJobs;
-        }
-
-        // By user, not by uid, so we need to traverse by key and check
-        public List<JobStatus> getJobsByUser(int userId) {
-            final ArrayList<JobStatus> result = new ArrayList<JobStatus>();
-            for (int i = mJobsPerSourceUid.size() - 1; i >= 0; i--) {
-                if (UserHandle.getUserId(mJobsPerSourceUid.keyAt(i)) == userId) {
-                    final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(i);
-                    if (jobs != null) {
-                        result.addAll(jobs);
-                    }
-                }
-            }
-            return result;
-        }
-
-        public boolean add(JobStatus job) {
-            final int uid = job.getUid();
-            final int sourceUid = job.getSourceUid();
-            ArraySet<JobStatus> jobs = mJobs.get(uid);
-            if (jobs == null) {
-                jobs = new ArraySet<JobStatus>();
-                mJobs.put(uid, jobs);
-            }
-            ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid);
-            if (jobsForSourceUid == null) {
-                jobsForSourceUid = new ArraySet<>();
-                mJobsPerSourceUid.put(sourceUid, jobsForSourceUid);
-            }
-            final boolean added = jobs.add(job);
-            final boolean addedInSource = jobsForSourceUid.add(job);
-            if (added != addedInSource) {
-                Slog.wtf(TAG, "mJobs and mJobsPerSourceUid mismatch; caller= " + added
-                        + " source= " + addedInSource);
-            }
-            return added || addedInSource;
-        }
-
-        public boolean remove(JobStatus job) {
-            final int uid = job.getUid();
-            final ArraySet<JobStatus> jobs = mJobs.get(uid);
-            final int sourceUid = job.getSourceUid();
-            final ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid);
-            final boolean didRemove = jobs != null && jobs.remove(job);
-            final boolean sourceRemove = jobsForSourceUid != null && jobsForSourceUid.remove(job);
-            if (didRemove != sourceRemove) {
-                Slog.wtf(TAG, "Job presence mismatch; caller=" + didRemove
-                        + " source=" + sourceRemove);
-            }
-            if (didRemove || sourceRemove) {
-                // no more jobs for this uid?  let the now-empty set objects be GC'd.
-                if (jobs != null && jobs.size() == 0) {
-                    mJobs.remove(uid);
-                }
-                if (jobsForSourceUid != null && jobsForSourceUid.size() == 0) {
-                    mJobsPerSourceUid.remove(sourceUid);
-                }
-                return true;
-            }
-            return false;
-        }
-
-        /**
-         * Removes the jobs of all users not specified by the whitelist of user ids.
-         * This will remove jobs scheduled *by* non-existent users as well as jobs scheduled *for*
-         * non-existent users
-         */
-        public void removeJobsOfNonUsers(final int[] whitelist) {
-            final Predicate<JobStatus> noSourceUser =
-                    job -> !ArrayUtils.contains(whitelist, job.getSourceUserId());
-            final Predicate<JobStatus> noCallingUser =
-                    job -> !ArrayUtils.contains(whitelist, job.getUserId());
-            removeAll(noSourceUser.or(noCallingUser));
-        }
-
-        private void removeAll(Predicate<JobStatus> predicate) {
-            for (int jobSetIndex = mJobs.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
-                final ArraySet<JobStatus> jobs = mJobs.valueAt(jobSetIndex);
-                for (int jobIndex = jobs.size() - 1; jobIndex >= 0; jobIndex--) {
-                    if (predicate.test(jobs.valueAt(jobIndex))) {
-                        jobs.removeAt(jobIndex);
-                    }
-                }
-                if (jobs.size() == 0) {
-                    mJobs.removeAt(jobSetIndex);
-                }
-            }
-            for (int jobSetIndex = mJobsPerSourceUid.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
-                final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(jobSetIndex);
-                for (int jobIndex = jobs.size() - 1; jobIndex >= 0; jobIndex--) {
-                    if (predicate.test(jobs.valueAt(jobIndex))) {
-                        jobs.removeAt(jobIndex);
-                    }
-                }
-                if (jobs.size() == 0) {
-                    mJobsPerSourceUid.removeAt(jobSetIndex);
-                }
-            }
-        }
-
-        public boolean contains(JobStatus job) {
-            final int uid = job.getUid();
-            ArraySet<JobStatus> jobs = mJobs.get(uid);
-            return jobs != null && jobs.contains(job);
-        }
-
-        public JobStatus get(int uid, int jobId) {
-            ArraySet<JobStatus> jobs = mJobs.get(uid);
-            if (jobs != null) {
-                for (int i = jobs.size() - 1; i >= 0; i--) {
-                    JobStatus job = jobs.valueAt(i);
-                    if (job.getJobId() == jobId) {
-                        return job;
-                    }
-                }
-            }
-            return null;
-        }
-
-        // Inefficient; use only for testing
-        public List<JobStatus> getAllJobs() {
-            ArrayList<JobStatus> allJobs = new ArrayList<JobStatus>(size());
-            for (int i = mJobs.size() - 1; i >= 0; i--) {
-                ArraySet<JobStatus> jobs = mJobs.valueAt(i);
-                if (jobs != null) {
-                    // Use a for loop over the ArraySet, so we don't need to make its
-                    // optional collection class iterator implementation or have to go
-                    // through a temporary array from toArray().
-                    for (int j = jobs.size() - 1; j >= 0; j--) {
-                        allJobs.add(jobs.valueAt(j));
-                    }
-                }
-            }
-            return allJobs;
-        }
-
-        public void clear() {
-            mJobs.clear();
-            mJobsPerSourceUid.clear();
-        }
-
-        public int size() {
-            int total = 0;
-            for (int i = mJobs.size() - 1; i >= 0; i--) {
-                total += mJobs.valueAt(i).size();
-            }
-            return total;
-        }
-
-        // We only want to count the jobs that this uid has scheduled on its own
-        // behalf, not those that the app has scheduled on someone else's behalf.
-        public int countJobsForUid(int uid) {
-            int total = 0;
-            ArraySet<JobStatus> jobs = mJobs.get(uid);
-            if (jobs != null) {
-                for (int i = jobs.size() - 1; i >= 0; i--) {
-                    JobStatus job = jobs.valueAt(i);
-                    if (job.getUid() == job.getSourceUid()) {
-                        total++;
-                    }
-                }
-            }
-            return total;
-        }
-
-        public void forEachJob(@Nullable Predicate<JobStatus> filterPredicate,
-                Consumer<JobStatus> functor) {
-            for (int uidIndex = mJobs.size() - 1; uidIndex >= 0; uidIndex--) {
-                ArraySet<JobStatus> jobs = mJobs.valueAt(uidIndex);
-                if (jobs != null) {
-                    for (int i = jobs.size() - 1; i >= 0; i--) {
-                        final JobStatus jobStatus = jobs.valueAt(i);
-                        if ((filterPredicate == null) || filterPredicate.test(jobStatus)) {
-                            functor.accept(jobStatus);
-                        }
-                    }
-                }
-            }
-        }
-
-        public void forEachJob(int callingUid, Consumer<JobStatus> functor) {
-            ArraySet<JobStatus> jobs = mJobs.get(callingUid);
-            if (jobs != null) {
-                for (int i = jobs.size() - 1; i >= 0; i--) {
-                    functor.accept(jobs.valueAt(i));
-                }
-            }
-        }
-
-        public void forEachJobForSourceUid(int sourceUid, Consumer<JobStatus> functor) {
-            final ArraySet<JobStatus> jobs = mJobsPerSourceUid.get(sourceUid);
-            if (jobs != null) {
-                for (int i = jobs.size() - 1; i >= 0; i--) {
-                    functor.accept(jobs.valueAt(i));
-                }
-            }
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/job/StateChangedListener.java b/services/core/java/com/android/server/job/StateChangedListener.java
deleted file mode 100644
index 87bfc27..0000000
--- a/services/core/java/com/android/server/job/StateChangedListener.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job;
-
-import com.android.server.job.controllers.JobStatus;
-
-/**
- * Interface through which a {@link com.android.server.job.controllers.StateController} informs
- * the {@link com.android.server.job.JobSchedulerService} that there are some tasks potentially
- * ready to be run.
- */
-public interface StateChangedListener {
-    /**
-     * Called by the controller to notify the JobManager that it should check on the state of a
-     * task.
-     */
-    public void onControllerStateChanged();
-
-    /**
-     * Called by the controller to notify the JobManager that regardless of the state of the task,
-     * it must be run immediately.
-     * @param jobStatus The state of the task which is to be run immediately. <strong>null
-     *                  indicates to the scheduler that any ready jobs should be flushed.</strong>
-     */
-    public void onRunJobNow(JobStatus jobStatus);
-
-    public void onDeviceIdleStateChanged(boolean deviceIdle);
-}
diff --git a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
deleted file mode 100644
index b698e5b..0000000
--- a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job.controllers;
-
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.util.Log;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Preconditions;
-import com.android.server.AppStateTracker;
-import com.android.server.AppStateTracker.Listener;
-import com.android.server.LocalServices;
-import com.android.server.job.JobSchedulerService;
-import com.android.server.job.JobStore;
-import com.android.server.job.StateControllerProto;
-import com.android.server.job.StateControllerProto.BackgroundJobsController.TrackedJob;
-
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-/**
- * Tracks the following pieces of JobStatus state:
- *
- * - the CONSTRAINT_BACKGROUND_NOT_RESTRICTED general constraint bit, which
- *    is used to selectively permit battery-saver exempted jobs to run; and
- *
- * - the uid-active boolean state expressed by the AppStateTracker.  Jobs in 'active'
- *    uids are inherently eligible to run jobs regardless of the uid's standby bucket.
- */
-public final class BackgroundJobsController extends StateController {
-    private static final String TAG = "JobScheduler.Background";
-    private static final boolean DEBUG = JobSchedulerService.DEBUG
-            || Log.isLoggable(TAG, Log.DEBUG);
-
-    // Tri-state about possible "is this uid 'active'?" knowledge
-    static final int UNKNOWN = 0;
-    static final int KNOWN_ACTIVE = 1;
-    static final int KNOWN_INACTIVE = 2;
-
-    private final AppStateTracker mAppStateTracker;
-
-    public BackgroundJobsController(JobSchedulerService service) {
-        super(service);
-
-        mAppStateTracker = Preconditions.checkNotNull(
-                LocalServices.getService(AppStateTracker.class));
-        mAppStateTracker.addListener(mForceAppStandbyListener);
-    }
-
-    @Override
-    public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
-        updateSingleJobRestrictionLocked(jobStatus, UNKNOWN);
-    }
-
-    @Override
-    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
-            boolean forUpdate) {
-    }
-
-    @Override
-    public void dumpControllerStateLocked(final IndentingPrintWriter pw,
-            final Predicate<JobStatus> predicate) {
-        mAppStateTracker.dump(pw);
-        pw.println();
-
-        mService.getJobStore().forEachJob(predicate, (jobStatus) -> {
-            final int uid = jobStatus.getSourceUid();
-            final String sourcePkg = jobStatus.getSourcePackageName();
-            pw.print("#");
-            jobStatus.printUniqueId(pw);
-            pw.print(" from ");
-            UserHandle.formatUid(pw, uid);
-            pw.print(mAppStateTracker.isUidActive(uid) ? " active" : " idle");
-            if (mAppStateTracker.isUidPowerSaveWhitelisted(uid) ||
-                    mAppStateTracker.isUidTempPowerSaveWhitelisted(uid)) {
-                pw.print(", whitelisted");
-            }
-            pw.print(": ");
-            pw.print(sourcePkg);
-
-            pw.print(" [RUN_ANY_IN_BACKGROUND ");
-            pw.print(mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, sourcePkg)
-                    ? "allowed]" : "disallowed]");
-
-            if ((jobStatus.satisfiedConstraints
-                    & JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
-                pw.println(" RUNNABLE");
-            } else {
-                pw.println(" WAITING");
-            }
-        });
-    }
-
-    @Override
-    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
-            Predicate<JobStatus> predicate) {
-        final long token = proto.start(fieldId);
-        final long mToken = proto.start(StateControllerProto.BACKGROUND);
-
-        mAppStateTracker.dumpProto(proto,
-                StateControllerProto.BackgroundJobsController.FORCE_APP_STANDBY_TRACKER);
-
-        mService.getJobStore().forEachJob(predicate, (jobStatus) -> {
-            final long jsToken =
-                    proto.start(StateControllerProto.BackgroundJobsController.TRACKED_JOBS);
-
-            jobStatus.writeToShortProto(proto,
-                    TrackedJob.INFO);
-            final int sourceUid = jobStatus.getSourceUid();
-            proto.write(TrackedJob.SOURCE_UID, sourceUid);
-            final String sourcePkg = jobStatus.getSourcePackageName();
-            proto.write(TrackedJob.SOURCE_PACKAGE_NAME, sourcePkg);
-
-            proto.write(TrackedJob.IS_IN_FOREGROUND,
-                    mAppStateTracker.isUidActive(sourceUid));
-            proto.write(TrackedJob.IS_WHITELISTED,
-                    mAppStateTracker.isUidPowerSaveWhitelisted(sourceUid) ||
-                    mAppStateTracker.isUidTempPowerSaveWhitelisted(sourceUid));
-
-            proto.write(
-                    TrackedJob.CAN_RUN_ANY_IN_BACKGROUND,
-                    mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(
-                            sourceUid, sourcePkg));
-
-            proto.write(
-                    TrackedJob.ARE_CONSTRAINTS_SATISFIED,
-                    (jobStatus.satisfiedConstraints &
-                            JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0);
-
-            proto.end(jsToken);
-        });
-
-        proto.end(mToken);
-        proto.end(token);
-    }
-
-    private void updateAllJobRestrictionsLocked() {
-        updateJobRestrictionsLocked(/*filterUid=*/ -1, UNKNOWN);
-    }
-
-    private void updateJobRestrictionsForUidLocked(int uid, boolean isActive) {
-        updateJobRestrictionsLocked(uid, (isActive) ? KNOWN_ACTIVE : KNOWN_INACTIVE);
-    }
-
-    private void updateJobRestrictionsLocked(int filterUid, int newActiveState) {
-        final UpdateJobFunctor updateTrackedJobs = new UpdateJobFunctor(newActiveState);
-
-        final long start = DEBUG ? SystemClock.elapsedRealtimeNanos() : 0;
-
-        final JobStore store = mService.getJobStore();
-        if (filterUid > 0) {
-            store.forEachJobForSourceUid(filterUid, updateTrackedJobs);
-        } else {
-            store.forEachJob(updateTrackedJobs);
-        }
-
-        final long time = DEBUG ? (SystemClock.elapsedRealtimeNanos() - start) : 0;
-        if (DEBUG) {
-            Slog.d(TAG, String.format(
-                    "Job status updated: %d/%d checked/total jobs, %d us",
-                    updateTrackedJobs.mCheckedCount,
-                    updateTrackedJobs.mTotalCount,
-                    (time / 1000)
-                    ));
-        }
-
-        if (updateTrackedJobs.mChanged) {
-            mStateChangedListener.onControllerStateChanged();
-        }
-    }
-
-    boolean updateSingleJobRestrictionLocked(JobStatus jobStatus, int activeState) {
-
-        final int uid = jobStatus.getSourceUid();
-        final String packageName = jobStatus.getSourcePackageName();
-
-        final boolean canRun = !mAppStateTracker.areJobsRestricted(uid, packageName,
-                (jobStatus.getInternalFlags() & JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION)
-                        != 0);
-
-        final boolean isActive;
-        if (activeState == UNKNOWN) {
-            isActive = mAppStateTracker.isUidActive(uid);
-        } else {
-            isActive = (activeState == KNOWN_ACTIVE);
-        }
-        boolean didChange = jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRun);
-        didChange |= jobStatus.setUidActive(isActive);
-        return didChange;
-    }
-
-    private final class UpdateJobFunctor implements Consumer<JobStatus> {
-        final int activeState;
-        boolean mChanged = false;
-        int mTotalCount = 0;
-        int mCheckedCount = 0;
-
-        public UpdateJobFunctor(int newActiveState) {
-            activeState = newActiveState;
-        }
-
-        @Override
-        public void accept(JobStatus jobStatus) {
-            mTotalCount++;
-            mCheckedCount++;
-            if (updateSingleJobRestrictionLocked(jobStatus, activeState)) {
-                mChanged = true;
-            }
-        }
-    }
-
-    private final Listener mForceAppStandbyListener = new Listener() {
-        @Override
-        public void updateAllJobs() {
-            synchronized (mLock) {
-                updateAllJobRestrictionsLocked();
-            }
-        }
-
-        @Override
-        public void updateJobsForUid(int uid, boolean isActive) {
-            synchronized (mLock) {
-                updateJobRestrictionsForUidLocked(uid, isActive);
-            }
-        }
-
-        @Override
-        public void updateJobsForUidPackage(int uid, String packageName, boolean isActive) {
-            synchronized (mLock) {
-                updateJobRestrictionsForUidLocked(uid, isActive);
-            }
-        }
-    };
-}
diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java
deleted file mode 100644
index 46658ad..0000000
--- a/services/core/java/com/android/server/job/controllers/BatteryController.java
+++ /dev/null
@@ -1,282 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job.controllers;
-
-import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.BatteryManager;
-import android.os.BatteryManagerInternal;
-import android.os.UserHandle;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.LocalServices;
-import com.android.server.job.JobSchedulerService;
-import com.android.server.job.StateControllerProto;
-
-import java.util.function.Predicate;
-
-/**
- * Simple controller that tracks whether the phone is charging or not. The phone is considered to
- * be charging when it's been plugged in for more than two minutes, and the system has broadcast
- * ACTION_BATTERY_OK.
- */
-public final class BatteryController extends StateController {
-    private static final String TAG = "JobScheduler.Battery";
-    private static final boolean DEBUG = JobSchedulerService.DEBUG
-            || Log.isLoggable(TAG, Log.DEBUG);
-
-    private final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>();
-    private ChargingTracker mChargeTracker;
-
-    @VisibleForTesting
-    public ChargingTracker getTracker() {
-        return mChargeTracker;
-    }
-
-    public BatteryController(JobSchedulerService service) {
-        super(service);
-        mChargeTracker = new ChargingTracker();
-        mChargeTracker.startTracking();
-    }
-
-    @Override
-    public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
-        if (taskStatus.hasPowerConstraint()) {
-            mTrackedTasks.add(taskStatus);
-            taskStatus.setTrackingController(JobStatus.TRACKING_BATTERY);
-            taskStatus.setChargingConstraintSatisfied(mChargeTracker.isOnStablePower());
-            taskStatus.setBatteryNotLowConstraintSatisfied(mChargeTracker.isBatteryNotLow());
-        }
-    }
-
-    @Override
-    public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) {
-        if (taskStatus.clearTrackingController(JobStatus.TRACKING_BATTERY)) {
-            mTrackedTasks.remove(taskStatus);
-        }
-    }
-
-    private void maybeReportNewChargingStateLocked() {
-        final boolean stablePower = mChargeTracker.isOnStablePower();
-        final boolean batteryNotLow = mChargeTracker.isBatteryNotLow();
-        if (DEBUG) {
-            Slog.d(TAG, "maybeReportNewChargingStateLocked: " + stablePower);
-        }
-        boolean reportChange = false;
-        for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
-            final JobStatus ts = mTrackedTasks.valueAt(i);
-            boolean previous = ts.setChargingConstraintSatisfied(stablePower);
-            if (previous != stablePower) {
-                reportChange = true;
-            }
-            previous = ts.setBatteryNotLowConstraintSatisfied(batteryNotLow);
-            if (previous != batteryNotLow) {
-                reportChange = true;
-            }
-        }
-        if (stablePower || batteryNotLow) {
-            // If one of our conditions has been satisfied, always schedule any newly ready jobs.
-            mStateChangedListener.onRunJobNow(null);
-        } else if (reportChange) {
-            // Otherwise, just let the job scheduler know the state has changed and take care of it
-            // as it thinks is best.
-            mStateChangedListener.onControllerStateChanged();
-        }
-    }
-
-    public final class ChargingTracker extends BroadcastReceiver {
-        /**
-         * Track whether we're "charging", where charging means that we're ready to commit to
-         * doing work.
-         */
-        private boolean mCharging;
-        /** Keep track of whether the battery is charged enough that we want to do work. */
-        private boolean mBatteryHealthy;
-        /** Sequence number of last broadcast. */
-        private int mLastBatterySeq = -1;
-
-        private BroadcastReceiver mMonitor;
-
-        public ChargingTracker() {
-        }
-
-        public void startTracking() {
-            IntentFilter filter = new IntentFilter();
-
-            // Battery health.
-            filter.addAction(Intent.ACTION_BATTERY_LOW);
-            filter.addAction(Intent.ACTION_BATTERY_OKAY);
-            // Charging/not charging.
-            filter.addAction(BatteryManager.ACTION_CHARGING);
-            filter.addAction(BatteryManager.ACTION_DISCHARGING);
-            mContext.registerReceiver(this, filter);
-
-            // Initialise tracker state.
-            BatteryManagerInternal batteryManagerInternal =
-                    LocalServices.getService(BatteryManagerInternal.class);
-            mBatteryHealthy = !batteryManagerInternal.getBatteryLevelLow();
-            mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
-        }
-
-        public void setMonitorBatteryLocked(boolean enabled) {
-            if (enabled) {
-                if (mMonitor == null) {
-                    mMonitor = new BroadcastReceiver() {
-                        @Override public void onReceive(Context context, Intent intent) {
-                            ChargingTracker.this.onReceive(context, intent);
-                        }
-                    };
-                    IntentFilter filter = new IntentFilter();
-                    filter.addAction(Intent.ACTION_BATTERY_CHANGED);
-                    mContext.registerReceiver(mMonitor, filter);
-                }
-            } else {
-                if (mMonitor != null) {
-                    mContext.unregisterReceiver(mMonitor);
-                    mMonitor = null;
-                }
-            }
-        }
-
-        public boolean isOnStablePower() {
-            return mCharging && mBatteryHealthy;
-        }
-
-        public boolean isBatteryNotLow() {
-            return mBatteryHealthy;
-        }
-
-        public boolean isMonitoring() {
-            return mMonitor != null;
-        }
-
-        public int getSeq() {
-            return mLastBatterySeq;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            onReceiveInternal(intent);
-        }
-
-        @VisibleForTesting
-        public void onReceiveInternal(Intent intent) {
-            synchronized (mLock) {
-                final String action = intent.getAction();
-                if (Intent.ACTION_BATTERY_LOW.equals(action)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Battery life too low to do work. @ "
-                                + sElapsedRealtimeClock.millis());
-                    }
-                    // If we get this action, the battery is discharging => it isn't plugged in so
-                    // there's no work to cancel. We track this variable for the case where it is
-                    // charging, but hasn't been for long enough to be healthy.
-                    mBatteryHealthy = false;
-                    maybeReportNewChargingStateLocked();
-                } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Battery life healthy enough to do work. @ "
-                                + sElapsedRealtimeClock.millis());
-                    }
-                    mBatteryHealthy = true;
-                    maybeReportNewChargingStateLocked();
-                } else if (BatteryManager.ACTION_CHARGING.equals(action)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Received charging intent, fired @ "
-                                + sElapsedRealtimeClock.millis());
-                    }
-                    mCharging = true;
-                    maybeReportNewChargingStateLocked();
-                } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Disconnected from power.");
-                    }
-                    mCharging = false;
-                    maybeReportNewChargingStateLocked();
-                }
-                mLastBatterySeq = intent.getIntExtra(BatteryManager.EXTRA_SEQUENCE,
-                        mLastBatterySeq);
-            }
-        }
-    }
-
-    @Override
-    public void dumpControllerStateLocked(IndentingPrintWriter pw,
-            Predicate<JobStatus> predicate) {
-        pw.println("Stable power: " + mChargeTracker.isOnStablePower());
-        pw.println("Not low: " + mChargeTracker.isBatteryNotLow());
-
-        if (mChargeTracker.isMonitoring()) {
-            pw.print("MONITORING: seq=");
-            pw.println(mChargeTracker.getSeq());
-        }
-        pw.println();
-
-        for (int i = 0; i < mTrackedTasks.size(); i++) {
-            final JobStatus js = mTrackedTasks.valueAt(i);
-            if (!predicate.test(js)) {
-                continue;
-            }
-            pw.print("#");
-            js.printUniqueId(pw);
-            pw.print(" from ");
-            UserHandle.formatUid(pw, js.getSourceUid());
-            pw.println();
-        }
-    }
-
-    @Override
-    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
-            Predicate<JobStatus> predicate) {
-        final long token = proto.start(fieldId);
-        final long mToken = proto.start(StateControllerProto.BATTERY);
-
-        proto.write(StateControllerProto.BatteryController.IS_ON_STABLE_POWER,
-                mChargeTracker.isOnStablePower());
-        proto.write(StateControllerProto.BatteryController.IS_BATTERY_NOT_LOW,
-                mChargeTracker.isBatteryNotLow());
-
-        proto.write(StateControllerProto.BatteryController.IS_MONITORING,
-                mChargeTracker.isMonitoring());
-        proto.write(StateControllerProto.BatteryController.LAST_BROADCAST_SEQUENCE_NUMBER,
-                mChargeTracker.getSeq());
-
-        for (int i = 0; i < mTrackedTasks.size(); i++) {
-            final JobStatus js = mTrackedTasks.valueAt(i);
-            if (!predicate.test(js)) {
-                continue;
-            }
-            final long jsToken = proto.start(StateControllerProto.BatteryController.TRACKED_JOBS);
-            js.writeToShortProto(proto, StateControllerProto.BatteryController.TrackedJob.INFO);
-            proto.write(StateControllerProto.BatteryController.TrackedJob.SOURCE_UID,
-                    js.getSourceUid());
-            proto.end(jsToken);
-        }
-
-        proto.end(mToken);
-        proto.end(token);
-    }
-}
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
deleted file mode 100644
index f8cf6ae..0000000
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ /dev/null
@@ -1,694 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job.controllers;
-
-import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
-
-import android.app.job.JobInfo;
-import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
-import android.net.INetworkPolicyListener;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
-import android.net.NetworkPolicyManager;
-import android.net.NetworkRequest;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.UserHandle;
-import android.text.format.DateUtils;
-import android.util.ArraySet;
-import android.util.DataUnit;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.LocalServices;
-import com.android.server.job.JobSchedulerService;
-import com.android.server.job.JobSchedulerService.Constants;
-import com.android.server.job.JobServiceContext;
-import com.android.server.job.StateControllerProto;
-import com.android.server.net.NetworkPolicyManagerInternal;
-
-import java.util.Objects;
-import java.util.function.Predicate;
-
-/**
- * Handles changes in connectivity.
- * <p>
- * Each app can have a different default networks or different connectivity
- * status due to user-requested network policies, so we need to check
- * constraints on a per-UID basis.
- *
- * Test: atest com.android.server.job.controllers.ConnectivityControllerTest
- */
-public final class ConnectivityController extends StateController implements
-        ConnectivityManager.OnNetworkActiveListener {
-    private static final String TAG = "JobScheduler.Connectivity";
-    private static final boolean DEBUG = JobSchedulerService.DEBUG
-            || Log.isLoggable(TAG, Log.DEBUG);
-
-    private final ConnectivityManager mConnManager;
-    private final NetworkPolicyManager mNetPolicyManager;
-    private final NetworkPolicyManagerInternal mNetPolicyManagerInternal;
-
-    /** List of tracked jobs keyed by source UID. */
-    @GuardedBy("mLock")
-    private final SparseArray<ArraySet<JobStatus>> mTrackedJobs = new SparseArray<>();
-
-    /**
-     * Keep track of all the UID's jobs that the controller has requested that NetworkPolicyManager
-     * grant an exception to in the app standby chain.
-     */
-    @GuardedBy("mLock")
-    private final SparseArray<ArraySet<JobStatus>> mRequestedWhitelistJobs = new SparseArray<>();
-
-    /** List of currently available networks. */
-    @GuardedBy("mLock")
-    private final ArraySet<Network> mAvailableNetworks = new ArraySet<>();
-
-    private boolean mUseQuotaLimit;
-
-    private static final int MSG_DATA_SAVER_TOGGLED = 0;
-    private static final int MSG_UID_RULES_CHANGES = 1;
-    private static final int MSG_REEVALUATE_JOBS = 2;
-
-    private final Handler mHandler;
-
-    public ConnectivityController(JobSchedulerService service) {
-        super(service);
-        mHandler = new CcHandler(mContext.getMainLooper());
-
-        mConnManager = mContext.getSystemService(ConnectivityManager.class);
-        mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);
-        mNetPolicyManagerInternal = LocalServices.getService(NetworkPolicyManagerInternal.class);
-
-        // We're interested in all network changes; internally we match these
-        // network changes against the active network for each UID with jobs.
-        final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
-        mConnManager.registerNetworkCallback(request, mNetworkCallback);
-
-        mNetPolicyManager.registerListener(mNetPolicyListener);
-
-        mUseQuotaLimit = !mConstants.USE_HEARTBEATS;
-    }
-
-    @GuardedBy("mLock")
-    @Override
-    public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
-        if (jobStatus.hasConnectivityConstraint()) {
-            updateConstraintsSatisfied(jobStatus);
-            ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUid());
-            if (jobs == null) {
-                jobs = new ArraySet<>();
-                mTrackedJobs.put(jobStatus.getSourceUid(), jobs);
-            }
-            jobs.add(jobStatus);
-            jobStatus.setTrackingController(JobStatus.TRACKING_CONNECTIVITY);
-        }
-    }
-
-    @GuardedBy("mLock")
-    @Override
-    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
-            boolean forUpdate) {
-        if (jobStatus.clearTrackingController(JobStatus.TRACKING_CONNECTIVITY)) {
-            ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUid());
-            if (jobs != null) {
-                jobs.remove(jobStatus);
-            }
-            maybeRevokeStandbyExceptionLocked(jobStatus);
-        }
-    }
-
-    @GuardedBy("mLock")
-    @Override
-    public void onConstantsUpdatedLocked() {
-        if (mConstants.USE_HEARTBEATS) {
-            // App idle exceptions are only requested for the rolling quota system.
-            if (DEBUG) Slog.i(TAG, "Revoking all standby exceptions");
-            for (int i = 0; i < mRequestedWhitelistJobs.size(); ++i) {
-                int uid = mRequestedWhitelistJobs.keyAt(i);
-                mNetPolicyManagerInternal.setAppIdleWhitelist(uid, false);
-            }
-            mRequestedWhitelistJobs.clear();
-        }
-        if (mUseQuotaLimit == mConstants.USE_HEARTBEATS) {
-            mUseQuotaLimit = !mConstants.USE_HEARTBEATS;
-            mHandler.obtainMessage(MSG_REEVALUATE_JOBS).sendToTarget();
-        }
-    }
-
-    /**
-     * Returns true if the job's requested network is available. This DOES NOT necesarilly mean
-     * that the UID has been granted access to the network.
-     */
-    public boolean isNetworkAvailable(JobStatus job) {
-        synchronized (mLock) {
-            for (int i = 0; i < mAvailableNetworks.size(); ++i) {
-                final Network network = mAvailableNetworks.valueAt(i);
-                final NetworkCapabilities capabilities = mConnManager.getNetworkCapabilities(
-                        network);
-                final boolean satisfied = isSatisfied(job, network, capabilities, mConstants);
-                if (DEBUG) {
-                    Slog.v(TAG, "isNetworkAvailable(" + job + ") with network " + network
-                            + " and capabilities " + capabilities + ". Satisfied=" + satisfied);
-                }
-                if (satisfied) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Request that NetworkPolicyManager grant an exception to the uid from its standby policy
-     * chain.
-     */
-    @VisibleForTesting
-    @GuardedBy("mLock")
-    void requestStandbyExceptionLocked(JobStatus job) {
-        final int uid = job.getSourceUid();
-        // Need to call this before adding the job.
-        final boolean isExceptionRequested = isStandbyExceptionRequestedLocked(uid);
-        ArraySet<JobStatus> jobs = mRequestedWhitelistJobs.get(uid);
-        if (jobs == null) {
-            jobs = new ArraySet<JobStatus>();
-            mRequestedWhitelistJobs.put(uid, jobs);
-        }
-        if (!jobs.add(job) || isExceptionRequested) {
-            if (DEBUG) {
-                Slog.i(TAG, "requestStandbyExceptionLocked found exception already requested.");
-            }
-            return;
-        }
-        if (DEBUG) Slog.i(TAG, "Requesting standby exception for UID: " + uid);
-        mNetPolicyManagerInternal.setAppIdleWhitelist(uid, true);
-    }
-
-    /** Returns whether a standby exception has been requested for the UID. */
-    @VisibleForTesting
-    @GuardedBy("mLock")
-    boolean isStandbyExceptionRequestedLocked(final int uid) {
-        ArraySet jobs = mRequestedWhitelistJobs.get(uid);
-        return jobs != null && jobs.size() > 0;
-    }
-
-    @VisibleForTesting
-    @GuardedBy("mLock")
-    boolean wouldBeReadyWithConnectivityLocked(JobStatus jobStatus) {
-        final boolean networkAvailable = isNetworkAvailable(jobStatus);
-        if (DEBUG) {
-            Slog.v(TAG, "wouldBeReadyWithConnectivityLocked: " + jobStatus.toShortString()
-                    + " networkAvailable=" + networkAvailable);
-        }
-        // If the network isn't available, then requesting an exception won't help.
-
-        return networkAvailable && wouldBeReadyWithConstraintLocked(jobStatus,
-                JobStatus.CONSTRAINT_CONNECTIVITY);
-    }
-
-    /**
-     * Tell NetworkPolicyManager not to block a UID's network connection if that's the only
-     * thing stopping a job from running.
-     */
-    @GuardedBy("mLock")
-    @Override
-    public void evaluateStateLocked(JobStatus jobStatus) {
-        if (mConstants.USE_HEARTBEATS) {
-            // This should only be used for the rolling quota system.
-            return;
-        }
-
-        if (!jobStatus.hasConnectivityConstraint()) {
-            return;
-        }
-
-        // Always check the full job readiness stat in case the component has been disabled.
-        if (wouldBeReadyWithConnectivityLocked(jobStatus)) {
-            if (DEBUG) {
-                Slog.i(TAG, "evaluateStateLocked finds job " + jobStatus + " would be ready.");
-            }
-            requestStandbyExceptionLocked(jobStatus);
-        } else {
-            if (DEBUG) {
-                Slog.i(TAG, "evaluateStateLocked finds job " + jobStatus + " would not be ready.");
-            }
-            maybeRevokeStandbyExceptionLocked(jobStatus);
-        }
-    }
-
-    @GuardedBy("mLock")
-    @Override
-    public void reevaluateStateLocked(final int uid) {
-        if (mConstants.USE_HEARTBEATS) {
-            return;
-        }
-        // Check if we still need a connectivity exception in case the JobService was disabled.
-        ArraySet<JobStatus> jobs = mTrackedJobs.get(uid);
-        if (jobs == null) {
-            return;
-        }
-        for (int i = jobs.size() - 1; i >= 0; i--) {
-            evaluateStateLocked(jobs.valueAt(i));
-        }
-    }
-
-    /** Cancel the requested standby exception if none of the jobs would be ready to run anyway. */
-    @VisibleForTesting
-    @GuardedBy("mLock")
-    void maybeRevokeStandbyExceptionLocked(final JobStatus job) {
-        final int uid = job.getSourceUid();
-        if (!isStandbyExceptionRequestedLocked(uid)) {
-            return;
-        }
-        ArraySet<JobStatus> jobs = mRequestedWhitelistJobs.get(uid);
-        if (jobs == null) {
-            Slog.wtf(TAG,
-                    "maybeRevokeStandbyExceptionLocked found null jobs array even though a "
-                            + "standby exception has been requested.");
-            return;
-        }
-        if (!jobs.remove(job) || jobs.size() > 0) {
-            if (DEBUG) {
-                Slog.i(TAG,
-                        "maybeRevokeStandbyExceptionLocked not revoking because there are still "
-                                + jobs.size() + " jobs left.");
-            }
-            return;
-        }
-        // No more jobs that need an exception.
-        revokeStandbyExceptionLocked(uid);
-    }
-
-    /**
-     * Tell NetworkPolicyManager to revoke any exception it granted from its standby policy chain
-     * for the uid.
-     */
-    @GuardedBy("mLock")
-    private void revokeStandbyExceptionLocked(final int uid) {
-        if (DEBUG) Slog.i(TAG, "Revoking standby exception for UID: " + uid);
-        mNetPolicyManagerInternal.setAppIdleWhitelist(uid, false);
-        mRequestedWhitelistJobs.remove(uid);
-    }
-
-    @GuardedBy("mLock")
-    @Override
-    public void onAppRemovedLocked(String pkgName, int uid) {
-        mTrackedJobs.delete(uid);
-    }
-
-    /**
-     * Test to see if running the given job on the given network is insane.
-     * <p>
-     * For example, if a job is trying to send 10MB over a 128Kbps EDGE
-     * connection, it would take 10.4 minutes, and has no chance of succeeding
-     * before the job times out, so we'd be insane to try running it.
-     */
-    private boolean isInsane(JobStatus jobStatus, Network network,
-            NetworkCapabilities capabilities, Constants constants) {
-        final long maxJobExecutionTimeMs = mUseQuotaLimit
-                ? mService.getMaxJobExecutionTimeMs(jobStatus)
-                : JobServiceContext.EXECUTING_TIMESLICE_MILLIS;
-
-        final long downloadBytes = jobStatus.getEstimatedNetworkDownloadBytes();
-        if (downloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
-            final long bandwidth = capabilities.getLinkDownstreamBandwidthKbps();
-            // If we don't know the bandwidth, all we can do is hope the job finishes in time.
-            if (bandwidth != LINK_BANDWIDTH_UNSPECIFIED) {
-                // Divide by 8 to convert bits to bytes.
-                final long estimatedMillis = ((downloadBytes * DateUtils.SECOND_IN_MILLIS)
-                        / (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8));
-                if (estimatedMillis > maxJobExecutionTimeMs) {
-                    // If we'd never finish before the timeout, we'd be insane!
-                    Slog.w(TAG, "Estimated " + downloadBytes + " download bytes over " + bandwidth
-                            + " kbps network would take " + estimatedMillis + "ms and job has "
-                            + maxJobExecutionTimeMs + "ms to run; that's insane!");
-                    return true;
-                }
-            }
-        }
-
-        final long uploadBytes = jobStatus.getEstimatedNetworkUploadBytes();
-        if (uploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
-            final long bandwidth = capabilities.getLinkUpstreamBandwidthKbps();
-            // If we don't know the bandwidth, all we can do is hope the job finishes in time.
-            if (bandwidth != LINK_BANDWIDTH_UNSPECIFIED) {
-                // Divide by 8 to convert bits to bytes.
-                final long estimatedMillis = ((uploadBytes * DateUtils.SECOND_IN_MILLIS)
-                        / (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8));
-                if (estimatedMillis > maxJobExecutionTimeMs) {
-                    // If we'd never finish before the timeout, we'd be insane!
-                    Slog.w(TAG, "Estimated " + uploadBytes + " upload bytes over " + bandwidth
-                            + " kbps network would take " + estimatedMillis + "ms and job has "
-                            + maxJobExecutionTimeMs + "ms to run; that's insane!");
-                    return true;
-                }
-            }
-        }
-
-        return false;
-    }
-
-    private static boolean isCongestionDelayed(JobStatus jobStatus, Network network,
-            NetworkCapabilities capabilities, Constants constants) {
-        // If network is congested, and job is less than 50% through the
-        // developer-requested window, then we're okay delaying the job.
-        if (!capabilities.hasCapability(NET_CAPABILITY_NOT_CONGESTED)) {
-            return jobStatus.getFractionRunTime() < constants.CONN_CONGESTION_DELAY_FRAC;
-        } else {
-            return false;
-        }
-    }
-
-    private static boolean isStrictSatisfied(JobStatus jobStatus, Network network,
-            NetworkCapabilities capabilities, Constants constants) {
-        return jobStatus.getJob().getRequiredNetwork().networkCapabilities
-                .satisfiedByNetworkCapabilities(capabilities);
-    }
-
-    private static boolean isRelaxedSatisfied(JobStatus jobStatus, Network network,
-            NetworkCapabilities capabilities, Constants constants) {
-        // Only consider doing this for prefetching jobs
-        if (!jobStatus.getJob().isPrefetch()) {
-            return false;
-        }
-
-        // See if we match after relaxing any unmetered request
-        final NetworkCapabilities relaxed = new NetworkCapabilities(
-                jobStatus.getJob().getRequiredNetwork().networkCapabilities)
-                        .removeCapability(NET_CAPABILITY_NOT_METERED);
-        if (relaxed.satisfiedByNetworkCapabilities(capabilities)) {
-            // TODO: treat this as "maybe" response; need to check quotas
-            return jobStatus.getFractionRunTime() > constants.CONN_PREFETCH_RELAX_FRAC;
-        } else {
-            return false;
-        }
-    }
-
-    @VisibleForTesting
-    boolean isSatisfied(JobStatus jobStatus, Network network,
-            NetworkCapabilities capabilities, Constants constants) {
-        // Zeroth, we gotta have a network to think about being satisfied
-        if (network == null || capabilities == null) return false;
-
-        // First, are we insane?
-        if (isInsane(jobStatus, network, capabilities, constants)) return false;
-
-        // Second, is the network congested?
-        if (isCongestionDelayed(jobStatus, network, capabilities, constants)) return false;
-
-        // Third, is the network a strict match?
-        if (isStrictSatisfied(jobStatus, network, capabilities, constants)) return true;
-
-        // Third, is the network a relaxed match?
-        if (isRelaxedSatisfied(jobStatus, network, capabilities, constants)) return true;
-
-        return false;
-    }
-
-    private boolean updateConstraintsSatisfied(JobStatus jobStatus) {
-        final Network network = mConnManager.getActiveNetworkForUid(jobStatus.getSourceUid());
-        final NetworkCapabilities capabilities = mConnManager.getNetworkCapabilities(network);
-        return updateConstraintsSatisfied(jobStatus, network, capabilities);
-    }
-
-    private boolean updateConstraintsSatisfied(JobStatus jobStatus, Network network,
-            NetworkCapabilities capabilities) {
-        // TODO: consider matching against non-active networks
-
-        final boolean ignoreBlocked = (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
-        final NetworkInfo info = mConnManager.getNetworkInfoForUid(network,
-                jobStatus.getSourceUid(), ignoreBlocked);
-
-        final boolean connected = (info != null) && info.isConnected();
-        final boolean satisfied = isSatisfied(jobStatus, network, capabilities, mConstants);
-
-        final boolean changed = jobStatus
-                .setConnectivityConstraintSatisfied(connected && satisfied);
-
-        // Pass along the evaluated network for job to use; prevents race
-        // conditions as default routes change over time, and opens the door to
-        // using non-default routes.
-        jobStatus.network = network;
-
-        if (DEBUG) {
-            Slog.i(TAG, "Connectivity " + (changed ? "CHANGED" : "unchanged")
-                    + " for " + jobStatus + ": connected=" + connected
-                    + " satisfied=" + satisfied);
-        }
-        return changed;
-    }
-
-    /**
-     * Update any jobs tracked by this controller that match given filters.
-     *
-     * @param filterUid     only update jobs belonging to this UID, or {@code -1} to
-     *                      update all tracked jobs.
-     * @param filterNetwork only update jobs that would use this
-     *                      {@link Network}, or {@code null} to update all tracked jobs.
-     */
-    private void updateTrackedJobs(int filterUid, Network filterNetwork) {
-        synchronized (mLock) {
-            // Since this is a really hot codepath, temporarily cache any
-            // answers that we get from ConnectivityManager.
-            final SparseArray<NetworkCapabilities> networkToCapabilities = new SparseArray<>();
-
-            boolean changed = false;
-            if (filterUid == -1) {
-                for (int i = mTrackedJobs.size() - 1; i >= 0; i--) {
-                    changed |= updateTrackedJobsLocked(mTrackedJobs.valueAt(i),
-                            filterNetwork, networkToCapabilities);
-                }
-            } else {
-                changed = updateTrackedJobsLocked(mTrackedJobs.get(filterUid),
-                        filterNetwork, networkToCapabilities);
-            }
-            if (changed) {
-                mStateChangedListener.onControllerStateChanged();
-            }
-        }
-    }
-
-    private boolean updateTrackedJobsLocked(ArraySet<JobStatus> jobs, Network filterNetwork,
-            SparseArray<NetworkCapabilities> networkToCapabilities) {
-        if (jobs == null || jobs.size() == 0) {
-            return false;
-        }
-
-        final Network network = mConnManager.getActiveNetworkForUid(jobs.valueAt(0).getSourceUid());
-        final int netId = network != null ? network.netId : -1;
-        NetworkCapabilities capabilities = networkToCapabilities.get(netId);
-        if (capabilities == null) {
-            capabilities = mConnManager.getNetworkCapabilities(network);
-            networkToCapabilities.put(netId, capabilities);
-        }
-        final boolean networkMatch = (filterNetwork == null
-                || Objects.equals(filterNetwork, network));
-
-        boolean changed = false;
-        for (int i = jobs.size() - 1; i >= 0; i--) {
-            final JobStatus js = jobs.valueAt(i);
-
-            // Update either when we have a network match, or when the
-            // job hasn't yet been evaluated against the currently
-            // active network; typically when we just lost a network.
-            if (networkMatch || !Objects.equals(js.network, network)) {
-                changed |= updateConstraintsSatisfied(js, network, capabilities);
-            }
-        }
-        return changed;
-    }
-
-    /**
-     * We know the network has just come up. We want to run any jobs that are ready.
-     */
-    @Override
-    public void onNetworkActive() {
-        synchronized (mLock) {
-            for (int i = mTrackedJobs.size()-1; i >= 0; i--) {
-                final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
-                for (int j = jobs.size() - 1; j >= 0; j--) {
-                    final JobStatus js = jobs.valueAt(j);
-                    if (js.isReady()) {
-                        if (DEBUG) {
-                            Slog.d(TAG, "Running " + js + " due to network activity.");
-                        }
-                        mStateChangedListener.onRunJobNow(js);
-                    }
-                }
-            }
-        }
-    }
-
-    private final NetworkCallback mNetworkCallback = new NetworkCallback() {
-        @Override
-        public void onAvailable(Network network) {
-            if (DEBUG) Slog.v(TAG, "onAvailable: " + network);
-            synchronized (mLock) {
-                mAvailableNetworks.add(network);
-            }
-        }
-
-        @Override
-        public void onCapabilitiesChanged(Network network, NetworkCapabilities capabilities) {
-            if (DEBUG) {
-                Slog.v(TAG, "onCapabilitiesChanged: " + network);
-            }
-            updateTrackedJobs(-1, network);
-        }
-
-        @Override
-        public void onLost(Network network) {
-            if (DEBUG) {
-                Slog.v(TAG, "onLost: " + network);
-            }
-            synchronized (mLock) {
-                mAvailableNetworks.remove(network);
-            }
-            updateTrackedJobs(-1, network);
-        }
-    };
-
-    private final INetworkPolicyListener mNetPolicyListener = new NetworkPolicyManager.Listener() {
-        @Override
-        public void onRestrictBackgroundChanged(boolean restrictBackground) {
-            if (DEBUG) {
-                Slog.v(TAG, "onRestrictBackgroundChanged: " + restrictBackground);
-            }
-            mHandler.obtainMessage(MSG_DATA_SAVER_TOGGLED).sendToTarget();
-        }
-
-        @Override
-        public void onUidRulesChanged(int uid, int uidRules) {
-            if (DEBUG) {
-                Slog.v(TAG, "onUidRulesChanged: " + uid);
-            }
-            mHandler.obtainMessage(MSG_UID_RULES_CHANGES, uid, 0).sendToTarget();
-        }
-    };
-
-    private class CcHandler extends Handler {
-        CcHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            synchronized (mLock) {
-                switch (msg.what) {
-                    case MSG_DATA_SAVER_TOGGLED:
-                        updateTrackedJobs(-1, null);
-                        break;
-                    case MSG_UID_RULES_CHANGES:
-                        updateTrackedJobs(msg.arg1, null);
-                        break;
-                    case MSG_REEVALUATE_JOBS:
-                        updateTrackedJobs(-1, null);
-                        break;
-                }
-            }
-        }
-    };
-
-    @GuardedBy("mLock")
-    @Override
-    public void dumpControllerStateLocked(IndentingPrintWriter pw,
-            Predicate<JobStatus> predicate) {
-        pw.print("mUseQuotaLimit="); pw.println(mUseQuotaLimit);
-
-        if (mRequestedWhitelistJobs.size() > 0) {
-            pw.print("Requested standby exceptions:");
-            for (int i = 0; i < mRequestedWhitelistJobs.size(); i++) {
-                pw.print(" ");
-                pw.print(mRequestedWhitelistJobs.keyAt(i));
-                pw.print(" (");
-                pw.print(mRequestedWhitelistJobs.valueAt(i).size());
-                pw.print(" jobs)");
-            }
-            pw.println();
-        }
-        if (mAvailableNetworks.size() > 0) {
-            pw.println("Available networks:");
-            pw.increaseIndent();
-            for (int i = 0; i < mAvailableNetworks.size(); i++) {
-                pw.println(mAvailableNetworks.valueAt(i));
-            }
-            pw.decreaseIndent();
-        } else {
-            pw.println("No available networks");
-        }
-        for (int i = 0; i < mTrackedJobs.size(); i++) {
-            final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
-            for (int j = 0; j < jobs.size(); j++) {
-                final JobStatus js = jobs.valueAt(j);
-                if (!predicate.test(js)) {
-                    continue;
-                }
-                pw.print("#");
-                js.printUniqueId(pw);
-                pw.print(" from ");
-                UserHandle.formatUid(pw, js.getSourceUid());
-                pw.print(": ");
-                pw.print(js.getJob().getRequiredNetwork());
-                pw.println();
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    @Override
-    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
-            Predicate<JobStatus> predicate) {
-        final long token = proto.start(fieldId);
-        final long mToken = proto.start(StateControllerProto.CONNECTIVITY);
-
-        for (int i = 0; i < mTrackedJobs.size(); i++) {
-            final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
-            for (int j = 0; j < jobs.size(); j++) {
-                final JobStatus js = jobs.valueAt(j);
-                if (!predicate.test(js)) {
-                    continue;
-                }
-                final long jsToken = proto.start(
-                        StateControllerProto.ConnectivityController.TRACKED_JOBS);
-                js.writeToShortProto(proto,
-                        StateControllerProto.ConnectivityController.TrackedJob.INFO);
-                proto.write(StateControllerProto.ConnectivityController.TrackedJob.SOURCE_UID,
-                        js.getSourceUid());
-                NetworkRequest rn = js.getJob().getRequiredNetwork();
-                if (rn != null) {
-                    rn.writeToProto(proto,
-                            StateControllerProto.ConnectivityController.TrackedJob
-                                    .REQUIRED_NETWORK);
-                }
-                proto.end(jsToken);
-            }
-        }
-
-        proto.end(mToken);
-        proto.end(token);
-    }
-}
diff --git a/services/core/java/com/android/server/job/controllers/ContentObserverController.java b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
deleted file mode 100644
index a775cf5..0000000
--- a/services/core/java/com/android/server/job/controllers/ContentObserverController.java
+++ /dev/null
@@ -1,544 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job.controllers;
-
-import android.annotation.UserIdInt;
-import android.app.job.JobInfo;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.TimeUtils;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.job.JobSchedulerService;
-import com.android.server.job.StateControllerProto;
-import com.android.server.job.StateControllerProto.ContentObserverController.Observer.TriggerContentData;
-
-import java.util.ArrayList;
-import java.util.function.Predicate;
-
-/**
- * Controller for monitoring changes to content URIs through a ContentObserver.
- */
-public final class ContentObserverController extends StateController {
-    private static final String TAG = "JobScheduler.ContentObserver";
-    private static final boolean DEBUG = JobSchedulerService.DEBUG
-            || Log.isLoggable(TAG, Log.DEBUG);
-
-    /**
-     * Maximum number of changing URIs we will batch together to report.
-     * XXX Should be smarter about this, restricting it by the maximum number
-     * of characters we will retain.
-     */
-    private static final int MAX_URIS_REPORTED = 50;
-
-    /**
-     * At this point we consider it urgent to schedule the job ASAP.
-     */
-    private static final int URIS_URGENT_THRESHOLD = 40;
-
-    final private ArraySet<JobStatus> mTrackedTasks = new ArraySet<>();
-    /**
-     * Per-userid {@link JobInfo.TriggerContentUri} keyed ContentObserver cache.
-     */
-    final SparseArray<ArrayMap<JobInfo.TriggerContentUri, ObserverInstance>> mObservers =
-            new SparseArray<>();
-    final Handler mHandler;
-
-    public ContentObserverController(JobSchedulerService service) {
-        super(service);
-        mHandler = new Handler(mContext.getMainLooper());
-    }
-
-    @Override
-    public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
-        if (taskStatus.hasContentTriggerConstraint()) {
-            if (taskStatus.contentObserverJobInstance == null) {
-                taskStatus.contentObserverJobInstance = new JobInstance(taskStatus);
-            }
-            if (DEBUG) {
-                Slog.i(TAG, "Tracking content-trigger job " + taskStatus);
-            }
-            mTrackedTasks.add(taskStatus);
-            taskStatus.setTrackingController(JobStatus.TRACKING_CONTENT);
-            boolean havePendingUris = false;
-            // If there is a previous job associated with the new job, propagate over
-            // any pending content URI trigger reports.
-            if (taskStatus.contentObserverJobInstance.mChangedAuthorities != null) {
-                havePendingUris = true;
-            }
-            // If we have previously reported changed authorities/uris, then we failed
-            // to complete the job with them so will re-record them to report again.
-            if (taskStatus.changedAuthorities != null) {
-                havePendingUris = true;
-                if (taskStatus.contentObserverJobInstance.mChangedAuthorities == null) {
-                    taskStatus.contentObserverJobInstance.mChangedAuthorities
-                            = new ArraySet<>();
-                }
-                for (String auth : taskStatus.changedAuthorities) {
-                    taskStatus.contentObserverJobInstance.mChangedAuthorities.add(auth);
-                }
-                if (taskStatus.changedUris != null) {
-                    if (taskStatus.contentObserverJobInstance.mChangedUris == null) {
-                        taskStatus.contentObserverJobInstance.mChangedUris = new ArraySet<>();
-                    }
-                    for (Uri uri : taskStatus.changedUris) {
-                        taskStatus.contentObserverJobInstance.mChangedUris.add(uri);
-                    }
-                }
-                taskStatus.changedAuthorities = null;
-                taskStatus.changedUris = null;
-            }
-            taskStatus.changedAuthorities = null;
-            taskStatus.changedUris = null;
-            taskStatus.setContentTriggerConstraintSatisfied(havePendingUris);
-        }
-        if (lastJob != null && lastJob.contentObserverJobInstance != null) {
-            // And now we can detach the instance state from the last job.
-            lastJob.contentObserverJobInstance.detachLocked();
-            lastJob.contentObserverJobInstance = null;
-        }
-    }
-
-    @Override
-    public void prepareForExecutionLocked(JobStatus taskStatus) {
-        if (taskStatus.hasContentTriggerConstraint()) {
-            if (taskStatus.contentObserverJobInstance != null) {
-                taskStatus.changedUris = taskStatus.contentObserverJobInstance.mChangedUris;
-                taskStatus.changedAuthorities
-                        = taskStatus.contentObserverJobInstance.mChangedAuthorities;
-                taskStatus.contentObserverJobInstance.mChangedUris = null;
-                taskStatus.contentObserverJobInstance.mChangedAuthorities = null;
-            }
-        }
-    }
-
-    @Override
-    public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob,
-            boolean forUpdate) {
-        if (taskStatus.clearTrackingController(JobStatus.TRACKING_CONTENT)) {
-            mTrackedTasks.remove(taskStatus);
-            if (taskStatus.contentObserverJobInstance != null) {
-                taskStatus.contentObserverJobInstance.unscheduleLocked();
-                if (incomingJob != null) {
-                    if (taskStatus.contentObserverJobInstance != null
-                            && taskStatus.contentObserverJobInstance.mChangedAuthorities != null) {
-                        // We are stopping this job, but it is going to be replaced by this given
-                        // incoming job.  We want to propagate our state over to it, so we don't
-                        // lose any content changes that had happened since the last one started.
-                        // If there is a previous job associated with the new job, propagate over
-                        // any pending content URI trigger reports.
-                        if (incomingJob.contentObserverJobInstance == null) {
-                            incomingJob.contentObserverJobInstance = new JobInstance(incomingJob);
-                        }
-                        incomingJob.contentObserverJobInstance.mChangedAuthorities
-                                = taskStatus.contentObserverJobInstance.mChangedAuthorities;
-                        incomingJob.contentObserverJobInstance.mChangedUris
-                                = taskStatus.contentObserverJobInstance.mChangedUris;
-                        taskStatus.contentObserverJobInstance.mChangedAuthorities = null;
-                        taskStatus.contentObserverJobInstance.mChangedUris = null;
-                    }
-                    // We won't detach the content observers here, because we want to
-                    // allow them to continue monitoring so we don't miss anything...  and
-                    // since we are giving an incomingJob here, we know this will be
-                    // immediately followed by a start tracking of that job.
-                } else {
-                    // But here there is no incomingJob, so nothing coming up, so time to detach.
-                    taskStatus.contentObserverJobInstance.detachLocked();
-                    taskStatus.contentObserverJobInstance = null;
-                }
-            }
-            if (DEBUG) {
-                Slog.i(TAG, "No longer tracking job " + taskStatus);
-            }
-        }
-    }
-
-    @Override
-    public void rescheduleForFailureLocked(JobStatus newJob, JobStatus failureToReschedule) {
-        if (failureToReschedule.hasContentTriggerConstraint()
-                && newJob.hasContentTriggerConstraint()) {
-            // Our job has failed, and we are scheduling a new job for it.
-            // Copy the last reported content changes in to the new job, so when
-            // we schedule the new one we will pick them up and report them again.
-            newJob.changedAuthorities = failureToReschedule.changedAuthorities;
-            newJob.changedUris = failureToReschedule.changedUris;
-        }
-    }
-
-    final class ObserverInstance extends ContentObserver {
-        final JobInfo.TriggerContentUri mUri;
-        final @UserIdInt int mUserId;
-        final ArraySet<JobInstance> mJobs = new ArraySet<>();
-
-        public ObserverInstance(Handler handler, JobInfo.TriggerContentUri uri,
-                @UserIdInt int userId) {
-            super(handler);
-            mUri = uri;
-            mUserId = userId;
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            if (DEBUG) {
-                Slog.i(TAG, "onChange(self=" + selfChange + ") for " + uri
-                        + " when mUri=" + mUri + " mUserId=" + mUserId);
-            }
-            synchronized (mLock) {
-                final int N = mJobs.size();
-                for (int i=0; i<N; i++) {
-                    JobInstance inst = mJobs.valueAt(i);
-                    if (inst.mChangedUris == null) {
-                        inst.mChangedUris = new ArraySet<>();
-                    }
-                    if (inst.mChangedUris.size() < MAX_URIS_REPORTED) {
-                        inst.mChangedUris.add(uri);
-                    }
-                    if (inst.mChangedAuthorities == null) {
-                        inst.mChangedAuthorities = new ArraySet<>();
-                    }
-                    inst.mChangedAuthorities.add(uri.getAuthority());
-                    inst.scheduleLocked();
-                }
-            }
-        }
-    }
-
-    static final class TriggerRunnable implements Runnable {
-        final JobInstance mInstance;
-
-        TriggerRunnable(JobInstance instance) {
-            mInstance = instance;
-        }
-
-        @Override public void run() {
-            mInstance.trigger();
-        }
-    }
-
-    final class JobInstance {
-        final ArrayList<ObserverInstance> mMyObservers = new ArrayList<>();
-        final JobStatus mJobStatus;
-        final Runnable mExecuteRunner;
-        final Runnable mTimeoutRunner;
-        ArraySet<Uri> mChangedUris;
-        ArraySet<String> mChangedAuthorities;
-
-        boolean mTriggerPending;
-
-        // This constructor must be called with the master job scheduler lock held.
-        JobInstance(JobStatus jobStatus) {
-            mJobStatus = jobStatus;
-            mExecuteRunner = new TriggerRunnable(this);
-            mTimeoutRunner = new TriggerRunnable(this);
-            final JobInfo.TriggerContentUri[] uris = jobStatus.getJob().getTriggerContentUris();
-            final int sourceUserId = jobStatus.getSourceUserId();
-            ArrayMap<JobInfo.TriggerContentUri, ObserverInstance> observersOfUser =
-                    mObservers.get(sourceUserId);
-            if (observersOfUser == null) {
-                observersOfUser = new ArrayMap<>();
-                mObservers.put(sourceUserId, observersOfUser);
-            }
-            if (uris != null) {
-                for (JobInfo.TriggerContentUri uri : uris) {
-                    ObserverInstance obs = observersOfUser.get(uri);
-                    if (obs == null) {
-                        obs = new ObserverInstance(mHandler, uri, jobStatus.getSourceUserId());
-                        observersOfUser.put(uri, obs);
-                        final boolean andDescendants = (uri.getFlags() &
-                                JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS) != 0;
-                        if (DEBUG) {
-                            Slog.v(TAG, "New observer " + obs + " for " + uri.getUri()
-                                    + " andDescendants=" + andDescendants
-                                    + " sourceUserId=" + sourceUserId);
-                        }
-                        mContext.getContentResolver().registerContentObserver(
-                                uri.getUri(),
-                                andDescendants,
-                                obs,
-                                sourceUserId
-                        );
-                    } else {
-                        if (DEBUG) {
-                            final boolean andDescendants = (uri.getFlags() &
-                                    JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS) != 0;
-                            Slog.v(TAG, "Reusing existing observer " + obs + " for " + uri.getUri()
-                                    + " andDescendants=" + andDescendants);
-                        }
-                    }
-                    obs.mJobs.add(this);
-                    mMyObservers.add(obs);
-                }
-            }
-        }
-
-        void trigger() {
-            boolean reportChange = false;
-            synchronized (mLock) {
-                if (mTriggerPending) {
-                    if (mJobStatus.setContentTriggerConstraintSatisfied(true)) {
-                        reportChange = true;
-                    }
-                    unscheduleLocked();
-                }
-            }
-            // Let the scheduler know that state has changed. This may or may not result in an
-            // execution.
-            if (reportChange) {
-                mStateChangedListener.onControllerStateChanged();
-            }
-        }
-
-        void scheduleLocked() {
-            if (!mTriggerPending) {
-                mTriggerPending = true;
-                mHandler.postDelayed(mTimeoutRunner, mJobStatus.getTriggerContentMaxDelay());
-            }
-            mHandler.removeCallbacks(mExecuteRunner);
-            if (mChangedUris.size() >= URIS_URGENT_THRESHOLD) {
-                // If we start getting near the limit, GO NOW!
-                mHandler.post(mExecuteRunner);
-            } else {
-                mHandler.postDelayed(mExecuteRunner, mJobStatus.getTriggerContentUpdateDelay());
-            }
-        }
-
-        void unscheduleLocked() {
-            if (mTriggerPending) {
-                mHandler.removeCallbacks(mExecuteRunner);
-                mHandler.removeCallbacks(mTimeoutRunner);
-                mTriggerPending = false;
-            }
-        }
-
-        void detachLocked() {
-            final int N = mMyObservers.size();
-            for (int i=0; i<N; i++) {
-                final ObserverInstance obs = mMyObservers.get(i);
-                obs.mJobs.remove(this);
-                if (obs.mJobs.size() == 0) {
-                    if (DEBUG) {
-                        Slog.i(TAG, "Unregistering observer " + obs + " for " + obs.mUri.getUri());
-                    }
-                    mContext.getContentResolver().unregisterContentObserver(obs);
-                    ArrayMap<JobInfo.TriggerContentUri, ObserverInstance> observerOfUser =
-                            mObservers.get(obs.mUserId);
-                    if (observerOfUser !=  null) {
-                        observerOfUser.remove(obs.mUri);
-                    }
-                }
-            }
-        }
-    }
-
-    @Override
-    public void dumpControllerStateLocked(IndentingPrintWriter pw,
-            Predicate<JobStatus> predicate) {
-        for (int i = 0; i < mTrackedTasks.size(); i++) {
-            JobStatus js = mTrackedTasks.valueAt(i);
-            if (!predicate.test(js)) {
-                continue;
-            }
-            pw.print("#");
-            js.printUniqueId(pw);
-            pw.print(" from ");
-            UserHandle.formatUid(pw, js.getSourceUid());
-            pw.println();
-        }
-        pw.println();
-
-        int N = mObservers.size();
-        if (N > 0) {
-            pw.println("Observers:");
-            pw.increaseIndent();
-            for (int userIdx = 0; userIdx < N; userIdx++) {
-                final int userId = mObservers.keyAt(userIdx);
-                ArrayMap<JobInfo.TriggerContentUri, ObserverInstance> observersOfUser =
-                        mObservers.get(userId);
-                int numbOfObserversPerUser = observersOfUser.size();
-                for (int observerIdx = 0 ; observerIdx < numbOfObserversPerUser; observerIdx++) {
-                    ObserverInstance obs = observersOfUser.valueAt(observerIdx);
-                    int M = obs.mJobs.size();
-                    boolean shouldDump = false;
-                    for (int j = 0; j < M; j++) {
-                        JobInstance inst = obs.mJobs.valueAt(j);
-                        if (predicate.test(inst.mJobStatus)) {
-                            shouldDump = true;
-                            break;
-                        }
-                    }
-                    if (!shouldDump) {
-                        continue;
-                    }
-                    JobInfo.TriggerContentUri trigger = observersOfUser.keyAt(observerIdx);
-                    pw.print(trigger.getUri());
-                    pw.print(" 0x");
-                    pw.print(Integer.toHexString(trigger.getFlags()));
-                    pw.print(" (");
-                    pw.print(System.identityHashCode(obs));
-                    pw.println("):");
-                    pw.increaseIndent();
-                    pw.println("Jobs:");
-                    pw.increaseIndent();
-                    for (int j = 0; j < M; j++) {
-                        JobInstance inst = obs.mJobs.valueAt(j);
-                        pw.print("#");
-                        inst.mJobStatus.printUniqueId(pw);
-                        pw.print(" from ");
-                        UserHandle.formatUid(pw, inst.mJobStatus.getSourceUid());
-                        if (inst.mChangedAuthorities != null) {
-                            pw.println(":");
-                            pw.increaseIndent();
-                            if (inst.mTriggerPending) {
-                                pw.print("Trigger pending: update=");
-                                TimeUtils.formatDuration(
-                                        inst.mJobStatus.getTriggerContentUpdateDelay(), pw);
-                                pw.print(", max=");
-                                TimeUtils.formatDuration(
-                                        inst.mJobStatus.getTriggerContentMaxDelay(), pw);
-                                pw.println();
-                            }
-                            pw.println("Changed Authorities:");
-                            for (int k = 0; k < inst.mChangedAuthorities.size(); k++) {
-                                pw.println(inst.mChangedAuthorities.valueAt(k));
-                            }
-                            if (inst.mChangedUris != null) {
-                                pw.println("          Changed URIs:");
-                                for (int k = 0; k < inst.mChangedUris.size(); k++) {
-                                    pw.println(inst.mChangedUris.valueAt(k));
-                                }
-                            }
-                            pw.decreaseIndent();
-                        } else {
-                            pw.println();
-                        }
-                    }
-                    pw.decreaseIndent();
-                    pw.decreaseIndent();
-                }
-            }
-            pw.decreaseIndent();
-        }
-    }
-
-    @Override
-    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
-            Predicate<JobStatus> predicate) {
-        final long token = proto.start(fieldId);
-        final long mToken = proto.start(StateControllerProto.CONTENT_OBSERVER);
-
-        for (int i = 0; i < mTrackedTasks.size(); i++) {
-            JobStatus js = mTrackedTasks.valueAt(i);
-            if (!predicate.test(js)) {
-                continue;
-            }
-            final long jsToken =
-                    proto.start(StateControllerProto.ContentObserverController.TRACKED_JOBS);
-            js.writeToShortProto(proto,
-                    StateControllerProto.ContentObserverController.TrackedJob.INFO);
-            proto.write(StateControllerProto.ContentObserverController.TrackedJob.SOURCE_UID,
-                    js.getSourceUid());
-            proto.end(jsToken);
-        }
-
-        final int n = mObservers.size();
-        for (int userIdx = 0; userIdx < n; userIdx++) {
-            final long oToken =
-                    proto.start(StateControllerProto.ContentObserverController.OBSERVERS);
-            final int userId = mObservers.keyAt(userIdx);
-
-            proto.write(StateControllerProto.ContentObserverController.Observer.USER_ID, userId);
-
-            ArrayMap<JobInfo.TriggerContentUri, ObserverInstance> observersOfUser =
-                    mObservers.get(userId);
-            int numbOfObserversPerUser = observersOfUser.size();
-            for (int observerIdx = 0 ; observerIdx < numbOfObserversPerUser; observerIdx++) {
-                ObserverInstance obs = observersOfUser.valueAt(observerIdx);
-                int m = obs.mJobs.size();
-                boolean shouldDump = false;
-                for (int j = 0; j < m; j++) {
-                    JobInstance inst = obs.mJobs.valueAt(j);
-                    if (predicate.test(inst.mJobStatus)) {
-                        shouldDump = true;
-                        break;
-                    }
-                }
-                if (!shouldDump) {
-                    continue;
-                }
-                final long tToken = proto.start(
-                        StateControllerProto.ContentObserverController.Observer.TRIGGERS);
-
-                JobInfo.TriggerContentUri trigger = observersOfUser.keyAt(observerIdx);
-                Uri u = trigger.getUri();
-                if (u != null) {
-                    proto.write(TriggerContentData.URI, u.toString());
-                }
-                proto.write(TriggerContentData.FLAGS, trigger.getFlags());
-
-                for (int j = 0; j < m; j++) {
-                    final long jToken = proto.start(TriggerContentData.JOBS);
-                    JobInstance inst = obs.mJobs.valueAt(j);
-
-                    inst.mJobStatus.writeToShortProto(proto, TriggerContentData.JobInstance.INFO);
-                    proto.write(TriggerContentData.JobInstance.SOURCE_UID,
-                            inst.mJobStatus.getSourceUid());
-
-                    if (inst.mChangedAuthorities == null) {
-                        proto.end(jToken);
-                        continue;
-                    }
-                    if (inst.mTriggerPending) {
-                        proto.write(TriggerContentData.JobInstance.TRIGGER_CONTENT_UPDATE_DELAY_MS,
-                                inst.mJobStatus.getTriggerContentUpdateDelay());
-                        proto.write(TriggerContentData.JobInstance.TRIGGER_CONTENT_MAX_DELAY_MS,
-                                inst.mJobStatus.getTriggerContentMaxDelay());
-                    }
-                    for (int k = 0; k < inst.mChangedAuthorities.size(); k++) {
-                        proto.write(TriggerContentData.JobInstance.CHANGED_AUTHORITIES,
-                                inst.mChangedAuthorities.valueAt(k));
-                    }
-                    if (inst.mChangedUris != null) {
-                        for (int k = 0; k < inst.mChangedUris.size(); k++) {
-                            u = inst.mChangedUris.valueAt(k);
-                            if (u != null) {
-                                proto.write(TriggerContentData.JobInstance.CHANGED_URIS,
-                                        u.toString());
-                            }
-                        }
-                    }
-
-                    proto.end(jToken);
-                }
-
-                proto.end(tToken);
-            }
-
-            proto.end(oToken);
-        }
-
-        proto.end(mToken);
-        proto.end(token);
-    }
-}
diff --git a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
deleted file mode 100644
index 127a5c8..0000000
--- a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ /dev/null
@@ -1,314 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job.controllers;
-
-import android.app.job.JobInfo;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.UserHandle;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseBooleanArray;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.DeviceIdleController;
-import com.android.server.LocalServices;
-import com.android.server.job.JobSchedulerService;
-import com.android.server.job.StateControllerProto;
-import com.android.server.job.StateControllerProto.DeviceIdleJobsController.TrackedJob;
-
-import java.util.Arrays;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-/**
- * When device is dozing, set constraint for all jobs, except whitelisted apps, as not satisfied.
- * When device is not dozing, set constraint for all jobs as satisfied.
- */
-public final class DeviceIdleJobsController extends StateController {
-    private static final String TAG = "JobScheduler.DeviceIdle";
-    private static final boolean DEBUG = JobSchedulerService.DEBUG
-            || Log.isLoggable(TAG, Log.DEBUG);
-
-    private static final long BACKGROUND_JOBS_DELAY = 3000;
-
-    static final int PROCESS_BACKGROUND_JOBS = 1;
-
-    /**
-     * These are jobs added with a special flag to indicate that they should be exempted from doze
-     * when the app is temp whitelisted or in the foreground.
-     */
-    private final ArraySet<JobStatus> mAllowInIdleJobs;
-    private final SparseBooleanArray mForegroundUids;
-    private final DeviceIdleUpdateFunctor mDeviceIdleUpdateFunctor;
-    private final DeviceIdleJobsDelayHandler mHandler;
-    private final PowerManager mPowerManager;
-    private final DeviceIdleController.LocalService mLocalDeviceIdleController;
-
-    /**
-     * True when in device idle mode, so we don't want to schedule any jobs.
-     */
-    private boolean mDeviceIdleMode;
-    private int[] mDeviceIdleWhitelistAppIds;
-    private int[] mPowerSaveTempWhitelistAppIds;
-
-    // onReceive
-    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            switch (intent.getAction()) {
-                case PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED:
-                case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
-                    updateIdleMode(mPowerManager != null && (mPowerManager.isDeviceIdleMode()
-                            || mPowerManager.isLightDeviceIdleMode()));
-                    break;
-                case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
-                    synchronized (mLock) {
-                        mDeviceIdleWhitelistAppIds =
-                                mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds();
-                        if (DEBUG) {
-                            Slog.d(TAG, "Got whitelist "
-                                    + Arrays.toString(mDeviceIdleWhitelistAppIds));
-                        }
-                    }
-                    break;
-                case PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED:
-                    synchronized (mLock) {
-                        mPowerSaveTempWhitelistAppIds =
-                                mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds();
-                        if (DEBUG) {
-                            Slog.d(TAG, "Got temp whitelist "
-                                    + Arrays.toString(mPowerSaveTempWhitelistAppIds));
-                        }
-                        boolean changed = false;
-                        for (int i = 0; i < mAllowInIdleJobs.size(); i++) {
-                            changed |= updateTaskStateLocked(mAllowInIdleJobs.valueAt(i));
-                        }
-                        if (changed) {
-                            mStateChangedListener.onControllerStateChanged();
-                        }
-                    }
-                    break;
-            }
-        }
-    };
-
-    public DeviceIdleJobsController(JobSchedulerService service) {
-        super(service);
-
-        mHandler = new DeviceIdleJobsDelayHandler(mContext.getMainLooper());
-        // Register for device idle mode changes
-        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-        mLocalDeviceIdleController =
-                LocalServices.getService(DeviceIdleController.LocalService.class);
-        mDeviceIdleWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds();
-        mPowerSaveTempWhitelistAppIds =
-                mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds();
-        mDeviceIdleUpdateFunctor = new DeviceIdleUpdateFunctor();
-        mAllowInIdleJobs = new ArraySet<>();
-        mForegroundUids = new SparseBooleanArray();
-        final IntentFilter filter = new IntentFilter();
-        filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
-        filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
-        filter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
-        filter.addAction(PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED);
-        mContext.registerReceiverAsUser(
-                mBroadcastReceiver, UserHandle.ALL, filter, null, null);
-    }
-
-    void updateIdleMode(boolean enabled) {
-        boolean changed = false;
-        synchronized (mLock) {
-            if (mDeviceIdleMode != enabled) {
-                changed = true;
-            }
-            mDeviceIdleMode = enabled;
-            if (DEBUG) Slog.d(TAG, "mDeviceIdleMode=" + mDeviceIdleMode);
-            if (enabled) {
-                mHandler.removeMessages(PROCESS_BACKGROUND_JOBS);
-                mService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
-            } else {
-                // When coming out of doze, process all foreground uids immediately, while others
-                // will be processed after a delay of 3 seconds.
-                for (int i = 0; i < mForegroundUids.size(); i++) {
-                    if (mForegroundUids.valueAt(i)) {
-                        mService.getJobStore().forEachJobForSourceUid(
-                                mForegroundUids.keyAt(i), mDeviceIdleUpdateFunctor);
-                    }
-                }
-                mHandler.sendEmptyMessageDelayed(PROCESS_BACKGROUND_JOBS, BACKGROUND_JOBS_DELAY);
-            }
-        }
-        // Inform the job scheduler service about idle mode changes
-        if (changed) {
-            mStateChangedListener.onDeviceIdleStateChanged(enabled);
-        }
-    }
-
-    /**
-     *  Called by jobscheduler service to report uid state changes between active and idle
-     */
-    public void setUidActiveLocked(int uid, boolean active) {
-        final boolean changed = (active != mForegroundUids.get(uid));
-        if (!changed) {
-            return;
-        }
-        if (DEBUG) {
-            Slog.d(TAG, "uid " + uid + " going " + (active ? "active" : "inactive"));
-        }
-        mForegroundUids.put(uid, active);
-        mDeviceIdleUpdateFunctor.mChanged = false;
-        mService.getJobStore().forEachJobForSourceUid(uid, mDeviceIdleUpdateFunctor);
-        if (mDeviceIdleUpdateFunctor.mChanged) {
-            mStateChangedListener.onControllerStateChanged();
-        }
-    }
-
-    /**
-     * Checks if the given job's scheduling app id exists in the device idle user whitelist.
-     */
-    boolean isWhitelistedLocked(JobStatus job) {
-        return Arrays.binarySearch(mDeviceIdleWhitelistAppIds,
-                UserHandle.getAppId(job.getSourceUid())) >= 0;
-    }
-
-    /**
-     * Checks if the given job's scheduling app id exists in the device idle temp whitelist.
-     */
-    boolean isTempWhitelistedLocked(JobStatus job) {
-        return ArrayUtils.contains(mPowerSaveTempWhitelistAppIds,
-                UserHandle.getAppId(job.getSourceUid()));
-    }
-
-    private boolean updateTaskStateLocked(JobStatus task) {
-        final boolean allowInIdle = ((task.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0)
-                && (mForegroundUids.get(task.getSourceUid()) || isTempWhitelistedLocked(task));
-        final boolean whitelisted = isWhitelistedLocked(task);
-        final boolean enableTask = !mDeviceIdleMode || whitelisted || allowInIdle;
-        return task.setDeviceNotDozingConstraintSatisfied(enableTask, whitelisted);
-    }
-
-    @Override
-    public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
-        if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
-            mAllowInIdleJobs.add(jobStatus);
-        }
-        updateTaskStateLocked(jobStatus);
-    }
-
-    @Override
-    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
-            boolean forUpdate) {
-        if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
-            mAllowInIdleJobs.remove(jobStatus);
-        }
-    }
-
-    @Override
-    public void dumpControllerStateLocked(final IndentingPrintWriter pw,
-            final Predicate<JobStatus> predicate) {
-        pw.println("Idle mode: " + mDeviceIdleMode);
-        pw.println();
-
-        mService.getJobStore().forEachJob(predicate, (jobStatus) -> {
-            pw.print("#");
-            jobStatus.printUniqueId(pw);
-            pw.print(" from ");
-            UserHandle.formatUid(pw, jobStatus.getSourceUid());
-            pw.print(": ");
-            pw.print(jobStatus.getSourcePackageName());
-            pw.print((jobStatus.satisfiedConstraints
-                    & JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0
-                            ? " RUNNABLE" : " WAITING");
-            if (jobStatus.dozeWhitelisted) {
-                pw.print(" WHITELISTED");
-            }
-            if (mAllowInIdleJobs.contains(jobStatus)) {
-                pw.print(" ALLOWED_IN_DOZE");
-            }
-            pw.println();
-        });
-    }
-
-    @Override
-    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
-            Predicate<JobStatus> predicate) {
-        final long token = proto.start(fieldId);
-        final long mToken = proto.start(StateControllerProto.DEVICE_IDLE);
-
-        proto.write(StateControllerProto.DeviceIdleJobsController.IS_DEVICE_IDLE_MODE,
-                mDeviceIdleMode);
-        mService.getJobStore().forEachJob(predicate, (jobStatus) -> {
-            final long jsToken =
-                    proto.start(StateControllerProto.DeviceIdleJobsController.TRACKED_JOBS);
-
-            jobStatus.writeToShortProto(proto, TrackedJob.INFO);
-            proto.write(TrackedJob.SOURCE_UID, jobStatus.getSourceUid());
-            proto.write(TrackedJob.SOURCE_PACKAGE_NAME, jobStatus.getSourcePackageName());
-            proto.write(TrackedJob.ARE_CONSTRAINTS_SATISFIED,
-                    (jobStatus.satisfiedConstraints &
-                        JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0);
-            proto.write(TrackedJob.IS_DOZE_WHITELISTED, jobStatus.dozeWhitelisted);
-            proto.write(TrackedJob.IS_ALLOWED_IN_DOZE, mAllowInIdleJobs.contains(jobStatus));
-
-            proto.end(jsToken);
-        });
-
-        proto.end(mToken);
-        proto.end(token);
-    }
-
-    final class DeviceIdleUpdateFunctor implements Consumer<JobStatus> {
-        boolean mChanged;
-
-        @Override
-        public void accept(JobStatus jobStatus) {
-            mChanged |= updateTaskStateLocked(jobStatus);
-        }
-    }
-
-    final class DeviceIdleJobsDelayHandler extends Handler {
-        public DeviceIdleJobsDelayHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case PROCESS_BACKGROUND_JOBS:
-                    // Just process all the jobs, the ones in foreground should already be running.
-                    synchronized (mLock) {
-                        mDeviceIdleUpdateFunctor.mChanged = false;
-                        mService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
-                        if (mDeviceIdleUpdateFunctor.mChanged) {
-                            mStateChangedListener.onControllerStateChanged();
-                        }
-                    }
-                    break;
-            }
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/job/controllers/IdleController.java b/services/core/java/com/android/server/job/controllers/IdleController.java
deleted file mode 100644
index e3c311f..0000000
--- a/services/core/java/com/android/server/job/controllers/IdleController.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job.controllers;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.UserHandle;
-import android.util.ArraySet;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.job.JobSchedulerService;
-import com.android.server.job.StateControllerProto;
-import com.android.server.job.controllers.idle.CarIdlenessTracker;
-import com.android.server.job.controllers.idle.DeviceIdlenessTracker;
-import com.android.server.job.controllers.idle.IdlenessListener;
-import com.android.server.job.controllers.idle.IdlenessTracker;
-
-import java.util.function.Predicate;
-
-public final class IdleController extends StateController implements IdlenessListener {
-    private static final String TAG = "JobScheduler.IdleController";
-    // Policy: we decide that we're "idle" if the device has been unused /
-    // screen off or dreaming or wireless charging dock idle for at least this long
-    final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>();
-    IdlenessTracker mIdleTracker;
-
-    public IdleController(JobSchedulerService service) {
-        super(service);
-        initIdleStateTracking(mContext);
-    }
-
-    /**
-     * StateController interface
-     */
-    @Override
-    public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
-        if (taskStatus.hasIdleConstraint()) {
-            mTrackedTasks.add(taskStatus);
-            taskStatus.setTrackingController(JobStatus.TRACKING_IDLE);
-            taskStatus.setIdleConstraintSatisfied(mIdleTracker.isIdle());
-        }
-    }
-
-    @Override
-    public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob,
-            boolean forUpdate) {
-        if (taskStatus.clearTrackingController(JobStatus.TRACKING_IDLE)) {
-            mTrackedTasks.remove(taskStatus);
-        }
-    }
-
-    /**
-     * State-change notifications from the idleness tracker
-     */
-    @Override
-    public void reportNewIdleState(boolean isIdle) {
-        synchronized (mLock) {
-            for (int i = mTrackedTasks.size()-1; i >= 0; i--) {
-                mTrackedTasks.valueAt(i).setIdleConstraintSatisfied(isIdle);
-            }
-        }
-        mStateChangedListener.onControllerStateChanged();
-    }
-
-    /**
-     * Idle state tracking, and messaging with the task manager when
-     * significant state changes occur
-     */
-    private void initIdleStateTracking(Context ctx) {
-        final boolean isCar = mContext.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_AUTOMOTIVE);
-        if (isCar) {
-            mIdleTracker = new CarIdlenessTracker();
-        } else {
-            mIdleTracker = new DeviceIdlenessTracker();
-        }
-        mIdleTracker.startTracking(ctx, this);
-    }
-
-    @Override
-    public void dumpControllerStateLocked(IndentingPrintWriter pw,
-            Predicate<JobStatus> predicate) {
-        pw.println("Currently idle: " + mIdleTracker.isIdle());
-        pw.println("Idleness tracker:"); mIdleTracker.dump(pw);
-        pw.println();
-
-        for (int i = 0; i < mTrackedTasks.size(); i++) {
-            final JobStatus js = mTrackedTasks.valueAt(i);
-            if (!predicate.test(js)) {
-                continue;
-            }
-            pw.print("#");
-            js.printUniqueId(pw);
-            pw.print(" from ");
-            UserHandle.formatUid(pw, js.getSourceUid());
-            pw.println();
-        }
-    }
-
-    @Override
-    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
-            Predicate<JobStatus> predicate) {
-        final long token = proto.start(fieldId);
-        final long mToken = proto.start(StateControllerProto.IDLE);
-
-        proto.write(StateControllerProto.IdleController.IS_IDLE, mIdleTracker.isIdle());
-
-        for (int i = 0; i < mTrackedTasks.size(); i++) {
-            final JobStatus js = mTrackedTasks.valueAt(i);
-            if (!predicate.test(js)) {
-                continue;
-            }
-            final long jsToken = proto.start(StateControllerProto.IdleController.TRACKED_JOBS);
-            js.writeToShortProto(proto, StateControllerProto.IdleController.TrackedJob.INFO);
-            proto.write(StateControllerProto.IdleController.TrackedJob.SOURCE_UID,
-                    js.getSourceUid());
-            proto.end(jsToken);
-        }
-
-        proto.end(mToken);
-        proto.end(token);
-    }
-}
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
deleted file mode 100644
index 6f2b334..0000000
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ /dev/null
@@ -1,1861 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job.controllers;
-
-import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
-
-import android.app.AppGlobals;
-import android.app.IActivityManager;
-import android.app.job.JobInfo;
-import android.app.job.JobWorkItem;
-import android.content.ClipData;
-import android.content.ComponentName;
-import android.net.Network;
-import android.net.Uri;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.text.format.TimeMigrationUtils;
-import android.util.ArraySet;
-import android.util.Pair;
-import android.util.Slog;
-import android.util.StatsLog;
-import android.util.TimeUtils;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.server.LocalServices;
-import com.android.server.job.GrantedUriPermissions;
-import com.android.server.job.JobSchedulerInternal;
-import com.android.server.job.JobSchedulerService;
-import com.android.server.job.JobServerProtoEnums;
-import com.android.server.job.JobStatusDumpProto;
-import com.android.server.job.JobStatusShortInfoProto;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.function.Predicate;
-
-/**
- * Uniquely identifies a job internally.
- * Created from the public {@link android.app.job.JobInfo} object when it lands on the scheduler.
- * Contains current state of the requirements of the job, as well as a function to evaluate
- * whether it's ready to run.
- * This object is shared among the various controllers - hence why the different fields are atomic.
- * This isn't strictly necessary because each controller is only interested in a specific field,
- * and the receivers that are listening for global state change will all run on the main looper,
- * but we don't enforce that so this is safer.
- *
- * Test: atest com.android.server.job.controllers.JobStatusTest
- * @hide
- */
-public final class JobStatus {
-    static final String TAG = "JobSchedulerService";
-    static final boolean DEBUG = JobSchedulerService.DEBUG;
-
-    public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
-    public static final long NO_EARLIEST_RUNTIME = 0L;
-
-    static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0
-    static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE;  // 1 << 2
-    static final int CONSTRAINT_BATTERY_NOT_LOW = JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
-    static final int CONSTRAINT_STORAGE_NOT_LOW = JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW; // 1 << 3
-    static final int CONSTRAINT_TIMING_DELAY = 1<<31;
-    static final int CONSTRAINT_DEADLINE = 1<<30;
-    static final int CONSTRAINT_CONNECTIVITY = 1<<28;
-    static final int CONSTRAINT_CONTENT_TRIGGER = 1<<26;
-    static final int CONSTRAINT_DEVICE_NOT_DOZING = 1 << 25; // Implicit constraint
-    static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24;      // Implicit constraint
-    static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint
-
-    /**
-     * The constraints that we want to log to statsd.
-     *
-     * Constraints that can be inferred from other atoms have been excluded to avoid logging too
-     * much information and to reduce redundancy:
-     *
-     * * CONSTRAINT_CHARGING can be inferred with PluggedStateChanged (Atom #32)
-     * * CONSTRAINT_BATTERY_NOT_LOW can be inferred with BatteryLevelChanged (Atom #30)
-     * * CONSTRAINT_CONNECTIVITY can be partially inferred with ConnectivityStateChanged
-     * (Atom #98) and BatterySaverModeStateChanged (Atom #20).
-     * * CONSTRAINT_DEVICE_NOT_DOZING can be mostly inferred with DeviceIdleModeStateChanged
-     * (Atom #21)
-     * * CONSTRAINT_BACKGROUND_NOT_RESTRICTED can be inferred with BatterySaverModeStateChanged
-     * (Atom #20)
-     */
-    private static final int STATSD_CONSTRAINTS_TO_LOG = CONSTRAINT_CONTENT_TRIGGER
-            | CONSTRAINT_DEADLINE
-            | CONSTRAINT_IDLE
-            | CONSTRAINT_STORAGE_NOT_LOW
-            | CONSTRAINT_TIMING_DELAY
-            | CONSTRAINT_WITHIN_QUOTA;
-
-    // TODO(b/129954980)
-    private static final boolean STATS_LOG_ENABLED = false;
-
-    // Soft override: ignore constraints like time that don't affect API availability
-    public static final int OVERRIDE_SOFT = 1;
-    // Full override: ignore all constraints including API-affecting like connectivity
-    public static final int OVERRIDE_FULL = 2;
-
-    /** If not specified, trigger update delay is 10 seconds. */
-    public static final long DEFAULT_TRIGGER_UPDATE_DELAY = 10*1000;
-
-    /** The minimum possible update delay is 1/2 second. */
-    public static final long MIN_TRIGGER_UPDATE_DELAY = 500;
-
-    /** If not specified, trigger maxumum delay is 2 minutes. */
-    public static final long DEFAULT_TRIGGER_MAX_DELAY = 2*60*1000;
-
-    /** The minimum possible update delay is 1 second. */
-    public static final long MIN_TRIGGER_MAX_DELAY = 1000;
-
-    final JobInfo job;
-    /**
-     * Uid of the package requesting this job.  This can differ from the "source"
-     * uid when the job was scheduled on the app's behalf, such as with the jobs
-     * that underly Sync Manager operation.
-     */
-    final int callingUid;
-    final String batteryName;
-
-    /**
-     * Identity of the app in which the job is hosted.
-     */
-    final String sourcePackageName;
-    final int sourceUserId;
-    final int sourceUid;
-    final String sourceTag;
-
-    final String tag;
-
-    private GrantedUriPermissions uriPerms;
-    private boolean prepared;
-
-    static final boolean DEBUG_PREPARE = true;
-    private Throwable unpreparedPoint = null;
-
-    /**
-     * Earliest point in the future at which this job will be eligible to run. A value of 0
-     * indicates there is no delay constraint. See {@link #hasTimingDelayConstraint()}.
-     */
-    private final long earliestRunTimeElapsedMillis;
-    /**
-     * Latest point in the future at which this job must be run. A value of {@link Long#MAX_VALUE}
-     * indicates there is no deadline constraint. See {@link #hasDeadlineConstraint()}.
-     */
-    private final long latestRunTimeElapsedMillis;
-
-    /**
-     * Valid only for periodic jobs. The original latest point in the future at which this
-     * job was expected to run.
-     */
-    private long mOriginalLatestRunTimeElapsedMillis;
-
-    /** How many times this job has failed, used to compute back-off. */
-    private final int numFailures;
-
-    /**
-     * Current standby heartbeat when this job was scheduled or last ran.  Used to
-     * pin the runnability check regardless of the job's app moving between buckets.
-     */
-    private final long baseHeartbeat;
-
-    /**
-     * Which app standby bucket this job's app is in.  Updated when the app is moved to a
-     * different bucket.
-     */
-    private int standbyBucket;
-
-    /**
-     * Debugging: timestamp if we ever defer this job based on standby bucketing, this
-     * is when we did so.
-     */
-    private long whenStandbyDeferred;
-
-    /** The first time this job was force batched. */
-    private long mFirstForceBatchedTimeElapsed;
-
-    // Constraints.
-    final int requiredConstraints;
-    private final int mRequiredConstraintsOfInterest;
-    int satisfiedConstraints = 0;
-    private int mSatisfiedConstraintsOfInterest = 0;
-
-    // Set to true if doze constraint was satisfied due to app being whitelisted.
-    public boolean dozeWhitelisted;
-
-    // Set to true when the app is "active" per AppStateTracker
-    public boolean uidActive;
-
-    /**
-     * Flag for {@link #trackingControllers}: the battery controller is currently tracking this job.
-     */
-    public static final int TRACKING_BATTERY = 1<<0;
-    /**
-     * Flag for {@link #trackingControllers}: the network connectivity controller is currently
-     * tracking this job.
-     */
-    public static final int TRACKING_CONNECTIVITY = 1<<1;
-    /**
-     * Flag for {@link #trackingControllers}: the content observer controller is currently
-     * tracking this job.
-     */
-    public static final int TRACKING_CONTENT = 1<<2;
-    /**
-     * Flag for {@link #trackingControllers}: the idle controller is currently tracking this job.
-     */
-    public static final int TRACKING_IDLE = 1<<3;
-    /**
-     * Flag for {@link #trackingControllers}: the storage controller is currently tracking this job.
-     */
-    public static final int TRACKING_STORAGE = 1<<4;
-    /**
-     * Flag for {@link #trackingControllers}: the time controller is currently tracking this job.
-     */
-    public static final int TRACKING_TIME = 1<<5;
-    /**
-     * Flag for {@link #trackingControllers}: the quota controller is currently tracking this job.
-     */
-    public static final int TRACKING_QUOTA = 1 << 6;
-
-    /**
-     * Bit mask of controllers that are currently tracking the job.
-     */
-    private int trackingControllers;
-
-    /**
-     * Flag for {@link #mInternalFlags}: this job was scheduled when the app that owns the job
-     * service (not necessarily the caller) was in the foreground and the job has no time
-     * constraints, which makes it exempted from the battery saver job restriction.
-     *
-     * @hide
-     */
-    public static final int INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION = 1 << 0;
-
-    /**
-     * Versatile, persistable flags for a job that's updated within the system server,
-     * as opposed to {@link JobInfo#flags} that's set by callers.
-     */
-    private int mInternalFlags;
-
-    // These are filled in by controllers when preparing for execution.
-    public ArraySet<Uri> changedUris;
-    public ArraySet<String> changedAuthorities;
-    public Network network;
-
-    public int lastEvaluatedPriority;
-
-    // If non-null, this is work that has been enqueued for the job.
-    public ArrayList<JobWorkItem> pendingWork;
-
-    // If non-null, this is work that is currently being executed.
-    public ArrayList<JobWorkItem> executingWork;
-
-    public int nextPendingWorkId = 1;
-
-    // Used by shell commands
-    public int overrideState = 0;
-
-    // When this job was enqueued, for ordering.  (in elapsedRealtimeMillis)
-    public long enqueueTime;
-
-    // Metrics about queue latency.  (in uptimeMillis)
-    public long madePending;
-    public long madeActive;
-
-    /**
-     * Last time a job finished successfully for a periodic job, in the currentTimeMillis time,
-     * for dumpsys.
-     */
-    private long mLastSuccessfulRunTime;
-
-    /**
-     * Last time a job finished unsuccessfully, in the currentTimeMillis time, for dumpsys.
-     */
-    private long mLastFailedRunTime;
-
-    /**
-     * Transient: when a job is inflated from disk before we have a reliable RTC clock time,
-     * we retain the canonical (delay, deadline) scheduling tuple read out of the persistent
-     * store in UTC so that we can fix up the job's scheduling criteria once we get a good
-     * wall-clock time.  If we have to persist the job again before the clock has been updated,
-     * we record these times again rather than calculating based on the earliest/latest elapsed
-     * time base figures.
-     *
-     * 'first' is the earliest/delay time, and 'second' is the latest/deadline time.
-     */
-    private Pair<Long, Long> mPersistedUtcTimes;
-
-    /**
-     * For use only by ContentObserverController: state it is maintaining about content URIs
-     * being observed.
-     */
-    ContentObserverController.JobInstance contentObserverJobInstance;
-
-    private long mTotalNetworkDownloadBytes = JobInfo.NETWORK_BYTES_UNKNOWN;
-    private long mTotalNetworkUploadBytes = JobInfo.NETWORK_BYTES_UNKNOWN;
-
-    /////// Booleans that track if a job is ready to run. They should be updated whenever dependent
-    /////// states change.
-
-    /**
-     * The deadline for the job has passed. This is only good for non-periodic jobs. A periodic job
-     * should only run if its constraints are satisfied.
-     * Computed as: NOT periodic AND has deadline constraint AND deadline constraint satisfied.
-     */
-    private boolean mReadyDeadlineSatisfied;
-
-    /**
-     * The device isn't Dozing or this job will be in the foreground. This implicit constraint must
-     * be satisfied.
-     */
-    private boolean mReadyNotDozing;
-
-    /**
-     * The job is not restricted from running in the background (due to Battery Saver). This
-     * implicit constraint must be satisfied.
-     */
-    private boolean mReadyNotRestrictedInBg;
-
-    /** The job is within its quota based on its standby bucket. */
-    private boolean mReadyWithinQuota;
-
-    /** Provide a handle to the service that this job will be run on. */
-    public int getServiceToken() {
-        return callingUid;
-    }
-
-    /**
-     * Core constructor for JobStatus instances.  All other ctors funnel down to this one.
-     *
-     * @param job The actual requested parameters for the job
-     * @param callingUid Identity of the app that is scheduling the job.  This may not be the
-     *     app in which the job is implemented; such as with sync jobs.
-     * @param sourcePackageName The package name of the app in which the job will run.
-     * @param sourceUserId The user in which the job will run
-     * @param standbyBucket The standby bucket that the source package is currently assigned to,
-     *     cached here for speed of handling during runnability evaluations (and updated when bucket
-     *     assignments are changed)
-     * @param heartbeat Timestamp of when the job was created, in the standby-related
-     *     timebase.
-     * @param tag A string associated with the job for debugging/logging purposes.
-     * @param numFailures Count of how many times this job has requested a reschedule because
-     *     its work was not yet finished.
-     * @param earliestRunTimeElapsedMillis Milestone: earliest point in time at which the job
-     *     is to be considered runnable
-     * @param latestRunTimeElapsedMillis Milestone: point in time at which the job will be
-     *     considered overdue
-     * @param lastSuccessfulRunTime When did we last run this job to completion?
-     * @param lastFailedRunTime When did we last run this job only to have it stop incomplete?
-     * @param internalFlags Non-API property flags about this job
-     */
-    private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
-            int sourceUserId, int standbyBucket, long heartbeat, String tag, int numFailures,
-            long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
-            long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags) {
-        this.job = job;
-        this.callingUid = callingUid;
-        this.standbyBucket = standbyBucket;
-        this.baseHeartbeat = heartbeat;
-
-        int tempSourceUid = -1;
-        if (sourceUserId != -1 && sourcePackageName != null) {
-            try {
-                tempSourceUid = AppGlobals.getPackageManager().getPackageUid(sourcePackageName, 0,
-                        sourceUserId);
-            } catch (RemoteException ex) {
-                // Can't happen, PackageManager runs in the same process.
-            }
-        }
-        if (tempSourceUid == -1) {
-            this.sourceUid = callingUid;
-            this.sourceUserId = UserHandle.getUserId(callingUid);
-            this.sourcePackageName = job.getService().getPackageName();
-            this.sourceTag = null;
-        } else {
-            this.sourceUid = tempSourceUid;
-            this.sourceUserId = sourceUserId;
-            this.sourcePackageName = sourcePackageName;
-            this.sourceTag = tag;
-        }
-
-        this.batteryName = this.sourceTag != null
-                ? this.sourceTag + ":" + job.getService().getPackageName()
-                : job.getService().flattenToShortString();
-        this.tag = "*job*/" + this.batteryName;
-
-        this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis;
-        this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
-        this.mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
-        this.numFailures = numFailures;
-
-        int requiredConstraints = job.getConstraintFlags();
-        if (job.getRequiredNetwork() != null) {
-            requiredConstraints |= CONSTRAINT_CONNECTIVITY;
-        }
-        if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME) {
-            requiredConstraints |= CONSTRAINT_TIMING_DELAY;
-        }
-        if (latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) {
-            requiredConstraints |= CONSTRAINT_DEADLINE;
-        }
-        if (job.getTriggerContentUris() != null) {
-            requiredConstraints |= CONSTRAINT_CONTENT_TRIGGER;
-        }
-        this.requiredConstraints = requiredConstraints;
-        mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST;
-        mReadyNotDozing = (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
-
-        mLastSuccessfulRunTime = lastSuccessfulRunTime;
-        mLastFailedRunTime = lastFailedRunTime;
-
-        mInternalFlags = internalFlags;
-
-        updateEstimatedNetworkBytesLocked();
-
-        if (job.getRequiredNetwork() != null) {
-            // Later, when we check if a given network satisfies the required
-            // network, we need to know the UID that is requesting it, so push
-            // our source UID into place.
-            job.getRequiredNetwork().networkCapabilities.setSingleUid(this.sourceUid);
-        }
-    }
-
-    /** Copy constructor: used specifically when cloning JobStatus objects for persistence,
-     *   so we preserve RTC window bounds if the source object has them. */
-    public JobStatus(JobStatus jobStatus) {
-        this(jobStatus.getJob(), jobStatus.getUid(),
-                jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(),
-                jobStatus.getStandbyBucket(), jobStatus.getBaseHeartbeat(),
-                jobStatus.getSourceTag(), jobStatus.getNumFailures(),
-                jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(),
-                jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(),
-                jobStatus.getInternalFlags());
-        mPersistedUtcTimes = jobStatus.mPersistedUtcTimes;
-        if (jobStatus.mPersistedUtcTimes != null) {
-            if (DEBUG) {
-                Slog.i(TAG, "Cloning job with persisted run times", new RuntimeException("here"));
-            }
-        }
-    }
-
-    /**
-     * Create a new JobStatus that was loaded from disk. We ignore the provided
-     * {@link android.app.job.JobInfo} time criteria because we can load a persisted periodic job
-     * from the {@link com.android.server.job.JobStore} and still want to respect its
-     * wallclock runtime rather than resetting it on every boot.
-     * We consider a freshly loaded job to no longer be in back-off, and the associated
-     * standby bucket is whatever the OS thinks it should be at this moment.
-     */
-    public JobStatus(JobInfo job, int callingUid, String sourcePkgName, int sourceUserId,
-            int standbyBucket, long baseHeartbeat, String sourceTag,
-            long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
-            long lastSuccessfulRunTime, long lastFailedRunTime,
-            Pair<Long, Long> persistedExecutionTimesUTC,
-            int innerFlags) {
-        this(job, callingUid, sourcePkgName, sourceUserId,
-                standbyBucket, baseHeartbeat,
-                sourceTag, 0,
-                earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
-                lastSuccessfulRunTime, lastFailedRunTime, innerFlags);
-
-        // Only during initial inflation do we record the UTC-timebase execution bounds
-        // read from the persistent store.  If we ever have to recreate the JobStatus on
-        // the fly, it means we're rescheduling the job; and this means that the calculated
-        // elapsed timebase bounds intrinsically become correct.
-        this.mPersistedUtcTimes = persistedExecutionTimesUTC;
-        if (persistedExecutionTimesUTC != null) {
-            if (DEBUG) {
-                Slog.i(TAG, "+ restored job with RTC times because of bad boot clock");
-            }
-        }
-    }
-
-    /** Create a new job to be rescheduled with the provided parameters. */
-    public JobStatus(JobStatus rescheduling, long newBaseHeartbeat,
-            long newEarliestRuntimeElapsedMillis,
-            long newLatestRuntimeElapsedMillis, int backoffAttempt,
-            long lastSuccessfulRunTime, long lastFailedRunTime) {
-        this(rescheduling.job, rescheduling.getUid(),
-                rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(),
-                rescheduling.getStandbyBucket(), newBaseHeartbeat,
-                rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis,
-                newLatestRuntimeElapsedMillis,
-                lastSuccessfulRunTime, lastFailedRunTime, rescheduling.getInternalFlags());
-    }
-
-    /**
-     * Create a newly scheduled job.
-     * @param callingUid Uid of the package that scheduled this job.
-     * @param sourcePkg Package name of the app that will actually run the job.  Null indicates
-     *     that the calling package is the source.
-     * @param sourceUserId User id for whom this job is scheduled. -1 indicates this is same as the
-     *     caller.
-     */
-    public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePkg,
-            int sourceUserId, String tag) {
-        final long elapsedNow = sElapsedRealtimeClock.millis();
-        final long earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis;
-        if (job.isPeriodic()) {
-            // Make sure period is in the interval [min_possible_period, max_possible_period].
-            final long period = Math.max(JobInfo.getMinPeriodMillis(),
-                    Math.min(JobSchedulerService.MAX_ALLOWED_PERIOD_MS, job.getIntervalMillis()));
-            latestRunTimeElapsedMillis = elapsedNow + period;
-            earliestRunTimeElapsedMillis = latestRunTimeElapsedMillis
-                    // Make sure flex is in the interval [min_possible_flex, period].
-                    - Math.max(JobInfo.getMinFlexMillis(), Math.min(period, job.getFlexMillis()));
-        } else {
-            earliestRunTimeElapsedMillis = job.hasEarlyConstraint() ?
-                    elapsedNow + job.getMinLatencyMillis() : NO_EARLIEST_RUNTIME;
-            latestRunTimeElapsedMillis = job.hasLateConstraint() ?
-                    elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME;
-        }
-        String jobPackage = (sourcePkg != null) ? sourcePkg : job.getService().getPackageName();
-
-        int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage,
-                sourceUserId, elapsedNow);
-        JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
-        long currentHeartbeat = js != null
-                ? js.baseHeartbeatForApp(jobPackage, sourceUserId, standbyBucket)
-                : 0;
-        return new JobStatus(job, callingUid, sourcePkg, sourceUserId,
-                standbyBucket, currentHeartbeat, tag, 0,
-                earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
-                0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
-                /*innerFlags=*/ 0);
-    }
-
-    public void enqueueWorkLocked(IActivityManager am, JobWorkItem work) {
-        if (pendingWork == null) {
-            pendingWork = new ArrayList<>();
-        }
-        work.setWorkId(nextPendingWorkId);
-        nextPendingWorkId++;
-        if (work.getIntent() != null
-                && GrantedUriPermissions.checkGrantFlags(work.getIntent().getFlags())) {
-            work.setGrants(GrantedUriPermissions.createFromIntent(am, work.getIntent(), sourceUid,
-                    sourcePackageName, sourceUserId, toShortString()));
-        }
-        pendingWork.add(work);
-        updateEstimatedNetworkBytesLocked();
-    }
-
-    public JobWorkItem dequeueWorkLocked() {
-        if (pendingWork != null && pendingWork.size() > 0) {
-            JobWorkItem work = pendingWork.remove(0);
-            if (work != null) {
-                if (executingWork == null) {
-                    executingWork = new ArrayList<>();
-                }
-                executingWork.add(work);
-                work.bumpDeliveryCount();
-            }
-            updateEstimatedNetworkBytesLocked();
-            return work;
-        }
-        return null;
-    }
-
-    public boolean hasWorkLocked() {
-        return (pendingWork != null && pendingWork.size() > 0) || hasExecutingWorkLocked();
-    }
-
-    public boolean hasExecutingWorkLocked() {
-        return executingWork != null && executingWork.size() > 0;
-    }
-
-    private static void ungrantWorkItem(IActivityManager am, JobWorkItem work) {
-        if (work.getGrants() != null) {
-            ((GrantedUriPermissions)work.getGrants()).revoke(am);
-        }
-    }
-
-    public boolean completeWorkLocked(IActivityManager am, int workId) {
-        if (executingWork != null) {
-            final int N = executingWork.size();
-            for (int i = 0; i < N; i++) {
-                JobWorkItem work = executingWork.get(i);
-                if (work.getWorkId() == workId) {
-                    executingWork.remove(i);
-                    ungrantWorkItem(am, work);
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    private static void ungrantWorkList(IActivityManager am, ArrayList<JobWorkItem> list) {
-        if (list != null) {
-            final int N = list.size();
-            for (int i = 0; i < N; i++) {
-                ungrantWorkItem(am, list.get(i));
-            }
-        }
-    }
-
-    public void stopTrackingJobLocked(IActivityManager am, JobStatus incomingJob) {
-        if (incomingJob != null) {
-            // We are replacing with a new job -- transfer the work!  We do any executing
-            // work first, since that was originally at the front of the pending work.
-            if (executingWork != null && executingWork.size() > 0) {
-                incomingJob.pendingWork = executingWork;
-            }
-            if (incomingJob.pendingWork == null) {
-                incomingJob.pendingWork = pendingWork;
-            } else if (pendingWork != null && pendingWork.size() > 0) {
-                incomingJob.pendingWork.addAll(pendingWork);
-            }
-            pendingWork = null;
-            executingWork = null;
-            incomingJob.nextPendingWorkId = nextPendingWorkId;
-            incomingJob.updateEstimatedNetworkBytesLocked();
-        } else {
-            // We are completely stopping the job...  need to clean up work.
-            ungrantWorkList(am, pendingWork);
-            pendingWork = null;
-            ungrantWorkList(am, executingWork);
-            executingWork = null;
-        }
-        updateEstimatedNetworkBytesLocked();
-    }
-
-    public void prepareLocked(IActivityManager am) {
-        if (prepared) {
-            Slog.wtf(TAG, "Already prepared: " + this);
-            return;
-        }
-        prepared = true;
-        if (DEBUG_PREPARE) {
-            unpreparedPoint = null;
-        }
-        final ClipData clip = job.getClipData();
-        if (clip != null) {
-            uriPerms = GrantedUriPermissions.createFromClip(am, clip, sourceUid, sourcePackageName,
-                    sourceUserId, job.getClipGrantFlags(), toShortString());
-        }
-    }
-
-    public void unprepareLocked(IActivityManager am) {
-        if (!prepared) {
-            Slog.wtf(TAG, "Hasn't been prepared: " + this);
-            if (DEBUG_PREPARE && unpreparedPoint != null) {
-                Slog.e(TAG, "Was already unprepared at ", unpreparedPoint);
-            }
-            return;
-        }
-        prepared = false;
-        if (DEBUG_PREPARE) {
-            unpreparedPoint = new Throwable().fillInStackTrace();
-        }
-        if (uriPerms != null) {
-            uriPerms.revoke(am);
-            uriPerms = null;
-        }
-    }
-
-    public boolean isPreparedLocked() {
-        return prepared;
-    }
-
-    public JobInfo getJob() {
-        return job;
-    }
-
-    public int getJobId() {
-        return job.getId();
-    }
-
-    public void printUniqueId(PrintWriter pw) {
-        UserHandle.formatUid(pw, callingUid);
-        pw.print("/");
-        pw.print(job.getId());
-    }
-
-    public int getNumFailures() {
-        return numFailures;
-    }
-
-    public ComponentName getServiceComponent() {
-        return job.getService();
-    }
-
-    public String getSourcePackageName() {
-        return sourcePackageName;
-    }
-
-    public int getSourceUid() {
-        return sourceUid;
-    }
-
-    public int getSourceUserId() {
-        return sourceUserId;
-    }
-
-    public int getUserId() {
-        return UserHandle.getUserId(callingUid);
-    }
-
-    public int getStandbyBucket() {
-        return standbyBucket;
-    }
-
-    public long getBaseHeartbeat() {
-        return baseHeartbeat;
-    }
-
-    public void setStandbyBucket(int newBucket) {
-        standbyBucket = newBucket;
-    }
-
-    // Called only by the standby monitoring code
-    public long getWhenStandbyDeferred() {
-        return whenStandbyDeferred;
-    }
-
-    // Called only by the standby monitoring code
-    public void setWhenStandbyDeferred(long now) {
-        whenStandbyDeferred = now;
-    }
-
-    /**
-     * Returns the first time this job was force batched, in the elapsed realtime timebase. Will be
-     * 0 if this job was never force batched.
-     */
-    public long getFirstForceBatchedTimeElapsed() {
-        return mFirstForceBatchedTimeElapsed;
-    }
-
-    public void setFirstForceBatchedTimeElapsed(long now) {
-        mFirstForceBatchedTimeElapsed = now;
-    }
-
-    public String getSourceTag() {
-        return sourceTag;
-    }
-
-    public int getUid() {
-        return callingUid;
-    }
-
-    public String getBatteryName() {
-        return batteryName;
-    }
-
-    public String getTag() {
-        return tag;
-    }
-
-    public int getPriority() {
-        return job.getPriority();
-    }
-
-    public int getFlags() {
-        return job.getFlags();
-    }
-
-    public int getInternalFlags() {
-        return mInternalFlags;
-    }
-
-    public void addInternalFlags(int flags) {
-        mInternalFlags |= flags;
-    }
-
-    public int getSatisfiedConstraintFlags() {
-        return satisfiedConstraints;
-    }
-
-    public void maybeAddForegroundExemption(Predicate<Integer> uidForegroundChecker) {
-        // Jobs with time constraints shouldn't be exempted.
-        if (job.hasEarlyConstraint() || job.hasLateConstraint()) {
-            return;
-        }
-        // Already exempted, skip the foreground check.
-        if ((mInternalFlags & INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0) {
-            return;
-        }
-        if (uidForegroundChecker.test(getSourceUid())) {
-            addInternalFlags(INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION);
-        }
-    }
-
-    private void updateEstimatedNetworkBytesLocked() {
-        mTotalNetworkDownloadBytes = job.getEstimatedNetworkDownloadBytes();
-        mTotalNetworkUploadBytes = job.getEstimatedNetworkUploadBytes();
-
-        if (pendingWork != null) {
-            for (int i = 0; i < pendingWork.size(); i++) {
-                if (mTotalNetworkDownloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
-                    // If any component of the job has unknown usage, we don't have a
-                    // complete picture of what data will be used, and we have to treat the
-                    // entire up/download as unknown.
-                    long downloadBytes = pendingWork.get(i).getEstimatedNetworkDownloadBytes();
-                    if (downloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
-                        mTotalNetworkDownloadBytes += downloadBytes;
-                    }
-                }
-                if (mTotalNetworkUploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
-                    // If any component of the job has unknown usage, we don't have a
-                    // complete picture of what data will be used, and we have to treat the
-                    // entire up/download as unknown.
-                    long uploadBytes = pendingWork.get(i).getEstimatedNetworkUploadBytes();
-                    if (uploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
-                        mTotalNetworkUploadBytes += uploadBytes;
-                    }
-                }
-            }
-        }
-    }
-
-    public long getEstimatedNetworkDownloadBytes() {
-        return mTotalNetworkDownloadBytes;
-    }
-
-    public long getEstimatedNetworkUploadBytes() {
-        return mTotalNetworkUploadBytes;
-    }
-
-    /** Does this job have any sort of networking constraint? */
-    public boolean hasConnectivityConstraint() {
-        return (requiredConstraints&CONSTRAINT_CONNECTIVITY) != 0;
-    }
-
-    public boolean hasChargingConstraint() {
-        return (requiredConstraints&CONSTRAINT_CHARGING) != 0;
-    }
-
-    public boolean hasBatteryNotLowConstraint() {
-        return (requiredConstraints&CONSTRAINT_BATTERY_NOT_LOW) != 0;
-    }
-
-    public boolean hasPowerConstraint() {
-        return (requiredConstraints&(CONSTRAINT_CHARGING|CONSTRAINT_BATTERY_NOT_LOW)) != 0;
-    }
-
-    public boolean hasStorageNotLowConstraint() {
-        return (requiredConstraints&CONSTRAINT_STORAGE_NOT_LOW) != 0;
-    }
-
-    public boolean hasTimingDelayConstraint() {
-        return (requiredConstraints&CONSTRAINT_TIMING_DELAY) != 0;
-    }
-
-    public boolean hasDeadlineConstraint() {
-        return (requiredConstraints&CONSTRAINT_DEADLINE) != 0;
-    }
-
-    public boolean hasIdleConstraint() {
-        return (requiredConstraints&CONSTRAINT_IDLE) != 0;
-    }
-
-    public boolean hasContentTriggerConstraint() {
-        return (requiredConstraints&CONSTRAINT_CONTENT_TRIGGER) != 0;
-    }
-
-    public long getTriggerContentUpdateDelay() {
-        long time = job.getTriggerContentUpdateDelay();
-        if (time < 0) {
-            return DEFAULT_TRIGGER_UPDATE_DELAY;
-        }
-        return Math.max(time, MIN_TRIGGER_UPDATE_DELAY);
-    }
-
-    public long getTriggerContentMaxDelay() {
-        long time = job.getTriggerContentMaxDelay();
-        if (time < 0) {
-            return DEFAULT_TRIGGER_MAX_DELAY;
-        }
-        return Math.max(time, MIN_TRIGGER_MAX_DELAY);
-    }
-
-    public boolean isPersisted() {
-        return job.isPersisted();
-    }
-
-    public long getEarliestRunTime() {
-        return earliestRunTimeElapsedMillis;
-    }
-
-    public long getLatestRunTimeElapsed() {
-        return latestRunTimeElapsedMillis;
-    }
-
-    public long getOriginalLatestRunTimeElapsed() {
-        return mOriginalLatestRunTimeElapsedMillis;
-    }
-
-    public void setOriginalLatestRunTimeElapsed(long latestRunTimeElapsed) {
-        mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsed;
-    }
-
-    /**
-     * Return the fractional position of "now" within the "run time" window of
-     * this job.
-     * <p>
-     * For example, if the earliest run time was 10 minutes ago, and the latest
-     * run time is 30 minutes from now, this would return 0.25.
-     * <p>
-     * If the job has no window defined, returns 1. When only an earliest or
-     * latest time is defined, it's treated as an infinitely small window at
-     * that time.
-     */
-    public float getFractionRunTime() {
-        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
-        if (earliestRunTimeElapsedMillis == 0 && latestRunTimeElapsedMillis == Long.MAX_VALUE) {
-            return 1;
-        } else if (earliestRunTimeElapsedMillis == 0) {
-            return now >= latestRunTimeElapsedMillis ? 1 : 0;
-        } else if (latestRunTimeElapsedMillis == Long.MAX_VALUE) {
-            return now >= earliestRunTimeElapsedMillis ? 1 : 0;
-        } else {
-            if (now <= earliestRunTimeElapsedMillis) {
-                return 0;
-            } else if (now >= latestRunTimeElapsedMillis) {
-                return 1;
-            } else {
-                return (float) (now - earliestRunTimeElapsedMillis)
-                        / (float) (latestRunTimeElapsedMillis - earliestRunTimeElapsedMillis);
-            }
-        }
-    }
-
-    public Pair<Long, Long> getPersistedUtcTimes() {
-        return mPersistedUtcTimes;
-    }
-
-    public void clearPersistedUtcTimes() {
-        mPersistedUtcTimes = null;
-    }
-
-    /** @return true if the constraint was changed, false otherwise. */
-    boolean setChargingConstraintSatisfied(boolean state) {
-        return setConstraintSatisfied(CONSTRAINT_CHARGING, state);
-    }
-
-    /** @return true if the constraint was changed, false otherwise. */
-    boolean setBatteryNotLowConstraintSatisfied(boolean state) {
-        return setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW, state);
-    }
-
-    /** @return true if the constraint was changed, false otherwise. */
-    boolean setStorageNotLowConstraintSatisfied(boolean state) {
-        return setConstraintSatisfied(CONSTRAINT_STORAGE_NOT_LOW, state);
-    }
-
-    /** @return true if the constraint was changed, false otherwise. */
-    boolean setTimingDelayConstraintSatisfied(boolean state) {
-        return setConstraintSatisfied(CONSTRAINT_TIMING_DELAY, state);
-    }
-
-    /** @return true if the constraint was changed, false otherwise. */
-    boolean setDeadlineConstraintSatisfied(boolean state) {
-        if (setConstraintSatisfied(CONSTRAINT_DEADLINE, state)) {
-            // The constraint was changed. Update the ready flag.
-            mReadyDeadlineSatisfied = !job.isPeriodic() && hasDeadlineConstraint() && state;
-            return true;
-        }
-        return false;
-    }
-
-    /** @return true if the constraint was changed, false otherwise. */
-    boolean setIdleConstraintSatisfied(boolean state) {
-        return setConstraintSatisfied(CONSTRAINT_IDLE, state);
-    }
-
-    /** @return true if the constraint was changed, false otherwise. */
-    boolean setConnectivityConstraintSatisfied(boolean state) {
-        return setConstraintSatisfied(CONSTRAINT_CONNECTIVITY, state);
-    }
-
-    /** @return true if the constraint was changed, false otherwise. */
-    boolean setContentTriggerConstraintSatisfied(boolean state) {
-        return setConstraintSatisfied(CONSTRAINT_CONTENT_TRIGGER, state);
-    }
-
-    /** @return true if the constraint was changed, false otherwise. */
-    boolean setDeviceNotDozingConstraintSatisfied(boolean state, boolean whitelisted) {
-        dozeWhitelisted = whitelisted;
-        if (setConstraintSatisfied(CONSTRAINT_DEVICE_NOT_DOZING, state)) {
-            // The constraint was changed. Update the ready flag.
-            mReadyNotDozing = state || (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
-            return true;
-        }
-        return false;
-    }
-
-    /** @return true if the constraint was changed, false otherwise. */
-    boolean setBackgroundNotRestrictedConstraintSatisfied(boolean state) {
-        if (setConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED, state)) {
-            // The constraint was changed. Update the ready flag.
-            mReadyNotRestrictedInBg = state;
-            return true;
-        }
-        return false;
-    }
-
-    /** @return true if the constraint was changed, false otherwise. */
-    boolean setQuotaConstraintSatisfied(boolean state) {
-        if (setConstraintSatisfied(CONSTRAINT_WITHIN_QUOTA, state)) {
-            // The constraint was changed. Update the ready flag.
-            mReadyWithinQuota = state;
-            return true;
-        }
-        return false;
-    }
-
-    /** @return true if the state was changed, false otherwise. */
-    boolean setUidActive(final boolean newActiveState) {
-        if (newActiveState != uidActive) {
-            uidActive = newActiveState;
-            return true;
-        }
-        return false; /* unchanged */
-    }
-
-    /** @return true if the constraint was changed, false otherwise. */
-    boolean setConstraintSatisfied(int constraint, boolean state) {
-        boolean old = (satisfiedConstraints&constraint) != 0;
-        if (old == state) {
-            return false;
-        }
-        satisfiedConstraints = (satisfiedConstraints&~constraint) | (state ? constraint : 0);
-        mSatisfiedConstraintsOfInterest = satisfiedConstraints & CONSTRAINTS_OF_INTEREST;
-        if (STATS_LOG_ENABLED && (STATSD_CONSTRAINTS_TO_LOG & constraint) != 0) {
-            StatsLog.write_non_chained(StatsLog.SCHEDULED_JOB_CONSTRAINT_CHANGED,
-                    sourceUid, null, getBatteryName(), getProtoConstraint(constraint),
-                    state ? StatsLog.SCHEDULED_JOB_CONSTRAINT_CHANGED__STATE__SATISFIED
-                            : StatsLog.SCHEDULED_JOB_CONSTRAINT_CHANGED__STATE__UNSATISFIED);
-        }
-        return true;
-    }
-
-    boolean isConstraintSatisfied(int constraint) {
-        return (satisfiedConstraints&constraint) != 0;
-    }
-
-    boolean clearTrackingController(int which) {
-        if ((trackingControllers&which) != 0) {
-            trackingControllers &= ~which;
-            return true;
-        }
-        return false;
-    }
-
-    void setTrackingController(int which) {
-        trackingControllers |= which;
-    }
-
-    public long getLastSuccessfulRunTime() {
-        return mLastSuccessfulRunTime;
-    }
-
-    public long getLastFailedRunTime() {
-        return mLastFailedRunTime;
-    }
-
-    /**
-     * @return Whether or not this job is ready to run, based on its requirements.
-     */
-    public boolean isReady() {
-        return isReady(mSatisfiedConstraintsOfInterest);
-    }
-
-    /**
-     * @return Whether or not this job would be ready to run if it had the specified constraint
-     * granted, based on its requirements.
-     */
-    boolean wouldBeReadyWithConstraint(int constraint) {
-        boolean oldValue = false;
-        int satisfied = mSatisfiedConstraintsOfInterest;
-        switch (constraint) {
-            case CONSTRAINT_BACKGROUND_NOT_RESTRICTED:
-                oldValue = mReadyNotRestrictedInBg;
-                mReadyNotRestrictedInBg = true;
-                break;
-            case CONSTRAINT_DEADLINE:
-                oldValue = mReadyDeadlineSatisfied;
-                mReadyDeadlineSatisfied = true;
-                break;
-            case CONSTRAINT_DEVICE_NOT_DOZING:
-                oldValue = mReadyNotDozing;
-                mReadyNotDozing = true;
-                break;
-            case CONSTRAINT_WITHIN_QUOTA:
-                oldValue = mReadyWithinQuota;
-                mReadyWithinQuota = true;
-                break;
-            default:
-                satisfied |= constraint;
-                break;
-        }
-
-        boolean toReturn = isReady(satisfied);
-
-        switch (constraint) {
-            case CONSTRAINT_BACKGROUND_NOT_RESTRICTED:
-                mReadyNotRestrictedInBg = oldValue;
-                break;
-            case CONSTRAINT_DEADLINE:
-                mReadyDeadlineSatisfied = oldValue;
-                break;
-            case CONSTRAINT_DEVICE_NOT_DOZING:
-                mReadyNotDozing = oldValue;
-                break;
-            case CONSTRAINT_WITHIN_QUOTA:
-                mReadyWithinQuota = oldValue;
-                break;
-        }
-        return toReturn;
-    }
-
-    private boolean isReady(int satisfiedConstraints) {
-        // Quota constraints trumps all other constraints.
-        if (!mReadyWithinQuota) {
-            return false;
-        }
-        // Deadline constraint trumps other constraints besides quota (except for periodic jobs
-        // where deadline is an implementation detail. A periodic job should only run if its
-        // constraints are satisfied).
-        // DeviceNotDozing implicit constraint must be satisfied
-        // NotRestrictedInBackground implicit constraint must be satisfied
-        return mReadyNotDozing && mReadyNotRestrictedInBg && (mReadyDeadlineSatisfied
-                || isConstraintsSatisfied(satisfiedConstraints));
-    }
-
-    static final int CONSTRAINTS_OF_INTEREST = CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW
-            | CONSTRAINT_STORAGE_NOT_LOW | CONSTRAINT_TIMING_DELAY | CONSTRAINT_CONNECTIVITY
-            | CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER;
-
-    // Soft override covers all non-"functional" constraints
-    static final int SOFT_OVERRIDE_CONSTRAINTS =
-            CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW
-                    | CONSTRAINT_TIMING_DELAY | CONSTRAINT_IDLE;
-
-    /**
-     * @return Whether the constraints set on this job are satisfied.
-     */
-    public boolean isConstraintsSatisfied() {
-        return isConstraintsSatisfied(mSatisfiedConstraintsOfInterest);
-    }
-
-    private boolean isConstraintsSatisfied(int satisfiedConstraints) {
-        if (overrideState == OVERRIDE_FULL) {
-            // force override: the job is always runnable
-            return true;
-        }
-
-        int sat = satisfiedConstraints;
-        if (overrideState == OVERRIDE_SOFT) {
-            // override: pretend all 'soft' requirements are satisfied
-            sat |= (requiredConstraints & SOFT_OVERRIDE_CONSTRAINTS);
-        }
-
-        return (sat & mRequiredConstraintsOfInterest) == mRequiredConstraintsOfInterest;
-    }
-
-    public boolean matches(int uid, int jobId) {
-        return this.job.getId() == jobId && this.callingUid == uid;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder(128);
-        sb.append("JobStatus{");
-        sb.append(Integer.toHexString(System.identityHashCode(this)));
-        sb.append(" #");
-        UserHandle.formatUid(sb, callingUid);
-        sb.append("/");
-        sb.append(job.getId());
-        sb.append(' ');
-        sb.append(batteryName);
-        sb.append(" u=");
-        sb.append(getUserId());
-        sb.append(" s=");
-        sb.append(getSourceUid());
-        if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME
-                || latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) {
-            long now = sElapsedRealtimeClock.millis();
-            sb.append(" TIME=");
-            formatRunTime(sb, earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME, now);
-            sb.append(":");
-            formatRunTime(sb, latestRunTimeElapsedMillis, NO_LATEST_RUNTIME, now);
-        }
-        if (job.getRequiredNetwork() != null) {
-            sb.append(" NET");
-        }
-        if (job.isRequireCharging()) {
-            sb.append(" CHARGING");
-        }
-        if (job.isRequireBatteryNotLow()) {
-            sb.append(" BATNOTLOW");
-        }
-        if (job.isRequireStorageNotLow()) {
-            sb.append(" STORENOTLOW");
-        }
-        if (job.isRequireDeviceIdle()) {
-            sb.append(" IDLE");
-        }
-        if (job.isPeriodic()) {
-            sb.append(" PERIODIC");
-        }
-        if (job.isPersisted()) {
-            sb.append(" PERSISTED");
-        }
-        if ((satisfiedConstraints&CONSTRAINT_DEVICE_NOT_DOZING) == 0) {
-            sb.append(" WAIT:DEV_NOT_DOZING");
-        }
-        if (job.getTriggerContentUris() != null) {
-            sb.append(" URIS=");
-            sb.append(Arrays.toString(job.getTriggerContentUris()));
-        }
-        if (numFailures != 0) {
-            sb.append(" failures=");
-            sb.append(numFailures);
-        }
-        if (isReady()) {
-            sb.append(" READY");
-        }
-        sb.append("}");
-        return sb.toString();
-    }
-
-    private void formatRunTime(PrintWriter pw, long runtime, long  defaultValue, long now) {
-        if (runtime == defaultValue) {
-            pw.print("none");
-        } else {
-            TimeUtils.formatDuration(runtime - now, pw);
-        }
-    }
-
-    private void formatRunTime(StringBuilder sb, long runtime, long  defaultValue, long now) {
-        if (runtime == defaultValue) {
-            sb.append("none");
-        } else {
-            TimeUtils.formatDuration(runtime - now, sb);
-        }
-    }
-
-    /**
-     * Convenience function to identify a job uniquely without pulling all the data that
-     * {@link #toString()} returns.
-     */
-    public String toShortString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append(Integer.toHexString(System.identityHashCode(this)));
-        sb.append(" #");
-        UserHandle.formatUid(sb, callingUid);
-        sb.append("/");
-        sb.append(job.getId());
-        sb.append(' ');
-        sb.append(batteryName);
-        return sb.toString();
-    }
-
-    /**
-     * Convenience function to identify a job uniquely without pulling all the data that
-     * {@link #toString()} returns.
-     */
-    public String toShortStringExceptUniqueId() {
-        StringBuilder sb = new StringBuilder();
-        sb.append(Integer.toHexString(System.identityHashCode(this)));
-        sb.append(' ');
-        sb.append(batteryName);
-        return sb.toString();
-    }
-
-    /**
-     * Convenience function to dump data that identifies a job uniquely to proto. This is intended
-     * to mimic {@link #toShortString}.
-     */
-    public void writeToShortProto(ProtoOutputStream proto, long fieldId) {
-        final long token = proto.start(fieldId);
-
-        proto.write(JobStatusShortInfoProto.CALLING_UID, callingUid);
-        proto.write(JobStatusShortInfoProto.JOB_ID, job.getId());
-        proto.write(JobStatusShortInfoProto.BATTERY_NAME, batteryName);
-
-        proto.end(token);
-    }
-
-    void dumpConstraints(PrintWriter pw, int constraints) {
-        if ((constraints&CONSTRAINT_CHARGING) != 0) {
-            pw.print(" CHARGING");
-        }
-        if ((constraints& CONSTRAINT_BATTERY_NOT_LOW) != 0) {
-            pw.print(" BATTERY_NOT_LOW");
-        }
-        if ((constraints& CONSTRAINT_STORAGE_NOT_LOW) != 0) {
-            pw.print(" STORAGE_NOT_LOW");
-        }
-        if ((constraints&CONSTRAINT_TIMING_DELAY) != 0) {
-            pw.print(" TIMING_DELAY");
-        }
-        if ((constraints&CONSTRAINT_DEADLINE) != 0) {
-            pw.print(" DEADLINE");
-        }
-        if ((constraints&CONSTRAINT_IDLE) != 0) {
-            pw.print(" IDLE");
-        }
-        if ((constraints&CONSTRAINT_CONNECTIVITY) != 0) {
-            pw.print(" CONNECTIVITY");
-        }
-        if ((constraints&CONSTRAINT_CONTENT_TRIGGER) != 0) {
-            pw.print(" CONTENT_TRIGGER");
-        }
-        if ((constraints&CONSTRAINT_DEVICE_NOT_DOZING) != 0) {
-            pw.print(" DEVICE_NOT_DOZING");
-        }
-        if ((constraints&CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
-            pw.print(" BACKGROUND_NOT_RESTRICTED");
-        }
-        if ((constraints & CONSTRAINT_WITHIN_QUOTA) != 0) {
-            pw.print(" WITHIN_QUOTA");
-        }
-        if (constraints != 0) {
-            pw.print(" [0x");
-            pw.print(Integer.toHexString(constraints));
-            pw.print("]");
-        }
-    }
-
-    /** Returns a {@link JobServerProtoEnums.Constraint} enum value for the given constraint. */
-    private int getProtoConstraint(int constraint) {
-        switch (constraint) {
-            case CONSTRAINT_BACKGROUND_NOT_RESTRICTED:
-                return JobServerProtoEnums.CONSTRAINT_BACKGROUND_NOT_RESTRICTED;
-            case CONSTRAINT_BATTERY_NOT_LOW:
-                return JobServerProtoEnums.CONSTRAINT_BATTERY_NOT_LOW;
-            case CONSTRAINT_CHARGING:
-                return JobServerProtoEnums.CONSTRAINT_CHARGING;
-            case CONSTRAINT_CONNECTIVITY:
-                return JobServerProtoEnums.CONSTRAINT_CONNECTIVITY;
-            case CONSTRAINT_CONTENT_TRIGGER:
-                return JobServerProtoEnums.CONSTRAINT_CONTENT_TRIGGER;
-            case CONSTRAINT_DEADLINE:
-                return JobServerProtoEnums.CONSTRAINT_DEADLINE;
-            case CONSTRAINT_DEVICE_NOT_DOZING:
-                return JobServerProtoEnums.CONSTRAINT_DEVICE_NOT_DOZING;
-            case CONSTRAINT_IDLE:
-                return JobServerProtoEnums.CONSTRAINT_IDLE;
-            case CONSTRAINT_STORAGE_NOT_LOW:
-                return JobServerProtoEnums.CONSTRAINT_STORAGE_NOT_LOW;
-            case CONSTRAINT_TIMING_DELAY:
-                return JobServerProtoEnums.CONSTRAINT_TIMING_DELAY;
-            case CONSTRAINT_WITHIN_QUOTA:
-                return JobServerProtoEnums.CONSTRAINT_WITHIN_QUOTA;
-            default:
-                return JobServerProtoEnums.CONSTRAINT_UNKNOWN;
-        }
-    }
-
-    /** Writes constraints to the given repeating proto field. */
-    void dumpConstraints(ProtoOutputStream proto, long fieldId, int constraints) {
-        if ((constraints & CONSTRAINT_CHARGING) != 0) {
-            proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_CHARGING);
-        }
-        if ((constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0) {
-            proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_BATTERY_NOT_LOW);
-        }
-        if ((constraints & CONSTRAINT_STORAGE_NOT_LOW) != 0) {
-            proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_STORAGE_NOT_LOW);
-        }
-        if ((constraints & CONSTRAINT_TIMING_DELAY) != 0) {
-            proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_TIMING_DELAY);
-        }
-        if ((constraints & CONSTRAINT_DEADLINE) != 0) {
-            proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_DEADLINE);
-        }
-        if ((constraints & CONSTRAINT_IDLE) != 0) {
-            proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_IDLE);
-        }
-        if ((constraints & CONSTRAINT_CONNECTIVITY) != 0) {
-            proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_CONNECTIVITY);
-        }
-        if ((constraints & CONSTRAINT_CONTENT_TRIGGER) != 0) {
-            proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_CONTENT_TRIGGER);
-        }
-        if ((constraints & CONSTRAINT_DEVICE_NOT_DOZING) != 0) {
-            proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_DEVICE_NOT_DOZING);
-        }
-        if ((constraints & CONSTRAINT_WITHIN_QUOTA) != 0) {
-            proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_WITHIN_QUOTA);
-        }
-        if ((constraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
-            proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_BACKGROUND_NOT_RESTRICTED);
-        }
-    }
-
-    private void dumpJobWorkItem(PrintWriter pw, String prefix, JobWorkItem work, int index) {
-        pw.print(prefix); pw.print("  #"); pw.print(index); pw.print(": #");
-        pw.print(work.getWorkId()); pw.print(" "); pw.print(work.getDeliveryCount());
-        pw.print("x "); pw.println(work.getIntent());
-        if (work.getGrants() != null) {
-            pw.print(prefix); pw.println("  URI grants:");
-            ((GrantedUriPermissions)work.getGrants()).dump(pw, prefix + "    ");
-        }
-    }
-
-    private void dumpJobWorkItem(ProtoOutputStream proto, long fieldId, JobWorkItem work) {
-        final long token = proto.start(fieldId);
-
-        proto.write(JobStatusDumpProto.JobWorkItem.WORK_ID, work.getWorkId());
-        proto.write(JobStatusDumpProto.JobWorkItem.DELIVERY_COUNT, work.getDeliveryCount());
-        if (work.getIntent() != null) {
-            work.getIntent().writeToProto(proto, JobStatusDumpProto.JobWorkItem.INTENT);
-        }
-        Object grants = work.getGrants();
-        if (grants != null) {
-            ((GrantedUriPermissions) grants).dump(proto, JobStatusDumpProto.JobWorkItem.URI_GRANTS);
-        }
-
-        proto.end(token);
-    }
-
-    /**
-     * Returns a bucket name based on the normalized bucket indices, not the AppStandby constants.
-     */
-    String getBucketName() {
-        return bucketName(standbyBucket);
-    }
-
-    /**
-     * Returns a bucket name based on the normalized bucket indices, not the AppStandby constants.
-     */
-    static String bucketName(int standbyBucket) {
-        switch (standbyBucket) {
-            case 0: return "ACTIVE";
-            case 1: return "WORKING_SET";
-            case 2: return "FREQUENT";
-            case 3: return "RARE";
-            case 4: return "NEVER";
-            default:
-                return "Unknown: " + standbyBucket;
-        }
-    }
-
-    // Dumpsys infrastructure
-    public void dump(PrintWriter pw, String prefix, boolean full, long elapsedRealtimeMillis) {
-        pw.print(prefix); UserHandle.formatUid(pw, callingUid);
-        pw.print(" tag="); pw.println(tag);
-        pw.print(prefix);
-        pw.print("Source: uid="); UserHandle.formatUid(pw, getSourceUid());
-        pw.print(" user="); pw.print(getSourceUserId());
-        pw.print(" pkg="); pw.println(getSourcePackageName());
-        if (full) {
-            pw.print(prefix); pw.println("JobInfo:");
-            pw.print(prefix); pw.print("  Service: ");
-            pw.println(job.getService().flattenToShortString());
-            if (job.isPeriodic()) {
-                pw.print(prefix); pw.print("  PERIODIC: interval=");
-                TimeUtils.formatDuration(job.getIntervalMillis(), pw);
-                pw.print(" flex="); TimeUtils.formatDuration(job.getFlexMillis(), pw);
-                pw.println();
-            }
-            if (job.isPersisted()) {
-                pw.print(prefix); pw.println("  PERSISTED");
-            }
-            if (job.getPriority() != 0) {
-                pw.print(prefix); pw.print("  Priority: ");
-                pw.println(JobInfo.getPriorityString(job.getPriority()));
-            }
-            if (job.getFlags() != 0) {
-                pw.print(prefix); pw.print("  Flags: ");
-                pw.println(Integer.toHexString(job.getFlags()));
-            }
-            if (getInternalFlags() != 0) {
-                pw.print(prefix); pw.print("  Internal flags: ");
-                pw.print(Integer.toHexString(getInternalFlags()));
-
-                if ((getInternalFlags()&INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0) {
-                    pw.print(" HAS_FOREGROUND_EXEMPTION");
-                }
-                pw.println();
-            }
-            pw.print(prefix); pw.print("  Requires: charging=");
-            pw.print(job.isRequireCharging()); pw.print(" batteryNotLow=");
-            pw.print(job.isRequireBatteryNotLow()); pw.print(" deviceIdle=");
-            pw.println(job.isRequireDeviceIdle());
-            if (job.getTriggerContentUris() != null) {
-                pw.print(prefix); pw.println("  Trigger content URIs:");
-                for (int i = 0; i < job.getTriggerContentUris().length; i++) {
-                    JobInfo.TriggerContentUri trig = job.getTriggerContentUris()[i];
-                    pw.print(prefix); pw.print("    ");
-                    pw.print(Integer.toHexString(trig.getFlags()));
-                    pw.print(' '); pw.println(trig.getUri());
-                }
-                if (job.getTriggerContentUpdateDelay() >= 0) {
-                    pw.print(prefix); pw.print("  Trigger update delay: ");
-                    TimeUtils.formatDuration(job.getTriggerContentUpdateDelay(), pw);
-                    pw.println();
-                }
-                if (job.getTriggerContentMaxDelay() >= 0) {
-                    pw.print(prefix); pw.print("  Trigger max delay: ");
-                    TimeUtils.formatDuration(job.getTriggerContentMaxDelay(), pw);
-                    pw.println();
-                }
-            }
-            if (job.getExtras() != null && !job.getExtras().maybeIsEmpty()) {
-                pw.print(prefix); pw.print("  Extras: ");
-                pw.println(job.getExtras().toShortString());
-            }
-            if (job.getTransientExtras() != null && !job.getTransientExtras().maybeIsEmpty()) {
-                pw.print(prefix); pw.print("  Transient extras: ");
-                pw.println(job.getTransientExtras().toShortString());
-            }
-            if (job.getClipData() != null) {
-                pw.print(prefix); pw.print("  Clip data: ");
-                StringBuilder b = new StringBuilder(128);
-                job.getClipData().toShortString(b);
-                pw.println(b);
-            }
-            if (uriPerms != null) {
-                pw.print(prefix); pw.println("  Granted URI permissions:");
-                uriPerms.dump(pw, prefix + "  ");
-            }
-            if (job.getRequiredNetwork() != null) {
-                pw.print(prefix); pw.print("  Network type: ");
-                pw.println(job.getRequiredNetwork());
-            }
-            if (mTotalNetworkDownloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
-                pw.print(prefix); pw.print("  Network download bytes: ");
-                pw.println(mTotalNetworkDownloadBytes);
-            }
-            if (mTotalNetworkUploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
-                pw.print(prefix); pw.print("  Network upload bytes: ");
-                pw.println(mTotalNetworkUploadBytes);
-            }
-            if (job.getMinLatencyMillis() != 0) {
-                pw.print(prefix); pw.print("  Minimum latency: ");
-                TimeUtils.formatDuration(job.getMinLatencyMillis(), pw);
-                pw.println();
-            }
-            if (job.getMaxExecutionDelayMillis() != 0) {
-                pw.print(prefix); pw.print("  Max execution delay: ");
-                TimeUtils.formatDuration(job.getMaxExecutionDelayMillis(), pw);
-                pw.println();
-            }
-            pw.print(prefix); pw.print("  Backoff: policy="); pw.print(job.getBackoffPolicy());
-            pw.print(" initial="); TimeUtils.formatDuration(job.getInitialBackoffMillis(), pw);
-            pw.println();
-            if (job.hasEarlyConstraint()) {
-                pw.print(prefix); pw.println("  Has early constraint");
-            }
-            if (job.hasLateConstraint()) {
-                pw.print(prefix); pw.println("  Has late constraint");
-            }
-        }
-        pw.print(prefix); pw.print("Required constraints:");
-        dumpConstraints(pw, requiredConstraints);
-        pw.println();
-        if (full) {
-            pw.print(prefix); pw.print("Satisfied constraints:");
-            dumpConstraints(pw, satisfiedConstraints);
-            pw.println();
-            pw.print(prefix); pw.print("Unsatisfied constraints:");
-            dumpConstraints(pw,
-                    ((requiredConstraints | CONSTRAINT_WITHIN_QUOTA) & ~satisfiedConstraints));
-            pw.println();
-            if (dozeWhitelisted) {
-                pw.print(prefix); pw.println("Doze whitelisted: true");
-            }
-            if (uidActive) {
-                pw.print(prefix); pw.println("Uid: active");
-            }
-            if (job.isExemptedFromAppStandby()) {
-                pw.print(prefix); pw.println("Is exempted from app standby");
-            }
-        }
-        if (trackingControllers != 0) {
-            pw.print(prefix); pw.print("Tracking:");
-            if ((trackingControllers&TRACKING_BATTERY) != 0) pw.print(" BATTERY");
-            if ((trackingControllers&TRACKING_CONNECTIVITY) != 0) pw.print(" CONNECTIVITY");
-            if ((trackingControllers&TRACKING_CONTENT) != 0) pw.print(" CONTENT");
-            if ((trackingControllers&TRACKING_IDLE) != 0) pw.print(" IDLE");
-            if ((trackingControllers&TRACKING_STORAGE) != 0) pw.print(" STORAGE");
-            if ((trackingControllers&TRACKING_TIME) != 0) pw.print(" TIME");
-            if ((trackingControllers & TRACKING_QUOTA) != 0) pw.print(" QUOTA");
-            pw.println();
-        }
-
-        pw.print(prefix); pw.println("Implicit constraints:");
-        pw.print(prefix); pw.print("  readyNotDozing: ");
-        pw.println(mReadyNotDozing);
-        pw.print(prefix); pw.print("  readyNotRestrictedInBg: ");
-        pw.println(mReadyNotRestrictedInBg);
-        if (!job.isPeriodic() && hasDeadlineConstraint()) {
-            pw.print(prefix); pw.print("  readyDeadlineSatisfied: ");
-            pw.println(mReadyDeadlineSatisfied);
-        }
-
-        if (changedAuthorities != null) {
-            pw.print(prefix); pw.println("Changed authorities:");
-            for (int i=0; i<changedAuthorities.size(); i++) {
-                pw.print(prefix); pw.print("  "); pw.println(changedAuthorities.valueAt(i));
-            }
-            if (changedUris != null) {
-                pw.print(prefix); pw.println("Changed URIs:");
-                for (int i=0; i<changedUris.size(); i++) {
-                    pw.print(prefix); pw.print("  "); pw.println(changedUris.valueAt(i));
-                }
-            }
-        }
-        if (network != null) {
-            pw.print(prefix); pw.print("Network: "); pw.println(network);
-        }
-        if (pendingWork != null && pendingWork.size() > 0) {
-            pw.print(prefix); pw.println("Pending work:");
-            for (int i = 0; i < pendingWork.size(); i++) {
-                dumpJobWorkItem(pw, prefix, pendingWork.get(i), i);
-            }
-        }
-        if (executingWork != null && executingWork.size() > 0) {
-            pw.print(prefix); pw.println("Executing work:");
-            for (int i = 0; i < executingWork.size(); i++) {
-                dumpJobWorkItem(pw, prefix, executingWork.get(i), i);
-            }
-        }
-        pw.print(prefix); pw.print("Standby bucket: ");
-        pw.println(getBucketName());
-        if (standbyBucket > 0) {
-            pw.print(prefix); pw.print("Base heartbeat: ");
-            pw.println(baseHeartbeat);
-        }
-        if (whenStandbyDeferred != 0) {
-            pw.print(prefix); pw.print("  Deferred since: ");
-            TimeUtils.formatDuration(whenStandbyDeferred, elapsedRealtimeMillis, pw);
-            pw.println();
-        }
-        if (mFirstForceBatchedTimeElapsed != 0) {
-            pw.print(prefix);
-            pw.print("  Time since first force batch attempt: ");
-            TimeUtils.formatDuration(mFirstForceBatchedTimeElapsed, elapsedRealtimeMillis, pw);
-            pw.println();
-        }
-        pw.print(prefix); pw.print("Enqueue time: ");
-        TimeUtils.formatDuration(enqueueTime, elapsedRealtimeMillis, pw);
-        pw.println();
-        pw.print(prefix); pw.print("Run time: earliest=");
-        formatRunTime(pw, earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME, elapsedRealtimeMillis);
-        pw.print(", latest=");
-        formatRunTime(pw, latestRunTimeElapsedMillis, NO_LATEST_RUNTIME, elapsedRealtimeMillis);
-        pw.print(", original latest=");
-        formatRunTime(pw, mOriginalLatestRunTimeElapsedMillis,
-                NO_LATEST_RUNTIME, elapsedRealtimeMillis);
-        pw.println();
-        if (numFailures != 0) {
-            pw.print(prefix); pw.print("Num failures: "); pw.println(numFailures);
-        }
-        if (mLastSuccessfulRunTime != 0) {
-            pw.print(prefix); pw.print("Last successful run: ");
-            pw.println(TimeMigrationUtils.formatMillisWithFixedFormat(mLastSuccessfulRunTime));
-        }
-        if (mLastFailedRunTime != 0) {
-            pw.print(prefix); pw.print("Last failed run: ");
-            pw.println(TimeMigrationUtils.formatMillisWithFixedFormat(mLastFailedRunTime));
-        }
-    }
-
-    public void dump(ProtoOutputStream proto, long fieldId, boolean full, long elapsedRealtimeMillis) {
-        final long token = proto.start(fieldId);
-
-        proto.write(JobStatusDumpProto.CALLING_UID, callingUid);
-        proto.write(JobStatusDumpProto.TAG, tag);
-        proto.write(JobStatusDumpProto.SOURCE_UID, getSourceUid());
-        proto.write(JobStatusDumpProto.SOURCE_USER_ID, getSourceUserId());
-        proto.write(JobStatusDumpProto.SOURCE_PACKAGE_NAME, getSourcePackageName());
-        proto.write(JobStatusDumpProto.INTERNAL_FLAGS, getInternalFlags());
-
-        if (full) {
-            final long jiToken = proto.start(JobStatusDumpProto.JOB_INFO);
-
-            job.getService().writeToProto(proto, JobStatusDumpProto.JobInfo.SERVICE);
-
-            proto.write(JobStatusDumpProto.JobInfo.IS_PERIODIC, job.isPeriodic());
-            proto.write(JobStatusDumpProto.JobInfo.PERIOD_INTERVAL_MS, job.getIntervalMillis());
-            proto.write(JobStatusDumpProto.JobInfo.PERIOD_FLEX_MS, job.getFlexMillis());
-
-            proto.write(JobStatusDumpProto.JobInfo.IS_PERSISTED, job.isPersisted());
-            proto.write(JobStatusDumpProto.JobInfo.PRIORITY, job.getPriority());
-            proto.write(JobStatusDumpProto.JobInfo.FLAGS, job.getFlags());
-
-            proto.write(JobStatusDumpProto.JobInfo.REQUIRES_CHARGING, job.isRequireCharging());
-            proto.write(JobStatusDumpProto.JobInfo.REQUIRES_BATTERY_NOT_LOW, job.isRequireBatteryNotLow());
-            proto.write(JobStatusDumpProto.JobInfo.REQUIRES_DEVICE_IDLE, job.isRequireDeviceIdle());
-
-            if (job.getTriggerContentUris() != null) {
-                for (int i = 0; i < job.getTriggerContentUris().length; i++) {
-                    final long tcuToken = proto.start(JobStatusDumpProto.JobInfo.TRIGGER_CONTENT_URIS);
-                    JobInfo.TriggerContentUri trig = job.getTriggerContentUris()[i];
-
-                    proto.write(JobStatusDumpProto.JobInfo.TriggerContentUri.FLAGS, trig.getFlags());
-                    Uri u = trig.getUri();
-                    if (u != null) {
-                        proto.write(JobStatusDumpProto.JobInfo.TriggerContentUri.URI, u.toString());
-                    }
-
-                    proto.end(tcuToken);
-                }
-                if (job.getTriggerContentUpdateDelay() >= 0) {
-                    proto.write(JobStatusDumpProto.JobInfo.TRIGGER_CONTENT_UPDATE_DELAY_MS,
-                            job.getTriggerContentUpdateDelay());
-                }
-                if (job.getTriggerContentMaxDelay() >= 0) {
-                    proto.write(JobStatusDumpProto.JobInfo.TRIGGER_CONTENT_MAX_DELAY_MS,
-                            job.getTriggerContentMaxDelay());
-                }
-            }
-            if (job.getExtras() != null && !job.getExtras().maybeIsEmpty()) {
-                job.getExtras().writeToProto(proto, JobStatusDumpProto.JobInfo.EXTRAS);
-            }
-            if (job.getTransientExtras() != null && !job.getTransientExtras().maybeIsEmpty()) {
-                job.getTransientExtras().writeToProto(proto, JobStatusDumpProto.JobInfo.TRANSIENT_EXTRAS);
-            }
-            if (job.getClipData() != null) {
-                job.getClipData().writeToProto(proto, JobStatusDumpProto.JobInfo.CLIP_DATA);
-            }
-            if (uriPerms != null) {
-                uriPerms.dump(proto, JobStatusDumpProto.JobInfo.GRANTED_URI_PERMISSIONS);
-            }
-            if (job.getRequiredNetwork() != null) {
-                job.getRequiredNetwork().writeToProto(proto, JobStatusDumpProto.JobInfo.REQUIRED_NETWORK);
-            }
-            if (mTotalNetworkDownloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
-                proto.write(JobStatusDumpProto.JobInfo.TOTAL_NETWORK_DOWNLOAD_BYTES,
-                        mTotalNetworkDownloadBytes);
-            }
-            if (mTotalNetworkUploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
-                proto.write(JobStatusDumpProto.JobInfo.TOTAL_NETWORK_UPLOAD_BYTES,
-                        mTotalNetworkUploadBytes);
-            }
-            proto.write(JobStatusDumpProto.JobInfo.MIN_LATENCY_MS, job.getMinLatencyMillis());
-            proto.write(JobStatusDumpProto.JobInfo.MAX_EXECUTION_DELAY_MS, job.getMaxExecutionDelayMillis());
-
-            final long bpToken = proto.start(JobStatusDumpProto.JobInfo.BACKOFF_POLICY);
-            proto.write(JobStatusDumpProto.JobInfo.Backoff.POLICY, job.getBackoffPolicy());
-            proto.write(JobStatusDumpProto.JobInfo.Backoff.INITIAL_BACKOFF_MS,
-                    job.getInitialBackoffMillis());
-            proto.end(bpToken);
-
-            proto.write(JobStatusDumpProto.JobInfo.HAS_EARLY_CONSTRAINT, job.hasEarlyConstraint());
-            proto.write(JobStatusDumpProto.JobInfo.HAS_LATE_CONSTRAINT, job.hasLateConstraint());
-
-            proto.end(jiToken);
-        }
-
-        dumpConstraints(proto, JobStatusDumpProto.REQUIRED_CONSTRAINTS, requiredConstraints);
-        if (full) {
-            dumpConstraints(proto, JobStatusDumpProto.SATISFIED_CONSTRAINTS, satisfiedConstraints);
-            dumpConstraints(proto, JobStatusDumpProto.UNSATISFIED_CONSTRAINTS,
-                    ((requiredConstraints | CONSTRAINT_WITHIN_QUOTA) & ~satisfiedConstraints));
-            proto.write(JobStatusDumpProto.IS_DOZE_WHITELISTED, dozeWhitelisted);
-            proto.write(JobStatusDumpProto.IS_UID_ACTIVE, uidActive);
-            proto.write(JobStatusDumpProto.IS_EXEMPTED_FROM_APP_STANDBY,
-                    job.isExemptedFromAppStandby());
-        }
-
-        // Tracking controllers
-        if ((trackingControllers&TRACKING_BATTERY) != 0) {
-            proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
-                    JobStatusDumpProto.TRACKING_BATTERY);
-        }
-        if ((trackingControllers&TRACKING_CONNECTIVITY) != 0) {
-            proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
-                    JobStatusDumpProto.TRACKING_CONNECTIVITY);
-        }
-        if ((trackingControllers&TRACKING_CONTENT) != 0) {
-            proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
-                    JobStatusDumpProto.TRACKING_CONTENT);
-        }
-        if ((trackingControllers&TRACKING_IDLE) != 0) {
-            proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
-                    JobStatusDumpProto.TRACKING_IDLE);
-        }
-        if ((trackingControllers&TRACKING_STORAGE) != 0) {
-            proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
-                    JobStatusDumpProto.TRACKING_STORAGE);
-        }
-        if ((trackingControllers&TRACKING_TIME) != 0) {
-            proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
-                    JobStatusDumpProto.TRACKING_TIME);
-        }
-        if ((trackingControllers & TRACKING_QUOTA) != 0) {
-            proto.write(JobStatusDumpProto.TRACKING_CONTROLLERS,
-                    JobStatusDumpProto.TRACKING_QUOTA);
-        }
-
-        // Implicit constraints
-        final long icToken = proto.start(JobStatusDumpProto.IMPLICIT_CONSTRAINTS);
-        proto.write(JobStatusDumpProto.ImplicitConstraints.IS_NOT_DOZING, mReadyNotDozing);
-        proto.write(JobStatusDumpProto.ImplicitConstraints.IS_NOT_RESTRICTED_IN_BG,
-                mReadyNotRestrictedInBg);
-        proto.end(icToken);
-
-        if (changedAuthorities != null) {
-            for (int k = 0; k < changedAuthorities.size(); k++) {
-                proto.write(JobStatusDumpProto.CHANGED_AUTHORITIES, changedAuthorities.valueAt(k));
-            }
-        }
-        if (changedUris != null) {
-            for (int i = 0; i < changedUris.size(); i++) {
-                Uri u = changedUris.valueAt(i);
-                proto.write(JobStatusDumpProto.CHANGED_URIS, u.toString());
-            }
-        }
-
-        if (network != null) {
-            network.writeToProto(proto, JobStatusDumpProto.NETWORK);
-        }
-
-        if (pendingWork != null && pendingWork.size() > 0) {
-            for (int i = 0; i < pendingWork.size(); i++) {
-                dumpJobWorkItem(proto, JobStatusDumpProto.PENDING_WORK, pendingWork.get(i));
-            }
-        }
-        if (executingWork != null && executingWork.size() > 0) {
-            for (int i = 0; i < executingWork.size(); i++) {
-                dumpJobWorkItem(proto, JobStatusDumpProto.EXECUTING_WORK, executingWork.get(i));
-            }
-        }
-
-        proto.write(JobStatusDumpProto.STANDBY_BUCKET, standbyBucket);
-        proto.write(JobStatusDumpProto.ENQUEUE_DURATION_MS, elapsedRealtimeMillis - enqueueTime);
-        proto.write(JobStatusDumpProto.TIME_SINCE_FIRST_DEFERRAL_MS,
-                whenStandbyDeferred == 0 ? 0 : elapsedRealtimeMillis - whenStandbyDeferred);
-        proto.write(JobStatusDumpProto.TIME_SINCE_FIRST_FORCE_BATCH_ATTEMPT_MS,
-                mFirstForceBatchedTimeElapsed == 0
-                        ? 0 : elapsedRealtimeMillis - mFirstForceBatchedTimeElapsed);
-        if (earliestRunTimeElapsedMillis == NO_EARLIEST_RUNTIME) {
-            proto.write(JobStatusDumpProto.TIME_UNTIL_EARLIEST_RUNTIME_MS, 0);
-        } else {
-            proto.write(JobStatusDumpProto.TIME_UNTIL_EARLIEST_RUNTIME_MS,
-                    earliestRunTimeElapsedMillis - elapsedRealtimeMillis);
-        }
-        if (latestRunTimeElapsedMillis == NO_LATEST_RUNTIME) {
-            proto.write(JobStatusDumpProto.TIME_UNTIL_LATEST_RUNTIME_MS, 0);
-        } else {
-            proto.write(JobStatusDumpProto.TIME_UNTIL_LATEST_RUNTIME_MS,
-                    latestRunTimeElapsedMillis - elapsedRealtimeMillis);
-        }
-
-        proto.write(JobStatusDumpProto.NUM_FAILURES, numFailures);
-        proto.write(JobStatusDumpProto.LAST_SUCCESSFUL_RUN_TIME, mLastSuccessfulRunTime);
-        proto.write(JobStatusDumpProto.LAST_FAILED_RUN_TIME, mLastFailedRunTime);
-
-        proto.end(token);
-    }
-}
diff --git a/services/core/java/com/android/server/job/controllers/QuotaController.java b/services/core/java/com/android/server/job/controllers/QuotaController.java
deleted file mode 100644
index b8cfac4..0000000
--- a/services/core/java/com/android/server/job/controllers/QuotaController.java
+++ /dev/null
@@ -1,2829 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.job.controllers;
-
-import static android.text.format.DateUtils.HOUR_IN_MILLIS;
-import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
-import static android.text.format.DateUtils.SECOND_IN_MILLIS;
-
-import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
-import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
-import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
-import static com.android.server.job.JobSchedulerService.RARE_INDEX;
-import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
-import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.app.ActivityManager;
-import android.app.ActivityManagerInternal;
-import android.app.AlarmManager;
-import android.app.AppGlobals;
-import android.app.IUidObserver;
-import android.app.usage.UsageStatsManagerInternal;
-import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.BatteryManager;
-import android.os.BatteryManagerInternal;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.KeyValueListParser;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.SparseSetArray;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.LocalServices;
-import com.android.server.job.ConstantsProto;
-import com.android.server.job.JobSchedulerService;
-import com.android.server.job.JobServiceContext;
-import com.android.server.job.StateControllerProto;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-/**
- * Controller that tracks whether an app has exceeded its standby bucket quota.
- *
- * With initial defaults, each app in each bucket is given 10 minutes to run within its respective
- * time window. Active jobs can run indefinitely, working set jobs can run for 10 minutes within a
- * 2 hour window, frequent jobs get to run 10 minutes in an 8 hour window, and rare jobs get to run
- * 10 minutes in a 24 hour window. The windows are rolling, so as soon as a job would have some
- * quota based on its bucket, it will be eligible to run. When a job's bucket changes, its new
- * quota is immediately applied to it.
- *
- * Job and session count limits are included to prevent abuse/spam. Each bucket has its own limit on
- * the number of jobs or sessions that can run within the window. Regardless of bucket, apps will
- * not be allowed to run more than 20 jobs within the past 10 minutes.
- *
- * Jobs are throttled while an app is not in a foreground state. All jobs are allowed to run
- * freely when an app enters the foreground state and are restricted when the app leaves the
- * foreground state. However, jobs that are started while the app is in the TOP state do not count
- * towards any quota and are not restricted regardless of the app's state change.
- *
- * Jobs will not be throttled when the device is charging. The device is considered to be charging
- * once the {@link BatteryManager#ACTION_CHARGING} intent has been broadcast.
- *
- * Note: all limits are enforced per bucket window unless explicitly stated otherwise.
- * All stated values are configurable and subject to change. See {@link QcConstants} for current
- * defaults.
- *
- * Test: atest com.android.server.job.controllers.QuotaControllerTest
- */
-public final class QuotaController extends StateController {
-    private static final String TAG = "JobScheduler.Quota";
-    private static final boolean DEBUG = JobSchedulerService.DEBUG
-            || Log.isLoggable(TAG, Log.DEBUG);
-
-    private static final String ALARM_TAG_CLEANUP = "*job.cleanup*";
-    private static final String ALARM_TAG_QUOTA_CHECK = "*job.quota_check*";
-
-    /**
-     * A sparse array of ArrayMaps, which is suitable for holding (userId, packageName)->object
-     * associations.
-     */
-    private static class UserPackageMap<T> {
-        private final SparseArray<ArrayMap<String, T>> mData = new SparseArray<>();
-
-        public void add(int userId, @NonNull String packageName, @Nullable T obj) {
-            ArrayMap<String, T> data = mData.get(userId);
-            if (data == null) {
-                data = new ArrayMap<String, T>();
-                mData.put(userId, data);
-            }
-            data.put(packageName, obj);
-        }
-
-        public void clear() {
-            for (int i = 0; i < mData.size(); ++i) {
-                mData.valueAt(i).clear();
-            }
-        }
-
-        /** Removes all the data for the user, if there was any. */
-        public void delete(int userId) {
-            mData.delete(userId);
-        }
-
-        /** Removes the data for the user and package, if there was any. */
-        public void delete(int userId, @NonNull String packageName) {
-            ArrayMap<String, T> data = mData.get(userId);
-            if (data != null) {
-                data.remove(packageName);
-            }
-        }
-
-        @Nullable
-        public T get(int userId, @NonNull String packageName) {
-            ArrayMap<String, T> data = mData.get(userId);
-            if (data != null) {
-                return data.get(packageName);
-            }
-            return null;
-        }
-
-        /** @see SparseArray#indexOfKey */
-        public int indexOfKey(int userId) {
-            return mData.indexOfKey(userId);
-        }
-
-        /** Returns the userId at the given index. */
-        public int keyAt(int index) {
-            return mData.keyAt(index);
-        }
-
-        /** Returns the package name at the given index. */
-        @NonNull
-        public String keyAt(int userIndex, int packageIndex) {
-            return mData.valueAt(userIndex).keyAt(packageIndex);
-        }
-
-        /** Returns the size of the outer (userId) array. */
-        public int numUsers() {
-            return mData.size();
-        }
-
-        public int numPackagesForUser(int userId) {
-            ArrayMap<String, T> data = mData.get(userId);
-            return data == null ? 0 : data.size();
-        }
-
-        /** Returns the value T at the given user and index. */
-        @Nullable
-        public T valueAt(int userIndex, int packageIndex) {
-            return mData.valueAt(userIndex).valueAt(packageIndex);
-        }
-
-        public void forEach(Consumer<T> consumer) {
-            for (int i = numUsers() - 1; i >= 0; --i) {
-                ArrayMap<String, T> data = mData.valueAt(i);
-                for (int j = data.size() - 1; j >= 0; --j) {
-                    consumer.accept(data.valueAt(j));
-                }
-            }
-        }
-    }
-
-    /**
-     * Standardize the output of userId-packageName combo.
-     */
-    private static String string(int userId, String packageName) {
-        return "<" + userId + ">" + packageName;
-    }
-
-    private static final class Package {
-        public final String packageName;
-        public final int userId;
-
-        Package(int userId, String packageName) {
-            this.userId = userId;
-            this.packageName = packageName;
-        }
-
-        @Override
-        public String toString() {
-            return string(userId, packageName);
-        }
-
-        public void writeToProto(ProtoOutputStream proto, long fieldId) {
-            final long token = proto.start(fieldId);
-
-            proto.write(StateControllerProto.QuotaController.Package.USER_ID, userId);
-            proto.write(StateControllerProto.QuotaController.Package.NAME, packageName);
-
-            proto.end(token);
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (obj instanceof Package) {
-                Package other = (Package) obj;
-                return userId == other.userId && Objects.equals(packageName, other.packageName);
-            } else {
-                return false;
-            }
-        }
-
-        @Override
-        public int hashCode() {
-            return packageName.hashCode() + userId;
-        }
-    }
-
-    private static int hashLong(long val) {
-        return (int) (val ^ (val >>> 32));
-    }
-
-    @VisibleForTesting
-    static class ExecutionStats {
-        /**
-         * The time after which this record should be considered invalid (out of date), in the
-         * elapsed realtime timebase.
-         */
-        public long expirationTimeElapsed;
-
-        public long windowSizeMs;
-        public int jobCountLimit;
-        public int sessionCountLimit;
-
-        /** The total amount of time the app ran in its respective bucket window size. */
-        public long executionTimeInWindowMs;
-        public int bgJobCountInWindow;
-
-        /** The total amount of time the app ran in the last {@link #MAX_PERIOD_MS}. */
-        public long executionTimeInMaxPeriodMs;
-        public int bgJobCountInMaxPeriod;
-
-        /**
-         * The number of {@link TimingSession}s within the bucket window size. This will include
-         * sessions that started before the window as long as they end within the window.
-         */
-        public int sessionCountInWindow;
-
-        /**
-         * The time after which the app will be under the bucket quota and can start running jobs
-         * again. This is only valid if
-         * {@link #executionTimeInWindowMs} >= {@link #mAllowedTimePerPeriodMs},
-         * {@link #executionTimeInMaxPeriodMs} >= {@link #mMaxExecutionTimeMs},
-         * {@link #bgJobCountInWindow} >= {@link #jobCountLimit}, or
-         * {@link #sessionCountInWindow} >= {@link #sessionCountLimit}.
-         */
-        public long inQuotaTimeElapsed;
-
-        /**
-         * The time after which {@link #jobCountInRateLimitingWindow} should be considered invalid,
-         * in the elapsed realtime timebase.
-         */
-        public long jobRateLimitExpirationTimeElapsed;
-
-        /**
-         * The number of jobs that ran in at least the last {@link #mRateLimitingWindowMs}.
-         * It may contain a few stale entries since cleanup won't happen exactly every
-         * {@link #mRateLimitingWindowMs}.
-         */
-        public int jobCountInRateLimitingWindow;
-
-        /**
-         * The time after which {@link #sessionCountInRateLimitingWindow} should be considered
-         * invalid, in the elapsed realtime timebase.
-         */
-        public long sessionRateLimitExpirationTimeElapsed;
-
-        /**
-         * The number of {@link TimingSession}s that ran in at least the last
-         * {@link #mRateLimitingWindowMs}. It may contain a few stale entries since cleanup won't
-         * happen exactly every {@link #mRateLimitingWindowMs}. This should only be considered
-         * valid before elapsed realtime has reached {@link #sessionRateLimitExpirationTimeElapsed}.
-         */
-        public int sessionCountInRateLimitingWindow;
-
-        @Override
-        public String toString() {
-            return "expirationTime=" + expirationTimeElapsed + ", "
-                    + "windowSizeMs=" + windowSizeMs + ", "
-                    + "jobCountLimit=" + jobCountLimit + ", "
-                    + "sessionCountLimit=" + sessionCountLimit + ", "
-                    + "executionTimeInWindow=" + executionTimeInWindowMs + ", "
-                    + "bgJobCountInWindow=" + bgJobCountInWindow + ", "
-                    + "executionTimeInMaxPeriod=" + executionTimeInMaxPeriodMs + ", "
-                    + "bgJobCountInMaxPeriod=" + bgJobCountInMaxPeriod + ", "
-                    + "sessionCountInWindow=" + sessionCountInWindow + ", "
-                    + "inQuotaTime=" + inQuotaTimeElapsed + ", "
-                    + "jobCountExpirationTime=" + jobRateLimitExpirationTimeElapsed + ", "
-                    + "jobCountInRateLimitingWindow=" + jobCountInRateLimitingWindow + ", "
-                    + "sessionCountExpirationTime=" + sessionRateLimitExpirationTimeElapsed + ", "
-                    + "sessionCountInRateLimitingWindow=" + sessionCountInRateLimitingWindow;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (obj instanceof ExecutionStats) {
-                ExecutionStats other = (ExecutionStats) obj;
-                return this.expirationTimeElapsed == other.expirationTimeElapsed
-                        && this.windowSizeMs == other.windowSizeMs
-                        && this.jobCountLimit == other.jobCountLimit
-                        && this.sessionCountLimit == other.sessionCountLimit
-                        && this.executionTimeInWindowMs == other.executionTimeInWindowMs
-                        && this.bgJobCountInWindow == other.bgJobCountInWindow
-                        && this.executionTimeInMaxPeriodMs == other.executionTimeInMaxPeriodMs
-                        && this.sessionCountInWindow == other.sessionCountInWindow
-                        && this.bgJobCountInMaxPeriod == other.bgJobCountInMaxPeriod
-                        && this.inQuotaTimeElapsed == other.inQuotaTimeElapsed
-                        && this.jobRateLimitExpirationTimeElapsed
-                                == other.jobRateLimitExpirationTimeElapsed
-                        && this.jobCountInRateLimitingWindow == other.jobCountInRateLimitingWindow
-                        && this.sessionRateLimitExpirationTimeElapsed
-                                == other.sessionRateLimitExpirationTimeElapsed
-                        && this.sessionCountInRateLimitingWindow
-                                == other.sessionCountInRateLimitingWindow;
-            } else {
-                return false;
-            }
-        }
-
-        @Override
-        public int hashCode() {
-            int result = 0;
-            result = 31 * result + hashLong(expirationTimeElapsed);
-            result = 31 * result + hashLong(windowSizeMs);
-            result = 31 * result + hashLong(jobCountLimit);
-            result = 31 * result + hashLong(sessionCountLimit);
-            result = 31 * result + hashLong(executionTimeInWindowMs);
-            result = 31 * result + bgJobCountInWindow;
-            result = 31 * result + hashLong(executionTimeInMaxPeriodMs);
-            result = 31 * result + bgJobCountInMaxPeriod;
-            result = 31 * result + sessionCountInWindow;
-            result = 31 * result + hashLong(inQuotaTimeElapsed);
-            result = 31 * result + hashLong(jobRateLimitExpirationTimeElapsed);
-            result = 31 * result + jobCountInRateLimitingWindow;
-            result = 31 * result + hashLong(sessionRateLimitExpirationTimeElapsed);
-            result = 31 * result + sessionCountInRateLimitingWindow;
-            return result;
-        }
-    }
-
-    /** List of all tracked jobs keyed by source package-userId combo. */
-    private final UserPackageMap<ArraySet<JobStatus>> mTrackedJobs = new UserPackageMap<>();
-
-    /** Timer for each package-userId combo. */
-    private final UserPackageMap<Timer> mPkgTimers = new UserPackageMap<>();
-
-    /** List of all timing sessions for a package-userId combo, in chronological order. */
-    private final UserPackageMap<List<TimingSession>> mTimingSessions = new UserPackageMap<>();
-
-    /**
-     * List of alarm listeners for each package that listen for when each package comes back within
-     * quota.
-     */
-    private final UserPackageMap<QcAlarmListener> mInQuotaAlarmListeners = new UserPackageMap<>();
-
-    /** Cached calculation results for each app, with the standby buckets as the array indices. */
-    private final UserPackageMap<ExecutionStats[]> mExecutionStatsCache = new UserPackageMap<>();
-
-    /** List of UIDs currently in the foreground. */
-    private final SparseBooleanArray mForegroundUids = new SparseBooleanArray();
-
-    /** Cached mapping of UIDs (for all users) to a list of packages in the UID. */
-    private final SparseSetArray<String> mUidToPackageCache = new SparseSetArray<>();
-
-    /**
-     * List of jobs that started while the UID was in the TOP state. There will be no more than
-     * 16 ({@link JobSchedulerService#MAX_JOB_CONTEXTS_COUNT}) running at once, so an ArraySet is
-     * fine.
-     */
-    private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>();
-
-    private final ActivityManagerInternal mActivityManagerInternal;
-    private final AlarmManager mAlarmManager;
-    private final ChargingTracker mChargeTracker;
-    private final Handler mHandler;
-    private final QcConstants mQcConstants;
-
-    private volatile boolean mInParole;
-
-    /**
-     * If the QuotaController should throttle apps based on their standby bucket and job activity.
-     * If false, all jobs will have their CONSTRAINT_WITHIN_QUOTA bit set to true immediately and
-     * indefinitely.
-     */
-    private boolean mShouldThrottle;
-
-    /** How much time each app will have to run jobs within their standby bucket window. */
-    private long mAllowedTimePerPeriodMs = QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_MS;
-
-    /**
-     * The maximum amount of time an app can have its jobs running within a {@link #MAX_PERIOD_MS}
-     * window.
-     */
-    private long mMaxExecutionTimeMs = QcConstants.DEFAULT_MAX_EXECUTION_TIME_MS;
-
-    /**
-     * How much time the app should have before transitioning from out-of-quota to in-quota.
-     * This should not affect processing if the app is already in-quota.
-     */
-    private long mQuotaBufferMs = QcConstants.DEFAULT_IN_QUOTA_BUFFER_MS;
-
-    /**
-     * {@link #mAllowedTimePerPeriodMs} - {@link #mQuotaBufferMs}. This can be used to determine
-     * when an app will have enough quota to transition from out-of-quota to in-quota.
-     */
-    private long mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
-
-    /**
-     * {@link #mMaxExecutionTimeMs} - {@link #mQuotaBufferMs}. This can be used to determine when an
-     * app will have enough quota to transition from out-of-quota to in-quota.
-     */
-    private long mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
-
-    /** The period of time used to rate limit recently run jobs. */
-    private long mRateLimitingWindowMs = QcConstants.DEFAULT_RATE_LIMITING_WINDOW_MS;
-
-    /** The maximum number of jobs that can run within the past {@link #mRateLimitingWindowMs}. */
-    private int mMaxJobCountPerRateLimitingWindow =
-            QcConstants.DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
-
-    /**
-     * The maximum number of {@link TimingSession}s that can run within the past {@link
-     * #mRateLimitingWindowMs}.
-     */
-    private int mMaxSessionCountPerRateLimitingWindow =
-            QcConstants.DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW;
-
-    private long mNextCleanupTimeElapsed = 0;
-    private final AlarmManager.OnAlarmListener mSessionCleanupAlarmListener =
-            new AlarmManager.OnAlarmListener() {
-                @Override
-                public void onAlarm() {
-                    mHandler.obtainMessage(MSG_CLEAN_UP_SESSIONS).sendToTarget();
-                }
-            };
-
-    private final IUidObserver mUidObserver = new IUidObserver.Stub() {
-        @Override
-        public void onUidStateChanged(int uid, int procState, long procStateSeq) {
-            mHandler.obtainMessage(MSG_UID_PROCESS_STATE_CHANGED, uid, procState).sendToTarget();
-        }
-
-        @Override
-        public void onUidGone(int uid, boolean disabled) {
-        }
-
-        @Override
-        public void onUidActive(int uid) {
-        }
-
-        @Override
-        public void onUidIdle(int uid, boolean disabled) {
-        }
-
-        @Override
-        public void onUidCachedChanged(int uid, boolean cached) {
-        }
-    };
-
-    private final BroadcastReceiver mPackageAddedReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent == null) {
-                return;
-            }
-            if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
-                return;
-            }
-            final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
-            synchronized (mLock) {
-                mUidToPackageCache.remove(uid);
-            }
-        }
-    };
-
-    /**
-     * The rolling window size for each standby bucket. Within each window, an app will have 10
-     * minutes to run its jobs.
-     */
-    private final long[] mBucketPeriodsMs = new long[]{
-            QcConstants.DEFAULT_WINDOW_SIZE_ACTIVE_MS,
-            QcConstants.DEFAULT_WINDOW_SIZE_WORKING_MS,
-            QcConstants.DEFAULT_WINDOW_SIZE_FREQUENT_MS,
-            QcConstants.DEFAULT_WINDOW_SIZE_RARE_MS
-    };
-
-    /** The maximum period any bucket can have. */
-    private static final long MAX_PERIOD_MS = 24 * 60 * MINUTE_IN_MILLIS;
-
-    /**
-     * The maximum number of jobs based on its standby bucket. For each max value count in the
-     * array, the app will not be allowed to run more than that many number of jobs within the
-     * latest time interval of its rolling window size.
-     *
-     * @see #mBucketPeriodsMs
-     */
-    private final int[] mMaxBucketJobCounts = new int[]{
-            QcConstants.DEFAULT_MAX_JOB_COUNT_ACTIVE,
-            QcConstants.DEFAULT_MAX_JOB_COUNT_WORKING,
-            QcConstants.DEFAULT_MAX_JOB_COUNT_FREQUENT,
-            QcConstants.DEFAULT_MAX_JOB_COUNT_RARE
-    };
-
-    /**
-     * The maximum number of {@link TimingSession}s based on its standby bucket. For each max value
-     * count in the array, the app will not be allowed to have more than that many number of
-     * {@link TimingSession}s within the latest time interval of its rolling window size.
-     *
-     * @see #mBucketPeriodsMs
-     */
-    private final int[] mMaxBucketSessionCounts = new int[]{
-            QcConstants.DEFAULT_MAX_SESSION_COUNT_ACTIVE,
-            QcConstants.DEFAULT_MAX_SESSION_COUNT_WORKING,
-            QcConstants.DEFAULT_MAX_SESSION_COUNT_FREQUENT,
-            QcConstants.DEFAULT_MAX_SESSION_COUNT_RARE
-    };
-
-    /**
-     * Treat two distinct {@link TimingSession}s as the same if they start and end within this
-     * amount of time of each other.
-     */
-    private long mTimingSessionCoalescingDurationMs =
-            QcConstants.DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS;
-
-    /** An app has reached its quota. The message should contain a {@link Package} object. */
-    private static final int MSG_REACHED_QUOTA = 0;
-    /** Drop any old timing sessions. */
-    private static final int MSG_CLEAN_UP_SESSIONS = 1;
-    /** Check if a package is now within its quota. */
-    private static final int MSG_CHECK_PACKAGE = 2;
-    /** Process state for a UID has changed. */
-    private static final int MSG_UID_PROCESS_STATE_CHANGED = 3;
-
-    public QuotaController(JobSchedulerService service) {
-        super(service);
-        mHandler = new QcHandler(mContext.getMainLooper());
-        mChargeTracker = new ChargingTracker();
-        mChargeTracker.startTracking();
-        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
-        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
-        mQcConstants = new QcConstants(mHandler);
-
-        final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
-        mContext.registerReceiverAsUser(mPackageAddedReceiver, UserHandle.ALL, filter, null, null);
-
-        // Set up the app standby bucketing tracker
-        UsageStatsManagerInternal usageStats = LocalServices.getService(
-                UsageStatsManagerInternal.class);
-        usageStats.addAppIdleStateChangeListener(new StandbyTracker());
-
-        try {
-            ActivityManager.getService().registerUidObserver(mUidObserver,
-                    ActivityManager.UID_OBSERVER_PROCSTATE,
-                    ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, null);
-        } catch (RemoteException e) {
-            // ignored; both services live in system_server
-        }
-
-        mShouldThrottle = !mConstants.USE_HEARTBEATS;
-    }
-
-    @Override
-    public void onSystemServicesReady() {
-        mQcConstants.start(mContext.getContentResolver());
-    }
-
-    @Override
-    public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
-        final int userId = jobStatus.getSourceUserId();
-        final String pkgName = jobStatus.getSourcePackageName();
-        // Still need to track jobs even if mShouldThrottle is false in case it's set to true at
-        // some point.
-        ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
-        if (jobs == null) {
-            jobs = new ArraySet<>();
-            mTrackedJobs.add(userId, pkgName, jobs);
-        }
-        jobs.add(jobStatus);
-        jobStatus.setTrackingController(JobStatus.TRACKING_QUOTA);
-        if (mShouldThrottle) {
-            final boolean isWithinQuota = isWithinQuotaLocked(jobStatus);
-            setConstraintSatisfied(jobStatus, isWithinQuota);
-            if (!isWithinQuota) {
-                maybeScheduleStartAlarmLocked(userId, pkgName,
-                        getEffectiveStandbyBucket(jobStatus));
-            }
-        } else {
-            // QuotaController isn't throttling, so always set to true.
-            jobStatus.setQuotaConstraintSatisfied(true);
-        }
-    }
-
-    @Override
-    public void prepareForExecutionLocked(JobStatus jobStatus) {
-        if (DEBUG) {
-            Slog.d(TAG, "Prepping for " + jobStatus.toShortString());
-        }
-
-        final int uid = jobStatus.getSourceUid();
-        if (mActivityManagerInternal.getUidProcessState(uid) <= ActivityManager.PROCESS_STATE_TOP) {
-            if (DEBUG) {
-                Slog.d(TAG, jobStatus.toShortString() + " is top started job");
-            }
-            mTopStartedJobs.add(jobStatus);
-            // Top jobs won't count towards quota so there's no need to involve the Timer.
-            return;
-        }
-
-        final int userId = jobStatus.getSourceUserId();
-        final String packageName = jobStatus.getSourcePackageName();
-        Timer timer = mPkgTimers.get(userId, packageName);
-        if (timer == null) {
-            timer = new Timer(uid, userId, packageName);
-            mPkgTimers.add(userId, packageName, timer);
-        }
-        timer.startTrackingJobLocked(jobStatus);
-    }
-
-    @Override
-    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
-            boolean forUpdate) {
-        if (jobStatus.clearTrackingController(JobStatus.TRACKING_QUOTA)) {
-            Timer timer = mPkgTimers.get(jobStatus.getSourceUserId(),
-                    jobStatus.getSourcePackageName());
-            if (timer != null) {
-                timer.stopTrackingJob(jobStatus);
-            }
-            ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUserId(),
-                    jobStatus.getSourcePackageName());
-            if (jobs != null) {
-                jobs.remove(jobStatus);
-            }
-            mTopStartedJobs.remove(jobStatus);
-        }
-    }
-
-    @Override
-    public void onConstantsUpdatedLocked() {
-        if (mShouldThrottle == mConstants.USE_HEARTBEATS) {
-            mShouldThrottle = !mConstants.USE_HEARTBEATS;
-
-            // Update job bookkeeping out of band.
-            BackgroundThread.getHandler().post(() -> {
-                synchronized (mLock) {
-                    maybeUpdateAllConstraintsLocked();
-                }
-            });
-        }
-    }
-
-    @Override
-    public void onAppRemovedLocked(String packageName, int uid) {
-        if (packageName == null) {
-            Slog.wtf(TAG, "Told app removed but given null package name.");
-            return;
-        }
-        final int userId = UserHandle.getUserId(uid);
-        mTrackedJobs.delete(userId, packageName);
-        Timer timer = mPkgTimers.get(userId, packageName);
-        if (timer != null) {
-            if (timer.isActive()) {
-                Slog.wtf(TAG, "onAppRemovedLocked called before Timer turned off.");
-                timer.dropEverythingLocked();
-            }
-            mPkgTimers.delete(userId, packageName);
-        }
-        mTimingSessions.delete(userId, packageName);
-        QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
-        if (alarmListener != null) {
-            mAlarmManager.cancel(alarmListener);
-            mInQuotaAlarmListeners.delete(userId, packageName);
-        }
-        mExecutionStatsCache.delete(userId, packageName);
-        mForegroundUids.delete(uid);
-        mUidToPackageCache.remove(uid);
-    }
-
-    @Override
-    public void onUserRemovedLocked(int userId) {
-        mTrackedJobs.delete(userId);
-        mPkgTimers.delete(userId);
-        mTimingSessions.delete(userId);
-        mInQuotaAlarmListeners.delete(userId);
-        mExecutionStatsCache.delete(userId);
-        mUidToPackageCache.clear();
-    }
-
-    private boolean isUidInForeground(int uid) {
-        if (UserHandle.isCore(uid)) {
-            return true;
-        }
-        synchronized (mLock) {
-            return mForegroundUids.get(uid);
-        }
-    }
-
-    /** @return true if the job was started while the app was in the TOP state. */
-    private boolean isTopStartedJobLocked(@NonNull final JobStatus jobStatus) {
-        return mTopStartedJobs.contains(jobStatus);
-    }
-
-    /** Returns the maximum amount of time this job could run for. */
-    public long getMaxJobExecutionTimeMsLocked(@NonNull final JobStatus jobStatus) {
-        // If quota is currently "free", then the job can run for the full amount of time.
-        if (mChargeTracker.isCharging()
-                || mInParole
-                || isTopStartedJobLocked(jobStatus)
-                || isUidInForeground(jobStatus.getSourceUid())) {
-            return JobServiceContext.EXECUTING_TIMESLICE_MILLIS;
-        }
-        return getRemainingExecutionTimeLocked(jobStatus);
-    }
-
-    /**
-     * Returns an appropriate standby bucket for the job, taking into account any standby
-     * exemptions.
-     */
-    private int getEffectiveStandbyBucket(@NonNull final JobStatus jobStatus) {
-        if (jobStatus.uidActive || jobStatus.getJob().isExemptedFromAppStandby()) {
-            // Treat these cases as if they're in the ACTIVE bucket so that they get throttled
-            // like other ACTIVE apps.
-            return ACTIVE_INDEX;
-        }
-        return jobStatus.getStandbyBucket();
-    }
-
-    @VisibleForTesting
-    boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) {
-        final int standbyBucket = getEffectiveStandbyBucket(jobStatus);
-        // A job is within quota if one of the following is true:
-        //   1. it was started while the app was in the TOP state
-        //   2. the app is currently in the foreground
-        //   3. the app overall is within its quota
-        return isTopStartedJobLocked(jobStatus)
-                || isUidInForeground(jobStatus.getSourceUid())
-                || isWithinQuotaLocked(
-                jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
-    }
-
-    @VisibleForTesting
-    boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
-            final int standbyBucket) {
-        if (standbyBucket == NEVER_INDEX) return false;
-        // This check is needed in case the flag is toggled after a job has been registered.
-        if (!mShouldThrottle) return true;
-
-        // Quota constraint is not enforced while charging or when parole is on.
-        if (mChargeTracker.isCharging() || mInParole) {
-            return true;
-        }
-
-        ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
-        return getRemainingExecutionTimeLocked(stats) > 0
-                && isUnderJobCountQuotaLocked(stats, standbyBucket)
-                && isUnderSessionCountQuotaLocked(stats, standbyBucket);
-    }
-
-    private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats,
-            final int standbyBucket) {
-        final long now = sElapsedRealtimeClock.millis();
-        final boolean isUnderAllowedTimeQuota =
-                (stats.jobRateLimitExpirationTimeElapsed <= now
-                        || stats.jobCountInRateLimitingWindow < mMaxJobCountPerRateLimitingWindow);
-        return isUnderAllowedTimeQuota
-                && (stats.bgJobCountInWindow < mMaxBucketJobCounts[standbyBucket]);
-    }
-
-    private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats,
-            final int standbyBucket) {
-        final long now = sElapsedRealtimeClock.millis();
-        final boolean isUnderAllowedTimeQuota = (stats.sessionRateLimitExpirationTimeElapsed <= now
-                || stats.sessionCountInRateLimitingWindow < mMaxSessionCountPerRateLimitingWindow);
-        return isUnderAllowedTimeQuota
-                && stats.sessionCountInWindow < mMaxBucketSessionCounts[standbyBucket];
-    }
-
-    @VisibleForTesting
-    long getRemainingExecutionTimeLocked(@NonNull final JobStatus jobStatus) {
-        return getRemainingExecutionTimeLocked(jobStatus.getSourceUserId(),
-                jobStatus.getSourcePackageName(),
-                getEffectiveStandbyBucket(jobStatus));
-    }
-
-    @VisibleForTesting
-    long getRemainingExecutionTimeLocked(final int userId, @NonNull final String packageName) {
-        final int standbyBucket = JobSchedulerService.standbyBucketForPackage(packageName,
-                userId, sElapsedRealtimeClock.millis());
-        return getRemainingExecutionTimeLocked(userId, packageName, standbyBucket);
-    }
-
-    /**
-     * Returns the amount of time, in milliseconds, that this job has remaining to run based on its
-     * current standby bucket. Time remaining could be negative if the app was moved from a less
-     * restricted to a more restricted bucket.
-     */
-    private long getRemainingExecutionTimeLocked(final int userId,
-            @NonNull final String packageName, final int standbyBucket) {
-        if (standbyBucket == NEVER_INDEX) {
-            return 0;
-        }
-        return getRemainingExecutionTimeLocked(
-                getExecutionStatsLocked(userId, packageName, standbyBucket));
-    }
-
-    private long getRemainingExecutionTimeLocked(@NonNull ExecutionStats stats) {
-        return Math.min(mAllowedTimePerPeriodMs - stats.executionTimeInWindowMs,
-                mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs);
-    }
-
-    /**
-     * Returns the amount of time, in milliseconds, until the package would have reached its
-     * duration quota, assuming it has a job counting towards its quota the entire time. This takes
-     * into account any {@link TimingSession}s that may roll out of the window as the job is
-     * running.
-     */
-    @VisibleForTesting
-    long getTimeUntilQuotaConsumedLocked(final int userId, @NonNull final String packageName) {
-        final long nowElapsed = sElapsedRealtimeClock.millis();
-        final int standbyBucket = JobSchedulerService.standbyBucketForPackage(
-                packageName, userId, nowElapsed);
-        if (standbyBucket == NEVER_INDEX) {
-            return 0;
-        }
-        List<TimingSession> sessions = mTimingSessions.get(userId, packageName);
-        if (sessions == null || sessions.size() == 0) {
-            return mAllowedTimePerPeriodMs;
-        }
-
-        final ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
-        final long startWindowElapsed = nowElapsed - stats.windowSizeMs;
-        final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS;
-        final long allowedTimeRemainingMs = mAllowedTimePerPeriodMs - stats.executionTimeInWindowMs;
-        final long maxExecutionTimeRemainingMs =
-                mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs;
-
-        // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can
-        // essentially run until they reach the maximum limit.
-        if (stats.windowSizeMs == mAllowedTimePerPeriodMs) {
-            return calculateTimeUntilQuotaConsumedLocked(
-                    sessions, startMaxElapsed, maxExecutionTimeRemainingMs);
-        }
-
-        // Need to check both max time and period time in case one is less than the other.
-        // For example, max time remaining could be less than bucket time remaining, but sessions
-        // contributing to the max time remaining could phase out enough that we'd want to use the
-        // bucket value.
-        return Math.min(
-                calculateTimeUntilQuotaConsumedLocked(
-                        sessions, startMaxElapsed, maxExecutionTimeRemainingMs),
-                calculateTimeUntilQuotaConsumedLocked(
-                        sessions, startWindowElapsed, allowedTimeRemainingMs));
-    }
-
-    /**
-     * Calculates how much time it will take, in milliseconds, until the quota is fully consumed.
-     *
-     * @param windowStartElapsed The start of the window, in the elapsed realtime timebase.
-     * @param deadSpaceMs        How much time can be allowed to count towards the quota
-     */
-    private long calculateTimeUntilQuotaConsumedLocked(@NonNull List<TimingSession> sessions,
-            final long windowStartElapsed, long deadSpaceMs) {
-        long timeUntilQuotaConsumedMs = 0;
-        long start = windowStartElapsed;
-        for (int i = 0; i < sessions.size(); ++i) {
-            TimingSession session = sessions.get(i);
-
-            if (session.endTimeElapsed < windowStartElapsed) {
-                // Outside of window. Ignore.
-                continue;
-            } else if (session.startTimeElapsed <= windowStartElapsed) {
-                // Overlapping session. Can extend time by portion of session in window.
-                timeUntilQuotaConsumedMs += session.endTimeElapsed - windowStartElapsed;
-                start = session.endTimeElapsed;
-            } else {
-                // Completely within the window. Can only consider if there's enough dead space
-                // to get to the start of the session.
-                long diff = session.startTimeElapsed - start;
-                if (diff > deadSpaceMs) {
-                    break;
-                }
-                timeUntilQuotaConsumedMs += diff
-                        + (session.endTimeElapsed - session.startTimeElapsed);
-                deadSpaceMs -= diff;
-                start = session.endTimeElapsed;
-            }
-        }
-        // Will be non-zero if the loop didn't look at any sessions.
-        timeUntilQuotaConsumedMs += deadSpaceMs;
-        if (timeUntilQuotaConsumedMs > mMaxExecutionTimeMs) {
-            Slog.wtf(TAG, "Calculated quota consumed time too high: " + timeUntilQuotaConsumedMs);
-        }
-        return timeUntilQuotaConsumedMs;
-    }
-
-    /** Returns the execution stats of the app in the most recent window. */
-    @VisibleForTesting
-    @NonNull
-    ExecutionStats getExecutionStatsLocked(final int userId, @NonNull final String packageName,
-            final int standbyBucket) {
-        return getExecutionStatsLocked(userId, packageName, standbyBucket, true);
-    }
-
-    @NonNull
-    private ExecutionStats getExecutionStatsLocked(final int userId,
-            @NonNull final String packageName, final int standbyBucket,
-            final boolean refreshStatsIfOld) {
-        if (standbyBucket == NEVER_INDEX) {
-            Slog.wtf(TAG, "getExecutionStatsLocked called for a NEVER app.");
-            return new ExecutionStats();
-        }
-        ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName);
-        if (appStats == null) {
-            appStats = new ExecutionStats[mBucketPeriodsMs.length];
-            mExecutionStatsCache.add(userId, packageName, appStats);
-        }
-        ExecutionStats stats = appStats[standbyBucket];
-        if (stats == null) {
-            stats = new ExecutionStats();
-            appStats[standbyBucket] = stats;
-        }
-        if (refreshStatsIfOld) {
-            final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket];
-            final int jobCountLimit = mMaxBucketJobCounts[standbyBucket];
-            final int sessionCountLimit = mMaxBucketSessionCounts[standbyBucket];
-            Timer timer = mPkgTimers.get(userId, packageName);
-            if ((timer != null && timer.isActive())
-                    || stats.expirationTimeElapsed <= sElapsedRealtimeClock.millis()
-                    || stats.windowSizeMs != bucketWindowSizeMs
-                    || stats.jobCountLimit != jobCountLimit
-                    || stats.sessionCountLimit != sessionCountLimit) {
-                // The stats are no longer valid.
-                stats.windowSizeMs = bucketWindowSizeMs;
-                stats.jobCountLimit = jobCountLimit;
-                stats.sessionCountLimit = sessionCountLimit;
-                updateExecutionStatsLocked(userId, packageName, stats);
-            }
-        }
-
-        return stats;
-    }
-
-    @VisibleForTesting
-    void updateExecutionStatsLocked(final int userId, @NonNull final String packageName,
-            @NonNull ExecutionStats stats) {
-        stats.executionTimeInWindowMs = 0;
-        stats.bgJobCountInWindow = 0;
-        stats.executionTimeInMaxPeriodMs = 0;
-        stats.bgJobCountInMaxPeriod = 0;
-        stats.sessionCountInWindow = 0;
-        stats.inQuotaTimeElapsed = 0;
-
-        Timer timer = mPkgTimers.get(userId, packageName);
-        final long nowElapsed = sElapsedRealtimeClock.millis();
-        stats.expirationTimeElapsed = nowElapsed + MAX_PERIOD_MS;
-        if (timer != null && timer.isActive()) {
-            stats.executionTimeInWindowMs =
-                    stats.executionTimeInMaxPeriodMs = timer.getCurrentDuration(nowElapsed);
-            stats.bgJobCountInWindow = stats.bgJobCountInMaxPeriod = timer.getBgJobCount();
-            // If the timer is active, the value will be stale at the next method call, so
-            // invalidate now.
-            stats.expirationTimeElapsed = nowElapsed;
-            if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
-                stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
-                        nowElapsed - mAllowedTimeIntoQuotaMs + stats.windowSizeMs);
-            }
-            if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
-                stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
-                        nowElapsed - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS);
-            }
-        }
-
-        List<TimingSession> sessions = mTimingSessions.get(userId, packageName);
-        if (sessions == null || sessions.size() == 0) {
-            return;
-        }
-
-        final long startWindowElapsed = nowElapsed - stats.windowSizeMs;
-        final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS;
-        int sessionCountInWindow = 0;
-        // The minimum time between the start time and the beginning of the sessions that were
-        // looked at --> how much time the stats will be valid for.
-        long emptyTimeMs = Long.MAX_VALUE;
-        // Sessions are non-overlapping and in order of occurrence, so iterating backwards will get
-        // the most recent ones.
-        final int loopStart = sessions.size() - 1;
-        for (int i = loopStart; i >= 0; --i) {
-            TimingSession session = sessions.get(i);
-
-            // Window management.
-            if (startWindowElapsed < session.endTimeElapsed) {
-                final long start;
-                if (startWindowElapsed < session.startTimeElapsed) {
-                    start = session.startTimeElapsed;
-                    emptyTimeMs =
-                            Math.min(emptyTimeMs, session.startTimeElapsed - startWindowElapsed);
-                } else {
-                    // The session started before the window but ended within the window. Only
-                    // include the portion that was within the window.
-                    start = startWindowElapsed;
-                    emptyTimeMs = 0;
-                }
-
-                stats.executionTimeInWindowMs += session.endTimeElapsed - start;
-                stats.bgJobCountInWindow += session.bgJobCount;
-                if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
-                    stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
-                            start + stats.executionTimeInWindowMs - mAllowedTimeIntoQuotaMs
-                                    + stats.windowSizeMs);
-                }
-                if (stats.bgJobCountInWindow >= stats.jobCountLimit) {
-                    stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
-                            session.endTimeElapsed + stats.windowSizeMs);
-                }
-                if (i == loopStart
-                        || (sessions.get(i + 1).startTimeElapsed - session.endTimeElapsed)
-                                > mTimingSessionCoalescingDurationMs) {
-                    // Coalesce sessions if they are very close to each other in time
-                    sessionCountInWindow++;
-
-                    if (sessionCountInWindow >= stats.sessionCountLimit) {
-                        stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
-                                session.endTimeElapsed + stats.windowSizeMs);
-                    }
-                }
-            }
-
-            // Max period check.
-            if (startMaxElapsed < session.startTimeElapsed) {
-                stats.executionTimeInMaxPeriodMs +=
-                        session.endTimeElapsed - session.startTimeElapsed;
-                stats.bgJobCountInMaxPeriod += session.bgJobCount;
-                emptyTimeMs = Math.min(emptyTimeMs, session.startTimeElapsed - startMaxElapsed);
-                if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
-                    stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
-                            session.startTimeElapsed + stats.executionTimeInMaxPeriodMs
-                                    - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS);
-                }
-            } else if (startMaxElapsed < session.endTimeElapsed) {
-                // The session started before the window but ended within the window. Only include
-                // the portion that was within the window.
-                stats.executionTimeInMaxPeriodMs += session.endTimeElapsed - startMaxElapsed;
-                stats.bgJobCountInMaxPeriod += session.bgJobCount;
-                emptyTimeMs = 0;
-                if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
-                    stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
-                            startMaxElapsed + stats.executionTimeInMaxPeriodMs
-                                    - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS);
-                }
-            } else {
-                // This session ended before the window. No point in going any further.
-                break;
-            }
-        }
-        stats.expirationTimeElapsed = nowElapsed + emptyTimeMs;
-        stats.sessionCountInWindow = sessionCountInWindow;
-    }
-
-    /** Invalidate ExecutionStats for all apps. */
-    @VisibleForTesting
-    void invalidateAllExecutionStatsLocked() {
-        final long nowElapsed = sElapsedRealtimeClock.millis();
-        mExecutionStatsCache.forEach((appStats) -> {
-            if (appStats != null) {
-                for (int i = 0; i < appStats.length; ++i) {
-                    ExecutionStats stats = appStats[i];
-                    if (stats != null) {
-                        stats.expirationTimeElapsed = nowElapsed;
-                    }
-                }
-            }
-        });
-    }
-
-    @VisibleForTesting
-    void invalidateAllExecutionStatsLocked(final int userId,
-            @NonNull final String packageName) {
-        ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName);
-        if (appStats != null) {
-            final long nowElapsed = sElapsedRealtimeClock.millis();
-            for (int i = 0; i < appStats.length; ++i) {
-                ExecutionStats stats = appStats[i];
-                if (stats != null) {
-                    stats.expirationTimeElapsed = nowElapsed;
-                }
-            }
-        }
-    }
-
-    @VisibleForTesting
-    void incrementJobCount(final int userId, @NonNull final String packageName, int count) {
-        final long now = sElapsedRealtimeClock.millis();
-        ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName);
-        if (appStats == null) {
-            appStats = new ExecutionStats[mBucketPeriodsMs.length];
-            mExecutionStatsCache.add(userId, packageName, appStats);
-        }
-        for (int i = 0; i < appStats.length; ++i) {
-            ExecutionStats stats = appStats[i];
-            if (stats == null) {
-                stats = new ExecutionStats();
-                appStats[i] = stats;
-            }
-            if (stats.jobRateLimitExpirationTimeElapsed <= now) {
-                stats.jobRateLimitExpirationTimeElapsed = now + mRateLimitingWindowMs;
-                stats.jobCountInRateLimitingWindow = 0;
-            }
-            stats.jobCountInRateLimitingWindow += count;
-        }
-    }
-
-    private void incrementTimingSessionCount(final int userId, @NonNull final String packageName) {
-        final long now = sElapsedRealtimeClock.millis();
-        ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName);
-        if (appStats == null) {
-            appStats = new ExecutionStats[mBucketPeriodsMs.length];
-            mExecutionStatsCache.add(userId, packageName, appStats);
-        }
-        for (int i = 0; i < appStats.length; ++i) {
-            ExecutionStats stats = appStats[i];
-            if (stats == null) {
-                stats = new ExecutionStats();
-                appStats[i] = stats;
-            }
-            if (stats.sessionRateLimitExpirationTimeElapsed <= now) {
-                stats.sessionRateLimitExpirationTimeElapsed = now + mRateLimitingWindowMs;
-                stats.sessionCountInRateLimitingWindow = 0;
-            }
-            stats.sessionCountInRateLimitingWindow++;
-        }
-    }
-
-    @VisibleForTesting
-    void saveTimingSession(final int userId, @NonNull final String packageName,
-            @NonNull final TimingSession session) {
-        synchronized (mLock) {
-            List<TimingSession> sessions = mTimingSessions.get(userId, packageName);
-            if (sessions == null) {
-                sessions = new ArrayList<>();
-                mTimingSessions.add(userId, packageName, sessions);
-            }
-            sessions.add(session);
-            // Adding a new session means that the current stats are now incorrect.
-            invalidateAllExecutionStatsLocked(userId, packageName);
-
-            maybeScheduleCleanupAlarmLocked();
-        }
-    }
-
-    private final class EarliestEndTimeFunctor implements Consumer<List<TimingSession>> {
-        public long earliestEndElapsed = Long.MAX_VALUE;
-
-        @Override
-        public void accept(List<TimingSession> sessions) {
-            if (sessions != null && sessions.size() > 0) {
-                earliestEndElapsed = Math.min(earliestEndElapsed, sessions.get(0).endTimeElapsed);
-            }
-        }
-
-        void reset() {
-            earliestEndElapsed = Long.MAX_VALUE;
-        }
-    }
-
-    private final EarliestEndTimeFunctor mEarliestEndTimeFunctor = new EarliestEndTimeFunctor();
-
-    /** Schedule a cleanup alarm if necessary and there isn't already one scheduled. */
-    @VisibleForTesting
-    void maybeScheduleCleanupAlarmLocked() {
-        if (mNextCleanupTimeElapsed > sElapsedRealtimeClock.millis()) {
-            // There's already an alarm scheduled. Just stick with that one. There's no way we'll
-            // end up scheduling an earlier alarm.
-            if (DEBUG) {
-                Slog.v(TAG, "Not scheduling cleanup since there's already one at "
-                        + mNextCleanupTimeElapsed + " (in " + (mNextCleanupTimeElapsed
-                        - sElapsedRealtimeClock.millis()) + "ms)");
-            }
-            return;
-        }
-        mEarliestEndTimeFunctor.reset();
-        mTimingSessions.forEach(mEarliestEndTimeFunctor);
-        final long earliestEndElapsed = mEarliestEndTimeFunctor.earliestEndElapsed;
-        if (earliestEndElapsed == Long.MAX_VALUE) {
-            // Couldn't find a good time to clean up. Maybe this was called after we deleted all
-            // timing sessions.
-            if (DEBUG) {
-                Slog.d(TAG, "Didn't find a time to schedule cleanup");
-            }
-            return;
-        }
-        // Need to keep sessions for all apps up to the max period, regardless of their current
-        // standby bucket.
-        long nextCleanupElapsed = earliestEndElapsed + MAX_PERIOD_MS;
-        if (nextCleanupElapsed - mNextCleanupTimeElapsed <= 10 * MINUTE_IN_MILLIS) {
-            // No need to clean up too often. Delay the alarm if the next cleanup would be too soon
-            // after it.
-            nextCleanupElapsed += 10 * MINUTE_IN_MILLIS;
-        }
-        mNextCleanupTimeElapsed = nextCleanupElapsed;
-        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextCleanupElapsed, ALARM_TAG_CLEANUP,
-                mSessionCleanupAlarmListener, mHandler);
-        if (DEBUG) {
-            Slog.d(TAG, "Scheduled next cleanup for " + mNextCleanupTimeElapsed);
-        }
-    }
-
-    private void handleNewChargingStateLocked() {
-        final long nowElapsed = sElapsedRealtimeClock.millis();
-        final boolean isCharging = mChargeTracker.isCharging();
-        if (DEBUG) {
-            Slog.d(TAG, "handleNewChargingStateLocked: " + isCharging);
-        }
-        // Deal with Timers first.
-        mPkgTimers.forEach((t) -> t.onStateChangedLocked(nowElapsed, isCharging));
-        // Now update jobs.
-        maybeUpdateAllConstraintsLocked();
-    }
-
-    private void maybeUpdateAllConstraintsLocked() {
-        boolean changed = false;
-        for (int u = 0; u < mTrackedJobs.numUsers(); ++u) {
-            final int userId = mTrackedJobs.keyAt(u);
-            for (int p = 0; p < mTrackedJobs.numPackagesForUser(userId); ++p) {
-                final String packageName = mTrackedJobs.keyAt(u, p);
-                changed |= maybeUpdateConstraintForPkgLocked(userId, packageName);
-            }
-        }
-        if (changed) {
-            mStateChangedListener.onControllerStateChanged();
-        }
-    }
-
-    /**
-     * Update the CONSTRAINT_WITHIN_QUOTA bit for all of the Jobs for a given package.
-     *
-     * @return true if at least one job had its bit changed
-     */
-    private boolean maybeUpdateConstraintForPkgLocked(final int userId,
-            @NonNull final String packageName) {
-        ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName);
-        if (jobs == null || jobs.size() == 0) {
-            return false;
-        }
-
-        // Quota is the same for all jobs within a package.
-        final int realStandbyBucket = jobs.valueAt(0).getStandbyBucket();
-        final boolean realInQuota = isWithinQuotaLocked(userId, packageName, realStandbyBucket);
-        boolean changed = false;
-        for (int i = jobs.size() - 1; i >= 0; --i) {
-            final JobStatus js = jobs.valueAt(i);
-            if (isTopStartedJobLocked(js)) {
-                // Job was started while the app was in the TOP state so we should allow it to
-                // finish.
-                changed |= js.setQuotaConstraintSatisfied(true);
-            } else if (realStandbyBucket != ACTIVE_INDEX
-                    && realStandbyBucket == getEffectiveStandbyBucket(js)) {
-                // An app in the ACTIVE bucket may be out of quota while the job could be in quota
-                // for some reason. Therefore, avoid setting the real value here and check each job
-                // individually.
-                changed |= setConstraintSatisfied(js, realInQuota);
-            } else {
-                // This job is somehow exempted. Need to determine its own quota status.
-                changed |= setConstraintSatisfied(js, isWithinQuotaLocked(js));
-            }
-        }
-        if (!realInQuota) {
-            // Don't want to use the effective standby bucket here since that bump the bucket to
-            // ACTIVE for one of the jobs, which doesn't help with other jobs that aren't
-            // exempted.
-            maybeScheduleStartAlarmLocked(userId, packageName, realStandbyBucket);
-        } else {
-            QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
-            if (alarmListener != null && alarmListener.isWaiting()) {
-                mAlarmManager.cancel(alarmListener);
-                // Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
-                alarmListener.setTriggerTime(0);
-            }
-        }
-        return changed;
-    }
-
-    private class UidConstraintUpdater implements Consumer<JobStatus> {
-        private final UserPackageMap<Integer> mToScheduleStartAlarms = new UserPackageMap<>();
-        public boolean wasJobChanged;
-
-        @Override
-        public void accept(JobStatus jobStatus) {
-            wasJobChanged |= setConstraintSatisfied(jobStatus, isWithinQuotaLocked(jobStatus));
-            final int userId = jobStatus.getSourceUserId();
-            final String packageName = jobStatus.getSourcePackageName();
-            final int realStandbyBucket = jobStatus.getStandbyBucket();
-            if (isWithinQuotaLocked(userId, packageName, realStandbyBucket)) {
-                QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
-                if (alarmListener != null && alarmListener.isWaiting()) {
-                    mAlarmManager.cancel(alarmListener);
-                    // Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
-                    alarmListener.setTriggerTime(0);
-                }
-            } else {
-                mToScheduleStartAlarms.add(userId, packageName, realStandbyBucket);
-            }
-        }
-
-        void postProcess() {
-            for (int u = 0; u < mToScheduleStartAlarms.numUsers(); ++u) {
-                final int userId = mToScheduleStartAlarms.keyAt(u);
-                for (int p = 0; p < mToScheduleStartAlarms.numPackagesForUser(userId); ++p) {
-                    final String packageName = mToScheduleStartAlarms.keyAt(u, p);
-                    final int standbyBucket = mToScheduleStartAlarms.get(userId, packageName);
-                    maybeScheduleStartAlarmLocked(userId, packageName, standbyBucket);
-                }
-            }
-        }
-
-        void reset() {
-            wasJobChanged = false;
-            mToScheduleStartAlarms.clear();
-        }
-    }
-
-    private final UidConstraintUpdater mUpdateUidConstraints = new UidConstraintUpdater();
-
-    private boolean maybeUpdateConstraintForUidLocked(final int uid) {
-        mService.getJobStore().forEachJobForSourceUid(uid, mUpdateUidConstraints);
-
-        mUpdateUidConstraints.postProcess();
-        boolean changed = mUpdateUidConstraints.wasJobChanged;
-        mUpdateUidConstraints.reset();
-        return changed;
-    }
-
-    /**
-     * Maybe schedule a non-wakeup alarm for the next time this package will have quota to run
-     * again. This should only be called if the package is already out of quota.
-     */
-    @VisibleForTesting
-    void maybeScheduleStartAlarmLocked(final int userId, @NonNull final String packageName,
-            final int standbyBucket) {
-        if (standbyBucket == NEVER_INDEX) {
-            return;
-        }
-
-        final String pkgString = string(userId, packageName);
-        ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
-        final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket);
-        final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats,
-                standbyBucket);
-
-        QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
-        if (stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs
-                && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs
-                && isUnderJobCountQuota
-                && isUnderTimingSessionCountQuota) {
-            // Already in quota. Why was this method called?
-            if (DEBUG) {
-                Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
-                        + " even though it already has "
-                        + getRemainingExecutionTimeLocked(userId, packageName, standbyBucket)
-                        + "ms in its quota.");
-            }
-            if (alarmListener != null) {
-                // Cancel any pending alarm.
-                mAlarmManager.cancel(alarmListener);
-                // Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
-                alarmListener.setTriggerTime(0);
-            }
-            mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
-            return;
-        }
-
-        if (alarmListener == null) {
-            alarmListener = new QcAlarmListener(userId, packageName);
-            mInQuotaAlarmListeners.add(userId, packageName, alarmListener);
-        }
-
-        // The time this app will have quota again.
-        long inQuotaTimeElapsed = stats.inQuotaTimeElapsed;
-        if (!isUnderJobCountQuota && stats.bgJobCountInWindow < stats.jobCountLimit) {
-            // App hit the rate limit.
-            inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
-                    stats.jobRateLimitExpirationTimeElapsed);
-        }
-        if (!isUnderTimingSessionCountQuota
-                && stats.sessionCountInWindow < stats.sessionCountLimit) {
-            // App hit the rate limit.
-            inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
-                    stats.sessionRateLimitExpirationTimeElapsed);
-        }
-        // Only schedule the alarm if:
-        // 1. There isn't one currently scheduled
-        // 2. The new alarm is significantly earlier than the previous alarm (which could be the
-        // case if the package moves into a higher standby bucket). If it's earlier but not
-        // significantly so, then we essentially delay the job a few extra minutes.
-        // 3. The alarm is after the current alarm by more than the quota buffer.
-        // TODO: this might be overengineering. Simplify if proven safe.
-        if (!alarmListener.isWaiting()
-                || inQuotaTimeElapsed < alarmListener.getTriggerTimeElapsed() - 3 * MINUTE_IN_MILLIS
-                || alarmListener.getTriggerTimeElapsed() < inQuotaTimeElapsed) {
-            if (DEBUG) {
-                Slog.d(TAG, "Scheduling start alarm for " + pkgString);
-            }
-            // If the next time this app will have quota is at least 3 minutes before the
-            // alarm is supposed to go off, reschedule the alarm.
-            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, inQuotaTimeElapsed,
-                    ALARM_TAG_QUOTA_CHECK, alarmListener, mHandler);
-            alarmListener.setTriggerTime(inQuotaTimeElapsed);
-        } else if (DEBUG) {
-            Slog.d(TAG, "No need to schedule start alarm for " + pkgString);
-        }
-    }
-
-    private boolean setConstraintSatisfied(@NonNull JobStatus jobStatus, boolean isWithinQuota) {
-        if (!isWithinQuota && jobStatus.getWhenStandbyDeferred() == 0) {
-            // Mark that the job is being deferred due to buckets.
-            jobStatus.setWhenStandbyDeferred(sElapsedRealtimeClock.millis());
-        }
-        return jobStatus.setQuotaConstraintSatisfied(isWithinQuota);
-    }
-
-    private final class ChargingTracker extends BroadcastReceiver {
-        /**
-         * Track whether we're charging. This has a slightly different definition than that of
-         * BatteryController.
-         */
-        private boolean mCharging;
-
-        ChargingTracker() {
-        }
-
-        public void startTracking() {
-            IntentFilter filter = new IntentFilter();
-
-            // Charging/not charging.
-            filter.addAction(BatteryManager.ACTION_CHARGING);
-            filter.addAction(BatteryManager.ACTION_DISCHARGING);
-            mContext.registerReceiver(this, filter);
-
-            // Initialise tracker state.
-            BatteryManagerInternal batteryManagerInternal =
-                    LocalServices.getService(BatteryManagerInternal.class);
-            mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
-        }
-
-        public boolean isCharging() {
-            return mCharging;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            synchronized (mLock) {
-                final String action = intent.getAction();
-                if (BatteryManager.ACTION_CHARGING.equals(action)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Received charging intent, fired @ "
-                                + sElapsedRealtimeClock.millis());
-                    }
-                    mCharging = true;
-                    handleNewChargingStateLocked();
-                } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Disconnected from power.");
-                    }
-                    mCharging = false;
-                    handleNewChargingStateLocked();
-                }
-            }
-        }
-    }
-
-    @VisibleForTesting
-    static final class TimingSession {
-        // Start timestamp in elapsed realtime timebase.
-        public final long startTimeElapsed;
-        // End timestamp in elapsed realtime timebase.
-        public final long endTimeElapsed;
-        // How many background jobs ran during this session.
-        public final int bgJobCount;
-
-        private final int mHashCode;
-
-        TimingSession(long startElapsed, long endElapsed, int bgJobCount) {
-            this.startTimeElapsed = startElapsed;
-            this.endTimeElapsed = endElapsed;
-            this.bgJobCount = bgJobCount;
-
-            int hashCode = 0;
-            hashCode = 31 * hashCode + hashLong(startTimeElapsed);
-            hashCode = 31 * hashCode + hashLong(endTimeElapsed);
-            hashCode = 31 * hashCode + bgJobCount;
-            mHashCode = hashCode;
-        }
-
-        @Override
-        public String toString() {
-            return "TimingSession{" + startTimeElapsed + "->" + endTimeElapsed + ", " + bgJobCount
-                    + "}";
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (obj instanceof TimingSession) {
-                TimingSession other = (TimingSession) obj;
-                return startTimeElapsed == other.startTimeElapsed
-                        && endTimeElapsed == other.endTimeElapsed
-                        && bgJobCount == other.bgJobCount;
-            } else {
-                return false;
-            }
-        }
-
-        @Override
-        public int hashCode() {
-            return mHashCode;
-        }
-
-        public void dump(IndentingPrintWriter pw) {
-            pw.print(startTimeElapsed);
-            pw.print(" -> ");
-            pw.print(endTimeElapsed);
-            pw.print(" (");
-            pw.print(endTimeElapsed - startTimeElapsed);
-            pw.print("), ");
-            pw.print(bgJobCount);
-            pw.print(" bg jobs.");
-            pw.println();
-        }
-
-        public void dump(@NonNull ProtoOutputStream proto, long fieldId) {
-            final long token = proto.start(fieldId);
-
-            proto.write(StateControllerProto.QuotaController.TimingSession.START_TIME_ELAPSED,
-                    startTimeElapsed);
-            proto.write(StateControllerProto.QuotaController.TimingSession.END_TIME_ELAPSED,
-                    endTimeElapsed);
-            proto.write(StateControllerProto.QuotaController.TimingSession.BG_JOB_COUNT,
-                    bgJobCount);
-
-            proto.end(token);
-        }
-    }
-
-    private final class Timer {
-        private final Package mPkg;
-        private final int mUid;
-
-        // List of jobs currently running for this app that started when the app wasn't in the
-        // foreground.
-        private final ArraySet<JobStatus> mRunningBgJobs = new ArraySet<>();
-        private long mStartTimeElapsed;
-        private int mBgJobCount;
-
-        Timer(int uid, int userId, String packageName) {
-            mPkg = new Package(userId, packageName);
-            mUid = uid;
-        }
-
-        void startTrackingJobLocked(@NonNull JobStatus jobStatus) {
-            if (isTopStartedJobLocked(jobStatus)) {
-                // We intentionally don't pay attention to fg state changes after a TOP job has
-                // started.
-                if (DEBUG) {
-                    Slog.v(TAG,
-                            "Timer ignoring " + jobStatus.toShortString() + " because isTop");
-                }
-                return;
-            }
-            if (DEBUG) {
-                Slog.v(TAG, "Starting to track " + jobStatus.toShortString());
-            }
-            // Always track jobs, even when charging.
-            mRunningBgJobs.add(jobStatus);
-            if (shouldTrackLocked()) {
-                mBgJobCount++;
-                incrementJobCount(mPkg.userId, mPkg.packageName, 1);
-                if (mRunningBgJobs.size() == 1) {
-                    // Started tracking the first job.
-                    mStartTimeElapsed = sElapsedRealtimeClock.millis();
-                    // Starting the timer means that all cached execution stats are now incorrect.
-                    invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName);
-                    scheduleCutoff();
-                }
-            }
-        }
-
-        void stopTrackingJob(@NonNull JobStatus jobStatus) {
-            if (DEBUG) {
-                Slog.v(TAG, "Stopping tracking of " + jobStatus.toShortString());
-            }
-            synchronized (mLock) {
-                if (mRunningBgJobs.size() == 0) {
-                    // maybeStopTrackingJobLocked can be called when an app cancels a job, so a
-                    // timer may not be running when it's asked to stop tracking a job.
-                    if (DEBUG) {
-                        Slog.d(TAG, "Timer isn't tracking any jobs but still told to stop");
-                    }
-                    return;
-                }
-                if (mRunningBgJobs.remove(jobStatus)
-                        && !mChargeTracker.isCharging() && mRunningBgJobs.size() == 0) {
-                    emitSessionLocked(sElapsedRealtimeClock.millis());
-                    cancelCutoff();
-                }
-            }
-        }
-
-        /**
-         * Stops tracking all jobs and cancels any pending alarms. This should only be called if
-         * the Timer is not going to be used anymore.
-         */
-        void dropEverythingLocked() {
-            mRunningBgJobs.clear();
-            cancelCutoff();
-        }
-
-        private void emitSessionLocked(long nowElapsed) {
-            if (mBgJobCount <= 0) {
-                // Nothing to emit.
-                return;
-            }
-            TimingSession ts = new TimingSession(mStartTimeElapsed, nowElapsed, mBgJobCount);
-            saveTimingSession(mPkg.userId, mPkg.packageName, ts);
-            mBgJobCount = 0;
-            // Don't reset the tracked jobs list as we need to keep tracking the current number
-            // of jobs.
-            // However, cancel the currently scheduled cutoff since it's not currently useful.
-            cancelCutoff();
-            incrementTimingSessionCount(mPkg.userId, mPkg.packageName);
-        }
-
-        /**
-         * Returns true if the Timer is actively tracking, as opposed to passively ref counting
-         * during charging.
-         */
-        public boolean isActive() {
-            synchronized (mLock) {
-                return mBgJobCount > 0;
-            }
-        }
-
-        boolean isRunning(JobStatus jobStatus) {
-            return mRunningBgJobs.contains(jobStatus);
-        }
-
-        long getCurrentDuration(long nowElapsed) {
-            synchronized (mLock) {
-                return !isActive() ? 0 : nowElapsed - mStartTimeElapsed;
-            }
-        }
-
-        int getBgJobCount() {
-            synchronized (mLock) {
-                return mBgJobCount;
-            }
-        }
-
-        private boolean shouldTrackLocked() {
-            return !mChargeTracker.isCharging() && !mForegroundUids.get(mUid);
-        }
-
-        void onStateChangedLocked(long nowElapsed, boolean isQuotaFree) {
-            if (isQuotaFree) {
-                emitSessionLocked(nowElapsed);
-            } else if (!isActive() && shouldTrackLocked()) {
-                // Start timing from unplug.
-                if (mRunningBgJobs.size() > 0) {
-                    mStartTimeElapsed = nowElapsed;
-                    // NOTE: this does have the unfortunate consequence that if the device is
-                    // repeatedly plugged in and unplugged, or an app changes foreground state
-                    // very frequently, the job count for a package may be artificially high.
-                    mBgJobCount = mRunningBgJobs.size();
-                    incrementJobCount(mPkg.userId, mPkg.packageName, mBgJobCount);
-                    // Starting the timer means that all cached execution stats are now
-                    // incorrect.
-                    invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName);
-                    // Schedule cutoff since we're now actively tracking for quotas again.
-                    scheduleCutoff();
-                }
-            }
-        }
-
-        void rescheduleCutoff() {
-            cancelCutoff();
-            scheduleCutoff();
-        }
-
-        private void scheduleCutoff() {
-            // Each package can only be in one standby bucket, so we only need to have one
-            // message per timer. We only need to reschedule when restarting timer or when
-            // standby bucket changes.
-            synchronized (mLock) {
-                if (!isActive()) {
-                    return;
-                }
-                Message msg = mHandler.obtainMessage(MSG_REACHED_QUOTA, mPkg);
-                final long timeRemainingMs = getTimeUntilQuotaConsumedLocked(mPkg.userId,
-                        mPkg.packageName);
-                if (DEBUG) {
-                    Slog.i(TAG, "Job for " + mPkg + " has " + timeRemainingMs + "ms left.");
-                }
-                // If the job was running the entire time, then the system would be up, so it's
-                // fine to use uptime millis for these messages.
-                mHandler.sendMessageDelayed(msg, timeRemainingMs);
-            }
-        }
-
-        private void cancelCutoff() {
-            mHandler.removeMessages(MSG_REACHED_QUOTA, mPkg);
-        }
-
-        public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
-            pw.print("Timer{");
-            pw.print(mPkg);
-            pw.print("} ");
-            if (isActive()) {
-                pw.print("started at ");
-                pw.print(mStartTimeElapsed);
-                pw.print(" (");
-                pw.print(sElapsedRealtimeClock.millis() - mStartTimeElapsed);
-                pw.print("ms ago)");
-            } else {
-                pw.print("NOT active");
-            }
-            pw.print(", ");
-            pw.print(mBgJobCount);
-            pw.print(" running bg jobs");
-            pw.println();
-            pw.increaseIndent();
-            for (int i = 0; i < mRunningBgJobs.size(); i++) {
-                JobStatus js = mRunningBgJobs.valueAt(i);
-                if (predicate.test(js)) {
-                    pw.println(js.toShortString());
-                }
-            }
-            pw.decreaseIndent();
-        }
-
-        public void dump(ProtoOutputStream proto, long fieldId, Predicate<JobStatus> predicate) {
-            final long token = proto.start(fieldId);
-
-            mPkg.writeToProto(proto, StateControllerProto.QuotaController.Timer.PKG);
-            proto.write(StateControllerProto.QuotaController.Timer.IS_ACTIVE, isActive());
-            proto.write(StateControllerProto.QuotaController.Timer.START_TIME_ELAPSED,
-                    mStartTimeElapsed);
-            proto.write(StateControllerProto.QuotaController.Timer.BG_JOB_COUNT, mBgJobCount);
-            for (int i = 0; i < mRunningBgJobs.size(); i++) {
-                JobStatus js = mRunningBgJobs.valueAt(i);
-                if (predicate.test(js)) {
-                    js.writeToShortProto(proto,
-                            StateControllerProto.QuotaController.Timer.RUNNING_JOBS);
-                }
-            }
-
-            proto.end(token);
-        }
-    }
-
-    /**
-     * Tracking of app assignments to standby buckets
-     */
-    final class StandbyTracker extends AppIdleStateChangeListener {
-
-        @Override
-        public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId,
-                boolean idle, int bucket, int reason) {
-            // Update job bookkeeping out of band.
-            BackgroundThread.getHandler().post(() -> {
-                final int bucketIndex = JobSchedulerService.standbyBucketToBucketIndex(bucket);
-                if (DEBUG) {
-                    Slog.i(TAG, "Moving pkg " + string(userId, packageName) + " to bucketIndex "
-                            + bucketIndex);
-                }
-                synchronized (mLock) {
-                    ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName);
-                    if (jobs == null || jobs.size() == 0) {
-                        return;
-                    }
-                    for (int i = jobs.size() - 1; i >= 0; i--) {
-                        JobStatus js = jobs.valueAt(i);
-                        js.setStandbyBucket(bucketIndex);
-                    }
-                    Timer timer = mPkgTimers.get(userId, packageName);
-                    if (timer != null && timer.isActive()) {
-                        timer.rescheduleCutoff();
-                    }
-                    if (!mShouldThrottle || maybeUpdateConstraintForPkgLocked(userId,
-                            packageName)) {
-                        mStateChangedListener.onControllerStateChanged();
-                    }
-                }
-            });
-        }
-
-        @Override
-        public void onParoleStateChanged(final boolean isParoleOn) {
-            mInParole = isParoleOn;
-            if (DEBUG) {
-                Slog.i(TAG, "Global parole state now " + (isParoleOn ? "ON" : "OFF"));
-            }
-            // Update job bookkeeping out of band.
-            BackgroundThread.getHandler().post(() -> {
-                synchronized (mLock) {
-                    maybeUpdateAllConstraintsLocked();
-                }
-            });
-        }
-    }
-
-    private final class DeleteTimingSessionsFunctor implements Consumer<List<TimingSession>> {
-        private final Predicate<TimingSession> mTooOld = new Predicate<TimingSession>() {
-            public boolean test(TimingSession ts) {
-                return ts.endTimeElapsed <= sElapsedRealtimeClock.millis() - MAX_PERIOD_MS;
-            }
-        };
-
-        @Override
-        public void accept(List<TimingSession> sessions) {
-            if (sessions != null) {
-                // Remove everything older than MAX_PERIOD_MS time ago.
-                sessions.removeIf(mTooOld);
-            }
-        }
-    }
-
-    private final DeleteTimingSessionsFunctor mDeleteOldSessionsFunctor =
-            new DeleteTimingSessionsFunctor();
-
-    @VisibleForTesting
-    void deleteObsoleteSessionsLocked() {
-        mTimingSessions.forEach(mDeleteOldSessionsFunctor);
-    }
-
-    private class QcHandler extends Handler {
-        QcHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            synchronized (mLock) {
-                switch (msg.what) {
-                    case MSG_REACHED_QUOTA: {
-                        Package pkg = (Package) msg.obj;
-                        if (DEBUG) {
-                            Slog.d(TAG, "Checking if " + pkg + " has reached its quota.");
-                        }
-
-                        long timeRemainingMs = getRemainingExecutionTimeLocked(pkg.userId,
-                                pkg.packageName);
-                        if (timeRemainingMs <= 50) {
-                            // Less than 50 milliseconds left. Start process of shutting down jobs.
-                            if (DEBUG) Slog.d(TAG, pkg + " has reached its quota.");
-                            if (maybeUpdateConstraintForPkgLocked(pkg.userId, pkg.packageName)) {
-                                mStateChangedListener.onControllerStateChanged();
-                            }
-                        } else {
-                            // This could potentially happen if an old session phases out while a
-                            // job is currently running.
-                            // Reschedule message
-                            Message rescheduleMsg = obtainMessage(MSG_REACHED_QUOTA, pkg);
-                            timeRemainingMs = getTimeUntilQuotaConsumedLocked(pkg.userId,
-                                    pkg.packageName);
-                            if (DEBUG) {
-                                Slog.d(TAG, pkg + " has " + timeRemainingMs + "ms left.");
-                            }
-                            sendMessageDelayed(rescheduleMsg, timeRemainingMs);
-                        }
-                        break;
-                    }
-                    case MSG_CLEAN_UP_SESSIONS:
-                        if (DEBUG) {
-                            Slog.d(TAG, "Cleaning up timing sessions.");
-                        }
-                        deleteObsoleteSessionsLocked();
-                        maybeScheduleCleanupAlarmLocked();
-
-                        break;
-                    case MSG_CHECK_PACKAGE: {
-                        String packageName = (String) msg.obj;
-                        int userId = msg.arg1;
-                        if (DEBUG) {
-                            Slog.d(TAG, "Checking pkg " + string(userId, packageName));
-                        }
-                        if (maybeUpdateConstraintForPkgLocked(userId, packageName)) {
-                            mStateChangedListener.onControllerStateChanged();
-                        }
-                        break;
-                    }
-                    case MSG_UID_PROCESS_STATE_CHANGED: {
-                        final int uid = msg.arg1;
-                        final int procState = msg.arg2;
-                        final int userId = UserHandle.getUserId(uid);
-                        final long nowElapsed = sElapsedRealtimeClock.millis();
-
-                        synchronized (mLock) {
-                            boolean isQuotaFree;
-                            if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
-                                mForegroundUids.put(uid, true);
-                                isQuotaFree = true;
-                            } else {
-                                mForegroundUids.delete(uid);
-                                isQuotaFree = false;
-                            }
-                            // Update Timers first.
-                            if (mPkgTimers.indexOfKey(userId) >= 0) {
-                                ArraySet<String> packages = mUidToPackageCache.get(uid);
-                                if (packages == null) {
-                                    try {
-                                        String[] pkgs = AppGlobals.getPackageManager()
-                                                .getPackagesForUid(uid);
-                                        if (pkgs != null) {
-                                            for (String pkg : pkgs) {
-                                                mUidToPackageCache.add(uid, pkg);
-                                            }
-                                            packages = mUidToPackageCache.get(uid);
-                                        }
-                                    } catch (RemoteException e) {
-                                        Slog.wtf(TAG, "Failed to get package list", e);
-                                    }
-                                }
-                                if (packages != null) {
-                                    for (int i = packages.size() - 1; i >= 0; --i) {
-                                        Timer t = mPkgTimers.get(userId, packages.valueAt(i));
-                                        if (t != null) {
-                                            t.onStateChangedLocked(nowElapsed, isQuotaFree);
-                                        }
-                                    }
-                                }
-                            }
-                            if (maybeUpdateConstraintForUidLocked(uid)) {
-                                mStateChangedListener.onControllerStateChanged();
-                            }
-                        }
-                        break;
-                    }
-                }
-            }
-        }
-    }
-
-    private class QcAlarmListener implements AlarmManager.OnAlarmListener {
-        private final int mUserId;
-        private final String mPackageName;
-        private volatile long mTriggerTimeElapsed;
-
-        QcAlarmListener(int userId, String packageName) {
-            mUserId = userId;
-            mPackageName = packageName;
-        }
-
-        boolean isWaiting() {
-            return mTriggerTimeElapsed > 0;
-        }
-
-        void setTriggerTime(long timeElapsed) {
-            mTriggerTimeElapsed = timeElapsed;
-        }
-
-        long getTriggerTimeElapsed() {
-            return mTriggerTimeElapsed;
-        }
-
-        @Override
-        public void onAlarm() {
-            mHandler.obtainMessage(MSG_CHECK_PACKAGE, mUserId, 0, mPackageName).sendToTarget();
-            mTriggerTimeElapsed = 0;
-        }
-    }
-
-    @VisibleForTesting
-    class QcConstants extends ContentObserver {
-        private ContentResolver mResolver;
-        private final KeyValueListParser mParser = new KeyValueListParser(',');
-
-        private static final String KEY_ALLOWED_TIME_PER_PERIOD_MS = "allowed_time_per_period_ms";
-        private static final String KEY_IN_QUOTA_BUFFER_MS = "in_quota_buffer_ms";
-        private static final String KEY_WINDOW_SIZE_ACTIVE_MS = "window_size_active_ms";
-        private static final String KEY_WINDOW_SIZE_WORKING_MS = "window_size_working_ms";
-        private static final String KEY_WINDOW_SIZE_FREQUENT_MS = "window_size_frequent_ms";
-        private static final String KEY_WINDOW_SIZE_RARE_MS = "window_size_rare_ms";
-        private static final String KEY_MAX_EXECUTION_TIME_MS = "max_execution_time_ms";
-        private static final String KEY_MAX_JOB_COUNT_ACTIVE = "max_job_count_active";
-        private static final String KEY_MAX_JOB_COUNT_WORKING = "max_job_count_working";
-        private static final String KEY_MAX_JOB_COUNT_FREQUENT = "max_job_count_frequent";
-        private static final String KEY_MAX_JOB_COUNT_RARE = "max_job_count_rare";
-        private static final String KEY_RATE_LIMITING_WINDOW_MS = "rate_limiting_window_ms";
-        private static final String KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW =
-                "max_job_count_per_rate_limiting_window";
-        private static final String KEY_MAX_SESSION_COUNT_ACTIVE = "max_session_count_active";
-        private static final String KEY_MAX_SESSION_COUNT_WORKING = "max_session_count_working";
-        private static final String KEY_MAX_SESSION_COUNT_FREQUENT = "max_session_count_frequent";
-        private static final String KEY_MAX_SESSION_COUNT_RARE = "max_session_count_rare";
-        private static final String KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW =
-                "max_session_count_per_rate_limiting_window";
-        private static final String KEY_TIMING_SESSION_COALESCING_DURATION_MS =
-                "timing_session_coalescing_duration_ms";
-
-        private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_MS =
-                10 * 60 * 1000L; // 10 minutes
-        private static final long DEFAULT_IN_QUOTA_BUFFER_MS =
-                30 * 1000L; // 30 seconds
-        private static final long DEFAULT_WINDOW_SIZE_ACTIVE_MS =
-                DEFAULT_ALLOWED_TIME_PER_PERIOD_MS; // ACTIVE apps can run jobs at any time
-        private static final long DEFAULT_WINDOW_SIZE_WORKING_MS =
-                2 * 60 * 60 * 1000L; // 2 hours
-        private static final long DEFAULT_WINDOW_SIZE_FREQUENT_MS =
-                8 * 60 * 60 * 1000L; // 8 hours
-        private static final long DEFAULT_WINDOW_SIZE_RARE_MS =
-                24 * 60 * 60 * 1000L; // 24 hours
-        private static final long DEFAULT_MAX_EXECUTION_TIME_MS =
-                4 * HOUR_IN_MILLIS;
-        private static final long DEFAULT_RATE_LIMITING_WINDOW_MS =
-                10 * MINUTE_IN_MILLIS;
-        private static final int DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 20;
-        private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE = // 20/window = 120/hr = 1/session
-                DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
-        private static final int DEFAULT_MAX_JOB_COUNT_WORKING = // 120/window = 60/hr = 12/session
-                (int) (60.0 * DEFAULT_WINDOW_SIZE_WORKING_MS / HOUR_IN_MILLIS);
-        private static final int DEFAULT_MAX_JOB_COUNT_FREQUENT = // 200/window = 25/hr = 25/session
-                (int) (25.0 * DEFAULT_WINDOW_SIZE_FREQUENT_MS / HOUR_IN_MILLIS);
-        private static final int DEFAULT_MAX_JOB_COUNT_RARE = // 48/window = 2/hr = 16/session
-                (int) (2.0 * DEFAULT_WINDOW_SIZE_RARE_MS / HOUR_IN_MILLIS);
-        private static final int DEFAULT_MAX_SESSION_COUNT_ACTIVE =
-                20; // 120/hr
-        private static final int DEFAULT_MAX_SESSION_COUNT_WORKING =
-                10; // 5/hr
-        private static final int DEFAULT_MAX_SESSION_COUNT_FREQUENT =
-                8; // 1/hr
-        private static final int DEFAULT_MAX_SESSION_COUNT_RARE =
-                3; // .125/hr
-        private static final int DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 20;
-        private static final long DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS = 5000; // 5 seconds
-
-        /** How much time each app will have to run jobs within their standby bucket window. */
-        public long ALLOWED_TIME_PER_PERIOD_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_MS;
-
-        /**
-         * How much time the package should have before transitioning from out-of-quota to in-quota.
-         * This should not affect processing if the package is already in-quota.
-         */
-        public long IN_QUOTA_BUFFER_MS = DEFAULT_IN_QUOTA_BUFFER_MS;
-
-        /**
-         * The quota window size of the particular standby bucket. Apps in this standby bucket are
-         * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
-         * WINDOW_SIZE_MS.
-         */
-        public long WINDOW_SIZE_ACTIVE_MS = DEFAULT_WINDOW_SIZE_ACTIVE_MS;
-
-        /**
-         * The quota window size of the particular standby bucket. Apps in this standby bucket are
-         * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
-         * WINDOW_SIZE_MS.
-         */
-        public long WINDOW_SIZE_WORKING_MS = DEFAULT_WINDOW_SIZE_WORKING_MS;
-
-        /**
-         * The quota window size of the particular standby bucket. Apps in this standby bucket are
-         * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
-         * WINDOW_SIZE_MS.
-         */
-        public long WINDOW_SIZE_FREQUENT_MS = DEFAULT_WINDOW_SIZE_FREQUENT_MS;
-
-        /**
-         * The quota window size of the particular standby bucket. Apps in this standby bucket are
-         * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
-         * WINDOW_SIZE_MS.
-         */
-        public long WINDOW_SIZE_RARE_MS = DEFAULT_WINDOW_SIZE_RARE_MS;
-
-        /**
-         * The maximum amount of time an app can have its jobs running within a 24 hour window.
-         */
-        public long MAX_EXECUTION_TIME_MS = DEFAULT_MAX_EXECUTION_TIME_MS;
-
-        /**
-         * The maximum number of jobs an app can run within this particular standby bucket's
-         * window size.
-         */
-        public int MAX_JOB_COUNT_ACTIVE = DEFAULT_MAX_JOB_COUNT_ACTIVE;
-
-        /**
-         * The maximum number of jobs an app can run within this particular standby bucket's
-         * window size.
-         */
-        public int MAX_JOB_COUNT_WORKING = DEFAULT_MAX_JOB_COUNT_WORKING;
-
-        /**
-         * The maximum number of jobs an app can run within this particular standby bucket's
-         * window size.
-         */
-        public int MAX_JOB_COUNT_FREQUENT = DEFAULT_MAX_JOB_COUNT_FREQUENT;
-
-        /**
-         * The maximum number of jobs an app can run within this particular standby bucket's
-         * window size.
-         */
-        public int MAX_JOB_COUNT_RARE = DEFAULT_MAX_JOB_COUNT_RARE;
-
-        /** The period of time used to rate limit recently run jobs. */
-        public long RATE_LIMITING_WINDOW_MS = DEFAULT_RATE_LIMITING_WINDOW_MS;
-
-        /**
-         * The maximum number of jobs that can run within the past {@link #RATE_LIMITING_WINDOW_MS}.
-         */
-        public int MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW =
-                DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
-
-        /**
-         * The maximum number of {@link TimingSession}s an app can run within this particular
-         * standby bucket's window size.
-         */
-        public int MAX_SESSION_COUNT_ACTIVE = DEFAULT_MAX_SESSION_COUNT_ACTIVE;
-
-        /**
-         * The maximum number of {@link TimingSession}s an app can run within this particular
-         * standby bucket's window size.
-         */
-        public int MAX_SESSION_COUNT_WORKING = DEFAULT_MAX_SESSION_COUNT_WORKING;
-
-        /**
-         * The maximum number of {@link TimingSession}s an app can run within this particular
-         * standby bucket's window size.
-         */
-        public int MAX_SESSION_COUNT_FREQUENT = DEFAULT_MAX_SESSION_COUNT_FREQUENT;
-
-        /**
-         * The maximum number of {@link TimingSession}s an app can run within this particular
-         * standby bucket's window size.
-         */
-        public int MAX_SESSION_COUNT_RARE = DEFAULT_MAX_SESSION_COUNT_RARE;
-
-        /**
-         * The maximum number of {@link TimingSession}s that can run within the past
-         * {@link #ALLOWED_TIME_PER_PERIOD_MS}.
-         */
-        public int MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW =
-                DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW;
-
-        /**
-         * Treat two distinct {@link TimingSession}s as the same if they start and end within this
-         * amount of time of each other.
-         */
-        public long TIMING_SESSION_COALESCING_DURATION_MS =
-                DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS;
-
-        // Safeguards
-
-        /** The minimum number of jobs that any bucket will be allowed to run within its window. */
-        private static final int MIN_BUCKET_JOB_COUNT = 10;
-
-        /**
-         * The minimum number of {@link TimingSession}s that any bucket will be allowed to run
-         * within its window.
-         */
-        private static final int MIN_BUCKET_SESSION_COUNT = 1;
-
-        /** The minimum value that {@link #MAX_EXECUTION_TIME_MS} can have. */
-        private static final long MIN_MAX_EXECUTION_TIME_MS = 60 * MINUTE_IN_MILLIS;
-
-        /** The minimum value that {@link #MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW} can have. */
-        private static final int MIN_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 10;
-
-        /** The minimum value that {@link #MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW} can have. */
-        private static final int MIN_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 10;
-
-        /** The minimum value that {@link #RATE_LIMITING_WINDOW_MS} can have. */
-        private static final long MIN_RATE_LIMITING_WINDOW_MS = 30 * SECOND_IN_MILLIS;
-
-        QcConstants(Handler handler) {
-            super(handler);
-        }
-
-        private void start(ContentResolver resolver) {
-            mResolver = resolver;
-            mResolver.registerContentObserver(Settings.Global.getUriFor(
-                    Settings.Global.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS), false, this);
-            updateConstants();
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            final String constants = Settings.Global.getString(
-                    mResolver, Settings.Global.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS);
-
-            try {
-                mParser.setString(constants);
-            } catch (Exception e) {
-                // Failed to parse the settings string, log this and move on with defaults.
-                Slog.e(TAG, "Bad jobscheduler quota controller settings", e);
-            }
-
-            ALLOWED_TIME_PER_PERIOD_MS = mParser.getDurationMillis(
-                    KEY_ALLOWED_TIME_PER_PERIOD_MS, DEFAULT_ALLOWED_TIME_PER_PERIOD_MS);
-            IN_QUOTA_BUFFER_MS = mParser.getDurationMillis(
-                    KEY_IN_QUOTA_BUFFER_MS, DEFAULT_IN_QUOTA_BUFFER_MS);
-            WINDOW_SIZE_ACTIVE_MS = mParser.getDurationMillis(
-                    KEY_WINDOW_SIZE_ACTIVE_MS, DEFAULT_WINDOW_SIZE_ACTIVE_MS);
-            WINDOW_SIZE_WORKING_MS = mParser.getDurationMillis(
-                    KEY_WINDOW_SIZE_WORKING_MS, DEFAULT_WINDOW_SIZE_WORKING_MS);
-            WINDOW_SIZE_FREQUENT_MS = mParser.getDurationMillis(
-                    KEY_WINDOW_SIZE_FREQUENT_MS, DEFAULT_WINDOW_SIZE_FREQUENT_MS);
-            WINDOW_SIZE_RARE_MS = mParser.getDurationMillis(
-                    KEY_WINDOW_SIZE_RARE_MS, DEFAULT_WINDOW_SIZE_RARE_MS);
-            MAX_EXECUTION_TIME_MS = mParser.getDurationMillis(
-                    KEY_MAX_EXECUTION_TIME_MS, DEFAULT_MAX_EXECUTION_TIME_MS);
-            MAX_JOB_COUNT_ACTIVE = mParser.getInt(
-                    KEY_MAX_JOB_COUNT_ACTIVE, DEFAULT_MAX_JOB_COUNT_ACTIVE);
-            MAX_JOB_COUNT_WORKING = mParser.getInt(
-                    KEY_MAX_JOB_COUNT_WORKING, DEFAULT_MAX_JOB_COUNT_WORKING);
-            MAX_JOB_COUNT_FREQUENT = mParser.getInt(
-                    KEY_MAX_JOB_COUNT_FREQUENT, DEFAULT_MAX_JOB_COUNT_FREQUENT);
-            MAX_JOB_COUNT_RARE = mParser.getInt(
-                    KEY_MAX_JOB_COUNT_RARE, DEFAULT_MAX_JOB_COUNT_RARE);
-            RATE_LIMITING_WINDOW_MS = mParser.getLong(
-                    KEY_RATE_LIMITING_WINDOW_MS, DEFAULT_RATE_LIMITING_WINDOW_MS);
-            MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = mParser.getInt(
-                    KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
-                    DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW);
-            MAX_SESSION_COUNT_ACTIVE = mParser.getInt(
-                    KEY_MAX_SESSION_COUNT_ACTIVE, DEFAULT_MAX_SESSION_COUNT_ACTIVE);
-            MAX_SESSION_COUNT_WORKING = mParser.getInt(
-                    KEY_MAX_SESSION_COUNT_WORKING, DEFAULT_MAX_SESSION_COUNT_WORKING);
-            MAX_SESSION_COUNT_FREQUENT = mParser.getInt(
-                    KEY_MAX_SESSION_COUNT_FREQUENT, DEFAULT_MAX_SESSION_COUNT_FREQUENT);
-            MAX_SESSION_COUNT_RARE = mParser.getInt(
-                    KEY_MAX_SESSION_COUNT_RARE, DEFAULT_MAX_SESSION_COUNT_RARE);
-            MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = mParser.getInt(
-                    KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
-                    DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
-            TIMING_SESSION_COALESCING_DURATION_MS = mParser.getLong(
-                    KEY_TIMING_SESSION_COALESCING_DURATION_MS,
-                    DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS);
-
-            updateConstants();
-        }
-
-        @VisibleForTesting
-        void updateConstants() {
-            synchronized (mLock) {
-                boolean changed = false;
-
-                long newMaxExecutionTimeMs = Math.max(MIN_MAX_EXECUTION_TIME_MS,
-                        Math.min(MAX_PERIOD_MS, MAX_EXECUTION_TIME_MS));
-                if (mMaxExecutionTimeMs != newMaxExecutionTimeMs) {
-                    mMaxExecutionTimeMs = newMaxExecutionTimeMs;
-                    mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
-                    changed = true;
-                }
-                long newAllowedTimeMs = Math.min(mMaxExecutionTimeMs,
-                        Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_MS));
-                if (mAllowedTimePerPeriodMs != newAllowedTimeMs) {
-                    mAllowedTimePerPeriodMs = newAllowedTimeMs;
-                    mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
-                    changed = true;
-                }
-                // Make sure quota buffer is non-negative, not greater than allowed time per period,
-                // and no more than 5 minutes.
-                long newQuotaBufferMs = Math.max(0, Math.min(mAllowedTimePerPeriodMs,
-                        Math.min(5 * MINUTE_IN_MILLIS, IN_QUOTA_BUFFER_MS)));
-                if (mQuotaBufferMs != newQuotaBufferMs) {
-                    mQuotaBufferMs = newQuotaBufferMs;
-                    mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
-                    mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
-                    changed = true;
-                }
-                long newActivePeriodMs = Math.max(mAllowedTimePerPeriodMs,
-                        Math.min(MAX_PERIOD_MS, WINDOW_SIZE_ACTIVE_MS));
-                if (mBucketPeriodsMs[ACTIVE_INDEX] != newActivePeriodMs) {
-                    mBucketPeriodsMs[ACTIVE_INDEX] = newActivePeriodMs;
-                    changed = true;
-                }
-                long newWorkingPeriodMs = Math.max(mAllowedTimePerPeriodMs,
-                        Math.min(MAX_PERIOD_MS, WINDOW_SIZE_WORKING_MS));
-                if (mBucketPeriodsMs[WORKING_INDEX] != newWorkingPeriodMs) {
-                    mBucketPeriodsMs[WORKING_INDEX] = newWorkingPeriodMs;
-                    changed = true;
-                }
-                long newFrequentPeriodMs = Math.max(mAllowedTimePerPeriodMs,
-                        Math.min(MAX_PERIOD_MS, WINDOW_SIZE_FREQUENT_MS));
-                if (mBucketPeriodsMs[FREQUENT_INDEX] != newFrequentPeriodMs) {
-                    mBucketPeriodsMs[FREQUENT_INDEX] = newFrequentPeriodMs;
-                    changed = true;
-                }
-                long newRarePeriodMs = Math.max(mAllowedTimePerPeriodMs,
-                        Math.min(MAX_PERIOD_MS, WINDOW_SIZE_RARE_MS));
-                if (mBucketPeriodsMs[RARE_INDEX] != newRarePeriodMs) {
-                    mBucketPeriodsMs[RARE_INDEX] = newRarePeriodMs;
-                    changed = true;
-                }
-                long newRateLimitingWindowMs = Math.min(MAX_PERIOD_MS,
-                        Math.max(MIN_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS));
-                if (mRateLimitingWindowMs != newRateLimitingWindowMs) {
-                    mRateLimitingWindowMs = newRateLimitingWindowMs;
-                    changed = true;
-                }
-                int newMaxJobCountPerRateLimitingWindow = Math.max(
-                        MIN_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
-                        MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW);
-                if (mMaxJobCountPerRateLimitingWindow != newMaxJobCountPerRateLimitingWindow) {
-                    mMaxJobCountPerRateLimitingWindow = newMaxJobCountPerRateLimitingWindow;
-                    changed = true;
-                }
-                int newActiveMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_ACTIVE);
-                if (mMaxBucketJobCounts[ACTIVE_INDEX] != newActiveMaxJobCount) {
-                    mMaxBucketJobCounts[ACTIVE_INDEX] = newActiveMaxJobCount;
-                    changed = true;
-                }
-                int newWorkingMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_WORKING);
-                if (mMaxBucketJobCounts[WORKING_INDEX] != newWorkingMaxJobCount) {
-                    mMaxBucketJobCounts[WORKING_INDEX] = newWorkingMaxJobCount;
-                    changed = true;
-                }
-                int newFrequentMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_FREQUENT);
-                if (mMaxBucketJobCounts[FREQUENT_INDEX] != newFrequentMaxJobCount) {
-                    mMaxBucketJobCounts[FREQUENT_INDEX] = newFrequentMaxJobCount;
-                    changed = true;
-                }
-                int newRareMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_RARE);
-                if (mMaxBucketJobCounts[RARE_INDEX] != newRareMaxJobCount) {
-                    mMaxBucketJobCounts[RARE_INDEX] = newRareMaxJobCount;
-                    changed = true;
-                }
-                int newMaxSessionCountPerRateLimitPeriod = Math.max(
-                        MIN_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
-                        MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
-                if (mMaxSessionCountPerRateLimitingWindow != newMaxSessionCountPerRateLimitPeriod) {
-                    mMaxSessionCountPerRateLimitingWindow = newMaxSessionCountPerRateLimitPeriod;
-                    changed = true;
-                }
-                int newActiveMaxSessionCount =
-                        Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_ACTIVE);
-                if (mMaxBucketSessionCounts[ACTIVE_INDEX] != newActiveMaxSessionCount) {
-                    mMaxBucketSessionCounts[ACTIVE_INDEX] = newActiveMaxSessionCount;
-                    changed = true;
-                }
-                int newWorkingMaxSessionCount =
-                        Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_WORKING);
-                if (mMaxBucketSessionCounts[WORKING_INDEX] != newWorkingMaxSessionCount) {
-                    mMaxBucketSessionCounts[WORKING_INDEX] = newWorkingMaxSessionCount;
-                    changed = true;
-                }
-                int newFrequentMaxSessionCount =
-                        Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_FREQUENT);
-                if (mMaxBucketSessionCounts[FREQUENT_INDEX] != newFrequentMaxSessionCount) {
-                    mMaxBucketSessionCounts[FREQUENT_INDEX] = newFrequentMaxSessionCount;
-                    changed = true;
-                }
-                int newRareMaxSessionCount =
-                        Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_RARE);
-                if (mMaxBucketSessionCounts[RARE_INDEX] != newRareMaxSessionCount) {
-                    mMaxBucketSessionCounts[RARE_INDEX] = newRareMaxSessionCount;
-                    changed = true;
-                }
-                long newSessionCoalescingDurationMs = Math.min(15 * MINUTE_IN_MILLIS,
-                        Math.max(0, TIMING_SESSION_COALESCING_DURATION_MS));
-                if (mTimingSessionCoalescingDurationMs != newSessionCoalescingDurationMs) {
-                    mTimingSessionCoalescingDurationMs = newSessionCoalescingDurationMs;
-                    changed = true;
-                }
-
-                if (changed && mShouldThrottle) {
-                    // Update job bookkeeping out of band.
-                    BackgroundThread.getHandler().post(() -> {
-                        synchronized (mLock) {
-                            invalidateAllExecutionStatsLocked();
-                            maybeUpdateAllConstraintsLocked();
-                        }
-                    });
-                }
-            }
-        }
-
-        private void dump(IndentingPrintWriter pw) {
-            pw.println();
-            pw.println("QuotaController:");
-            pw.increaseIndent();
-            pw.printPair(KEY_ALLOWED_TIME_PER_PERIOD_MS, ALLOWED_TIME_PER_PERIOD_MS).println();
-            pw.printPair(KEY_IN_QUOTA_BUFFER_MS, IN_QUOTA_BUFFER_MS).println();
-            pw.printPair(KEY_WINDOW_SIZE_ACTIVE_MS, WINDOW_SIZE_ACTIVE_MS).println();
-            pw.printPair(KEY_WINDOW_SIZE_WORKING_MS, WINDOW_SIZE_WORKING_MS).println();
-            pw.printPair(KEY_WINDOW_SIZE_FREQUENT_MS, WINDOW_SIZE_FREQUENT_MS).println();
-            pw.printPair(KEY_WINDOW_SIZE_RARE_MS, WINDOW_SIZE_RARE_MS).println();
-            pw.printPair(KEY_MAX_EXECUTION_TIME_MS, MAX_EXECUTION_TIME_MS).println();
-            pw.printPair(KEY_MAX_JOB_COUNT_ACTIVE, MAX_JOB_COUNT_ACTIVE).println();
-            pw.printPair(KEY_MAX_JOB_COUNT_WORKING, MAX_JOB_COUNT_WORKING).println();
-            pw.printPair(KEY_MAX_JOB_COUNT_FREQUENT, MAX_JOB_COUNT_FREQUENT).println();
-            pw.printPair(KEY_MAX_JOB_COUNT_RARE, MAX_JOB_COUNT_RARE).println();
-            pw.printPair(KEY_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS).println();
-            pw.printPair(KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
-                    MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW).println();
-            pw.printPair(KEY_MAX_SESSION_COUNT_ACTIVE, MAX_SESSION_COUNT_ACTIVE).println();
-            pw.printPair(KEY_MAX_SESSION_COUNT_WORKING, MAX_SESSION_COUNT_WORKING).println();
-            pw.printPair(KEY_MAX_SESSION_COUNT_FREQUENT, MAX_SESSION_COUNT_FREQUENT).println();
-            pw.printPair(KEY_MAX_SESSION_COUNT_RARE, MAX_SESSION_COUNT_RARE).println();
-            pw.printPair(KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
-                    MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW).println();
-            pw.printPair(KEY_TIMING_SESSION_COALESCING_DURATION_MS,
-                    TIMING_SESSION_COALESCING_DURATION_MS).println();
-            pw.decreaseIndent();
-        }
-
-        private void dump(ProtoOutputStream proto) {
-            final long qcToken = proto.start(ConstantsProto.QUOTA_CONTROLLER);
-            proto.write(ConstantsProto.QuotaController.ALLOWED_TIME_PER_PERIOD_MS,
-                    ALLOWED_TIME_PER_PERIOD_MS);
-            proto.write(ConstantsProto.QuotaController.IN_QUOTA_BUFFER_MS, IN_QUOTA_BUFFER_MS);
-            proto.write(ConstantsProto.QuotaController.ACTIVE_WINDOW_SIZE_MS,
-                    WINDOW_SIZE_ACTIVE_MS);
-            proto.write(ConstantsProto.QuotaController.WORKING_WINDOW_SIZE_MS,
-                    WINDOW_SIZE_WORKING_MS);
-            proto.write(ConstantsProto.QuotaController.FREQUENT_WINDOW_SIZE_MS,
-                    WINDOW_SIZE_FREQUENT_MS);
-            proto.write(ConstantsProto.QuotaController.RARE_WINDOW_SIZE_MS, WINDOW_SIZE_RARE_MS);
-            proto.write(ConstantsProto.QuotaController.MAX_EXECUTION_TIME_MS,
-                    MAX_EXECUTION_TIME_MS);
-            proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_ACTIVE, MAX_JOB_COUNT_ACTIVE);
-            proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_WORKING,
-                    MAX_JOB_COUNT_WORKING);
-            proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_FREQUENT,
-                    MAX_JOB_COUNT_FREQUENT);
-            proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_RARE, MAX_JOB_COUNT_RARE);
-            proto.write(ConstantsProto.QuotaController.RATE_LIMITING_WINDOW_MS,
-                    RATE_LIMITING_WINDOW_MS);
-            proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
-                    MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW);
-            proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_ACTIVE,
-                    MAX_SESSION_COUNT_ACTIVE);
-            proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_WORKING,
-                    MAX_SESSION_COUNT_WORKING);
-            proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_FREQUENT,
-                    MAX_SESSION_COUNT_FREQUENT);
-            proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_RARE,
-                    MAX_SESSION_COUNT_RARE);
-            proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
-                    MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
-            proto.write(ConstantsProto.QuotaController.TIMING_SESSION_COALESCING_DURATION_MS,
-                    TIMING_SESSION_COALESCING_DURATION_MS);
-            proto.end(qcToken);
-        }
-    }
-
-    //////////////////////// TESTING HELPERS /////////////////////////////
-
-    @VisibleForTesting
-    long getAllowedTimePerPeriodMs() {
-        return mAllowedTimePerPeriodMs;
-    }
-
-    @VisibleForTesting
-    @NonNull
-    int[] getBucketMaxJobCounts() {
-        return mMaxBucketJobCounts;
-    }
-
-    @VisibleForTesting
-    @NonNull
-    int[] getBucketMaxSessionCounts() {
-        return mMaxBucketSessionCounts;
-    }
-
-    @VisibleForTesting
-    @NonNull
-    long[] getBucketWindowSizes() {
-        return mBucketPeriodsMs;
-    }
-
-    @VisibleForTesting
-    @NonNull
-    SparseBooleanArray getForegroundUids() {
-        return mForegroundUids;
-    }
-
-    @VisibleForTesting
-    @NonNull
-    Handler getHandler() {
-        return mHandler;
-    }
-
-    @VisibleForTesting
-    long getInQuotaBufferMs() {
-        return mQuotaBufferMs;
-    }
-
-    @VisibleForTesting
-    long getMaxExecutionTimeMs() {
-        return mMaxExecutionTimeMs;
-    }
-
-    @VisibleForTesting
-    int getMaxJobCountPerRateLimitingWindow() {
-        return mMaxJobCountPerRateLimitingWindow;
-    }
-
-    @VisibleForTesting
-    int getMaxSessionCountPerRateLimitingWindow() {
-        return mMaxSessionCountPerRateLimitingWindow;
-    }
-
-    @VisibleForTesting
-    long getRateLimitingWindowMs() {
-        return mRateLimitingWindowMs;
-    }
-
-    @VisibleForTesting
-    long getTimingSessionCoalescingDurationMs() {
-        return mTimingSessionCoalescingDurationMs;
-    }
-
-    @VisibleForTesting
-    @Nullable
-    List<TimingSession> getTimingSessions(int userId, String packageName) {
-        return mTimingSessions.get(userId, packageName);
-    }
-
-    @VisibleForTesting
-    @NonNull
-    QcConstants getQcConstants() {
-        return mQcConstants;
-    }
-
-    //////////////////////////// DATA DUMP //////////////////////////////
-
-    @Override
-    public void dumpControllerStateLocked(final IndentingPrintWriter pw,
-            final Predicate<JobStatus> predicate) {
-        pw.println("Is throttling: " + mShouldThrottle);
-        pw.println("Is charging: " + mChargeTracker.isCharging());
-        pw.println("In parole: " + mInParole);
-        pw.println("Current elapsed time: " + sElapsedRealtimeClock.millis());
-        pw.println();
-
-        pw.print("Foreground UIDs: ");
-        pw.println(mForegroundUids.toString());
-        pw.println();
-
-        pw.println("Cached UID->package map:");
-        pw.increaseIndent();
-        for (int i = 0; i < mUidToPackageCache.size(); ++i) {
-            final int uid = mUidToPackageCache.keyAt(i);
-            pw.print(uid);
-            pw.print(": ");
-            pw.println(mUidToPackageCache.get(uid));
-        }
-        pw.decreaseIndent();
-        pw.println();
-
-        mTrackedJobs.forEach((jobs) -> {
-            for (int j = 0; j < jobs.size(); j++) {
-                final JobStatus js = jobs.valueAt(j);
-                if (!predicate.test(js)) {
-                    continue;
-                }
-                pw.print("#");
-                js.printUniqueId(pw);
-                pw.print(" from ");
-                UserHandle.formatUid(pw, js.getSourceUid());
-                if (mTopStartedJobs.contains(js)) {
-                    pw.print(" (TOP)");
-                }
-                pw.println();
-
-                pw.increaseIndent();
-                pw.print(JobStatus.bucketName(getEffectiveStandbyBucket(js)));
-                pw.print(", ");
-                if (js.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) {
-                    pw.print("within quota");
-                } else {
-                    pw.print("not within quota");
-                }
-                pw.print(", ");
-                pw.print(getRemainingExecutionTimeLocked(js));
-                pw.print("ms remaining in quota");
-                pw.decreaseIndent();
-                pw.println();
-            }
-        });
-
-        pw.println();
-        for (int u = 0; u < mPkgTimers.numUsers(); ++u) {
-            final int userId = mPkgTimers.keyAt(u);
-            for (int p = 0; p < mPkgTimers.numPackagesForUser(userId); ++p) {
-                final String pkgName = mPkgTimers.keyAt(u, p);
-                mPkgTimers.valueAt(u, p).dump(pw, predicate);
-                pw.println();
-                List<TimingSession> sessions = mTimingSessions.get(userId, pkgName);
-                if (sessions != null) {
-                    pw.increaseIndent();
-                    pw.println("Saved sessions:");
-                    pw.increaseIndent();
-                    for (int j = sessions.size() - 1; j >= 0; j--) {
-                        TimingSession session = sessions.get(j);
-                        session.dump(pw);
-                    }
-                    pw.decreaseIndent();
-                    pw.decreaseIndent();
-                    pw.println();
-                }
-            }
-        }
-
-        pw.println("Cached execution stats:");
-        pw.increaseIndent();
-        for (int u = 0; u < mExecutionStatsCache.numUsers(); ++u) {
-            final int userId = mExecutionStatsCache.keyAt(u);
-            for (int p = 0; p < mExecutionStatsCache.numPackagesForUser(userId); ++p) {
-                final String pkgName = mExecutionStatsCache.keyAt(u, p);
-                ExecutionStats[] stats = mExecutionStatsCache.valueAt(u, p);
-
-                pw.println(string(userId, pkgName));
-                pw.increaseIndent();
-                for (int i = 0; i < stats.length; ++i) {
-                    ExecutionStats executionStats = stats[i];
-                    if (executionStats != null) {
-                        pw.print(JobStatus.bucketName(i));
-                        pw.print(": ");
-                        pw.println(executionStats);
-                    }
-                }
-                pw.decreaseIndent();
-            }
-        }
-        pw.decreaseIndent();
-
-        pw.println();
-        pw.println("In quota alarms:");
-        pw.increaseIndent();
-        for (int u = 0; u < mInQuotaAlarmListeners.numUsers(); ++u) {
-            final int userId = mInQuotaAlarmListeners.keyAt(u);
-            for (int p = 0; p < mInQuotaAlarmListeners.numPackagesForUser(userId); ++p) {
-                final String pkgName = mInQuotaAlarmListeners.keyAt(u, p);
-                QcAlarmListener alarmListener = mInQuotaAlarmListeners.valueAt(u, p);
-
-                pw.print(string(userId, pkgName));
-                pw.print(": ");
-                if (alarmListener.isWaiting()) {
-                    pw.println(alarmListener.getTriggerTimeElapsed());
-                } else {
-                    pw.println("NOT WAITING");
-                }
-            }
-        }
-        pw.decreaseIndent();
-    }
-
-    @Override
-    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
-            Predicate<JobStatus> predicate) {
-        final long token = proto.start(fieldId);
-        final long mToken = proto.start(StateControllerProto.QUOTA);
-
-        proto.write(StateControllerProto.QuotaController.IS_CHARGING, mChargeTracker.isCharging());
-        proto.write(StateControllerProto.QuotaController.IS_IN_PAROLE, mInParole);
-        proto.write(StateControllerProto.QuotaController.ELAPSED_REALTIME,
-                sElapsedRealtimeClock.millis());
-
-        for (int i = 0; i < mForegroundUids.size(); ++i) {
-            proto.write(StateControllerProto.QuotaController.FOREGROUND_UIDS,
-                    mForegroundUids.keyAt(i));
-        }
-
-        mTrackedJobs.forEach((jobs) -> {
-            for (int j = 0; j < jobs.size(); j++) {
-                final JobStatus js = jobs.valueAt(j);
-                if (!predicate.test(js)) {
-                    continue;
-                }
-                final long jsToken = proto.start(
-                        StateControllerProto.QuotaController.TRACKED_JOBS);
-                js.writeToShortProto(proto,
-                        StateControllerProto.QuotaController.TrackedJob.INFO);
-                proto.write(StateControllerProto.QuotaController.TrackedJob.SOURCE_UID,
-                        js.getSourceUid());
-                proto.write(
-                        StateControllerProto.QuotaController.TrackedJob.EFFECTIVE_STANDBY_BUCKET,
-                        getEffectiveStandbyBucket(js));
-                proto.write(StateControllerProto.QuotaController.TrackedJob.IS_TOP_STARTED_JOB,
-                        mTopStartedJobs.contains(js));
-                proto.write(StateControllerProto.QuotaController.TrackedJob.HAS_QUOTA,
-                        js.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
-                proto.write(StateControllerProto.QuotaController.TrackedJob.REMAINING_QUOTA_MS,
-                        getRemainingExecutionTimeLocked(js));
-                proto.end(jsToken);
-            }
-        });
-
-        for (int u = 0; u < mPkgTimers.numUsers(); ++u) {
-            final int userId = mPkgTimers.keyAt(u);
-            for (int p = 0; p < mPkgTimers.numPackagesForUser(userId); ++p) {
-                final String pkgName = mPkgTimers.keyAt(u, p);
-                final long psToken = proto.start(
-                        StateControllerProto.QuotaController.PACKAGE_STATS);
-                mPkgTimers.valueAt(u, p).dump(proto,
-                        StateControllerProto.QuotaController.PackageStats.TIMER, predicate);
-
-                List<TimingSession> sessions = mTimingSessions.get(userId, pkgName);
-                if (sessions != null) {
-                    for (int j = sessions.size() - 1; j >= 0; j--) {
-                        TimingSession session = sessions.get(j);
-                        session.dump(proto,
-                                StateControllerProto.QuotaController.PackageStats.SAVED_SESSIONS);
-                    }
-                }
-
-                ExecutionStats[] stats = mExecutionStatsCache.get(userId, pkgName);
-                if (stats != null) {
-                    for (int bucketIndex = 0; bucketIndex < stats.length; ++bucketIndex) {
-                        ExecutionStats es = stats[bucketIndex];
-                        if (es == null) {
-                            continue;
-                        }
-                        final long esToken = proto.start(
-                                StateControllerProto.QuotaController.PackageStats.EXECUTION_STATS);
-                        proto.write(
-                                StateControllerProto.QuotaController.ExecutionStats.STANDBY_BUCKET,
-                                bucketIndex);
-                        proto.write(
-                                StateControllerProto.QuotaController.ExecutionStats.EXPIRATION_TIME_ELAPSED,
-                                es.expirationTimeElapsed);
-                        proto.write(
-                                StateControllerProto.QuotaController.ExecutionStats.WINDOW_SIZE_MS,
-                                es.windowSizeMs);
-                        proto.write(
-                                StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_LIMIT,
-                                es.jobCountLimit);
-                        proto.write(
-                                StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_LIMIT,
-                                es.sessionCountLimit);
-                        proto.write(
-                                StateControllerProto.QuotaController.ExecutionStats.EXECUTION_TIME_IN_WINDOW_MS,
-                                es.executionTimeInWindowMs);
-                        proto.write(
-                                StateControllerProto.QuotaController.ExecutionStats.BG_JOB_COUNT_IN_WINDOW,
-                                es.bgJobCountInWindow);
-                        proto.write(
-                                StateControllerProto.QuotaController.ExecutionStats.EXECUTION_TIME_IN_MAX_PERIOD_MS,
-                                es.executionTimeInMaxPeriodMs);
-                        proto.write(
-                                StateControllerProto.QuotaController.ExecutionStats.BG_JOB_COUNT_IN_MAX_PERIOD,
-                                es.bgJobCountInMaxPeriod);
-                        proto.write(
-                                StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_IN_WINDOW,
-                                es.sessionCountInWindow);
-                        proto.write(
-                                StateControllerProto.QuotaController.ExecutionStats.IN_QUOTA_TIME_ELAPSED,
-                                es.inQuotaTimeElapsed);
-                        proto.write(
-                                StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_EXPIRATION_TIME_ELAPSED,
-                                es.jobRateLimitExpirationTimeElapsed);
-                        proto.write(
-                                StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_IN_RATE_LIMITING_WINDOW,
-                                es.jobCountInRateLimitingWindow);
-                        proto.write(
-                                StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_EXPIRATION_TIME_ELAPSED,
-                                es.sessionRateLimitExpirationTimeElapsed);
-                        proto.write(
-                                StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_IN_RATE_LIMITING_WINDOW,
-                                es.sessionCountInRateLimitingWindow);
-                        proto.end(esToken);
-                    }
-                }
-
-                QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, pkgName);
-                if (alarmListener != null) {
-                    final long alToken = proto.start(
-                            StateControllerProto.QuotaController.PackageStats.IN_QUOTA_ALARM_LISTENER);
-                    proto.write(StateControllerProto.QuotaController.AlarmListener.IS_WAITING,
-                            alarmListener.isWaiting());
-                    proto.write(
-                            StateControllerProto.QuotaController.AlarmListener.TRIGGER_TIME_ELAPSED,
-                            alarmListener.getTriggerTimeElapsed());
-                    proto.end(alToken);
-                }
-
-                proto.end(psToken);
-            }
-        }
-
-        proto.end(mToken);
-        proto.end(token);
-    }
-
-    @Override
-    public void dumpConstants(IndentingPrintWriter pw) {
-        mQcConstants.dump(pw);
-    }
-
-    @Override
-    public void dumpConstants(ProtoOutputStream proto) {
-        mQcConstants.dump(proto);
-    }
-}
diff --git a/services/core/java/com/android/server/job/controllers/StateController.java b/services/core/java/com/android/server/job/controllers/StateController.java
deleted file mode 100644
index 51be38b..0000000
--- a/services/core/java/com/android/server/job/controllers/StateController.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job.controllers;
-
-import static com.android.server.job.JobSchedulerService.DEBUG;
-
-import android.content.Context;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.job.JobSchedulerService;
-import com.android.server.job.JobSchedulerService.Constants;
-import com.android.server.job.StateChangedListener;
-
-import java.util.function.Predicate;
-
-/**
- * Incorporates shared controller logic between the various controllers of the JobManager.
- * These are solely responsible for tracking a list of jobs, and notifying the JM when these
- * are ready to run, or whether they must be stopped.
- */
-public abstract class StateController {
-    private static final String TAG = "JobScheduler.SC";
-
-    protected final JobSchedulerService mService;
-    protected final StateChangedListener mStateChangedListener;
-    protected final Context mContext;
-    protected final Object mLock;
-    protected final Constants mConstants;
-
-    StateController(JobSchedulerService service) {
-        mService = service;
-        mStateChangedListener = service;
-        mContext = service.getTestableContext();
-        mLock = service.getLock();
-        mConstants = service.getConstants();
-    }
-
-    /**
-     * Called when the system boot phase has reached
-     * {@link com.android.server.SystemService#PHASE_SYSTEM_SERVICES_READY}.
-     */
-    public void onSystemServicesReady() {
-    }
-
-    /**
-     * Implement the logic here to decide whether a job should be tracked by this controller.
-     * This logic is put here so the JobManager can be completely agnostic of Controller logic.
-     * Also called when updating a task, so implementing controllers have to be aware of
-     * preexisting tasks.
-     */
-    public abstract void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob);
-
-    /**
-     * Optionally implement logic here to prepare the job to be executed.
-     */
-    public void prepareForExecutionLocked(JobStatus jobStatus) {
-    }
-
-    /**
-     * Remove task - this will happen if the task is cancelled, completed, etc.
-     */
-    public abstract void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
-            boolean forUpdate);
-
-    /**
-     * Called when a new job is being created to reschedule an old failed job.
-     */
-    public void rescheduleForFailureLocked(JobStatus newJob, JobStatus failureToReschedule) {
-    }
-
-    /**
-     * Called when the JobScheduler.Constants are updated.
-     */
-    public void onConstantsUpdatedLocked() {
-    }
-
-    /** Called when a package is uninstalled from the device (not for an update). */
-    public void onAppRemovedLocked(String packageName, int uid) {
-    }
-
-    /** Called when a user is removed from the device. */
-    public void onUserRemovedLocked(int userId) {
-    }
-
-    /**
-     * Called when JobSchedulerService has determined that the job is not ready to be run. The
-     * Controller can evaluate if it can or should do something to promote this job's readiness.
-     */
-    public void evaluateStateLocked(JobStatus jobStatus) {
-    }
-
-    /**
-     * Called when something with the UID has changed. The controller should re-evaluate any
-     * internal state tracking dependent on this UID.
-     */
-    public void reevaluateStateLocked(int uid) {
-    }
-
-    protected boolean wouldBeReadyWithConstraintLocked(JobStatus jobStatus, int constraint) {
-        // This is very cheap to check (just a few conditions on data in JobStatus).
-        final boolean jobWouldBeReady = jobStatus.wouldBeReadyWithConstraint(constraint);
-        if (DEBUG) {
-            Slog.v(TAG, "wouldBeReadyWithConstraintLocked: " + jobStatus.toShortString()
-                    + " constraint=" + constraint
-                    + " readyWithConstraint=" + jobWouldBeReady);
-        }
-        if (!jobWouldBeReady) {
-            // If the job wouldn't be ready, nothing to do here.
-            return false;
-        }
-
-        // This is potentially more expensive since JSS may have to query component
-        // presence.
-        return mService.areComponentsInPlaceLocked(jobStatus);
-    }
-
-    public abstract void dumpControllerStateLocked(IndentingPrintWriter pw,
-            Predicate<JobStatus> predicate);
-    public abstract void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
-            Predicate<JobStatus> predicate);
-
-    /** Dump any internal constants the Controller may have. */
-    public void dumpConstants(IndentingPrintWriter pw) {
-    }
-
-    /** Dump any internal constants the Controller may have. */
-    public void dumpConstants(ProtoOutputStream proto) {
-    }
-}
diff --git a/services/core/java/com/android/server/job/controllers/StorageController.java b/services/core/java/com/android/server/job/controllers/StorageController.java
deleted file mode 100644
index 51187df..0000000
--- a/services/core/java/com/android/server/job/controllers/StorageController.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job.controllers;
-
-import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.UserHandle;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.job.JobSchedulerService;
-import com.android.server.job.StateControllerProto;
-import com.android.server.storage.DeviceStorageMonitorService;
-
-import java.util.function.Predicate;
-
-/**
- * Simple controller that tracks the status of the device's storage.
- */
-public final class StorageController extends StateController {
-    private static final String TAG = "JobScheduler.Storage";
-    private static final boolean DEBUG = JobSchedulerService.DEBUG
-            || Log.isLoggable(TAG, Log.DEBUG);
-
-    private final ArraySet<JobStatus> mTrackedTasks = new ArraySet<JobStatus>();
-    private final StorageTracker mStorageTracker;
-
-    @VisibleForTesting
-    public StorageTracker getTracker() {
-        return mStorageTracker;
-    }
-
-    public StorageController(JobSchedulerService service) {
-        super(service);
-        mStorageTracker = new StorageTracker();
-        mStorageTracker.startTracking();
-    }
-
-    @Override
-    public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
-        if (taskStatus.hasStorageNotLowConstraint()) {
-            mTrackedTasks.add(taskStatus);
-            taskStatus.setTrackingController(JobStatus.TRACKING_STORAGE);
-            taskStatus.setStorageNotLowConstraintSatisfied(mStorageTracker.isStorageNotLow());
-        }
-    }
-
-    @Override
-    public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob,
-            boolean forUpdate) {
-        if (taskStatus.clearTrackingController(JobStatus.TRACKING_STORAGE)) {
-            mTrackedTasks.remove(taskStatus);
-        }
-    }
-
-    private void maybeReportNewStorageState() {
-        final boolean storageNotLow = mStorageTracker.isStorageNotLow();
-        boolean reportChange = false;
-        synchronized (mLock) {
-            for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
-                final JobStatus ts = mTrackedTasks.valueAt(i);
-                reportChange |= ts.setStorageNotLowConstraintSatisfied(storageNotLow);
-            }
-        }
-        if (storageNotLow) {
-            // Tell the scheduler that any ready jobs should be flushed.
-            mStateChangedListener.onRunJobNow(null);
-        } else if (reportChange) {
-            // Let the scheduler know that state has changed. This may or may not result in an
-            // execution.
-            mStateChangedListener.onControllerStateChanged();
-        }
-    }
-
-    public final class StorageTracker extends BroadcastReceiver {
-        /**
-         * Track whether storage is low.
-         */
-        private boolean mStorageLow;
-        /** Sequence number of last broadcast. */
-        private int mLastStorageSeq = -1;
-
-        public StorageTracker() {
-        }
-
-        public void startTracking() {
-            IntentFilter filter = new IntentFilter();
-
-            // Storage status.  Just need to register, since STORAGE_LOW is a sticky
-            // broadcast we will receive that if it is currently active.
-            filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
-            filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
-            mContext.registerReceiver(this, filter);
-        }
-
-        public boolean isStorageNotLow() {
-            return !mStorageLow;
-        }
-
-        public int getSeq() {
-            return mLastStorageSeq;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            onReceiveInternal(intent);
-        }
-
-        @VisibleForTesting
-        public void onReceiveInternal(Intent intent) {
-            final String action = intent.getAction();
-            mLastStorageSeq = intent.getIntExtra(DeviceStorageMonitorService.EXTRA_SEQUENCE,
-                    mLastStorageSeq);
-            if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Available storage too low to do work. @ "
-                            + sElapsedRealtimeClock.millis());
-                }
-                mStorageLow = true;
-                maybeReportNewStorageState();
-            } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Available storage high enough to do work. @ "
-                            + sElapsedRealtimeClock.millis());
-                }
-                mStorageLow = false;
-                maybeReportNewStorageState();
-            }
-        }
-    }
-
-    @Override
-    public void dumpControllerStateLocked(IndentingPrintWriter pw,
-            Predicate<JobStatus> predicate) {
-        pw.println("Not low: " + mStorageTracker.isStorageNotLow());
-        pw.println("Sequence: " + mStorageTracker.getSeq());
-        pw.println();
-
-        for (int i = 0; i < mTrackedTasks.size(); i++) {
-            final JobStatus js = mTrackedTasks.valueAt(i);
-            if (!predicate.test(js)) {
-                continue;
-            }
-            pw.print("#");
-            js.printUniqueId(pw);
-            pw.print(" from ");
-            UserHandle.formatUid(pw, js.getSourceUid());
-            pw.println();
-        }
-    }
-
-    @Override
-    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
-            Predicate<JobStatus> predicate) {
-        final long token = proto.start(fieldId);
-        final long mToken = proto.start(StateControllerProto.STORAGE);
-
-        proto.write(StateControllerProto.StorageController.IS_STORAGE_NOT_LOW,
-                mStorageTracker.isStorageNotLow());
-        proto.write(StateControllerProto.StorageController.LAST_BROADCAST_SEQUENCE_NUMBER,
-                mStorageTracker.getSeq());
-
-        for (int i = 0; i < mTrackedTasks.size(); i++) {
-            final JobStatus js = mTrackedTasks.valueAt(i);
-            if (!predicate.test(js)) {
-                continue;
-            }
-            final long jsToken = proto.start(StateControllerProto.StorageController.TRACKED_JOBS);
-            js.writeToShortProto(proto, StateControllerProto.StorageController.TrackedJob.INFO);
-            proto.write(StateControllerProto.StorageController.TrackedJob.SOURCE_UID,
-                    js.getSourceUid());
-            proto.end(jsToken);
-        }
-
-        proto.end(mToken);
-        proto.end(token);
-    }
-}
diff --git a/services/core/java/com/android/server/job/controllers/TimeController.java b/services/core/java/com/android/server/job/controllers/TimeController.java
deleted file mode 100644
index 4c11947..0000000
--- a/services/core/java/com/android/server/job/controllers/TimeController.java
+++ /dev/null
@@ -1,504 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job.controllers;
-
-import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
-
-import android.annotation.Nullable;
-import android.app.AlarmManager;
-import android.app.AlarmManager.OnAlarmListener;
-import android.content.Context;
-import android.os.Process;
-import android.os.UserHandle;
-import android.os.WorkSource;
-import android.util.Log;
-import android.util.Slog;
-import android.util.TimeUtils;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.job.JobSchedulerService;
-import com.android.server.job.StateControllerProto;
-
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.function.Predicate;
-
-/**
- * This class sets an alarm for the next expiring job, and determines whether a job's minimum
- * delay has been satisfied.
- */
-public final class TimeController extends StateController {
-    private static final String TAG = "JobScheduler.Time";
-    private static final boolean DEBUG = JobSchedulerService.DEBUG
-            || Log.isLoggable(TAG, Log.DEBUG);
-
-    /** Deadline alarm tag for logging purposes */
-    private final String DEADLINE_TAG = "*job.deadline*";
-    /** Delay alarm tag for logging purposes */
-    private final String DELAY_TAG = "*job.delay*";
-
-    private long mNextJobExpiredElapsedMillis;
-    private long mNextDelayExpiredElapsedMillis;
-
-    private final boolean mChainedAttributionEnabled;
-
-    private AlarmManager mAlarmService = null;
-    /** List of tracked jobs, sorted asc. by deadline */
-    private final List<JobStatus> mTrackedJobs = new LinkedList<>();
-
-    public TimeController(JobSchedulerService service) {
-        super(service);
-
-        mNextJobExpiredElapsedMillis = Long.MAX_VALUE;
-        mNextDelayExpiredElapsedMillis = Long.MAX_VALUE;
-        mChainedAttributionEnabled = mService.isChainedAttributionEnabled();
-    }
-
-    /**
-     * Check if the job has a timing constraint, and if so determine where to insert it in our
-     * list.
-     */
-    @Override
-    public void maybeStartTrackingJobLocked(JobStatus job, JobStatus lastJob) {
-        if (job.hasTimingDelayConstraint() || job.hasDeadlineConstraint()) {
-            maybeStopTrackingJobLocked(job, null, false);
-
-            // First: check the constraints now, because if they are already satisfied
-            // then there is no need to track it.  This gives us a fast path for a common
-            // pattern of having a job with a 0 deadline constraint ("run immediately").
-            // Unlike most controllers, once one of our constraints has been satisfied, it
-            // will never be unsatisfied (our time base can not go backwards).
-            final long nowElapsedMillis = sElapsedRealtimeClock.millis();
-            if (job.hasDeadlineConstraint() && evaluateDeadlineConstraint(job, nowElapsedMillis)) {
-                return;
-            } else if (job.hasTimingDelayConstraint() && evaluateTimingDelayConstraint(job,
-                    nowElapsedMillis)) {
-                if (!job.hasDeadlineConstraint()) {
-                    // If it doesn't have a deadline, we'll never have to touch it again.
-                    return;
-                }
-            }
-
-            boolean isInsert = false;
-            ListIterator<JobStatus> it = mTrackedJobs.listIterator(mTrackedJobs.size());
-            while (it.hasPrevious()) {
-                JobStatus ts = it.previous();
-                if (ts.getLatestRunTimeElapsed() < job.getLatestRunTimeElapsed()) {
-                    // Insert
-                    isInsert = true;
-                    break;
-                }
-            }
-            if (isInsert) {
-                it.next();
-            }
-            it.add(job);
-
-            job.setTrackingController(JobStatus.TRACKING_TIME);
-            WorkSource ws = deriveWorkSource(job.getSourceUid(), job.getSourcePackageName());
-
-            // Only update alarms if the job would be ready with the relevant timing constraint
-            // satisfied.
-            if (job.hasTimingDelayConstraint()
-                    && wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_TIMING_DELAY)) {
-                maybeUpdateDelayAlarmLocked(job.getEarliestRunTime(), ws);
-            }
-            if (job.hasDeadlineConstraint()
-                    && wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_DEADLINE)) {
-                maybeUpdateDeadlineAlarmLocked(job.getLatestRunTimeElapsed(), ws);
-            }
-        }
-    }
-
-    /**
-     * When we stop tracking a job, we only need to update our alarms if the job we're no longer
-     * tracking was the one our alarms were based off of.
-     */
-    @Override
-    public void maybeStopTrackingJobLocked(JobStatus job, JobStatus incomingJob,
-            boolean forUpdate) {
-        if (job.clearTrackingController(JobStatus.TRACKING_TIME)) {
-            if (mTrackedJobs.remove(job)) {
-                checkExpiredDelaysAndResetAlarm();
-                checkExpiredDeadlinesAndResetAlarm();
-            }
-        }
-    }
-
-    @Override
-    public void evaluateStateLocked(JobStatus job) {
-        final long nowElapsedMillis = sElapsedRealtimeClock.millis();
-
-        // Check deadline constraint first because if it's satisfied, we avoid a little bit of
-        // unnecessary processing of the timing delay.
-        if (job.hasDeadlineConstraint()
-                && !job.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE)
-                && job.getLatestRunTimeElapsed() <= mNextJobExpiredElapsedMillis) {
-            if (evaluateDeadlineConstraint(job, nowElapsedMillis)) {
-                checkExpiredDeadlinesAndResetAlarm();
-                checkExpiredDelaysAndResetAlarm();
-            } else {
-                final boolean isAlarmForJob =
-                        job.getLatestRunTimeElapsed() == mNextJobExpiredElapsedMillis;
-                final boolean wouldBeReady = wouldBeReadyWithConstraintLocked(
-                        job, JobStatus.CONSTRAINT_DEADLINE);
-                if ((isAlarmForJob && !wouldBeReady) || (!isAlarmForJob && wouldBeReady)) {
-                    checkExpiredDeadlinesAndResetAlarm();
-                }
-            }
-        }
-        if (job.hasTimingDelayConstraint()
-                && !job.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)
-                && job.getEarliestRunTime() <= mNextDelayExpiredElapsedMillis) {
-            if (evaluateTimingDelayConstraint(job, nowElapsedMillis)) {
-                checkExpiredDelaysAndResetAlarm();
-            } else {
-                final boolean isAlarmForJob =
-                        job.getEarliestRunTime() == mNextDelayExpiredElapsedMillis;
-                final boolean wouldBeReady = wouldBeReadyWithConstraintLocked(
-                        job, JobStatus.CONSTRAINT_TIMING_DELAY);
-                if ((isAlarmForJob && !wouldBeReady) || (!isAlarmForJob && wouldBeReady)) {
-                    checkExpiredDelaysAndResetAlarm();
-                }
-            }
-        }
-    }
-
-    @Override
-    public void reevaluateStateLocked(int uid) {
-        checkExpiredDeadlinesAndResetAlarm();
-        checkExpiredDelaysAndResetAlarm();
-    }
-
-    /**
-     * Determines whether this controller can stop tracking the given job.
-     * The controller is no longer interested in a job once its time constraint is satisfied, and
-     * the job's deadline is fulfilled - unlike other controllers a time constraint can't toggle
-     * back and forth.
-     */
-    private boolean canStopTrackingJobLocked(JobStatus job) {
-        return (!job.hasTimingDelayConstraint()
-                        || job.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY))
-                && (!job.hasDeadlineConstraint()
-                        || job.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
-    }
-
-    private void ensureAlarmServiceLocked() {
-        if (mAlarmService == null) {
-            mAlarmService = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
-        }
-    }
-
-    /**
-     * Checks list of jobs for ones that have an expired deadline, sending them to the JobScheduler
-     * if so, removing them from this list, and updating the alarm for the next expiry time.
-     */
-    @VisibleForTesting
-    void checkExpiredDeadlinesAndResetAlarm() {
-        synchronized (mLock) {
-            long nextExpiryTime = Long.MAX_VALUE;
-            int nextExpiryUid = 0;
-            String nextExpiryPackageName = null;
-            final long nowElapsedMillis = sElapsedRealtimeClock.millis();
-
-            ListIterator<JobStatus> it = mTrackedJobs.listIterator();
-            while (it.hasNext()) {
-                JobStatus job = it.next();
-                if (!job.hasDeadlineConstraint()) {
-                    continue;
-                }
-
-                if (evaluateDeadlineConstraint(job, nowElapsedMillis)) {
-                    if (job.isReady()) {
-                        // If the job still isn't ready, there's no point trying to rush the
-                        // Scheduler.
-                        mStateChangedListener.onRunJobNow(job);
-                    }
-                    it.remove();
-                } else {  // Sorted by expiry time, so take the next one and stop.
-                    if (!wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_DEADLINE)) {
-                        if (DEBUG) {
-                            Slog.i(TAG,
-                                    "Skipping " + job + " because deadline won't make it ready.");
-                        }
-                        continue;
-                    }
-                    nextExpiryTime = job.getLatestRunTimeElapsed();
-                    nextExpiryUid = job.getSourceUid();
-                    nextExpiryPackageName = job.getSourcePackageName();
-                    break;
-                }
-            }
-            setDeadlineExpiredAlarmLocked(nextExpiryTime,
-                    deriveWorkSource(nextExpiryUid, nextExpiryPackageName));
-        }
-    }
-
-    /** @return true if the job's deadline constraint is satisfied */
-    private boolean evaluateDeadlineConstraint(JobStatus job, long nowElapsedMillis) {
-        final long jobDeadline = job.getLatestRunTimeElapsed();
-
-        if (jobDeadline <= nowElapsedMillis) {
-            if (job.hasTimingDelayConstraint()) {
-                job.setTimingDelayConstraintSatisfied(true);
-            }
-            job.setDeadlineConstraintSatisfied(true);
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Handles alarm that notifies us that a job's delay has expired. Iterates through the list of
-     * tracked jobs and marks them as ready as appropriate.
-     */
-    @VisibleForTesting
-    void checkExpiredDelaysAndResetAlarm() {
-        synchronized (mLock) {
-            final long nowElapsedMillis = sElapsedRealtimeClock.millis();
-            long nextDelayTime = Long.MAX_VALUE;
-            int nextDelayUid = 0;
-            String nextDelayPackageName = null;
-            boolean ready = false;
-            Iterator<JobStatus> it = mTrackedJobs.iterator();
-            while (it.hasNext()) {
-                final JobStatus job = it.next();
-                if (!job.hasTimingDelayConstraint()) {
-                    continue;
-                }
-                if (evaluateTimingDelayConstraint(job, nowElapsedMillis)) {
-                    if (canStopTrackingJobLocked(job)) {
-                        it.remove();
-                    }
-                    if (job.isReady()) {
-                        ready = true;
-                    }
-                } else {
-                    if (!wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_TIMING_DELAY)) {
-                        if (DEBUG) {
-                            Slog.i(TAG,
-                                    "Skipping " + job + " because delay won't make it ready.");
-                        }
-                        continue;
-                    }
-                    // If this job still doesn't have its delay constraint satisfied,
-                    // then see if it is the next upcoming delay time for the alarm.
-                    final long jobDelayTime = job.getEarliestRunTime();
-                    if (nextDelayTime > jobDelayTime) {
-                        nextDelayTime = jobDelayTime;
-                        nextDelayUid = job.getSourceUid();
-                        nextDelayPackageName = job.getSourcePackageName();
-                    }
-                }
-            }
-            if (ready) {
-                mStateChangedListener.onControllerStateChanged();
-            }
-            setDelayExpiredAlarmLocked(nextDelayTime,
-                    deriveWorkSource(nextDelayUid, nextDelayPackageName));
-        }
-    }
-
-    private WorkSource deriveWorkSource(int uid, @Nullable String packageName) {
-        if (mChainedAttributionEnabled) {
-            WorkSource ws = new WorkSource();
-            ws.createWorkChain()
-                    .addNode(uid, packageName)
-                    .addNode(Process.SYSTEM_UID, "JobScheduler");
-            return ws;
-        } else {
-            return packageName == null ? new WorkSource(uid) : new WorkSource(uid, packageName);
-        }
-    }
-
-    /** @return true if the job's delay constraint is satisfied */
-    private boolean evaluateTimingDelayConstraint(JobStatus job, long nowElapsedMillis) {
-        final long jobDelayTime = job.getEarliestRunTime();
-        if (jobDelayTime <= nowElapsedMillis) {
-            job.setTimingDelayConstraintSatisfied(true);
-            return true;
-        }
-        return false;
-    }
-
-    private void maybeUpdateDelayAlarmLocked(long delayExpiredElapsed, WorkSource ws) {
-        if (delayExpiredElapsed < mNextDelayExpiredElapsedMillis) {
-            setDelayExpiredAlarmLocked(delayExpiredElapsed, ws);
-        }
-    }
-
-    private void maybeUpdateDeadlineAlarmLocked(long deadlineExpiredElapsed, WorkSource ws) {
-        if (deadlineExpiredElapsed < mNextJobExpiredElapsedMillis) {
-            setDeadlineExpiredAlarmLocked(deadlineExpiredElapsed, ws);
-        }
-    }
-
-    /**
-     * Set an alarm with the {@link android.app.AlarmManager} for the next time at which a job's
-     * delay will expire.
-     * This alarm <b>will</b> wake up the phone.
-     */
-    private void setDelayExpiredAlarmLocked(long alarmTimeElapsedMillis, WorkSource ws) {
-        alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis);
-        if (mNextDelayExpiredElapsedMillis == alarmTimeElapsedMillis) {
-            return;
-        }
-        mNextDelayExpiredElapsedMillis = alarmTimeElapsedMillis;
-        updateAlarmWithListenerLocked(DELAY_TAG, mNextDelayExpiredListener,
-                mNextDelayExpiredElapsedMillis, ws);
-    }
-
-    /**
-     * Set an alarm with the {@link android.app.AlarmManager} for the next time at which a job's
-     * deadline will expire.
-     * This alarm <b>will</b> wake up the phone.
-     */
-    private void setDeadlineExpiredAlarmLocked(long alarmTimeElapsedMillis, WorkSource ws) {
-        alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis);
-        if (mNextJobExpiredElapsedMillis == alarmTimeElapsedMillis) {
-            return;
-        }
-        mNextJobExpiredElapsedMillis = alarmTimeElapsedMillis;
-        updateAlarmWithListenerLocked(DEADLINE_TAG, mDeadlineExpiredListener,
-                mNextJobExpiredElapsedMillis, ws);
-    }
-
-    private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) {
-        return Math.max(proposedAlarmTimeElapsedMillis, sElapsedRealtimeClock.millis());
-    }
-
-    private void updateAlarmWithListenerLocked(String tag, OnAlarmListener listener,
-            long alarmTimeElapsed, WorkSource ws) {
-        ensureAlarmServiceLocked();
-        if (alarmTimeElapsed == Long.MAX_VALUE) {
-            mAlarmService.cancel(listener);
-        } else {
-            if (DEBUG) {
-                Slog.d(TAG, "Setting " + tag + " for: " + alarmTimeElapsed);
-            }
-            mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTimeElapsed,
-                    AlarmManager.WINDOW_HEURISTIC, 0, tag, listener, null, ws);
-        }
-    }
-
-    // Job/delay expiration alarm handling
-
-    private final OnAlarmListener mDeadlineExpiredListener = new OnAlarmListener() {
-        @Override
-        public void onAlarm() {
-            if (DEBUG) {
-                Slog.d(TAG, "Deadline-expired alarm fired");
-            }
-            checkExpiredDeadlinesAndResetAlarm();
-        }
-    };
-
-    private final OnAlarmListener mNextDelayExpiredListener = new OnAlarmListener() {
-        @Override
-        public void onAlarm() {
-            if (DEBUG) {
-                Slog.d(TAG, "Delay-expired alarm fired");
-            }
-            checkExpiredDelaysAndResetAlarm();
-        }
-    };
-
-    @VisibleForTesting
-    void recheckAlarmsLocked() {
-        checkExpiredDeadlinesAndResetAlarm();
-        checkExpiredDelaysAndResetAlarm();
-    }
-
-    @Override
-    public void dumpControllerStateLocked(IndentingPrintWriter pw,
-            Predicate<JobStatus> predicate) {
-        final long nowElapsed = sElapsedRealtimeClock.millis();
-        pw.println("Elapsed clock: " + nowElapsed);
-
-        pw.print("Next delay alarm in ");
-        TimeUtils.formatDuration(mNextDelayExpiredElapsedMillis, nowElapsed, pw);
-        pw.println();
-        pw.print("Next deadline alarm in ");
-        TimeUtils.formatDuration(mNextJobExpiredElapsedMillis, nowElapsed, pw);
-        pw.println();
-        pw.println();
-
-        for (JobStatus ts : mTrackedJobs) {
-            if (!predicate.test(ts)) {
-                continue;
-            }
-            pw.print("#");
-            ts.printUniqueId(pw);
-            pw.print(" from ");
-            UserHandle.formatUid(pw, ts.getSourceUid());
-            pw.print(": Delay=");
-            if (ts.hasTimingDelayConstraint()) {
-                TimeUtils.formatDuration(ts.getEarliestRunTime(), nowElapsed, pw);
-            } else {
-                pw.print("N/A");
-            }
-            pw.print(", Deadline=");
-            if (ts.hasDeadlineConstraint()) {
-                TimeUtils.formatDuration(ts.getLatestRunTimeElapsed(), nowElapsed, pw);
-            } else {
-                pw.print("N/A");
-            }
-            pw.println();
-        }
-    }
-
-    @Override
-    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
-            Predicate<JobStatus> predicate) {
-        final long token = proto.start(fieldId);
-        final long mToken = proto.start(StateControllerProto.TIME);
-
-        final long nowElapsed = sElapsedRealtimeClock.millis();
-        proto.write(StateControllerProto.TimeController.NOW_ELAPSED_REALTIME, nowElapsed);
-        proto.write(StateControllerProto.TimeController.TIME_UNTIL_NEXT_DELAY_ALARM_MS,
-                mNextDelayExpiredElapsedMillis - nowElapsed);
-        proto.write(StateControllerProto.TimeController.TIME_UNTIL_NEXT_DEADLINE_ALARM_MS,
-                mNextJobExpiredElapsedMillis - nowElapsed);
-
-        for (JobStatus ts : mTrackedJobs) {
-            if (!predicate.test(ts)) {
-                continue;
-            }
-            final long tsToken = proto.start(StateControllerProto.TimeController.TRACKED_JOBS);
-            ts.writeToShortProto(proto, StateControllerProto.TimeController.TrackedJob.INFO);
-
-            proto.write(StateControllerProto.TimeController.TrackedJob.HAS_TIMING_DELAY_CONSTRAINT,
-                    ts.hasTimingDelayConstraint());
-            proto.write(StateControllerProto.TimeController.TrackedJob.DELAY_TIME_REMAINING_MS,
-                    ts.getEarliestRunTime() - nowElapsed);
-
-            proto.write(StateControllerProto.TimeController.TrackedJob.HAS_DEADLINE_CONSTRAINT,
-                    ts.hasDeadlineConstraint());
-            proto.write(StateControllerProto.TimeController.TrackedJob.TIME_REMAINING_UNTIL_DEADLINE_MS,
-                    ts.getLatestRunTimeElapsed() - nowElapsed);
-
-            proto.end(tsToken);
-        }
-
-        proto.end(mToken);
-        proto.end(token);
-    }
-}
diff --git a/services/core/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java b/services/core/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java
deleted file mode 100644
index 82c33f5..0000000
--- a/services/core/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job.controllers.idle;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.util.Log;
-import android.util.Slog;
-
-import com.android.server.am.ActivityManagerService;
-import com.android.server.job.JobSchedulerService;
-
-import java.io.PrintWriter;
-
-public final class CarIdlenessTracker extends BroadcastReceiver implements IdlenessTracker {
-    private static final String TAG = "JobScheduler.CarIdlenessTracker";
-    private static final boolean DEBUG = JobSchedulerService.DEBUG
-            || Log.isLoggable(TAG, Log.DEBUG);
-
-    public static final String ACTION_GARAGE_MODE_ON =
-            "com.android.server.jobscheduler.GARAGE_MODE_ON";
-    public static final String ACTION_GARAGE_MODE_OFF =
-            "com.android.server.jobscheduler.GARAGE_MODE_OFF";
-
-    public static final String ACTION_FORCE_IDLE = "com.android.server.jobscheduler.FORCE_IDLE";
-    public static final String ACTION_UNFORCE_IDLE = "com.android.server.jobscheduler.UNFORCE_IDLE";
-
-    // After construction, mutations of idle/screen-on state will only happen
-    // on the main looper thread, either in onReceive() or in an alarm callback.
-    private boolean mIdle;
-    private boolean mGarageModeOn;
-    private boolean mForced;
-    private IdlenessListener mIdleListener;
-
-    public CarIdlenessTracker() {
-        // At boot we presume that the user has just "interacted" with the
-        // device in some meaningful way.
-        mIdle = false;
-        mGarageModeOn = false;
-        mForced = false;
-    }
-
-    @Override
-    public boolean isIdle() {
-        return mIdle;
-    }
-
-    @Override
-    public void startTracking(Context context, IdlenessListener listener) {
-        mIdleListener = listener;
-
-        IntentFilter filter = new IntentFilter();
-
-        // Screen state
-        filter.addAction(Intent.ACTION_SCREEN_ON);
-
-        // State of GarageMode
-        filter.addAction(ACTION_GARAGE_MODE_ON);
-        filter.addAction(ACTION_GARAGE_MODE_OFF);
-
-        // Debugging/instrumentation
-        filter.addAction(ACTION_FORCE_IDLE);
-        filter.addAction(ACTION_UNFORCE_IDLE);
-        filter.addAction(ActivityManagerService.ACTION_TRIGGER_IDLE);
-
-        context.registerReceiver(this, filter);
-    }
-
-    @Override
-    public void dump(PrintWriter pw) {
-        pw.print("  mIdle: "); pw.println(mIdle);
-        pw.print("  mGarageModeOn: "); pw.println(mGarageModeOn);
-    }
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        final String action = intent.getAction();
-        logIfDebug("Received action: " + action);
-
-        // Check for forced actions
-        if (action.equals(ACTION_FORCE_IDLE)) {
-            logIfDebug("Forcing idle...");
-            setForceIdleState(true);
-        } else if (action.equals(ACTION_UNFORCE_IDLE)) {
-            logIfDebug("Unforcing idle...");
-            setForceIdleState(false);
-        } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
-            logIfDebug("Screen is on...");
-            handleScreenOn();
-        } else if (action.equals(ACTION_GARAGE_MODE_ON)) {
-            logIfDebug("GarageMode is on...");
-            mGarageModeOn = true;
-            updateIdlenessState();
-        } else if (action.equals(ACTION_GARAGE_MODE_OFF)) {
-            logIfDebug("GarageMode is off...");
-            mGarageModeOn = false;
-            updateIdlenessState();
-        } else if (action.equals(ActivityManagerService.ACTION_TRIGGER_IDLE)) {
-            if (!mGarageModeOn) {
-                logIfDebug("Idle trigger fired...");
-                triggerIdlenessOnce();
-            } else {
-                logIfDebug("TRIGGER_IDLE received but not changing state; idle="
-                        + mIdle + " screen=" + mGarageModeOn);
-            }
-        }
-    }
-
-    private void setForceIdleState(boolean forced) {
-        mForced = forced;
-        updateIdlenessState();
-    }
-
-    private void updateIdlenessState() {
-        final boolean newState = (mForced || mGarageModeOn);
-        if (mIdle != newState) {
-            // State of idleness changed. Notifying idleness controller
-            logIfDebug("Device idleness changed. New idle=" + newState);
-            mIdle = newState;
-            mIdleListener.reportNewIdleState(mIdle);
-        } else {
-            // Nothing changed, device idleness is in the same state as new state
-            logIfDebug("Device idleness is the same. Current idle=" + newState);
-        }
-    }
-
-    private void triggerIdlenessOnce() {
-        // This is simply triggering idleness once until some constraint will switch it back off
-        if (mIdle) {
-            // Already in idle state. Nothing to do
-            logIfDebug("Device is already idle");
-        } else {
-            // Going idle once
-            logIfDebug("Device is going idle once");
-            mIdle = true;
-            mIdleListener.reportNewIdleState(mIdle);
-        }
-    }
-
-    private void handleScreenOn() {
-        if (mForced || mGarageModeOn) {
-            // Even though screen is on, the device remains idle
-            logIfDebug("Screen is on, but device cannot exit idle");
-        } else if (mIdle) {
-            // Exiting idle
-            logIfDebug("Device is exiting idle");
-            mIdle = false;
-        } else {
-            // Already in non-idle state. Nothing to do
-            logIfDebug("Device is already non-idle");
-        }
-    }
-
-    private static void logIfDebug(String msg) {
-        if (DEBUG) {
-            Slog.v(TAG, msg);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java b/services/core/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java
deleted file mode 100644
index a85bd40..0000000
--- a/services/core/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job.controllers.idle;
-
-import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
-
-import android.app.AlarmManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-
-import android.util.Log;
-import android.util.Slog;
-import com.android.server.am.ActivityManagerService;
-import com.android.server.job.JobSchedulerService;
-
-import java.io.PrintWriter;
-
-public final class DeviceIdlenessTracker extends BroadcastReceiver implements IdlenessTracker {
-    private static final String TAG = "JobScheduler.DeviceIdlenessTracker";
-    private static final boolean DEBUG = JobSchedulerService.DEBUG
-            || Log.isLoggable(TAG, Log.DEBUG);
-
-    private AlarmManager mAlarm;
-
-    // After construction, mutations of idle/screen-on state will only happen
-    // on the main looper thread, either in onReceive() or in an alarm callback.
-    private long mInactivityIdleThreshold;
-    private long mIdleWindowSlop;
-    private boolean mIdle;
-    private boolean mScreenOn;
-    private boolean mDockIdle;
-    private IdlenessListener mIdleListener;
-
-    private AlarmManager.OnAlarmListener mIdleAlarmListener = () -> {
-        handleIdleTrigger();
-    };
-
-    public DeviceIdlenessTracker() {
-        // At boot we presume that the user has just "interacted" with the
-        // device in some meaningful way.
-        mIdle = false;
-        mScreenOn = true;
-        mDockIdle = false;
-    }
-
-    @Override
-    public boolean isIdle() {
-        return mIdle;
-    }
-
-    @Override
-    public void startTracking(Context context, IdlenessListener listener) {
-        mIdleListener = listener;
-        mInactivityIdleThreshold = context.getResources().getInteger(
-                com.android.internal.R.integer.config_jobSchedulerInactivityIdleThreshold);
-        mIdleWindowSlop = context.getResources().getInteger(
-                com.android.internal.R.integer.config_jobSchedulerIdleWindowSlop);
-        mAlarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
-
-        IntentFilter filter = new IntentFilter();
-
-        // Screen state
-        filter.addAction(Intent.ACTION_SCREEN_ON);
-        filter.addAction(Intent.ACTION_SCREEN_OFF);
-
-        // Dreaming state
-        filter.addAction(Intent.ACTION_DREAMING_STARTED);
-        filter.addAction(Intent.ACTION_DREAMING_STOPPED);
-
-        // Debugging/instrumentation
-        filter.addAction(ActivityManagerService.ACTION_TRIGGER_IDLE);
-
-        // Wireless charging dock state
-        filter.addAction(Intent.ACTION_DOCK_IDLE);
-        filter.addAction(Intent.ACTION_DOCK_ACTIVE);
-
-        context.registerReceiver(this, filter);
-    }
-
-    @Override
-    public void dump(PrintWriter pw) {
-        pw.print("  mIdle: "); pw.println(mIdle);
-        pw.print("  mScreenOn: "); pw.println(mScreenOn);
-        pw.print("  mDockIdle: "); pw.println(mDockIdle);
-    }
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        final String action = intent.getAction();
-        if (action.equals(Intent.ACTION_SCREEN_ON)
-                || action.equals(Intent.ACTION_DREAMING_STOPPED)
-                || action.equals(Intent.ACTION_DOCK_ACTIVE)) {
-            if (action.equals(Intent.ACTION_DOCK_ACTIVE)) {
-                if (!mScreenOn) {
-                    // Ignore this intent during screen off
-                    return;
-                } else {
-                    mDockIdle = false;
-                }
-            } else {
-                mScreenOn = true;
-                mDockIdle = false;
-            }
-            if (DEBUG) {
-                Slog.v(TAG,"exiting idle : " + action);
-            }
-            //cancel the alarm
-            mAlarm.cancel(mIdleAlarmListener);
-            if (mIdle) {
-            // possible transition to not-idle
-                mIdle = false;
-                mIdleListener.reportNewIdleState(mIdle);
-            }
-        } else if (action.equals(Intent.ACTION_SCREEN_OFF)
-                || action.equals(Intent.ACTION_DREAMING_STARTED)
-                || action.equals(Intent.ACTION_DOCK_IDLE)) {
-            // when the screen goes off or dreaming starts or wireless charging dock in idle,
-            // we schedule the alarm that will tell us when we have decided the device is
-            // truly idle.
-            if (action.equals(Intent.ACTION_DOCK_IDLE)) {
-                if (!mScreenOn) {
-                    // Ignore this intent during screen off
-                    return;
-                } else {
-                    mDockIdle = true;
-                }
-            } else {
-                mScreenOn = false;
-                mDockIdle = false;
-            }
-            final long nowElapsed = sElapsedRealtimeClock.millis();
-            final long when = nowElapsed + mInactivityIdleThreshold;
-            if (DEBUG) {
-                Slog.v(TAG, "Scheduling idle : " + action + " now:" + nowElapsed + " when="
-                        + when);
-            }
-            mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                    when, mIdleWindowSlop, "JS idleness", mIdleAlarmListener, null);
-        } else if (action.equals(ActivityManagerService.ACTION_TRIGGER_IDLE)) {
-            handleIdleTrigger();
-        }
-    }
-
-    private void handleIdleTrigger() {
-        // idle time starts now. Do not set mIdle if screen is on.
-        if (!mIdle && (!mScreenOn || mDockIdle)) {
-            if (DEBUG) {
-                Slog.v(TAG, "Idle trigger fired @ " + sElapsedRealtimeClock.millis());
-            }
-            mIdle = true;
-            mIdleListener.reportNewIdleState(mIdle);
-        } else {
-            if (DEBUG) {
-                Slog.v(TAG, "TRIGGER_IDLE received but not changing state; idle="
-                        + mIdle + " screen=" + mScreenOn);
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/job/controllers/idle/IdlenessListener.java b/services/core/java/com/android/server/job/controllers/idle/IdlenessListener.java
deleted file mode 100644
index 7ffd7cd..0000000
--- a/services/core/java/com/android/server/job/controllers/idle/IdlenessListener.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job.controllers.idle;
-
-/**
- * Interface through which an IdlenessTracker informs the job scheduler of
- * changes in the device's inactivity state.
- */
-public interface IdlenessListener {
-    /**
-     * Tell the job scheduler that the device's idle state has changed.
-     *
-     * @param deviceIsIdle {@code true} to indicate that the device is now considered
-     * to be idle; {@code false} to indicate that the device is now being interacted with,
-     * so jobs with idle constraints should not be run.
-     */
-    void reportNewIdleState(boolean deviceIsIdle);
-}
diff --git a/services/core/java/com/android/server/job/controllers/idle/IdlenessTracker.java b/services/core/java/com/android/server/job/controllers/idle/IdlenessTracker.java
deleted file mode 100644
index 09f01c2..0000000
--- a/services/core/java/com/android/server/job/controllers/idle/IdlenessTracker.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job.controllers.idle;
-
-import android.content.Context;
-
-import java.io.PrintWriter;
-
-public interface IdlenessTracker {
-    /**
-     * One-time initialization:  this method is called once, after construction of
-     * the IdlenessTracker instance.  This is when the tracker should actually begin
-     * monitoring whatever signals it consumes in deciding when the device is in a
-     * non-interacting state.  When the idle state changes thereafter, the given
-     * listener must be called to report the new state.
-     */
-    void startTracking(Context context, IdlenessListener listener);
-
-    /**
-     * Report whether the device is currently considered "idle" for purposes of
-     * running scheduled jobs with idleness constraints.
-     *
-     * @return {@code true} if the job scheduler should consider idleness
-     * constraints to be currently satisfied; {@code false} otherwise.
-     */
-    boolean isIdle();
-
-    /**
-     * Dump useful information about tracked idleness-related state in plaintext.
-     */
-    void dump(PrintWriter pw);
-}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 9fe44dc..8161ac9 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -102,7 +102,6 @@
 import com.android.server.input.InputManagerService;
 import com.android.server.inputmethod.InputMethodManagerService;
 import com.android.server.inputmethod.MultiClientInputMethodManagerService;
-import com.android.server.job.JobSchedulerService;
 import com.android.server.lights.LightsService;
 import com.android.server.media.MediaResourceMonitorService;
 import com.android.server.media.MediaRouterService;
@@ -1575,8 +1574,9 @@
             mSystemServiceManager.startService(ColorDisplayService.class);
             t.traceEnd();
 
+            // TODO(aml-jobscheduler): Think about how to do it properly.
             t.traceBegin("StartJobScheduler");
-            mSystemServiceManager.startService(JobSchedulerService.class);
+            mSystemServiceManager.startService(JOB_SCHEDULER_SERVICE_CLASS);
             t.traceEnd();
 
             t.traceBegin("StartSoundTrigger");
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 2baa4d8..ad94e61 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -20,6 +20,7 @@
     static_libs: [
         "services.core",
         "services.net",
+        "jobscheduler-service",
         "androidx.test.runner",
         "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 0614b3a..b37e460 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -40,6 +40,7 @@
         "hamcrest-library",
         "servicestests-utils",
         "xml-writer-device-lib",
+        "jobscheduler-service",
     ],
 
     aidl: {