1 /* 2 * Copyright 2018 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 package androidx.work 17 18 import android.app.job.JobInfo 19 import android.app.job.JobScheduler 20 import androidx.annotation.IntDef 21 import androidx.annotation.IntRange 22 import androidx.annotation.RequiresApi 23 import androidx.work.WorkInfo.State 24 import java.util.UUID 25 26 /** 27 * Information about a particular [WorkRequest] containing the id of the WorkRequest, its current 28 * [State], output, tags, and run attempt count. Note that output is only available for the terminal 29 * states ([State.SUCCEEDED] and [State.FAILED]). 30 */ 31 class WorkInfo 32 @JvmOverloads 33 constructor( 34 /** The identifier of the [WorkRequest]. */ 35 val id: UUID, 36 /** The current [State] of the [WorkRequest]. */ 37 val state: State, 38 /** The [Set] of tags associated with the [WorkRequest]. */ 39 val tags: Set<String>, 40 /** 41 * The output [Data] for the [WorkRequest]. If the WorkRequest is unfinished, this is always 42 * [Data.EMPTY]. 43 */ 44 val outputData: Data = Data.EMPTY, 45 /** The progress [Data] associated with the [WorkRequest]. */ 46 val progress: Data = Data.EMPTY, 47 /** 48 * The run attempt count of the [WorkRequest]. Note that for [PeriodicWorkRequest]s, the run 49 * attempt count gets reset between successful runs. 50 */ 51 @get:IntRange(from = 0) val runAttemptCount: Int = 0, 52 53 /** 54 * The latest generation of this Worker. 55 * 56 * A work has multiple generations, if it was updated via [WorkManager.updateWork] or 57 * [WorkManager.enqueueUniquePeriodicWork] using [ExistingPeriodicWorkPolicy.UPDATE]. 58 * 59 * If this worker is currently running, it can possibly be of an older generation rather than 60 * returned by this function if an update has happened during an execution of this worker. 61 */ 62 val generation: Int = 0, 63 64 /** [Constraints] of this worker. */ 65 val constraints: Constraints = Constraints.NONE, 66 67 /** The initial delay for this work set in the [WorkRequest] */ 68 val initialDelayMillis: Long = 0, 69 70 /** 71 * For periodic work, the period and flex duration set in the [PeriodicWorkRequest]. 72 * 73 * Null if this is onetime work. 74 */ 75 val periodicityInfo: PeriodicityInfo? = null, 76 77 /** 78 * The earliest time this work is eligible to run next, if this work is [State.ENQUEUED]. 79 * 80 * This is the earliest [System.currentTimeMillis] time that WorkManager would consider running 81 * this work, regardless of any other system. It only represents the time that the initialDelay, 82 * periodic configuration, and backoff criteria are considered to be met. 83 * 84 * Work will almost never run at this time in the real world. This method is intended for use in 85 * scheduling tests or to check set schedules in app. Work run times are dependent on many 86 * factors like the underlying system scheduler, doze and power saving modes of the OS, and 87 * meeting any configured constraints. This is expected and is not considered a bug. 88 * 89 * The returned value may be in the past if the work was not able to run at that time. It will 90 * be eligible to run any time after that time. 91 * 92 * Defaults to [Long.MAX_VALUE] for all other states, including if the work is currently 93 * [State.RUNNING] or [State.BLOCKED] on prerequisite work. 94 * 95 * Even if this value is set, the work may not be registered with the system scheduler if there 96 * are limited scheduling slots or other factors. 97 */ 98 val nextScheduleTimeMillis: Long = Long.MAX_VALUE, 99 100 /** 101 * The reason why this worker was stopped on the previous run attempt. 102 * 103 * For a worker being stopped, at first it should have attempted to run, i.e. its state should 104 * be == RUNNING and then [ListenableWorker.onStopped] should have been called, resulting in 105 * this worker's state going back [WorkInfo.State.ENQUEUED]. In this situation 106 * (`runAttemptCount > 0` and `state == ENQUEUED`) this `stopReason` property could be checked 107 * to see for additional information. Please note, that this state (`runAttemptCount > 0` and 108 * `state == ENQUEUED`) can happen not only because a worker was stopped, but also when a worker 109 * returns `ListenableWorker.Result.retry()`. In this situation this property will return 110 * [STOP_REASON_NOT_STOPPED]. 111 */ 112 @StopReason @get:RequiresApi(31) val stopReason: Int = STOP_REASON_NOT_STOPPED 113 ) { equalsnull114 override fun equals(other: Any?): Boolean { 115 if (this === other) return true 116 if (other == null || javaClass != other.javaClass) return false 117 val workInfo = other as WorkInfo 118 if (runAttemptCount != workInfo.runAttemptCount) return false 119 if (generation != workInfo.generation) return false 120 if (id != workInfo.id) return false 121 if (state != workInfo.state) return false 122 if (outputData != workInfo.outputData) return false 123 if (constraints != workInfo.constraints) return false 124 if (initialDelayMillis != workInfo.initialDelayMillis) return false 125 if (periodicityInfo != workInfo.periodicityInfo) return false 126 if (nextScheduleTimeMillis != workInfo.nextScheduleTimeMillis) return false 127 if (stopReason != workInfo.stopReason) return false 128 return if (tags != workInfo.tags) false else progress == workInfo.progress 129 } 130 hashCodenull131 override fun hashCode(): Int { 132 var result = id.hashCode() 133 result = 31 * result + state.hashCode() 134 result = 31 * result + outputData.hashCode() 135 result = 31 * result + tags.hashCode() 136 result = 31 * result + progress.hashCode() 137 result = 31 * result + runAttemptCount 138 result = 31 * result + generation 139 result = 31 * result + constraints.hashCode() 140 result = 31 * result + initialDelayMillis.hashCode() 141 result = 31 * result + periodicityInfo.hashCode() 142 result = 31 * result + nextScheduleTimeMillis.hashCode() 143 result = 31 * result + stopReason.hashCode() 144 return result 145 } 146 toStringnull147 override fun toString(): String { 148 return ("WorkInfo{id='$id', state=$state, " + 149 "outputData=$outputData, tags=$tags, progress=$progress, " + 150 "runAttemptCount=$runAttemptCount, generation=$generation, " + 151 "constraints=$constraints, initialDelayMillis=$initialDelayMillis, " + 152 "periodicityInfo=$periodicityInfo, " + 153 "nextScheduleTimeMillis=$nextScheduleTimeMillis}, " + 154 "stopReason=$stopReason") 155 } 156 157 /** The current lifecycle state of a [WorkRequest]. */ 158 enum class State { 159 /** 160 * Used to indicate that the [WorkRequest] is enqueued and eligible to run when its 161 * [Constraints] are met and resources are available. 162 */ 163 ENQUEUED, 164 165 /** Used to indicate that the [WorkRequest] is currently being executed. */ 166 RUNNING, 167 168 /** 169 * Used to indicate that the [WorkRequest] has completed in a successful state. Note that 170 * [PeriodicWorkRequest]s will never enter this state (they will simply go back to 171 * [.ENQUEUED] and be eligible to run again). 172 */ 173 SUCCEEDED, 174 175 /** 176 * Used to indicate that the [WorkRequest] has completed in a failure state. All dependent 177 * work will also be marked as `#FAILED` and will never run. 178 */ 179 FAILED, 180 181 /** 182 * Used to indicate that the [WorkRequest] is currently blocked because its prerequisites 183 * haven't finished successfully. 184 */ 185 BLOCKED, 186 187 /** 188 * Used to indicate that the [WorkRequest] has been cancelled and will not execute. All 189 * dependent work will also be marked as `#CANCELLED` and will not run. 190 */ 191 CANCELLED; 192 193 /** 194 * Returns `true` if this State is considered finished: [.SUCCEEDED], [.FAILED], 195 * and * [.CANCELLED] 196 */ 197 val isFinished: Boolean 198 get() = this == SUCCEEDED || this == FAILED || this == CANCELLED 199 } 200 201 /** A periodic work's interval and flex duration */ 202 class PeriodicityInfo( 203 /** 204 * The periodic work's configured repeat interval in millis, as configured in 205 * [PeriodicWorkRequest.Builder] 206 */ 207 val repeatIntervalMillis: Long, 208 /** 209 * The duration in millis in which this work repeats from the end of the `repeatInterval`, 210 * as configured in [PeriodicWorkRequest.Builder]. 211 */ 212 val flexIntervalMillis: Long 213 ) { equalsnull214 override fun equals(other: Any?): Boolean { 215 if (this === other) return true 216 if (other == null || javaClass != other.javaClass) return false 217 val period = other as PeriodicityInfo 218 return period.repeatIntervalMillis == repeatIntervalMillis && 219 period.flexIntervalMillis == flexIntervalMillis 220 } 221 hashCodenull222 override fun hashCode(): Int { 223 return 31 * repeatIntervalMillis.hashCode() + flexIntervalMillis.hashCode() 224 } 225 toStringnull226 override fun toString(): String { 227 return "PeriodicityInfo{repeatIntervalMillis=$repeatIntervalMillis, " + 228 "flexIntervalMillis=$flexIntervalMillis}" 229 } 230 } 231 232 companion object { 233 234 /** 235 * The foreground worker used up its maximum execution time and timed out. 236 * 237 * Foreground workers have a maximum execution time limit depending on the [ForegroundInfo] 238 * type. See the notes on [android.content.pm.ServiceInfo] types. 239 */ 240 const val STOP_REASON_FOREGROUND_SERVICE_TIMEOUT = -128 241 242 /** 243 * Additional stop reason, that is returned from [WorkInfo.stopReason] in cases when a 244 * worker in question wasn't stopped. E.g. when a worker was just enqueued, but didn't run 245 * yet. 246 */ 247 const val STOP_REASON_NOT_STOPPED = -256 248 249 /** 250 * Stop reason that is used in cases when worker did stop, but the reason for this is 251 * unknown. For example, when the app abruptly stopped due to a crash or when a device 252 * suddenly ran out of the battery. 253 */ 254 const val STOP_REASON_UNKNOWN = -512 255 256 /** 257 * The worker was cancelled directly by the app, either by calling cancel methods, e.g. 258 * [WorkManager.cancelUniqueWork], or enqueueing uniquely named worker with a policy that 259 * cancels an existing worker, e.g. [ExistingWorkPolicy.REPLACE]. 260 */ 261 const val STOP_REASON_CANCELLED_BY_APP = 1 262 263 /** The job was stopped to run a higher priority job of the app. */ 264 const val STOP_REASON_PREEMPT = 2 265 266 /** 267 * The worker used up its maximum execution time and timed out. Each individual worker has a 268 * maximum execution time limit, regardless of how much total quota the app has. See the 269 * note on [JobScheduler] for the execution time limits. 270 */ 271 const val STOP_REASON_TIMEOUT = 3 272 273 /** 274 * The device state (eg. Doze, battery saver, memory usage, etc) requires WorkManager to 275 * stop this worker. 276 */ 277 const val STOP_REASON_DEVICE_STATE = 4 278 279 /** 280 * The requested battery-not-low constraint is no longer satisfied. 281 * 282 * @see JobInfo.Builder.setRequiresBatteryNotLow 283 */ 284 const val STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW = 5 285 286 /** 287 * The requested charging constraint is no longer satisfied. 288 * 289 * @see JobInfo.Builder.setRequiresCharging 290 */ 291 const val STOP_REASON_CONSTRAINT_CHARGING = 6 292 293 /** The requested connectivity constraint is no longer satisfied. */ 294 const val STOP_REASON_CONSTRAINT_CONNECTIVITY = 7 295 296 /** The requested idle constraint is no longer satisfied. */ 297 const val STOP_REASON_CONSTRAINT_DEVICE_IDLE = 8 298 299 /** The requested storage-not-low constraint is no longer satisfied. */ 300 const val STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW = 9 301 302 /** 303 * The app has consumed all of its current quota. Each app is assigned a quota of how much 304 * it can run workers within a certain time frame. The quota is informed, in part, by app 305 * standby buckets. 306 * 307 * @see android.app.job.JobParameters.STOP_REASON_QUOTA 308 */ 309 const val STOP_REASON_QUOTA = 10 310 311 /** 312 * The app is restricted from running in the background. 313 * 314 * @see android.app.job.JobParameters.STOP_REASON_BACKGROUND_RESTRICTION 315 */ 316 const val STOP_REASON_BACKGROUND_RESTRICTION = 11 317 318 /** 319 * The current standby bucket requires that the job stop now. 320 * 321 * @see android.app.job.JobParameters.STOP_REASON_APP_STANDBY 322 */ 323 const val STOP_REASON_APP_STANDBY = 12 324 325 /** 326 * The user stopped the job. This can happen either through force-stop, adb shell commands, 327 * uninstalling, or some other UI. 328 * 329 * @see android.app.job.JobParameters.STOP_REASON_USER 330 */ 331 const val STOP_REASON_USER = 13 332 333 /** 334 * The system is doing some processing that requires stopping this job. 335 * 336 * @see android.app.job.JobParameters.STOP_REASON_SYSTEM_PROCESSING 337 */ 338 const val STOP_REASON_SYSTEM_PROCESSING = 14 339 340 /** 341 * The system's estimate of when the app will be launched changed significantly enough to 342 * decide this worker shouldn't be running right now. 343 */ 344 const val STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED = 15 345 } 346 } 347 348 /** 349 * Stops reason integers are divided in ranges since some corresponds to platform equivalents, while 350 * other are WorkManager specific. 351 * * `-512` - Special STOP_REASON_UNKNOWN 352 * * `-256` - Special STOP_REASON_NOT_STOPPED 353 * * `[-255, -128]` - Reserved for WM specific reasons (i.e. not reflected by JobScheduler). 354 * * `[-127, -1]` - Unused on purpose. 355 * * `[0, MAX_VALUE]` - Reserved for JobScheduler mirror reasons (i.e. JobParameters.STOP_REASON_X). 356 */ 357 @Retention(AnnotationRetention.SOURCE) 358 @IntDef( 359 WorkInfo.STOP_REASON_UNKNOWN, 360 WorkInfo.STOP_REASON_NOT_STOPPED, 361 WorkInfo.STOP_REASON_FOREGROUND_SERVICE_TIMEOUT, 362 WorkInfo.STOP_REASON_CANCELLED_BY_APP, 363 WorkInfo.STOP_REASON_PREEMPT, 364 WorkInfo.STOP_REASON_TIMEOUT, 365 WorkInfo.STOP_REASON_DEVICE_STATE, 366 WorkInfo.STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW, 367 WorkInfo.STOP_REASON_CONSTRAINT_CHARGING, 368 WorkInfo.STOP_REASON_CONSTRAINT_CONNECTIVITY, 369 WorkInfo.STOP_REASON_CONSTRAINT_DEVICE_IDLE, 370 WorkInfo.STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW, 371 WorkInfo.STOP_REASON_QUOTA, 372 WorkInfo.STOP_REASON_BACKGROUND_RESTRICTION, 373 WorkInfo.STOP_REASON_APP_STANDBY, 374 WorkInfo.STOP_REASON_USER, 375 WorkInfo.STOP_REASON_SYSTEM_PROCESSING, 376 WorkInfo.STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED 377 ) 378 internal annotation class StopReason 379