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 static android.app.job.JobScheduler.THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION; 20 21 import android.annotation.BytesLong; 22 import android.annotation.IntDef; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.app.Notification; 26 import android.app.Service; 27 import android.compat.Compatibility; 28 import android.content.Intent; 29 import android.os.IBinder; 30 import android.util.Log; 31 32 import java.lang.annotation.Retention; 33 import java.lang.annotation.RetentionPolicy; 34 35 /** 36 * <p>Entry point for the callback from the {@link android.app.job.JobScheduler}.</p> 37 * <p>This is the base class that handles asynchronous requests that were previously scheduled. You 38 * are responsible for overriding {@link JobService#onStartJob(JobParameters)}, which is where 39 * you will implement your job logic.</p> 40 * <p>This service executes each incoming job on a {@link android.os.Handler} running on your 41 * application's main thread. This means that you <b>must</b> offload your execution logic to 42 * another thread/handler/{@link android.os.AsyncTask} of your choosing. Not doing so will result 43 * in blocking any future callbacks from the JobScheduler - specifically 44 * {@link #onStopJob(android.app.job.JobParameters)}, which is meant to inform you that the 45 * scheduling requirements are no longer being met.</p> 46 * 47 * <p class="note"> 48 * Since the introduction of JobScheduler, if an app did not return from 49 * {@link #onStartJob(JobParameters)} within several seconds, JobScheduler would consider the app 50 * unresponsive and clean up job execution. In such cases, the app was no longer considered 51 * to be running a job and therefore did not have any of the job lifecycle guarantees outlined 52 * in {@link JobScheduler}. However, prior to Android version 53 * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the failure and cleanup were silent 54 * and apps had no indication that they no longer had job lifecycle guarantees. 55 * Starting with Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, 56 * JobScheduler will explicitly trigger an ANR in such cases so that apps and developers 57 * can be aware of the issue. 58 * Similar behavior applies to the return time from {@link #onStopJob(JobParameters)} as well. 59 * <br /> <br /> 60 * If you see ANRs, then the app may be doing too much work on the UI thread. Ensure that 61 * potentially long operations are moved to a worker thread. 62 * 63 * <p>As a subclass of {@link Service}, there will only be one active instance of any JobService 64 * subclasses, regardless of job ID. This means that if you schedule multiple jobs with different 65 * job IDs but using the same JobService class, that JobService may receive multiple calls to 66 * {@link #onStartJob(JobParameters)} and {@link #onStopJob(JobParameters)}, with each call being 67 * for the separate jobs.</p> 68 */ 69 public abstract class JobService extends Service { 70 private static final String TAG = "JobService"; 71 72 /** 73 * Job services must be protected with this permission: 74 * 75 * <pre class="prettyprint"> 76 * <service android:name="MyJobService" 77 * android:permission="android.permission.BIND_JOB_SERVICE" > 78 * ... 79 * </service> 80 * </pre> 81 * 82 * <p>If a job service is declared in the manifest but not protected with this 83 * permission, that service will be ignored by the system. 84 */ 85 public static final String PERMISSION_BIND = 86 "android.permission.BIND_JOB_SERVICE"; 87 88 /** 89 * Detach the notification supplied to 90 * {@link #setNotification(JobParameters, int, Notification, int)} when the job ends. 91 * The notification will remain shown even after JobScheduler stops the job. 92 */ 93 public static final int JOB_END_NOTIFICATION_POLICY_DETACH = 0; 94 /** 95 * Cancel and remove the notification supplied to 96 * {@link #setNotification(JobParameters, int, Notification, int)} when the job ends. 97 * The notification will be removed from the notification shade. 98 */ 99 public static final int JOB_END_NOTIFICATION_POLICY_REMOVE = 1; 100 101 /** @hide */ 102 @IntDef(prefix = {"JOB_END_NOTIFICATION_POLICY_"}, value = { 103 JOB_END_NOTIFICATION_POLICY_DETACH, 104 JOB_END_NOTIFICATION_POLICY_REMOVE, 105 }) 106 @Retention(RetentionPolicy.SOURCE) 107 public @interface JobEndNotificationPolicy { 108 } 109 110 private JobServiceEngine mEngine; 111 112 /** @hide */ onBind(Intent intent)113 public final IBinder onBind(Intent intent) { 114 if (mEngine == null) { 115 mEngine = new JobServiceEngine(this) { 116 @Override 117 public boolean onStartJob(JobParameters params) { 118 return JobService.this.onStartJob(params); 119 } 120 121 @Override 122 public boolean onStopJob(JobParameters params) { 123 return JobService.this.onStopJob(params); 124 } 125 126 @Override 127 @BytesLong 128 public long getTransferredDownloadBytes(@NonNull JobParameters params, 129 @Nullable JobWorkItem item) { 130 if (item == null) { 131 return JobService.this.getTransferredDownloadBytes(params); 132 } else { 133 return JobService.this.getTransferredDownloadBytes(params, item); 134 } 135 } 136 137 @Override 138 @BytesLong 139 public long getTransferredUploadBytes(@NonNull JobParameters params, 140 @Nullable JobWorkItem item) { 141 if (item == null) { 142 return JobService.this.getTransferredUploadBytes(params); 143 } else { 144 return JobService.this.getTransferredUploadBytes(params, item); 145 } 146 } 147 148 @Override 149 public void onNetworkChanged(@NonNull JobParameters params) { 150 JobService.this.onNetworkChanged(params); 151 } 152 }; 153 } 154 return mEngine.getBinder(); 155 } 156 157 /** 158 * Call this to inform the JobScheduler that the job has finished its work. When the 159 * system receives this message, it releases the wakelock being held for the job. 160 * This does not need to be called if {@link #onStopJob(JobParameters)} has been called. 161 * <p> 162 * You can request that the job be scheduled again by passing {@code true} as 163 * the <code>wantsReschedule</code> parameter. This will apply back-off policy 164 * for the job; this policy can be adjusted through the 165 * {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)} method 166 * when the job is originally scheduled. The job's initial 167 * requirements are preserved when jobs are rescheduled, regardless of backed-off 168 * policy. 169 * <p class="note"> 170 * A job running while the device is dozing will not be rescheduled with the normal back-off 171 * policy. Instead, the job will be re-added to the queue and executed again during 172 * a future idle maintenance window. 173 * </p> 174 * 175 * <p class="note"> 176 * Any {@link JobInfo.Builder#setUserInitiated(boolean) user-initiated job} 177 * cannot be rescheduled when the user has asked to stop the app 178 * via a system provided affordance (such as the Task Manager). 179 * In such situations, the value of {@code wantsReschedule} is always treated as {@code false}. 180 * 181 * @param params The parameters identifying this job, as supplied to 182 * the job in the {@link #onStartJob(JobParameters)} callback. 183 * @param wantsReschedule {@code true} if this job should be rescheduled according 184 * to the back-off criteria specified when it was first scheduled; {@code false} 185 * otherwise. When {@code false} is returned for a periodic job, 186 * the job will be rescheduled according to its periodic policy. 187 */ jobFinished(JobParameters params, boolean wantsReschedule)188 public final void jobFinished(JobParameters params, boolean wantsReschedule) { 189 mEngine.jobFinished(params, wantsReschedule); 190 } 191 192 /** 193 * Called to indicate that the job has begun executing. Override this method with the 194 * logic for your job. Like all other component lifecycle callbacks, this method executes 195 * on your application's main thread. 196 * <p> 197 * Return {@code true} from this method if your job needs to continue running. If you 198 * do this, the job remains active until you call 199 * {@link #jobFinished(JobParameters, boolean)} to tell the system that it has completed 200 * its work, or until the job's required constraints are no longer satisfied. For 201 * example, if the job was scheduled using 202 * {@link JobInfo.Builder#setRequiresCharging(boolean) setRequiresCharging(true)}, 203 * it will be immediately halted by the system if the user unplugs the device from power, 204 * the job's {@link #onStopJob(JobParameters)} callback will be invoked, and the app 205 * will be expected to shut down all ongoing work connected with that job. 206 * <p> 207 * The system holds a wakelock on behalf of your app as long as your job is executing. 208 * This wakelock is acquired before this method is invoked, and is not released until either 209 * you call {@link #jobFinished(JobParameters, boolean)}, or after the system invokes 210 * {@link #onStopJob(JobParameters)} to notify your job that it is being shut down 211 * prematurely. 212 * <p> 213 * Returning {@code false} from this method means your job is already finished. The 214 * system's wakelock for the job will be released, and {@link #onStopJob(JobParameters)} 215 * will not be invoked. 216 * 217 * @param params Parameters specifying info about this job, including the optional 218 * extras configured with {@link JobInfo.Builder#setExtras(android.os.PersistableBundle)}. 219 * This object serves to identify this specific running job instance when calling 220 * {@link #jobFinished(JobParameters, boolean)}. 221 * @return {@code true} if your service will continue running, using a separate thread 222 * when appropriate. {@code false} means that this job has completed its work. 223 */ onStartJob(JobParameters params)224 public abstract boolean onStartJob(JobParameters params); 225 226 /** 227 * This method is called if the system has determined that you must stop execution of your job 228 * even before you've had a chance to call {@link #jobFinished(JobParameters, boolean)}. 229 * Once this method is called, you no longer need to call 230 * {@link #jobFinished(JobParameters, boolean)}. 231 * 232 * <p>This may happen if the requirements specified at schedule time are no longer met. For 233 * example you may have requested WiFi with 234 * {@link android.app.job.JobInfo.Builder#setRequiredNetworkType(int)}, yet while your 235 * job was executing the user toggled WiFi. Another example is if you had specified 236 * {@link android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)}, and the phone left 237 * its idle state. There are many other reasons a job can be stopped early besides 238 * constraints no longer being satisfied. {@link JobParameters#getStopReason()} will return the 239 * reason this method was called. You are solely responsible for the behavior of your 240 * application upon receipt of this message; your app will likely start to misbehave if you 241 * ignore it. 242 * <p> 243 * Once this method returns (or times out), the system releases the wakelock that it is holding 244 * on behalf of the job.</p> 245 * 246 * <p class="note"> 247 * Any {@link JobInfo.Builder#setUserInitiated(boolean) user-initiated job} 248 * cannot be rescheduled when stopped by the user via a system provided affordance (such as 249 * the Task Manager). In such situations, the returned value from this method call is always 250 * treated as {@code false}. 251 * 252 * <p class="caution"><strong>Note:</strong> When a job is stopped and rescheduled via this 253 * method call, the deadline constraint is excluded from the rescheduled job's constraint set. 254 * The rescheduled job will run again once all remaining constraints are satisfied. 255 * 256 * @param params The parameters identifying this job, similar to what was supplied to the job in 257 * the {@link #onStartJob(JobParameters)} callback, but with the stop reason 258 * included. 259 * @return {@code true} to indicate to the JobScheduler whether you'd like to reschedule 260 * this job based on the retry criteria provided at job creation-time; or {@code false} 261 * to end the job entirely (or, for a periodic job, to reschedule it according to its 262 * requested periodic criteria). Regardless of the value returned, your job must stop executing. 263 */ onStopJob(JobParameters params)264 public abstract boolean onStopJob(JobParameters params); 265 266 /** 267 * This method is called that for a job that has a network constraint when the network 268 * to be used by the job changes. The new network object will be available via 269 * {@link JobParameters#getNetwork()}. Any network that results in this method call will 270 * match the job's requested network constraints. 271 * 272 * <p> 273 * For example, if a device is on a metered mobile network and then connects to an 274 * unmetered WiFi network, and the job has indicated that both networks satisfy its 275 * network constraint, then this method will be called to notify the job of the new 276 * unmetered WiFi network. 277 * 278 * @param params The parameters identifying this job, similar to what was supplied to the job in 279 * the {@link #onStartJob(JobParameters)} callback, but with an updated network. 280 * @see JobInfo.Builder#setRequiredNetwork(android.net.NetworkRequest) 281 * @see JobInfo.Builder#setRequiredNetworkType(int) 282 */ onNetworkChanged(@onNull JobParameters params)283 public void onNetworkChanged(@NonNull JobParameters params) { 284 Log.w(TAG, "onNetworkChanged() not implemented in " + getClass().getName() 285 + ". Must override in a subclass."); 286 } 287 288 /** 289 * Update the amount of data this job is estimated to transfer after the job has started. 290 * 291 * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long) 292 */ updateEstimatedNetworkBytes(@onNull JobParameters params, @BytesLong long downloadBytes, @BytesLong long uploadBytes)293 public final void updateEstimatedNetworkBytes(@NonNull JobParameters params, 294 @BytesLong long downloadBytes, @BytesLong long uploadBytes) { 295 mEngine.updateEstimatedNetworkBytes(params, null, downloadBytes, uploadBytes); 296 } 297 298 /** 299 * Update the amount of data this JobWorkItem is estimated to transfer after the job has 300 * started. 301 * 302 * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long) 303 */ updateEstimatedNetworkBytes(@onNull JobParameters params, @NonNull JobWorkItem jobWorkItem, @BytesLong long downloadBytes, @BytesLong long uploadBytes)304 public final void updateEstimatedNetworkBytes(@NonNull JobParameters params, 305 @NonNull JobWorkItem jobWorkItem, 306 @BytesLong long downloadBytes, @BytesLong long uploadBytes) { 307 mEngine.updateEstimatedNetworkBytes(params, jobWorkItem, downloadBytes, uploadBytes); 308 } 309 310 /** 311 * Tell JobScheduler how much data has successfully been transferred for the data transfer job. 312 */ updateTransferredNetworkBytes(@onNull JobParameters params, @BytesLong long transferredDownloadBytes, @BytesLong long transferredUploadBytes)313 public final void updateTransferredNetworkBytes(@NonNull JobParameters params, 314 @BytesLong long transferredDownloadBytes, @BytesLong long transferredUploadBytes) { 315 mEngine.updateTransferredNetworkBytes(params, null, 316 transferredDownloadBytes, transferredUploadBytes); 317 } 318 319 /** 320 * Tell JobScheduler how much data has been transferred for the data transfer 321 * {@link JobWorkItem}. 322 */ updateTransferredNetworkBytes(@onNull JobParameters params, @NonNull JobWorkItem item, @BytesLong long transferredDownloadBytes, @BytesLong long transferredUploadBytes)323 public final void updateTransferredNetworkBytes(@NonNull JobParameters params, 324 @NonNull JobWorkItem item, 325 @BytesLong long transferredDownloadBytes, @BytesLong long transferredUploadBytes) { 326 mEngine.updateTransferredNetworkBytes(params, item, 327 transferredDownloadBytes, transferredUploadBytes); 328 } 329 330 /** 331 * Get the number of bytes the app has successfully downloaded for this job. JobScheduler 332 * will call this if the job has specified positive estimated download bytes and 333 * {@link #updateTransferredNetworkBytes(JobParameters, long, long)} 334 * hasn't been called recently. 335 * 336 * <p> 337 * This must be implemented for all data transfer jobs. 338 * 339 * @hide 340 * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long) 341 * @see JobInfo#NETWORK_BYTES_UNKNOWN 342 */ 343 // TODO(255371817): specify the actual time JS will wait for progress before requesting 344 @BytesLong getTransferredDownloadBytes(@onNull JobParameters params)345 public long getTransferredDownloadBytes(@NonNull JobParameters params) { 346 if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) { 347 // Regular jobs don't have to implement this and JobScheduler won't call this API for 348 // non-data transfer jobs. 349 throw new RuntimeException("Not implemented. Must override in a subclass."); 350 } 351 return 0; 352 } 353 354 /** 355 * Get the number of bytes the app has successfully downloaded for this job. JobScheduler 356 * will call this if the job has specified positive estimated upload bytes and 357 * {@link #updateTransferredNetworkBytes(JobParameters, long, long)} 358 * hasn't been called recently. 359 * 360 * <p> 361 * This must be implemented for all data transfer jobs. 362 * 363 * @hide 364 * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long) 365 * @see JobInfo#NETWORK_BYTES_UNKNOWN 366 */ 367 // TODO(255371817): specify the actual time JS will wait for progress before requesting 368 @BytesLong getTransferredUploadBytes(@onNull JobParameters params)369 public long getTransferredUploadBytes(@NonNull JobParameters params) { 370 if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) { 371 // Regular jobs don't have to implement this and JobScheduler won't call this API for 372 // non-data transfer jobs. 373 throw new RuntimeException("Not implemented. Must override in a subclass."); 374 } 375 return 0; 376 } 377 378 /** 379 * Get the number of bytes the app has successfully downloaded for this job. JobScheduler 380 * will call this if the job has specified positive estimated download bytes and 381 * {@link #updateTransferredNetworkBytes(JobParameters, JobWorkItem, long, long)} 382 * hasn't been called recently and the job has 383 * {@link JobWorkItem JobWorkItems} that have been 384 * {@link JobParameters#dequeueWork dequeued} but not 385 * {@link JobParameters#completeWork(JobWorkItem) completed}. 386 * 387 * <p> 388 * This must be implemented for all data transfer jobs. 389 * 390 * @hide 391 * @see JobInfo#NETWORK_BYTES_UNKNOWN 392 */ 393 // TODO(255371817): specify the actual time JS will wait for progress before requesting 394 @BytesLong getTransferredDownloadBytes(@onNull JobParameters params, @NonNull JobWorkItem item)395 public long getTransferredDownloadBytes(@NonNull JobParameters params, 396 @NonNull JobWorkItem item) { 397 if (item == null) { 398 return getTransferredDownloadBytes(params); 399 } 400 if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) { 401 // Regular jobs don't have to implement this and JobScheduler won't call this API for 402 // non-data transfer jobs. 403 throw new RuntimeException("Not implemented. Must override in a subclass."); 404 } 405 return 0; 406 } 407 408 /** 409 * Get the number of bytes the app has successfully downloaded for this job. JobScheduler 410 * will call this if the job has specified positive estimated upload bytes and 411 * {@link #updateTransferredNetworkBytes(JobParameters, JobWorkItem, long, long)} 412 * hasn't been called recently and the job has 413 * {@link JobWorkItem JobWorkItems} that have been 414 * {@link JobParameters#dequeueWork dequeued} but not 415 * {@link JobParameters#completeWork(JobWorkItem) completed}. 416 * 417 * <p> 418 * This must be implemented for all data transfer jobs. 419 * 420 * @hide 421 * @see JobInfo#NETWORK_BYTES_UNKNOWN 422 */ 423 // TODO(255371817): specify the actual time JS will wait for progress before requesting 424 @BytesLong getTransferredUploadBytes(@onNull JobParameters params, @NonNull JobWorkItem item)425 public long getTransferredUploadBytes(@NonNull JobParameters params, 426 @NonNull JobWorkItem item) { 427 if (item == null) { 428 return getTransferredUploadBytes(params); 429 } 430 if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) { 431 // Regular jobs don't have to implement this and JobScheduler won't call this API for 432 // non-data transfer jobs. 433 throw new RuntimeException("Not implemented. Must override in a subclass."); 434 } 435 return 0; 436 } 437 438 /** 439 * Provide JobScheduler with a notification to post and tie to this job's lifecycle. 440 * This is only required for those user-initiated jobs which return {@code true} via 441 * {@link JobParameters#isUserInitiatedJob()}. 442 * If the app does not call this method for a required notification within 443 * 10 seconds after {@link #onStartJob(JobParameters)} is called, 444 * the system will trigger an ANR and stop this job. 445 * 446 * The notification must provide an accurate description of the work that the job is doing 447 * and, if possible, the state of the work. 448 * 449 * <p> 450 * Note that certain types of jobs 451 * (e.g. {@link JobInfo.Builder#setEstimatedNetworkBytes(long, long) data transfer jobs}) 452 * may require the notification to have certain characteristics 453 * and their documentation will state any such requirements. 454 * 455 * <p> 456 * JobScheduler will not remember this notification after the job has finished running, 457 * so apps must call this every time the job is started (if required or desired). 458 * 459 * <p> 460 * If separate jobs use the same notification ID with this API, the most recently provided 461 * notification will be shown to the user, and the 462 * {@code jobEndNotificationPolicy} of the last job to stop will be applied. 463 * 464 * @param params The parameters identifying this job, as supplied to 465 * the job in the {@link #onStartJob(JobParameters)} callback. 466 * @param notificationId The ID for this notification, as per 467 * {@link android.app.NotificationManager#notify(int, 468 * Notification)}. 469 * @param notification The notification to be displayed. 470 * @param jobEndNotificationPolicy The policy to apply to the notification when the job stops. 471 */ setNotification(@onNull JobParameters params, int notificationId, @NonNull Notification notification, @JobEndNotificationPolicy int jobEndNotificationPolicy)472 public final void setNotification(@NonNull JobParameters params, int notificationId, 473 @NonNull Notification notification, 474 @JobEndNotificationPolicy int jobEndNotificationPolicy) { 475 mEngine.setNotification(params, notificationId, notification, jobEndNotificationPolicy); 476 } 477 } 478