Home | History | Annotate | Download | only in controllers
      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.controllers;
     18 
     19 import android.app.AlarmManager;
     20 import android.app.AlarmManager.OnAlarmListener;
     21 import android.content.Context;
     22 import android.os.SystemClock;
     23 import android.os.UserHandle;
     24 import android.os.WorkSource;
     25 import android.util.Slog;
     26 import android.util.TimeUtils;
     27 
     28 import com.android.server.job.JobSchedulerService;
     29 import com.android.server.job.StateChangedListener;
     30 
     31 import java.io.PrintWriter;
     32 import java.util.Iterator;
     33 import java.util.LinkedList;
     34 import java.util.List;
     35 import java.util.ListIterator;
     36 
     37 /**
     38  * This class sets an alarm for the next expiring job, and determines whether a job's minimum
     39  * delay has been satisfied.
     40  */
     41 public final class TimeController extends StateController {
     42     private static final String TAG = "JobScheduler.Time";
     43 
     44     /** Deadline alarm tag for logging purposes */
     45     private final String DEADLINE_TAG = "*job.deadline*";
     46     /** Delay alarm tag for logging purposes */
     47     private final String DELAY_TAG = "*job.delay*";
     48 
     49     private long mNextJobExpiredElapsedMillis;
     50     private long mNextDelayExpiredElapsedMillis;
     51 
     52     private AlarmManager mAlarmService = null;
     53     /** List of tracked jobs, sorted asc. by deadline */
     54     private final List<JobStatus> mTrackedJobs = new LinkedList<>();
     55     /** Singleton. */
     56     private static TimeController mSingleton;
     57 
     58     public static synchronized TimeController get(JobSchedulerService jms) {
     59         if (mSingleton == null) {
     60             mSingleton = new TimeController(jms, jms.getContext(), jms.getLock());
     61         }
     62         return mSingleton;
     63     }
     64 
     65     private TimeController(StateChangedListener stateChangedListener, Context context,
     66                 Object lock) {
     67         super(stateChangedListener, context, lock);
     68 
     69         mNextJobExpiredElapsedMillis = Long.MAX_VALUE;
     70         mNextDelayExpiredElapsedMillis = Long.MAX_VALUE;
     71     }
     72 
     73     /**
     74      * Check if the job has a timing constraint, and if so determine where to insert it in our
     75      * list.
     76      */
     77     @Override
     78     public void maybeStartTrackingJobLocked(JobStatus job, JobStatus lastJob) {
     79         if (job.hasTimingDelayConstraint() || job.hasDeadlineConstraint()) {
     80             maybeStopTrackingJobLocked(job, null, false);
     81 
     82             // First: check the constraints now, because if they are already satisfied
     83             // then there is no need to track it.  This gives us a fast path for a common
     84             // pattern of having a job with a 0 deadline constraint ("run immediately").
     85             // Unlike most controllers, once one of our constraints has been satisfied, it
     86             // will never be unsatisfied (our time base can not go backwards).
     87             final long nowElapsedMillis = SystemClock.elapsedRealtime();
     88             if (job.hasDeadlineConstraint() && evaluateDeadlineConstraint(job, nowElapsedMillis)) {
     89                 return;
     90             } else if (job.hasTimingDelayConstraint() && evaluateTimingDelayConstraint(job,
     91                     nowElapsedMillis)) {
     92                 return;
     93             }
     94 
     95             boolean isInsert = false;
     96             ListIterator<JobStatus> it = mTrackedJobs.listIterator(mTrackedJobs.size());
     97             while (it.hasPrevious()) {
     98                 JobStatus ts = it.previous();
     99                 if (ts.getLatestRunTimeElapsed() < job.getLatestRunTimeElapsed()) {
    100                     // Insert
    101                     isInsert = true;
    102                     break;
    103                 }
    104             }
    105             if (isInsert) {
    106                 it.next();
    107             }
    108             it.add(job);
    109             job.setTrackingController(JobStatus.TRACKING_TIME);
    110             maybeUpdateAlarmsLocked(
    111                     job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE,
    112                     job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE,
    113                     job.getSourceUid());
    114         }
    115     }
    116 
    117     /**
    118      * When we stop tracking a job, we only need to update our alarms if the job we're no longer
    119      * tracking was the one our alarms were based off of.
    120      */
    121     @Override
    122     public void maybeStopTrackingJobLocked(JobStatus job, JobStatus incomingJob,
    123             boolean forUpdate) {
    124         if (job.clearTrackingController(JobStatus.TRACKING_TIME)) {
    125             if (mTrackedJobs.remove(job)) {
    126                 checkExpiredDelaysAndResetAlarm();
    127                 checkExpiredDeadlinesAndResetAlarm();
    128             }
    129         }
    130     }
    131 
    132     /**
    133      * Determines whether this controller can stop tracking the given job.
    134      * The controller is no longer interested in a job once its time constraint is satisfied, and
    135      * the job's deadline is fulfilled - unlike other controllers a time constraint can't toggle
    136      * back and forth.
    137      */
    138     private boolean canStopTrackingJobLocked(JobStatus job) {
    139         return (!job.hasTimingDelayConstraint() ||
    140                 (job.satisfiedConstraints&JobStatus.CONSTRAINT_TIMING_DELAY) != 0) &&
    141                 (!job.hasDeadlineConstraint() ||
    142                         (job.satisfiedConstraints&JobStatus.CONSTRAINT_DEADLINE) != 0);
    143     }
    144 
    145     private void ensureAlarmServiceLocked() {
    146         if (mAlarmService == null) {
    147             mAlarmService = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
    148         }
    149     }
    150 
    151     /**
    152      * Checks list of jobs for ones that have an expired deadline, sending them to the JobScheduler
    153      * if so, removing them from this list, and updating the alarm for the next expiry time.
    154      */
    155     private void checkExpiredDeadlinesAndResetAlarm() {
    156         synchronized (mLock) {
    157             long nextExpiryTime = Long.MAX_VALUE;
    158             int nextExpiryUid = 0;
    159             final long nowElapsedMillis = SystemClock.elapsedRealtime();
    160 
    161             Iterator<JobStatus> it = mTrackedJobs.iterator();
    162             while (it.hasNext()) {
    163                 JobStatus job = it.next();
    164                 if (!job.hasDeadlineConstraint()) {
    165                     continue;
    166                 }
    167 
    168                 if (evaluateDeadlineConstraint(job, nowElapsedMillis)) {
    169                     mStateChangedListener.onRunJobNow(job);
    170                     it.remove();
    171                 } else {  // Sorted by expiry time, so take the next one and stop.
    172                     nextExpiryTime = job.getLatestRunTimeElapsed();
    173                     nextExpiryUid = job.getSourceUid();
    174                     break;
    175                 }
    176             }
    177             setDeadlineExpiredAlarmLocked(nextExpiryTime, nextExpiryUid);
    178         }
    179     }
    180 
    181     private boolean evaluateDeadlineConstraint(JobStatus job, long nowElapsedMillis) {
    182         final long jobDeadline = job.getLatestRunTimeElapsed();
    183 
    184         if (jobDeadline <= nowElapsedMillis) {
    185             if (job.hasTimingDelayConstraint()) {
    186                 job.setTimingDelayConstraintSatisfied(true);
    187             }
    188             job.setDeadlineConstraintSatisfied(true);
    189             return true;
    190         }
    191         return false;
    192     }
    193 
    194     /**
    195      * Handles alarm that notifies us that a job's delay has expired. Iterates through the list of
    196      * tracked jobs and marks them as ready as appropriate.
    197      */
    198     private void checkExpiredDelaysAndResetAlarm() {
    199         synchronized (mLock) {
    200             final long nowElapsedMillis = SystemClock.elapsedRealtime();
    201             long nextDelayTime = Long.MAX_VALUE;
    202             int nextDelayUid = 0;
    203             boolean ready = false;
    204             Iterator<JobStatus> it = mTrackedJobs.iterator();
    205             while (it.hasNext()) {
    206                 final JobStatus job = it.next();
    207                 if (!job.hasTimingDelayConstraint()) {
    208                     continue;
    209                 }
    210                 if (evaluateTimingDelayConstraint(job, nowElapsedMillis)) {
    211                     if (canStopTrackingJobLocked(job)) {
    212                         it.remove();
    213                     }
    214                     if (job.isReady()) {
    215                         ready = true;
    216                     }
    217                 } else if (!job.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)) {
    218                     // If this job still doesn't have its delay constraint satisfied,
    219                     // then see if it is the next upcoming delay time for the alarm.
    220                     final long jobDelayTime = job.getEarliestRunTime();
    221                     if (nextDelayTime > jobDelayTime) {
    222                         nextDelayTime = jobDelayTime;
    223                         nextDelayUid = job.getSourceUid();
    224                     }
    225                 }
    226             }
    227             if (ready) {
    228                 mStateChangedListener.onControllerStateChanged();
    229             }
    230             setDelayExpiredAlarmLocked(nextDelayTime, nextDelayUid);
    231         }
    232     }
    233 
    234     private boolean evaluateTimingDelayConstraint(JobStatus job, long nowElapsedMillis) {
    235         final long jobDelayTime = job.getEarliestRunTime();
    236         if (jobDelayTime <= nowElapsedMillis) {
    237             job.setTimingDelayConstraintSatisfied(true);
    238             return true;
    239         }
    240         return false;
    241     }
    242 
    243     private void maybeUpdateAlarmsLocked(long delayExpiredElapsed, long deadlineExpiredElapsed,
    244             int uid) {
    245         if (delayExpiredElapsed < mNextDelayExpiredElapsedMillis) {
    246             setDelayExpiredAlarmLocked(delayExpiredElapsed, uid);
    247         }
    248         if (deadlineExpiredElapsed < mNextJobExpiredElapsedMillis) {
    249             setDeadlineExpiredAlarmLocked(deadlineExpiredElapsed, uid);
    250         }
    251     }
    252 
    253     /**
    254      * Set an alarm with the {@link android.app.AlarmManager} for the next time at which a job's
    255      * delay will expire.
    256      * This alarm <b>will</b> wake up the phone.
    257      */
    258     private void setDelayExpiredAlarmLocked(long alarmTimeElapsedMillis, int uid) {
    259         alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis);
    260         mNextDelayExpiredElapsedMillis = alarmTimeElapsedMillis;
    261         updateAlarmWithListenerLocked(DELAY_TAG, mNextDelayExpiredListener,
    262                 mNextDelayExpiredElapsedMillis, uid);
    263     }
    264 
    265     /**
    266      * Set an alarm with the {@link android.app.AlarmManager} for the next time at which a job's
    267      * deadline will expire.
    268      * This alarm <b>will</b> wake up the phone.
    269      */
    270     private void setDeadlineExpiredAlarmLocked(long alarmTimeElapsedMillis, int uid) {
    271         alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis);
    272         mNextJobExpiredElapsedMillis = alarmTimeElapsedMillis;
    273         updateAlarmWithListenerLocked(DEADLINE_TAG, mDeadlineExpiredListener,
    274                 mNextJobExpiredElapsedMillis, uid);
    275     }
    276 
    277     private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) {
    278         final long earliestWakeupTimeElapsed = SystemClock.elapsedRealtime();
    279         if (proposedAlarmTimeElapsedMillis < earliestWakeupTimeElapsed) {
    280             return earliestWakeupTimeElapsed;
    281         }
    282         return proposedAlarmTimeElapsedMillis;
    283     }
    284 
    285     private void updateAlarmWithListenerLocked(String tag, OnAlarmListener listener,
    286             long alarmTimeElapsed, int uid) {
    287         ensureAlarmServiceLocked();
    288         if (alarmTimeElapsed == Long.MAX_VALUE) {
    289             mAlarmService.cancel(listener);
    290         } else {
    291             if (DEBUG) {
    292                 Slog.d(TAG, "Setting " + tag + " for: " + alarmTimeElapsed);
    293             }
    294             mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTimeElapsed,
    295                     AlarmManager.WINDOW_HEURISTIC, 0, tag, listener, null, new WorkSource(uid));
    296         }
    297     }
    298 
    299     // Job/delay expiration alarm handling
    300 
    301     private final OnAlarmListener mDeadlineExpiredListener = new OnAlarmListener() {
    302         @Override
    303         public void onAlarm() {
    304             if (DEBUG) {
    305                 Slog.d(TAG, "Deadline-expired alarm fired");
    306             }
    307             checkExpiredDeadlinesAndResetAlarm();
    308         }
    309     };
    310 
    311     private final OnAlarmListener mNextDelayExpiredListener = new OnAlarmListener() {
    312         @Override
    313         public void onAlarm() {
    314             if (DEBUG) {
    315                 Slog.d(TAG, "Delay-expired alarm fired");
    316             }
    317             checkExpiredDelaysAndResetAlarm();
    318         }
    319     };
    320 
    321     @Override
    322     public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
    323         final long nowElapsed = SystemClock.elapsedRealtime();
    324         pw.print("Alarms: now=");
    325         pw.print(SystemClock.elapsedRealtime());
    326         pw.println();
    327         pw.print("Next delay alarm in ");
    328         TimeUtils.formatDuration(mNextDelayExpiredElapsedMillis, nowElapsed, pw);
    329         pw.println();
    330         pw.print("Next deadline alarm in ");
    331         TimeUtils.formatDuration(mNextJobExpiredElapsedMillis, nowElapsed, pw);
    332         pw.println();
    333         pw.print("Tracking ");
    334         pw.print(mTrackedJobs.size());
    335         pw.println(":");
    336         for (JobStatus ts : mTrackedJobs) {
    337             if (!ts.shouldDump(filterUid)) {
    338                 continue;
    339             }
    340             pw.print("  #");
    341             ts.printUniqueId(pw);
    342             pw.print(" from ");
    343             UserHandle.formatUid(pw, ts.getSourceUid());
    344             pw.print(": Delay=");
    345             if (ts.hasTimingDelayConstraint()) {
    346                 TimeUtils.formatDuration(ts.getEarliestRunTime(), nowElapsed, pw);
    347             } else {
    348                 pw.print("N/A");
    349             }
    350             pw.print(", Deadline=");
    351             if (ts.hasDeadlineConstraint()) {
    352                 TimeUtils.formatDuration(ts.getLatestRunTimeElapsed(), nowElapsed, pw);
    353             } else {
    354                 pw.print("N/A");
    355             }
    356             pw.println();
    357         }
    358     }
    359 }