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