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

Commit 2f36fd6f authored by Christopher Tate's avatar Christopher Tate
Browse files

Limit scheduled jobs to 100 per app

Packages that are entitled to schedule jobs on behalf of other uids
are not subject to the limit.  Also break the JobStore's monolithic
set of jobs into per-uid slices for efficiency and orthogonality.

Bug 27150350

Change-Id: I8f5f718bf200d55f9977a6fc53b7f617e7652ad9
parent 272fe133
Loading
Loading
Loading
Loading
+150 −82
Original line number Original line Diff line number Diff line
@@ -49,14 +49,13 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserHandle;
import android.os.Process;
import android.util.ArraySet;
import android.util.Slog;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseArray;


import com.android.internal.app.IBatteryStats;
import com.android.internal.app.IBatteryStats;
import com.android.server.DeviceIdleController;
import com.android.server.DeviceIdleController;
import com.android.server.LocalServices;
import com.android.server.LocalServices;
import com.android.server.job.JobStore.JobStatusFunctor;
import com.android.server.job.controllers.AppIdleController;
import com.android.server.job.controllers.AppIdleController;
import com.android.server.job.controllers.BatteryController;
import com.android.server.job.controllers.BatteryController;
import com.android.server.job.controllers.ConnectivityController;
import com.android.server.job.controllers.ConnectivityController;
@@ -80,11 +79,15 @@ import com.android.server.job.controllers.TimeController;
 */
 */
public final class JobSchedulerService extends com.android.server.SystemService
public final class JobSchedulerService extends com.android.server.SystemService
        implements StateChangedListener, JobCompletedListener {
        implements StateChangedListener, JobCompletedListener {
    static final String TAG = "JobSchedulerService";
    public static final boolean DEBUG = false;
    public static final boolean DEBUG = false;

    /** The number of concurrent jobs we run at one time. */
    /** The number of concurrent jobs we run at one time. */
    private static final int MAX_JOB_CONTEXTS_COUNT
    private static final int MAX_JOB_CONTEXTS_COUNT
            = ActivityManager.isLowRamDeviceStatic() ? 3 : 6;
            = ActivityManager.isLowRamDeviceStatic() ? 3 : 6;
    static final String TAG = "JobSchedulerService";
    /** The maximum number of jobs that we allow an unprivileged app to schedule */
    private static final int MAX_JOBS_PER_APP = 100;

    /** Global local for all job scheduler state. */
    /** Global local for all job scheduler state. */
    final Object mLock = new Object();
    final Object mLock = new Object();
    /** Master list of jobs. */
    /** Master list of jobs. */
@@ -250,6 +253,15 @@ public final class JobSchedulerService extends com.android.server.SystemService
        if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
        if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
        JobStatus toCancel;
        JobStatus toCancel;
        synchronized (mLock) {
        synchronized (mLock) {
            // Jobs on behalf of others don't apply to the per-app job cap
            if (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");
                }
            }

            toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
            toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
        }
        }
        startTrackingJob(jobStatus, toCancel);
        startTrackingJob(jobStatus, toCancel);
@@ -261,18 +273,16 @@ public final class JobSchedulerService extends com.android.server.SystemService
    }
    }


    public List<JobInfo> getPendingJobs(int uid) {
    public List<JobInfo> getPendingJobs(int uid) {
        ArrayList<JobInfo> outList = new ArrayList<JobInfo>();
        synchronized (mLock) {
        synchronized (mLock) {
            ArraySet<JobStatus> jobs = mJobs.getJobs();
            List<JobStatus> jobs = mJobs.getJobsByUid(uid);
            for (int i=0; i<jobs.size(); i++) {
            ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
                JobStatus job = jobs.valueAt(i);
            for (int i = jobs.size() - 1; i >= 0; i--) {
                if (job.getUid() == uid) {
                JobStatus job = jobs.get(i);
                outList.add(job.getJob());
                outList.add(job.getJob());
            }
            }
            }
        }
            return outList;
            return outList;
        }
        }
    }


    void cancelJobsForUser(int userHandle) {
    void cancelJobsForUser(int userHandle) {
        List<JobStatus> jobsForUser;
        List<JobStatus> jobsForUser;
@@ -471,14 +481,16 @@ public final class JobSchedulerService extends com.android.server.SystemService
                                    getContext().getMainLooper()));
                                    getContext().getMainLooper()));
                }
                }
                // Attach jobs to their controllers.
                // Attach jobs to their controllers.
                ArraySet<JobStatus> jobs = mJobs.getJobs();
                mJobs.forEachJob(new JobStatusFunctor() {
                for (int i=0; i<jobs.size(); i++) {
                    @Override
                    JobStatus job = jobs.valueAt(i);
                    public void process(JobStatus job) {
                        for (int controller = 0; controller < mControllers.size(); controller++) {
                        for (int controller = 0; controller < mControllers.size(); controller++) {
                        mControllers.get(controller).deviceIdleModeChanged(mDeviceIdleMode);
                            final StateController sc = mControllers.get(controller);
                        mControllers.get(controller).maybeStartTrackingJobLocked(job, null);
                            sc.deviceIdleModeChanged(mDeviceIdleMode);
                            sc.maybeStartTrackingJobLocked(job, null);
                        }
                        }
                    }
                    }
                });
                // GO GO GO!
                // GO GO GO!
                mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
                mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
            }
            }
@@ -741,32 +753,50 @@ public final class JobSchedulerService extends com.android.server.SystemService
         * as many as we can.
         * as many as we can.
         */
         */
        private void queueReadyJobsForExecutionLockedH() {
        private void queueReadyJobsForExecutionLockedH() {
            ArraySet<JobStatus> jobs = mJobs.getJobs();
            mPendingJobs.clear();
            if (DEBUG) {
            if (DEBUG) {
                Slog.d(TAG, "queuing all ready jobs for execution:");
                Slog.d(TAG, "queuing all ready jobs for execution:");
            }
            }
            for (int i=0; i<jobs.size(); i++) {
            mPendingJobs.clear();
                JobStatus job = jobs.valueAt(i);
            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.");
                }
            }
        }

        class ReadyJobQueueFunctor implements JobStatusFunctor {
            ArrayList<JobStatus> newReadyJobs;

            @Override
            public void process(JobStatus job) {
                if (isReadyToBeExecutedLocked(job)) {
                if (isReadyToBeExecutedLocked(job)) {
                    if (DEBUG) {
                    if (DEBUG) {
                        Slog.d(TAG, "    queued " + job.toShortString());
                        Slog.d(TAG, "    queued " + job.toShortString());
                    }
                    }
                    mPendingJobs.add(job);
                    if (newReadyJobs == null) {
                        newReadyJobs = new ArrayList<JobStatus>();
                    }
                    newReadyJobs.add(job);
                } else if (areJobConstraintsNotSatisfiedLocked(job)) {
                } else if (areJobConstraintsNotSatisfiedLocked(job)) {
                    stopJobOnServiceContextLocked(job,
                    stopJobOnServiceContextLocked(job,
                            JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
                            JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
                }
                }
            }
            }
            if (DEBUG) {

                final int queuedJobs = mPendingJobs.size();
            public void postProcess() {
                if (queuedJobs == 0) {
                if (newReadyJobs != null) {
                    Slog.d(TAG, "No jobs pending.");
                    mPendingJobs.addAll(newReadyJobs);
                } else {
                    Slog.d(TAG, queuedJobs + " jobs queued.");
                }
                }
                newReadyJobs = null;
            }
            }
        }
        }
        private final ReadyJobQueueFunctor mReadyQueueFunctor = new ReadyJobQueueFunctor();


        /**
        /**
         * The state of at least one job has changed. Here is where we could enforce various
         * The state of at least one job has changed. Here is where we could enforce various
@@ -777,18 +807,21 @@ public final class JobSchedulerService extends com.android.server.SystemService
         * If more than 4 jobs total are ready we send them all off.
         * If more than 4 jobs total are ready we send them all off.
         * TODO: It would be nice to consolidate these sort of high-level policies somewhere.
         * TODO: It would be nice to consolidate these sort of high-level policies somewhere.
         */
         */
        private void maybeQueueReadyJobsForExecutionLockedH() {
        class MaybeReadyJobQueueFunctor implements JobStatusFunctor {
            mPendingJobs.clear();
            int chargingCount;
            int chargingCount = 0;
            int idleCount;
            int idleCount =  0;
            int backoffCount;
            int backoffCount = 0;
            int connectivityCount;
            int connectivityCount = 0;
            int contentCount;
            int contentCount = 0;
            List<JobStatus> runnableJobs;
            List<JobStatus> runnableJobs = null;

            ArraySet<JobStatus> jobs = mJobs.getJobs();
            public MaybeReadyJobQueueFunctor() {
            if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
                reset();
            for (int i=0; i<jobs.size(); i++) {
            }
                JobStatus job = jobs.valueAt(i);

            // Functor method invoked for each job via JobStore.forEachJob()
            @Override
            public void process(JobStatus job) {
                if (isReadyToBeExecutedLocked(job)) {
                if (isReadyToBeExecutedLocked(job)) {
                    try {
                    try {
                        if (ActivityManagerNative.getDefault().getAppStartMode(job.getUid(),
                        if (ActivityManagerNative.getDefault().getAppStartMode(job.getUid(),
@@ -797,7 +830,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
                            Slog.w(TAG, "Aborting job " + job.getUid() + ":"
                            Slog.w(TAG, "Aborting job " + job.getUid() + ":"
                                    + job.getJob().toString() + " -- package not allowed to start");
                                    + job.getJob().toString() + " -- package not allowed to start");
                            mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
                            mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
                            continue;
                            return;
                        }
                        }
                    } catch (RemoteException e) {
                    } catch (RemoteException e) {
                    }
                    }
@@ -825,6 +858,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
                            JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
                            JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
                }
                }
            }
            }

            public void postProcess() {
                if (backoffCount > 0 ||
                if (backoffCount > 0 ||
                        idleCount >= MIN_IDLE_COUNT ||
                        idleCount >= MIN_IDLE_COUNT ||
                        connectivityCount >= MIN_CONNECTIVITY_COUNT ||
                        connectivityCount >= MIN_CONNECTIVITY_COUNT ||
@@ -834,14 +869,34 @@ public final class JobSchedulerService extends com.android.server.SystemService
                    if (DEBUG) {
                    if (DEBUG) {
                        Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
                        Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
                    }
                    }
                for (int i=0; i<runnableJobs.size(); i++) {
                    mPendingJobs.addAll(runnableJobs);
                    mPendingJobs.add(runnableJobs.get(i));
                }
                } else {
                } else {
                    if (DEBUG) {
                    if (DEBUG) {
                        Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
                        Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
                    }
                    }
                }
                }

                // Be ready for next time
                reset();
            }

            private void reset() {
                chargingCount = 0;
                idleCount =  0;
                backoffCount = 0;
                connectivityCount = 0;
                contentCount = 0;
                runnableJobs = null;
            }
        }
        private final MaybeReadyJobQueueFunctor mMaybeQueueFunctor = new MaybeReadyJobQueueFunctor();

        private void maybeQueueReadyJobsForExecutionLockedH() {
            if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");

            mPendingJobs.clear();
            mJobs.forEachJob(mMaybeQueueFunctor);
            mMaybeQueueFunctor.postProcess();
        }
        }


        /**
        /**
@@ -1090,17 +1145,27 @@ public final class JobSchedulerService extends com.android.server.SystemService
        @Override
        @Override
        public int scheduleAsPackage(JobInfo job, String packageName, int userId)
        public int scheduleAsPackage(JobInfo job, String packageName, int userId)
                throws RemoteException {
                throws RemoteException {
            final int callerUid = Binder.getCallingUid();
            if (DEBUG) {
            if (DEBUG) {
                Slog.d(TAG, "Scheduling job: " + job.toString() + " on behalf of " + packageName);
                Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString()
                        + " on behalf of " + packageName);
            }
            }
            final int uid = Binder.getCallingUid();

            if (uid != Process.SYSTEM_UID) {
            if (packageName == null) {
                throw new IllegalArgumentException("Only system process is allowed"
                throw new NullPointerException("Must specify a package for scheduleAsPackage()");
                        + "to set packageName");
            }

            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");
            }
            }

            long ident = Binder.clearCallingIdentity();
            long ident = Binder.clearCallingIdentity();
            try {
            try {
                return JobSchedulerService.this.scheduleAsPackage(job, uid, packageName, userId);
                return JobSchedulerService.this.scheduleAsPackage(job, callerUid,
                        packageName, userId);
            } finally {
            } finally {
                Binder.restoreCallingIdentity(ident);
                Binder.restoreCallingIdentity(ident);
            }
            }
@@ -1183,7 +1248,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
        return s.toString();
        return s.toString();
    }
    }


    void dumpInternal(PrintWriter pw) {
    void dumpInternal(final PrintWriter pw) {
        final long now = SystemClock.elapsedRealtime();
        final long now = SystemClock.elapsedRealtime();
        synchronized (mLock) {
        synchronized (mLock) {
            pw.print("Started users: ");
            pw.print("Started users: ");
@@ -1193,10 +1258,12 @@ public final class JobSchedulerService extends com.android.server.SystemService
            pw.println();
            pw.println();
            pw.println("Registered jobs:");
            pw.println("Registered jobs:");
            if (mJobs.size() > 0) {
            if (mJobs.size() > 0) {
                ArraySet<JobStatus> jobs = mJobs.getJobs();
                mJobs.forEachJob(new JobStatusFunctor() {
                for (int i=0; i<jobs.size(); i++) {
                    private int index = 0;
                    JobStatus job = jobs.valueAt(i);

                    pw.print("  Job #"); pw.print(i); pw.print(": ");
                    @Override
                    public void process(JobStatus job) {
                        pw.print("  Job #"); pw.print(index++); pw.print(": ");
                        pw.println(job.toShortString());
                        pw.println(job.toShortString());
                        job.dump(pw, "    ");
                        job.dump(pw, "    ");
                        pw.print("    Ready: ");
                        pw.print("    Ready: ");
@@ -1211,6 +1278,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
                        pw.print(mStartedUsers.contains(job.getUserId()));
                        pw.print(mStartedUsers.contains(job.getUserId()));
                        pw.println(")");
                        pw.println(")");
                    }
                    }
                });
            } else {
            } else {
                pw.println("  None.");
                pw.println("  None.");
            }
            }
+151 −56
Original line number Original line Diff line number Diff line
@@ -29,6 +29,7 @@ import android.util.AtomicFile;
import android.util.ArraySet;
import android.util.ArraySet;
import android.util.Pair;
import android.util.Pair;
import android.util.Slog;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
import android.util.Xml;


import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting;
@@ -68,8 +69,8 @@ public class JobStore {


    /** Threshold to adjust how often we want to write to the db. */
    /** Threshold to adjust how often we want to write to the db. */
    private static final int MAX_OPS_BEFORE_WRITE = 1;
    private static final int MAX_OPS_BEFORE_WRITE = 1;
    final ArraySet<JobStatus> mJobSet;
    final Object mLock;
    final Object mLock;
    final JobSet mJobSet; // per-caller-uid tracking
    final Context mContext;
    final Context mContext;


    private int mDirtyOperations;
    private int mDirtyOperations;
@@ -114,7 +115,7 @@ public class JobStore {
        jobDir.mkdirs();
        jobDir.mkdirs();
        mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"));
        mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"));


        mJobSet = new ArraySet<JobStatus>();
        mJobSet = new JobSet();


        readJobMapFromDisk(mJobSet);
        readJobMapFromDisk(mJobSet);
    }
    }
@@ -137,19 +138,6 @@ public class JobStore {
        return replaced;
        return replaced;
    }
    }


    /**
     * Whether this jobStatus object already exists in the JobStore.
     */
    public boolean containsJobIdForUid(int jobId, int uId) {
        for (int i=mJobSet.size()-1; i>=0; i--) {
            JobStatus ts = mJobSet.valueAt(i);
            if (ts.getUid() == uId && ts.getJobId() == jobId) {
                return true;
            }
        }
        return false;
    }

    boolean containsJob(JobStatus jobStatus) {
    boolean containsJob(JobStatus jobStatus) {
        return mJobSet.contains(jobStatus);
        return mJobSet.contains(jobStatus);
    }
    }
@@ -158,6 +146,10 @@ public class JobStore {
        return mJobSet.size();
        return mJobSet.size();
    }
    }


    public int countJobsForUid(int uid) {
        return mJobSet.countJobsForUid(uid);
    }

    /**
    /**
     * Remove the provided job. Will also delete the job if it was persisted.
     * Remove the provided job. Will also delete the job if it was persisted.
     * @param writeBack If true, the job will be deleted (if it was persisted) immediately.
     * @param writeBack If true, the job will be deleted (if it was persisted) immediately.
@@ -188,14 +180,7 @@ public class JobStore {
     * @return A list of all the jobs scheduled by the provided user. Never null.
     * @return A list of all the jobs scheduled by the provided user. Never null.
     */
     */
    public List<JobStatus> getJobsByUser(int userHandle) {
    public List<JobStatus> getJobsByUser(int userHandle) {
        List<JobStatus> matchingJobs = new ArrayList<JobStatus>();
        return mJobSet.getJobsByUser(userHandle);
        for (int i=mJobSet.size()-1; i>=0; i--) {
            JobStatus ts = mJobSet.valueAt(i);
            if (UserHandle.getUserId(ts.getUid()) == userHandle) {
                matchingJobs.add(ts);
            }
        }
        return matchingJobs;
    }
    }


    /**
    /**
@@ -203,14 +188,7 @@ public class JobStore {
     * @return All JobStatus objects for a given uid from the master list. Never null.
     * @return All JobStatus objects for a given uid from the master list. Never null.
     */
     */
    public List<JobStatus> getJobsByUid(int uid) {
    public List<JobStatus> getJobsByUid(int uid) {
        List<JobStatus> matchingJobs = new ArrayList<JobStatus>();
        return mJobSet.getJobsByUid(uid);
        for (int i=mJobSet.size()-1; i>=0; i--) {
            JobStatus ts = mJobSet.valueAt(i);
            if (ts.getUid() == uid) {
                matchingJobs.add(ts);
            }
        }
        return matchingJobs;
    }
    }


    /**
    /**
@@ -219,20 +197,21 @@ public class JobStore {
     * @return the JobStatus that matches the provided uId and jobId, or null if none found.
     * @return the JobStatus that matches the provided uId and jobId, or null if none found.
     */
     */
    public JobStatus getJobByUidAndJobId(int uid, int jobId) {
    public JobStatus getJobByUidAndJobId(int uid, int jobId) {
        for (int i=mJobSet.size()-1; i>=0; i--) {
        return mJobSet.get(uid, jobId);
            JobStatus ts = mJobSet.valueAt(i);
            if (ts.getUid() == uid && ts.getJobId() == jobId) {
                return ts;
            }
        }
        return null;
    }
    }


    /**
    /**
     * @return The live array of JobStatus objects.
     * 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 ArraySet<JobStatus> getJobs() {
    public void forEachJob(JobStatusFunctor functor) {
        return mJobSet;
        mJobSet.forEachJob(functor);
    }

    public interface JobStatusFunctor {
        public void process(JobStatus jobStatus);
    }
    }


    /** Version of the db schema. */
    /** Version of the db schema. */
@@ -261,7 +240,7 @@ public class JobStore {
    }
    }


    @VisibleForTesting
    @VisibleForTesting
    public void readJobMapFromDisk(ArraySet<JobStatus> jobSet) {
    public void readJobMapFromDisk(JobSet jobSet) {
        new ReadJobMapFromDiskRunnable(jobSet).run();
        new ReadJobMapFromDiskRunnable(jobSet).run();
    }
    }


@@ -273,21 +252,19 @@ public class JobStore {
        @Override
        @Override
        public void run() {
        public void run() {
            final long startElapsed = SystemClock.elapsedRealtime();
            final long startElapsed = SystemClock.elapsedRealtime();
            List<JobStatus> mStoreCopy = new ArrayList<JobStatus>();
            final List<JobStatus> storeCopy = new ArrayList<JobStatus>();
            synchronized (mLock) {
            synchronized (mLock) {
                // Copy over the jobs so we can release the lock before writing.
                // Clone the jobs so we can release the lock before writing.
                for (int i=0; i<mJobSet.size(); i++) {
                mJobSet.forEachJob(new JobStatusFunctor() {
                    JobStatus jobStatus = mJobSet.valueAt(i);
                    @Override

                    public void process(JobStatus job) {
                    if (!jobStatus.isPersisted()){
                        if (job.isPersisted()) {
                        continue;
                            storeCopy.add(new JobStatus(job));
                        }
                        }

                    JobStatus copy = new JobStatus(jobStatus);
                    mStoreCopy.add(copy);
                    }
                    }
                });
            }
            }
            writeJobsMapImpl(mStoreCopy);
            writeJobsMapImpl(storeCopy);
            if (JobSchedulerService.DEBUG) {
            if (JobSchedulerService.DEBUG) {
                Slog.v(TAG, "Finished writing, took " + (SystemClock.elapsedRealtime()
                Slog.v(TAG, "Finished writing, took " + (SystemClock.elapsedRealtime()
                        - startElapsed) + "ms");
                        - startElapsed) + "ms");
@@ -440,13 +417,13 @@ public class JobStore {
     * need to go through {@link JobStore#add(com.android.server.job.controllers.JobStatus)}.
     * need to go through {@link JobStore#add(com.android.server.job.controllers.JobStatus)}.
     */
     */
    private class ReadJobMapFromDiskRunnable implements Runnable {
    private class ReadJobMapFromDiskRunnable implements Runnable {
        private final ArraySet<JobStatus> jobSet;
        private final JobSet jobSet;


        /**
        /**
         * @param jobSet Reference to the (empty) set of JobStatus objects that back the JobStore,
         * @param jobSet Reference to the (empty) set of JobStatus objects that back the JobStore,
         *               so that after disk read we can populate it directly.
         *               so that after disk read we can populate it directly.
         */
         */
        ReadJobMapFromDiskRunnable(ArraySet<JobStatus> jobSet) {
        ReadJobMapFromDiskRunnable(JobSet jobSet) {
            this.jobSet = jobSet;
            this.jobSet = jobSet;
        }
        }


@@ -759,4 +736,122 @@ public class JobStore {
            return Pair.create(earliestRunTimeElapsed, latestRunTimeElapsed);
            return Pair.create(earliestRunTimeElapsed, latestRunTimeElapsed);
        }
        }
    }
    }

    static class JobSet {
        // Key is the getUid() originator of the jobs in each sheaf
        private SparseArray<ArraySet<JobStatus>> mJobs;

        public JobSet() {
            mJobs = new SparseArray<ArraySet<JobStatus>>();
        }

        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) {
            ArrayList<JobStatus> result = new ArrayList<JobStatus>();
            for (int i = mJobs.size() - 1; i >= 0; i--) {
                if (UserHandle.getUserId(mJobs.keyAt(i)) == userId) {
                    ArraySet<JobStatus> jobs = mJobs.get(i);
                    if (jobs != null) {
                        result.addAll(jobs);
                    }
                }
            }
            return result;
        }

        public boolean add(JobStatus job) {
            final int uid = job.getUid();
            ArraySet<JobStatus> jobs = mJobs.get(uid);
            if (jobs == null) {
                jobs = new ArraySet<JobStatus>();
                mJobs.put(uid, jobs);
            }
            return jobs.add(job);
        }

        public boolean remove(JobStatus job) {
            final int uid = job.getUid();
            ArraySet<JobStatus> jobs = mJobs.get(uid);
            boolean didRemove = (jobs != null) ? jobs.remove(job) : false;
            if (didRemove && jobs.size() == 0) {
                // no more jobs for this uid; let the now-empty set object be GC'd.
                mJobs.remove(uid);
            }
            return didRemove;
        }

        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(); i >= 0; i--) {
                allJobs.addAll(mJobs.valueAt(i));
            }
            return allJobs;
        }

        public void clear() {
            mJobs.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(JobStatusFunctor functor) {
            for (int uidIndex = mJobs.size() - 1; uidIndex >= 0; uidIndex--) {
                ArraySet<JobStatus> jobs = mJobs.valueAt(uidIndex);
                for (int i = jobs.size() - 1; i >= 0; i--) {
                    functor.process(jobs.valueAt(i));
                }
            }
        }
    }
}
}
+17 −16

File changed.

Preview size limit exceeded, changes collapsed.