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