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     final AtomicBoolean appNotIdleConstraintSatisfied = new AtomicBoolean();
     58 
     59     /**
     60      * Earliest point in the future at which this job will be eligible to run. A value of 0
     61      * indicates there is no delay constraint. See {@link #hasTimingDelayConstraint()}.
     62      */
     63     private long earliestRunTimeElapsedMillis;
     64     /**
     65      * Latest point in the future at which this job must be run. A value of {@link Long#MAX_VALUE}
     66      * indicates there is no deadline constraint. See {@link #hasDeadlineConstraint()}.
     67      */
     68     private long latestRunTimeElapsedMillis;
     69     /** How many times this job has failed, used to compute back-off. */
     70     private final int numFailures;
     71 
     72     /** Provide a handle to the service that this job will be run on. */
     73     public int getServiceToken() {
     74         return uId;
     75     }
     76 
     77     private JobStatus(JobInfo job, int uId, int numFailures) {
     78         this.job = job;
     79         this.uId = uId;
     80         this.name = job.getService().flattenToShortString();
     81         this.tag = "*job*/" + this.name;
     82         this.numFailures = numFailures;
     83     }
     84 
     85     /** Create a newly scheduled job. */
     86     public JobStatus(JobInfo job, int uId) {
     87         this(job, uId, 0);
     88 
     89         final long elapsedNow = SystemClock.elapsedRealtime();
     90 
     91         if (job.isPeriodic()) {
     92             earliestRunTimeElapsedMillis = elapsedNow;
     93             latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis();
     94         } else {
     95             earliestRunTimeElapsedMillis = job.hasEarlyConstraint() ?
     96                     elapsedNow + job.getMinLatencyMillis() : NO_EARLIEST_RUNTIME;
     97             latestRunTimeElapsedMillis = job.hasLateConstraint() ?
     98                     elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME;
     99         }
    100     }
    101 
    102     /**
    103      * Create a new JobStatus that was loaded from disk. We ignore the provided
    104      * {@link android.app.job.JobInfo} time criteria because we can load a persisted periodic job
    105      * from the {@link com.android.server.job.JobStore} and still want to respect its
    106      * wallclock runtime rather than resetting it on every boot.
    107      * We consider a freshly loaded job to no longer be in back-off.
    108      */
    109     public JobStatus(JobInfo job, int uId, long earliestRunTimeElapsedMillis,
    110                       long latestRunTimeElapsedMillis) {
    111         this(job, uId, 0);
    112 
    113         this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis;
    114         this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
    115     }
    116 
    117     /** Create a new job to be rescheduled with the provided parameters. */
    118     public JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis,
    119                       long newLatestRuntimeElapsedMillis, int backoffAttempt) {
    120         this(rescheduling.job, rescheduling.getUid(), backoffAttempt);
    121 
    122         earliestRunTimeElapsedMillis = newEarliestRuntimeElapsedMillis;
    123         latestRunTimeElapsedMillis = newLatestRuntimeElapsedMillis;
    124     }
    125 
    126     public JobInfo getJob() {
    127         return job;
    128     }
    129 
    130     public int getJobId() {
    131         return job.getId();
    132     }
    133 
    134     public int getNumFailures() {
    135         return numFailures;
    136     }
    137 
    138     public ComponentName getServiceComponent() {
    139         return job.getService();
    140     }
    141 
    142     public int getUserId() {
    143         return UserHandle.getUserId(uId);
    144     }
    145 
    146     public int getUid() {
    147         return uId;
    148     }
    149 
    150     public String getName() {
    151         return name;
    152     }
    153 
    154     public String getTag() {
    155         return tag;
    156     }
    157 
    158     public PersistableBundle getExtras() {
    159         return job.getExtras();
    160     }
    161 
    162     public boolean hasConnectivityConstraint() {
    163         return job.getNetworkType() == JobInfo.NETWORK_TYPE_ANY;
    164     }
    165 
    166     public boolean hasUnmeteredConstraint() {
    167         return job.getNetworkType() == JobInfo.NETWORK_TYPE_UNMETERED;
    168     }
    169 
    170     public boolean hasChargingConstraint() {
    171         return job.isRequireCharging();
    172     }
    173 
    174     public boolean hasTimingDelayConstraint() {
    175         return earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME;
    176     }
    177 
    178     public boolean hasDeadlineConstraint() {
    179         return latestRunTimeElapsedMillis != NO_LATEST_RUNTIME;
    180     }
    181 
    182     public boolean hasIdleConstraint() {
    183         return job.isRequireDeviceIdle();
    184     }
    185 
    186     public boolean isPersisted() {
    187         return job.isPersisted();
    188     }
    189 
    190     public long getEarliestRunTime() {
    191         return earliestRunTimeElapsedMillis;
    192     }
    193 
    194     public long getLatestRunTimeElapsed() {
    195         return latestRunTimeElapsedMillis;
    196     }
    197 
    198     /**
    199      * @return Whether or not this job is ready to run, based on its requirements. This is true if
    200      * the constraints are satisfied <strong>or</strong> the deadline on the job has expired.
    201      */
    202     public synchronized boolean isReady() {
    203         // Deadline constraint trumps other constraints
    204         // AppNotIdle implicit constraint trumps all!
    205         return (isConstraintsSatisfied()
    206                     || (hasDeadlineConstraint() && deadlineConstraintSatisfied.get()))
    207                 && appNotIdleConstraintSatisfied.get();
    208     }
    209 
    210     /**
    211      * @return Whether the constraints set on this job are satisfied.
    212      */
    213     public synchronized boolean isConstraintsSatisfied() {
    214         return (!hasChargingConstraint() || chargingConstraintSatisfied.get())
    215                 && (!hasTimingDelayConstraint() || timeDelayConstraintSatisfied.get())
    216                 && (!hasConnectivityConstraint() || connectivityConstraintSatisfied.get())
    217                 && (!hasUnmeteredConstraint() || unmeteredConstraintSatisfied.get())
    218                 && (!hasIdleConstraint() || idleConstraintSatisfied.get());
    219     }
    220 
    221     public boolean matches(int uid, int jobId) {
    222         return this.job.getId() == jobId && this.uId == uid;
    223     }
    224 
    225     @Override
    226     public String toString() {
    227         return String.valueOf(hashCode()).substring(0, 3) + ".."
    228                 + ":[" + job.getService()
    229                 + ",jId=" + job.getId()
    230                 + ",u" + getUserId()
    231                 + ",R=(" + formatRunTime(earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME)
    232                 + "," + formatRunTime(latestRunTimeElapsedMillis, NO_LATEST_RUNTIME) + ")"
    233                 + ",N=" + job.getNetworkType() + ",C=" + job.isRequireCharging()
    234                 + ",I=" + job.isRequireDeviceIdle() + ",F=" + numFailures
    235                 + ",P=" + job.isPersisted()
    236                 + ",ANI=" + appNotIdleConstraintSatisfied.get()
    237                 + (isReady() ? "(READY)" : "")
    238                 + "]";
    239     }
    240 
    241     private String formatRunTime(long runtime, long  defaultValue) {
    242         if (runtime == defaultValue) {
    243             return "none";
    244         } else {
    245             long elapsedNow = SystemClock.elapsedRealtime();
    246             long nextRuntime = runtime - elapsedNow;
    247             if (nextRuntime > 0) {
    248                 return DateUtils.formatElapsedTime(nextRuntime / 1000);
    249             } else {
    250                 return "-" + DateUtils.formatElapsedTime(nextRuntime / -1000);
    251             }
    252         }
    253     }
    254 
    255     /**
    256      * Convenience function to identify a job uniquely without pulling all the data that
    257      * {@link #toString()} returns.
    258      */
    259     public String toShortString() {
    260         return job.getService().flattenToShortString() + " jId=" + job.getId() +
    261                 ", u" + getUserId();
    262     }
    263 
    264     // Dumpsys infrastructure
    265     public void dump(PrintWriter pw, String prefix) {
    266         pw.print(prefix);
    267         pw.println(this.toString());
    268     }
    269 }
    270