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. */
getServiceToken()73     public int getServiceToken() {
74         return uId;
75     }
76 
JobStatus(JobInfo job, int uId, int numFailures)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. */
JobStatus(JobInfo job, int uId)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      */
JobStatus(JobInfo job, int uId, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis)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. */
JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis, long newLatestRuntimeElapsedMillis, int backoffAttempt)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 
getJob()126     public JobInfo getJob() {
127         return job;
128     }
129 
getJobId()130     public int getJobId() {
131         return job.getId();
132     }
133 
getNumFailures()134     public int getNumFailures() {
135         return numFailures;
136     }
137 
getServiceComponent()138     public ComponentName getServiceComponent() {
139         return job.getService();
140     }
141 
getUserId()142     public int getUserId() {
143         return UserHandle.getUserId(uId);
144     }
145 
getUid()146     public int getUid() {
147         return uId;
148     }
149 
getName()150     public String getName() {
151         return name;
152     }
153 
getTag()154     public String getTag() {
155         return tag;
156     }
157 
getExtras()158     public PersistableBundle getExtras() {
159         return job.getExtras();
160     }
161 
hasConnectivityConstraint()162     public boolean hasConnectivityConstraint() {
163         return job.getNetworkType() == JobInfo.NETWORK_TYPE_ANY;
164     }
165 
hasUnmeteredConstraint()166     public boolean hasUnmeteredConstraint() {
167         return job.getNetworkType() == JobInfo.NETWORK_TYPE_UNMETERED;
168     }
169 
hasChargingConstraint()170     public boolean hasChargingConstraint() {
171         return job.isRequireCharging();
172     }
173 
hasTimingDelayConstraint()174     public boolean hasTimingDelayConstraint() {
175         return earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME;
176     }
177 
hasDeadlineConstraint()178     public boolean hasDeadlineConstraint() {
179         return latestRunTimeElapsedMillis != NO_LATEST_RUNTIME;
180     }
181 
hasIdleConstraint()182     public boolean hasIdleConstraint() {
183         return job.isRequireDeviceIdle();
184     }
185 
isPersisted()186     public boolean isPersisted() {
187         return job.isPersisted();
188     }
189 
getEarliestRunTime()190     public long getEarliestRunTime() {
191         return earliestRunTimeElapsedMillis;
192     }
193 
getLatestRunTimeElapsed()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      */
isReady()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      */
isConstraintsSatisfied()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 
matches(int uid, int jobId)221     public boolean matches(int uid, int jobId) {
222         return this.job.getId() == jobId && this.uId == uid;
223     }
224 
225     @Override
toString()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 
formatRunTime(long runtime, long defaultValue)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      */
toShortString()259     public String toShortString() {
260         return job.getService().flattenToShortString() + " jId=" + job.getId() +
261                 ", u" + getUserId();
262     }
263 
264     // Dumpsys infrastructure
dump(PrintWriter pw, String prefix)265     public void dump(PrintWriter pw, String prefix) {
266         pw.print(prefix);
267         pw.println(this.toString());
268     }
269 }
270