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 android.app.job; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.compat.annotation.UnsupportedAppUsage; 22 import android.content.ClipData; 23 import android.net.Network; 24 import android.net.Uri; 25 import android.os.Bundle; 26 import android.os.IBinder; 27 import android.os.Parcel; 28 import android.os.Parcelable; 29 import android.os.PersistableBundle; 30 import android.os.RemoteException; 31 32 /** 33 * Contains the parameters used to configure/identify your job. You do not create this object 34 * yourself, instead it is handed in to your application by the System. 35 */ 36 public class JobParameters implements Parcelable { 37 38 /** @hide */ 39 public static final int REASON_CANCELED = JobProtoEnums.STOP_REASON_CANCELLED; // 0. 40 /** @hide */ 41 public static final int REASON_CONSTRAINTS_NOT_SATISFIED = 42 JobProtoEnums.STOP_REASON_CONSTRAINTS_NOT_SATISFIED; //1. 43 /** @hide */ 44 public static final int REASON_PREEMPT = JobProtoEnums.STOP_REASON_PREEMPT; // 2. 45 /** @hide */ 46 public static final int REASON_TIMEOUT = JobProtoEnums.STOP_REASON_TIMEOUT; // 3. 47 /** @hide */ 48 public static final int REASON_DEVICE_IDLE = JobProtoEnums.STOP_REASON_DEVICE_IDLE; // 4. 49 /** @hide */ 50 public static final int REASON_DEVICE_THERMAL = JobProtoEnums.STOP_REASON_DEVICE_THERMAL; // 5. 51 /** 52 * The job is in the {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} 53 * bucket. 54 * 55 * @hide 56 */ 57 public static final int REASON_RESTRICTED_BUCKET = 58 JobProtoEnums.STOP_REASON_RESTRICTED_BUCKET; // 6. 59 60 /** 61 * All the stop reason codes. This should be regarded as an immutable array at runtime. 62 * 63 * Note the order of these values will affect "dumpsys batterystats", and we do not want to 64 * change the order of existing fields, so adding new fields is okay but do not remove or 65 * change existing fields. When deprecating a field, just replace that with "-1" in this array. 66 * 67 * @hide 68 */ 69 public static final int[] JOB_STOP_REASON_CODES = { 70 REASON_CANCELED, 71 REASON_CONSTRAINTS_NOT_SATISFIED, 72 REASON_PREEMPT, 73 REASON_TIMEOUT, 74 REASON_DEVICE_IDLE, 75 REASON_DEVICE_THERMAL, 76 REASON_RESTRICTED_BUCKET, 77 }; 78 79 /** 80 * @hide 81 */ 82 // TODO(142420609): make it @SystemApi for mainline 83 @NonNull getReasonCodeDescription(int reasonCode)84 public static String getReasonCodeDescription(int reasonCode) { 85 switch (reasonCode) { 86 case REASON_CANCELED: return "canceled"; 87 case REASON_CONSTRAINTS_NOT_SATISFIED: return "constraints"; 88 case REASON_PREEMPT: return "preempt"; 89 case REASON_TIMEOUT: return "timeout"; 90 case REASON_DEVICE_IDLE: return "device_idle"; 91 case REASON_DEVICE_THERMAL: return "thermal"; 92 case REASON_RESTRICTED_BUCKET: return "restricted_bucket"; 93 default: return "unknown:" + reasonCode; 94 } 95 } 96 97 /** @hide */ 98 // @SystemApi TODO make it a system api for mainline 99 @NonNull getJobStopReasonCodes()100 public static int[] getJobStopReasonCodes() { 101 return JOB_STOP_REASON_CODES; 102 } 103 104 @UnsupportedAppUsage 105 private final int jobId; 106 private final PersistableBundle extras; 107 private final Bundle transientExtras; 108 private final ClipData clipData; 109 private final int clipGrantFlags; 110 @UnsupportedAppUsage 111 private final IBinder callback; 112 private final boolean overrideDeadlineExpired; 113 private final Uri[] mTriggeredContentUris; 114 private final String[] mTriggeredContentAuthorities; 115 private final Network network; 116 117 private int stopReason; // Default value of stopReason is REASON_CANCELED 118 private String debugStopReason; // Human readable stop reason for debugging. 119 120 /** @hide */ JobParameters(IBinder callback, int jobId, PersistableBundle extras, Bundle transientExtras, ClipData clipData, int clipGrantFlags, boolean overrideDeadlineExpired, Uri[] triggeredContentUris, String[] triggeredContentAuthorities, Network network)121 public JobParameters(IBinder callback, int jobId, PersistableBundle extras, 122 Bundle transientExtras, ClipData clipData, int clipGrantFlags, 123 boolean overrideDeadlineExpired, Uri[] triggeredContentUris, 124 String[] triggeredContentAuthorities, Network network) { 125 this.jobId = jobId; 126 this.extras = extras; 127 this.transientExtras = transientExtras; 128 this.clipData = clipData; 129 this.clipGrantFlags = clipGrantFlags; 130 this.callback = callback; 131 this.overrideDeadlineExpired = overrideDeadlineExpired; 132 this.mTriggeredContentUris = triggeredContentUris; 133 this.mTriggeredContentAuthorities = triggeredContentAuthorities; 134 this.network = network; 135 } 136 137 /** 138 * @return The unique id of this job, specified at creation time. 139 */ getJobId()140 public int getJobId() { 141 return jobId; 142 } 143 144 /** 145 * Reason onStopJob() was called on this job. 146 * @hide 147 */ getStopReason()148 public int getStopReason() { 149 return stopReason; 150 } 151 152 /** 153 * Reason onStopJob() was called on this job. 154 * @hide 155 */ getDebugStopReason()156 public String getDebugStopReason() { 157 return debugStopReason; 158 } 159 160 /** 161 * @return The extras you passed in when constructing this job with 162 * {@link android.app.job.JobInfo.Builder#setExtras(android.os.PersistableBundle)}. This will 163 * never be null. If you did not set any extras this will be an empty bundle. 164 */ getExtras()165 public @NonNull PersistableBundle getExtras() { 166 return extras; 167 } 168 169 /** 170 * @return The transient extras you passed in when constructing this job with 171 * {@link android.app.job.JobInfo.Builder#setTransientExtras(android.os.Bundle)}. This will 172 * never be null. If you did not set any extras this will be an empty bundle. 173 */ getTransientExtras()174 public @NonNull Bundle getTransientExtras() { 175 return transientExtras; 176 } 177 178 /** 179 * @return The clip you passed in when constructing this job with 180 * {@link android.app.job.JobInfo.Builder#setClipData(ClipData, int)}. Will be null 181 * if it was not set. 182 */ getClipData()183 public @Nullable ClipData getClipData() { 184 return clipData; 185 } 186 187 /** 188 * @return The clip grant flags you passed in when constructing this job with 189 * {@link android.app.job.JobInfo.Builder#setClipData(ClipData, int)}. Will be 0 190 * if it was not set. 191 */ getClipGrantFlags()192 public int getClipGrantFlags() { 193 return clipGrantFlags; 194 } 195 196 /** 197 * For jobs with {@link android.app.job.JobInfo.Builder#setOverrideDeadline(long)} set, this 198 * provides an easy way to tell whether the job is being executed due to the deadline 199 * expiring. Note: If the job is running because its deadline expired, it implies that its 200 * constraints will not be met. 201 */ isOverrideDeadlineExpired()202 public boolean isOverrideDeadlineExpired() { 203 return overrideDeadlineExpired; 204 } 205 206 /** 207 * For jobs with {@link android.app.job.JobInfo.Builder#addTriggerContentUri} set, this 208 * reports which URIs have triggered the job. This will be null if either no URIs have 209 * triggered it (it went off due to a deadline or other reason), or the number of changed 210 * URIs is too large to report. Whether or not the number of URIs is too large, you can 211 * always use {@link #getTriggeredContentAuthorities()} to determine whether the job was 212 * triggered due to any content changes and the authorities they are associated with. 213 */ getTriggeredContentUris()214 public @Nullable Uri[] getTriggeredContentUris() { 215 return mTriggeredContentUris; 216 } 217 218 /** 219 * For jobs with {@link android.app.job.JobInfo.Builder#addTriggerContentUri} set, this 220 * reports which content authorities have triggered the job. It will only be null if no 221 * authorities have triggered it -- that is, the job executed for some other reason, such 222 * as a deadline expiring. If this is non-null, you can use {@link #getTriggeredContentUris()} 223 * to retrieve the details of which URIs changed (as long as that has not exceeded the maximum 224 * number it can reported). 225 */ getTriggeredContentAuthorities()226 public @Nullable String[] getTriggeredContentAuthorities() { 227 return mTriggeredContentAuthorities; 228 } 229 230 /** 231 * Return the network that should be used to perform any network requests 232 * for this job. 233 * <p> 234 * Devices may have multiple active network connections simultaneously, or 235 * they may not have a default network route at all. To correctly handle all 236 * situations like this, your job should always use the network returned by 237 * this method instead of implicitly using the default network route. 238 * <p> 239 * Note that the system may relax the constraints you originally requested, 240 * such as allowing a {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over 241 * a metered network when there is a surplus of metered data available. 242 * 243 * @return the network that should be used to perform any network requests 244 * for this job, or {@code null} if this job didn't set any required 245 * network type. 246 * @see JobInfo.Builder#setRequiredNetworkType(int) 247 */ getNetwork()248 public @Nullable Network getNetwork() { 249 return network; 250 } 251 252 /** 253 * Dequeue the next pending {@link JobWorkItem} from these JobParameters associated with their 254 * currently running job. Calling this method when there is no more work available and all 255 * previously dequeued work has been completed will result in the system taking care of 256 * stopping the job for you -- 257 * you should not call {@link JobService#jobFinished(JobParameters, boolean)} yourself 258 * (otherwise you risk losing an upcoming JobWorkItem that is being enqueued at the same time). 259 * 260 * <p>Once you are done with the {@link JobWorkItem} returned by this method, you must call 261 * {@link #completeWork(JobWorkItem)} with it to inform the system that you are done 262 * executing the work. The job will not be finished until all dequeued work has been 263 * completed. You do not, however, have to complete each returned work item before deqeueing 264 * the next one -- you can use {@link #dequeueWork()} multiple times before completing 265 * previous work if you want to process work in parallel, and you can complete the work 266 * in whatever order you want.</p> 267 * 268 * <p>If the job runs to the end of its available time period before all work has been 269 * completed, it will stop as normal. You should return true from 270 * {@link JobService#onStopJob(JobParameters)} in order to have the job rescheduled, and by 271 * doing so any pending as well as remaining uncompleted work will be re-queued 272 * for the next time the job runs.</p> 273 * 274 * <p>This example shows how to construct a JobService that will serially dequeue and 275 * process work that is available for it:</p> 276 * 277 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/JobWorkService.java 278 * service} 279 * 280 * @return Returns a new {@link JobWorkItem} if there is one pending, otherwise null. 281 * If null is returned, the system will also stop the job if all work has also been completed. 282 * (This means that for correct operation, you must always call dequeueWork() after you have 283 * completed other work, to check either for more work or allow the system to stop the job.) 284 */ dequeueWork()285 public @Nullable JobWorkItem dequeueWork() { 286 try { 287 return getCallback().dequeueWork(getJobId()); 288 } catch (RemoteException e) { 289 throw e.rethrowFromSystemServer(); 290 } 291 } 292 293 /** 294 * Report the completion of executing a {@link JobWorkItem} previously returned by 295 * {@link #dequeueWork()}. This tells the system you are done with the 296 * work associated with that item, so it will not be returned again. Note that if this 297 * is the last work in the queue, completing it here will <em>not</em> finish the overall 298 * job -- for that to happen, you still need to call {@link #dequeueWork()} 299 * again. 300 * 301 * <p>If you are enqueueing work into a job, you must call this method for each piece 302 * of work you process. Do <em>not</em> call 303 * {@link JobService#jobFinished(JobParameters, boolean)} 304 * or else you can lose work in your queue.</p> 305 * 306 * @param work The work you have completed processing, as previously returned by 307 * {@link #dequeueWork()} 308 */ completeWork(@onNull JobWorkItem work)309 public void completeWork(@NonNull JobWorkItem work) { 310 try { 311 if (!getCallback().completeWork(getJobId(), work.getWorkId())) { 312 throw new IllegalArgumentException("Given work is not active: " + work); 313 } 314 } catch (RemoteException e) { 315 throw e.rethrowFromSystemServer(); 316 } 317 } 318 319 /** @hide */ 320 @UnsupportedAppUsage getCallback()321 public IJobCallback getCallback() { 322 return IJobCallback.Stub.asInterface(callback); 323 } 324 JobParameters(Parcel in)325 private JobParameters(Parcel in) { 326 jobId = in.readInt(); 327 extras = in.readPersistableBundle(); 328 transientExtras = in.readBundle(); 329 if (in.readInt() != 0) { 330 clipData = ClipData.CREATOR.createFromParcel(in); 331 clipGrantFlags = in.readInt(); 332 } else { 333 clipData = null; 334 clipGrantFlags = 0; 335 } 336 callback = in.readStrongBinder(); 337 overrideDeadlineExpired = in.readInt() == 1; 338 mTriggeredContentUris = in.createTypedArray(Uri.CREATOR); 339 mTriggeredContentAuthorities = in.createStringArray(); 340 if (in.readInt() != 0) { 341 network = Network.CREATOR.createFromParcel(in); 342 } else { 343 network = null; 344 } 345 stopReason = in.readInt(); 346 debugStopReason = in.readString(); 347 } 348 349 /** @hide */ setStopReason(int reason, String debugStopReason)350 public void setStopReason(int reason, String debugStopReason) { 351 stopReason = reason; 352 this.debugStopReason = debugStopReason; 353 } 354 355 @Override describeContents()356 public int describeContents() { 357 return 0; 358 } 359 360 @Override writeToParcel(Parcel dest, int flags)361 public void writeToParcel(Parcel dest, int flags) { 362 dest.writeInt(jobId); 363 dest.writePersistableBundle(extras); 364 dest.writeBundle(transientExtras); 365 if (clipData != null) { 366 dest.writeInt(1); 367 clipData.writeToParcel(dest, flags); 368 dest.writeInt(clipGrantFlags); 369 } else { 370 dest.writeInt(0); 371 } 372 dest.writeStrongBinder(callback); 373 dest.writeInt(overrideDeadlineExpired ? 1 : 0); 374 dest.writeTypedArray(mTriggeredContentUris, flags); 375 dest.writeStringArray(mTriggeredContentAuthorities); 376 if (network != null) { 377 dest.writeInt(1); 378 network.writeToParcel(dest, flags); 379 } else { 380 dest.writeInt(0); 381 } 382 dest.writeInt(stopReason); 383 dest.writeString(debugStopReason); 384 } 385 386 public static final @android.annotation.NonNull Creator<JobParameters> CREATOR = new Creator<JobParameters>() { 387 @Override 388 public JobParameters createFromParcel(Parcel in) { 389 return new JobParameters(in); 390 } 391 392 @Override 393 public JobParameters[] newArray(int size) { 394 return new JobParameters[size]; 395 } 396 }; 397 } 398