1 /*
<lambda>null2 * Copyright (C) 2017 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.impl.model
17
18 import android.annotation.SuppressLint
19 import android.app.job.JobParameters.STOP_REASON_CANCELLED_BY_APP
20 import androidx.lifecycle.LiveData
21 import androidx.room.Dao
22 import androidx.room.Insert
23 import androidx.room.OnConflictStrategy
24 import androidx.room.Query
25 import androidx.room.Transaction
26 import androidx.room.Update
27 import androidx.work.CONSTRAINTS_COLUMNS
28 import androidx.work.Data
29 import androidx.work.WorkInfo
30 import androidx.work.impl.model.WorkTypeConverters.StateIds.CANCELLED
31 import androidx.work.impl.model.WorkTypeConverters.StateIds.COMPLETED_STATES
32 import androidx.work.impl.model.WorkTypeConverters.StateIds.ENQUEUED
33 import androidx.work.impl.model.WorkTypeConverters.StateIds.RUNNING
34 import java.util.UUID
35 import kotlinx.coroutines.CoroutineDispatcher
36 import kotlinx.coroutines.flow.Flow
37 import kotlinx.coroutines.flow.distinctUntilChanged
38 import kotlinx.coroutines.flow.flowOn
39 import kotlinx.coroutines.flow.map
40 import org.intellij.lang.annotations.Language
41
42 /** The Data Access Object for [WorkSpec]s. */
43 @Dao
44 @SuppressLint("UnknownNullness")
45 interface WorkSpecDao {
46 /**
47 * Attempts to insert a [WorkSpec] into the database.
48 *
49 * @param workSpec The WorkSpec to insert.
50 */
51 @Insert(onConflict = OnConflictStrategy.IGNORE) fun insertWorkSpec(workSpec: WorkSpec)
52
53 /**
54 * Deletes [WorkSpec]s from the database.
55 *
56 * @param id The WorkSpec id to delete.
57 */
58 @Query("DELETE FROM workspec WHERE id=:id") fun delete(id: String)
59
60 /**
61 * @param id The identifier
62 * @return The WorkSpec associated with that id
63 */
64 @Query("SELECT * FROM workspec WHERE id=:id") fun getWorkSpec(id: String): WorkSpec?
65
66 /**
67 * @param name The work graph name
68 * @return The [WorkSpec]s labelled with the given name
69 */
70 @Query(
71 "SELECT id, state FROM workspec WHERE id IN " +
72 "(SELECT work_spec_id FROM workname WHERE name=:name)"
73 )
74 fun getWorkSpecIdAndStatesForName(name: String): List<WorkSpec.IdAndState>
75
76 /** @return All WorkSpec ids in the database. */
77 @Query("SELECT id FROM workspec") fun getAllWorkSpecIds(): List<String>
78
79 /** @return A [LiveData] list of all WorkSpec ids in the database. */
80 @Transaction
81 @Query("SELECT id FROM workspec")
82 fun getAllWorkSpecIdsLiveData(): LiveData<List<String>>
83
84 /**
85 * Updates the state of at least one [WorkSpec] by ID.
86 *
87 * @param state The new state
88 * @param id The IDs for the [WorkSpec]s to update
89 * @return The number of rows that were updated
90 */
91 @Query("UPDATE workspec SET state=:state WHERE id=:id")
92 fun setState(state: WorkInfo.State, id: String): Int
93
94 /**
95 * Sets cancelled state for workspec
96 *
97 * @param id The IDs for the [WorkSpec]s to update
98 * @return The number of rows that were updated
99 */
100 @Query(
101 "UPDATE workspec " +
102 "SET stop_reason = CASE WHEN state=$RUNNING THEN $STOP_REASON_CANCELLED_BY_APP " +
103 "ELSE ${WorkInfo.STOP_REASON_NOT_STOPPED} END, state=$CANCELLED WHERE id=:id"
104 )
105 fun setCancelledState(id: String): Int
106
107 /** Increment periodic counter. */
108 @Query("UPDATE workspec SET period_count=period_count+1 WHERE id=:id")
109 fun incrementPeriodCount(id: String)
110
111 /**
112 * Updates the output of a [WorkSpec].
113 *
114 * @param id The [WorkSpec] identifier to update
115 * @param output The [Data] to set as the output
116 */
117 @Query("UPDATE workspec SET output=:output WHERE id=:id")
118 fun setOutput(id: String, output: Data)
119
120 /**
121 * Updates the period start time of a [WorkSpec].
122 *
123 * @param id The [WorkSpec] identifier to update
124 * @param enqueueTime The time when the period started.
125 */
126 @Query("UPDATE workspec SET last_enqueue_time=:enqueueTime WHERE id=:id")
127 fun setLastEnqueueTime(id: String, enqueueTime: Long)
128
129 /**
130 * Increment run attempt count of a [WorkSpec].
131 *
132 * @param id The identifier for the [WorkSpec]
133 * @return The number of rows that were updated (should be 0 or 1)
134 */
135 @Query("UPDATE workspec SET run_attempt_count=run_attempt_count+1 WHERE id=:id")
136 fun incrementWorkSpecRunAttemptCount(id: String): Int
137
138 /**
139 * Reset run attempt count of a [WorkSpec].
140 *
141 * @param id The identifier for the [WorkSpec]
142 * @return The number of rows that were updated (should be 0 or 1)
143 */
144 @Query("UPDATE workspec SET run_attempt_count=0 WHERE id=:id")
145 fun resetWorkSpecRunAttemptCount(id: String): Int
146
147 /**
148 * Updates the next schedule time of a [WorkSpec].
149 *
150 * @param id The [WorkSpec] identifier to update
151 * @param nextScheduleTimeOverrideMillis The next schedule time in millis since epoch. See
152 * [WorkSpec.nextScheduleTimeOverride]
153 */
154 @Query(
155 "UPDATE workspec SET next_schedule_time_override=:nextScheduleTimeOverrideMillis " +
156 "WHERE id=:id"
157 )
158 fun setNextScheduleTimeOverride(id: String, nextScheduleTimeOverrideMillis: Long)
159
160 /**
161 * Resets the next schedule time override of a [WorkSpec] if the override generation has not
162 * changed.
163 *
164 * @param id The identifier for the [WorkSpec]
165 */
166 @Query(
167 "UPDATE workspec SET next_schedule_time_override=${Long.MAX_VALUE} WHERE " +
168 "(id=:id AND next_schedule_time_override_generation=:overrideGeneration)"
169 )
170 fun resetWorkSpecNextScheduleTimeOverride(id: String, overrideGeneration: Int)
171
172 /**
173 * Retrieves the state of a [WorkSpec].
174 *
175 * @param id The identifier for the [WorkSpec]
176 * @return The state of the [WorkSpec]
177 */
178 @Query("SELECT state FROM workspec WHERE id=:id") fun getState(id: String): WorkInfo.State?
179
180 /**
181 * For a [WorkSpec] identifier, retrieves its [WorkSpec.WorkInfoPojo].
182 *
183 * @param id The identifier of the [WorkSpec]
184 * @return A list of [WorkSpec.WorkInfoPojo]
185 */
186 @Transaction
187 @Query("SELECT $WORK_INFO_COLUMNS FROM workspec WHERE id=:id")
188 fun getWorkStatusPojoForId(id: String): WorkSpec.WorkInfoPojo?
189
190 /**
191 * For a list of [WorkSpec] identifiers, retrieves a [List] of their [WorkSpec.WorkInfoPojo].
192 *
193 * @param ids The identifier of the [WorkSpec]s
194 * @return A [List] of [WorkSpec.WorkInfoPojo]
195 */
196 @Transaction
197 @Query("SELECT $WORK_INFO_COLUMNS FROM workspec WHERE id IN (:ids)")
198 fun getWorkStatusPojoForIds(ids: List<String>): List<WorkSpec.WorkInfoPojo>
199
200 /**
201 * For a list of [WorkSpec] identifiers, retrieves a [LiveData] list of their
202 * [WorkSpec.WorkInfoPojo].
203 *
204 * @param ids The identifier of the [WorkSpec]s
205 * @return A [LiveData] list of [WorkSpec.WorkInfoPojo]
206 */
207 @Transaction
208 @Query(WORK_INFO_BY_IDS)
209 fun getWorkStatusPojoLiveDataForIds(ids: List<String>): LiveData<List<WorkSpec.WorkInfoPojo>>
210
211 /**
212 * For a list of [WorkSpec] identifiers, retrieves a [LiveData] list of their
213 * [WorkSpec.WorkInfoPojo].
214 *
215 * @param ids The identifier of the [WorkSpec]s
216 * @return A [Flow] list of [WorkSpec.WorkInfoPojo]
217 */
218 @Transaction
219 @Query(WORK_INFO_BY_IDS)
220 fun getWorkStatusPojoFlowDataForIds(ids: List<String>): Flow<List<WorkSpec.WorkInfoPojo>>
221
222 /**
223 * Retrieves a list of [WorkSpec.WorkInfoPojo] for all work with a given tag.
224 *
225 * @param tag The tag for the [WorkSpec]s
226 * @return A list of [WorkSpec.WorkInfoPojo]
227 */
228 @Transaction
229 @Query(
230 """SELECT $WORK_INFO_COLUMNS FROM workspec WHERE id IN
231 (SELECT work_spec_id FROM worktag WHERE tag=:tag)"""
232 )
233 fun getWorkStatusPojoForTag(tag: String): List<WorkSpec.WorkInfoPojo>
234
235 /**
236 * Retrieves a [LiveData] list of [WorkSpec.WorkInfoPojo] for all work with a given tag.
237 *
238 * @param tag The tag for the [WorkSpec]s
239 * @return A [LiveData] list of [WorkSpec.WorkInfoPojo]
240 */
241 @Transaction
242 @Query(WORK_INFO_BY_TAG)
243 fun getWorkStatusPojoFlowForTag(tag: String): Flow<List<WorkSpec.WorkInfoPojo>>
244
245 /**
246 * Retrieves a [LiveData] list of [WorkSpec.WorkInfoPojo] for all work with a given tag.
247 *
248 * @param tag The tag for the [WorkSpec]s
249 * @return A [LiveData] list of [WorkSpec.WorkInfoPojo]
250 */
251 @Transaction
252 @Query(WORK_INFO_BY_TAG)
253 fun getWorkStatusPojoLiveDataForTag(tag: String): LiveData<List<WorkSpec.WorkInfoPojo>>
254
255 /**
256 * Retrieves a list of [WorkSpec.WorkInfoPojo] for all work with a given name.
257 *
258 * @param name The name of the [WorkSpec]s
259 * @return A list of [WorkSpec.WorkInfoPojo]
260 */
261 @Transaction
262 @Query(
263 "SELECT $WORK_INFO_COLUMNS FROM workspec WHERE id IN " +
264 "(SELECT work_spec_id FROM workname WHERE name=:name)"
265 )
266 fun getWorkStatusPojoForName(name: String): List<WorkSpec.WorkInfoPojo>
267
268 /**
269 * Retrieves a [LiveData] list of [WorkSpec.WorkInfoPojo] for all work with a given name.
270 *
271 * @param name The name for the [WorkSpec]s
272 * @return A [LiveData] list of [WorkSpec.WorkInfoPojo]
273 */
274 @Transaction
275 @Query(WORK_INFO_BY_NAME)
276 fun getWorkStatusPojoLiveDataForName(name: String): LiveData<List<WorkSpec.WorkInfoPojo>>
277
278 /**
279 * Retrieves a [Flow] list of [WorkSpec.WorkInfoPojo] for all work with a given name.
280 *
281 * @param name The name for the [WorkSpec]s
282 * @return A [LiveData] list of [WorkSpec.WorkInfoPojo]
283 */
284 @Transaction
285 @Query(WORK_INFO_BY_NAME)
286 fun getWorkStatusPojoFlowForName(name: String): Flow<List<WorkSpec.WorkInfoPojo>>
287
288 /**
289 * Gets all inputs coming from prerequisites for a particular [WorkSpec]. These are [Data] set
290 * via `Worker#setOutputData()`.
291 *
292 * @param id The [WorkSpec] identifier
293 * @return A list of all inputs coming from prerequisites for `id`
294 */
295 @Query(
296 """SELECT output FROM workspec WHERE id IN
297 (SELECT prerequisite_id FROM dependency WHERE work_spec_id=:id)"""
298 )
299 fun getInputsFromPrerequisites(id: String): List<Data>
300
301 /**
302 * Retrieves work ids for unfinished work with a given tag.
303 *
304 * @param tag The tag used to identify the work
305 * @return A list of work ids
306 */
307 @Query(
308 "SELECT id FROM workspec WHERE state NOT IN " +
309 COMPLETED_STATES +
310 " AND id IN (SELECT work_spec_id FROM worktag WHERE tag=:tag)"
311 )
312 fun getUnfinishedWorkWithTag(tag: String): List<String>
313
314 /**
315 * Retrieves work ids for unfinished work with a given name.
316 *
317 * @param name THe tag used to identify the work
318 * @return A list of work ids
319 */
320 @Query(
321 "SELECT id FROM workspec WHERE state NOT IN " +
322 COMPLETED_STATES +
323 " AND id IN (SELECT work_spec_id FROM workname WHERE name=:name)"
324 )
325 fun getUnfinishedWorkWithName(name: String): List<String>
326
327 /**
328 * Retrieves work ids for all unfinished work.
329 *
330 * @return A list of work ids
331 */
332 @Query("SELECT id FROM workspec WHERE state NOT IN " + COMPLETED_STATES)
333 fun getAllUnfinishedWork(): List<String>
334
335 /** @return `true` if there is pending work. */
336 @Query("SELECT COUNT(*) > 0 FROM workspec WHERE state NOT IN $COMPLETED_STATES LIMIT 1")
337 fun hasUnfinishedWorkFlow(): Flow<Boolean>
338
339 /**
340 * Marks a [WorkSpec] as scheduled.
341 *
342 * @param id The identifier for the [WorkSpec]
343 * @param startTime The time at which the [WorkSpec] was scheduled.
344 * @return The number of rows that were updated (should be 0 or 1)
345 */
346 @Query("UPDATE workspec SET schedule_requested_at=:startTime WHERE id=:id")
347 fun markWorkSpecScheduled(id: String, startTime: Long): Int
348
349 /** @return The time at which the [WorkSpec] was scheduled. */
350 @Query("SELECT schedule_requested_at FROM workspec WHERE id=:id")
351 fun getScheduleRequestedAtLiveData(id: String): LiveData<Long?>
352
353 /**
354 * Resets the scheduled state on the [WorkSpec]s that are not in a a completed state.
355 *
356 * @return The number of rows that were updated
357 */
358 @Query(
359 "UPDATE workspec SET schedule_requested_at=" +
360 WorkSpec.SCHEDULE_NOT_REQUESTED_YET +
361 " WHERE state NOT IN " +
362 COMPLETED_STATES
363 )
364 fun resetScheduledState(): Int
365
366 /** @return The List of [WorkSpec]s that are eligible to be scheduled. */
367 @Query(
368 "SELECT * FROM workspec WHERE " +
369 "state=" +
370 ENQUEUED +
371 // We only want WorkSpecs which have not been previously scheduled.
372 " AND schedule_requested_at=" +
373 WorkSpec.SCHEDULE_NOT_REQUESTED_YET +
374 // Order by period start time so we execute scheduled WorkSpecs in FIFO order
375 " ORDER BY last_enqueue_time" +
376 " LIMIT " +
377 "(SELECT MAX(:schedulerLimit" +
378 "-COUNT(*), 0) FROM workspec WHERE" +
379 " schedule_requested_at<>" +
380 WorkSpec.SCHEDULE_NOT_REQUESTED_YET +
381 // content_uri_triggers aren't counted here because they have separate limit
382 " AND LENGTH(content_uri_triggers)=0" +
383 " AND state NOT IN " +
384 COMPLETED_STATES +
385 ")"
386 )
387 fun getEligibleWorkForScheduling(schedulerLimit: Int): List<WorkSpec>
388
389 /** @return The List of [WorkSpec]s that are eligible to be scheduled. */
390 @Query(
391 "SELECT * FROM workspec WHERE " +
392 "state=$ENQUEUED" +
393 // We only want WorkSpecs which have not been previously scheduled.
394 " AND schedule_requested_at=${WorkSpec.SCHEDULE_NOT_REQUESTED_YET}" +
395 " AND LENGTH(content_uri_triggers)<>0" +
396 // Order by period start time so we execute scheduled WorkSpecs in FIFO order
397 " ORDER BY last_enqueue_time"
398 )
399 fun getEligibleWorkForSchedulingWithContentUris(): List<WorkSpec>
400
401 /** @return The List of [WorkSpec]s that can be scheduled irrespective of scheduling limits. */
402 @Query(
403 "SELECT * FROM workspec WHERE " +
404 "state=$ENQUEUED" +
405 // Order by period start time so we execute scheduled WorkSpecs in FIFO order
406 " ORDER BY last_enqueue_time" +
407 " LIMIT :maxLimit"
408 )
409 fun getAllEligibleWorkSpecsForScheduling(maxLimit: Int): List<WorkSpec> // Unfinished work
410
411 // We only want WorkSpecs which have been scheduled.
412 /** @return The List of [WorkSpec]s that are unfinished and scheduled. */
413 @Query(
414 "SELECT * FROM workspec WHERE " + // Unfinished work
415 "state=" +
416 ENQUEUED + // We only want WorkSpecs which have been scheduled.
417 " AND schedule_requested_at<>" +
418 WorkSpec.SCHEDULE_NOT_REQUESTED_YET
419 )
420 fun getScheduledWork(): List<WorkSpec>
421
422 /** @return The List of [WorkSpec]s that are running. */
423 @Query(
424 "SELECT * FROM workspec WHERE " + // Unfinished work
425 "state=" +
426 WorkTypeConverters.StateIds.RUNNING
427 )
428 fun getRunningWork(): List<WorkSpec>
429
430 /** @return The List of [WorkSpec] which completed recently. */
431 @Query(
432 "SELECT * FROM workspec WHERE " +
433 "last_enqueue_time >= :startingAt" +
434 " AND state IN " +
435 COMPLETED_STATES +
436 " ORDER BY last_enqueue_time DESC"
437 )
438 fun getRecentlyCompletedWork(startingAt: Long): List<WorkSpec>
439
440 /**
441 * Immediately prunes eligible work from the database meeting the following criteria:
442 * - Is finished (succeeded, failed, or cancelled)
443 * - Has zero unfinished dependents
444 */
445 @Query(
446 "DELETE FROM workspec WHERE " +
447 "state IN " +
448 COMPLETED_STATES +
449 " AND (SELECT COUNT(*)=0 FROM dependency WHERE " +
450 " prerequisite_id=id AND " +
451 " work_spec_id NOT IN " +
452 " (SELECT id FROM workspec WHERE state IN " +
453 COMPLETED_STATES +
454 "))"
455 )
456 fun pruneFinishedWorkWithZeroDependentsIgnoringKeepForAtLeast()
457
458 @Query("UPDATE workspec SET generation=generation+1 WHERE id=:id")
459 fun incrementGeneration(id: String)
460
461 @Update fun updateWorkSpec(workSpec: WorkSpec)
462
463 @Query(
464 "Select COUNT(*) FROM workspec WHERE LENGTH(content_uri_triggers)<>0" +
465 " AND state NOT IN $COMPLETED_STATES"
466 )
467 fun countNonFinishedContentUriTriggerWorkers(): Int
468
469 @Query("UPDATE workspec SET stop_reason=:stopReason WHERE id=:id")
470 fun setStopReason(id: String, stopReason: Int)
471 }
472
WorkSpecDaonull473 fun WorkSpecDao.getWorkStatusPojoFlowDataForIds(id: UUID): Flow<WorkInfo?> =
474 getWorkStatusPojoFlowDataForIds(listOf("$id"))
475 .map { it.firstOrNull()?.toWorkInfo() }
476 .distinctUntilChanged()
477
getWorkStatusPojoFlowForNamenull478 fun WorkSpecDao.getWorkStatusPojoFlowForName(
479 dispatcher: CoroutineDispatcher,
480 name: String
481 ): Flow<List<WorkInfo>> = getWorkStatusPojoFlowForName(name).dedup(dispatcher)
482
483 fun WorkSpecDao.getWorkStatusPojoFlowForTag(
484 dispatcher: CoroutineDispatcher,
485 tag: String
486 ): Flow<List<WorkInfo>> = getWorkStatusPojoFlowForTag(tag).dedup(dispatcher)
487
488 internal fun Flow<List<WorkSpec.WorkInfoPojo>>.dedup(
489 dispatcher: CoroutineDispatcher
490 ): Flow<List<WorkInfo>> =
491 map { list -> list.map { pojo -> pojo.toWorkInfo() } }.distinctUntilChanged().flowOn(dispatcher)
492
493 private const val WORK_INFO_COLUMNS =
494 "id, state, output, run_attempt_count, generation" +
495 ", $CONSTRAINTS_COLUMNS, initial_delay, interval_duration, flex_duration, backoff_policy" +
496 ", backoff_delay_duration, last_enqueue_time, period_count, next_schedule_time_override, " +
497 "stop_reason"
498
499 @Language("sql")
500 private const val WORK_INFO_BY_IDS = "SELECT $WORK_INFO_COLUMNS FROM workspec WHERE id IN (:ids)"
501
502 @Language("sql")
503 private const val WORK_INFO_BY_TAG =
504 """SELECT $WORK_INFO_COLUMNS FROM workspec WHERE id IN
505 (SELECT work_spec_id FROM worktag WHERE tag=:tag)"""
506
507 @Language("sql")
508 private const val WORK_INFO_BY_NAME =
509 "SELECT $WORK_INFO_COLUMNS FROM workspec WHERE id IN " +
510 "(SELECT work_spec_id FROM workname WHERE name=:name)"
511