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.job.JobInfo;
     20 import android.content.ComponentName;
     21 import android.os.PersistableBundle;
     22 import android.os.SystemClock;
     23 import android.os.UserHandle;
     24 import android.text.format.DateUtils;
     25 
     26 import java.io.PrintWriter;
     27 import java.util.concurrent.atomic.AtomicBoolean;
     28 
     29 /**
     30  * Uniquely identifies a job internally.
     31  * Created from the public {@link android.app.job.JobInfo} object when it lands on the scheduler.
     32  * Contains current state of the requirements of the job, as well as a function to evaluate
     33  * whether it's ready to run.
     34  * This object is shared among the various controllers - hence why the different fields are atomic.
     35  * This isn't strictly necessary because each controller is only interested in a specific field,
     36  * and the receivers that are listening for global state change will all run on the main looper,
     37  * but we don't enforce that so this is safer.
     38  * @hide
     39  */
     40 public class JobStatus {
     41     public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
     42     public static final long NO_EARLIEST_RUNTIME = 0L;
     43 
     44     final JobInfo job;
     45     /** Uid of the package requesting this job. */
     46     final int uId;
     47     final String name;
     48     final String tag;
     49 
     50     // Constraints.
     51     final AtomicBoolean chargingConstraintSatisfied = new AtomicBoolean();
     52     final AtomicBoolean timeDelayConstraintSatisfied = new AtomicBoolean();
     53     final AtomicBoolean deadlineConstraintSatisfied = new AtomicBoolean();
     54     final AtomicBoolean idleConstraintSatisfied = new AtomicBoolean();
     55     final AtomicBoolean unmeteredConstraintSatisfied = new AtomicBoolean();
     56     final AtomicBoolean connectivityConstraintSatisfied = new AtomicBoolean();
     57 
     58     /**
     59      * Earliest point in the future at which this job will be eligible to run. A value of 0
     60      * indicates there is no delay constraint. See {@link #hasTimingDelayConstraint()}.
     61      */
     62     private long earliestRunTimeElapsedMillis;
     63     /**
     64      * Latest point in the future at which this job must be run. A value of {@link Long#MAX_VALUE}
     65      * indicates there is no deadline constraint. See {@link #hasDeadlineConstraint()}.
     66      */
     67     private long latestRunTimeElapsedMillis;
     68     /** How many times this job has failed, used to compute back-off. */
     69     private final int numFailures;
     70 
     71     /** Provide a handle to the service that this job will be run on. */
     72     public int getServiceToken() {
     73         return uId;
     74     }
     75 
     76     private JobStatus(JobInfo job, int uId, int numFailures) {
     77         this.job = job;
     78         this.uId = uId;
     79         this.name = job.getService().flattenToShortString();
     80         this.tag = "*job*/" + this.name;
     81         this.numFailures = numFailures;
     82     }
     83 
     84     /** Create a newly scheduled job. */
     85     public JobStatus(JobInfo job, int uId) {
     86         this(job, uId, 0);
     87 
     88         final long elapsedNow = SystemClock.elapsedRealtime();
     89 
     90         if (job.isPeriodic()) {
     91             earliestRunTimeElapsedMillis = elapsedNow;
     92             latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis();
     93         } else {
     94             earliestRunTimeElapsedMillis = job.hasEarlyConstraint() ?
     95                     elapsedNow + job.getMinLatencyMillis() : NO_EARLIEST_RUNTIME;
     96             latestRunTimeElapsedMillis = job.hasLateConstraint() ?
     97                     elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME;
     98         }
     99     }
    100 
    101     /**
    102      * Create a new JobStatus that was loaded from disk. We ignore the provided
    103      * {@link android.app.job.JobInfo} time criteria because we can load a persisted periodic job
    104      * from the {@link com.android.server.job.JobStore} and still want to respect its
    105      * wallclock runtime rather than resetting it on every boot.
    106      * We consider a freshly loaded job to no longer be in back-off.
    107      */
    108     public JobStatus(JobInfo job, int uId, long earliestRunTimeElapsedMillis,
    109                       long latestRunTimeElapsedMillis) {
    110         this(job, uId, 0);
    111 
    112         this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis;
    113         this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
    114     }
    115 
    116     /** Create a new job to be rescheduled with the provided parameters. */
    117     public JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis,
    118                       long newLatestRuntimeElapsedMillis, int backoffAttempt) {
    119         this(rescheduling.job, rescheduling.getUid(), backoffAttempt);
    120 
    121         earliestRunTimeElapsedMillis = newEarliestRuntimeElapsedMillis;
    122         latestRunTimeElapsedMillis = newLatestRuntimeElapsedMillis;
    123     }
    124 
    125     public JobInfo getJob() {
    126         return job;
    127     }
    128 
    129     public int getJobId() {
    130         return job.getId();
    131     }
    132 
    133     public int getNumFailures() {
    134         return numFailures;
    135     }
    136 
    137     public ComponentName getServiceComponent() {
    138         return job.getService();
    139     }
    140 
    141     public int getUserId() {
    142         return UserHandle.getUserId(uId);
    143     }
    144 
    145     public int getUid() {
    146         return uId;
    147     }
    148 
    149     public String getName() {
    150         return name;
    151     }
    152 
    153     public String getTag() {
    154         return tag;
    155     }
    156 
    157     public PersistableBundle getExtras() {
    158         return job.getExtras();
    159     }
    160 
    161     public boolean hasConnectivityConstraint() {
    162         return job.getNetworkType() == JobInfo.NETWORK_TYPE_ANY;
    163     }
    164 
    165     public boolean hasUnmeteredConstraint() {
    166         return job.getNetworkType() == JobInfo.NETWORK_TYPE_UNMETERED;
    167     }
    168 
    169     public boolean hasChargingConstraint() {
    170         return job.isRequireCharging();
    171     }
    172 
    173     public boolean hasTimingDelayConstraint() {
    174         return earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME;
    175     }
    176 
    177     public boolean hasDeadlineConstraint() {
    178         return latestRunTimeElapsedMillis != NO_LATEST_RUNTIME;
    179     }
    180 
    181     public boolean hasIdleConstraint() {
    182         return job.isRequireDeviceIdle();
    183     }
    184 
    185     public boolean isPersisted() {
    186         return job.isPersisted();
    187     }
    188 
    189     public long getEarliestRunTime() {
    190         return earliestRunTimeElapsedMillis;
    191     }
    192 
    193     public long getLatestRunTimeElapsed() {
    194         return latestRunTimeElapsedMillis;
    195     }
    196 
    197     /**
    198      * @return Whether or not this job is ready to run, based on its requirements. This is true if
    199      * the constraints are satisfied <strong>or</strong> the deadline on the job has expired.
    200      */
    201     public synchronized boolean isReady() {
    202         return isConstraintsSatisfied()
    203                 || (hasDeadlineConstraint() && deadlineConstraintSatisfied.get());
    204     }
    205 
    206     /**
    207      * @return Whether the constraints set on this job are satisfied.
    208      */
    209     public synchronized boolean isConstraintsSatisfied() {
    210         return (!hasChargingConstraint() || chargingConstraintSatisfied.get())
    211                 && (!hasTimingDelayConstraint() || timeDelayConstraintSatisfied.get())
    212                 && (!hasConnectivityConstraint() || connectivityConstraintSatisfied.get())
    213                 && (!hasUnmeteredConstraint() || unmeteredConstraintSatisfied.get())
    214                 && (!hasIdleConstraint() || idleConstraintSatisfied.get());
    215     }
    216 
    217     public boolean matches(int uid, int jobId) {
    218         return this.job.getId() == jobId && this.uId == uid;
    219     }
    220 
    221     @Override
    222     public String toString() {
    223         return String.valueOf(hashCode()).substring(0, 3) + ".."
    224                 + ":[" + job.getService()
    225                 + ",jId=" + job.getId()
    226                 + ",u" + getUserId()
    227                 + ",R=(" + formatRunTime(earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME)
    228                 + "," + formatRunTime(latestRunTimeElapsedMillis, NO_LATEST_RUNTIME) + ")"
    229                 + ",N=" + job.getNetworkType() + ",C=" + job.isRequireCharging()
    230                 + ",I=" + job.isRequireDeviceIdle() + ",F=" + numFailures
    231                 + ",P=" + job.isPersisted()
    232                 + (isReady() ? "(READY)" : "")
    233                 + "]";
    234     }
    235 
    236     private String formatRunTime(long runtime, long  defaultValue) {
    237         if (runtime == defaultValue) {
    238             return "none";
    239         } else {
    240             long elapsedNow = SystemClock.elapsedRealtime();
    241             long nextRuntime = runtime - elapsedNow;
    242             if (nextRuntime > 0) {
    243                 return DateUtils.formatElapsedTime(nextRuntime / 1000);
    244             } else {
    245                 return "-" + DateUtils.formatElapsedTime(nextRuntime / -1000);
    246             }
    247         }
    248     }
    249 
    250     /**
    251      * Convenience function to identify a job uniquely without pulling all the data that
    252      * {@link #toString()} returns.
    253      */
    254     public String toShortString() {
    255         return job.getService().flattenToShortString() + " jId=" + job.getId() +
    256                 ", u" + getUserId();
    257     }
    258 
    259     // Dumpsys infrastructure
    260     public void dump(PrintWriter pw, String prefix) {
    261         pw.print(prefix);
    262         pw.println(this.toString());
    263     }
    264 }
    265