1 /*
2  * Copyright 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 @file:JvmName("CancelWorkRunnable")
17 
18 package androidx.work.impl.utils
19 
20 import android.app.job.JobParameters
21 import androidx.work.Operation
22 import androidx.work.WorkInfo
23 import androidx.work.impl.Schedulers
24 import androidx.work.impl.WorkDatabase
25 import androidx.work.impl.WorkManagerImpl
26 import androidx.work.launchOperation
27 import java.util.UUID
28 import kotlin.collections.removeLast as removeLastKt
29 
cancelnull30 private fun cancel(workManagerImpl: WorkManagerImpl, workSpecId: String) {
31     iterativelyCancelWorkAndDependents(workManagerImpl.workDatabase, workSpecId)
32     val processor = workManagerImpl.processor
33     processor.stopAndCancelWork(workSpecId, JobParameters.STOP_REASON_CANCELLED_BY_APP)
34     for (scheduler in workManagerImpl.schedulers) {
35         scheduler.cancel(workSpecId)
36     }
37 }
38 
reschedulePendingWorkersnull39 private fun reschedulePendingWorkers(workManagerImpl: WorkManagerImpl) {
40     Schedulers.schedule(
41         workManagerImpl.configuration,
42         workManagerImpl.workDatabase,
43         workManagerImpl.schedulers
44     )
45 }
46 
iterativelyCancelWorkAndDependentsnull47 private fun iterativelyCancelWorkAndDependents(workDatabase: WorkDatabase, workSpecId: String) {
48     val workSpecDao = workDatabase.workSpecDao()
49     val dependencyDao = workDatabase.dependencyDao()
50     val idsToProcess = mutableListOf(workSpecId)
51     while (idsToProcess.isNotEmpty()) {
52         val id = idsToProcess.removeLastKt()
53         // Don't fail already cancelled work.
54         val state = workSpecDao.getState(id)
55         if (state !== WorkInfo.State.SUCCEEDED && state !== WorkInfo.State.FAILED) {
56             workSpecDao.setCancelledState(id)
57         }
58         idsToProcess.addAll(dependencyDao.getDependentWorkIds(id))
59     }
60 }
61 
62 /**
63  * Cancels work for a specific id.
64  *
65  * @param id The id to cancel
66  * @param workManagerImpl The [WorkManagerImpl] to use
67  * @return A [Operation]
68  */
forIdnull69 fun forId(id: UUID, workManagerImpl: WorkManagerImpl): Operation =
70     launchOperation(
71         tracer = workManagerImpl.configuration.tracer,
72         label = "CancelWorkById",
73         workManagerImpl.workTaskExecutor.serialTaskExecutor
74     ) {
75         val workDatabase = workManagerImpl.workDatabase
76         workDatabase.runInTransaction { cancel(workManagerImpl, id.toString()) }
77         reschedulePendingWorkers(workManagerImpl)
78     }
79 
80 /**
81  * Cancels work for a specific tag.
82  *
83  * @param tag The tag to cancel
84  * @param workManagerImpl The [WorkManagerImpl] to use
85  * @return A [Operation]
86  */
forTagnull87 fun forTag(tag: String, workManagerImpl: WorkManagerImpl): Operation =
88     launchOperation(
89         tracer = workManagerImpl.configuration.tracer,
90         label = "CancelWorkByTag_$tag",
91         executor = workManagerImpl.workTaskExecutor.serialTaskExecutor
92     ) {
93         val workDatabase = workManagerImpl.workDatabase
94         workDatabase.runInTransaction {
95             val workSpecDao = workDatabase.workSpecDao()
96             val workSpecIds = workSpecDao.getUnfinishedWorkWithTag(tag)
97             for (workSpecId in workSpecIds) {
98                 cancel(workManagerImpl, workSpecId)
99             }
100         }
101         reschedulePendingWorkers(workManagerImpl)
102     }
103 
104 /**
105  * Cancels work labelled with a specific name.
106  *
107  * @param name The name to cancel
108  * @param workManagerImpl The [WorkManagerImpl] to use
109  * @return A [Operation]
110  */
forNamenull111 fun forName(name: String, workManagerImpl: WorkManagerImpl): Operation =
112     launchOperation(
113         tracer = workManagerImpl.configuration.tracer,
114         label = "CancelWorkByName_$name",
115         workManagerImpl.workTaskExecutor.serialTaskExecutor
116     ) {
117         forNameInline(name, workManagerImpl)
118         reschedulePendingWorkers(workManagerImpl)
119     }
120 
forNameInlinenull121 fun forNameInline(name: String, workManagerImpl: WorkManagerImpl) {
122     val workDatabase = workManagerImpl.workDatabase
123     workDatabase.runInTransaction {
124         val workSpecDao = workDatabase.workSpecDao()
125         val workSpecIds = workSpecDao.getUnfinishedWorkWithName(name)
126         for (workSpecId in workSpecIds) {
127             cancel(workManagerImpl, workSpecId)
128         }
129     }
130 }
131 
132 /**
133  * Cancels all work.
134  *
135  * @param workManagerImpl The [WorkManagerImpl] to use
136  * @return A [Operation] that cancels all work
137  */
forAllnull138 fun forAll(workManagerImpl: WorkManagerImpl): Operation =
139     launchOperation(
140         tracer = workManagerImpl.configuration.tracer,
141         label = "CancelAllWork",
142         workManagerImpl.workTaskExecutor.serialTaskExecutor
143     ) {
144         val workDatabase = workManagerImpl.workDatabase
145         workDatabase.runInTransaction {
146             val workSpecDao = workDatabase.workSpecDao()
147             val workSpecIds = workSpecDao.getAllUnfinishedWork()
148             for (workSpecId in workSpecIds) {
149                 cancel(workManagerImpl, workSpecId)
150             }
151             // Update the last cancelled time in Preference.
152             PreferenceUtils(workDatabase)
153                 .setLastCancelAllTimeMillis(workManagerImpl.configuration.clock.currentTimeMillis())
154         }
155         // No need to call reschedule pending workers here as we just cancelled everything.
156     }
157