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