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.work.impl.utils.toMillisCompat
22 import java.time.Duration
23 import java.util.concurrent.TimeUnit
24 import kotlin.reflect.KClass
25
26 /**
27 * A [WorkRequest] for repeating work. This work executes multiple times until it is cancelled, with
28 * the first execution happening immediately or as soon as the given [Constraints] are met. The next
29 * execution will happen during the period interval; note that execution may be delayed because
30 * [WorkManager] is subject to OS battery optimizations, such as doze mode.
31 *
32 * You can control when the work executes in the period interval more exactly - see
33 * [PeriodicWorkRequest.Builder] for documentation on `flexInterval`s.
34 *
35 * Periodic work has a minimum interval of 15 minutes.
36 *
37 * Periodic work is intended for use cases where you want a fairly consistent delay between
38 * consecutive runs, and you are willing to accept inexactness due to battery optimizations and doze
39 * mode. Please note that if your periodic work has constraints, it will not execute until the
40 * constraints are met, even if the delay between periods has been met.
41 *
42 * If you need to schedule work that happens exactly at a certain time or only during a certain time
43 * window, you should consider using [OneTimeWorkRequest]s.
44 *
45 * The normal lifecycle of a PeriodicWorkRequest is `ENQUEUED -> RUNNING -> ENQUEUED`. By
46 * definition, periodic work cannot terminate in a succeeded or failed state, since it must recur.
47 * It can only terminate if explicitly cancelled. However, in the case of retries, periodic work
48 * will still back off according to [PeriodicWorkRequest.Builder.setBackoffCriteria].
49 *
50 * Periodic work cannot be part of a chain or graph of work.
51 */
52 class PeriodicWorkRequest internal constructor(builder: Builder) :
53 WorkRequest(builder.id, builder.workSpec, builder.tags) {
54
55 /** Builder for [PeriodicWorkRequest]s. */
56 class Builder : WorkRequest.Builder<Builder, PeriodicWorkRequest> {
57
58 /**
59 * Creates a [PeriodicWorkRequest] to run periodically once every interval period. The
60 * [PeriodicWorkRequest] is guaranteed to run exactly one time during this interval (subject
61 * to OS battery optimizations, such as doze mode). The repeat interval must be greater than
62 * or equal to [PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS]. It may run immediately,
63 * at the end of the period, or any time in between so long as the other conditions are
64 * satisfied at the time. The run time of the [PeriodicWorkRequest] can be restricted to a
65 * flex period within an interval (see `#Builder(Class, long, TimeUnit, long, TimeUnit)`).
66 *
67 * @param workerClass The [ListenableWorker] class to run for this work
68 * @param repeatInterval The repeat interval in `repeatIntervalTimeUnit` units
69 * @param repeatIntervalTimeUnit The [TimeUnit] for `repeatInterval`
70 */
71 constructor(
72 workerClass: Class<out ListenableWorker?>,
73 repeatInterval: Long,
74 repeatIntervalTimeUnit: TimeUnit
75 ) : super(workerClass) {
76 workSpec.setPeriodic(repeatIntervalTimeUnit.toMillis(repeatInterval))
77 }
78
79 /**
80 * Creates a [PeriodicWorkRequest] to run periodically once every interval period. The
81 * [PeriodicWorkRequest] is guaranteed to run exactly one time during this interval (subject
82 * to OS battery optimizations, such as doze mode). The repeat interval must be greater than
83 * or equal to [PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS]. It may run immediately,
84 * at the end of the period, or any time in between so long as the other conditions are
85 * satisfied at the time. The run time of the [PeriodicWorkRequest] can be restricted to a
86 * flex period within an interval (see `#Builder(Class, long, TimeUnit, long, TimeUnit)`).
87 *
88 * @param workerClass The [ListenableWorker] class to run for this work
89 * @param repeatInterval The repeat interval in `repeatIntervalTimeUnit` units
90 * @param repeatIntervalTimeUnit The [TimeUnit] for `repeatInterval`
91 */
92 constructor(
93 workerClass: KClass<out ListenableWorker>,
94 repeatInterval: Long,
95 repeatIntervalTimeUnit: TimeUnit
96 ) : super(workerClass.java) {
97 workSpec.setPeriodic(repeatIntervalTimeUnit.toMillis(repeatInterval))
98 }
99
100 /**
101 * Creates a [PeriodicWorkRequest] to run periodically once every interval period. The
102 * [PeriodicWorkRequest] is guaranteed to run exactly one time during this interval (subject
103 * to OS battery optimizations, such as doze mode). The repeat interval must be greater than
104 * or equal to [PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS]. It may run immediately,
105 * at the end of the period, or any time in between so long as the other conditions are
106 * satisfied at the time. The run time of the [PeriodicWorkRequest] can be restricted to a
107 * flex period within an interval (see `#Builder(Class, Duration, Duration)`).
108 *
109 * @param workerClass The [ListenableWorker] class to run for this work
110 * @param repeatInterval The repeat interval
111 */
112 @RequiresApi(26)
113 constructor(
114 workerClass: Class<out ListenableWorker>,
115 repeatInterval: Duration
116 ) : super(workerClass) {
117 workSpec.setPeriodic(repeatInterval.toMillisCompat())
118 }
119
120 /**
121 * Creates a [PeriodicWorkRequest] to run periodically once every interval period. The
122 * [PeriodicWorkRequest] is guaranteed to run exactly one time during this interval (subject
123 * to OS battery optimizations, such as doze mode). The repeat interval must be greater than
124 * or equal to [PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS]. It may run immediately,
125 * at the end of the period, or any time in between so long as the other conditions are
126 * satisfied at the time. The run time of the [PeriodicWorkRequest] can be restricted to a
127 * flex period within an interval (see `#Builder(Class, Duration, Duration)`).
128 *
129 * @param workerClass The [ListenableWorker] class to run for this work
130 * @param repeatInterval The repeat interval
131 */
132 @RequiresApi(26)
133 constructor(
134 workerClass: KClass<out ListenableWorker>,
135 repeatInterval: Duration
136 ) : super(workerClass.java) {
137 workSpec.setPeriodic(repeatInterval.toMillisCompat())
138 }
139
140 /**
141 * Creates a [PeriodicWorkRequest] to run periodically once within the **flex period** of
142 * every interval period. See diagram below. The flex period begins at `repeatInterval -
143 * flexInterval` to the end of the interval. The repeat interval must be greater than or
144 * equal to [PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS] and the flex interval must be
145 * greater than or equal to [PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS].
146 *
147 * ```
148 * [_____before flex_____|_____flex_____][_____before flex_____|_____flex_____]...
149 * [___cannot run work___|_can run work_][___cannot run work___|_can run work_]...
150 * \____________________________________/\____________________________________/...
151 * interval 1 interval 2 ...(repeat)
152 * ```
153 *
154 * @param workerClass The [ListenableWorker] class to run for this work
155 * @param repeatInterval The repeat interval in `repeatIntervalTimeUnit` units
156 * @param repeatIntervalTimeUnit The [TimeUnit] for `repeatInterval`
157 * @param flexInterval The duration in `flexIntervalTimeUnit` units for which this work
158 * repeats from the end of the `repeatInterval`
159 * @param flexIntervalTimeUnit The [TimeUnit] for `flexInterval`
160 */
161 constructor(
162 workerClass: Class<out ListenableWorker?>,
163 repeatInterval: Long,
164 repeatIntervalTimeUnit: TimeUnit,
165 flexInterval: Long,
166 flexIntervalTimeUnit: TimeUnit
167 ) : super(workerClass) {
168 workSpec.setPeriodic(
169 repeatIntervalTimeUnit.toMillis(repeatInterval),
170 flexIntervalTimeUnit.toMillis(flexInterval)
171 )
172 }
173
174 /**
175 * Creates a [PeriodicWorkRequest] to run periodically once within the **flex period** of
176 * every interval period. See diagram below. The flex period begins at `repeatInterval -
177 * flexInterval` to the end of the interval. The repeat interval must be greater than or
178 * equal to [PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS] and the flex interval must be
179 * greater than or equal to [PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS].
180 *
181 * ```
182 * [_____before flex_____|_____flex_____][_____before flex_____|_____flex_____]...
183 * [___cannot run work___|_can run work_][___cannot run work___|_can run work_]...
184 * \____________________________________/\____________________________________/...
185 * interval 1 interval 2 ...(repeat)
186 * ```
187 *
188 * @param workerClass The [ListenableWorker] class to run for this work
189 * @param repeatInterval The repeat interval in `repeatIntervalTimeUnit` units
190 * @param repeatIntervalTimeUnit The [TimeUnit] for `repeatInterval`
191 * @param flexInterval The duration in `flexIntervalTimeUnit` units for which this work
192 * repeats from the end of the `repeatInterval`
193 * @param flexIntervalTimeUnit The [TimeUnit] for `flexInterval`
194 */
195 constructor(
196 workerClass: KClass<out ListenableWorker>,
197 repeatInterval: Long,
198 repeatIntervalTimeUnit: TimeUnit,
199 flexInterval: Long,
200 flexIntervalTimeUnit: TimeUnit
201 ) : super(workerClass.java) {
202 workSpec.setPeriodic(
203 repeatIntervalTimeUnit.toMillis(repeatInterval),
204 flexIntervalTimeUnit.toMillis(flexInterval)
205 )
206 }
207
208 /**
209 * Creates a [PeriodicWorkRequest] to run periodically once within the **flex period** of
210 * every interval period. See diagram below. The flex period begins at `repeatInterval -
211 * flexInterval` to the end of the interval. The repeat interval must be greater than or
212 * equal to [PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS] and the flex interval must be
213 * greater than or equal to [PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS].
214 *
215 * ```
216 * [_____before flex_____|_____flex_____][_____before flex_____|_____flex_____]...
217 * [___cannot run work___|_can run work_][___cannot run work___|_can run work_]...
218 * \____________________________________/\____________________________________/...
219 * interval 1 interval 2 ...(repeat)
220 * ```
221 *
222 * @param workerClass The [ListenableWorker] class to run for this work
223 * @param repeatInterval The repeat interval
224 * @param flexInterval The duration in for which this work repeats from the end of the
225 * `repeatInterval`
226 */
227 @RequiresApi(26)
228 constructor(
229 workerClass: Class<out ListenableWorker?>,
230 repeatInterval: Duration,
231 flexInterval: Duration
232 ) : super(workerClass) {
233 workSpec.setPeriodic(repeatInterval.toMillisCompat(), flexInterval.toMillisCompat())
234 }
235
236 /**
237 * Creates a [PeriodicWorkRequest] to run periodically once within the **flex period** of
238 * every interval period. See diagram below. The flex period begins at `repeatInterval -
239 * flexInterval` to the end of the interval. The repeat interval must be greater than or
240 * equal to [PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS] and the flex interval must be
241 * greater than or equal to [PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS].
242 *
243 * ```
244 * [_____before flex_____|_____flex_____][_____before flex_____|_____flex_____]...
245 * [___cannot run work___|_can run work_][___cannot run work___|_can run work_]...
246 * \____________________________________/\____________________________________/...
247 * interval 1 interval 2 ...(repeat)
248 * ```
249 *
250 * @param workerClass The [ListenableWorker] class to run for this work
251 * @param repeatInterval The repeat interval
252 * @param flexInterval The duration in for which this work repeats from the end of the
253 * `repeatInterval`
254 */
255 @RequiresApi(26)
256 constructor(
257 workerClass: KClass<out ListenableWorker>,
258 repeatInterval: Duration,
259 flexInterval: Duration
260 ) : super(workerClass.java) {
261 workSpec.setPeriodic(repeatInterval.toMillisCompat(), flexInterval.toMillisCompat())
262 }
263
264 /**
265 * Overrides the next time this work is scheduled to run.
266 *
267 * Calling this method sets a specific time at which the work will be scheduled to run next,
268 * overriding the normal interval, flex, initial delay, and backoff.
269 *
270 * This allows dynamic calculation of the next Periodic work schedule, which can be used to
271 * implement advanced features like adaptive refresh times, custom retry behavior, or making
272 * a newsfeed worker run before the user wakes up every morning without drift.
273 * [ExistingPeriodicWorkPolicy.UPDATE] should be used with these techniques to avoid
274 * cancelling a currently-running worker while scheduling the next one.
275 *
276 * This method only sets the single next Work schedule. After that Work finishes, the
277 * override will be cleared and the Work will be scheduled normally according to the
278 * interval or backoff. The override can be cleared by setting
279 * [clearNextScheduleTimeOverride] on a work update request. Otherwise, the override time
280 * will persist after unrelated invocations of [WorkManager.updateWork].
281 *
282 * This method can be used from outside or inside a [Worker.startWork] method. If the Worker
283 * is currently running, then it will override the next time the Work starts, even if the
284 * current Worker returns [ListenableWorker.Result.Retry]. This behavior can be used to
285 * customize the backoff behavior of a Worker by catching Exceptions in startWork and using
286 * this method to schedule a retry.
287 *
288 * [MIN_PERIODIC_INTERVAL_MILLIS] is enforced on this method to prevent infinite loops. If a
289 * previous run time occurred less than the minimum period before the override time, then
290 * the override schedule will be delayed to preserve the minimum spacing. This restriction
291 * does not apply to the very first run of periodic work, which may be instant.
292 *
293 * Work will almost never run at this exact time in the real world. This method assigns the
294 * scheduled run time accurately, but cannot guarantee an actual run time. Actual Work run
295 * times are dependent on many factors like the underlying system scheduler, doze and power
296 * saving modes of the OS, and meeting any configured constraints. This is expected and is
297 * not considered a bug.
298 *
299 * @param nextScheduleTimeOverrideMillis The time, in [System.currentTimeMillis] time, to
300 * schedule this work next. If this is in the past, work may run immediately.
301 */
setNextScheduleTimeOverridenull302 fun setNextScheduleTimeOverride(nextScheduleTimeOverrideMillis: Long): Builder {
303 require(nextScheduleTimeOverrideMillis != Long.MAX_VALUE) {
304 "Cannot set Long.MAX_VALUE as the schedule override time"
305 }
306
307 workSpec.nextScheduleTimeOverride = nextScheduleTimeOverrideMillis
308 workSpec.nextScheduleTimeOverrideGeneration = 1
309 return this
310 }
311
312 /**
313 * Clears any override set by [setNextScheduleTimeOverride].
314 *
315 * When an override is cleared, the next schedule is based on the previous enqueue time or
316 * run time of the Work and the result of that previous run. Eg. if the previous run
317 * returned [ListenableWorker.Result.Retry] at some time=T, and the next run was set by
318 * override, then clearing that override will return the schedule to `T+backoffInterval` if
319 * using linear backoff.
320 *
321 * Override may be cleared while a Worker is running. The worker will schedule the next run
322 * based on its result type and interval.
323 */
clearNextScheduleTimeOverridenull324 fun clearNextScheduleTimeOverride(): Builder {
325 workSpec.nextScheduleTimeOverride = Long.MAX_VALUE
326 // Clearing an override increments the generation.
327 workSpec.nextScheduleTimeOverrideGeneration = 1
328 return this
329 }
330
buildInternalnull331 override fun buildInternal(): PeriodicWorkRequest {
332 require(
333 !(backoffCriteriaSet &&
334 Build.VERSION.SDK_INT >= 23 &&
335 workSpec.constraints.requiresDeviceIdle())
336 ) {
337 "Cannot set backoff criteria on an idle mode job"
338 }
339 require(!workSpec.expedited) { "PeriodicWorkRequests cannot be expedited" }
340 return PeriodicWorkRequest(this)
341 }
342
343 override val thisObject: Builder
344 get() = this
345 }
346
347 companion object {
348 /** The minimum interval duration for [PeriodicWorkRequest] (in milliseconds). */
349 @SuppressLint("MinMaxConstant")
350 const val MIN_PERIODIC_INTERVAL_MILLIS = 15 * 60 * 1000L // 15 minutes.
351
352 /** The minimum flex duration for [PeriodicWorkRequest] (in milliseconds). */
353 @SuppressLint("MinMaxConstant")
354 const val MIN_PERIODIC_FLEX_MILLIS = 5 * 60 * 1000L // 5 minutes.
355 }
356 }
357
358 /**
359 * Creates a [PeriodicWorkRequest.Builder] with a given [ListenableWorker].
360 *
361 * @param repeatInterval @see [androidx.work.PeriodicWorkRequest.Builder]
362 * @param repeatIntervalTimeUnit @see [androidx.work.PeriodicWorkRequest.Builder]
363 */
PeriodicWorkRequestBuildernull364 public inline fun <reified W : ListenableWorker> PeriodicWorkRequestBuilder(
365 repeatInterval: Long,
366 repeatIntervalTimeUnit: TimeUnit
367 ): PeriodicWorkRequest.Builder {
368 return PeriodicWorkRequest.Builder(W::class.java, repeatInterval, repeatIntervalTimeUnit)
369 }
370
371 /**
372 * Creates a [PeriodicWorkRequest.Builder] with a given [ListenableWorker].
373 *
374 * @param repeatInterval @see [androidx.work.PeriodicWorkRequest.Builder]
375 */
376 @RequiresApi(26)
PeriodicWorkRequestBuildernull377 public inline fun <reified W : ListenableWorker> PeriodicWorkRequestBuilder(
378 repeatInterval: Duration
379 ): PeriodicWorkRequest.Builder {
380 return PeriodicWorkRequest.Builder(W::class.java, repeatInterval)
381 }
382
383 /**
384 * Creates a [PeriodicWorkRequest.Builder] with a given [ListenableWorker].
385 *
386 * @param repeatInterval @see [androidx.work.PeriodicWorkRequest.Builder]
387 * @param repeatIntervalTimeUnit @see [androidx.work.PeriodicWorkRequest.Builder]
388 * @param flexTimeInterval @see [androidx.work.PeriodicWorkRequest.Builder]
389 * @param flexTimeIntervalUnit @see [androidx.work.PeriodicWorkRequest.Builder]
390 */
PeriodicWorkRequestBuildernull391 public inline fun <reified W : ListenableWorker> PeriodicWorkRequestBuilder(
392 repeatInterval: Long,
393 repeatIntervalTimeUnit: TimeUnit,
394 flexTimeInterval: Long,
395 flexTimeIntervalUnit: TimeUnit
396 ): PeriodicWorkRequest.Builder {
397
398 return PeriodicWorkRequest.Builder(
399 W::class.java,
400 repeatInterval,
401 repeatIntervalTimeUnit,
402 flexTimeInterval,
403 flexTimeIntervalUnit
404 )
405 }
406
407 /**
408 * Creates a [PeriodicWorkRequest.Builder] with a given [ListenableWorker].
409 *
410 * @param repeatInterval @see [androidx.work.PeriodicWorkRequest.Builder]
411 * @param flexTimeInterval @see [androidx.work.PeriodicWorkRequest.Builder]
412 */
413 @RequiresApi(26)
PeriodicWorkRequestBuildernull414 public inline fun <reified W : ListenableWorker> PeriodicWorkRequestBuilder(
415 repeatInterval: Duration,
416 flexTimeInterval: Duration
417 ): PeriodicWorkRequest.Builder {
418 return PeriodicWorkRequest.Builder(W::class.java, repeatInterval, flexTimeInterval)
419 }
420