blob: 004b895b89401798cf4a9ef56c098a0cca42fb7b [file] [log] [blame]
Sumir Kataria15f793d2017-12-18 13:30:46 -08001/*
Rahul Ravikumar87d37e52018-01-04 18:18:29 -08002 * Copyright 2018 The Android Open Source Project
Sumir Kataria15f793d2017-12-18 13:30:46 -08003 *
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
Sumir Kataria564e4302018-02-14 11:22:30 -080017package androidx.work.impl.utils;
Sumir Kataria15f793d2017-12-18 13:30:46 -080018
Sumir Kataria564e4302018-02-14 11:22:30 -080019import static androidx.work.ExistingWorkPolicy.APPEND;
20import static androidx.work.ExistingWorkPolicy.KEEP;
21import static androidx.work.State.BLOCKED;
Sumir Kataria84354e32018-03-07 15:30:16 -080022import static androidx.work.State.CANCELLED;
Sumir Kataria564e4302018-02-14 11:22:30 -080023import static androidx.work.State.ENQUEUED;
Sumir Kataria84354e32018-03-07 15:30:16 -080024import static androidx.work.State.FAILED;
Sumir Kataria564e4302018-02-14 11:22:30 -080025import static androidx.work.State.RUNNING;
26import static androidx.work.State.SUCCEEDED;
Sumir Kataria31099f82018-03-16 16:17:36 -070027import static androidx.work.impl.workers.ConstraintTrackingWorker.ARGUMENT_CLASS_NAME;
Sumir Kataria15f793d2017-12-18 13:30:46 -080028
Sumir Kataria31099f82018-03-16 16:17:36 -070029import android.os.Build;
Sumir Kataria15f793d2017-12-18 13:30:46 -080030import android.support.annotation.NonNull;
31import android.support.annotation.RestrictTo;
Rahul Ravikumarc342d152018-01-05 12:10:04 -080032import android.support.annotation.VisibleForTesting;
Sumir Kataria4b59a5f2017-12-18 09:46:37 -080033import android.text.TextUtils;
Rahul Ravikumar697d6a42018-04-16 15:44:09 -070034import android.util.Log;
Sumir Kataria4b59a5f2017-12-18 09:46:37 -080035
Sumir Kataria31099f82018-03-16 16:17:36 -070036import androidx.work.Constraints;
Sumir Kataria64e6bd82018-03-28 17:14:22 -070037import androidx.work.Data;
Sumir Kataria564e4302018-02-14 11:22:30 -080038import androidx.work.ExistingWorkPolicy;
Sumir Kataria84354e32018-03-07 15:30:16 -080039import androidx.work.State;
Rahul Ravikumar7031a0f2018-04-19 14:24:30 -070040import androidx.work.WorkRequest;
Rahul Ravikumar9f91ee82018-03-20 17:33:38 -070041import androidx.work.impl.Schedulers;
Sumir Kataria564e4302018-02-14 11:22:30 -080042import androidx.work.impl.WorkContinuationImpl;
43import androidx.work.impl.WorkDatabase;
44import androidx.work.impl.WorkManagerImpl;
Sumir Kataria564e4302018-02-14 11:22:30 -080045import androidx.work.impl.model.Dependency;
46import androidx.work.impl.model.DependencyDao;
Sumir Kataria3665b822018-03-12 16:43:01 -070047import androidx.work.impl.model.WorkName;
Sumir Kataria564e4302018-02-14 11:22:30 -080048import androidx.work.impl.model.WorkSpec;
49import androidx.work.impl.model.WorkSpecDao;
50import androidx.work.impl.model.WorkTag;
Sumir Kataria31099f82018-03-16 16:17:36 -070051import androidx.work.impl.workers.ConstraintTrackingWorker;
52
53import java.util.ArrayList;
Sumir Kataria31099f82018-03-16 16:17:36 -070054import java.util.List;
55import java.util.Set;
Sumir Kataria564e4302018-02-14 11:22:30 -080056
Sumir Kataria15f793d2017-12-18 13:30:46 -080057/**
Rahul Ravikumar87d37e52018-01-04 18:18:29 -080058 * Manages the enqueuing of a {@link WorkContinuationImpl}.
Sumir Kataria15f793d2017-12-18 13:30:46 -080059 *
60 * @hide
61 */
62@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
63public class EnqueueRunnable implements Runnable {
64
Sumir Kataria4b59a5f2017-12-18 09:46:37 -080065 private static final String TAG = "EnqueueRunnable";
66
Rahul Ravikumar87d37e52018-01-04 18:18:29 -080067 private final WorkContinuationImpl mWorkContinuation;
Sumir Kataria15f793d2017-12-18 13:30:46 -080068
Rahul Ravikumar87d37e52018-01-04 18:18:29 -080069 public EnqueueRunnable(@NonNull WorkContinuationImpl workContinuation) {
70 mWorkContinuation = workContinuation;
71 }
72
73 @Override
74 public void run() {
Rahul Ravikumar2ca6b2d2018-03-27 17:31:12 -070075 if (mWorkContinuation.hasCycles()) {
76 throw new IllegalStateException(
77 String.format("WorkContinuation has cycles (%s)", mWorkContinuation));
78 }
Rahul Ravikumar9f91ee82018-03-20 17:33:38 -070079 boolean needsScheduling = addToDatabase();
80 if (needsScheduling) {
81 scheduleWorkInBackground();
82 }
Rahul Ravikumarc342d152018-01-05 12:10:04 -080083 }
84
85 /**
86 * Adds the {@link WorkSpec}'s to the datastore, parent first.
87 * Schedules work on the background scheduler, if transaction is successful.
88 */
89 @VisibleForTesting
Rahul Ravikumar9f91ee82018-03-20 17:33:38 -070090 public boolean addToDatabase() {
Rahul Ravikumar87d37e52018-01-04 18:18:29 -080091 WorkManagerImpl workManagerImpl = mWorkContinuation.getWorkManagerImpl();
92 WorkDatabase workDatabase = workManagerImpl.getWorkDatabase();
93 workDatabase.beginTransaction();
94 try {
Rahul Ravikumar9f91ee82018-03-20 17:33:38 -070095 boolean needsScheduling = processContinuation(mWorkContinuation);
Rahul Ravikumar87d37e52018-01-04 18:18:29 -080096 workDatabase.setTransactionSuccessful();
Rahul Ravikumar9f91ee82018-03-20 17:33:38 -070097 return needsScheduling;
Rahul Ravikumar87d37e52018-01-04 18:18:29 -080098 } finally {
99 workDatabase.endTransaction();
100 }
101 }
102
Rahul Ravikumarc342d152018-01-05 12:10:04 -0800103 /**
104 * Schedules work on the background scheduler.
105 */
106 @VisibleForTesting
107 public void scheduleWorkInBackground() {
Rahul Ravikumar9f91ee82018-03-20 17:33:38 -0700108 WorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl();
109 Schedulers.schedule(workManager.getWorkDatabase(), workManager.getSchedulers());
Rahul Ravikumarc342d152018-01-05 12:10:04 -0800110 }
111
Rahul Ravikumar9f91ee82018-03-20 17:33:38 -0700112 private static boolean processContinuation(@NonNull WorkContinuationImpl workContinuation) {
Rahul Ravikumar0c252a22018-04-06 16:07:11 -0700113 boolean needsScheduling = false;
Rahul Ravikumar59cbb572018-01-10 09:41:16 -0800114 List<WorkContinuationImpl> parents = workContinuation.getParents();
115 if (parents != null) {
116 for (WorkContinuationImpl parent : parents) {
117 // When chaining off a completed continuation we need to pay
118 // attention to parents that may have been marked as enqueued before.
119 if (!parent.isEnqueued()) {
Rahul Ravikumar0c252a22018-04-06 16:07:11 -0700120 needsScheduling |= processContinuation(parent);
Rahul Ravikumar59cbb572018-01-10 09:41:16 -0800121 } else {
Rahul Ravikumar697d6a42018-04-16 15:44:09 -0700122 Log.w(TAG, String.format("Already enqueued work ids (%s).",
123 TextUtils.join(", ", parent.getIds())));
Rahul Ravikumar59cbb572018-01-10 09:41:16 -0800124 }
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800125 }
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800126 }
Rahul Ravikumar0c252a22018-04-06 16:07:11 -0700127 needsScheduling |= enqueueContinuation(workContinuation);
128 return needsScheduling;
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800129 }
130
Rahul Ravikumar9f91ee82018-03-20 17:33:38 -0700131 private static boolean enqueueContinuation(@NonNull WorkContinuationImpl workContinuation) {
Rahul Ravikumar2ca6b2d2018-03-27 17:31:12 -0700132 Set<String> prerequisiteIds = WorkContinuationImpl.prerequisitesFor(workContinuation);
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800133
Rahul Ravikumar9f91ee82018-03-20 17:33:38 -0700134 boolean needsScheduling = enqueueWorkWithPrerequisites(
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800135 workContinuation.getWorkManagerImpl(),
136 workContinuation.getWork(),
Rahul Ravikumar59cbb572018-01-10 09:41:16 -0800137 prerequisiteIds.toArray(new String[0]),
Sumir Kataria3665b822018-03-12 16:43:01 -0700138 workContinuation.getName(),
Rahul Ravikumar9f91ee82018-03-20 17:33:38 -0700139 workContinuation.getExistingWorkPolicy());
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800140
141 workContinuation.markEnqueued();
Rahul Ravikumar9f91ee82018-03-20 17:33:38 -0700142 return needsScheduling;
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800143 }
144
145 /**
146 * Enqueues the {@link WorkSpec}'s while keeping track of the prerequisites.
Rahul Ravikumar9f91ee82018-03-20 17:33:38 -0700147 *
148 * @return {@code true} If there is any scheduling to be done.
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800149 */
Rahul Ravikumar9f91ee82018-03-20 17:33:38 -0700150 private static boolean enqueueWorkWithPrerequisites(
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800151 WorkManagerImpl workManagerImpl,
Rahul Ravikumar7031a0f2018-04-19 14:24:30 -0700152 @NonNull List<? extends WorkRequest> workList,
Sumir Kataria4b59a5f2017-12-18 09:46:37 -0800153 String[] prerequisiteIds,
Sumir Kataria3665b822018-03-12 16:43:01 -0700154 String name,
Rahul Ravikumar9f91ee82018-03-20 17:33:38 -0700155 ExistingWorkPolicy existingWorkPolicy) {
Sumir Kataria15f793d2017-12-18 13:30:46 -0800156
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800157 long currentTimeMillis = System.currentTimeMillis();
158 WorkDatabase workDatabase = workManagerImpl.getWorkDatabase();
159
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800160 boolean hasPrerequisite = (prerequisiteIds != null && prerequisiteIds.length > 0);
161 boolean hasCompletedAllPrerequisites = true;
Sumir Kataria84354e32018-03-07 15:30:16 -0800162 boolean hasFailedPrerequisites = false;
163 boolean hasCancelledPrerequisites = false;
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800164
165 if (hasPrerequisite) {
166 // If there are prerequisites, make sure they actually exist before enqueuing
167 // anything. Prerequisites may not exist if we are using unique tags, because the
168 // chain of work could have been wiped out already.
169 for (String id : prerequisiteIds) {
170 WorkSpec prerequisiteWorkSpec = workDatabase.workSpecDao().getWorkSpec(id);
171 if (prerequisiteWorkSpec == null) {
Rahul Ravikumar697d6a42018-04-16 15:44:09 -0700172 Log.e(TAG, String.format("Prerequisite %s doesn't exist; not enqueuing", id));
Rahul Ravikumar9f91ee82018-03-20 17:33:38 -0700173 return false;
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800174 }
Sumir Kataria84354e32018-03-07 15:30:16 -0800175
Sumir Katariab5728f42018-03-19 12:58:41 -0700176 State prerequisiteState = prerequisiteWorkSpec.state;
Sumir Kataria84354e32018-03-07 15:30:16 -0800177 hasCompletedAllPrerequisites &= (prerequisiteState == SUCCEEDED);
178 if (prerequisiteState == FAILED) {
179 hasFailedPrerequisites = true;
180 } else if (prerequisiteState == CANCELLED) {
181 hasCancelledPrerequisites = true;
182 }
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800183 }
184 }
185
Sumir Kataria3665b822018-03-12 16:43:01 -0700186 boolean isNamed = !TextUtils.isEmpty(name);
Sumir Kataria5a04d6c2018-02-01 11:21:24 -0800187
188 // We only apply existing work policies for unique tag sequences that are the beginning of
189 // chains.
Sumir Kataria3665b822018-03-12 16:43:01 -0700190 boolean shouldApplyExistingWorkPolicy = isNamed && !hasPrerequisite;
Sumir Kataria5a04d6c2018-02-01 11:21:24 -0800191 if (shouldApplyExistingWorkPolicy) {
192 // Get everything with the unique tag.
Sumir Kataria9cf4f3d2018-02-06 09:57:51 -0800193 List<WorkSpec.IdAndState> existingWorkSpecIdAndStates =
Sumir Kataria3665b822018-03-12 16:43:01 -0700194 workDatabase.workSpecDao().getWorkSpecIdAndStatesForName(name);
Sumir Kataria5a04d6c2018-02-01 11:21:24 -0800195
Sumir Kataria9cf4f3d2018-02-06 09:57:51 -0800196 if (!existingWorkSpecIdAndStates.isEmpty()) {
Sumir Kataria5a04d6c2018-02-01 11:21:24 -0800197 // If appending, these are the new prerequisites.
Sumir Kataria8812a252018-02-02 12:40:07 -0800198 if (existingWorkPolicy == APPEND) {
Sumir Kataria5a04d6c2018-02-01 11:21:24 -0800199 DependencyDao dependencyDao = workDatabase.dependencyDao();
200 List<String> newPrerequisiteIds = new ArrayList<>();
Sumir Kataria9cf4f3d2018-02-06 09:57:51 -0800201 for (WorkSpec.IdAndState idAndState : existingWorkSpecIdAndStates) {
202 if (!dependencyDao.hasDependents(idAndState.id)) {
203 hasCompletedAllPrerequisites &= (idAndState.state == SUCCEEDED);
Sumir Kataria84354e32018-03-07 15:30:16 -0800204 if (idAndState.state == FAILED) {
205 hasFailedPrerequisites = true;
206 } else if (idAndState.state == CANCELLED) {
207 hasCancelledPrerequisites = true;
208 }
Sumir Kataria9cf4f3d2018-02-06 09:57:51 -0800209 newPrerequisiteIds.add(idAndState.id);
Sumir Katariab54385f2018-01-26 13:40:22 -0800210 }
211 }
Sumir Kataria5a04d6c2018-02-01 11:21:24 -0800212 prerequisiteIds = newPrerequisiteIds.toArray(prerequisiteIds);
213 hasPrerequisite = (prerequisiteIds.length > 0);
214 } else {
215 // If we're keeping existing work, make sure to do so only if something is
216 // enqueued or running.
Sumir Kataria8812a252018-02-02 12:40:07 -0800217 if (existingWorkPolicy == KEEP) {
Sumir Kataria9cf4f3d2018-02-06 09:57:51 -0800218 for (WorkSpec.IdAndState idAndState : existingWorkSpecIdAndStates) {
219 if (idAndState.state == ENQUEUED || idAndState.state == RUNNING) {
Rahul Ravikumar9f91ee82018-03-20 17:33:38 -0700220 return false;
Sumir Kataria5a04d6c2018-02-01 11:21:24 -0800221 }
222 }
223 }
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800224
Sumir Kataria5a04d6c2018-02-01 11:21:24 -0800225 // Cancel all of these workers.
Sumir Kataria3665b822018-03-12 16:43:01 -0700226 CancelWorkRunnable.forName(name, workManagerImpl).run();
Sumir Kataria5a04d6c2018-02-01 11:21:24 -0800227 // And delete all the database records.
228 WorkSpecDao workSpecDao = workDatabase.workSpecDao();
Sumir Kataria9cf4f3d2018-02-06 09:57:51 -0800229 for (WorkSpec.IdAndState idAndState : existingWorkSpecIdAndStates) {
230 workSpecDao.delete(idAndState.id);
Sumir Kataria5a04d6c2018-02-01 11:21:24 -0800231 }
Sumir Katariab54385f2018-01-26 13:40:22 -0800232 }
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800233 }
234 }
235
Rahul Ravikumar9f91ee82018-03-20 17:33:38 -0700236 boolean needsScheduling = false;
Rahul Ravikumar7031a0f2018-04-19 14:24:30 -0700237 for (WorkRequest work : workList) {
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800238 WorkSpec workSpec = work.getWorkSpec();
239
240 if (hasPrerequisite && !hasCompletedAllPrerequisites) {
Sumir Kataria84354e32018-03-07 15:30:16 -0800241 if (hasFailedPrerequisites) {
Sumir Katariab5728f42018-03-19 12:58:41 -0700242 workSpec.state = FAILED;
Sumir Kataria84354e32018-03-07 15:30:16 -0800243 } else if (hasCancelledPrerequisites) {
Sumir Katariab5728f42018-03-19 12:58:41 -0700244 workSpec.state = CANCELLED;
Sumir Kataria84354e32018-03-07 15:30:16 -0800245 } else {
Sumir Katariab5728f42018-03-19 12:58:41 -0700246 workSpec.state = BLOCKED;
Sumir Kataria84354e32018-03-07 15:30:16 -0800247 }
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800248 } else {
249 // Set scheduled times only for work without prerequisites. Dependent work
250 // will set their scheduled times when they are unblocked.
Sumir Katariab5728f42018-03-19 12:58:41 -0700251 workSpec.periodStartTime = currentTimeMillis;
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800252 }
253
Sumir Kataria31099f82018-03-16 16:17:36 -0700254 if (Build.VERSION.SDK_INT >= 23 && Build.VERSION.SDK_INT <= 25) {
255 tryDelegateConstrainedWorkSpec(workSpec);
256 }
257
Rahul Ravikumar9f91ee82018-03-20 17:33:38 -0700258 // If we have one WorkSpec with an enqueued state, then we need to schedule.
259 if (workSpec.state == ENQUEUED) {
260 needsScheduling = true;
261 }
262
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800263 workDatabase.workSpecDao().insertWorkSpec(workSpec);
Sumir Kataria15f793d2017-12-18 13:30:46 -0800264
Sumir Kataria4b59a5f2017-12-18 09:46:37 -0800265 if (hasPrerequisite) {
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800266 for (String prerequisiteId : prerequisiteIds) {
Sumir Katariafa284c92018-04-23 14:25:53 -0700267 Dependency dep = new Dependency(work.getStringId(), prerequisiteId);
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800268 workDatabase.dependencyDao().insertDependency(dep);
Sumir Kataria4b59a5f2017-12-18 09:46:37 -0800269 }
270 }
271
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800272 for (String tag : work.getTags()) {
Sumir Katariafa284c92018-04-23 14:25:53 -0700273 workDatabase.workTagDao().insert(new WorkTag(tag, work.getStringId()));
Sumir Kataria4b59a5f2017-12-18 09:46:37 -0800274 }
275
Sumir Kataria3665b822018-03-12 16:43:01 -0700276 if (isNamed) {
Sumir Katariafa284c92018-04-23 14:25:53 -0700277 workDatabase.workNameDao().insert(new WorkName(name, work.getStringId()));
Sumir Kataria15f793d2017-12-18 13:30:46 -0800278 }
Rahul Ravikumar87d37e52018-01-04 18:18:29 -0800279 }
Rahul Ravikumar9f91ee82018-03-20 17:33:38 -0700280 return needsScheduling;
Sumir Kataria31099f82018-03-16 16:17:36 -0700281 }
282
283 private static void tryDelegateConstrainedWorkSpec(WorkSpec workSpec) {
284 // requiresBatteryNotLow and requiresStorageNotLow require API 26 for JobScheduler.
285 // Delegate to ConstraintTrackingWorker between API 23-25.
Sumir Katariab5728f42018-03-19 12:58:41 -0700286 Constraints constraints = workSpec.constraints;
Sumir Kataria31099f82018-03-16 16:17:36 -0700287 if (constraints.requiresBatteryNotLow() || constraints.requiresStorageNotLow()) {
Sumir Katariab5728f42018-03-19 12:58:41 -0700288 String workerClassName = workSpec.workerClassName;
Sumir Kataria64e6bd82018-03-28 17:14:22 -0700289 Data.Builder builder = new Data.Builder();
Sumir Kataria31099f82018-03-16 16:17:36 -0700290 // Copy all arguments
Sumir Kataria64e6bd82018-03-28 17:14:22 -0700291 builder.putAll(workSpec.input)
Sumir Kataria31099f82018-03-16 16:17:36 -0700292 .putString(ARGUMENT_CLASS_NAME, workerClassName);
Sumir Katariab5728f42018-03-19 12:58:41 -0700293 workSpec.workerClassName = ConstraintTrackingWorker.class.getName();
Sumir Kataria64e6bd82018-03-28 17:14:22 -0700294 workSpec.input = builder.build();
Sumir Kataria15f793d2017-12-18 13:30:46 -0800295 }
296 }
297}