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 
17 package androidx.work.impl.utils;
18 
19 import static androidx.work.State.CANCELLED;
20 import static androidx.work.State.FAILED;
21 import static androidx.work.State.SUCCEEDED;
22 
23 import android.support.annotation.NonNull;
24 import android.support.annotation.RestrictTo;
25 import android.support.annotation.WorkerThread;
26 
27 import androidx.work.State;
28 import androidx.work.impl.Processor;
29 import androidx.work.impl.Scheduler;
30 import androidx.work.impl.Schedulers;
31 import androidx.work.impl.WorkDatabase;
32 import androidx.work.impl.WorkManagerImpl;
33 import androidx.work.impl.model.DependencyDao;
34 import androidx.work.impl.model.WorkSpecDao;
35 
36 import java.util.List;
37 import java.util.UUID;
38 
39 /**
40  * A {@link Runnable} to cancel work.
41  *
42  * @hide
43  */
44 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
45 public abstract class CancelWorkRunnable implements Runnable {
46 
cancel(WorkManagerImpl workManagerImpl, String workSpecId)47     void cancel(WorkManagerImpl workManagerImpl, String workSpecId) {
48         recursivelyCancelWorkAndDependents(workManagerImpl.getWorkDatabase(), workSpecId);
49 
50         Processor processor = workManagerImpl.getProcessor();
51         processor.stopAndCancelWork(workSpecId);
52 
53         for (Scheduler scheduler : workManagerImpl.getSchedulers()) {
54             scheduler.cancel(workSpecId);
55         }
56     }
57 
reschedulePendingWorkers(WorkManagerImpl workManagerImpl)58     void reschedulePendingWorkers(WorkManagerImpl workManagerImpl) {
59         Schedulers.schedule(
60                 workManagerImpl.getConfiguration(),
61                 workManagerImpl.getWorkDatabase(),
62                 workManagerImpl.getSchedulers());
63     }
64 
recursivelyCancelWorkAndDependents(WorkDatabase workDatabase, String workSpecId)65     private void recursivelyCancelWorkAndDependents(WorkDatabase workDatabase, String workSpecId) {
66 
67         WorkSpecDao workSpecDao = workDatabase.workSpecDao();
68         DependencyDao dependencyDao = workDatabase.dependencyDao();
69 
70         List<String> dependentIds = dependencyDao.getDependentWorkIds(workSpecId);
71         for (String id : dependentIds) {
72             recursivelyCancelWorkAndDependents(workDatabase, id);
73         }
74 
75         State state = workSpecDao.getState(workSpecId);
76         if (state != SUCCEEDED && state != FAILED) {
77             workSpecDao.setState(CANCELLED, workSpecId);
78         }
79     }
80 
81     /**
82      * Creates a {@link CancelWorkRunnable} that cancels work for a specific id.
83      *
84      * @param id The id to cancel
85      * @param workManagerImpl The {@link WorkManagerImpl} to use
86      * @return A {@link Runnable} that cancels work for a specific id
87      */
forId( @onNull final UUID id, @NonNull final WorkManagerImpl workManagerImpl)88     public static Runnable forId(
89             @NonNull final UUID id,
90             @NonNull final WorkManagerImpl workManagerImpl) {
91         return new CancelWorkRunnable() {
92             @WorkerThread
93             @Override
94             public void run() {
95                 cancel(workManagerImpl, id.toString());
96                 reschedulePendingWorkers(workManagerImpl);
97             }
98         };
99     }
100 
101     /**
102      * Creates a {@link CancelWorkRunnable} that cancels work for a specific tag.
103      *
104      * @param tag The tag to cancel
105      * @param workManagerImpl The {@link WorkManagerImpl} to use
106      * @return A {@link Runnable} that cancels work for a specific tag
107      */
108     public static Runnable forTag(
109             @NonNull final String tag,
110             @NonNull final WorkManagerImpl workManagerImpl) {
111         return new CancelWorkRunnable() {
112             @WorkerThread
113             @Override
114             public void run() {
115                 WorkDatabase workDatabase = workManagerImpl.getWorkDatabase();
116                 workDatabase.beginTransaction();
117                 try {
118                     WorkSpecDao workSpecDao = workDatabase.workSpecDao();
119                     List<String> workSpecIds = workSpecDao.getUnfinishedWorkWithTag(tag);
120                     for (String workSpecId : workSpecIds) {
121                         cancel(workManagerImpl, workSpecId);
122                     }
123                     workDatabase.setTransactionSuccessful();
124                 } finally {
125                     workDatabase.endTransaction();
126                 }
127                 reschedulePendingWorkers(workManagerImpl);
128             }
129         };
130     }
131 
132     /**
133      * Creates a {@link CancelWorkRunnable} that cancels work labelled with a specific name.
134      *
135      * @param name The name to cancel
136      * @param workManagerImpl The {@link WorkManagerImpl} to use
137      * @return A {@link Runnable} that cancels work labelled with a specific name
138      */
139     public static Runnable forName(
140             @NonNull final String name,
141             @NonNull final WorkManagerImpl workManagerImpl) {
142         return new CancelWorkRunnable() {
143             @WorkerThread
144             @Override
145             public void run() {
146                 WorkDatabase workDatabase = workManagerImpl.getWorkDatabase();
147                 workDatabase.beginTransaction();
148                 try {
149                     WorkSpecDao workSpecDao = workDatabase.workSpecDao();
150                     List<String> workSpecIds = workSpecDao.getUnfinishedWorkWithName(name);
151                     for (String workSpecId : workSpecIds) {
152                         cancel(workManagerImpl, workSpecId);
153                     }
154                     workDatabase.setTransactionSuccessful();
155                 } finally {
156                     workDatabase.endTransaction();
157                 }
158                 reschedulePendingWorkers(workManagerImpl);
159             }
160         };
161     }
162 
163     /**
164      * Creates a {@link CancelWorkRunnable} that cancels all work.
165      *
166      * @param workManagerImpl The {@link WorkManagerImpl} to use
167      * @return A {@link Runnable} that cancels all work
168      */
169     public static Runnable forAll(@NonNull final WorkManagerImpl workManagerImpl) {
170         return new CancelWorkRunnable() {
171             @Override
172             public void run() {
173                 WorkDatabase workDatabase = workManagerImpl.getWorkDatabase();
174                 workDatabase.beginTransaction();
175                 try {
176                     WorkSpecDao workSpecDao = workDatabase.workSpecDao();
177                     List<String> workSpecIds = workSpecDao.getAllUnfinishedWork();
178                     for (String workSpecId : workSpecIds) {
179                         cancel(workManagerImpl, workSpecId);
180                     }
181                     workDatabase.setTransactionSuccessful();
182                     // Update the preferences
183                     new Preferences(workManagerImpl.getApplicationContext())
184                             .setLastCancelAllTimeMillis(System.currentTimeMillis());
185                 } finally {
186                     workDatabase.endTransaction();
187                 }
188                 // No need to call reschedule pending workers here as we just cancelled everything.
189             }
190         };
191     }
192 }
193