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.annotation.SuppressLint 19 import android.os.Build 20 import androidx.annotation.RequiresApi 21 import androidx.annotation.RestrictTo 22 import androidx.annotation.VisibleForTesting 23 import androidx.work.impl.model.WorkSpec 24 import androidx.work.impl.utils.toMillisCompat 25 import java.time.Duration 26 import java.util.UUID 27 import java.util.concurrent.TimeUnit 28 29 /** 30 * The base class for specifying parameters for work that should be enqueued in [WorkManager]. There 31 * are two concrete implementations of this class: [OneTimeWorkRequest] and [PeriodicWorkRequest]. 32 */ 33 abstract class WorkRequest 34 internal constructor( 35 /** The unique identifier associated with this unit of work. */ 36 open val id: UUID, 37 /** The [WorkSpec] associated with this unit of work. */ 38 @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) val workSpec: WorkSpec, 39 /** The tags associated with this unit of work. */ 40 @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) val tags: Set<String> 41 ) { 42 43 /** 44 * Gets the string for the unique identifier associated with this unit of work. 45 * 46 * @return The string identifier for this unit of work 47 */ 48 @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 49 val stringId: String 50 get() = id.toString() 51 52 /** 53 * A builder for [WorkRequest]s. There are two concrete implementations of this class: 54 * [OneTimeWorkRequest.Builder] and [PeriodicWorkRequest.Builder]. 55 */ 56 abstract class Builder<B : Builder<B, *>, W : WorkRequest> 57 internal constructor(internal val workerClass: Class<out ListenableWorker>) { 58 internal var backoffCriteriaSet = false 59 internal var id: UUID = UUID.randomUUID() 60 internal var workSpec: WorkSpec = WorkSpec(id.toString(), workerClass.name) 61 internal val tags: MutableSet<String> = mutableSetOf(workerClass.name) 62 63 /** 64 * Sets a unique identifier for this unit of work. 65 * 66 * The id can be useful when retrieving [WorkInfo] by `id` or when trying to update an 67 * existing work. For example, using [WorkManager.updateWork] requires that the work has an 68 * id. 69 * 70 * @param id The unique identifier for this unit of work. 71 * @return The current [Builder] 72 */ 73 @SuppressWarnings("SetterReturnsThis") setIdnull74 fun setId(id: UUID): B { 75 this.id = id 76 workSpec = WorkSpec(id.toString(), workSpec) 77 return thisObject 78 } 79 80 /** 81 * Sets the backoff policy and backoff delay for the work. The default values are 82 * [BackoffPolicy.EXPONENTIAL] and [WorkRequest#DEFAULT_BACKOFF_DELAY_MILLIS], respectively. 83 * `backoffDelay` will be clamped between [WorkRequest.MIN_BACKOFF_MILLIS] and 84 * [WorkRequest.MAX_BACKOFF_MILLIS]. 85 * 86 * @param backoffPolicy The [BackoffPolicy] to use when increasing backoff time 87 * @param backoffDelay Time to wait before retrying the work in `timeUnit` units 88 * @param timeUnit The [TimeUnit] for `backoffDelay` 89 * @return The current [Builder] 90 */ setBackoffCriterianull91 fun setBackoffCriteria( 92 backoffPolicy: BackoffPolicy, 93 backoffDelay: Long, 94 timeUnit: TimeUnit 95 ): B { 96 backoffCriteriaSet = true 97 workSpec.backoffPolicy = backoffPolicy 98 workSpec.setBackoffDelayDuration(timeUnit.toMillis(backoffDelay)) 99 return thisObject 100 } 101 102 /** 103 * Sets the backoff policy and backoff delay for the work. The default values are 104 * [BackoffPolicy.EXPONENTIAL] and [WorkRequest#DEFAULT_BACKOFF_DELAY_MILLIS], respectively. 105 * `duration` will be clamped between [WorkRequest.MIN_BACKOFF_MILLIS] and 106 * [WorkRequest.MAX_BACKOFF_MILLIS]. 107 * 108 * @param backoffPolicy The [BackoffPolicy] to use when increasing backoff time 109 * @param duration Time to wait before retrying the work 110 * @return The current [Builder] 111 */ 112 @RequiresApi(26) setBackoffCriterianull113 fun setBackoffCriteria(backoffPolicy: BackoffPolicy, duration: Duration): B { 114 backoffCriteriaSet = true 115 workSpec.backoffPolicy = backoffPolicy 116 workSpec.setBackoffDelayDuration(duration.toMillisCompat()) 117 return thisObject 118 } 119 120 /** 121 * Adds constraints to the [WorkRequest]. 122 * 123 * @param constraints The constraints for the work 124 * @return The current [Builder] 125 */ setConstraintsnull126 fun setConstraints(constraints: Constraints): B { 127 workSpec.constraints = constraints 128 return thisObject 129 } 130 131 /** 132 * Adds input [Data] to the work. If a worker has prerequisites in its chain, this Data will 133 * be merged with the outputs of the prerequisites using an [InputMerger]. 134 * 135 * @param inputData key/value pairs that will be provided to the worker 136 * @return The current [Builder] 137 */ setInputDatanull138 fun setInputData(inputData: Data): B { 139 workSpec.input = inputData 140 return thisObject 141 } 142 143 /** 144 * Adds a tag for the work. You can query and cancel work by tags. Tags are particularly 145 * useful for modules or libraries to find and operate on their own work. 146 * 147 * @param tag A tag for identifying the work in queries. 148 * @return The current [Builder] 149 */ addTagnull150 fun addTag(tag: String): B { 151 tags.add(tag) 152 return thisObject 153 } 154 155 /** 156 * Specifies the name of the trace span to be used by [WorkManager] when executing the 157 * specified [WorkRequest]. 158 * 159 * [WorkManager] uses the simple name of the [ListenableWorker] class truncated to a `127` 160 * character string, as the [traceTag] by default. 161 * 162 * You should override the [traceTag], when you are using [ListenableWorker] delegation via 163 * a [WorkerFactory]. 164 * 165 * @param traceTag The name of the trace tag, truncate to a `127` character string if 166 * necessary. 167 * @return The current [Builder] 168 */ 169 @Suppress("MissingGetterMatchingBuilder") 170 @SuppressWarnings("SetterReturnsThis") setTraceTagnull171 fun setTraceTag(traceTag: String): B { 172 workSpec.traceTag = traceTag 173 return thisObject 174 } 175 176 /** 177 * Specifies that the results of this work should be kept for at least the specified amount 178 * of time. After this time has elapsed, the results **may** be pruned at the discretion of 179 * WorkManager when there are no pending dependent jobs. 180 * 181 * When the results of a work are pruned, it becomes impossible to query for its [WorkInfo]. 182 * 183 * Specifying a long duration here may adversely affect performance in terms of app storage 184 * and database query time. 185 * 186 * @param duration The minimum duration of time (in `timeUnit` units) to keep the results of 187 * this work 188 * @param timeUnit The unit of time for `duration` 189 * @return The current [Builder] 190 */ keepResultsForAtLeastnull191 fun keepResultsForAtLeast(duration: Long, timeUnit: TimeUnit): B { 192 workSpec.minimumRetentionDuration = timeUnit.toMillis(duration) 193 return thisObject 194 } 195 196 /** 197 * Specifies that the backoff policy (as specified via [setBackoffCriteria]) will be applied 198 * when work is interrupted by the system without the app requesting it. This might happen 199 * when the [ListenableWorker] runs longer than it should, or when constraints defined for a 200 * given [ListenableWorker] are unmet. 201 * 202 * @return The current [Builder] 203 * @see setBackoffCriteria 204 */ 205 @ExperimentalWorkRequestBuilderApi 206 @Suppress("MissingGetterMatchingBuilder") 207 @SuppressWarnings("SetterReturnsThis") setBackoffForSystemInterruptionsnull208 fun setBackoffForSystemInterruptions(): B { 209 workSpec.backOffOnSystemInterruptions = true 210 return thisObject 211 } 212 213 /** 214 * Specifies that the results of this work should be kept for at least the specified amount 215 * of time. After this time has elapsed, the results may be pruned at the discretion of 216 * WorkManager when this WorkRequest has reached a finished state (see 217 * [WorkInfo.State.isFinished]) and there are no pending dependent jobs. 218 * 219 * When the results of a work are pruned, it becomes impossible to query for its [WorkInfo]. 220 * 221 * Specifying a long duration here may adversely affect performance in terms of app storage 222 * and database query time. 223 * 224 * @param duration The minimum duration of time to keep the results of this work 225 * @return The current [Builder] 226 */ 227 @RequiresApi(26) keepResultsForAtLeastnull228 fun keepResultsForAtLeast(duration: Duration): B { 229 workSpec.minimumRetentionDuration = duration.toMillisCompat() 230 return thisObject 231 } 232 233 /** 234 * Sets an initial delay for the [WorkRequest]. 235 * 236 * @param duration The length of the delay in `timeUnit` units 237 * @param timeUnit The units of time for `duration` 238 * @return The current [Builder] 239 * @throws IllegalArgumentException if the given initial delay will push the execution time 240 * past `Long.MAX_VALUE` and cause an overflow 241 */ setInitialDelaynull242 open fun setInitialDelay(duration: Long, timeUnit: TimeUnit): B { 243 workSpec.initialDelay = timeUnit.toMillis(duration) 244 require(Long.MAX_VALUE - System.currentTimeMillis() > workSpec.initialDelay) { 245 ("The given initial delay is too large and will cause an overflow!") 246 } 247 return thisObject 248 } 249 250 /** 251 * Sets an initial delay for the [WorkRequest]. 252 * 253 * @param duration The length of the delay 254 * @return The current [Builder] 255 * @throws IllegalArgumentException if the given initial delay will push the execution time 256 * past `Long.MAX_VALUE` and cause an overflow 257 */ 258 @RequiresApi(26) setInitialDelaynull259 open fun setInitialDelay(duration: Duration): B { 260 workSpec.initialDelay = duration.toMillisCompat() 261 require(Long.MAX_VALUE - System.currentTimeMillis() > workSpec.initialDelay) { 262 "The given initial delay is too large and will cause an overflow!" 263 } 264 return thisObject 265 } 266 267 /** 268 * Marks the [WorkRequest] as important to the user. In this case, WorkManager provides an 269 * additional signal to the OS that this work is important. 270 * 271 * @param policy The [OutOfQuotaPolicy] to be used. 272 */ 273 @SuppressLint("MissingGetterMatchingBuilder") setExpeditednull274 open fun setExpedited(policy: OutOfQuotaPolicy): B { 275 workSpec.expedited = true 276 workSpec.outOfQuotaPolicy = policy 277 return thisObject 278 } 279 280 /** 281 * Builds a [WorkRequest] based on this [Builder]. 282 * 283 * @return A [WorkRequest] based on this [Builder] 284 */ buildnull285 fun build(): W { 286 val returnValue = buildInternal() 287 val constraints = workSpec.constraints 288 // Check for unsupported constraints. 289 val hasUnsupportedConstraints = 290 (Build.VERSION.SDK_INT >= 24 && constraints.hasContentUriTriggers() || 291 constraints.requiresBatteryNotLow() || 292 constraints.requiresCharging() || 293 Build.VERSION.SDK_INT >= 23 && constraints.requiresDeviceIdle()) 294 if (workSpec.expedited) { 295 require(!hasUnsupportedConstraints) { 296 "Expedited jobs only support network and storage constraints" 297 } 298 require(workSpec.initialDelay <= 0) { "Expedited jobs cannot be delayed" } 299 } 300 val traceTag = workSpec.traceTag 301 if (traceTag == null) { 302 // Derive a trace tag based on the fully qualified class name if 303 // one has not already been defined. 304 workSpec.traceTag = deriveTraceTagFromClassName(workSpec.workerClassName) 305 } else if (traceTag.length > MAX_TRACE_SPAN_LENGTH) { 306 // If there is a trace tag but it exceeds the limit, then truncate it. 307 // Since we also pipe this tag to JobInfo we need to not exceed the limit even 308 // though androidx.tracing.Trace already truncate tags. 309 workSpec.traceTag = traceTag.take(MAX_TRACE_SPAN_LENGTH) 310 } 311 // Create a new id and WorkSpec so this WorkRequest.Builder can be used multiple times. 312 setId(UUID.randomUUID()) 313 return returnValue 314 } 315 buildInternalnull316 internal abstract fun buildInternal(): W 317 318 internal abstract val thisObject: B 319 320 /** 321 * Sets the initial state for this work. Used in testing only. 322 * 323 * @param state The [WorkInfo.State] to set 324 * @return The current [Builder] 325 */ 326 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 327 @VisibleForTesting 328 fun setInitialState(state: WorkInfo.State): B { 329 workSpec.state = state 330 return thisObject 331 } 332 333 /** 334 * Sets the initial run attempt count for this work. Used in testing only. 335 * 336 * @param runAttemptCount The initial run attempt count 337 * @return The current [Builder] 338 */ 339 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 340 @VisibleForTesting setInitialRunAttemptCountnull341 fun setInitialRunAttemptCount(runAttemptCount: Int): B { 342 workSpec.runAttemptCount = runAttemptCount 343 return thisObject 344 } 345 346 /** 347 * Sets the enqueue time for this work. Used in testing only. 348 * 349 * @param lastEnqueueTime The enqueue time in `timeUnit` units 350 * @param timeUnit The [TimeUnit] for `periodStartTime` 351 * @return The current [Builder] 352 */ 353 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 354 @VisibleForTesting setLastEnqueueTimenull355 fun setLastEnqueueTime(lastEnqueueTime: Long, timeUnit: TimeUnit): B { 356 workSpec.lastEnqueueTime = timeUnit.toMillis(lastEnqueueTime) 357 return thisObject 358 } 359 360 /** 361 * Sets when the scheduler actually schedules the worker. 362 * 363 * @param scheduleRequestedAt The time at which the scheduler scheduled a worker. 364 * @param timeUnit The [TimeUnit] for `scheduleRequestedAt` 365 * @return The current [Builder] 366 */ 367 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 368 @VisibleForTesting setScheduleRequestedAtnull369 fun setScheduleRequestedAt(scheduleRequestedAt: Long, timeUnit: TimeUnit): B { 370 workSpec.scheduleRequestedAt = timeUnit.toMillis(scheduleRequestedAt) 371 return thisObject 372 } 373 } 374 375 companion object { 376 /** The default initial backoff time (in milliseconds) for work that has to be retried. */ 377 const val DEFAULT_BACKOFF_DELAY_MILLIS = 30000L 378 379 /** The maximum backoff time (in milliseconds) for work that has to be retried. */ 380 @SuppressLint("MinMaxConstant") 381 const val MAX_BACKOFF_MILLIS = 5 * 60 * 60 * 1000L // 5 hours 382 383 /** The minimum backoff time for work (in milliseconds) that has to be retried. */ 384 @SuppressLint("MinMaxConstant") const val MIN_BACKOFF_MILLIS = 10 * 1000L // 10 seconds. 385 386 /** The maximum length of a trace span. */ 387 private const val MAX_TRACE_SPAN_LENGTH = 127 388 389 /** 390 * The [androidx.tracing.Trace] class already truncates names. 391 * 392 * We try and extract the class name so it does not get truncated, given package name can be 393 * implied from other sources of information. 394 */ deriveTraceTagFromClassNamenull395 private fun deriveTraceTagFromClassName(workerClassName: String): String { 396 val components = workerClassName.split(".") 397 val label = 398 when (components.size) { 399 1 -> components[0] 400 else -> components.last() 401 } 402 return if (label.length <= MAX_TRACE_SPAN_LENGTH) { 403 label 404 } else { 405 label.take(MAX_TRACE_SPAN_LENGTH) 406 } 407 } 408 } 409 } 410