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