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