Home | History | Annotate | Download | only in job
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.server.job;
     18 
     19 import java.io.FileDescriptor;
     20 import java.io.PrintWriter;
     21 import java.util.ArrayList;
     22 import java.util.Iterator;
     23 import java.util.List;
     24 
     25 import android.app.AppGlobals;
     26 import android.app.job.JobInfo;
     27 import android.app.job.JobScheduler;
     28 import android.app.job.JobService;
     29 import android.app.job.IJobScheduler;
     30 import android.content.BroadcastReceiver;
     31 import android.content.ComponentName;
     32 import android.content.Context;
     33 import android.content.Intent;
     34 import android.content.IntentFilter;
     35 import android.content.pm.IPackageManager;
     36 import android.content.pm.PackageManager;
     37 import android.content.pm.ServiceInfo;
     38 import android.os.BatteryStats;
     39 import android.os.Binder;
     40 import android.os.Handler;
     41 import android.os.Looper;
     42 import android.os.Message;
     43 import android.os.RemoteException;
     44 import android.os.ServiceManager;
     45 import android.os.SystemClock;
     46 import android.os.UserHandle;
     47 import android.util.ArraySet;
     48 import android.util.Slog;
     49 import android.util.SparseArray;
     50 
     51 import com.android.internal.app.IBatteryStats;
     52 import com.android.server.job.controllers.BatteryController;
     53 import com.android.server.job.controllers.ConnectivityController;
     54 import com.android.server.job.controllers.IdleController;
     55 import com.android.server.job.controllers.JobStatus;
     56 import com.android.server.job.controllers.StateController;
     57 import com.android.server.job.controllers.TimeController;
     58 
     59 /**
     60  * Responsible for taking jobs representing work to be performed by a client app, and determining
     61  * based on the criteria specified when that job should be run against the client application's
     62  * endpoint.
     63  * Implements logic for scheduling, and rescheduling jobs. The JobSchedulerService knows nothing
     64  * about constraints, or the state of active jobs. It receives callbacks from the various
     65  * controllers and completed jobs and operates accordingly.
     66  *
     67  * Note on locking: Any operations that manipulate {@link #mJobs} need to lock on that object.
     68  * Any function with the suffix 'Locked' also needs to lock on {@link #mJobs}.
     69  * @hide
     70  */
     71 public class JobSchedulerService extends com.android.server.SystemService
     72         implements StateChangedListener, JobCompletedListener {
     73     static final boolean DEBUG = false;
     74     /** The number of concurrent jobs we run at one time. */
     75     private static final int MAX_JOB_CONTEXTS_COUNT = 3;
     76     static final String TAG = "JobSchedulerService";
     77     /** Master list of jobs. */
     78     final JobStore mJobs;
     79 
     80     static final int MSG_JOB_EXPIRED = 0;
     81     static final int MSG_CHECK_JOB = 1;
     82 
     83     // Policy constants
     84     /**
     85      * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
     86      * early.
     87      */
     88     static final int MIN_IDLE_COUNT = 1;
     89     /**
     90      * Minimum # of charging jobs that must be ready in order to force the JMS to schedule things
     91      * early.
     92      */
     93     static final int MIN_CHARGING_COUNT = 1;
     94     /**
     95      * Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule
     96      * things early.
     97      */
     98     static final int MIN_CONNECTIVITY_COUNT = 2;
     99     /**
    100      * Minimum # of jobs (with no particular constraints) for which the JMS will be happy running
    101      * some work early.
    102      * This is correlated with the amount of batching we'll be able to do.
    103      */
    104     static final int MIN_READY_JOBS_COUNT = 2;
    105 
    106     /**
    107      * Track Services that have currently active or pending jobs. The index is provided by
    108      * {@link JobStatus#getServiceToken()}
    109      */
    110     final List<JobServiceContext> mActiveServices = new ArrayList<JobServiceContext>();
    111     /** List of controllers that will notify this service of updates to jobs. */
    112     List<StateController> mControllers;
    113     /**
    114      * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
    115      * when ready to execute them.
    116      */
    117     final ArrayList<JobStatus> mPendingJobs = new ArrayList<JobStatus>();
    118 
    119     final ArrayList<Integer> mStartedUsers = new ArrayList();
    120 
    121     final JobHandler mHandler;
    122     final JobSchedulerStub mJobSchedulerStub;
    123 
    124     IBatteryStats mBatteryStats;
    125 
    126     /**
    127      * Set to true once we are allowed to run third party apps.
    128      */
    129     boolean mReadyToRock;
    130 
    131     /**
    132      * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
    133      * still clean up. On reinstall the package will have a new uid.
    134      */
    135     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
    136         @Override
    137         public void onReceive(Context context, Intent intent) {
    138             Slog.d(TAG, "Receieved: " + intent.getAction());
    139             if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
    140                 int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1);
    141                 if (DEBUG) {
    142                     Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
    143                 }
    144                 cancelJobsForUid(uidRemoved);
    145             } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
    146                 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
    147                 if (DEBUG) {
    148                     Slog.d(TAG, "Removing jobs for user: " + userId);
    149                 }
    150                 cancelJobsForUser(userId);
    151             }
    152         }
    153     };
    154 
    155     @Override
    156     public void onStartUser(int userHandle) {
    157         mStartedUsers.add(userHandle);
    158         // Let's kick any outstanding jobs for this user.
    159         mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
    160     }
    161 
    162     @Override
    163     public void onStopUser(int userHandle) {
    164         mStartedUsers.remove(Integer.valueOf(userHandle));
    165     }
    166 
    167     /**
    168      * Entry point from client to schedule the provided job.
    169      * This cancels the job if it's already been scheduled, and replaces it with the one provided.
    170      * @param job JobInfo object containing execution parameters
    171      * @param uId The package identifier of the application this job is for.
    172      * @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes.
    173      */
    174     public int schedule(JobInfo job, int uId) {
    175         JobStatus jobStatus = new JobStatus(job, uId);
    176         cancelJob(uId, job.getId());
    177         startTrackingJob(jobStatus);
    178         mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
    179         return JobScheduler.RESULT_SUCCESS;
    180     }
    181 
    182     public List<JobInfo> getPendingJobs(int uid) {
    183         ArrayList<JobInfo> outList = new ArrayList<JobInfo>();
    184         synchronized (mJobs) {
    185             ArraySet<JobStatus> jobs = mJobs.getJobs();
    186             for (int i=0; i<jobs.size(); i++) {
    187                 JobStatus job = jobs.valueAt(i);
    188                 if (job.getUid() == uid) {
    189                     outList.add(job.getJob());
    190                 }
    191             }
    192         }
    193         return outList;
    194     }
    195 
    196     private void cancelJobsForUser(int userHandle) {
    197         List<JobStatus> jobsForUser;
    198         synchronized (mJobs) {
    199             jobsForUser = mJobs.getJobsByUser(userHandle);
    200         }
    201         for (int i=0; i<jobsForUser.size(); i++) {
    202             JobStatus toRemove = jobsForUser.get(i);
    203             cancelJobImpl(toRemove);
    204         }
    205     }
    206 
    207     /**
    208      * Entry point from client to cancel all jobs originating from their uid.
    209      * This will remove the job from the master list, and cancel the job if it was staged for
    210      * execution or being executed.
    211      * @param uid Uid to check against for removal of a job.
    212      */
    213     public void cancelJobsForUid(int uid) {
    214         List<JobStatus> jobsForUid;
    215         synchronized (mJobs) {
    216             jobsForUid = mJobs.getJobsByUid(uid);
    217         }
    218         for (int i=0; i<jobsForUid.size(); i++) {
    219             JobStatus toRemove = jobsForUid.get(i);
    220             cancelJobImpl(toRemove);
    221         }
    222     }
    223 
    224     /**
    225      * Entry point from client to cancel the job corresponding to the jobId provided.
    226      * This will remove the job from the master list, and cancel the job if it was staged for
    227      * execution or being executed.
    228      * @param uid Uid of the calling client.
    229      * @param jobId Id of the job, provided at schedule-time.
    230      */
    231     public void cancelJob(int uid, int jobId) {
    232         JobStatus toCancel;
    233         synchronized (mJobs) {
    234             toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
    235         }
    236         if (toCancel != null) {
    237             cancelJobImpl(toCancel);
    238         }
    239     }
    240 
    241     private void cancelJobImpl(JobStatus cancelled) {
    242         if (DEBUG) {
    243             Slog.d(TAG, "Cancelling: " + cancelled);
    244         }
    245         stopTrackingJob(cancelled);
    246         synchronized (mJobs) {
    247             // Remove from pending queue.
    248             mPendingJobs.remove(cancelled);
    249             // Cancel if running.
    250             stopJobOnServiceContextLocked(cancelled);
    251         }
    252     }
    253 
    254     /**
    255      * Initializes the system service.
    256      * <p>
    257      * Subclasses must define a single argument constructor that accepts the context
    258      * and passes it to super.
    259      * </p>
    260      *
    261      * @param context The system server context.
    262      */
    263     public JobSchedulerService(Context context) {
    264         super(context);
    265         // Create the controllers.
    266         mControllers = new ArrayList<StateController>();
    267         mControllers.add(ConnectivityController.get(this));
    268         mControllers.add(TimeController.get(this));
    269         mControllers.add(IdleController.get(this));
    270         mControllers.add(BatteryController.get(this));
    271 
    272         mHandler = new JobHandler(context.getMainLooper());
    273         mJobSchedulerStub = new JobSchedulerStub();
    274         mJobs = JobStore.initAndGet(this);
    275     }
    276 
    277     @Override
    278     public void onStart() {
    279         publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
    280     }
    281 
    282     @Override
    283     public void onBootPhase(int phase) {
    284         if (PHASE_SYSTEM_SERVICES_READY == phase) {
    285             // Register br for package removals and user removals.
    286             final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
    287             filter.addDataScheme("package");
    288             getContext().registerReceiverAsUser(
    289                     mBroadcastReceiver, UserHandle.ALL, filter, null, null);
    290             final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
    291             getContext().registerReceiverAsUser(
    292                     mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
    293         } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
    294             synchronized (mJobs) {
    295                 // Let's go!
    296                 mReadyToRock = true;
    297                 mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
    298                         BatteryStats.SERVICE_NAME));
    299                 // Create the "runners".
    300                 for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
    301                     mActiveServices.add(
    302                             new JobServiceContext(this, mBatteryStats,
    303                                     getContext().getMainLooper()));
    304                 }
    305                 // Attach jobs to their controllers.
    306                 ArraySet<JobStatus> jobs = mJobs.getJobs();
    307                 for (int i=0; i<jobs.size(); i++) {
    308                     JobStatus job = jobs.valueAt(i);
    309                     for (int controller=0; controller<mControllers.size(); controller++) {
    310                         mControllers.get(controller).maybeStartTrackingJob(job);
    311                     }
    312                 }
    313                 // GO GO GO!
    314                 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
    315             }
    316         }
    317     }
    318 
    319     /**
    320      * Called when we have a job status object that we need to insert in our
    321      * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know
    322      * about.
    323      */
    324     private void startTrackingJob(JobStatus jobStatus) {
    325         boolean update;
    326         boolean rocking;
    327         synchronized (mJobs) {
    328             update = mJobs.add(jobStatus);
    329             rocking = mReadyToRock;
    330         }
    331         if (rocking) {
    332             for (int i=0; i<mControllers.size(); i++) {
    333                 StateController controller = mControllers.get(i);
    334                 if (update) {
    335                     controller.maybeStopTrackingJob(jobStatus);
    336                 }
    337                 controller.maybeStartTrackingJob(jobStatus);
    338             }
    339         }
    340     }
    341 
    342     /**
    343      * Called when we want to remove a JobStatus object that we've finished executing. Returns the
    344      * object removed.
    345      */
    346     private boolean stopTrackingJob(JobStatus jobStatus) {
    347         boolean removed;
    348         boolean rocking;
    349         synchronized (mJobs) {
    350             // Remove from store as well as controllers.
    351             removed = mJobs.remove(jobStatus);
    352             rocking = mReadyToRock;
    353         }
    354         if (removed && rocking) {
    355             for (int i=0; i<mControllers.size(); i++) {
    356                 StateController controller = mControllers.get(i);
    357                 controller.maybeStopTrackingJob(jobStatus);
    358             }
    359         }
    360         return removed;
    361     }
    362 
    363     private boolean stopJobOnServiceContextLocked(JobStatus job) {
    364         for (int i=0; i<mActiveServices.size(); i++) {
    365             JobServiceContext jsc = mActiveServices.get(i);
    366             final JobStatus executing = jsc.getRunningJob();
    367             if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
    368                 jsc.cancelExecutingJob();
    369                 return true;
    370             }
    371         }
    372         return false;
    373     }
    374 
    375     /**
    376      * @param job JobStatus we are querying against.
    377      * @return Whether or not the job represented by the status object is currently being run or
    378      * is pending.
    379      */
    380     private boolean isCurrentlyActiveLocked(JobStatus job) {
    381         for (int i=0; i<mActiveServices.size(); i++) {
    382             JobServiceContext serviceContext = mActiveServices.get(i);
    383             final JobStatus running = serviceContext.getRunningJob();
    384             if (running != null && running.matches(job.getUid(), job.getJobId())) {
    385                 return true;
    386             }
    387         }
    388         return false;
    389     }
    390 
    391     /**
    392      * A job is rescheduled with exponential back-off if the client requests this from their
    393      * execution logic.
    394      * A caveat is for idle-mode jobs, for which the idle-mode constraint will usurp the
    395      * timeliness of the reschedule. For an idle-mode job, no deadline is given.
    396      * @param failureToReschedule Provided job status that we will reschedule.
    397      * @return A newly instantiated JobStatus with the same constraints as the last job except
    398      * with adjusted timing constraints.
    399      */
    400     private JobStatus getRescheduleJobForFailure(JobStatus failureToReschedule) {
    401         final long elapsedNowMillis = SystemClock.elapsedRealtime();
    402         final JobInfo job = failureToReschedule.getJob();
    403 
    404         final long initialBackoffMillis = job.getInitialBackoffMillis();
    405         final int backoffAttempts = failureToReschedule.getNumFailures() + 1;
    406         long delayMillis;
    407 
    408         switch (job.getBackoffPolicy()) {
    409             case JobInfo.BACKOFF_POLICY_LINEAR:
    410                 delayMillis = initialBackoffMillis * backoffAttempts;
    411                 break;
    412             default:
    413                 if (DEBUG) {
    414                     Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
    415                 }
    416             case JobInfo.BACKOFF_POLICY_EXPONENTIAL:
    417                 delayMillis =
    418                         (long) Math.scalb(initialBackoffMillis, backoffAttempts - 1);
    419                 break;
    420         }
    421         delayMillis =
    422                 Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
    423         return new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis,
    424                 JobStatus.NO_LATEST_RUNTIME, backoffAttempts);
    425     }
    426 
    427     /**
    428      * Called after a periodic has executed so we can to re-add it. We take the last execution time
    429      * of the job to be the time of completion (i.e. the time at which this function is called).
    430      * This could be inaccurate b/c the job can run for as long as
    431      * {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
    432      * to underscheduling at least, rather than if we had taken the last execution time to be the
    433      * start of the execution.
    434      * @return A new job representing the execution criteria for this instantiation of the
    435      * recurring job.
    436      */
    437     private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
    438         final long elapsedNow = SystemClock.elapsedRealtime();
    439         // Compute how much of the period is remaining.
    440         long runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0);
    441         long newEarliestRunTimeElapsed = elapsedNow + runEarly;
    442         long period = periodicToReschedule.getJob().getIntervalMillis();
    443         long newLatestRuntimeElapsed = newEarliestRunTimeElapsed + period;
    444 
    445         if (DEBUG) {
    446             Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
    447                     newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
    448         }
    449         return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
    450                 newLatestRuntimeElapsed, 0 /* backoffAttempt */);
    451     }
    452 
    453     // JobCompletedListener implementations.
    454 
    455     /**
    456      * A job just finished executing. We fetch the
    457      * {@link com.android.server.job.controllers.JobStatus} from the store and depending on
    458      * whether we want to reschedule we readd it to the controllers.
    459      * @param jobStatus Completed job.
    460      * @param needsReschedule Whether the implementing class should reschedule this job.
    461      */
    462     @Override
    463     public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) {
    464         if (DEBUG) {
    465             Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
    466         }
    467         if (!stopTrackingJob(jobStatus)) {
    468             if (DEBUG) {
    469                 Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
    470             }
    471             return;
    472         }
    473         if (needsReschedule) {
    474             JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
    475             startTrackingJob(rescheduled);
    476         } else if (jobStatus.getJob().isPeriodic()) {
    477             JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
    478             startTrackingJob(rescheduledPeriodic);
    479         }
    480         mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
    481     }
    482 
    483     // StateChangedListener implementations.
    484 
    485     /**
    486      * Posts a message to the {@link com.android.server.job.JobSchedulerService.JobHandler} that
    487      * some controller's state has changed, so as to run through the list of jobs and start/stop
    488      * any that are eligible.
    489      */
    490     @Override
    491     public void onControllerStateChanged() {
    492         mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
    493     }
    494 
    495     @Override
    496     public void onRunJobNow(JobStatus jobStatus) {
    497         mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
    498     }
    499 
    500     private class JobHandler extends Handler {
    501 
    502         public JobHandler(Looper looper) {
    503             super(looper);
    504         }
    505 
    506         @Override
    507         public void handleMessage(Message message) {
    508             synchronized (mJobs) {
    509                 if (!mReadyToRock) {
    510                     return;
    511                 }
    512             }
    513             switch (message.what) {
    514                 case MSG_JOB_EXPIRED:
    515                     synchronized (mJobs) {
    516                         JobStatus runNow = (JobStatus) message.obj;
    517                         // runNow can be null, which is a controller's way of indicating that its
    518                         // state is such that all ready jobs should be run immediately.
    519                         if (runNow != null && !mPendingJobs.contains(runNow)
    520                                 && mJobs.containsJob(runNow)) {
    521                             mPendingJobs.add(runNow);
    522                         }
    523                         queueReadyJobsForExecutionLockedH();
    524                     }
    525                     break;
    526                 case MSG_CHECK_JOB:
    527                     synchronized (mJobs) {
    528                         // Check the list of jobs and run some of them if we feel inclined.
    529                         maybeQueueReadyJobsForExecutionLockedH();
    530                     }
    531                     break;
    532             }
    533             maybeRunPendingJobsH();
    534             // Don't remove JOB_EXPIRED in case one came along while processing the queue.
    535             removeMessages(MSG_CHECK_JOB);
    536         }
    537 
    538         /**
    539          * Run through list of jobs and execute all possible - at least one is expired so we do
    540          * as many as we can.
    541          */
    542         private void queueReadyJobsForExecutionLockedH() {
    543             ArraySet<JobStatus> jobs = mJobs.getJobs();
    544             if (DEBUG) {
    545                 Slog.d(TAG, "queuing all ready jobs for execution:");
    546             }
    547             for (int i=0; i<jobs.size(); i++) {
    548                 JobStatus job = jobs.valueAt(i);
    549                 if (isReadyToBeExecutedLocked(job)) {
    550                     if (DEBUG) {
    551                         Slog.d(TAG, "    queued " + job.toShortString());
    552                     }
    553                     mPendingJobs.add(job);
    554                 } else if (isReadyToBeCancelledLocked(job)) {
    555                     stopJobOnServiceContextLocked(job);
    556                 }
    557             }
    558             if (DEBUG) {
    559                 final int queuedJobs = mPendingJobs.size();
    560                 if (queuedJobs == 0) {
    561                     Slog.d(TAG, "No jobs pending.");
    562                 } else {
    563                     Slog.d(TAG, queuedJobs + " jobs queued.");
    564                 }
    565             }
    566         }
    567 
    568         /**
    569          * The state of at least one job has changed. Here is where we could enforce various
    570          * policies on when we want to execute jobs.
    571          * Right now the policy is such:
    572          * If >1 of the ready jobs is idle mode we send all of them off
    573          * if more than 2 network connectivity jobs are ready we send them all off.
    574          * If more than 4 jobs total are ready we send them all off.
    575          * TODO: It would be nice to consolidate these sort of high-level policies somewhere.
    576          */
    577         private void maybeQueueReadyJobsForExecutionLockedH() {
    578             int chargingCount = 0;
    579             int idleCount =  0;
    580             int backoffCount = 0;
    581             int connectivityCount = 0;
    582             List<JobStatus> runnableJobs = new ArrayList<JobStatus>();
    583             ArraySet<JobStatus> jobs = mJobs.getJobs();
    584             for (int i=0; i<jobs.size(); i++) {
    585                 JobStatus job = jobs.valueAt(i);
    586                 if (isReadyToBeExecutedLocked(job)) {
    587                     if (job.getNumFailures() > 0) {
    588                         backoffCount++;
    589                     }
    590                     if (job.hasIdleConstraint()) {
    591                         idleCount++;
    592                     }
    593                     if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()) {
    594                         connectivityCount++;
    595                     }
    596                     if (job.hasChargingConstraint()) {
    597                         chargingCount++;
    598                     }
    599                     runnableJobs.add(job);
    600                 } else if (isReadyToBeCancelledLocked(job)) {
    601                     stopJobOnServiceContextLocked(job);
    602                 }
    603             }
    604             if (backoffCount > 0 ||
    605                     idleCount >= MIN_IDLE_COUNT ||
    606                     connectivityCount >= MIN_CONNECTIVITY_COUNT ||
    607                     chargingCount >= MIN_CHARGING_COUNT ||
    608                     runnableJobs.size() >= MIN_READY_JOBS_COUNT) {
    609                 if (DEBUG) {
    610                     Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
    611                 }
    612                 for (int i=0; i<runnableJobs.size(); i++) {
    613                     mPendingJobs.add(runnableJobs.get(i));
    614                 }
    615             } else {
    616                 if (DEBUG) {
    617                     Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
    618                 }
    619             }
    620             if (DEBUG) {
    621                 Slog.d(TAG, "idle=" + idleCount + " connectivity=" +
    622                 connectivityCount + " charging=" + chargingCount + " tot=" +
    623                         runnableJobs.size());
    624             }
    625         }
    626 
    627         /**
    628          * Criteria for moving a job into the pending queue:
    629          *      - It's ready.
    630          *      - It's not pending.
    631          *      - It's not already running on a JSC.
    632          *      - The user that requested the job is running.
    633          */
    634         private boolean isReadyToBeExecutedLocked(JobStatus job) {
    635             final boolean jobReady = job.isReady();
    636             final boolean jobPending = mPendingJobs.contains(job);
    637             final boolean jobActive = isCurrentlyActiveLocked(job);
    638             final boolean userRunning = mStartedUsers.contains(job.getUserId());
    639 
    640             if (DEBUG) {
    641                 Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
    642                         + " ready=" + jobReady + " pending=" + jobPending
    643                         + " active=" + jobActive + " userRunning=" + userRunning);
    644             }
    645             return userRunning && jobReady && !jobPending && !jobActive;
    646         }
    647 
    648         /**
    649          * Criteria for cancelling an active job:
    650          *      - It's not ready
    651          *      - It's running on a JSC.
    652          */
    653         private boolean isReadyToBeCancelledLocked(JobStatus job) {
    654             return !job.isReady() && isCurrentlyActiveLocked(job);
    655         }
    656 
    657         /**
    658          * Reconcile jobs in the pending queue against available execution contexts.
    659          * A controller can force a job into the pending queue even if it's already running, but
    660          * here is where we decide whether to actually execute it.
    661          */
    662         private void maybeRunPendingJobsH() {
    663             synchronized (mJobs) {
    664                 Iterator<JobStatus> it = mPendingJobs.iterator();
    665                 if (DEBUG) {
    666                     Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
    667                 }
    668                 while (it.hasNext()) {
    669                     JobStatus nextPending = it.next();
    670                     JobServiceContext availableContext = null;
    671                     for (int i=0; i<mActiveServices.size(); i++) {
    672                         JobServiceContext jsc = mActiveServices.get(i);
    673                         final JobStatus running = jsc.getRunningJob();
    674                         if (running != null && running.matches(nextPending.getUid(),
    675                                 nextPending.getJobId())) {
    676                             // Already running this job for this uId, skip.
    677                             availableContext = null;
    678                             break;
    679                         }
    680                         if (jsc.isAvailable()) {
    681                             availableContext = jsc;
    682                         }
    683                     }
    684                     if (availableContext != null) {
    685                         if (!availableContext.executeRunnableJob(nextPending)) {
    686                             if (DEBUG) {
    687                                 Slog.d(TAG, "Error executing " + nextPending);
    688                             }
    689                             mJobs.remove(nextPending);
    690                         }
    691                         it.remove();
    692                     }
    693                 }
    694             }
    695         }
    696     }
    697 
    698     /**
    699      * Binder stub trampoline implementation
    700      */
    701     final class JobSchedulerStub extends IJobScheduler.Stub {
    702         /** Cache determination of whether a given app can persist jobs
    703          * key is uid of the calling app; value is undetermined/true/false
    704          */
    705         private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
    706 
    707         // Enforce that only the app itself (or shared uid participant) can schedule a
    708         // job that runs one of the app's services, as well as verifying that the
    709         // named service properly requires the BIND_JOB_SERVICE permission
    710         private void enforceValidJobRequest(int uid, JobInfo job) {
    711             final IPackageManager pm = AppGlobals.getPackageManager();
    712             final ComponentName service = job.getService();
    713             try {
    714                 ServiceInfo si = pm.getServiceInfo(service, 0, UserHandle.getUserId(uid));
    715                 if (si == null) {
    716                     throw new IllegalArgumentException("No such service " + service);
    717                 }
    718                 if (si.applicationInfo.uid != uid) {
    719                     throw new IllegalArgumentException("uid " + uid +
    720                             " cannot schedule job in " + service.getPackageName());
    721                 }
    722                 if (!JobService.PERMISSION_BIND.equals(si.permission)) {
    723                     throw new IllegalArgumentException("Scheduled service " + service
    724                             + " does not require android.permission.BIND_JOB_SERVICE permission");
    725                 }
    726             } catch (RemoteException e) {
    727                 // Can't happen; the Package Manager is in this same process
    728             }
    729         }
    730 
    731         private boolean canPersistJobs(int pid, int uid) {
    732             // If we get this far we're good to go; all we need to do now is check
    733             // whether the app is allowed to persist its scheduled work.
    734             final boolean canPersist;
    735             synchronized (mPersistCache) {
    736                 Boolean cached = mPersistCache.get(uid);
    737                 if (cached != null) {
    738                     canPersist = cached.booleanValue();
    739                 } else {
    740                     // Persisting jobs is tantamount to running at boot, so we permit
    741                     // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
    742                     // permission
    743                     int result = getContext().checkPermission(
    744                             android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
    745                     canPersist = (result == PackageManager.PERMISSION_GRANTED);
    746                     mPersistCache.put(uid, canPersist);
    747                 }
    748             }
    749             return canPersist;
    750         }
    751 
    752         // IJobScheduler implementation
    753         @Override
    754         public int schedule(JobInfo job) throws RemoteException {
    755             if (DEBUG) {
    756                 Slog.d(TAG, "Scheduling job: " + job.toString());
    757             }
    758             final int pid = Binder.getCallingPid();
    759             final int uid = Binder.getCallingUid();
    760 
    761             enforceValidJobRequest(uid, job);
    762             if (job.isPersisted()) {
    763                 if (!canPersistJobs(pid, uid)) {
    764                     throw new IllegalArgumentException("Error: requested job be persisted without"
    765                             + " holding RECEIVE_BOOT_COMPLETED permission.");
    766                 }
    767             }
    768 
    769             long ident = Binder.clearCallingIdentity();
    770             try {
    771                 return JobSchedulerService.this.schedule(job, uid);
    772             } finally {
    773                 Binder.restoreCallingIdentity(ident);
    774             }
    775         }
    776 
    777         @Override
    778         public List<JobInfo> getAllPendingJobs() throws RemoteException {
    779             final int uid = Binder.getCallingUid();
    780 
    781             long ident = Binder.clearCallingIdentity();
    782             try {
    783                 return JobSchedulerService.this.getPendingJobs(uid);
    784             } finally {
    785                 Binder.restoreCallingIdentity(ident);
    786             }
    787         }
    788 
    789         @Override
    790         public void cancelAll() throws RemoteException {
    791             final int uid = Binder.getCallingUid();
    792 
    793             long ident = Binder.clearCallingIdentity();
    794             try {
    795                 JobSchedulerService.this.cancelJobsForUid(uid);
    796             } finally {
    797                 Binder.restoreCallingIdentity(ident);
    798             }
    799         }
    800 
    801         @Override
    802         public void cancel(int jobId) throws RemoteException {
    803             final int uid = Binder.getCallingUid();
    804 
    805             long ident = Binder.clearCallingIdentity();
    806             try {
    807                 JobSchedulerService.this.cancelJob(uid, jobId);
    808             } finally {
    809                 Binder.restoreCallingIdentity(ident);
    810             }
    811         }
    812 
    813         /**
    814          * "dumpsys" infrastructure
    815          */
    816         @Override
    817         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    818             getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
    819 
    820             long identityToken = Binder.clearCallingIdentity();
    821             try {
    822                 JobSchedulerService.this.dumpInternal(pw);
    823             } finally {
    824                 Binder.restoreCallingIdentity(identityToken);
    825             }
    826         }
    827     };
    828 
    829     void dumpInternal(PrintWriter pw) {
    830         final long now = SystemClock.elapsedRealtime();
    831         synchronized (mJobs) {
    832             pw.print("Started users: ");
    833             for (int i=0; i<mStartedUsers.size(); i++) {
    834                 pw.print("u" + mStartedUsers.get(i) + " ");
    835             }
    836             pw.println();
    837             pw.println("Registered jobs:");
    838             if (mJobs.size() > 0) {
    839                 ArraySet<JobStatus> jobs = mJobs.getJobs();
    840                 for (int i=0; i<jobs.size(); i++) {
    841                     JobStatus job = jobs.valueAt(i);
    842                     job.dump(pw, "  ");
    843                 }
    844             } else {
    845                 pw.println("  None.");
    846             }
    847             for (int i=0; i<mControllers.size(); i++) {
    848                 pw.println();
    849                 mControllers.get(i).dumpControllerState(pw);
    850             }
    851             pw.println();
    852             pw.println("Pending:");
    853             for (int i=0; i<mPendingJobs.size(); i++) {
    854                 pw.println(mPendingJobs.get(i).hashCode());
    855             }
    856             pw.println();
    857             pw.println("Active jobs:");
    858             for (int i=0; i<mActiveServices.size(); i++) {
    859                 JobServiceContext jsc = mActiveServices.get(i);
    860                 if (jsc.isAvailable()) {
    861                     continue;
    862                 } else {
    863                     final long timeout = jsc.getTimeoutElapsed();
    864                     pw.print("Running for: ");
    865                     pw.print((now - jsc.getExecutionStartTimeElapsed())/1000);
    866                     pw.print("s timeout=");
    867                     pw.print(timeout);
    868                     pw.print(" fromnow=");
    869                     pw.println(timeout-now);
    870                     jsc.getRunningJob().dump(pw, "  ");
    871                 }
    872             }
    873             pw.println();
    874             pw.print("mReadyToRock="); pw.println(mReadyToRock);
    875         }
    876         pw.println();
    877     }
    878 }
    879